From e7a7edd814d7afe33acd279e7770e9533827e1d7 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Sat, 31 Dec 2022 03:06:13 -0800 Subject: [PATCH] chore(sdk): solana gencode use bigint (#271) --- packages/sdk/package.json | 3 +- .../src/builtin/solana/wormhole-processor.ts | 147 ++++++++++++++---- packages/sdk/src/core/numberish.ts | 23 +-- packages/sdk/src/solana-codegen/codegen.ts | 27 +++- packages/sdk/src/tests/solana.test.ts | 2 +- .../sdk/src/tests/wormhole-token-bridge.ts | 4 +- packages/sdk/tsconfig.json | 1 + yarn.lock | 9 +- 8 files changed, 161 insertions(+), 55 deletions(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6b7fdd681..45b85b3a9 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -20,10 +20,11 @@ "docs": "typedoc --options typedoc.json" }, "dependencies": { + "@dao-xyz/borsh": "^4.0.7", "@ethersproject/providers": "~5.7.0", "@project-serum/anchor": "^0.26.0", - "@sentio/cli": "^1.0.0", "@sentio/base": "^1.0.0", + "@sentio/cli": "^1.0.0", "@sentio/protos": "^1.0.0", "@solana/web3.js": "^1.47.3", "@typechain/ethers-v5": "^10.0.0", diff --git a/packages/sdk/src/builtin/solana/wormhole-processor.ts b/packages/sdk/src/builtin/solana/wormhole-processor.ts index e1c65132c..cae6d7c97 100644 --- a/packages/sdk/src/builtin/solana/wormhole-processor.ts +++ b/packages/sdk/src/builtin/solana/wormhole-processor.ts @@ -1,8 +1,13 @@ import { SolanaBaseProcessor, SolanaContext, SolanaBindOptions } from '@sentio/sdk' import { Instruction } from '@project-serum/anchor' -import * as borsh from "@coral-xyz/borsh"; import bs58 from 'bs58' +import { + deserialize, + field, + vec, fixedArray, option, +} from "@dao-xyz/borsh"; + // https://github.com/certusone/wormhole/blob/8818d4b8f0471095dd48fa6f5da9c315cbfc9b52/solana/modules/token_bridge/program/src/lib.rs#L100 enum TokenBridgeInstruction { Initialize = 0, @@ -20,30 +25,104 @@ enum TokenBridgeInstruction { TransferNativeWithPayload, } +class TransferDataValues { + @field({ type: 'u32' }) + nonce: number + + @field({type: 'u64'}) + amount: bigint + + @field({type: 'u64'}) + fee: bigint + + @field({ type: fixedArray('u8', 32) }) + target_address: number[]; + + @field({ type: 'u16' }) + target_chain: number + + constructor(data?: TransferDataValues) { + if(data){ + Object.assign(this,data) + } + } +} + +class TransferNativeWithPayloadData { + @field({ type: 'u32' }) + nonce: number + + @field({type: 'u64'}) + amount: bigint + + @field({type: 'u64'}) + fee: bigint + + @field({ type: fixedArray('u8', 32) }) + target_address: number[]; + + @field({ type: 'u16' }) + target_chain: number + + @field({ type: vec('u8') }) + payload: number[]; + + @field({type: option(fixedArray('u8', 32) )}) + cpi_program_id?: number[] + + constructor(data?: TransferNativeWithPayloadData) { + if(data){ + Object.assign(this,data) + } + } +} + +class AttestTokenValues { + @field({ type: 'u32' }) + nonce: number + + constructor(data?: AttestTokenValues) { + if(data){ + Object.assign(this,data) + } + } +} + +class InitializeDataValues { + @field({type: fixedArray('u8', 32) }) + bridge: number[] + + constructor(data?: InitializeDataValues) { + if(data){ + Object.assign(this,data) + } + } +} + export class TokenBridgeProcessor extends SolanaBaseProcessor { - private readonly transferDataValues = [ - borsh.u32('nonce'), - borsh.u64('amount'), - borsh.u64('fee'), - borsh.array(borsh.u8(undefined), 32, 'target_address'), - borsh.u16('target_chain') - ] + // private readonly transferDataValues = [ + // borsh.u32('nonce'), + // borsh.u64('amount'), + // borsh.u64('fee'), + // borsh.array(borsh.u8(undefined), 32, 'target_address'), + // borsh.u16('target_chain') + // ] // https://github.com/certusone/wormhole/blob/8818d4b8f0471095dd48fa6f5da9c315cbfc9b52/solana/modules/token_bridge/program/src/api/transfer_payload.rs#L170 - private readonly transferDataWithPayloadValues = [ - borsh.u32('nonce'), - borsh.u64('amount'), - borsh.array(borsh.u8(undefined), 32, 'target_address'), - borsh.u16('target_chain'), - borsh.vecU8('payload'), - borsh.option(borsh.publicKey(undefined), 'cpi_program_id') - ] - private readonly attestTokenValues = [ - borsh.u32('nonce') - ] - private readonly initializeDataValues = [ - borsh.publicKey('bridge') - ] + // private readonly transferDataWithPayloadValues = [ + // borsh.u32('nonce'), + // borsh.u64('amount'), + // borsh.array(borsh.u8(undefined), 32, 'target_address'), + // borsh.u16('target_chain'), + // borsh.vecU8('payload'), + // borsh.option(borsh.publicKey(undefined), 'cpi_program_id') + // ] + // private readonly attestTokenValues = [ + // borsh.u32('nonce') + // ] + // private readonly initializeDataValues = [ + // borsh.publicKey('bridge') + // ] static bind(options: SolanaBindOptions): TokenBridgeProcessor { if (options && !options.name) { @@ -67,39 +146,51 @@ export class TokenBridgeProcessor extends SolanaBaseProcessor { name: TokenBridgeInstruction[TokenBridgeInstruction.Initialize] } case TokenBridgeInstruction.TransferNative: + data = deserialize(Buffer.from(u8Arr.slice(1)), TransferDataValues) + // struct is defined at: https://github.com/certusone/wormhole/blob/8818d4b8f0471095dd48fa6f5da9c315cbfc9b52/solana/modules/token_bridge/program/src/api/transfer.rs#L295 - data = borsh.struct(this.transferDataValues, 'TransferNativeData').decode(Buffer.from(u8Arr.slice(1))) + // data = borsh.struct(this.transferDataValues, 'TransferNativeData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.TransferNative] } case TokenBridgeInstruction.TransferWrapped: + data = deserialize(Buffer.from(u8Arr.slice(1)), TransferDataValues) + // stuct is defined at: https://github.com/certusone/wormhole/blob/8818d4b8f0471095dd48fa6f5da9c315cbfc9b52/solana/modules/token_bridge/program/src/api/transfer.rs#L295 - data = borsh.struct(this.transferDataValues, 'TransferWrappedData').decode(Buffer.from(u8Arr.slice(1))) + // data = borsh.struct(this.transferDataValues, 'TransferWrappedData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.TransferWrapped] } case TokenBridgeInstruction.TransferNativeWithPayload: - data = borsh.struct(this.transferDataWithPayloadValues, 'TransferNativeWithPayloadData').decode(Buffer.from(u8Arr.slice(1))) + data = deserialize(Buffer.from(u8Arr.slice(1)), TransferNativeWithPayloadData) + + // data = borsh.struct(this.transferDataWithPayloadValues, 'TransferNativeWithPayloadData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.TransferNativeWithPayload] } case TokenBridgeInstruction.TransferWrappedWithPayload: - data = borsh.struct(this.transferDataWithPayloadValues, 'TransferWrappedWithPayloadData').decode(Buffer.from(u8Arr.slice(1))) + data = deserialize(Buffer.from(u8Arr.slice(1)), TransferNativeWithPayloadData) + + // data = borsh.struct(this.transferDataWithPayloadValues, 'TransferWrappedWithPayloadData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.TransferWrappedWithPayload] } case TokenBridgeInstruction.Initialize: - data = borsh.struct(this.initializeDataValues, 'InitializeData').decode(Buffer.from(u8Arr.slice(1))) + data = deserialize(Buffer.from(u8Arr.slice(1)), InitializeDataValues) + + // data = borsh.struct(this.initializeDataValues, 'InitializeData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.Initialize] } case TokenBridgeInstruction.AttestToken: - data = borsh.struct(this.attestTokenValues, 'AttestTokenData').decode(Buffer.from(u8Arr.slice(1))) + data = deserialize(Buffer.from(u8Arr.slice(1)), AttestTokenValues) + + // data = borsh.struct(this.attestTokenValues, 'AttestTokenData').decode(Buffer.from(u8Arr.slice(1))) return { data, name: TokenBridgeInstruction[TokenBridgeInstruction.AttestToken] diff --git a/packages/sdk/src/core/numberish.ts b/packages/sdk/src/core/numberish.ts index 2a20ad2fe..338dd1c4f 100644 --- a/packages/sdk/src/core/numberish.ts +++ b/packages/sdk/src/core/numberish.ts @@ -1,10 +1,9 @@ import { BigNumber } from 'ethers' import { BigInteger, MetricValue } from '@sentio/protos' import { BigDecimal } from '.' -import { BN } from '@project-serum/anchor' import { BlockTag } from '@ethersproject/providers' -export type Numberish = number | BigNumber | bigint | BigDecimal +export type Numberish = number | BigNumber | bigint | BigDecimal | string export function toBlockTag(a: number | bigint): BlockTag { if (typeof a === 'number') { @@ -41,9 +40,9 @@ export function toMetricValue(value: Numberish): MetricValue { }) } } - if (BN.isBN(value)) { + if (typeof value === 'string') { return MetricValue.fromPartial({ - bigInteger: bnToBigInteger(value), + bigDecimal: value, }) } if (typeof value === 'bigint' || Number.isInteger(value)) { @@ -74,14 +73,6 @@ function bigDecimalToBigInteger(a: BigDecimal): BigInteger { return hexToBigInteger(a.toString(16), negative) } -function bnToBigInteger(a: BN): BigInteger { - const negative = a.isNeg() - if (negative) { - a = a.abs() - } - return hexToBigInteger(a.toString(16), negative) -} - function intToBigInteger(a: bigint | number): BigInteger { const negative = a < 0 if (negative) { @@ -94,12 +85,14 @@ export function toBigInteger(a: Numberish): BigInteger { if (a instanceof BigDecimal) { return bigDecimalToBigInteger(a) } - if (a instanceof BN) { - return bnToBigInteger(a) - } + if (a instanceof BigNumber) { return intToBigInteger(a.toBigInt()) } + if (typeof a === 'string') { + return intToBigInteger(BigInt(a)) + } + return intToBigInteger(a) // Following code is actually very slow diff --git a/packages/sdk/src/solana-codegen/codegen.ts b/packages/sdk/src/solana-codegen/codegen.ts index e2ca8efea..ae51f1b5e 100644 --- a/packages/sdk/src/solana-codegen/codegen.ts +++ b/packages/sdk/src/solana-codegen/codegen.ts @@ -32,7 +32,7 @@ function codeGenSolanaIdlProcessor(idlObj: any): string { const idlName = idlObj.name const idlNamePascalCase = toPascalCase(idlName) const instructions: any[] = idlObj.instructions - return `import { BorshInstructionCoder, Instruction, Idl, BN } from '@project-serum/anchor' + return `import { BorshInstructionCoder, Instruction, Idl } from '@project-serum/anchor' import { SolanaBaseProcessor, SolanaContext, SolanaBindOptions } from "@sentio/sdk" import { ${idlName}_idl } from "./${idlName}" import bs58 from 'bs58' @@ -59,14 +59,15 @@ export class ${idlNamePascalCase}Processor extends SolanaBaseProcessor { function codeGenSolanaInstruction(idlName: string, ins: any): string { const instructionName = ins.name - const argsTypeString = codeGenInstructionArgs(ins.args) return ` - on${ - instructionName.charAt(0).toUpperCase() + instructionName.slice(1) - }(handler: (args: ${argsTypeString}, accounts: string[], ctx: SolanaContext) => void): ${idlName}Processor { + on${instructionName.charAt(0).toUpperCase() + instructionName.slice(1)}(handler: (args: ${codeGenInstructionArgsType( + ins.args + )}, accounts: string[], ctx: SolanaContext) => void): ${idlName}Processor { this.onInstruction('${instructionName}', (ins: Instruction, ctx, accounts: string[]) => { + const origin = ins.data as any + const data = ${codeGenInstructionArgs(ins.args)} if (ins) { - handler(ins.data as ${argsTypeString}, accounts, ctx) + handler(data, accounts, ctx) } }) return this @@ -75,6 +76,18 @@ function codeGenSolanaInstruction(idlName: string, ins: any): string { } function codeGenInstructionArgs(args: { name: string; type: string }[]): string { + return `{ ${args.map((arg) => codeGenInstructionArg(arg.name, arg.type)).join(', ')} }` +} + +function codeGenInstructionArg(name: string, type: string): string { + const mType = mapType(type) + if (mType === 'bigint') { + return `${name}: BigInt(origin.${name}.toString())` + } + return `${name}: origin.${name} as ${mType}` +} + +function codeGenInstructionArgsType(args: { name: string; type: string }[]): string { return `{ ${args.map((arg) => arg.name + ': ' + mapType(arg.type)).join(', ')} }` } @@ -100,7 +113,7 @@ function mapType(tpe: string): string { case 'i64': case 'u128': case 'i128': - return 'BN' + return 'bigint' default: return 'any' } diff --git a/packages/sdk/src/tests/solana.test.ts b/packages/sdk/src/tests/solana.test.ts index a19f432d6..cf473391b 100644 --- a/packages/sdk/src/tests/solana.test.ts +++ b/packages/sdk/src/tests/solana.test.ts @@ -68,7 +68,7 @@ describe('Test Solana Example', () => { expect(res.result?.counters).length(1) expect(res.result?.gauges).length(0) expect(res.result?.counters[0].metadata?.blockNumber).equal(0n) - expect(firstCounterValue(res.result, 'totalWeth_supply')).equal(12000000000000) + expect(firstCounterValue(res.result, 'totalWeth_supply')?.toString()).equal('12000000000000') expect(res.result?.counters[0].runtimeInfo?.from).equals(HandlerType.SOL_INSTRUCTION) }) }) diff --git a/packages/sdk/src/tests/wormhole-token-bridge.ts b/packages/sdk/src/tests/wormhole-token-bridge.ts index b78a727a3..fd0913777 100644 --- a/packages/sdk/src/tests/wormhole-token-bridge.ts +++ b/packages/sdk/src/tests/wormhole-token-bridge.ts @@ -10,11 +10,11 @@ TokenBridgeProcessor.bind({ SPLTokenProcessor.bind({ address: 'wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb', processInnerInstruction: true }) .onMintTo((data, ctx) => { if (data.mint === '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs') { - ctx.meter.Counter('totalWeth_supply').add(data.amount as number) + ctx.meter.Counter('totalWeth_supply').add(BigInt(data.amount)) } }) .onBurn((data, ctx) => { if (data.mint === '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs') { - ctx.meter.Counter('totalWeth_supply').sub(data.amount as number) + ctx.meter.Counter('totalWeth_supply').sub(BigInt(data.amount)) } }) diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json index 16fb27e13..efa17dbbd 100644 --- a/packages/sdk/tsconfig.json +++ b/packages/sdk/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "experimentalDecorators": true, "inlineSources": true, "rootDir": "./src", "baseUrl": "./src", diff --git a/yarn.lock b/yarn.lock index 179ea60d0..767fcf2b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -333,6 +333,13 @@ enabled "2.0.x" kuler "^2.0.0" +"@dao-xyz/borsh@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@dao-xyz/borsh/-/borsh-4.0.7.tgz#e70f2b646e0a72f01024e537a9eb2b015f2746fd" + integrity sha512-YvpI4jPenXDpBpKsQu6ZIYTKJej5XQdacLYH5mFJUiz08/TOTnM2mywKZE0BNZi13ujtLK5x4pDFk5YoFfEhaQ== + dependencies: + "@protobufjs/utf8" "^1.1.0" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -746,7 +753,7 @@ "@grpc/grpc-js@^1.6.1": version "1.7.3" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.7.3.tgz#f2ea79f65e31622d7f86d4b4c9ae38f13ccab99a" integrity sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog== dependencies: "@grpc/proto-loader" "^0.7.0"