原标题:《以太坊智能合约ABI、事件和日志》
作者:北京大学胡悦阳,本文仅代表作者观点
原文链接:https://mp.weixin.qq.com/s/QKz8r1MpntGuw_xM9gh9_w
智能合约是什么
智能合约是在区块链上运行的应用或程序。通常情况下,它们为一组具有特定规则的数字化协议,且该协议能够被强制执行。这些规则由计算机源代码预先定义,网络节点会复制和执行这些计算机源码。
简单的说,智能合约就是区块链上一个包含合约代码和存储空间的虚拟账户。
智能合约的行为由合约代码控制,而智能合约的账户存储则保存了合约的状态。
在以太坊平台上,智能合约的代码运行在以太坊虚拟机中,EVM是一个图灵完备的虚拟机,是以太坊的核心。
在以太坊的点对点网络中,每个全节点上都包含一个以太坊虚拟机,当节点需要打包或验证区块时,便将交易相关的可执行代码送入EVM中执行,执行的结果更新了以太坊账户的状态并被记录在区块链上。
智能合约的操作
创建智能合约的流程:
编写智能合约的代码编译智能合约的代码变成可以在EVM上执行的bytecode,同时可以通过编译获得智能合约的ABI部署到区块链,通过一个交易将bytecode存储在链上,并获得合约地址调用智能合约的流程:
发起一笔指向智能合约地址的交易,智能合约代码分布式地运行在网络中每个节点的以太坊虚拟机中,然后会获得交易的回执。回执保存交易的输入参数、输出、执行状态等。
举一个例子,首先在Remix平台编写一个智能合约Hello.sol
pragma?solidity?>=0.4.21;contract?Hello{????string?message;????event?SetMessage(string?_message);????function?set(string?memory?_message)?public?{????????message?=?_message;??????????emit?SetMessage(_message);????}????function?get()?public?view?returns(string?memory){????????return?message;????}}
编译成字节码,发起一笔交易部署到区块链中,得到交易回执。
这样就成功的将合约部署到区块链网络中了。
ABI是什么
上文提到调用智能合约,需要发起一笔指向合约地址的交易,以太坊节点会根据输入的信息,选择要执行合约中的哪一个函数和函数的参数。如何知道智能合约提供哪些函数以及参数要求呢,就需要用到ABI了。
合约ABI是在以太坊生态系统中与合约进行交互的标准方法,既可以从区块链外部进行,也可以用于合约间的交互。
ABI类似程序中的接口文档,描述了字段名称、字段类型、方法名称、参数名称、参数类型、方法返回值类型等。
通俗的解释:
ABI是合约接口的说明ABI定义与合约进行交互的数据编码解码规则以之前的Hello.sol为例,在编译合约的时候可以生成合约的ABI
,????????"name":?"SetMessage",????????"type":?"event"????},????{????????"inputs":?,????????"name":?"get",????????"outputs":?,????????"stateMutability":?"view",????????"type":?"function"????},????{????????"inputs":?,????????"name":?"set",????????"outputs":?,????????"stateMutability":?"nonpayable",????????"type":?"function"????}]
以太坊技术服务商OpenZeppelin发布Optimism智能合约审计报告:11月25日消息,以太坊技术服务商OpenZeppelin发布Optimism智能合约审计报告。从2021年3月15日开始,OpenZeppelin在7周的时间里与3位审计师一起审计了Optimism的代码库,共发现4个严重和4个高度严重的问题,并提出修改建议以减少代码的攻击面和提高其整体质量。[2021/11/25 7:10:43]
ABI各参数的意义
??name:函数名称
??type:方法类型,包括function、event等
??payable:布尔值,表明方法是否可以接受ether
??stateMutability:状态类型,包括pure(不读取区块链状态),view(和constant类型,只能查看,不会修改合约字段),nonpayable,payable。
??inputs:数组,描述参数的名称和类型
–name:参数名称
–type:参数类型
??outputs:和inputs一样,如果没有返回值,缺省是一个空数组
当用户调用一个合约时,要对调用的函数名和传入的函数参数进行编码,这样EVM才能执行,知道用户调用的是哪个接口,以及正确读取用户的参数,下面介绍以太坊是如何生成可供EVM调用的字节码的。
生成的字节码主要分两部分:函数选择器和参数编码。
函数选择器?FunctionSelector
函数调用的调用数据的前四个字节指定要调用的函数。它是通过将函数签名进行Keccak-256哈希运算后,取前四个字节得到的。
以Hello.sol为例,set函数的接口定义为:
functionset(stringmemory_message)public;
在python3环境下安装ethereum库
>?from?ethereum.utils?import?sha3>?sha3("set(string)").hex()'4ed3885e778f096a5fd9407b264b5478208ea71532d13d454b0307e5f1542101'>?sha3("set(string)").hex()'4ed3885e'
取前四个字节即:4ed3885e
参数编码ArgumentEncoding
从第五个字节开始,后面是编码参数。参数的编码根据类型的不同,编码方式也有所区别。主要分为固定类型和动态类型。
1、固定类型
?uint:M位的无符号整数类型,0<M<=256,M%8==0,如uint32,uint8,uint256.。
?int:M位的两个补码有符号整数类型.0<M<=256,M%8==0.
?uint和int:整型,分别是uint256和int256的别名。注意:函数参数类型是uint,转sha3码时要变成uint256。
?address:地址,20个字节,160bits,等同于uint160。
?bool:布尔类型,1个字节,true:1,false:0
?bytes:固定大小的字节数组,0<M<=32,byte都是bytes1的别名。
2、动态类型
?bytes:动态分配大小的字节数组
?string:动态大小UTF8编码的字符串
?:给定类型的元素的可变长度数组。
BXH笨小孩将于20:00正式登陆以太坊主链:据官方消息,DeFi平台BXH笨小孩于8月26日20:00(UTC+8)正式登陆以太坊主链。首批上线单币矿池:WETH、USDT、USDC、DAI、WBTC、UNI。同步开启流动性矿池:WBTC/USDT、ETH/USDT、ETH/USDC、WBTC/ETH、USDT/USDC、UNI/USDT。
BXH笨小孩以太坊跨链上线,初步完成多链计划,实现资产互通,为用户打造一站式全生态交易协议。[2021/8/26 22:39:25]
?:给定类型的元素的定长数组。
编码规则
固定类型的编码就很简单,直接将参数值转成32字节长度的16进制即可。需要注意的是:数字类型,不足32bytes时,如果是正数高位补0,如果是负数高位补1。布尔类型高位补0。字节类型、字符串类型在低位补全。?动态类型的编码稍微复杂点,如果是固定长度就不需要计算偏移量,如果是不定长度就需要先计算偏移量,并在最后加上长度和具体值的编码。下面举例说明。
Example
给出如下合约,参考官方文档:
https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
pragma?solidity?>=0.4.16?<0.8.0;contract?Foo?{????function?bar(bytes3?memory)?public?pure?{}????function?baz(uint32?x,?bool?y)?public?pure?returns?(bool?r)?{?r?=?x?>?32?||?y;?}????function?sam(bytes?memory,?bool,?uint?memory)?public?pure?{}}
案例1
函数:baz(uint32,bool)?
调用:baz(69,true)
?0xcdcd77c0:函数选择器,在python中通过sha3("set(string)").hex()得到0xcdcd77c0
?0x0000000000000000000000000000000000000000000000000000000000000045,十进制69,转成16进制为45,因为是正数,高位补0至32bytes
?0x0000000000000000000000000000000000000000000000000000000000000001,bool类型,true=1,false=0,高位补0
最终的字节码为
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
会返回bool类型.在这个调用中,返回值是false,它的输出将是单字节数组
0x0000000000000000000000000000000000000000000000000000000000000000
案例2
函数:bar(bytes3memory)?
调用:bar()
?0xfce353f6:函数选择器,在python中通过sha3("bar(bytes3)").hex()得到0xfce353f6
?固定长度不需要计算偏移量
?0x6162630000000000000000000000000000000000000000000000000000000000,字符串abc转成16进制后为616263,低位补0
V神:分片是以太坊可扩展性的未来:V神发文表示,分片是以太坊可扩展性的未来,它将是帮助以太坊生态系统支持每秒数千笔交易,并允许世界上大部分人用可以承受的成本定期使用以太坊平台的关键。他在文中解释了分片提供的特定属性,分片与没有分片的其他技术有何不同,以及为达到这些属性而必须做出的牺牲。他表示,分片的主要目标是尽可能地复制传统(非分片)区块链最重要的安全属性,而无需每个节点亲自验证每笔交易。[2021/4/8 19:57:24]
?0x6465660000000000000000000000000000000000000000000000000000000000,字符串def转成16进制后为646566,低位补0
字符串转16进制的python参考代码
import?binasciis?=?'abc'str_16?=?binascii.b2a_hex(s.encode('utf-8'))??#?字符串转16进制print(str_16)
案例3
函数:sam(bytes,bool,uint)
调用:sam("dave",true,")
?a5643bf2:函数选择器,在python中通过sha3("sam(bytes,bool,uint256)").hex()得到a5643bf2.请注意,将uint替换为其规范表示形式uint256。
?0x0000000000000000000000000000000000000000000000000000000000000060:动态类型,计算偏移量。这个的偏移量是指实际存储值的位置,由于这个函数有3个变量,那么实际存储值的位置就是第四个32bytes位置,也就是说偏移量等于3*32bytes=96,转成16进制后就是对应的值
?0x0000000000000000000000000000000000000000000000000000000000000001:第二个参数,布尔值true
?0x00000000000000000000000000000000000000000000000000000000000000a0:动态类型,计算偏移量,这个偏移量就等于参数长度3*32bytes+前面的动态参数参数占有的长度,那么具体的值就是3*32bytes+(1*32bytes+1*32bytes)=5*32bytes=160,转成16进制就是a0,高位补全就是对应的值
?0x0000000000000000000000000000000000000000000000000000000000000004:第一个参数的数据部分,代表元素中字节数组的长度,在这种情况下为4。
?0x6461766500000000000000000000000000000000000000000000000000000000:“dave”的utf-8编码,填充为32字节。
?0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的数据部分,代表数组中元素的个数,在这种情况下为3.
?0x0000000000000000000000000000000000000000000000000000000000000001:第三个参数的第一项。
?0x0000000000000000000000000000000000000000000000000000000000000002:第三个参数的第二项。
?0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的第三项。
最终的字节码为
Tether在以太坊网络增发1.2亿枚USDT:金色财经报道,Whale Alert数据显示,北京时间09月01日23:02,Tether在以太坊网络增发1.2亿枚USDT,增发哈希为:0x0e54e9bf2f34d5bf052bd9d9b60e931fc36176cd14261fd7775c3464f57aa003。[2020/9/1]
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
综上所述,ABI是合约接口的说明,并定义了与合约进行交互的数据编码解码规则。
事件和日志
区块链是一个块列表,从根本上讲就是交易列表。每一个交易都有一个收据,其中包含0个或多个日志记录。日志记录表示从智能合约触发的事件的结果。在以太坊中,事件是一个基本功能,可以将数据记录成日志,保存在区块链上。事件也可以与外部交互,比如与前端进行交互。事件强调功能,是指触发操作的行为,日志强调存储,是指触发事件后,将数据保存在区块链上,形成日志。
事件如何定义和触发
在solidity中,使用关键字event来定义事件,使用关键字emit来触发事件,其参数列表就是需要保存在区块链上的数据,最多可有三个具有indexed属性的参数,表示其可以被索引,便于查找。
contract?MyContract{????event?Transfer(address?indexed?from,?address?indexed?to,?uint256?value);????function?transfer(address?_to,?uint256?_value)?public?returns?(bool)?{????emit?Transfer(msg.sender,?_to,?_value);????return?true;??}}
事件的作用
事件可以在不同的场景下使用,主要有如下三种作用。
获取合约执行结果过滤日志存储合约数据1、获取合约执行结果
在开发Dapp时,我们会通过发送一笔交易来调用智能合约的某个函数,但是我们不能立即得到返回值。因为交易不是立刻打包进区块链的,在这种场景下,可以使用事件来解决这个问题。
以和前端交互为例,我们可以通过编写代码来监听某一特定事件来做到更新前端。例如通过如下代码来监听上文提到的合约中的Transfer事件。
var?event?=?myContract.Transfer();event.watch(function(error,?result){????if?(err)?{????console.log(err)????return;??}??console.log(result.args._value。);
火币研究院马天元:以太坊2.0升级后,现有矿工还可以挖很久但需更谨慎:7月8日15时,火币尖峰对话—“Waiting for ETH2.0”系列AMA活动第二期在线开启,本期主题是以太坊2.0,你该知道的真相,由火币矿池PoS负责人丁元深度对话TokenInsight 首席分析师Johnson、ECN以太坊中文社区负责人Esther、哈尔滨工业大学区块链研究中心常务副主任许志锋及火币研究院首席技术研究员马天元。
火币研究院首席技术研究员马天元在回答有关以太坊现有矿工生态问题时表示,ETH1.0会继续存在至少3-5年,这个是以太坊官方博客The 1.x Files: a fast-sync的文章里提到的,所以现在的矿工还可以继续挖很久,但是,采购新矿机的时候得再掂量掂量,好好想想。
以太坊社区对相关问题也非常谨慎,很担心社区有阻力,据估计难度炸弹还有可能推迟若干次。[2020/7/8]
当调用transfer函数的交易被打包进区块链中时,将会触发回调中的watch函数,前端可以得到有效的transfer函数的返回值。
事件通常可以被看作带有数据的异步触发器。当一个合约想要触发前端时,合约会发出一个事件。因为前端正在监听这个事件,一旦监听到相关事件,前端可以采取相应的操作,比如显示消息,更新前端展示内容等。
2、过滤日志
日志不能被合约访问,Solidity没有提供查询日志的接口,在监听日志的时候,Solidity提供了filter功能,借此我们可以实现对日志的查找过滤。在Transfer事件中,from和to参数被设置成indexed,说明其是可以被索引的。所以我们可以监听特定的事件,例如转账地址为0xab213的事件,也可以监听从0xab213地址转账到0x417ac的事件,但由于value参数没有indexed属性,所以我们不能监听例如value为100的事件。
在此场景下,我们如果想过滤指定地址发出的交易,我们可以通过web3.js编写如下代码。
Mycontract.deployed().then(function?(instance)?{var?event?=?instance.Transfer({}function?(error,?result)?{????var?obj1?=?{????????'_to':?'0xab213',????}????var?obj2?=?{????????'fromBlock':?0,????????'toBlock':?'latest'????}????var?event?=?instance.Transfer(obj1,?obj2)????event.watch(function?(error,?result)?{????????console.log(JSON.stringify(result))????}。).then(function?(value)?{????console.log(value。).catch(function?(e)?{????console.log(e。)
参数说明:
?obj1:添加indexed属性的参数,在这里我们可以过滤特定地址0xab213发起的交易
?obj2:Solidity提供的额外的过滤参数,可选的主要参数有:
–fromBlock:指定过滤的起始位置,值为块的编号,默认为latest
–toBlock:指定过滤的结束位置,值为块的编号,默认为latest
·callfunction:回调函数function(error,result
3、存储合约数据
与上面讲述的不同,事件可以作为便宜的多的一种存储形式。通过触发事件,存储在日志上的数据,基本上每字节花费8gas,但是智能合约每存储32字节花费20000gas。尽管日志可以节省大量gas,但是无法从任何智能合约中读取日志信息。需要根据使用场景来选择适合的存储办法,日志作为一种廉价存储的方式,适合存储可由前端展示的历史数据。
日志记录的组成
EVM具有5个用于发出日志的操作码:LOG0,LOG1,LOG2,LOG3和LOG4。通过这些操作码来创建日志记录。
每个日志记录都包含topics和data。Topics是bytes32类型的参数,不同的操作码描述包含在日志记录中的Topics数量。LOG1包含1个topic,LOG2包含2个topics,最多支持4个topics。
Topics用于描述事件,日志中存储的不同的具有indexed属性的事件就叫不同的主题。比如对于事件Transfer来说,其定义为eventTransfer(addressindexedfrom,addressindexedto,uint256value);,有三个主题。第一个主题为事件签名的哈希值,即通过keccak256("Transfer(address,address,uint256)")来得到,如果该事件是匿名事件,那么就没有这个主题。后面两个具有indexed属性的参数,可以用来进行过滤进行精确查找。
由于只能容纳32个字节的数据,所以无法将数据或字符串之类的参数用作Topic,应该将其作为data包含在日志记录中。如果想要包含超过32字节的topic,应该将其哈希。Topic可视作事件的索引,其使用场景在于可以有效缩小搜索查询范围内的数据。
日志记录的另一个部分是数据。Topic是可以搜索的,但是数据却不可以,数据可以摆脱Topic的32字节大小的限制,包含例如数组或字符串的复杂数据。
下面举一个例子来说明。还是以上文中的Transfer事件为例,由于Transfer不是一个匿名事件,所以第一个Topic包含事件签名。
contract?MyContract{????event?Transfer(address?indexed?from,?address?indexed?to,?uint256?value);????function?transfer(address?_to,?uint256?_value)?public?returns?(bool)?{????emit?Transfer(msg.sender,?_to,?_value);????return?true;??}}
在事件的参数部分,Transfer事件有三个参数from、to、value,其中from和to被声明为indexed,标识其被视为topic,value参数不会被索引,将会作为日志的数据部分。此事件包含3个topics,将使用LOG3操作码来创建日志记录。
举个例子,将上述示例合约部署到区块链网络中,我们使用:0x246B0ED379bdDbe1aDaC56277Ce5cB3018c24E04地址调用transfer函数,to参数指定为0x3F0b9B0D373C26328A879430383e87F4780AD410,value指定为1,发起一笔交易获得交易的回执信息。其中在logs属性中,记录如下信息
,????"type":"mined",????"id":"log_cf046b97"??}]
其中,topics数组中有三个元素,第一个为keccak256("Transfer(address,address,uint256)")的结果,第二个为from参数的值,这里为0x246B0ED379bdDbe1aDaC56277Ce5cB3018c24E04,第三个为to参数的值,这里为0x3F0b9B0D373C26328A879430383e87F4780AD410。data字段是通过对value进行编码得到的,这里将1转成32字节长度的16进制的值,得到0x0000000000000000000000000000000000000000000000000000000000000001。
布隆过滤器LogBloomandFilter
布隆过滤器在以太坊中用于检索交易日志log,方便交易结果的查询以及交易事件通知。在以太坊的区块头中,有一个区域叫做logsBloom。这个区域存储了当前区块中所有收据的日志的布隆过滤器,有2048个bit,相当于256个字节。在一个交易的收据中,可能存在0个或多个日志记录,每个日志记录中包含了相应的Topics和data。在一个交易的收据中同样也存在布隆过滤器,记录了所有的日志记录数据。
下面介绍布隆过滤器的原理。我们知道,查找一个元素是否存在于一个集合中,可以使用数组这种数据结构。但是当数据量非常庞大的情况下,数组的空间开销和查询开销也会变得非常大。
布隆过滤器的原理是当一个元素被加入到一个集合中时,使用K个哈希函数,对该元素求哈希值,得到K个不同的哈希值,分别记作X1,X2,X3,…,XK。将这K个数字作为位图的下标,将对应的
BitMap,BitMap,BitMap,…,BitMap都设置为1。即用K个bit来表示一个元素是否存在。
当想要查询某个元素是否存在于这个集合中时,用相同的K个哈希函数,得到Y,Y,Y...Y,如果这K个哈希值对应的位图下标均为1,则表示这个元素可能存在,如果有任意一个下标不为1,则说明这个元素肯定不存在。
举例说明,假设集合中现在有3个元素{X,Y,Z},使用3个哈希函数。首先将数组进行初始化,每一位置为0。然后对于集合中的每一个元素,都通过3个哈希函数进行哈希计算,每次计算都会产生一个下标,将数组中该下标对应的值设置为1。这时候想要查询一个元素W是否在该集合中,依次用这3个哈希函数将W映射到数组的3个位点上,如果3个位点对应的值都为1,则可能存在于集合中,若有一个位点的值不为1,则该元素一定不在这个集合中。
布隆过滤器存在一定的误判性,因为存在一定的哈希碰撞可能,所以当位点上的值都为1时,只能反映该元素可能存在于该集合中。但这不影响其广泛的应用性,布隆过滤器是一种存储效率很高的数据结构,其空间利用率和时间利用率非常高,插入数据和查询数据的时间复杂度为O。
布隆过滤器可以用于事件查询,在以太坊中,系统会先创建各个主题的布隆过滤器,然后通过合并获得事件的布隆过滤器,再次合并得到交易的布隆过滤器,最后合并得到区块的布隆过滤器。在查询过程中,查询满足指定特征的事件的过程是,先根据查询条件得到布隆过滤器,如果其位向量是区块布隆过滤器的子向量,则可能存在此区块中,如果不是其子向量,那么就不存在此区块中。如果可能存在,继续比对区块中各个交易的布隆过滤器,比对交易中每个事件的布隆过滤器。如果与事件的布隆过滤器匹配,再进行严格的数据验证,相同的话即说明存在。
参考资料
https://academy.binance.com/zh/articles/what-are-smart-contracts
https://segmentfault.com/a/1190000016634359
https://cloud.tencent.com/developer/article/1328286
https://fisco-bcos-documentation.readthedocs.io/zhCN/latest/docs/articles/3features/35contract/abiof_contract.html
http://www.jouypub.com/2018/437e42a5629ea0ccd567909c94abb4a4/
https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e
标签:以太坊TRAINTESS怎么得到以太坊币交易Australian Safe ShepherdPlatonic Quintessenceguesscoin
据U.Today消息,Cardano创始人CharlesHoskinson今日在推特上表示,对于Cardano来说,“智能合约”这个词其实并不恰当,并建议社区将其新功能称为“可编程验证器”.
1900/1/1 0:00:00据Cointelegraph9月28日消息,Glassnode数据显示,闪电网络节点的数量在9月份增加了160%,创下了15600个的历史新高。据悉,8月底,只有6000个LN节点处于活动状态.
1900/1/1 0:00:00律动BlockBeats消息,9月27日,以太坊核心开发者TimBeiko在Github上公布了以太坊12月点网络升级计划,将仅包括「推迟难度炸弹」一项内容,不包含其他内容的升级.
1900/1/1 0:00:00据新华社消息,9月29日,建设银行上海市分行与上海农村产权交易所签署金融服务推动乡村振兴战略合作协议。双方将在农村产权交易与涉农普惠金融创新、乡村振兴领域数字人民币服务等五个领域深入开展合作.
1900/1/1 0:00:00据Forkast9月25日报道,自2014年仅有两家公司——PayPal和WaltDisney涉足区块链到现在,区块链技术的采用已经呈指数级增长.
1900/1/1 0:00:00PanteraCapital合伙人PaulVeradittakit发文介绍了该机构近期投资的元宇宙基础设施项目游骑兵协议.
1900/1/1 0:00:00