Solidity Event是如何实现的

一个Solidity Event的定义如下:

event Deposit(
    address indexed _from,
    bytes32 indexed _id,
    uint _value
);
  • 最多 3 indexed参数.
  • 如果一个 indexed 参数的类型是大于32 bytes (比如 string 和 bytes), 就不存实际数据, 而是存数据的KECCAK256摘要(Digest).

EVM Log Primitives

先来看log0log1, ..., log4 EVM 指令.

EVM 日志功能使用不同的术语:

  • “topics”: 最多 4 个topics. 每个topic 32 bytes.
  • “data”: 数据是event的Payload. 可以是任意长度的bytes.

一个Solidity event 如何映射到一个log 原语?

  • 所有“non-indexed 参数” 被存为data.
  • 每个“indexed 参数” 被存为一个32 bytes的topic.

The log0 Primitive

Log0生成一个只有data的日志项目, 没有topic. data可以任意长度的 bytes.

下面看一个例子

pragma solidity ^0.4.18;
contract Logger {
  function Logger() public {
    log0(0xc0fefe);
  }
}

编译后

0x40指针是内存的空闲指针。第一部分将数据导入内存,第二部分将数据的大小在栈上准备好

memory: { 0x40 => 0x60 }

tag_1:
  // copy data into memory
  0xc0fefe
    [0xc0fefe]
  mload(0x40)
    [0x60 0xc0fefe]
  swap1
    [0xc0fefe 0x60]
  dup2
    [0x60 0xc0fefe 0x60]
  mstore
    [0x60]
    memory: {
      0x40 => 0x60
      0x60 => 0xc0fefe
    }

// calculate data start position and size
  0x20
    [0x20 0x60]
  add
    [0x80]
  mload(0x40)
    [0x60 0x80]
  dup1
    [0x60 0x60 0x80]
  swap2
    [0x60 0x80 0x60]
  sub
    [0x20 0x60]
  swap1
    [0x60 0x20]
log0

 

在执行log0前, 在栈上有2个参数: [0x60 0x20].

  • start: 0x60 是用来存放数据的内存指针.
  • size: 0x20 (或者32) 指定了载入数据的大小.

Logging With Topics

下面的例子使用 log2 原语. 第一个参数是数据(可以任意长字节),其后跟着 2个 topics (每个32 bytes ):

// log-2.sol
pragma solidity ^0.4.18;

contract Logger {
  function Logger() public {
    log2(0xc0fefe, 0xaaaa1111, 0xbbbb2222);
  }
}

汇编代码是非常相似的。唯一的区别是2个topics (0xbbbb22220xaaaa1111)被推倒了栈上:

tag_1:
  // push topics
  0xbbbb2222
  0xaaaa1111
// copy data into memory
  0xc0fefe
  mload(0x40)
  swap1
  dup2
  mstore
  0x20
  add
  mload(0x40)
  dup1
  swap2
  sub
  swap1

// create log
  log2

数据还是0xc0fefe, 被拷贝到内存. 执行 log2前,状态如下:

stack: [0x60 0x20 0xaaaa1111 0xbbbb2222]
memory: {
  0x60: 0xc0fefe
}
log2

头两个参数指定日志数据的内存领域,2个新增的栈上元素是2个32 bytes 的topics.

All EVM Logging Primitives

EVM支持5个日志的原语:

0xa0 LOG0
0xa1 LOG1
0xa2 LOG2
0xa3 LOG3
0xa4 LOG4

Logging Testnet Demo

pragma solidity ^0.4.18;

contract Logger {
  function Logger() public {
    log0(0x0);
    log1(0x1, 0xa);
    log2(0x2, 0xa, 0xb);
    log3(0x3, 0xa, 0xb, 0xc);
    log4(0x4, 0xa, 0xb, 0xc, 0xd);
  }
}

这个合约被部署到Rinkeby测试网络. 在: https://rinkeby.etherscan.io/tx/0x0e88c5281bb38290ae2e9cd8588cd979bc92755605021e78550fbc4d130053d1

Solidity Events

下面是一个Log事件,带着3个uint256的参数(non-indexed):

pragma solidity ^0.4.18;
contract Logger {
  event Log(uint256 a, uint256 b, uint256 c);
  function log(uint256 a, uint256 b, uint256 c) public {
    Log(a, b, c);
  }
}

生成的原始日志在:

https://rinkeby.etherscan.io/tx/0x9d3d394867330ae75d7153def724d062b474b0feb1f824fe1ff79e772393d395

数据是事件参数, ABI编码:

0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003

有一个topic, 1个 32 bytes hash:

0x00032a912636b05d31af43f00b91359ddcfddebcffa7c15470a13ba1992e10f0

这是事件类型的签名的SHA3哈希:

# Install pyethereum 
# https://github.com/ethereum/pyethereum/#installation
> from ethereum.utils import sha3
> sha3("Log(uint256,uint256,uint256)").hex()
'00032a912636b05d31af43f00b91359ddcfddebcffa7c15470a13ba1992e10f0'

因为Solidity事件为事件签名用掉了一个topic, 留给indexed 参数的只有3个topic.

Solidity Event With Indexed Arguments

下面是有一个 indexed uint256 参数的事件:

pragma solidity ^0.4.18;

contract Logger {
  event Log(uint256 a, uint256 indexed b, uint256 c);
  function log(uint256 a, uint256 b, uint256 c) public {
    Log(a, b, c);
  }
}

有2个topic:

0x00032a912636b05d31af43f00b91359ddcfddebcffa7c15470a13ba1992e10f0
0x0000000000000000000000000000000000000000000000000000000000000002
  • 第一个topic是方法的签名.
  • 第二个topic是indexed参数的值.

除了indexed 参数,数据是ABI编码:

0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000003

String/Bytes Event Parameter

将事件的参数设为字符串:

pragma solidity ^0.4.18;

contract Logger {
  event Log(string a, string indexed b, string c);
  function log(string a, string b, string c) public {
    Log(a, b, c);
  }
}

交易在: https://rinkeby.etherscan.io/tx/0x21221c2924bbf1860db9e098ab98b3fd7a5de24dd68bab1ea9ce19ae9c303b56

有2个topics:

0xb857d3ea78d03217f929ae616bf22aea6a354b78e5027773679b7b4a6f66e86b
0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510
  • 第一个topic是方法的签名.
  • 第二个topic是字符串参数的SHA256摘要.

验证“b”的哈希值和第二个topic是一样的:

>>> sha3("b").hex()
'b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510'

日志数据是2个non-indexed 字符串 “a” 和 “c”, ABI编码:

0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000001
6100000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001
6300000000000000000000000000000000000000000000000000000000000000

indexed 字符串参数没有被存储,所以DApp客户无法恢复它.

如果你确实需要最初的字符串, 那就记录2次, indexed 和 non-indexed:

event Log(string a, string indexed indexedB, string b);
Log("a", "b", "b");

 

 

参考

转载于:https://my.oschina.net/gavinzheng731/blog/1924015

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值