事件和日志
事件使用了EVM内置日志功能,以太坊客户端可以使用JavaScript的回调函数监听事件。当事件触发时,会将事件及其参数存储到以太坊的日志中,与合约账户绑定。以太坊的日志是与区块相关的,只要区块可以访问则日志会一直存在。日志无法在合约中访问,即使是创建该日志的合约。
为了方便查找日志,可以给事件建立索引。每个事件最多有3个参数可以使用indexed关键字来设置索引。设置索引后,就可以根据参数查找日志,甚至可以根据特定的值来过滤。如果一个数组(包括bytes和string)被设置为索引,则会使用相对应的Keccak-256哈希值作为topic。所有未被索引的参数将作为日志的一部分储存起来。
以下代码创建了一个含有事件的合约。
contractFunding {
eventDeposit(
addressindexed_from,
bytes32indexed_id,
uint_value
);
functiondeposit(bytes32_id)payable{
//在JavaScriptAPI中过滤Deposit事件
//每次该函数的调用都可以被监听到
Deposit(msg.sender, _id,msg.value);
}
}
除了使用event以外,还可以使用一些底层接口来记录日志,这些接口可以用log0,log1,log2,log3和log4这些函数来访问。logi可以接受i+1个bytes32类型的参数,其中第一个参数用作日志的数据部分,其他参数作为topics保存下来。
上面的event代码与下面使用logi接口的代码效果一致,其中msg.value是第一个参数,作为日志的数据部分(未被索引),其他三个参数都被索引了。其中一个十六进制数大小等于这个事件的签名,keccak256("Deposit(address,hash256,uint256)"),这是因为事件的签名本身就是一个默认的topic。
log3(
msg.value,
0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
msg.sender,
_id
);
下面的代码简单展示了如何使用以太坊客户端JavaScripe API监听事件,下一章将会更加具体地介绍如何查看日志。
varabi=/* abi由编译器生成*/;
varFunding=web3.eth.contract(abi);
varfunding=Funding.at(0x123/*合约地址*/);
varevent=funding.Deposit();
//监听事件
event.watch(function(error, result){
// result包含各种信息,包括事件的多个参数
if(!error) console.log(result);
});
//也可以直接传一个回调函数给合约的事件,无须通过event的watch方法
varevent=funding.Deposit(function(error, result) {
if(!error) console.log(result);
});
智能合约的继承
Solidity支持继承与多重继承,它的继承系统与python很像,尤其是多重继承方面。值得注意的是,当一个通过继承产生的合约被部署到区块链上时,实际上区块链上只创建出了一个合约,所有基类合约的代码都会在子类合约中有一份拷贝。
下面的例子展示了如何进行合约的继承及其中可能存在的一些问题。
contractowned {
functionowned() { owner=msg.sender; }
addressowner;
}
contractmortalisowned {
functionkill() {
if(msg.sender==owner)selfdestruct(owner);
}
}
contractBase1ismortal {
functionkill() {/* do cleanup 1 */super.kill(); }
}
contractBase2ismortal {
functionkill() {/* do cleanup 2 */super.kill(); }
}
contractFinalisBase2, Base1 {
}
使用is关键字进行合约的继承,is关键字后面可以跟多个合约名,mortal是owned的派生合约,Base1与Base2是mortal的派生合约,Final是Base2和Base1的派生合约。
上面的代码中有一些细节需要注意。首先,Final派生自两个合约,Base2和Base1,这两个合约名的顺序是有意义的,继承时会按照从左到右的顺序依次继承重写。第二,合约中的函数都是虚函数,这意味着除非指定类名,否则调用的都是最后派生的函数。第三,Base1和Base2中都是用了super来指定继承序列上的上一级合约的kill函数,而不是使用mortal.kill。
在上面这个例子中,Final先继承Base2,然后继承Base1,此时Base2中的kill函数将会被Base1中的kill函数覆盖。从最后派生的合约(包括自身)开始,Final的继承序列是Final,Base1,Base2,mortal,owned。现在如果调用Final实例的kill函数,将会依次调用Base1.kill(),Base2.kill(),mortal.kill()。但是如果将Base1和Base2中的super.kill()使用mortal.kill()替代,那么在执行Base1.kill()之后将会直接执行mortal.kill(),Base2.kill()将会被绕过。进行多重继承时需要特别仔细小心的验证执行路径是否与期望的一致。
派生合约的构造函数需要提供基类合约构造函数的所有参数,实现的方法有以下两种:一种是直接在继承列表中指定(is Base(7)),这种方法在构造函数的参数为常量时比较方便,第二种是在定义派生类构造函数时提供(Base(_y * _y)),当基本合约的构造函数参数为变量时则必须使用第二种方式。在下面的例子中两种方式同时存在时,第二种方式生效而第一种将会被忽略。
contract Base {
uintx;
function Base(uint_x) { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint_y) Base(_y * _y) {
}
}
Solidity还允许使用抽象合约。抽象合约是指一个合约只有函数声明而没有函数的具体实现,即函数的声明使用“;”符号结束。只要合约中有一个函数没有具体的实现,即使合约中其他函数都已实现,这一抽象合约就不能被编译,但抽象合约仍可以作为基本合约被继承。
contract Feline {
function utterance() returns (bytes32);
}
contract Cat is Feline {
function utterance() returns (bytes32) { return "miaow"; }
}
章节小结
在以太坊平台上,智能合约是一段保存在区块链上的逻辑代码,运行在以太坊虚拟机中。使用智能合约,用户可以十分方便地在以太坊平台上创建去中心化应用。Solidity是一门用于编写智能合约的高级语言,拥有非常多的用户,可以极大地提高智能合约的开发效率。本章首先为读者介绍了什么是智能合约,通过一个简单的场景讲述了智能合约的工作原理及其优势。此外,本章还为读者介绍了部分智能合约的底层工作机制,包括以太坊虚拟机、存储方式、指令集和消息调用等内容。最后,本章还介绍了Solidity语言的基础知识,怎样使用Solidity语言编写一个智能合约。
下一章我们将介绍《区块链开发实战:HyperLedger关键技术与案例分析》的部分章节
感谢机械工业出版社华章分社的投稿,本文来自于华章出版的著作《以太坊技术详解》。
作者简介
闫莺(博士)
微软亚洲研究院主管研究员,区块链领域负责人,微软Coco区块链平台中国负责人
郑凯(博士)
电子科技大学教授,博士生导师,澳大利亚昆士兰大学计算机科学博士
郭众鑫
微软亚洲研究院研发工程师,微软Coco区块链平台核心开发者
扫描上方微信
备注“入群”,小助手拉你进群
活动多多,交流多多
矩阵财经出品
转载请注明:矩阵财经(矩阵数字经济智库)
领取专属 10元无门槛券
私享最新 技术干货