Ethereum學習筆記 ---- 透過 Event 學習《合約ABI規範》

HorseShoe2016發表於2024-08-12

以太坊合約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. for uint indexed foo, it would return uint256). This value is only present in topics[0] if the event is not declared as anonymous;

  • topics[n]: abi_encode(EVENT_INDEXED_ARGS[n - 1]) if the event is not declared as anonymous or abi_encode(EVENT_INDEXED_ARGS[n]) if it is (EVENT_INDEXED_ARGS is the series of EVENT_ARGS that are indexed);

  • data: ABI encoding of EVENT_NON_INDEXED_ARGS (EVENT_NON_INDEXED_ARGS is the series of EVENT_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 的系列);

  • dataEVENT_NON_INDEXED_ARGS 的ABI編碼 ( EVENT_NON_INDEXED_ARGS 是一系列沒有索引的 EVENT_ARGSabi_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

Transfer2Transfer 相似,只是第三個引數 tokenId 沒有使用 indexed 進行修飾,所以 tokenId 沒有放在在 topics 中進行編碼,而是放在 data 中進行編碼;tokenIduint256 型別,所以編碼結果是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-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": ["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-RPC web3_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-RPC web3_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 編碼結果。

相關文章