diff --git a/packages/jellyfish-api-core/__tests__/category/oracle/updateOracle.test.ts b/packages/jellyfish-api-core/__tests__/category/oracle/updateOracle.test.ts new file mode 100644 index 0000000000..c93cec2d2c --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/oracle/updateOracle.test.ts @@ -0,0 +1,232 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '../../../src' +import { UTXO } from '../../../src/category/oracle' + +describe('Oracle', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await container.waitForReady() + await container.waitForWalletCoinbaseMaturity() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should updateOracle', async () => { + // Appoint oracle + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [await container.getNewAddress(), appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: 'FB', currency: 'CNY' }, + { token: 'MSFT', currency: 'SGD' } + ] + + await client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2 + }) + + await container.generate(1) + + const data = await container.call('getoracledata', [oracleid]) + + expect(data).toStrictEqual({ + weightage: 2, + oracleid, + address: expect.any(String), + priceFeeds: updateOraclePriceFeeds, + tokenPrices: [] + }) + }) + + it('should not updateOracle for invalid oracleid', async () => { + const oracleid = '8430ac5711d78dc6f98591e144916d27f80952271c62cc15410f878d9b08300d' + + const priceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const promise = client.oracle.updateOracle(oracleid, await container.getNewAddress(), { priceFeeds, weightage: 1 }) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow(`RpcApiError: 'Test UpdateOracleAppointTx execution failed:\noracle <${oracleid as string}> not found', code: -32600, method: updateoracle`) + }) + + it('should updateOracle using same tokens and currencies', async () => { + // Appoint oracle + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [await container.getNewAddress(), appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: 'CNY', currency: 'FB' }, + { token: 'CNY', currency: 'FB' } + ] + + await client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2 + }) + + await container.generate(1) + + const data = await container.call('getoracledata', [oracleid]) + + expect(data.priceFeeds).toStrictEqual([{ token: 'CNY', currency: 'FB' }]) + }) + + it('should updateOracle using random tokens and currencies with 1 letter', async () => { + // Appoint oracle + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [await container.getNewAddress(), appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: 'A', currency: 'C' }, + { token: 'B', currency: 'D' } + ] + + await client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2 + }) + + await container.generate(1) + + const data = await container.call('getoracledata', [oracleid]) + + expect(data.priceFeeds).toStrictEqual(updateOraclePriceFeeds) + }) + + it('should updateOracle using random tokens and currencies with 15 letters', async () => { + // Appoint oracle + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [await container.getNewAddress(), appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: '123456789012345', currency: '123456789012345' }, + { token: 'ABCDEFGHIJKLMNO', currency: 'ABCDEFGHIJKLMNO' } + ] + + await client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2 + }) + + await container.generate(1) + + const data = await container.call('getoracledata', [oracleid]) + + expect(data.priceFeeds).toStrictEqual([ + { token: '12345678', currency: '12345678' }, + { token: 'ABCDEFGH', currency: 'ABCDEFGH' } + ]) + }) + + it('should updateOracle with utxos', async () => { + // Appoint oracle + const address = await container.getNewAddress() + + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [address, appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: 'FB', currency: 'CNY' }, + { token: 'MSFT', currency: 'SGD' } + ] + + const utxos = await container.call('listunspent', [1, 9999999, [address], true]) + const inputs: UTXO[] = utxos.map((utxo: UTXO) => { + return { + txid: utxo.txid, + vout: utxo.vout + } + }) + + await client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2, + utxos: inputs + }) + + await container.generate(1) + + const data = await container.call('getoracledata', [oracleid]) + + expect(data).toStrictEqual({ + weightage: 2, + oracleid, + address: expect.any(String), + priceFeeds: updateOraclePriceFeeds, + tokenPrices: [] + }) + }) + + it('should not updateOracle with arbitrary utxos', async () => { + // Appoint oracle + const appointOraclePriceFeeds = [ + { token: 'APPLE', currency: 'EUR' }, + { token: 'TESLA', currency: 'USD' } + ] + + const oracleid = await container.call('appointoracle', [await container.getNewAddress(), appointOraclePriceFeeds, 1]) + + await container.generate(1) + + // Update oracle + const updateOraclePriceFeeds = [ + { token: 'FB', currency: 'CNY' }, + { token: 'MSFT', currency: 'SGD' } + ] + + const { txid, vout } = await container.fundAddress(await container.getNewAddress(), 10) + const promise = client.oracle.updateOracle(oracleid, await container.getNewAddress(), { + priceFeeds: updateOraclePriceFeeds, + weightage: 2, + utxos: [{ txid, vout }] + }) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow('RpcApiError: \'Test UpdateOracleAppointTx execution failed:\ntx not from foundation member\', code: -32600, method: updateoracle') + }) +}) diff --git a/packages/jellyfish-api-core/src/category/oracle.ts b/packages/jellyfish-api-core/src/category/oracle.ts index 72b30e35be..10bda71a17 100644 --- a/packages/jellyfish-api-core/src/category/oracle.ts +++ b/packages/jellyfish-api-core/src/category/oracle.ts @@ -20,7 +20,7 @@ export class Oracle { * @param {UTXO[]} [options.utxos = []] * @param {string} [options.utxos.txid] * @param {number} [options.utxos.vout] - * @return {Promise} oracleid + * @return {Promise} oracleid, also the txn id for txn created to appoint oracle */ async appointOracle (address: string, priceFeeds: PriceFeed[], options: AppointOracleOptions = {}): Promise { const { utxos = [] } = options @@ -34,16 +34,29 @@ export class Oracle { * @param {UTXO[]} [utxos = []] * @param {string} [utxos.txid] * @param {number} [utxos.vout] - * @return {Promise} oracleid + * @return {Promise} txid */ async removeOracle (oracleid: string, utxos: UTXO[] = []): Promise { return await this.client.call('removeoracle', [oracleid, utxos], 'number') } -} -export interface PriceFeed { - currency: string - token: string + /** + * Update a price oracle for rely of real time price data. + * + * @param {string} oracleid + * @param {string} address + * @param {UpdateOracleOptions} [options] + * @param {PriceFeed[]} options.priceFeeds + * @param {number} options.weightage + * @param {UTXO[]} [options.utxos = []] + * @param {string} [options.utxos.txid] + * @param {number} [options.utxos.vout] + * @return {Promise} txid + */ + async updateOracle (oracleid: string, address: string, options: UpdateOracleOptions = {}): Promise { + const { utxos = [] } = options + return await this.client.call('updateoracle', [oracleid, address, options.priceFeeds, options.weightage, utxos], 'number') + } } export interface AppointOracleOptions { @@ -51,6 +64,17 @@ export interface AppointOracleOptions { utxos?: UTXO[] } +export interface UpdateOracleOptions { + priceFeeds?: PriceFeed[] + weightage?: number + utxos?: UTXO[] +} + +export interface PriceFeed { + currency: string + token: string +} + export interface UTXO { txid: string vout: number diff --git a/website/docs/jellyfish/api/oracle.md b/website/docs/jellyfish/api/oracle.md index 08020842c6..3a04a3b8e3 100644 --- a/website/docs/jellyfish/api/oracle.md +++ b/website/docs/jellyfish/api/oracle.md @@ -52,3 +52,29 @@ interface UTXO { vout: number } ``` + +## updateOracle + +Update a price oracle for rely of real time price data. + +```ts title="client.oracle.updateOracle()" +interface oracle { + updateOracle (oracleid: string, address: string, options: UpdateOracleOptions = {}): Promise +} + +interface UpdateOracleOptions { + priceFeeds?: PriceFeed[] + weightage?: number + utxos?: UTXO[] +} + +interface PriceFeed { + currency: string + token: string +} + +interface UTXO { + txid: string + vout: number +} +```