本文会通过两个示例来简单介绍智能合约
示例一:存储(Storage)
下面这段代码实现了一个uint值的存储。
pragma solidity ^0.4.0; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public constant returns (uint) { return storedData; } }
在某种意义上,Solidity合约就是在以太坊区块链中特定地址内的一组代码(合约的函数)和数据(合约的state)的集合。
uint storedData;
这一行声明了一个uint(256bit的无符号整数)类型的state变量:storedData。你可以认为storedData是数据库中的一个存储单元,它可以被通过执行数据库管理代码的方式查询和修改。在以太坊中只有合约的所有者能这样做。示例代码中,set、get函数可以被用于修改或检索变量的值
获取state对象,不需要像其他语言那样加this.前缀
由于此合约仅存储了一个完全公开的数字,所以它的发布是无法被阻止的。但任何人都可以通过以另一参数调用set来覆盖你刚刚保存的数字(但你写过的那个数字仍会被保存在区块链历史中)。稍后,你将看到如何设置限制以确保只有你自己可以修改这个变量的值
注意,所有标识符(如合约名,变量名,函数名等)只能使用ascii字符集。UTF-8编码的数据可以存储在字符串变量中
使用与其他字符相似的Unicode字符时需要保持小心
示例二:子货币(Subcurrency)
下面的合约将会实现一种最简单的加密货币。这里定义的货币可以凭空产生,但只有创建合约的人才有能力凭空生成货币(实现一个不同的发币计划是件微不足道的小事)。 此外,任何人都可以互相发送货币而不需要使用用户名和密码进行注册——进行所有的一切,只需要一个以太坊密钥对。
pragma solidity ^0.4.21; contract Coin { // The keyword "public" makes those variables // readable from outside. address public minter; mapping (address => uint) public balances; // Events allow light clients to react on // changes efficiently. event Sent(address from, address to, uint amount); // This is the constructor whose code is // run only when the contract is created. function Coin() public { minter = msg.sender; } function mint(address receiver, uint amount) public { if (msg.sender != minter) return; balances[receiver] += amount; } function send(address receiver, uint amount) public { if (balances[msg.sender] < amount) return; balances[msg.sender] -= amount; balances[receiver] += amount; emit Sent(msg.sender, receiver, amount); } }
这段合约带来了一些新的概念,以下将一一介绍
address public minter;声明了一个address类型的state变量,这个变量是所有人都可以读取的。address类型是一个不允许进行任何算术操作的160位值。它很适合被用来存储合约地址或属于外部个人的密钥对。关键字public会自动生成一个允许你从合约外部获取一个state变量当前值的函数。若没有这个关键字,其他合约将无法获取到这个变量。由编译器生成的代码和下面这行代码大致相同
function minter() returns (address) { return minter; }
(直接加上这句会导致编译器报错,因为此函数和state变量重名)
下一行:mapping (address => uint) public balances;
这句话创建了一个数据类型更加复杂的公共state变量——一个从地址到无符号整型的映射。此处的映射可以被看做一个虚拟初始化了的hash表,以便每个可能的密钥存在,并映射到一个字节表示全是0的值上(这个比喻不太合理,因为包含一个有映射的所有键的列表是不可能的,包含所有值也是不可能的)。因此要么保存添加到映射中的内容,要么在不需要这种内容的部分使用它(就像这句)。由public关键字创建的getter函数【注1】在这种情况下更加复杂一些。看起来大概是这样的:
function balances(address _account) public view returns (uint) { return balances[_account]; }
用这个函数可以轻松查询一个账户的余额
event Sent(address from, address to, uint amount);
这行声明了一个所谓的“event(事件)”,它被最后的send函数发出。包括服务端应用在内的用户接口可以以较低的成本监听这些被发送到区块链上的事件。一旦事件被发送,监听者就会获取到from、to、amount参数,以便监听者跟踪交易。为监听这一事件,我们会使用
Coin.Sent().watch({}, '', function(error, result) { if (!error) { console.log("Coin transfer: " + result.args.amount + " coins were sent from " + result.args.from + " to " + result.args.to + "."); console.log("Balances now:\n" + "Sender: " + Coin.balances.call(result.args.from) + "Receiver: " + Coin.balances.call(result.args.to)); } })
注意,自动生成的函数balance是怎样从用户接口调用的
Coin这个特殊的函数时在创建合约时调用的构造器,他不能在合约构造完成后再被调用。它永久存储了合约创建者的地址:msg(和tx、block一起)。msg是一个神奇的全局变量,包含一些允许连接到区块链的属性。msg.sender永远是当前的(外部)函数调用所来自的地址。
这个合约中的函数可以被用户和其他合约调用,如mint和send。mint只能被合约创建者进行有效的调用。而send可以被任何已经持有一些这种币的人调用来向他人进行转账。注意如果你用这个合约发送币到其他地址,你在区块链浏览器上浏览目标地址时并不会看到任何效果产生,因为你发送了币并改变余额的事实只存储于这个特定的货币合约的数据存储中。因为有了事件的使用,创建一个用于跟踪你自己的货币的交易和余额的“区块链浏览器”变得比较简单。
【注1】:getter函数时编译器为所有public的state变量自动创建的,它会返回一个uint类型的名为“data”的state变量值。状态变量的初始化可以在声明时完成。