# EVM Transaction Parsing **Published by:** [ShaoN](https://paragraph.com/@shaon/) **Published on:** 2024-03-03 **URL:** https://paragraph.com/@shaon/evm-transaction-parsing ## Content #Web3 持续更新 …在做一些 Web3 产品的时候,产品中往往需要对链上交易进行解析,这样可以让用户有更好的可读性以及产品易用性,这对于打磨产品也是非常重要的一个环节,比如钱包类产品、DAPP 等。 在主流的公链生态中,EVM 都居在核心且重要的位置上,所以很多产品都是从 Ethereum, Binance Smart Chain, Polygon, Optimism, Arbitrium One 等链开始起步进行构建产品。然而在实现交易解析的过程中,会遇到比较多的问题快速找到目标协议的相关交易在开发过程中不可避免的要进行单元测试、集成测试,全面的测试 Case 是必要的;比如 0x Protocol,它的内部实现非常多样,支持 uniswapv2、uniswapv3、pancake、otc order 、native order 、liquidity provider 等,而且每个实现中又包含不同的 function,要想在正式上生产之前把这些 Case 全部都测试到,就需要所有这些 function 的链上交易,使用这些真实的交易进行测试,准确度才会更高;所以,如果遇到协议下的某个 function 无法在浏览器的 Transactions 列表找到 (比如这个 0x Protocol Transactions,列表中的交易数据非常多),可以用下面的办法找到目标 Function 的 ABI,一般情况下很容易在区块浏览器的已认证合约下面找到;如果找不到,就需要找到项目的合约源码,从代码中找到方法签名使用签名函数对步骤 1 的方法进行签名在线工具:ABI Hashex,EVM Function Selector命令行工具:web3contract按照 Readme 安装 Node.js 等执行 npx hardhat selector ““, 比如 npx hardhat selector “transfer(address,uint256)“得到 Function Selector 后,最好在 bytes4 database 中检索一下,确保方法签名存在,以及是我们想要的,有的时候会搞错在区块浏览器的高级筛选下对 Function Selector 做筛选,然后根据列表中的 to 可以甄别是不是我们的目标合约以及想要的交易Advanced Filter: https://etherscan.io/advanced-filter?mtd=0x1baaa00b ,可以把 mtd=0x1baaa00b 替换成自己的 Function Selector关于 Function Signature 大部分情况下,我们可以很轻松的获取到 Function Signature,但是有些复杂的参数会导致 Function Signature 没有那么容易获取到,比如library LibNativeOrder { struct LimitOrder { IERC20Token makerToken; IERC20Token takerToken; uint128 makerAmount; uint128 takerAmount; uint128 takerTokenFeeAmount; address maker; address taker; address sender; address feeRecipient; bytes32 pool; uint64 expiry; uint256 salt; } } library LibSignature { enum SignatureType { ILLEGAL, INVALID, EIP712, ETHSIGN, PRESIGNED } /// @dev Encoded EC signature. struct Signature { // How to validate the signature. SignatureType signatureType; // EC Signature data. uint8 v; // EC Signature data. bytes32 r; // EC Signature data. bytes32 s; } } function batchFillLimitOrders( LibNativeOrder.LimitOrder[] calldata orders, LibSignature.Signature[] calldata signatures, uint128[] calldata takerTokenFillAmounts, bool revertIfIncomplete ) external payable returns (uint128[] memory takerTokenFilledAmounts, uint128[] memory makerTokenFilledAmounts); 所以 batchFillLimitOrders 函数的签名是什么样的?对于结构体,我们可以用 (),也就是说 struct → (), 结构体的参数放在 () 中对于 enum 我们可以映射成 uint8对于 IERC20Token 这类指向某个合约的,可以映射到 address对于加了 [],我们原样添加[] 即可那些基础类型,是什么就用什么综上我们可以得到签名:batchFillLimitOrders((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool) ,看着是不是很复杂~~ 我们使用上面介绍到的 npx hardhat selector 就可以得到:id: 0x1baaa00b1ba411405fdb49f17881677944423e1855d044738abacf54c0703072selector: 0x1baaa00b其实还有一种更加复杂的情况,就是参数是 struct,这个struct 又套了一层 struct,但是原理是一致的,比如struct MetaTransactionFeeData { // ERC20 fee recipient address recipient; // ERC20 fee amount uint256 amount; } struct MetaTransactionDataV2 { // Signer of meta-transaction. On whose behalf to execute the MTX. address payable signer; // Required sender, or NULL for anyone. address sender; // MTX is invalid after this time. uint256 expirationTimeSeconds; // Nonce to make this MTX unique. uint256 salt; // Encoded call data to a function on the exchange proxy. bytes callData; // ERC20 fee `signer` pays `sender`. IERC20Token feeToken; // ERC20 fees. MetaTransactionFeeData[] fees; } function batchExecuteMetaTransactionsV2( MetaTransactionDataV2 calldata mtx, LibSignature.Signature calldata signature ) external returns (bytes[] memory returnResults); 同理我们就可以得到 executeMetaTransactionV2((address,address,uint256,uint256,bytes,address,(address,uint256)[]),(uint8,uint8,bytes32,bytes32)) ,最终可以得到 Selector 是 0x3d8d4082关于 ABI有时候我们会用 ABI 文件进行解码,所以有获取 ABI 的需要;那如果遇到上文所说的情况,该怎么做呢,看 executeMetaTransactionV2 的例子,我们可以得到如下的 ABI{ inputs: [ { 'components': [ { 'internalType': 'address', 'name': 'signer', 'type': 'address' }, { 'internalType': 'address', 'name': 'sender', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'expirationTimeSeconds', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': 'salt', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': 'callData', 'type': 'bytes' }, { 'internalType': 'address', 'name': 'feeToken', 'type': 'address' }, { 'components': [ { 'internalType': 'address', 'name': 'recipient', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'amount', 'type': 'uint256' } ], 'internalType': 'struct IMetaTransactionsFeatureV2.MetaTransactionFeeData[]', 'name': 'fees', 'type': 'tuple[]' } ], 'internalType': 'struct IMetaTransactionsFeatureV2.MetaTransactionDataV2[]', 'name': 'mtx', 'type': 'tuple' }, { 'components': [ { 'internalType': 'uint8', 'name': 'signatureType', 'type': 'uint8' }, { 'internalType': 'uint8', 'name': 'v', 'type': 'uint8' }, { 'internalType': 'bytes32', 'name': 'r', 'type': 'bytes32' }, { 'internalType': 'bytes32', 'name': 's', 'type': 'bytes32' } ], internalType: 'struct LibSignature.Signature[]', name: 'signature', type: 'tuple' } ], name: 'executeMetaTransactionV2', outputs: [{ internalType: 'bytes', name: 'returnResult', type: 'bytes' }], stateMutability: 'payable', type: 'function' } 其中有两个地方需要注意struct 参数,需要配合 components 和 internalTypecomponents 里包含的是 struct 中的元素列表,一定要按顺序声明internalType 其实没那么重要,最好也写正确,可读性更好嵌套的 struct 同样遵循 1 中所述的规则这样我们在写代码的时候就可以利用 ethers.js 或者 web3.js 进行解码 input data 数据了 (其他编程语言是类似的)。 Reference:web3.js decodeLogweb3.js decodeParameter ## Publication Information - [ShaoN](https://paragraph.com/@shaon/): Publication homepage - [All Posts](https://paragraph.com/@shaon/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@shaon): Subscribe to updates - [Twitter](https://twitter.com/Shaohan_N): Follow on Twitter