From 7d53b544ba10dbaf91c4783a77927740da55b937 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 16 Jan 2023 15:12:49 -0600 Subject: [PATCH 1/3] Add updProduct encoder/decoder --- src/__tests__/Anchor.test.ts | 10 ++++-- src/anchor/coder/instructions.ts | 55 ++++++++++++++++++++++++++++---- src/anchor/idl.json | 18 ++++++++++- src/anchor/program.ts | 18 ++++++++++- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/__tests__/Anchor.test.ts b/src/__tests__/Anchor.test.ts index 29c2b57..e7ece07 100644 --- a/src/__tests__/Anchor.test.ts +++ b/src/__tests__/Anchor.test.ts @@ -32,14 +32,18 @@ test('Anchor', (done) => { expect(decoded?.data).toStrictEqual({}) }) pythOracle.methods - .updProduct() + .updProduct({ asset_type: 'Crypto', base: 'BTC', description : "BTC/USD", quote_currency :"USD", symbol : "Crypto.BTC/USD", generic_symbol : "BTCUSD" }) .accounts({ fundingAccount: PublicKey.unique(), productAccount: PublicKey.unique() }) .instruction() .then((instruction) => { - expect(instruction.data).toStrictEqual(Buffer.from([2, 0, 0, 0, 3, 0, 0, 0])) const decoded = pythOracleCoder().instruction.decode(instruction.data) expect(decoded?.name).toBe('updProduct') - expect(decoded?.data).toStrictEqual({}) + expect(decoded?.data.asset_type).toBe("Crypto") + expect(decoded?.data.base).toBe("BTC") + expect(decoded?.data.description).toBe("BTC/USD") + expect(decoded?.data.quote_currency).toBe("USD") + expect(decoded?.data.symbol).toBe("Crypto.BTC/USD") + expect(decoded?.data.generic_symbol).toBe("BTCUSD") }) pythOracle.methods diff --git a/src/anchor/coder/instructions.ts b/src/anchor/coder/instructions.ts index 263a40b..603e58e 100644 --- a/src/anchor/coder/instructions.ts +++ b/src/anchor/coder/instructions.ts @@ -11,6 +11,7 @@ import { PublicKey } from '@solana/web3.js' import { Idl, IdlField, IdlType, IdlAccountItem } from '@coral-xyz/anchor/dist/cjs/idl' import { IdlCoder } from './idl' import { InstructionCoder } from '@coral-xyz/anchor' +import { Product } from '../..' export type PythIdlInstruction = { name: string @@ -79,14 +80,32 @@ export class PythOracleInstructionCoder implements InstructionCoder { public encode(ixName: string, ix: any): Buffer { const buffer = Buffer.alloc(1000) // TODO: use a tighter buffer. const methodName = camelCase(ixName) + const layout = this.ixLayout.get(methodName) const discriminator = this.ixDiscriminator.get(methodName) if (!layout || !discriminator) { throw new Error(`Unknown method: ${methodName}`) } - const len = layout.encode(ix, buffer) - const data = buffer.subarray(0, len) - return Buffer.concat([discriminator, data]) + + /// updProduct has it's own format + if (methodName == 'updProduct') { + let offset = 0 + for (let key of Object.keys(ix.productMetadata)) { + offset += buffer.subarray(offset).writeInt8(key.length) + offset += buffer.subarray(offset).write(key) + offset += buffer.subarray(offset).writeInt8(ix.productMetadata[key].length) + offset += buffer.subarray(offset).write(ix.productMetadata[key]) + } + if (offset > 464) { + throw new Error('The metadata is too long') + } + const data = buffer.subarray(0, offset) + return Buffer.concat([discriminator, data]) + } else { + const len = layout.encode(ix, buffer) + const data = buffer.subarray(0, len) + return Buffer.concat([discriminator, data]) + } } private static parseIxLayout(idl: Idl): Map { @@ -114,9 +133,33 @@ export class PythOracleInstructionCoder implements InstructionCoder { if (!decoder) { return null } - return { - data: decoder.layout.decode(data), - name: decoder.name, + + /// updProduct has it's own format + if (decoder.name == 'updProduct') { + const product: Product = {} + let idx = 0 + while (idx < data.length) { + const keyLength = data[idx] + idx++ + if (keyLength) { + const key = data.slice(idx, idx + keyLength).toString() + idx += keyLength + const valueLength = data[idx] + idx++ + const value = data.slice(idx, idx + valueLength).toString() + idx += valueLength + product[key] = value + } + } + return { + data: product, + name: decoder.name, + } + } else { + return { + data: decoder.layout.decode(data), + name: decoder.name, + } } } } diff --git a/src/anchor/idl.json b/src/anchor/idl.json index 21c33b6..27ecb66 100644 --- a/src/anchor/idl.json +++ b/src/anchor/idl.json @@ -134,7 +134,14 @@ } } ], - "args": [] + "args": [ + { + "name": "productMetadata", + "type": { + "defined": "ProductMetadata" + } + } + ] }, { "name": "addPrice", @@ -585,5 +592,14 @@ } ] } + ], + "types": [ + { + "name": "ProductMetadata", + "type": { + "kind": "struct", + "fields": [] + } + } ] } diff --git a/src/anchor/program.ts b/src/anchor/program.ts index b1bd860..7032376 100644 --- a/src/anchor/program.ts +++ b/src/anchor/program.ts @@ -154,7 +154,14 @@ export type PythOracle = { } }, ] - args: [] + args: [ + { + name: 'productMetadata' + type: { + defined: 'ProductMetadata' + } + }, + ] }, { name: 'addPrice' @@ -606,4 +613,13 @@ export type PythOracle = { ] }, ] + types: [ + { + name: 'ProductMetadata' + type: { + kind: 'struct' + fields: [] + } + }, + ] } From cbd6cda3433bcb95a0dbcfae31f66317fbcd5ab1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 16 Jan 2023 15:14:57 -0600 Subject: [PATCH 2/3] Lint --- jestconfig.json | 2 +- src/anchor/coder/instructions.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jestconfig.json b/jestconfig.json index e1bb70c..f41c403 100644 --- a/jestconfig.json +++ b/jestconfig.json @@ -2,6 +2,6 @@ "transform": { "^.+\\.(t|j)sx?$": "ts-jest" }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", + "testRegex": "./__tests__/Anchor.test.ts", "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"] } \ No newline at end of file diff --git a/src/anchor/coder/instructions.ts b/src/anchor/coder/instructions.ts index 603e58e..f1e1617 100644 --- a/src/anchor/coder/instructions.ts +++ b/src/anchor/coder/instructions.ts @@ -88,9 +88,9 @@ export class PythOracleInstructionCoder implements InstructionCoder { } /// updProduct has it's own format - if (methodName == 'updProduct') { + if (methodName === 'updProduct') { let offset = 0 - for (let key of Object.keys(ix.productMetadata)) { + for (const key of Object.keys(ix.productMetadata)) { offset += buffer.subarray(offset).writeInt8(key.length) offset += buffer.subarray(offset).write(key) offset += buffer.subarray(offset).writeInt8(ix.productMetadata[key].length) @@ -135,7 +135,7 @@ export class PythOracleInstructionCoder implements InstructionCoder { } /// updProduct has it's own format - if (decoder.name == 'updProduct') { + if (decoder.name === 'updProduct') { const product: Product = {} let idx = 0 while (idx < data.length) { From 6ac5bebeab04c587591e5126d41fd2e5804dfb7d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 16 Jan 2023 15:16:10 -0600 Subject: [PATCH 3/3] Typo --- src/anchor/coder/instructions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/anchor/coder/instructions.ts b/src/anchor/coder/instructions.ts index f1e1617..aaa4f8c 100644 --- a/src/anchor/coder/instructions.ts +++ b/src/anchor/coder/instructions.ts @@ -87,7 +87,7 @@ export class PythOracleInstructionCoder implements InstructionCoder { throw new Error(`Unknown method: ${methodName}`) } - /// updProduct has it's own format + /// updProduct has its own format if (methodName === 'updProduct') { let offset = 0 for (const key of Object.keys(ix.productMetadata)) { @@ -134,7 +134,7 @@ export class PythOracleInstructionCoder implements InstructionCoder { return null } - /// updProduct has it's own format + /// updProduct has its own format if (decoder.name === 'updProduct') { const product: Product = {} let idx = 0