7. 合约交互
原文地址:http://truffleframework.com/docs/getting_started/contracts
背景
标准的与以太坊网络交互的方法是通过以太坊官方构建的Web3库。尽管这个库非常有用,但使用其提供接口与合约交互有些困难,特别是以太坊的新手。为降低学习曲线,Truffle使用Ether Pudding库,它也是基于Web3的基础之上,目的是为了让交互更简单。
读写数据
以太坊网络把在网络上读与写数据进行了区分,这个区分对于如何写程序影响很大。通常来说,写数据被称作交易(transaction)
,读数据被称作调用(call)
。对于交易与调用,他们分别有如下特性:
交易(Transaction)
交易本质上改变了整个以太坊网络的数据状态。一个交易可以是向另一个帐户发送ether(以太坊网络代币)这样的简单行为,也可以是执行合约函数,添加一个新合约到以太坊网络这样的复杂行为。交易的典型特征是写入(或修改)数据。交易需要花费ether,也被称作gas,交易的执行需要时间。当你通过交易执行一个合约的函数时,你并不能立即得到执行结果,因为交易并不是立即执行的。大多娄情况下,通过执行交易不会返回值;它会返回一个交易的ID.总的来说,交易具有如下特征:
- 需要gas(Ether)
- 改变网络的状态
- 不会立即执行
- 不会暴露返回结果(仅有交易ID)
调用
调用,则与上述的交易非常不同。调用可以在网络上执行代码,但没有数据会被改变(也许仅仅是些临时变量被改变)。调用的执行是免费的,典型的行为就是读取数据。通过调用执行一个合约函数,你会立即得到结果。总的来说,调用具有如下特征:
- 免费(不花费gas)
- 不改变网络状态
- 立即执行
- 有返回结果。
如果选择,取决于你想干什么,或者说想写数据,还是读数据。
接口(abstract)
为了来体验一下合约接口的作用,我们使用框架自带的默认metacoin的合约例子。
import "ConvertLib.sol";
contract MetaCoin {
mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
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;
Transfer(msg.sender, 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];
}
}
合约有三个方法和一个构造方法。所有三个方法可以被执行为交易或调用。
现在我们来看看Truffle和Ether Pudding为我们提供的叫MetaCoin
的Javascript对象,可以在前端中使用:
// Print the deployed version of MetaCoin
console.log(MetaCoin.deployed());
// outputs:
//
// Contract
// - address: "0xa9f441a487754e6b27ba044a5a8eb2eec77f6b92"
// - allEvents: ()
// - getBalance: ()
// - getBalanceInEth: ()
// - sendCoin: ()
接口层提供了合约中以应的函数名。它还包含一个地址,指向到MetaCoin合约的部署版本。
执行合约函数
通过这套框架为我们提供的接口,我们可以简单的在以太坊网络上执行合约函数。
执行交易
在上述例子MetaCoin合约中,我们有三个可以执行的函数。如果你对这三个函数稍加分析就会发现,只有sendCoin
会对网络造成更改。sendCoin
函数的目标将Meta Coin从一个帐户发送到另一些帐户,这些更改需要被永久存下来。
当调用sendCoin
,我们将把他们作为一个交易来执行。下面的例子我们来演示下把10个币,从一个帐户发到另一个帐户,改变要永久的保存在网络上:
var account_one = "0x1234..."; // an address
var account_two = "0xabcd..."; // another address
var meta = MetaCoin.deployed();
meta.sendCoin(account_two, 10, {from: account_one}).then(function(tx_id) {
// If this callback is called, the transaction was successfully processed.
// Note that Ether Pudding takes care of watching the network and triggering
// this callback.
alert("Transaction successful!")
}).catch(function(e) {
// There was an error! Handle it.
})
上述代码有一些有趣点,我们来了解一下:
- 我们直接调用接口的
sendCoin
函数。最终是默认以交易的方式来执行的。 - 交易被成功执行时,回调函数会直到交易被执行时才真正被触发。这样带来的一个好处是你不用一直去检查交易的状态。
- 我们对
sendCoin
函数传递了第三个参数,需要注意的是原始合约函数的定义中并没有第三个参数。这里你看到的是一个特殊的对象,用于编辑一些交易中的指定细节,它可以总是做为第三个参数传进。这里,我们设置from
的地址为account_one
.
执行调用
继续用MetaCoin的例子。其中的getBalance
函数就是一个很好的从网络中读取数据的例子。它压根不需要进行任何数据上的变更,它只是返回传入的地址的帐户余额,我们来简单看一下:
var account_one = "0x1234..."; // an address
var meta = MetaCoin.deployed();
meta.getBalance.call(account_one, {from: account_one}).then(function(balance) {
// If this callback is called, the call was successfully executed.
// Note that this returns immediately without any waiting.
// Let's print the return value.
console.log(balance.toNumber());
}).catch(function(e) {
// There was an error! Handle it.
})
一些有意思的地方如下:
- 我们必须通过
.call()
来显示的向以太坊网络表明,我们并不会持久化一些数据变化。 - 我们得到了返回结果,而不是一个交易ID。这里有个需要注意的是,以太坊网网络可以处理非常大的数字,我们被返回了一个BigNumber对象,框架再将这个对象转化了一个number类型。
警告:我们在上述的例子中将返回值转成了一个number类型,是因为例子中的返回值比较小,如果将一个BigNumber转换为比javascript支持的number最大整数都大,你将会出现错误或不可预期的行为。
捕捉事件(Catching Events)
你的合约可以触发事件,你可以进行捕捉以进行更多的控制。事件API与Web3一样。可以参考Web3 documentation来了解更多。
var meta = MetaCoin.deployed();
var transfers = meta.Transfer({fromBlock: "latest"});
transfers.watch(function(error, result) {
// This will catch all Transfer events, regardless of how they originated.
if (error == null) {
console.log(result.args);
}
}
METHOD:DEPLOYED()
每一个抽象出来的合约接口都有一个deployed()
方法,上述例子中,你已经见到过。调用这个函数返回一个实例,这个实例代表的是之前部署到网络的合约所对应的抽象接口的实例。
var meta = MetaCoin.deployed();
警告:这仅对使用truffle deploy
部署的合约,且一定是在project configuration
中配置发布的才有效。如果不是这样,这个函数执行时会抛出异常。
METHOD:AT()
类似于deployed()
,你可以通过一个地址来得到一个代表合约的抽象接口实例。当然这个地址一定是这个合约的部署地址。
var meta = MetaCoin.at("0x1234...")
警告:当你的地址不正确,或地址对应的合约不正确时,这个函数并不会抛出异常。但调用接口时会报错。请保证在使用at()
时输入正确的地址。
METHOD:NEW()
你可以通过这个方法来部署一个完全全新的合约到网络中。
MetaCoin.new().then(function(instance) {
// `instance` is a new instance of the abstraction.
// If this callback is called, the deployment was successful.
console.log(instance.address);
}).catch(function(e) {
// There was an error! Handle it.
});
需要注意的是这是一个交易,会改变网络的状态。
如果任何问题,欢迎留言批评指正。
处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。