diff --git a/packages/bodhi/src/utils.ts b/packages/bodhi/src/utils.ts index 3b45e93bf..22e79a074 100644 --- a/packages/bodhi/src/utils.ts +++ b/packages/bodhi/src/utils.ts @@ -1,15 +1,11 @@ -/* eslint-disable prefer-promise-reject-errors */ -import { BigNumber } from '@ethersproject/bignumber'; import { BodhiProvider } from '@acala-network/eth-providers'; -import { BodhiSigner } from './BodhiSigner'; import { BytesLike } from '@ethersproject/bytes'; import { KeyringPair } from '@polkadot/keyring/types'; import { WsProvider } from '@polkadot/api'; import { bufferToU8a, isBuffer, isU8a, u8aToHex } from '@polkadot/util'; import { createTestPairs } from '@polkadot/keyring'; -export const U32MAX = BigNumber.from('0xffffffff'); -export const U64MAX = BigNumber.from('0xffffffffffffffff'); +import { BodhiSigner } from './BodhiSigner'; export const dataToString = (bytes: BytesLike): string => { if (isBuffer(bytes)) { diff --git a/packages/eth-providers/package.json b/packages/eth-providers/package.json index fce4aa028..167fca134 100644 --- a/packages/eth-providers/package.json +++ b/packages/eth-providers/package.json @@ -10,7 +10,7 @@ "test:e2e": "vitest --run --config vitest.config.e2e.ts" }, "peerDependencies": { - "@acala-network/api": "6.1.0", + "@acala-network/api": "^6.1.3", "@polkadot/api": "^10.11.1" }, "dependencies": { @@ -23,7 +23,7 @@ "lru-cache": "~7.8.2" }, "devDependencies": { - "@acala-network/api": "6.1.0", + "@acala-network/api": "^6.1.3", "@types/bn.js": "~5.1.0", "@types/lru-cache": "~7.6.1", "dotenv": "~10.0.0", diff --git a/packages/eth-providers/src/__tests__/e2e/__snapshots__/trace.test.ts.snap b/packages/eth-providers/src/__tests__/e2e/__snapshots__/trace.test.ts.snap new file mode 100644 index 000000000..649d90bde Binary files /dev/null and b/packages/eth-providers/src/__tests__/e2e/__snapshots__/trace.test.ts.snap differ diff --git a/packages/eth-providers/src/__tests__/e2e/trace.test.ts b/packages/eth-providers/src/__tests__/e2e/trace.test.ts new file mode 100644 index 000000000..673fde5bf --- /dev/null +++ b/packages/eth-providers/src/__tests__/e2e/trace.test.ts @@ -0,0 +1,44 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import { EvmRpcProvider } from '../../rpc-provider'; + +const LOCAL_NODE_WITH_TRACING = 'ws://localhost:8000'; +const ACALA_SUBQL = 'https://subql-query-acala.aca-api.network'; + +describe('tracing', async () => { + const provider = EvmRpcProvider.from(LOCAL_NODE_WITH_TRACING, { subqlUrl: ACALA_SUBQL }); + await provider.isReady(); + + afterAll(async () => { + await provider.disconnect(); + }); + + describe('trace calls', () => { + const tracerConf = { tracer: 'callTracer' }; + + it('send native token', async () => { + const trace = await provider.traceTx('0x89dd673cd8527943939904cb0d1f11992a9fd60a171ad7588c4dedf8712cfb7c', tracerConf); + expect(trace).to.toMatchSnapshot(); + }); + + it('transfer erc20', async () => { + const trace = await provider.traceTx('0xf93095f41414f28b09866553ba2ac9957d865f32b6d2b0e220b08ff20e47612a', tracerConf); + expect(trace).to.toMatchSnapshot(); + }); + + it('euphrates stake', async () => { + const trace = await provider.traceTx('0x16a70b2202ceb4968dcd1c44ee782a145a51bf016b92cc871b25ca5723ceffc8', tracerConf); + expect(trace).to.toMatchSnapshot(); + }); + + // it('dex swap', async () => { + // const trace = await provider.traceTx('0x42c61da1a663e7c097399b2031d6bc38e0dff083e04de7083e145884bbfe8d9f', tracerConf); + // expect(trace).to.toMatchSnapshot(); + // }); + }); + + // describe('trace opcodes', () => { + // const tracerConf = { tracer: 'opcodeTracer' }; + // }); + +}); diff --git a/packages/eth-providers/src/base-provider.ts b/packages/eth-providers/src/base-provider.ts index 838d3eb2c..bc99531fd 100644 --- a/packages/eth-providers/src/base-provider.ts +++ b/packages/eth-providers/src/base-provider.ts @@ -97,6 +97,7 @@ import { import { BlockCache, CacheInspect } from './utils/BlockCache'; import { MaxSizeSet } from './utils/MaxSizeSet'; import { SubqlProvider } from './utils/subqlProvider'; +import { TracerType, traceCall, traceVM } from './utils/trace'; import { _Metadata } from './utils/gqlTypes'; export interface HeadsInfo { @@ -2164,4 +2165,36 @@ export abstract class BaseProvider extends AbstractProvider { listeners = (_eventName?: EventType): Array => throwNotImplemented('listeners'); off = (_eventName: EventType, _listener?: Listener): Provider => throwNotImplemented('off'); removeAllListeners = (_eventName?: EventType): Provider => throwNotImplemented('removeAllListeners'); + + traceTx = async (txHash: string, traceConf: any) => { + const tracerOptions = Object.values(TracerType); + if (!tracerOptions.includes(traceConf.tracer)) { + logger.throwError( + `traceTx: invalid tracer, must be one of { ${tracerOptions.join(', ')} }`, + Logger.errors.INVALID_ARGUMENT, + { tracer: traceConf.tracer }, + ); + } + + if (!this.api.call.evmTraceApi) { + logger.throwError('traceTx: evm tracing not supported', Logger.errors.NOT_IMPLEMENTED, { txHash }); + } + + const receipt = await this.getReceipt(txHash); + if (!receipt) { + logger.throwError('traceTx: tx not found', Logger.errors.UNKNOWN_ERROR, { txHash }); + } + + const blockData = await this.api.rpc.chain.getBlock(receipt.blockHash); + + const targetExtrinsic = blockData.block.extrinsics.find(ex => ex.hash.toHex() === txHash); + if (!targetExtrinsic) { + // if receipt can be found, but no evm extrinsic, it's either orphan or batch tx + logger.throwError('traceTx: tracing for orphan/batch tx is not supported', Logger.errors.UNKNOWN_ERROR, { txHash }); + } + + return traceConf.tracer === TracerType.CallTracer + ? await traceCall(this.api, targetExtrinsic) + : await traceVM(this.api, targetExtrinsic); + }; } diff --git a/packages/eth-providers/src/consts.ts b/packages/eth-providers/src/consts.ts deleted file mode 100644 index 5c0e9258d..000000000 --- a/packages/eth-providers/src/consts.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { BigNumber } from '@ethersproject/bignumber'; - -export const ZERO = 0; -export const BIGNUMBER_ONE = BigNumber.from(1); -export const EMPTY_HEX_STRING = '0x'; - -export const BIGNUMBER_ZERO = BigNumber.from(ZERO); - -export const GAS_PRICE = BIGNUMBER_ONE; -export const MAX_FEE_PER_GAS = BIGNUMBER_ONE; -export const MAX_PRIORITY_FEE_PER_GAS = BIGNUMBER_ONE; -export const U32MAX = BigNumber.from('0xffffffff'); -export const U64MAX = BigNumber.from('0xffffffffffffffff'); - -export const DUMMY_BLOCK_HASH = '0xdummydummy'; -export const DUMMY_ADDRESS = '0x1111111111333333333355555555558888888888'; -export const DUMMY_LOGS_BLOOM = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; -export const DUMMY_V = '0x25'; -export const DUMMY_R = '0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea'; -export const DUMMY_S = '0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c'; -export const DUMMY_V_R_S = { - v: DUMMY_V, - r: DUMMY_R, - s: DUMMY_S, -}; -export const DUMMY_BLOCK_NONCE = '0x0000000000000000'; -export const ZERO_BLOCK_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000'; -export const EMTPY_UNCLE_HASH = '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'; -export const EMTPY_UNCLES = []; -export const ERROR_PATTERN = [ - // Assume that Error is nested only once - /execution fatal: Module\(ModuleError { index: (\d+), error: \[(\d+), 0, 0, 0\], message: None }\)/, - /execution fatal: Module\(ModuleError { index: (\d+), error: (\d+), message: None }\)/, -]; -export const ERC20_ABI = [ - // Read-Only Functions - 'function balanceOf(address owner) view returns (uint256)', - 'function decimals() view returns (uint8)', - 'function symbol() view returns (string)', - - // Authenticated Functions - 'function transfer(address to, uint amount) returns (bool)', - - // Events - 'event Transfer(address indexed from, address indexed to, uint amount)', -]; - -export const LOCAL_MODE_MSG = ` - ------------------------------- - 🔨 local development mode is ON - ❌ don't use it for production! - ------------------------------- -`; - -export const PROD_MODE_MSG = ` - ------------------------------------------ - ⚡️ running in production (standard) mode ⚡️ - ------------------------------------------ -`; - -export const SAFE_MODE_WARNING_MSG = ` - ------------------------------- WARNING ----------------------------- - 🔒 SafeMode is ON, and RPCs behave very differently than usual world! - ❗ This mode is DEPRECATED, please use \`finalized\` block tag - --------------------------------------------------------------------- -`; - -export const CACHE_SIZE_WARNING = ` - ------------------- WARNING ------------------- - Max cached blocks is big, please be cautious! - If memory exploded, try decrease MAX_CACHE_SIZE - ----------------------------------------------- -`; - -export const ORPHAN_TX_DEFAULT_INFO = { - value: '0x0', - gas: 2_100_000, - input: '0x', - nonce: 0, - ...DUMMY_V_R_S, -}; - -export const BLOCK_GAS_LIMIT = 29_990_016; -export const BLOCK_STORAGE_LIMIT = 3_670_016; -export const MAX_GAS_LIMIT_CC = 21; // log2(BLOCK_STORAGE_LIMIT) - -export const ONE_GWEI = 1_000_000_000n; -export const TEN_GWEI = ONE_GWEI * 10n; -export const ONE_HUNDRED_GWEI = ONE_GWEI * 100n; -export const ONE_THOUSAND_GWEI = ONE_GWEI * 1000n; - -export const GAS_MASK = 100000; -export const STORAGE_MASK = 100; -export const GAS_LIMIT_CHUNK = BigNumber.from(30000); - -export const U32_MAX = 4_294_967_295n; diff --git a/packages/eth-providers/src/consts/gas.ts b/packages/eth-providers/src/consts/gas.ts new file mode 100644 index 000000000..52257c26a --- /dev/null +++ b/packages/eth-providers/src/consts/gas.ts @@ -0,0 +1,14 @@ +import { BigNumber } from 'ethers'; + +export const BLOCK_GAS_LIMIT = 29_990_016; +export const BLOCK_STORAGE_LIMIT = 3_670_016; +export const MAX_GAS_LIMIT_CC = 21; // log2(BLOCK_STORAGE_LIMIT) + +export const ONE_GWEI = 1_000_000_000n; +export const TEN_GWEI = ONE_GWEI * 10n; +export const ONE_HUNDRED_GWEI = ONE_GWEI * 100n; +export const ONE_THOUSAND_GWEI = ONE_GWEI * 1000n; + +export const GAS_MASK = 100000; +export const STORAGE_MASK = 100; +export const GAS_LIMIT_CHUNK = BigNumber.from(30000); diff --git a/packages/eth-providers/src/consts/index.ts b/packages/eth-providers/src/consts/index.ts new file mode 100644 index 000000000..9b5fc84aa --- /dev/null +++ b/packages/eth-providers/src/consts/index.ts @@ -0,0 +1,5 @@ +export * from './gas'; +export * from './misc'; +export * from './msgs'; +export * from './trace'; +export * from './tx'; diff --git a/packages/eth-providers/src/consts/misc.ts b/packages/eth-providers/src/consts/misc.ts new file mode 100644 index 000000000..8ade559c3 --- /dev/null +++ b/packages/eth-providers/src/consts/misc.ts @@ -0,0 +1,16 @@ +import { BigNumber } from '@ethersproject/bignumber'; + +export const ZERO = 0; +export const EMPTY_HEX_STRING = '0x'; + +export const BIGNUMBER_ZERO = BigNumber.from(ZERO); +export const U32_MAX = 4_294_967_295n; + +export const U32MAX = BigNumber.from('0xffffffff'); +export const U64MAX = BigNumber.from('0xffffffffffffffff'); + +export const ERROR_PATTERN = [ + // Assume that Error is nested only once + /execution fatal: Module\(ModuleError { index: (\d+), error: \[(\d+), 0, 0, 0\], message: None }\)/, + /execution fatal: Module\(ModuleError { index: (\d+), error: (\d+), message: None }\)/, +]; diff --git a/packages/eth-providers/src/consts/msgs.ts b/packages/eth-providers/src/consts/msgs.ts new file mode 100644 index 000000000..d1527edeb --- /dev/null +++ b/packages/eth-providers/src/consts/msgs.ts @@ -0,0 +1,26 @@ +export const LOCAL_MODE_MSG = ` + ------------------------------- + 🔨 local development mode is ON + ❌ don't use it for production! + ------------------------------- +`; + +export const PROD_MODE_MSG = ` + ------------------------------------------ + ⚡️ running in production (standard) mode ⚡️ + ------------------------------------------ +`; + +export const SAFE_MODE_WARNING_MSG = ` + ------------------------------- WARNING ----------------------------- + 🔒 SafeMode is ON, and RPCs behave very differently than usual world! + ❗ This mode is DEPRECATED, please use \`finalized\` block tag + --------------------------------------------------------------------- +`; + +export const CACHE_SIZE_WARNING = ` + ------------------- WARNING ------------------- + Max cached blocks is big, please be cautious! + If memory exploded, try decrease MAX_CACHE_SIZE + ----------------------------------------------- +`; diff --git a/packages/eth-providers/src/consts/trace.ts b/packages/eth-providers/src/consts/trace.ts new file mode 100644 index 000000000..f05a84b38 --- /dev/null +++ b/packages/eth-providers/src/consts/trace.ts @@ -0,0 +1,152 @@ +export const OPNAME_TABLE = { + '0x00': 'STOP', + '0x01': 'ADD', + '0x02': 'MUL', + '0x03': 'SUB', + '0x04': 'DIV', + '0x05': 'SDIV', + '0x06': 'MOD', + '0x07': 'SMOD', + '0x08': 'ADDMOD', + '0x09': 'MULMOD', + '0x0a': 'EXP', + '0x0b': 'SIGNEXTEND', + '0x10': 'LT', + '0x11': 'GT', + '0x12': 'SLT', + '0x13': 'SGT', + '0x14': 'EQ', + '0x15': 'ISZERO', + '0x16': 'AND', + '0x17': 'OR', + '0x18': 'XOR', + '0x19': 'NOT', + '0x1a': 'BYTE', + '0x35': 'CALLDATALOAD', + '0x36': 'CALLDATASIZE', + '0x37': 'CALLDATACOPY', + '0x38': 'CODESIZE', + '0x39': 'CODECOPY', + '0x1b': 'SHL', + '0x1c': 'SHR', + '0x1d': 'SAR', + '0x50': 'POP', + '0x51': 'MLOAD', + '0x52': 'MSTORE', + '0x53': 'MSTORE8', + '0x56': 'JUMP', + '0x57': 'JUMPI', + '0x58': 'PC', + '0x59': 'MSIZE', + '0x5b': 'JUMPDEST', + '0x5f': 'PUSH0', + '0x60': 'PUSH1', + '0x61': 'PUSH2', + '0x62': 'PUSH3', + '0x63': 'PUSH4', + '0x64': 'PUSH5', + '0x65': 'PUSH6', + '0x66': 'PUSH7', + '0x67': 'PUSH8', + '0x68': 'PUSH9', + '0x69': 'PUSH10', + '0x6a': 'PUSH11', + '0x6b': 'PUSH12', + '0x6c': 'PUSH13', + '0x6d': 'PUSH14', + '0x6e': 'PUSH15', + '0x6f': 'PUSH16', + '0x70': 'PUSH17', + '0x71': 'PUSH18', + '0x72': 'PUSH19', + '0x73': 'PUSH20', + '0x74': 'PUSH21', + '0x75': 'PUSH22', + '0x76': 'PUSH23', + '0x77': 'PUSH24', + '0x78': 'PUSH25', + '0x79': 'PUSH26', + '0x7a': 'PUSH27', + '0x7b': 'PUSH28', + '0x7c': 'PUSH29', + '0x7d': 'PUSH30', + '0x7e': 'PUSH31', + '0x7f': 'PUSH32', + '0x80': 'DUP1', + '0x81': 'DUP2', + '0x82': 'DUP3', + '0x83': 'DUP4', + '0x84': 'DUP5', + '0x85': 'DUP6', + '0x86': 'DUP7', + '0x87': 'DUP8', + '0x88': 'DUP9', + '0x89': 'DUP10', + '0x8a': 'DUP11', + '0x8b': 'DUP12', + '0x8c': 'DUP13', + '0x8d': 'DUP14', + '0x8e': 'DUP15', + '0x8f': 'DUP16', + '0x90': 'SWAP1', + '0x91': 'SWAP2', + '0x92': 'SWAP3', + '0x93': 'SWAP4', + '0x94': 'SWAP5', + '0x95': 'SWAP6', + '0x96': 'SWAP7', + '0x97': 'SWAP8', + '0x98': 'SWAP9', + '0x99': 'SWAP10', + '0x9a': 'SWAP11', + '0x9b': 'SWAP12', + '0x9c': 'SWAP13', + '0x9d': 'SWAP14', + '0x9e': 'SWAP15', + '0x9f': 'SWAP16', + '0xf3': 'RETURN', + '0xfd': 'REVERT', + '0xfe': 'INVALID', + '0xef': 'EOFMAGIC', + '0x20': 'SHA3', + '0x30': 'ADDRESS', + '0x31': 'BALANCE', + '0x47': 'SELFBALANCE', + '0x48': 'BASEFEE', + '0x32': 'ORIGIN', + '0x33': 'CALLER', + '0x34': 'CALLVALUE', + '0x3a': 'GASPRICE', + '0x3b': 'EXTCODESIZE', + '0x3c': 'EXTCODECOPY', + '0x3f': 'EXTCODEHASH', + '0x3d': 'RETURNDATASIZE', + '0x3e': 'RETURNDATACOPY', + '0x40': 'BLOCKHASH', + '0x41': 'COINBASE', + '0x42': 'TIMESTAMP', + '0x43': 'NUMBER', + '0x44': 'DIFFICULTY', + '0x45': 'GASLIMIT', + '0x54': 'SLOAD', + '0x55': 'SSTORE', + '0x5a': 'GAS', + '0xa0': 'LOG0', + '0xa1': 'LOG1', + '0xa2': 'LOG2', + '0xa3': 'LOG3', + '0xa4': 'LOG4', + '0xf0': 'CREATE', + '0xf5': 'CREATE2', + '0xf1': 'CALL', + '0xf2': 'CALLCODE', + '0xf4': 'DELEGATECALL', + '0xfa': 'STATICCALL', + '0xff': 'SUICIDE', + '0x46': 'CHAINID', +}; + +export const opName = (opCode: number): string => { + const opCodeHex = `0x${opCode.toString(16).padStart(2, '0')}`; + return OPNAME_TABLE[opCodeHex] ?? opCodeHex; +}; diff --git a/packages/eth-providers/src/consts/tx.ts b/packages/eth-providers/src/consts/tx.ts new file mode 100644 index 000000000..198f6d2b6 --- /dev/null +++ b/packages/eth-providers/src/consts/tx.ts @@ -0,0 +1,23 @@ +export const DUMMY_ADDRESS = '0x1111111111333333333355555555558888888888'; +export const DUMMY_LOGS_BLOOM = + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; +export const DUMMY_V = '0x25'; +export const DUMMY_R = '0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea'; +export const DUMMY_S = '0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c'; +export const DUMMY_V_R_S = { + v: DUMMY_V, + r: DUMMY_R, + s: DUMMY_S, +}; +export const DUMMY_BLOCK_NONCE = '0x0000000000000000'; +export const ZERO_BLOCK_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000'; +export const EMTPY_UNCLE_HASH = '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'; +export const EMTPY_UNCLES = []; + +export const ORPHAN_TX_DEFAULT_INFO = { + value: '0x0', + gas: 2_100_000, + input: '0x', + nonce: 0, + ...DUMMY_V_R_S, +}; diff --git a/packages/eth-providers/src/utils/trace.ts b/packages/eth-providers/src/utils/trace.ts new file mode 100644 index 000000000..d8d7a5184 --- /dev/null +++ b/packages/eth-providers/src/utils/trace.ts @@ -0,0 +1,121 @@ +import { ApiPromise } from '@polkadot/api'; +import { Extrinsic } from '@polkadot/types/interfaces'; +import { HexString } from '@polkadot/util/types'; +import { IExtrinsic } from '@polkadot/types/types'; + +import { opName } from '../consts'; + +export type CallTrace = { + type: 'CALL' | 'CALLCODE' | 'STATICCALL' | 'DELEGATECALL' | 'CREATE' | 'SUICIDE'; + from: HexString; + to: HexString; + input: HexString; + value: HexString; + gas: number; + gasUsed: number; + output: HexString | null; + error: string | null; + revertReason: string | null; + depth: number; + calls: CallTrace[]; +} + +export interface StepBase { + pc: number; + depth: number; + gas: number; + stack: HexString[]; + memory: string[] | null; +} + +export interface StepRaw extends StepBase { + op: number; +} + +export interface Step extends StepBase { + op: string; +} + +export enum TracerType { + CallTracer = 'callTracer', + OpcodeTracer = 'opcodeTracer' +} + +export const traceVM = async ( + api: ApiPromise, + extrinsic: Extrinsic | IExtrinsic | string | Uint8Array, +): Promise => { + if (!api.call.evmTraceApi) { + throw new Error('traceCall: EVM tracing is not supported by the node'); + } + + const pageSize = 10000; + const traceConf = { + page: 0, + pageSize, + disableStack: false, + enableMemory: true, + }; + + let traceNextPage = true; + let steps: Step[] = []; + while (traceNextPage) { + const res = await api.call.evmTraceApi.traceExtrinsic(extrinsic, { OpcodeTracer: traceConf }); + + if (!res.isOk) { + throw new Error(`traceVM: trace failed. Err: ${res.asErr.toString()}`); + } + + const okRes = res.asOk; + if (!okRes.isSteps) { + throw new Error('traceVM: invalid outcome'); + } + + const curSteps = okRes.asSteps.toJSON() as unknown as StepRaw[]; + + steps = steps.concat( + curSteps.map(step => ({ + ...step, + op: opName(step.op), + // transform memory to 64 bytes chunks + memory: step.memory + ? step.memory.map((chunk, idx) => { + // remove 0x prefix + const slice = chunk.slice(2); + // make sure each chunk is 64 bytes + return slice.length < 64 && idx + 1 < step.memory!.length + ? slice.padStart(64, '0') + : slice; + }) + : null, + })), + ); + + traceConf.page++; + traceNextPage = curSteps.length == pageSize; + } + + return steps; +}; + +export const traceCall = async ( + api: ApiPromise, + extrinsic: Extrinsic | IExtrinsic | string | Uint8Array, +): Promise => { + if (!api.call.evmTraceApi) { + throw new Error('traceCall: EVM tracing is not supported by the node'); + } + + const traceConf = { CallTracer: null }; + const res = await api.call.evmTraceApi.traceExtrinsic(extrinsic, traceConf); + if (!res.isOk) { + throw new Error(`traceCall: trace failed. Err: ${res.asErr.toString()}`); + } + + const okRes = res.asOk; + if (!okRes.isCalls) { + throw new Error('traceVM: invalid outcome'); + } + + return okRes.asCalls.toJSON() as CallTrace[]; +}; diff --git a/packages/eth-providers/vitest.config.e2e.ts b/packages/eth-providers/vitest.config.e2e.ts index 110cc112c..776e20714 100644 --- a/packages/eth-providers/vitest.config.e2e.ts +++ b/packages/eth-providers/vitest.config.e2e.ts @@ -8,7 +8,7 @@ export default defineConfig({ testTimeout: 300_000, hookTimeout: 60_000, environment: 'jsdom', - exclude: ['src/__tests__/utils.test.ts'], + // exclude: ['src/__tests__/utils.test.ts'], }, plugins: [swc.vite(), tsconfigPaths()], }); diff --git a/packages/eth-rpc-adapter/src/eip1193-bridge.ts b/packages/eth-rpc-adapter/src/eip1193-bridge.ts index be92e0a59..88791b311 100644 --- a/packages/eth-rpc-adapter/src/eip1193-bridge.ts +++ b/packages/eth-rpc-adapter/src/eip1193-bridge.ts @@ -32,12 +32,8 @@ export class Eip1193Bridge extends EventEmitter { return this.send(request.method, request.params || []); } - isMethodValid(method: string): boolean { - return method.startsWith('eth_') || method.startsWith('net_') || method.startsWith('web3_') || method.startsWith('txpool_'); - } - isMethodImplemented(method: string): method is keyof Eip1193BridgeImpl { - return this.isMethodValid(method) && method in this.#impl; + return method in this.#impl; } async send(method: string, params: any[] = [], ws?: WebSocket): Promise { @@ -501,4 +497,9 @@ class Eip1193BridgeImpl { validate([], params); return this.#provider.txpoolContent(); } + + async debug_traceTransaction(params: any[]): Promise { + validate([{ type: 'trasactionHash' }, { type: '?' }], params); + return this.#provider.traceTx(params[0], params[1]); + } } diff --git a/packages/eth-rpc-adapter/src/validate.ts b/packages/eth-rpc-adapter/src/validate.ts index 2e2d51054..57cf2b8ce 100644 --- a/packages/eth-rpc-adapter/src/validate.ts +++ b/packages/eth-rpc-adapter/src/validate.ts @@ -16,7 +16,8 @@ export type Schema = { | 'message' | 'hexNumber' | 'eventName' - | 'substrateGasParams?'; + | 'substrateGasParams?' + | '?'; }[]; export const validateEventName = (value: any) => { @@ -201,6 +202,10 @@ export const validate = (schema: Schema, data: unknown[]) => { data[i] && validateSubstrateGasParams(data[i] as any); break; } + case '?': { + // TODO: refactor the param validating process + break; + } default: break; } diff --git a/yarn.lock b/yarn.lock index 317e14941..9f638b831 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,26 +12,26 @@ __metadata: languageName: node linkType: hard -"@acala-network/api-derive@npm:6.1.0": - version: 6.1.0 - resolution: "@acala-network/api-derive@npm:6.1.0" +"@acala-network/api-derive@npm:6.1.3": + version: 6.1.3 + resolution: "@acala-network/api-derive@npm:6.1.3" dependencies: - "@acala-network/types": 6.1.0 + "@acala-network/types": 6.1.3 peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: a29227d804e451a26e9011d312fec3d35e872107e2420accd6ba76445eaf603c163150120a0325b851556ef263412ecd668aabba82acb40ef13e30ff4e71d025 + "@polkadot/api": ^12 + checksum: b5ad7dd41188a4246f9b27230ef0aefc82a220ff3a0ed2383c91833c7bb3807bacff9e63516f9c90158a8faad4f95cb35286e74992e9642fe632ff0dbf85013c languageName: node linkType: hard -"@acala-network/api@npm:6.1.0": - version: 6.1.0 - resolution: "@acala-network/api@npm:6.1.0" +"@acala-network/api@npm:^6.1.3": + version: 6.1.3 + resolution: "@acala-network/api@npm:6.1.3" dependencies: - "@acala-network/api-derive": 6.1.0 - "@acala-network/types": 6.1.0 + "@acala-network/api-derive": 6.1.3 + "@acala-network/types": 6.1.3 peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: 286aa8b219d1255c89f8be8c9af2594192178b78a992b8528e1d0df5dc647f6c02dac291c3fa03025a6a30fc6b9e77a00ac4368191d1f4bd07838e32070a3181 + "@polkadot/api": ^12 + checksum: 4a187e649e7807cb18886204544fbbaf0844d333d413c6a9bb457c250f4ad92f0efa086fb66cf3eae6648483c263ebf2393f7a76c4abf910cb76f71d90906b3a languageName: node linkType: hard @@ -69,7 +69,7 @@ __metadata: version: 0.0.0-use.local resolution: "@acala-network/eth-providers@workspace:packages/eth-providers" dependencies: - "@acala-network/api": 6.1.0 + "@acala-network/api": ^6.1.3 "@acala-network/contracts": 4.3.4 "@acala-network/eth-transactions": "workspace:*" "@types/bn.js": ~5.1.0 @@ -83,7 +83,7 @@ __metadata: lru-cache: ~7.8.2 vitest: 0.34.2 peerDependencies: - "@acala-network/api": 6.1.0 + "@acala-network/api": ^6.1.3 "@polkadot/api": ^10.11.1 languageName: unknown linkType: soft @@ -147,12 +147,12 @@ __metadata: languageName: unknown linkType: soft -"@acala-network/types@npm:6.1.0": - version: 6.1.0 - resolution: "@acala-network/types@npm:6.1.0" +"@acala-network/types@npm:6.1.3": + version: 6.1.3 + resolution: "@acala-network/types@npm:6.1.3" peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: 52790bf0e75ceaa2cf8533c87182b362b5da6c27055767ac535dafbcc207bd2f5ddfc7a3582d0d14e40cb5b52ef66848222ed620777b6c874c9c9936db2b300e + "@polkadot/api": ^12 + checksum: e53c202a3388a23aad5613402b7ca8db35105f609a786bde18ee84f5857130ddf57bccf42e0899b4033f2e973b481270fe23aa105da458442090451ff650df68 languageName: node linkType: hard