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/__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..aaa4f8c 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 its own format + if (methodName === 'updProduct') { + let offset = 0 + 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) + 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 its 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: [] + } + }, + ] }