Ethereum学习笔记 ---- 通过 Event 学习《合约ABI规范》

以太坊合约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 编码结果。

posted on 2024-08-12 01:37  HorseShoe2016  阅读(72)  评论(0编辑  收藏  举报