據(jù)的編碼與解碼全解析)
web3-eth-abi 使用指南web3.js 中 EVM 數(shù)據(jù)的編碼與解碼全解析【免費(fèi)下載鏈接】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導(dǎo)讀web3-eth-abi是 web3.js 4.x 的官方子包職責(zé)單一而明確負(fù)責(zé) EVM以太坊虛擬機(jī)輸入/輸出數(shù)據(jù)的 ABI 編碼與解碼。無論是構(gòu)造一筆合約調(diào)用、解讀合約返回值、解析事件日志還是還原合約拋出的錯誤信息都離不開這一層翻譯器。本文將圍繞該包的編碼/解碼 API 展開完整繼承 README 中的安裝方式與工程腳本并結(jié)合 src 下的源碼實現(xiàn)與 test 單元測試帶你從會用深入到懂原理掌握函數(shù)簽名、參數(shù)、日志、錯誤數(shù)據(jù)以及 EIP-712 結(jié)構(gòu)化數(shù)據(jù)的編解碼全鏈路。一、包定位與安裝web3-eth-abi是 [web3.js][repo] 的獨(dú)立子包其 package 描述為 Web3 module encode and decode EVM in/output見 package.json。它提供了一批對 Solidity 函數(shù)、事件、錯誤、參數(shù)的編碼與解碼函數(shù)底層采用 TypeScript 編寫支持 ESM 與 CommonJS 雙模塊體系exports字段同時暴露import與require入口Node.js 環(huán)境要求14。安裝方式官方支持 NPM 與 Yarn 兩種方式# 使用 NPM npm install web3-eth-abi # 使用 Yarn yarn add web3-eth-abi兩種使用姿勢方式一通過完整的web3包訪問。安裝web3后ABI 工具函數(shù)會掛在web3.eth.abi命名空間下。在 eth.exports.ts 中可以看到export * as abi from web3-eth-abi;即web3.eth.abi正是本包的完整轉(zhuǎn)發(fā)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 }, ], });方式二按需引入獨(dú)立包更利于構(gòu)建輕量應(yīng)用。這正是官方推薦的 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 完全一致區(qū)別僅在于依賴體積與打包產(chǎn)物。環(huán)境前提NodeJSLTS 版本即可包聲明engines.node 14包管理器NPM 或 Yarn倉庫本身使用 Yarn 工作區(qū) Lerna 管理多包二、包級腳本速覽官方 README 列出了包的工程腳本與 package.json 中的 scripts 一一對應(yīng)ScriptDescriptionclean使用rimraf移除dist/實際為dist與libbuild使用tsc構(gòu)建本包及其依賴包lint使用eslint檢查代碼lint:fix使用eslint檢查并自動修復(fù)警告format使用prettier格式化代碼test使用jest運(yùn)行單元測試test:integration使用jest運(yùn)行/test/integration下的測試test:unit使用jest運(yùn)行/test/unit下的測試值得留意的是 build 命令被拆分為build:cjs、build:esm、build:types三個并行任務(wù)通過concurrently驅(qū)動分別產(chǎn)出 CommonJS、ES Module 與類型聲明文件這也解釋了包入口中exports的多目標(biāo)映射設(shè)計。三、核心 API 全景從入口到實現(xiàn)包的總?cè)肟?src/index.ts 將能力劃分為五個 API 模塊并額外導(dǎo)出decodeContractErrorData與 EIP-712 的getMessage別名為getEncodedEip712Data模塊文件提供的能力api/functions_api.ts函數(shù)簽名與函數(shù)調(diào)用的編解碼api/events_api.ts事件簽名編碼api/logs_api.ts事件日志data topics解碼api/parameters_api.ts單個/批量參數(shù)的編解碼api/errors_api.ts自定義錯誤簽名編碼decode_contract_error_data.ts從 RPC 錯誤數(shù)據(jù)中還原錯誤名稱/簽名/參數(shù)eip_712.tsEIP-712 類型化數(shù)據(jù)消息哈希所有 API 最終都匯聚到 coders/encode.ts 與 coders/decode.ts 這兩個底層編解碼器以及 coders/base 下針對address、array、bool、bytes、number、string、tuple等基礎(chǔ)類型的逐一實現(xiàn)。下面按使用場景逐一深入。四、函數(shù)調(diào)用從簽名到完整 calldata4.1 編碼函數(shù)簽名選擇器以太坊交易中的函數(shù)選擇器selector是函數(shù)簽名字符串name(param1Type,param2Type,...)的keccak256哈希的前 4 字節(jié)。實現(xiàn)位于 functions_api.tsexport const encodeFunctionSignature (functionName: string | AbiFunctionFragment): string { // 入?yún)⑿r灱炔皇亲址膊皇呛戏ǖ?AbiFunctionFragment 時拋 AbiError let name: string; if (functionName (typeof functionName function || typeof functionName object)) { name jsonInterfaceMethodToString(functionName); // 將 JSON ABI 片段規(guī)范化為 myMethod(uint256,string) } else { name functionName; } return sha3Raw(name).slice(0, 10); // keccak256 前 4 字節(jié)含 0x 前綴共 10 字符 };它接受兩種入?yún)⑿问阶址仨毷呛瘮?shù)名(參數(shù)類型列表)形式例如myMethod(uint256,string)、safeTransferFrom(address,address,uint256,bytes)JSON ABI 片段對象包含name、type: function、inputs數(shù)組的完整函數(shù)描述。官方案例// 傳入 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 展開為規(guī)范類型串例如myMethod((uint256,string),uint256)這種含元組的簽名也能正確生成。4.2 編碼完整函數(shù)調(diào)用encodeFunctionCall(jsonInterface, params)把函數(shù)選擇器與編碼后的參數(shù)拼接成完整的 calldatafunctions_api.tsreturn ${encodeFunctionSignature(jsonInterface)}${encodeParameters( jsonInterface.inputs ?? [], params ?? [], ).replace(0x, )};即calldata 4 字節(jié)選擇器 去除0x前綴的參數(shù) 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 參數(shù) 動態(tài) string 參數(shù)的 ABI 編碼)對balanceOf這種單地址參數(shù)的調(diào)用const sig web3.eth.abi.encodeFunctionCall( { inputs: [{ name: account, type: address }], name: balanceOf, outputs: [{ name: , type: uint256 }], stateMutability: view, type: function, }, [0x1234567890123456789012345678901234567890], ); // 0x70a0823100000000000000000000000012345678901234567890123456789012345678904.3 解碼函數(shù)調(diào)用數(shù)據(jù)decodeFunctionCall(functionsAbi, data, methodSignatureProvided true)用于把鏈上/交易中的 calldata 還原為參數(shù)對象functions_api.tsconst value methodSignatureProvided data data.length 10 data.startsWith(0x) ? data.slice(10) // 默認(rèn)剝離前 4 字節(jié)選擇器 : data; const result decodeParameters([...functionsAbi.inputs], value); return { ...result, __method__: jsonInterfaceMethodToString(functionsAbi) };關(guān)鍵行為methodSignatureProvided默認(rèn)true為true時自動剝離開頭的 4 字節(jié)選擇器若你傳入的 data 本身不含選擇器應(yīng)傳false返回對象除按索引0、1與按參數(shù)名_greeting命名的字段外還附帶__length__參數(shù)個數(shù)與__method__規(guī)范化方法簽名。官方示例解碼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 解碼函數(shù)返回值decodeFunctionReturn(functionsAbi, returnValues)專門處理合約調(diào)用的返回數(shù)據(jù)functions_api.ts其設(shè)計有兩個貼心約定構(gòu)造函數(shù)的type constructor直接原樣返回因為構(gòu)造函數(shù)沒有返回值可解碼單返回值時直接返回該值本身多返回值時才返回帶__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五、參數(shù)級編解碼encodeParameter / encodeParameters5.1 編碼單個參數(shù)encodeParameter(abi, param)本質(zhì)是encodeParameters([abi], [param])的單元素特例parameters_api.ts。abi可以是類型字符串也可以是帶components的對象描述。官方示例覆蓋了定長/變長字節(jié)、數(shù)值、動態(tài)數(shù)組與嵌套結(jié)構(gòu)體// uint256uint 等價 web3.eth.abi.encodeParameter(uint256, 2345675643); // 0x000000000000000000000000000000000000000000000000000000008bd02b7b // bytes32 定長右側(cè)補(bǔ)零到 32 字節(jié) web3.eth.abi.encodeParameter(bytes32, 0xdf3234); // 0xdf32340000000000000000000000000000000000000000000000000000000000 // bytes 變長需要偏移量 長度 數(shù)據(jù)三段 web3.eth.abi.encodeParameter(bytes, 0xdf3234); // 0x0000000000000000000000000000000000000000000000000000000000000020 // 0000000000000000000000000000000000000000000000000000000000000003 // df32340000000000000000000000000000000000000000000000000000000000 // bytes32[] 動態(tài)數(shù)組 web3.eth.abi.encodeParameter(bytes32[], [0xdf3234, 0xfdfd]); // 0x...0020偏移...0002長度df3234...00 fdfd...00 // 嵌套結(jié)構(gòu)體簡化格式 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 字節(jié)一組5.2 編碼多個參數(shù)encodeParameters(abi: AbiInput[], params: unknown[])在 coders/encode.ts 中實現(xiàn)。注意它強(qiáng)校驗 ABI 數(shù)量與參數(shù)數(shù)量必須一致否則拋出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, ); }從實現(xiàn)可見參數(shù)編碼統(tǒng)一被包裝成一個匿名tuple交給encodeTuple處理返回值由 Uint8Array 轉(zhuǎn)為十六進(jìn)制字符串。官方示例const res web3.eth.abi.encodeParameters([uint256, string], [2345675643, Hello!%]); // 0x000000000000000000000000000000000000000000000000000000008bd02b7b // 0000000000000000000000000000000000000000000000000000000000000040 // 0000000000000000000000000000000000000000000000000000000000000007 // 48656c6c6f2125000000000000000000000000000000000000000000000000005.3 類型推斷編碼inferTypesAndEncodeParametersinferTypesAndEncodeParameters(params)是一個偷懶工具當(dāng)你不確定參數(shù)類型時它會從 JS 值推斷 ABI 類型coders/encode.ts。推斷規(guī)則在inferParamsAbi中實現(xiàn)coders/encode.ts數(shù)組→ 遞歸推斷后作為tuple處理其他值→ 通過toHex(value, true)推斷例如整數(shù)推斷為int256/uint256字符串推斷為string0x前綴十六進(jìn)制字符串推斷為bytes。官方文檔明確指出其適用邊界當(dāng)你明確知道參數(shù)類型時不要使用此方法請改用encodeParameters。類型推斷并不完美尤其在數(shù)組、非 256 位 uint、bytes 等場景下可能產(chǎn)生意外結(jié)果。 這是對生產(chǎn)代碼非常實用的忠告。六、事件與日志簽名編碼與日志解碼6.1 編碼事件簽名encodeEventSignature(functionName)與函數(shù)選擇器不同它返回的是完整的 32 字節(jié) keccak256 哈希事件沒有前 4 字節(jié)約定實現(xiàn)位于 events_api.tsreturn sha3Raw(name); // 不截斷返回完整 64 位十六進(jìn)制哈希官方示例const event web3.eth.abi.encodeEventSignature({ name: myEvent, type: event, inputs: [ { type: uint256, name: myNumber }, { type: bytes32, name: myBytes }, ], }); // 0xf2eeb729e636a8cb783be044acf6b7b1e2c5863735b60d6daae84c366ee87d97 // 經(jīng)典 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負(fù)責(zé)把它們還原為可讀參數(shù)其算法要點(diǎn)按indexed標(biāo)記拆分輸入indexed: true的參數(shù)從 topics 解碼非 indexed 參數(shù)從data解碼自動計算 topic 偏移offset topics.length - indexedInputs.length從而正確處理 topic[0] 是事件簽名非匿名事件的情況靜態(tài)類型直接解碼動態(tài)類型如 string、數(shù)組、結(jié)構(gòu)體僅返回原始 topic 值這符合 EVM 的規(guī)則——動態(tài)類型被索引時 topic 里存的是其 keccak 哈希無法還原原文。源碼通過STATIC_TYPES [bool, string, int, uint, address, fixed, ufixed]前綴匹配判斷l(xiāng)ogs_api.ts并單獨(dú)處理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 恰好對應(yīng)該事件的兩個 indexed 參數(shù)。若傳入包含 topic[0] 的完整 topicsoffset機(jī)制會自動跳過它。七、錯誤處理從 revert 數(shù)據(jù)還原錯誤信息7.1 編碼錯誤簽名encodeErrorSignature(functionName)errors_api.ts與事件簽名同理返回sha3Raw(name)的完整 32 字節(jié)哈希用于自定義錯誤Solidity 的error關(guān)鍵字的識別。7.2 解碼合約錯誤數(shù)據(jù)decodeContractErrorData這是排查鏈上交易失敗最有用的工具。decodeContractErrorData(errorsAbi, error)decode_contract_error_data.ts從 RPC 返回的Eip838ExecutionError.data中還原錯誤信息解碼流程遵循 EIP-838 的 Error ABI 編碼優(yōu)先在自定義錯誤 ABI 中匹配取error.data前 4 字節(jié)errorSha遍歷errorsAbi找到encodeErrorSignature(abi)前綴匹配的那一項然后按該項的inputs解碼剩余數(shù)據(jù)兜底識別兩個著名內(nèi)置錯誤0x08c379a0→Error(string)require/revert(msg)產(chǎn)生的錯誤按單個string message解碼0x4e487b71→Panic(uint256)Solidity 0.8 算術(shù)溢出等 panic按單個uint256 code解碼解碼成功后通過error.setDecodedProperties(errorName, errorSignature, errorArgs)把還原出的名稱、簽名與參數(shù)掛回錯誤對象。這一點(diǎn)在 web3.js 的高層封裝如 web3-eth-contract中也被自動調(diào)用幫助開發(fā)者直接看到 InsufficientBalance 之類的可讀錯誤名而非一串十六進(jìn)制 data。八、擴(kuò)展EIP-712 結(jié)構(gòu)化數(shù)據(jù)哈希包還內(nèi)置了 EIP-712 類型化數(shù)據(jù)typed data的支持。eip_712.ts 的getMessage對外導(dǎo)出別名為getEncodedEip712Data見 index.ts用于計算 EIP-712 簽名所需的結(jié)構(gòu)化消息哈希。其實現(xiàn)源自 EIP-712 規(guī)范實現(xiàn)包含getDependencies遞歸收集結(jié)構(gòu)體類型依賴并去重encodeType/encodeData按規(guī)范編碼類型串與數(shù)據(jù)哈希組合keccak256(0x1901 domainSeparator hashStruct(message))。這使web3-eth-abi不局限于合約調(diào)用還能服務(wù)于離線簽名如 MetaMask 風(fēng)格的eth_signTypedData場景。對應(yīng)測試見 test/unit/get_encoded_eip712_data.test.ts。九、源碼實現(xiàn)細(xì)節(jié)格式化與類型映射包內(nèi) src/utils.ts 提供了一批對編解碼正確性至關(guān)重要的輔助函數(shù)理解它們有助于排查編碼結(jié)果異常isAbiFunctionFragment/isAbiEventFragment/isAbiErrorFragment/isAbiConstructorFragment基于type字段區(qū)分四類 ABI 片段src/utils.ts編解碼 API 均以此做入?yún)⒑戏ㄐ孕r瀎lattenTypes(includeTuple, puts)把帶components的元組參數(shù)展開為規(guī)范簽名串如tuple(uint256,string)[]供jsonInterfaceMethodToString生成myMethod((uint256,string),uint256)這類含元組的簽名src/utils.tsmapStructToCoderFormat/mapStructNameAndType把簡化結(jié)構(gòu)體格式{ ParentStruct: { propertyOne: uint256 } }映射為標(biāo)準(zhǔn) ABI 的{ type: tuple, components: [...] }形式src/utils.ts這就是前面encodeParameter支持嵌套結(jié)構(gòu)體對象的底層機(jī)制formatParam處理與 Ethers V4 的向后兼容包括BigInt→ 十進(jìn)制字符串、按位寬補(bǔ)零leftPad/rightPad、bytesN奇數(shù)長度十六進(jìn)制自動補(bǔ)0formatOddHexstrings等src/utils.tsmapTypes一個值得注意的細(xì)節(jié)——把 Solidity 的function類型參數(shù)重映射為bytes24合約地址 選擇器哈希的定長編碼以保證與底層編碼器的兼容src/utils.ts。這些細(xì)節(jié)解釋了為什么encodeParameter(bytes32, 0xdf3234)會右側(cè)補(bǔ)零、為什么 uint 類型會自動補(bǔ)足 256 位寬——ABI 規(guī)范要求所有靜態(tài)類型按 32 字節(jié)256 位對齊所有動態(tài)類型string、bytes、數(shù)組、tuple先給出偏移量再附帶長度與數(shù)據(jù)。十、測試體系如何驗證編解碼正確性包的單元測試位于 test/unit其中與本主題直接相關(guān)的包括encodeDecodeParams.test.ts參數(shù)編解碼的成對驗證api/functions_api.test.tsencodeFunctionSignature與encodeFunctionCall的合法/非法入?yún)⒕仃嚋y試非法入?yún)嘌話佸eapi/events_api.test.ts事件簽名編碼測試decodeMethodParamsAndReturn.test.ts函數(shù)調(diào)用數(shù)據(jù)與返回值的解碼測試decodeContractErrorData.test.ts錯誤數(shù)據(jù)解碼測試get_encoded_eip712_data.test.tsEIP-712 消息哈希測試。測試夾具統(tǒng)一維護(hù)在 test/fixtures/data.ts包含validFunctionsSignatures、validFunctionsCall、inValidFunctionsSignatures等數(shù)據(jù)表。例如functions_api.test.ts用it.each(validFunctionsSignatures)逐條斷言encodeFunctionSignature(input) output并斷言非法輸入拋出AbiError。這種輸入-輸出對 異常斷言的測試模式恰好為使用者在遷移或自定義編碼器時提供了可直接對照的黃金樣本。運(yùn)行全部單元測試cd packages/web3-eth-abi yarn test:unit結(jié)語web3-eth-abi以極小的 API 面覆蓋了 EVM 交互中最關(guān)鍵的翻譯環(huán)節(jié)函數(shù)選擇器與 calldata 的編解碼、參數(shù)含動態(tài)類型與嵌套結(jié)構(gòu)體的編解碼、事件日志的 data/topics 解碼、錯誤數(shù)據(jù)的還原以及 EIP-712 結(jié)構(gòu)化消息哈希。透過 coders 底層的 tuple 編碼模型與 utils.ts 的類型映射可以看到它對 Solidity ABI 規(guī)范的完整遵守以及對 Ethers V4 兼容性的細(xì)致處理。無論你是在構(gòu)造交易、解析事件還是調(diào)試 revert這份EVM 數(shù)據(jù)翻譯指南都值得放進(jìn)你的工具箱。【免費(fèi)下載鏈接】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創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考