#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 的方法进行签名
命令行工具: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 的需要;那如果遇到上文所说的情况,该怎么做呢,看
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 和 internalType
components 里包含的是 struct 中的元素列表,一定要按顺序声明
internalType 其实没那么重要,最好也写正确,可读性更好
嵌套的 struct 同样遵循 1 中所述的规则
这样我们在写代码的时候就可以利用 ethers.js 或者 web3.js 进行解码 input data 数据了 (其他编程语言是类似的)。
Reference:

