Skip to content
This repository has been archived by the owner on Mar 8, 2022. It is now read-only.

set-oracle-data bot #190

Merged
merged 4 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/module.playground/_module.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ it('should have tokens setup', async () => {

it('should have oracles setup', async () => {
const oracles = await client.oracle.listOracles()
expect(Object.values(oracles).length).toBe(3)
expect(Object.values(oracles).length).toBe(4)
})

it('should have masternode setup', async () => {
Expand Down
2 changes: 2 additions & 0 deletions src/module.playground/_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PlaygroundSetup } from '@src/module.playground/setup/setup'
import { GenesisKeys } from '@defichain/testcontainers'
import { SetupMasternode } from '@src/module.playground/setup/setup.masternode'
import { SetupUtxo } from '@src/module.playground/setup/setup.utxo'
import { OracleBot } from './bot/oracle.bot'

@Global()
@Module({
Expand All @@ -18,6 +19,7 @@ import { SetupUtxo } from '@src/module.playground/setup/setup.utxo'
SetupDex,
SetupOracle,
SetupMasternode,
OracleBot,
PlaygroundBlock,
PlaygroundProbeIndicator
],
Expand Down
53 changes: 53 additions & 0 deletions src/module.playground/bot/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import { Injectable } from '@nestjs/common'
import { GenesisKeys } from '@defichain/testcontainers'
import { Interval } from '@nestjs/schedule'

@Injectable()
export abstract class PlaygroundBot<Each> {
static MN_KEY = GenesisKeys[0]

/**
* @return {string} address that should be used for everything
*/
static get address (): string {
return PlaygroundBot.MN_KEY.operator.address
}

/**
* @return {string} privKey that should be used for everything
*/
static get privKey (): string {
return PlaygroundBot.MN_KEY.operator.privKey
}

constructor (protected readonly client: JsonRpcClient) {
}

@Interval(3000)
async runAll (): Promise<void> {
const list = this.list()
for (const each of list) {
if (!await this.has(each)) {
continue
}

await this.run(each)
}
}

/**
* @return {Each[]} to run
*/
abstract list (): Each[]

/**
* @param {Each} each to run
*/
abstract run (each: Each): Promise<void>

/**
* @param {Each} each to check if it exists so we can run.
*/
abstract has (each: Each): Promise<boolean>
monstrobishi marked this conversation as resolved.
Show resolved Hide resolved
}
188 changes: 188 additions & 0 deletions src/module.playground/bot/oracle.bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { Injectable } from '@nestjs/common'
import { GenesisKeys } from '@defichain/testcontainers'
import { BigNumber } from '@defichain/jellyfish-json'
import { PlaygroundBot } from './bot'
import { SetupOracle } from '../setup/setup.oracle'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'

enum PriceDirection {
UP, // Always going up
DOWN, // Always going down
RANDOM, // Randomly goes up or down (minimum $1)
STABLE // Fixed value
}

type PriceDirectionFunction = (price: BigNumber, priceChange: BigNumber) => BigNumber

const PriceDirectionFunctions: Record<PriceDirection, PriceDirectionFunction> = {
[PriceDirection.UP]: (price: BigNumber, priceChange: BigNumber) => {
return price.plus(priceChange)
},
[PriceDirection.DOWN]: (price: BigNumber, priceChange: BigNumber) => {
return price.minus(priceChange)
},
[PriceDirection.RANDOM]: (price: BigNumber, priceChange: BigNumber) => {
return BigNumber.random().gt(0.5)
? price.plus(priceChange)
: BigNumber.max(price.minus(priceChange), 1)
},
[PriceDirection.STABLE]: (price: BigNumber, priceChange: BigNumber) => {
return price
}
}

interface SimulatedOracleFeed {
token: string
startingPrice: BigNumber
priceChange: BigNumber
timeInterval: number
priceDirection: PriceDirection
}

@Injectable()
export class OracleBot extends PlaygroundBot<SimulatedOracleFeed> {
oracleOwnerAddress: string = GenesisKeys[7].operator.address

constructor (
protected readonly client: JsonRpcClient,
protected readonly setupOracle: SetupOracle) {
super(client)
}

list (): SimulatedOracleFeed[] {
return [
{
token: 'U25',
startingPrice: new BigNumber(100),
priceChange: new BigNumber(25),
timeInterval: 15000,
priceDirection: PriceDirection.UP
},
{
token: 'U50',
startingPrice: new BigNumber(100),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.UP
},
{
token: 'U100',
startingPrice: new BigNumber(100),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.UP
},
{
token: 'D25',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(25),
monstrobishi marked this conversation as resolved.
Show resolved Hide resolved
timeInterval: 15000,
priceDirection: PriceDirection.DOWN
},
{
token: 'D50',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.DOWN
},
{
token: 'D100',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.DOWN
},
{
token: 'S25',
startingPrice: new BigNumber(25),
priceChange: new BigNumber(0),
timeInterval: 15000,
priceDirection: PriceDirection.STABLE
},
{
token: 'S50',
startingPrice: new BigNumber(50),
priceChange: new BigNumber(0),
timeInterval: 15000,
priceDirection: PriceDirection.STABLE
},
{
token: 'S100',
startingPrice: new BigNumber(100),
priceChange: new BigNumber(0),
timeInterval: 15000,
priceDirection: PriceDirection.STABLE
},
{
token: 'R25',
startingPrice: new BigNumber(5000),
priceChange: new BigNumber(25),
timeInterval: 15000,
priceDirection: PriceDirection.RANDOM
},
{
token: 'R50',
startingPrice: new BigNumber(5000),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.RANDOM
},
{
token: 'R100',
startingPrice: new BigNumber(5000),
priceChange: new BigNumber(100),
timeInterval: 15000,
priceDirection: PriceDirection.RANDOM
}
]
}

private async sendInitial (oracleId: string, medianTime: number, each: SimulatedOracleFeed): Promise<void> {
await this.client.oracle.setOracleData(oracleId, medianTime, {
prices: [
{
tokenAmount: `${each.startingPrice.toFixed(8)}@${each.token}`,
currency: 'USD'
}
]
})
}

async run (each: SimulatedOracleFeed): Promise<void> {
const oracleIds = this.setupOracle.oracleIds[each.token]
const blockchainInfo = await this.client.blockchain.getBlockchainInfo()
const medianTime = blockchainInfo.mediantime
for (const oracleId of oracleIds) {
const oracleData = await this.client.oracle.getOracleData(oracleId)
const tokenPrice = oracleData.tokenPrices.find(x => x.token === each.token)

if (tokenPrice === undefined) {
await this.sendInitial(oracleId, medianTime, each)
continue
}

if (medianTime - tokenPrice.timestamp < (each.timeInterval / 1000)) {
continue
}

const price = new BigNumber(tokenPrice.amount)
const priceFunction = PriceDirectionFunctions[each.priceDirection]
const newPrice = priceFunction(price, each.priceChange)

await this.client.oracle.setOracleData(oracleId, medianTime, {
prices: [
{
tokenAmount: `${newPrice.toFixed(8)}@${each.token}`,
currency: 'USD'
}
]
})
}
}

async has (each: SimulatedOracleFeed): Promise<boolean> {
const oracleIds = this.setupOracle.oracleIds[each.token]
return oracleIds !== undefined
}
}
66 changes: 64 additions & 2 deletions src/module.playground/setup/setup.oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface OracleSetup {
@Injectable()
export class SetupOracle extends PlaygroundSetup<OracleSetup> {
oracleOwnerAddress: string = GenesisKeys[7].operator.address
oracleIds: Record<string, string[]> = {}

list (): OracleSetup[] {
return [
Expand Down Expand Up @@ -74,19 +75,80 @@ export class SetupOracle extends PlaygroundSetup<OracleSetup> {
options: {
weightage: 1
}
},
{
address: this.oracleOwnerAddress,
priceFeeds: [
{
token: 'U25',
currency: 'USD'
},
{
token: 'U50',
currency: 'USD'
},
{
token: 'U100',
currency: 'USD'
},
{
token: 'D25',
currency: 'USD'
},
{
token: 'D50',
currency: 'USD'
},
{
token: 'D100',
currency: 'USD'
},
{
token: 'R25',
currency: 'USD'
},
{
token: 'R50',
currency: 'USD'
},
{
token: 'R100',
currency: 'USD'
},
{
token: 'S25',
currency: 'USD'
},
{
token: 'S50',
currency: 'USD'
},
{
token: 'S100',
currency: 'USD'
}
],
options: {
weightage: 1
}
}
]
}

async create (each: OracleSetup): Promise<void> {
await this.waitForBalance(101)
await this.client.oracle.appointOracle(each.address, each.priceFeeds, each.options)
const oracleId = await this.client.oracle.appointOracle(each.address, each.priceFeeds, each.options)

for (const { token } of each.priceFeeds) {
this.oracleIds[token] = [...(this.oracleIds[token] ?? []), oracleId]
}

await this.generate(1)
}

async has (each: OracleSetup): Promise<boolean> {
try {
return (await this.client.oracle.listOracles()).length >= 3
return (await this.client.oracle.listOracles()).length >= this.list().length
} catch (e) {
return false
}
Expand Down