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

Commit

Permalink
set-oracle-data bot (#190)
Browse files Browse the repository at this point in the history
* Added bot

* Fix oracle ids

* Fix oracles test

* PR suggestions and test
  • Loading branch information
monstrobishi authored Sep 30, 2021
1 parent 3df67e2 commit 03dbfda
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 4 deletions.
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>
}
65 changes: 65 additions & 0 deletions src/module.playground/bot/oracle.bot.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { createTestingApp, stopTestingApp } from '@src/e2e.module'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import waitForExpect from 'wait-for-expect'
import { SetupOracle } from '../setup/setup.oracle'

const container = new MasterNodeRegTestContainer()
let client: JsonRpcClient
let app: NestFastifyApplication

beforeAll(async () => {
await container.start()
await container.waitForWalletCoinbaseMaturity()
app = await createTestingApp(container)
client = new JsonRpcClient(await container.getCachedRpcUrl())
})

afterAll(async () => {
await stopTestingApp(container, app)
})

describe('oracle bot', () => {
it('should change oracle price', async () => {
const oracles = await client.oracle.listOracles()
expect(Object.values(oracles).length).toBe(4)

// Wait for block 157
await waitForExpect(async () => {
const blocks = await client.blockchain.getBlockCount()
expect(blocks).toBeGreaterThan(157)
}, 200000)

const setupOracle = app.get(SetupOracle)
const oracleIds = setupOracle.oracleIds.U10
const oracleData = await client.oracle.getOracleData(oracleIds[0])

const tokenPriceU10 = oracleData.tokenPrices.find(x => x.token === 'U10')
expect(tokenPriceU10?.amount).toStrictEqual(11)

const tokenPriceU25 = oracleData.tokenPrices.find(x => x.token === 'U25')
expect(tokenPriceU25?.amount).toStrictEqual(12.5)

const tokenPriceU50 = oracleData.tokenPrices.find(x => x.token === 'U50')
expect(tokenPriceU50?.amount).toStrictEqual(15)

const tokenPriceD10 = oracleData.tokenPrices.find(x => x.token === 'D10')
expect(tokenPriceD10?.amount).toStrictEqual(9000000)

const tokenPriceD25 = oracleData.tokenPrices.find(x => x.token === 'D25')
expect(tokenPriceD25?.amount).toStrictEqual(7500000)

const tokenPriceD50 = oracleData.tokenPrices.find(x => x.token === 'D50')
expect(tokenPriceD50?.amount).toStrictEqual(5000000)

const tokenPriceS25 = oracleData.tokenPrices.find(x => x.token === 'S25')
expect(tokenPriceS25?.amount).toStrictEqual(25)

const tokenPriceS50 = oracleData.tokenPrices.find(x => x.token === 'S50')
expect(tokenPriceS50?.amount).toStrictEqual(50)

const tokenPriceS100 = oracleData.tokenPrices.find(x => x.token === 'S100')
expect(tokenPriceS100?.amount).toStrictEqual(100)
})
})
212 changes: 212 additions & 0 deletions src/module.playground/bot/oracle.bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
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'

/**
* The following symbols are automated by this bot:
* - U10: Goes up every 15 seconds by 10%
* - U25: Goes up every 15 seconds by 25%
* - U50: Goes up every 15 seconds by 50%
* - D10: Goes down every 15 seconds by 10%
* - D25: Goes down every 15 seconds by 25%
* - D50: Goes down every 15 seconds by 50%
* - S25: Is always $25
* - S50: Is always $50
* - S100: Is always $100
* - R25: Goes down every 15 seconds by $25
* - R50: Goes down every 15 seconds by $50
* - R100: Goes down every 15 seconds by $100
*/

enum PriceDirection {
UP_ABSOLUTE, // Always going up (absolute value)
DOWN_ABSOLUTE, // Always going down (absolute value)
UP_PERCENTAGE, // Always going up (percentage value)
DOWN_PERCENTAGE, // Always going down (percentage value)
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_ABSOLUTE]: (price: BigNumber, priceChange: BigNumber) => {
return price.plus(priceChange)
},
[PriceDirection.DOWN_ABSOLUTE]: (price: BigNumber, priceChange: BigNumber) => {
return price.minus(priceChange)
},
[PriceDirection.UP_PERCENTAGE]: (price: BigNumber, priceChange: BigNumber) => {
return price.plus(price.times(priceChange.dividedBy(100)))
},
[PriceDirection.DOWN_PERCENTAGE]: (price: BigNumber, priceChange: BigNumber) => {
return price.minus(price.times(priceChange.dividedBy(100)))
},
[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[0].owner.address

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

list (): SimulatedOracleFeed[] {
return [
{
token: 'U10',
startingPrice: new BigNumber(10),
priceChange: new BigNumber(10),
timeInterval: 15000,
priceDirection: PriceDirection.UP_PERCENTAGE
},
{
token: 'U25',
startingPrice: new BigNumber(10),
priceChange: new BigNumber(25),
timeInterval: 15000,
priceDirection: PriceDirection.UP_PERCENTAGE
},
{
token: 'U50',
startingPrice: new BigNumber(10),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.UP_PERCENTAGE
},
{
token: 'D10',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(10),
timeInterval: 15000,
priceDirection: PriceDirection.DOWN_PERCENTAGE
},
{
token: 'D25',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(25),
timeInterval: 15000,
priceDirection: PriceDirection.DOWN_PERCENTAGE
},
{
token: 'D50',
startingPrice: new BigNumber(10000000),
priceChange: new BigNumber(50),
timeInterval: 15000,
priceDirection: PriceDirection.DOWN_PERCENTAGE
},
{
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
}
}
Loading

0 comments on commit 03dbfda

Please sign in to comment.