1.3 简单的智能合约
本节将以2个简单具体的示例来说明智能合约编程语言Solidity的基本语法,通过阐述Solidity编程语言的API及使用方式,使读者对智能合约编程有一个初步印象。本节的2个示例均来自以太坊官方网站。
1.3.1 示例1
下面仅以一个简单的智能合约示例,介绍智能合约的基本组成元素,本合约定义一个uint类型的变量,以及这个变量对应的读写函数。
01 pragma solidity >=0.4.0 <0.6.0; 02 03 contract SimpleStorage { //使用关键字contract定义合约结构体 04 uint storedData; //定义一个成语变量 05 06 function set(uint x)public { //定义关于该变量的设置函数 07 storedData = x; 08 } 09 10 function get()public view returns(uint){//定义关于该变量的读取函数 11 return storedData; 12 } 13 }
第1行代码指明语法解释使用的编译器版本,目前以太坊的智能合约虚拟机仍然在开发升级中,尚未有release版本,不同版本之间的API变动比较大,因此在编写智能合约时,必须指明合约逻辑使用的API的版本号;第3行代码通过contract关键字,声明了一个智能合约结构体,可以将该关键字与面向对象编程的class做类比,contract的设计模式和方法重载、父子继承等特性,与面向对象的设计非常接近,因此说Solidity作为一种高级的编程语言,极大地降低了对以太坊公链编程的难度。
第4行代码定义了一个成语变量,该变量的可见范围在contract的大括号之内;第6行、第7行代码定义了一个函数,该函数有可见范围的修饰符public,关于可见范围的详细解释放在了第3章中,在此读者仅仅需要了解,该描述符说明该函数可以被区块链以外的系统通过Web 3.0接口调用,也可以通过以太坊内的其他智能合约调用。
第10~12行代码定义了一个读取函数,函数的可见范围也是public,但是因为这个函数没有修改区块链上的任何数据和状态,所以可以将该函数定义为view,类似于其他编程语言的readonly属性,同时在函数的最后定义了函数的返回类型。
这个智能合约相对简单,仅仅是将一个uint的数据保存在区块链分布式账本中的合约。任何账户都可以读写这个数据,本示例并没有对数据做任何访问限制,这意味着任何人、任何账户都可以读取和修改这个数据,在后面的章节中,会详细介绍数据的访问权限和安全性问题。
1.3.2 示例2
本示例定义了一个简单的数字货币的智能合约。当前关于区块链技术的讨论,几乎全部围绕数字货币展开,但是大多数人对数字货币的基本知识都比较匮乏,其实目前很多数字货币本质上都是以太坊公链上的智能合约。关于基于以太坊公链发行的数字货币的详细知识,笔者将会在第5章详细论述。本节以一个简单的数字货币智能合约来讲解数字货币智能合约应该具有的基本元素。
01 pragma solidity >0.4.99 <0.6.0; 02 03 contract Coin { 04 address public minter; //定义一个地址类型的变量 05 //定义一个地址到余额数量的映射 06 mapping(address => uint)public balances; 07 //声明一个记录日志的事件 08 event Sent(address from, address to, uint amount); 09 constructor()public { //合约的构造函数 10 minter = msg.sender; 11 } 12 //定义修改余额数据结构的函数,修改方式是直接增加某个地址的余额值 13 function mint(address receiver, uint amount)public { 14 require(msg.sender == minter); 15 require(amount < 1e60); 16 balances[receiver] += amount; 17 } 18 //定义转移余额的函数 19 function send(address receiver, uint amount)public { 20 require(amount <= balances[msg.sender], "Insufficient balance."); 21 balances[msg.sender] -= amount; 22 balances[receiver] += amount; 23 emit Sent(msg.sender, receiver, amount); 24 } 25 }
第4行代码定义了一个区块链地址类型(address)的变量,该变量为public类型,定义这个变量是为了记录铸造该合约的账户地址,该地址在合约构造函数中进行了赋值。第9~11行代码定义了该合约的构造函数,该构造函数仅仅在合约部署到以太坊公链时执行一次,合约部署成功之后就无法修改此合约的逻辑了,第10行代码中的msg.sender表示的是包含该合约初始化代码的交易发起者的区块链地址。也就是说Coin这个合约的铸造者就是部署本合约的账户,该账户可以是外部账户,也可以是智能合约账户。
第6行代码定义了一个映射关系数据结构,这个结构定义于区块链地址下,关于该数字货币的余额,目前基于以太坊的数字货币,只要符合ERC20协议,很多第三方开源的数字货币钱包都可以显示数字货币的余额,其实这个余额就是该Coin合约中,balances变量存储的数值,不同的地址存储了不同的数值,在数字货币钱包中,通过查询以太坊公链下某一地址下的ERC20数字货币合约的balances变量,就可以显示数字货币余额。
第13~17行代码是一个铸币函数,所谓的铸币函数,本质上就是修改balances这个数据结构的内容,因为该结构存储了各个区块链地址下的数字货币的余额,所以调用这个函数时,需要检查调用者的区块链地址是否与智能合约初始化时合约的部署者一致,如果不一致则该函数执行失败,同时要检查本次铸币的数量不能高于1e60个,如果数字货币总量超过这个数字,铸币操作也会失败,如果以上条件都满足,那么就会在receiver这个账户下增加amount个数字货币。如果以上require的条件不满足,该合约调用会失败并会导致以太坊状态机发生回滚,以太坊的状态会恢复到调用该函数之前。
第9~24行代码是一个转账函数,在检查调用者有充足的余额后,在调用者名下减少amount个本智能合约代表的数字货币,同时在receive这个地址下增加相应数量的数字货币,在函数被成功执行之后,将本次执行时间emit一个事件,该事件产生log,并被永久的保存在以太坊区块链上。该事件需要首先在第8行代码进行声明,然后在后面的函数中调用。由本函数可知,所谓的数字货币转账实际上只是修改balances下存储的数据而已。
本示例是一个简单的铸币智能合约,简单地演示了铸币的权限检查、数字货币的账户余额管理及账户之间互相转账的功能,本智能合约还有很多关于数字货币的问题没有解决,只是简单地给读者展示一个发行数字货币的逻辑,以便读者可以对当下火爆的数字货币有初步代码级的认知。以上代码仅供学习使用,完全没有达到可以商用的要求,请读者使用时注意。