以太坊合约ABI规范见 官方文档-合约ABI规范
这里通过实验来印证 ABI 编码在 Event log 中的实现。
本地启动 ganache
首先在本地启动 ganache 作为 evm 链单节点,稍后与以太坊的交互都是通过与本地的 ganache 节点交互来实现的。
Ganache官网
将 ganache 节点的端口设置为以太坊的默认RPC端口8545,方便后续的调试。
创建 hardhat 项目
执行如下命令
$ mkdir abi$ cd abi $ npm init$ npm install -D hardhat
然后在abi项目根目录中初始化 hardhat 项目
$ npx hardhat init
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888👷 Welcome to Hardhat v2.22.8 👷✔ What do you want to do? · Create a JavaScript project
✔ Hardhat project root: · /Users/dongling/mycode/blockchain/abi
✔ Do you want to add a .gitignore? (Y/n) · y
✔ Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)? (Y/n) · ynpm install --save-dev "@nomicfoundation/hardhat-toolbox@^5.0.0"
⠋
创建合约文件
在 abi 项目中创建合约文件 contracts/EventFactory.sol
,内容如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;contract EventFactory {event Transfer(address indexed from,address indexed to,uint256 indexed tokenId);event Transfer2(address indexed from, address indexed to, uint256 tokenId);event Transfer3(address indexed from, address to, uint256 tokenId);event Msg(address indexed from,address indexed to,string indexed msg1,string msg2);event Msg2(address indexed from,address indexed to,string msg1,string msg2);address public owner;constructor() {owner = msg.sender;}function emitTransfer(address to, uint tokenId) public {emit Transfer(msg.sender, to, tokenId);}function emitTransfer2(address to, uint tokenId) public {emit Transfer2(msg.sender, to, tokenId);}function emitTransfer3(address to, uint tokenId) public {emit Transfer3(msg.sender, to, tokenId);}function emitMsg(address to,string memory msg1,string memory msg2) public {emit Msg(msg.sender, to, msg1, msg2);}function emitMsg2(address to,string memory msg1,string memory msg2) public {emit Msg2(msg.sender, to, msg1, msg2);}
}
编译合约:
$ npx hardhat compile
Compiled 2 Solidity files successfully (evm target: paris).
创建安装合约的脚本
创建 ignition/modules/EventFactory.js
,内容如下
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules')module.exports = buildModule('EventFactoryModule', (m) => {const eventFactory = m.contract('EventFactory')return { eventFactory }
})
部署合约
ganache 中链启动的时候,默认会创建10个 account,并且给每一个 account 设置初始的 balance 为 100ETH。
部署合约的时候,默认使用的是 ganache 中的第一个 account
在 abi 项目中执行 npx hardhat ignition deploy ./ignition/modules/EventFactory.js --network localhost
命令部署合约
$ npx hardhat ignition deploy ./ignition/modules/EventFactory.js --network localhost
✔ Confirm deploy to network localhost (1337)? … yes
Hardhat Ignition 🚀Deploying [ EventFactoryModule ]Batch #1Executed EventFactoryModule#EventFactory[ EventFactoryModule ] successfully deployed 🚀Deployed AddressesEventFactoryModule#EventFactory - 0x87400459ABbd2D180c4DDae75D0060b16775cc84
可以看到 EventFactory 合约被部署后的地址是 0x87400459ABbd2D180c4DDae75D0060b16775cc84
。
Evm 中合约地址是通过如下方式计算得到的:
contract_address = keccak256(rpl_encode(creator_account_address, creator_nonce))
不同人的操作得到的地址结果可能不同。
可以在 ganache 中看到创建合约新生成的 block:
创建测试脚本
在 abi 项目根目录中,创建 config.js
,将刚才部署的合约的地址填写进去,内容如下:
const allchain = {localchain: 'http://localhost:8545',
}const Contracts = {eventFactory: {address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',},
}module.exports = {allchain,Contracts,
}
然后创建文件 test/EventFactory.js
,内容如下:
const contractName = 'EventFactory'const eventFactoryABI =require(`../artifacts/contracts/${contractName}.sol/${contractName}.json`)['abi']
const { allchain, Contracts } = require('../config.js')describe('EventFactory', () => {let provider, allAccounts, eventFactorylet alice, bobbeforeEach(async () => {provider = new ethers.JsonRpcProvider(allchain.localchain)allAccounts = await provider.listAccounts();[alice, bob] = allAccountseventFactory = new ethers.Contract(Contracts.eventFactory.address,eventFactoryABI,provider)})describe('EventFactory contract', () => {it('emit events', async () => {console.log('alice:', alice.address)console.log('bob:', bob.address)let tx = await eventFactory.connect(alice).emitTransfer(bob.address, 5)await tx.wait()tx = await eventFactory.connect(alice).emitTransfer2(bob.address, 5)await tx.wait()tx = await eventFactory.connect(alice).emitTransfer3(bob.address, 5)await tx.wait()tx = await eventFactory.connect(alice).emitMsg(bob.address,'how are you','good better best never let it rest, till good is better, and better is best')await tx.wait()tx = await eventFactory.connect(alice).emitMsg(bob.address,'The quick brown fox jumps over the lazy dog','good better best never let it rest, till good is better, and better is best')await tx.wait()tx = await eventFactory.connect(alice).emitMsg2(bob.address,'how are you','good better best never let it rest, till good is better, and better is best')await tx.wait()})it.skip('filter event logs', async () => {let events = ['Transfer', 'Transfer2', 'Transfer3', 'Msg', 'Msg2']for (let event of events) {let eventResult = await eventFactory.queryFilter(eventFactory.filters[event],43 // fromBlock, default 0; inclusive//100 // toBlock, default 'latest'; inclusive)console.log('Event[%s]:\n', event, eventResult)console.log('==========================================')}})})
})
执行测试用例
$ npx hardhat test test/EventFactory.js EventFactoryEventFactory contract
alice: 0x01a387460D96Ca136631232765e6Ff7e855Ef283
bob: 0x784E8581a693308781223b60D05bce7608f0cadf✔ emit events (25173ms)- filter event logs1 passing (25s)1 pending
可以看到调用合约方法的测试用例 emit events
执行成功。
接下来将 emit events
测试用例的 it
修改成 it.skip
,将filter event logs
测试用例的 it.skip
修改成 it
,再次执行测试命令npx hardhat test test/EventFactory.js
,这次将会略过 emit events
,只执行 filter event logs
来过滤 emit events
步骤中发出的 event。执行结果如下:
$ npx hardhat test test/EventFactory.js EventFactoryEventFactory contract- emit events
Event[Transfer]:[EventLog {provider: JsonRpcProvider {},transactionHash: '0xe0de4c0f343c4afeda73b80e14f5652d7b028b96f02c7c44c3f88212930f604f',blockHash: '0x169bd2547de85358584d74bc50bd81c4e922bfb87c880f42eb213b6af34701fb',blockNumber: 43,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x',topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283','0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf','0x0000000000000000000000000000000000000000000000000000000000000005'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Transfer',anonymous: false},args: Result(3) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf',5n]}
]
==========================================
Event[Transfer2]:[EventLog {provider: JsonRpcProvider {},transactionHash: '0x075ca454a8cc870c157431e663fc368d9cc9a1905a6538fed183f7b50b272af2',blockHash: '0xfce37abbea895037cae15724d64f30716f33551140e7c334e434bd8ad688c6b2',blockNumber: 44,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x0000000000000000000000000000000000000000000000000000000000000005',topics: ['0x6a0adf17eb20399b431dc509145e9448c952cb299ead47af739fed7289c553f5','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283','0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Transfer2',anonymous: false},args: Result(3) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf',5n]}
]
==========================================
Event[Transfer3]:[EventLog {provider: JsonRpcProvider {},transactionHash: '0xad771fe51e7a4cb8cff316ba15c5745f30c200025c834cc0931166bed8f8b34f',blockHash: '0x7c0d73ff6300af7c57698170717e48d3e6d65323583d6d9ce2b1dd7ecd55dd13',blockNumber: 45,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf0000000000000000000000000000000000000000000000000000000000000005',topics: ['0x2411965a414db56a3afdcb174d6049ed9467924617e5455ab694e45e8f69fd80','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Transfer3',anonymous: false},args: Result(3) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf',5n]}
]
==========================================
Event[Msg]:[EventLog {provider: JsonRpcProvider {},transactionHash: '0x4f8b80d243832d9ad25f2df3dbdb7f7e63f968e0a552f612d3f6141bb17e153b',blockHash: '0x6965462fd249a980aeb43e7713f2b2bd3370ed794b677d4be0e221db7174ccec',blockNumber: 46,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000',topics: ['0x8fc6e328d73f399066d9993d9bddb182ebabfc570eb5c4ba235fb99073172bcc','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283','0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf','0x6b69b0c17a167ec6f6698d7336ec915e3d1b019470128672b70c20c7106c8bb3'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Msg',anonymous: false},args: Result(4) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf',[Indexed],'good better best never let it rest, till good is better, and better is best']},EventLog {provider: JsonRpcProvider {},transactionHash: '0xef71afe9fa8d516a4f250c4e64710fb5004b6168ee3441ba1900e9bd30fbc315',blockHash: '0x9e397437a3c03b142c46f94cf7743345b91be15e095c4d10badc1b6d7b15b0af',blockNumber: 47,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000',topics: ['0x8fc6e328d73f399066d9993d9bddb182ebabfc570eb5c4ba235fb99073172bcc','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283','0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf','0x4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Msg',anonymous: false},args: Result(4) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf',[Indexed],'good better best never let it rest, till good is better, and better is best']}
]
==========================================
Event[Msg2]:[EventLog {provider: JsonRpcProvider {},transactionHash: '0x4a76b83604fa7587f2ac8f05842a7f73b0a68fe5d1eeebb8f8ea6d1ae740d6b4',blockHash: '0x8e164c2dfbe0e7e71f577ecb780ee015b6142b1ed761af81819afcac1eb09e22',blockNumber: 48,removed: false,address: '0x87400459ABbd2D180c4DDae75D0060b16775cc84',data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b686f772061726520796f75000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000',topics: ['0x24b5fc4f941a6ebaa645b15f9bdeb17b7f2eb51f22fcada5f988e1ffd6b518ee','0x00000000000000000000000001a387460d96ca136631232765e6ff7e855ef283','0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf'],index: 0,transactionIndex: 0,interface: Interface {fragments: [Array],deploy: [ConstructorFragment],fallback: null,receive: false},fragment: EventFragment {type: 'event',inputs: [Array],name: 'Msg2',anonymous: false},args: Result(4) ['0x01a387460D96Ca136631232765e6Ff7e855Ef283','0x784E8581a693308781223b60D05bce7608f0cadf','how are you','good better best never let it rest, till good is better, and better is best']}
]
==========================================✔ filter event logs (89ms)1 passing (126ms)1 pending
分析 Event Log
ABI 规范 Event:
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract’s address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3 (for non-anonymous events) or 4 (for anonymous ones), are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event.
In effect, a log entry using this ABI is described as:
address
: the address of the contract (intrinsically provided by Ethereum);
topics[0]
:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of
is a function that simply returns the canonical type of a given argument, e.g. foruint indexed foo
, it would returnuint256
). This value is only present intopics[0]
if the event is not declared asanonymous
;
topics[n]
:abi_encode(EVENT_INDEXED_ARGS[n - 1])
if the event is not declared asanonymous
orabi_encode(EVENT_INDEXED_ARGS[n])
if it is (EVENT_INDEXED_ARGS
is the series ofEVENT_ARGS
that are indexed);
data
: ABI encoding ofEVENT_NON_INDEXED_ARGS
(EVENT_NON_INDEXED_ARGS
is the series ofEVENT_ARGS
that are not indexed,abi_encode
is the ABI encoding function used for returning a series of typed values from a function, as described above).
ABI 规范中文版 事件:
事件是Ethereum日志/事件观察协议的一个抽象。日志条目提供了合约的地址, 一系列最多四个主题和一些任意长度的二进制数据。 事件利用现有的函数ABI,以便将其(连同接口规范)解释为一个正确的类型化结构。
给定一个事件名称和一系列的事件参数,我们把它们分成两个子系列:那些有索引的和那些没有索引的。 那些被索引的参数,可能多达3个(对于非匿名事件)或4个(对于匿名事件), 与事件签名的Keccak散列一起使用,形成日志条目的主题。 那些没有索引的则构成事件的字节数组。
实际上,使用该ABI的日志条目被描述为:
address
: 合约的地址(由以太坊真正提供);
topics[0]
:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of
是一个可以返回给定参数的权威类型的函数,例如,对uint indexed foo
它会返回uint256
)。 如果事件被声明为anonymous
,那么topics[0]
不会被生成;
topics[n]
: 如果事件没有被声明为anonymous
, 则为abi_encode(EVENT_INDEXED_ARGS[n - 1])
或者如果它被声明为该类型,则为abi_encode(EVENT_INDEXED_ARGS[n])
(EVENT_INDEXED_ARGS
是被索引的EVENT_ARGS
的系列);
data
:EVENT_NON_INDEXED_ARGS
的ABI编码 (EVENT_NON_INDEXED_ARGS
是一系列没有索引的EVENT_ARGS
,abi_encode
是ABI编码函数, 用于从一个函数返回一系列类型的值,如上所述)。
根据以上规范说明,我们来分析一下
Event Transfer
topics[0]
= 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
正是事件签名 "Transfer(address,address,uint256)"
的 keccak256 哈希值。
字符串 "Transfer(address,address,uint256)"
的 16 进制编码为 5472616e7366657228616464726573732c616464726573732c75696e7432353629
,使用 evm 节点的 JSON-RPC web3_sha3
计算 keccak256 哈希值,如下所示:
$ curl --request POST \--url http://localhost:8545/ \--header 'user-agent: vscode-restclient' \--data '{"jsonrpc": "2.0","id": 64,"method": "web3_sha3","params": ["0x5472616e7366657228616464726573732c616464726573732c75696e7432353629"]}'{"id":64,"jsonrpc":"2.0","result":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}
结果与 topics[0]
相等。
topics[1]
和 topics[2]
是 20字节的 alice.address 0x01a387460D96Ca136631232765e6Ff7e855Ef283
和 bob.address 0x784E8581a693308781223b60D05bce7608f0cadf
左填充到32字节后的结果。
topics[3]
是数字 5
的32字节编码结果。
Event Transfer2
Transfer2
与 Transfer
相似,只是第三个参数 tokenId
没有使用 indexed
进行修饰,所以 tokenId
没有放在在 topics
中进行编码,而是放在 data
中进行编码;tokenId
是 uint256 类型,所以编码结果是32字节的内容,用16进制表示,结果是64个字符(未计算 0x
前缀): 0x0000000000000000000000000000000000000000000000000000000000000005
。
Event Transfer3
由于只有第一个参数有 indexed 修饰,所以后面的两个参数都放入了 data 字段
0x000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf0000000000000000000000000000000000000000000000000000000000000005
前64个字符 000000000000000000000000784e8581a693308781223b60d05bce7608f0cadf
正是 address to = 784e8581a693308781223b60d05bce7608f0cadf
这个20字节、40字符的地址补足为32字节后的16进制表示,
而后64个字符 0000000000000000000000000000000000000000000000000000000000000005
正是 uint256 tokenId = 5 的 16进制表示。
Event Msg
-
topics[0]
=0x8fc6e328d73f399066d9993d9bddb182ebabfc570eb5c4ba235fb99073172bcc
是事件签名"Msg(address,address,string,string)"
的 keccak256 哈希值。
字符串"Msg(address,address,string,string)"
的16进制编码结果是4d736728616464726573732c616464726573732c737472696e672c737472696e6729
,使用 JSON-RPCweb3_sha3
验证其 keccak256 哈希值:$ curl --request POST \--url http://localhost:8545/ \--header 'user-agent: vscode-restclient' \--data '{"jsonrpc": "2.0","id": 64,"method": "web3_sha3","params": ["0x4d736728616464726573732c616464726573732c737472696e672c737472696e6729"]}'{"id":64,"jsonrpc":"2.0","result":"0x8fc6e328d73f399066d9993d9bddb182ebabfc570eb5c4ba235fb99073172bcc"}
结果符合预期。
-
topics[1]
、topics[2]
为address
左填充到32字节的16进制编码结果。 -
打印的日志用有两个
Event Msg
:
第一个topics[3]
=0x6b69b0c17a167ec6f6698d7336ec915e3d1b019470128672b70c20c7106c8bb3
由test/EventFactory.js
中的代码可以看到,该 topic 对应的原始值为"how are you"
,其16进制表示结果为686f772061726520796f75
,使用 JSON-RPCweb3_sha3
计算keccak256("how are you")
:$ curl --request POST \--url http://localhost:8545/ \--header 'user-agent: vscode-restclient' \--data '{"jsonrpc": "2.0","id": 64,"method": "web3_sha3","params": ["0x686f772061726520796f75"]}'{"id":64,"jsonrpc":"2.0","result":"0x6b69b0c17a167ec6f6698d7336ec915e3d1b019470128672b70c20c7106c8bb3"}
符合预期。
第二个
topics[3]
=0x4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15
,对应的原始值为"The quick brown fox jumps over the lazy dog"
,其16进制编码结果为:54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67
,使用 JSON-RPCweb3_sha3
计算keccak256("The quick brown fox jumps over the lazy dog")
:$ curl --request POST \--url http://localhost:8545/ \--header 'user-agent: vscode-restclient' \--data '{"jsonrpc": "2.0","id": 64,"method": "web3_sha3","params": ["0x54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"]}'{"id":64,"jsonrpc":"2.0","result":"0x4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15"}
符合预期。
-
data
部分:
两个 Msg 的 msg2 相同,所以 data 部分的数据也相同,同为0x0000000000000000000000000000000000000000000000000000000000000020_000000000000000000000000000000000000000000000000000000000000004b_676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000
这部分数据除去开头的前缀
0x
,共计320个字符,也就是160个字节的数据。这段数据是包含75个字符的字符串"good better best never let it rest, till good is better, and better is best"
ABI编码后的结果,分成了3部分,如上所示,用_
隔开了每一个部分的最后一个字节。各部分解释如下:-
第一部分
0000000000000000000000000000000000000000000000000000000000000020
:
32个字节,0x20 的数值为32,表示从 data[32] 开始表示数据的长度,表示长度的数据也是32字节;这里将 data 看作是字节数组,而不是16进制的字符串 -
第二部分
000000000000000000000000000000000000000000000000000000000000004b
:
32个字节,0x4b 的数值为75,正是原始字符串的长度,表示从 data[32+32] 开始接下来的75个字节是msg2数据 -
第三部分
676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000
:
96个字节,前75个字节为字符串"good better best never let it rest, till good is better, and better is best"
的16进制编码,后面的21个字节为0,用于 padding,与前面的75个字节构成32个字节的整数倍。
-
Event Msg2
由于 msg1 和 msg2 都没有被 indexed 修饰,所以 msg1="how are you"
和 msg2="good better best never let it rest, till good is better, and better is best"
都被 ABI 编码进了 data
字段,为:0x0000000000000000000000000000000000000000000000000000000000000040_0000000000000000000000000000000000000000000000000000000000000080_000000000000000000000000000000000000000000000000000000000000000b_686f772061726520796f75000000000000000000000000000000000000000000_000000000000000000000000000000000000000000000000000000000000004b_676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000
。
这部分去掉开头的 0x
,共计512个16进制字符,也就是256个字节的数据。这段数据是["how are you", "good better best never let it rest, till good is better, and better is best"]
ABI编码后的结果,分成了6部分,如上所示,用 _
隔开了每一部分。各部分解释如下:
- 第一部分
0000000000000000000000000000000000000000000000000000000000000040
32个字节,0x40
的数值为64,表示 data[64:64+32] 是 msg1 的数据长度。 - 第二部分
0000000000000000000000000000000000000000000000000000000000000080
32个字节,0x80
的数值为128,表示 data[128:128+32] 是 msg2 的数据长度。 - 第三部分
000000000000000000000000000000000000000000000000000000000000000b
offset=64,是由第一部分指向的数据长度,32个字节,0xb
数值为11,表示后续11个字节是 msg1 的数据 - 第四部分
686f772061726520796f75000000000000000000000000000000000000000000
32个字节,前11个字节是"how are you"
的16进制编码,后面的21个字节为0,用于 padding,与前面的11字节构成32个字节的整数倍。 - 第五部分
000000000000000000000000000000000000000000000000000000000000004b
offset=128,是第二部分指向的数据长度,32个字节,0x4b
数值为75,表示后续的75个字节是 msg2 的数据 - 第六部分
676f6f64206265747465722062657374206e65766572206c657420697420726573742c2074696c6c20676f6f64206973206265747465722c20616e64206265747465722069732062657374000000000000000000000000000000000000000000
96个字节,前75个字节是"good better best never let it rest, till good is better, and better is best"
的16进制编码,后面的21个字节为0,用于 padding,与前面的75个字节构成32个字节的整数倍。
至此,我们通过实验数据印证了 Ethereum的 ABI 编码结果。