“一个智能合约是一套以数字形式定义的承诺(promises) ,包括合约参与方可以在上面执行这些承诺的协议。”协议是技术实现(technical implementation),在这个基础上,合约承诺被实现,或者合约承诺实现被记录下来。选择哪个协议取决于许多因素,最重要的因素是在合约履行期间,被交易资产的本质。 再次以销售合约为例。假设,参与方同意货款以比特币支付。选择的协议很明显将会是比特币协议,在此协议上,智能合约被实施。因此,合约必须要用到的“数字形式”就是比特币脚本语言。比特币脚本语言是一种非图灵完备的、命令式的、基于栈的编程语言,类似于Forth。下面将以一个简单的合约脚本以及与之对应的合约功能测试脚本来进行相应的解释。
下面是一个简单的合约脚本:
import "ConvertLib.sol";
// This is just a simple example of a coin-like contract.
// It is not standards compatible and cannot be expected to talk to other
// coin/token contracts. If you want to create a standards-compliant
// token, see: https://github.com/ConsenSys/Tokens. Cheers!
contract MetaCoin {
mapping (address => uint) balances;
function MetaCoin() {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
return true;
}
function getBalanceInEth(address addr) returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) returns(uint) {
return balances[addr];
}
}
脚本合约规定了账户初始值的设置以及进行转账时的一些规定,基本上所有的合约脚本都遵循这样的一种代码组织形式,下面进行一些关键字的简单说明:
Address:地址类型,这个地址会在合约的构造函数functionConference()中被赋值。很多时候也称呼这种地址为'owner'(所有人)。
uint.:无符号整型,区块链上的存储空间很紧张,保持数据尽可能的小。
Public:这个关键字表明变量可以被合约之外的对象使用。private修饰符则表示变量只能被本合约(或者衍生合约)内的对象使用。
Mapping或数组:在Solidity加入数组类型之前,大家都使用类似mapping (address => uint)的Mapping类型。这个声明也可以写作address registrantsPaid[],不过Mapping的存储占用更小(smaller footprint)。这个Mapping变量会用来保存参加者(用他们的钱包地址表示)的付款数量以便在退款时使用。
在写完合约脚本后,我们需要将其部署在我们的区块链网络上面去(truffle开源框架已经做得比较好了,感兴趣的可以看看相关资料),部署完成后,我们可以写一些简单的test case来验证我们的合约脚本是否可以被区块链中的对等节点正确的调用,比如:
contract('MetaCoin', function(accounts) {
it("should put 10000 MetaCoin in the first account", function(done) {
//it should be executed every time to any testcase
var meta = MetaCoin.deployed();
/*代码中的那些then和return就是Promise。它们的作用写成一个深深的嵌套调用链的话会是这样
conference.numRegistrants.call().then(
function(num) {
assert.equal(num, 0, "Registrants should be zero!");
conference.organizer.call().then(
function(organizer) {
assert.equal(organizer, accounts[0], "Owner doesn't match!");
}).then(
function(...))
}).then(
function(...))
// Because this would get hairy...
*/
meta.getBalance.call(accounts[0]).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
//stops tests at this point
}).then(done).catch(done);
});
it("should call a function that depends on a linked library ", function(done){
var meta = MetaCoin.deployed();
var metaCoinBalance;
var metaCoinEthBalance;
meta.getBalance.call(accounts[0]).then(function(outCoinBalance){
metaCoinBalance = outCoinBalance.toNumber();
return meta.getBalanceInEth.call(accounts[0]);
}).then(function(outCoinBalanceEth){
metaCoinEthBalance = outCoinBalanceEth.toNumber();
}).then(function(){
assert.equal(metaCoinEthBalance,2*metaCoinBalance,"Library function returned unexpeced function, linkage may be broken");
}).then(done).catch(done);
});
it("should send coin correctly", function(done) {
var meta = MetaCoin.deployed();
// Get initial balances of first and second account.
var account_one = accounts[0];
var account_two = accounts[1];
var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;
var amount = 10;
meta.getBalance.call(account_one).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();
assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
}).then(done).catch(done);
});
});
其中关键字段已经给出了一些简单的解释,重点是其中的then语句的使用,对于then语句,其实就是我们合约中预先规定的合约办法,即一个事件来了后该做怎么样的处理。