事件
事件是区块链底层虚拟机日志基础设施提供的一个便利接口。当触发事件时,事件中的参数存储到交易收据的日志字段中,日志是一种特殊的数据结构,这些日志与合约地址相关联,并随交易收据记录到区块链中。每条交易收据中可以包含 0 条或多条日志记录。在分布式应用中,如果监听了某事件,则当该事件发生时,便会触发应用相应的回调。
创建事件
Liquid 中使用结构体(struct
)语法定义事件。结构体中的每个成员都是事件的参数,为向 Liquid 告知该结构体用于定义事件,需要使用#[liquid(event)]
属性标注该结构体,例如:
#[liquid(event)]
struct Foo {
s: String,
i: i32,
}
上述代码中,我们定义了一个名为Foo
的事件,事件中包含两个参数,分别为String
及i32
。更进一步,还可以使用#[liquid(indexed)]
属性将事件参数标注为可被索引:
#[liquid(event)]
struct Foo {
#[liquid(indexed)]
s: String,
i: i32,
}
被索引的参数本身不会被保存,但是分布式应用可以通过被索引参数的值来对事件进行检索。在 Liquid 中,一个事件最多有四个参数可被用于被索引,但是第一个索引恒定为事件签名(事件名及其参数类型)的哈希值,因此在事件定义中,最多可以使用#[liquid(indexed)]
标注三个参数。
与状态变量定义类似,不能为定义事件的结构体添加可见性声明或模板参数。但和状态变量定义不同的是,其内部每个成员也不允许添加可见性声明。当前为与 Solidity 兼容,事件参数及索引参数的类型均需要在 Solidity 中存在相应的类型,具体的限制可参考类型一节,未来可能会放宽这一限制。
触发事件
在 Liquid 中,通过环境对象触发事件。环境对象由 Liquid 自动生成,可以在合约方法中通过调用self.env()
来获取环境对象。获取环境对象后,可以通过调用环境对象的emit
方法来触发我们之前定义的事件,例如:
self.env().emit(Foo {
s: String::from("hello"),
i: 42,
})
上述代码中,emit
方法以事件对象为参数,事件对象可通过结构体初始化语法直接进行构造。提供给emit
方法的参数类型一定需要是有效的事件类型(即被#[liquid(event)]
属性标注的结构体类型),否则会报出类型不匹配的编译错误。事件被出发后,对应交易的回执中会多出一条日志记录,例如:
"logs": [
{
"address": "0x6119432a43a2a5da27f31fa4912f1c43400b1690",
"data": "0x00000000000000000000000000000000000000000000000000000000000002a",
"topics": [
"0x1be2d150ed559c350b05f7dfa5a74669ec8d2ce63bb14c134730ffa02d2d111c",
"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
]
}
]
日志记录,address
字段是合约地址;data
字段中保存了非索引参数的ABI 编码,此处因为我们只有一个非索引参数i
,因此data
字段中只保存了它的值 42;topics
字段包含了两个可用于索引该事件的值,其中第一个是事件签名的哈希值,第二个则是事件中索引参数s
的值的哈希值。对于String
这类动态对象,Liquid 会将它们的哈希值作为事件索引,以提高检索效率并减少存储空间占用。因此若需要在应用中按照字符串检索事件,则需要在本地预先计算待检索字符串的哈希值。
注意
Liquid目前支持将合约编译为国密版本或非国密版本,两种版本的合约在计算哈希值时采用的哈希算法并不相同,分别为 sm3
和 keccak256
。如果需要使用动态对象索引事件,则请务必确保所使用的哈希算法与产生日志的Liquid合约一致。