ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

web3-eth-abi 使用指南:web3.js 中 EVM 数据的编码与解码全解析

web3-eth-abi 使用指南:web3.js 中 EVM 数据的编码与解码全解析 web3-eth-abi 使用指南web3.js 中 EVM 数据的编码与解码全解析【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js导读web3-eth-abi是 web3.js 4.x 的官方子包职责单一而明确负责 EVM以太坊虚拟机输入/输出数据的 ABI 编码与解码。无论是构造一笔合约调用、解读合约返回值、解析事件日志还是还原合约抛出的错误信息都离不开这一层翻译器。本文将围绕该包的编码/解码 API 展开完整继承 README 中的安装方式与工程脚本并结合 src 下的源码实现与 test 单元测试带你从会用深入到懂原理掌握函数签名、参数、日志、错误数据以及 EIP-712 结构化数据的编解码全链路。一、包定位与安装web3-eth-abi是 [web3.js][repo] 的独立子包其 package 描述为 Web3 module encode and decode EVM in/output见 package.json。它提供了一批对 Solidity 函数、事件、错误、参数的编码与解码函数底层采用 TypeScript 编写支持 ESM 与 CommonJS 双模块体系exports字段同时暴露import与require入口Node.js 环境要求14。安装方式官方支持 NPM 与 Yarn 两种方式# 使用 NPM npm install web3-eth-abi # 使用 Yarn yarn add web3-eth-abi两种使用姿势方式一通过完整的web3包访问。安装web3后ABI 工具函数会挂在web3.eth.abi命名空间下。在 eth.exports.ts 中可以看到export * as abi from web3-eth-abi;即web3.eth.abi正是本包的完整转发import { Web3 } from web3; const web3 new Web3(); const encoded web3.eth.abi.encodeFunctionSignature({ name: myMethod, type: function, inputs: [ { type: uint256, name: myNumber }, { type: string, name: myString }, ], });方式二按需引入独立包更利于构建轻量应用。这正是官方推荐的 tree-shaking 友好做法也是本文主要采用的方式import { encodeFunctionSignature } from web3-eth-abi; const encoded encodeFunctionSignature({ name: myMethod, type: function, inputs: [ { type: uint256, name: myNumber }, { type: string, name: myString }, ], });两种方式的 API 完全一致区别仅在于依赖体积与打包产物。环境前提NodeJSLTS 版本即可包声明engines.node 14包管理器NPM 或 Yarn仓库本身使用 Yarn 工作区 Lerna 管理多包二、包级脚本速览官方 README 列出了包的工程脚本与 package.json 中的 scripts 一一对应ScriptDescriptionclean使用rimraf移除dist/实际为dist与libbuild使用tsc构建本包及其依赖包lint使用eslint检查代码lint:fix使用eslint检查并自动修复警告format使用prettier格式化代码test使用jest运行单元测试test:integration使用jest运行/test/integration下的测试test:unit使用jest运行/test/unit下的测试值得留意的是 build 命令被拆分为build:cjs、build:esm、build:types三个并行任务通过concurrently驱动分别产出 CommonJS、ES Module 与类型声明文件这也解释了包入口中exports的多目标映射设计。三、核心 API 全景从入口到实现包的总入口 src/index.ts 将能力划分为五个 API 模块并额外导出decodeContractErrorData与 EIP-712 的getMessage别名为getEncodedEip712Data模块文件提供的能力api/functions_api.ts函数签名与函数调用的编解码api/events_api.ts事件签名编码api/logs_api.ts事件日志data topics解码api/parameters_api.ts单个/批量参数的编解码api/errors_api.ts自定义错误签名编码decode_contract_error_data.ts从 RPC 错误数据中还原错误名称/签名/参数eip_712.tsEIP-712 类型化数据消息哈希所有 API 最终都汇聚到 coders/encode.ts 与 coders/decode.ts 这两个底层编解码器以及 coders/base 下针对address、array、bool、bytes、number、string、tuple等基础类型的逐一实现。下面按使用场景逐一深入。四、函数调用从签名到完整 calldata4.1 编码函数签名选择器以太坊交易中的函数选择器selector是函数签名字符串name(param1Type,param2Type,...)的keccak256哈希的前 4 字节。实现位于 functions_api.tsexport const encodeFunctionSignature (functionName: string | AbiFunctionFragment): string { // 入参校验既不是字符串也不是合法的 AbiFunctionFragment 时抛 AbiError let name: string; if (functionName (typeof functionName function || typeof functionName object)) { name jsonInterfaceMethodToString(functionName); // 将 JSON ABI 片段规范化为 myMethod(uint256,string) } else { name functionName; } return sha3Raw(name).slice(0, 10); // keccak256 前 4 字节含 0x 前缀共 10 字符 };它接受两种入参形式字符串必须是函数名(参数类型列表)形式例如myMethod(uint256,string)、safeTransferFrom(address,address,uint256,bytes)JSON ABI 片段对象包含name、type: function、inputs数组的完整函数描述。官方案例// 传入 JSON ABI 片段 const signature web3.eth.abi.encodeFunctionSignature({ name: myMethod, type: function, inputs: [ { type: uint256, name: myNumber }, { type: string, name: myString }, ], }); // 0x24ee0097 // 传入字符串 const signature web3.eth.abi.encodeFunctionSignature(myMethod(uint256,string)); // 0x24ee0097 const signature web3.eth.abi.encodeFunctionSignature(safeTransferFrom(address,address,uint256,bytes)); // 0xb88d4fde底层依赖jsonInterfaceMethodToStringsrc/utils.ts它会用flattenTypes把嵌套的 tuple/components 展开为规范类型串例如myMethod((uint256,string),uint256)这种含元组的签名也能正确生成。4.2 编码完整函数调用encodeFunctionCall(jsonInterface, params)把函数选择器与编码后的参数拼接成完整的 calldatafunctions_api.tsreturn ${encodeFunctionSignature(jsonInterface)}${encodeParameters( jsonInterface.inputs ?? [], params ?? [], ).replace(0x, )};即calldata 4 字节选择器 去除0x前缀的参数 ABI 编码。官方示例const sig web3.eth.abi.encodeFunctionCall( { name: myMethod, type: function, inputs: [ { type: uint256, name: myNumber }, { type: string, name: myString }, ], }, [2345675643, Hello!%], ); console.log(sig); // 0x24ee0097 (uint256 参数 动态 string 参数的 ABI 编码)对balanceOf这种单地址参数的调用const sig web3.eth.abi.encodeFunctionCall( { inputs: [{ name: account, type: address }], name: balanceOf, outputs: [{ name: , type: uint256 }], stateMutability: view, type: function, }, [0x1234567890123456789012345678901234567890], ); // 0x70a0823100000000000000000000000012345678901234567890123456789012345678904.3 解码函数调用数据decodeFunctionCall(functionsAbi, data, methodSignatureProvided true)用于把链上/交易中的 calldata 还原为参数对象functions_api.tsconst value methodSignatureProvided data data.length 10 data.startsWith(0x) ? data.slice(10) // 默认剥离前 4 字节选择器 : data; const result decodeParameters([...functionsAbi.inputs], value); return { ...result, __method__: jsonInterfaceMethodToString(functionsAbi) };关键行为methodSignatureProvided默认true为true时自动剥离开头的 4 字节选择器若你传入的 data 本身不含选择器应传false返回对象除按索引0、1与按参数名_greeting命名的字段外还附带__length__参数个数与__method__规范化方法签名。官方示例解码setGreeting(string,string)的 calldataconst data 0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000; const params decodeFunctionCall( { inputs: [ { internalType: string, name: _greeting, type: string }, { internalType: string, name: _second_greeting, type: string }, ], name: setGreeting, outputs: [ { internalType: bool, name: , type: bool }, { internalType: string, name: , type: string }, ], stateMutability: nonpayable, type: function, }, data, ); console.log(params); // { // 0: Hello, // 1: Another Greeting, // __length__: 2, // __method__: setGreeting(string,string), // _greeting: Hello, // _second_greeting: Another Greeting, // }4.4 解码函数返回值decodeFunctionReturn(functionsAbi, returnValues)专门处理合约调用的返回数据functions_api.ts其设计有两个贴心约定构造函数的type constructor直接原样返回因为构造函数没有返回值可解码单返回值时直接返回该值本身多返回值时才返回带__length__的对象——这是与旧版 web3.js 保持一致的遗留行为。// 多返回值返回对象 const data 0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000; const decodedResult decodeFunctionReturn( { inputs: [{ internalType: string, name: _greeting, type: string }], name: setGreeting, outputs: [ { internalType: string, name: , type: string }, { internalType: bool, name: , type: bool }, ], stateMutability: nonpayable, type: function, }, data, ); // { 0: Hello, 1: true, __length__: 2 } // 单返回值直接返回原始值 const singleData 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000; const singleResult decodeFunctionReturn( { inputs: [{ internalType: string, name: _greeting, type: string }], name: setGreeting, outputs: [{ internalType: string, name: , type: string }], stateMutability: nonpayable, type: function, }, singleData, ); // Hello五、参数级编解码encodeParameter / encodeParameters5.1 编码单个参数encodeParameter(abi, param)本质是encodeParameters([abi], [param])的单元素特例parameters_api.ts。abi可以是类型字符串也可以是带components的对象描述。官方示例覆盖了定长/变长字节、数值、动态数组与嵌套结构体// uint256uint 等价 web3.eth.abi.encodeParameter(uint256, 2345675643); // 0x000000000000000000000000000000000000000000000000000000008bd02b7b // bytes32 定长右侧补零到 32 字节 web3.eth.abi.encodeParameter(bytes32, 0xdf3234); // 0xdf32340000000000000000000000000000000000000000000000000000000000 // bytes 变长需要偏移量 长度 数据三段 web3.eth.abi.encodeParameter(bytes, 0xdf3234); // 0x0000000000000000000000000000000000000000000000000000000000000020 // 0000000000000000000000000000000000000000000000000000000000000003 // df32340000000000000000000000000000000000000000000000000000000000 // bytes32[] 动态数组 web3.eth.abi.encodeParameter(bytes32[], [0xdf3234, 0xfdfd]); // 0x...0020偏移...0002长度df3234...00 fdfd...00 // 嵌套结构体简化格式 web3.eth.abi.encodeParameter( { ParentStruct: { propertyOne: uint256, propertyTwo: uint256, childStruct: { propertyOne: uint256, propertyTwo: uint256, }, }, }, { propertyOne: 42, propertyTwo: 56, childStruct: { propertyOne: 45, propertyTwo: 78 }, }, ); // 0x...2a...38...2d...4e 即 42, 56, 45, 78 依次 32 字节一组5.2 编码多个参数encodeParameters(abi: AbiInput[], params: unknown[])在 coders/encode.ts 中实现。注意它强校验 ABI 数量与参数数量必须一致否则抛出AbiError(Invalid number of values received for given ABI)export function encodeParameters(abi: ReadonlyArrayAbiInput, params: unknown[]): string { if (abi?.length ! params.length) { throw new AbiError(Invalid number of values received for given ABI, { expected: abi?.length, received: params.length, }); } const abiParams toAbiParams(abi); return utils.uint8ArrayToHexString( encodeTuple({ type: tuple, name: , components: abiParams }, params).encoded, ); }从实现可见参数编码统一被包装成一个匿名tuple交给encodeTuple处理返回值由 Uint8Array 转为十六进制字符串。官方示例const res web3.eth.abi.encodeParameters([uint256, string], [2345675643, Hello!%]); // 0x000000000000000000000000000000000000000000000000000000008bd02b7b // 0000000000000000000000000000000000000000000000000000000000000040 // 0000000000000000000000000000000000000000000000000000000000000007 // 48656c6c6f2125000000000000000000000000000000000000000000000000005.3 类型推断编码inferTypesAndEncodeParametersinferTypesAndEncodeParameters(params)是一个偷懒工具当你不确定参数类型时它会从 JS 值推断 ABI 类型coders/encode.ts。推断规则在inferParamsAbi中实现coders/encode.ts数组→ 递归推断后作为tuple处理其他值→ 通过toHex(value, true)推断例如整数推断为int256/uint256字符串推断为string0x前缀十六进制字符串推断为bytes。官方文档明确指出其适用边界当你明确知道参数类型时不要使用此方法请改用encodeParameters。类型推断并不完美尤其在数组、非 256 位 uint、bytes 等场景下可能产生意外结果。 这是对生产代码非常实用的忠告。六、事件与日志签名编码与日志解码6.1 编码事件签名encodeEventSignature(functionName)与函数选择器不同它返回的是完整的 32 字节 keccak256 哈希事件没有前 4 字节约定实现位于 events_api.tsreturn sha3Raw(name); // 不截断返回完整 64 位十六进制哈希官方示例const event web3.eth.abi.encodeEventSignature({ name: myEvent, type: event, inputs: [ { type: uint256, name: myNumber }, { type: bytes32, name: myBytes }, ], }); // 0xf2eeb729e636a8cb783be044acf6b7b1e2c5863735b60d6daae84c366ee87d97 // 经典 ERC-20 Transfer 事件 const event web3.eth.abi.encodeEventSignature({ inputs: [ { indexed: true, name: from, type: address }, { indexed: true, name: to, type: address }, { indexed: false, name: value, type: uint256 }, ], name: Transfer, type: event, }); // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6.2 解码日志decodeLogEVM 日志Log由data与topics两部分组成。decodeLog(inputs, data, topics)logs_api.ts负责把它们还原为可读参数其算法要点按indexed标记拆分输入indexed: true的参数从 topics 解码非 indexed 参数从data解码自动计算 topic 偏移offset topics.length - indexedInputs.length从而正确处理 topic[0] 是事件签名非匿名事件的情况静态类型直接解码动态类型如 string、数组、结构体仅返回原始 topic 值这符合 EVM 的规则——动态类型被索引时 topic 里存的是其 keccak 哈希无法还原原文。源码通过STATIC_TYPES [bool, string, int, uint, address, fixed, ufixed]前缀匹配判断logs_api.ts并单独处理string类型。官方示例const res web3.eth.abi.decodeLog( [ { type: string, name: myString }, { type: uint256, name: myNumber, indexed: true }, { type: uint8, name: mySmallNumber, indexed: true }, ], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000748656c6c6f252100000000000000000000000000000000000000000000000000, [ 0x000000000000000000000000000000000000000000000000000000000000f310, 0x0000000000000000000000000000000000000000000000000000000000000010, ], ); // { // 0: Hello%!, // 1: 62224n, // 2: 16n, // __length__: 3, // myString: Hello%!, // myNumber: 62224n, // mySmallNumber: 16n // }注意这里传入的 topics不包含 topic[0]即非匿名事件时省略事件签名 topic两个 topics 恰好对应该事件的两个 indexed 参数。若传入包含 topic[0] 的完整 topicsoffset机制会自动跳过它。七、错误处理从 revert 数据还原错误信息7.1 编码错误签名encodeErrorSignature(functionName)errors_api.ts与事件签名同理返回sha3Raw(name)的完整 32 字节哈希用于自定义错误Solidity 的error关键字的识别。7.2 解码合约错误数据decodeContractErrorData这是排查链上交易失败最有用的工具。decodeContractErrorData(errorsAbi, error)decode_contract_error_data.ts从 RPC 返回的Eip838ExecutionError.data中还原错误信息解码流程遵循 EIP-838 的 Error ABI 编码优先在自定义错误 ABI 中匹配取error.data前 4 字节errorSha遍历errorsAbi找到encodeErrorSignature(abi)前缀匹配的那一项然后按该项的inputs解码剩余数据兜底识别两个著名内置错误0x08c379a0→Error(string)require/revert(msg)产生的错误按单个string message解码0x4e487b71→Panic(uint256)Solidity 0.8 算术溢出等 panic按单个uint256 code解码解码成功后通过error.setDecodedProperties(errorName, errorSignature, errorArgs)把还原出的名称、签名与参数挂回错误对象。这一点在 web3.js 的高层封装如 web3-eth-contract中也被自动调用帮助开发者直接看到 InsufficientBalance 之类的可读错误名而非一串十六进制 data。八、扩展EIP-712 结构化数据哈希包还内置了 EIP-712 类型化数据typed data的支持。eip_712.ts 的getMessage对外导出别名为getEncodedEip712Data见 index.ts用于计算 EIP-712 签名所需的结构化消息哈希。其实现源自 EIP-712 规范实现包含getDependencies递归收集结构体类型依赖并去重encodeType/encodeData按规范编码类型串与数据哈希组合keccak256(0x1901 domainSeparator hashStruct(message))。这使web3-eth-abi不局限于合约调用还能服务于离线签名如 MetaMask 风格的eth_signTypedData场景。对应测试见 test/unit/get_encoded_eip712_data.test.ts。九、源码实现细节格式化与类型映射包内 src/utils.ts 提供了一批对编解码正确性至关重要的辅助函数理解它们有助于排查编码结果异常isAbiFunctionFragment/isAbiEventFragment/isAbiErrorFragment/isAbiConstructorFragment基于type字段区分四类 ABI 片段src/utils.ts编解码 API 均以此做入参合法性校验flattenTypes(includeTuple, puts)把带components的元组参数展开为规范签名串如tuple(uint256,string)[]供jsonInterfaceMethodToString生成myMethod((uint256,string),uint256)这类含元组的签名src/utils.tsmapStructToCoderFormat/mapStructNameAndType把简化结构体格式{ ParentStruct: { propertyOne: uint256 } }映射为标准 ABI 的{ type: tuple, components: [...] }形式src/utils.ts这就是前面encodeParameter支持嵌套结构体对象的底层机制formatParam处理与 Ethers V4 的向后兼容包括BigInt→ 十进制字符串、按位宽补零leftPad/rightPad、bytesN奇数长度十六进制自动补0formatOddHexstrings等src/utils.tsmapTypes一个值得注意的细节——把 Solidity 的function类型参数重映射为bytes24合约地址 选择器哈希的定长编码以保证与底层编码器的兼容src/utils.ts。这些细节解释了为什么encodeParameter(bytes32, 0xdf3234)会右侧补零、为什么 uint 类型会自动补足 256 位宽——ABI 规范要求所有静态类型按 32 字节256 位对齐所有动态类型string、bytes、数组、tuple先给出偏移量再附带长度与数据。十、测试体系如何验证编解码正确性包的单元测试位于 test/unit其中与本主题直接相关的包括encodeDecodeParams.test.ts参数编解码的成对验证api/functions_api.test.tsencodeFunctionSignature与encodeFunctionCall的合法/非法入参矩阵测试非法入参断言抛错api/events_api.test.ts事件签名编码测试decodeMethodParamsAndReturn.test.ts函数调用数据与返回值的解码测试decodeContractErrorData.test.ts错误数据解码测试get_encoded_eip712_data.test.tsEIP-712 消息哈希测试。测试夹具统一维护在 test/fixtures/data.ts包含validFunctionsSignatures、validFunctionsCall、inValidFunctionsSignatures等数据表。例如functions_api.test.ts用it.each(validFunctionsSignatures)逐条断言encodeFunctionSignature(input) output并断言非法输入抛出AbiError。这种输入-输出对 异常断言的测试模式恰好为使用者在迁移或自定义编码器时提供了可直接对照的黄金样本。运行全部单元测试cd packages/web3-eth-abi yarn test:unit结语web3-eth-abi以极小的 API 面覆盖了 EVM 交互中最关键的翻译环节函数选择器与 calldata 的编解码、参数含动态类型与嵌套结构体的编解码、事件日志的 data/topics 解码、错误数据的还原以及 EIP-712 结构化消息哈希。透过 coders 底层的 tuple 编码模型与 utils.ts 的类型映射可以看到它对 Solidity ABI 规范的完整遵守以及对 Ethers V4 兼容性的细致处理。无论你是在构造交易、解析事件还是调试 revert这份EVM 数据翻译指南都值得放进你的工具箱。【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表