以太坊合約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) · y
npm 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 #1
Executed EventFactoryModule#EventFactory
[ EventFactoryModule ] successfully deployed 🚀
Deployed Addresses
EventFactoryModule#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, eventFactory
let alice, bob
beforeEach(async () => {
provider = new ethers.JsonRpcProvider(allchain.localchain)
allAccounts = await provider.listAccounts()
;[alice, bob] = allAccounts
eventFactory = 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
EventFactory
EventFactory contract
alice: 0x01a387460D96Ca136631232765e6Ff7e855Ef283
bob: 0x784E8581a693308781223b60D05bce7608f0cadf
✔ emit events (25173ms)
- filter event logs
1 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
EventFactory
EventFactory 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 編碼結果。