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.
});

需要注意的是这是一个交易,会改变网络的状态。

如果任何问题,欢迎留言批评指正。

处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。