Truffle packages for truffle-contract
包 truffle-contract 能给以太坊智能合约提供更好的抽象,同时适用于 Node 和浏览器。
安装
$ npm install truffle-contract
特征
- 同步的交易可以获得更好的控制流(即,交易将无法完成直到对应的区块被挖出来)。
- Promises。没有更多的回调地狱。适用于
ES6
和async / await
。 - 交易的默认值,例如
from
地址或gas
。 - 返回每个同步交易的日志,交易回执和交易哈希。
使用方法
首先,设置一个新的 web3 provider 实例并初始化合约,然后 require(“truffle-contract”)
。 contract
函数的输入是由truffle-contract-schema定义的 JSON blob。此 JSON blob 的结构可以传递给所有与 Truffle 相关的项目。
var provider = new Web3.providers.HttpProvider("http://localhost:8545");
var contract = require("truffle-contract");
var MyContract = contract({
abi: ...,
unlinked_binary: ...,
address: ..., // optional
// many more
})
MyContract.setProvider(provider);
也可以直接从合约编译后的同名 .json 文件中加载合约,如:
var MyContract = contract(require('./build/contracts/MyContract.json'));
现在可以访问 MyContract
的以下函数以及许多其他函数:
-
at()
: 创建一个MyContract
实例,代表特定地址的合约。 -
deployed()
: 创建一个MyContract
实例,表示由MyContract
管理的默认地址。 -
new()
: 将此合约的新版本部署到网络,获取代表新部署的实例的MyContract
实例。
每个实例都绑定到以太坊网络上的特定地址,每个实例都具有从 Javascript 函数到合约函数的 1 对 1 映射。例如,如果 Solidity 合约有一个函数定义了 someFunction(uint value){}
(solidity),那么可以在网络上执行该函数,如下所示:
var deployed;
MyContract.deployed().then(function(instance) {
var deployed = instance;
return instance.someFunction(5);
}).then(function(result) {
// Do something with the result or continue with more transactions.
});
浏览器使用方法
在 head
元素中,包含 Web3,然后包含 truffle-contract:
<script type="text/javascript" src="./path/to/web3.min.js"></script>
<script type="text/javascript" src="./dist/truffle-contract.min.js"></script>
或者,可以使用非缩小版本以便于调试。
通过这种用法,truffle-contract
将通过 TruffleContract
对象提供:
var MyContract = TruffleContract(...);
完成示例
让我们使用 truffle-contract
和 Dapps For Beginners 的示例合约。在这种情况下,抽象已被 truffle-artifactor 保存到 .sol.js
文件中:
// Require the package that was previosly saved by truffle-artifactor
var MetaCoin = require("./path/to/MetaCoin.sol.js");
// Remember to set the Web3 provider (see above).
MetaCoin.setProvider(provider);
// In this scenario, two users will send MetaCoin back and forth, showing
// how truffle-contract allows for easy control flow.
var account_one = "5b42bd01ff...";
var account_two = "e1fd0d4a52...";
// Note our MetaCoin contract exists at a specific address.
var contract_address = "8e2e2cf785...";
var coin;
MetaCoin.at(contract_address).then(function(instance) {
coin = instance;
// Make a transaction that calls the function `sendCoin`, sending 3 MetaCoin
// to the account listed as account_two.
return coin.sendCoin(account_two, 3, {from: account_one});
}).then(function(result) {
// This code block will not be executed until truffle-contract has verified
// the transaction has been processed and it is included in a mined block.
// truffle-contract will error if the transaction hasn't been processed in 120 seconds.
// Since we're using promises, we can return a promise for a call that will
// check account two's balance.
return coin.balances.call(account_two);
}).then(function(balance_of_account_two) {
alert("Balance of account two is " + balance_of_account_two + "!"); // => 3
// But maybe too much was sent. Let's send some back.
// Like before, will create a transaction that returns a promise, where
// the callback won't be executed until the transaction has been processed.
return coin.sendCoin(account_one, 1.5, {from: account_two});
}).then(function(result) {
// Again, get the balance of account two
return coin.balances.call(account_two)
}).then(function(balance_of_account_two) {
alert("Balance of account two is " + balance_of_account_two + "!") // => 1.5
}).catch(function(err) {
// Easily catch all errors along the whole execution.
alert("ERROR! " + err.message);
});
需要说明的是,也可以先编译智能合约 contracts/MetaCoin.sol 生成 build/contract/MetaCoin.json。
var MetaCoin = require("./path/to/MetaCoin.json");
智能合约 MetaCoin.sol 如下:
contract metaCoin {
mapping (address => uint) balances;
function metaCoin() {
balances[msg.sender] = 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;
}
}
API
有两个 API 需要注意,一个是静态合约抽象 API,另一个是合约实例 API。抽象 API 是一组存在于所有合约抽象中的函数,这些函数存在于抽象本身上(即 MyContract.at()
)。相反,实例 API 是可用于合约实例的 API - 即表示网络上特定合约的抽象 - 并且 API 是根据 Solidity 源文件中可用的函数动态创建的。
合约抽象 API(Contract Abstraction API)
每个合约抽象 -- 如上例中的 MyContract
-- 拥有下列有用的函数:
MyContract.new([arg1, arg2, ...], [tx params])
此函数采用合约所需的任何构造函数参数,并将合约的新实例部署到网络。有一个可选的最后一个参数,可以使用它来传递交易参数,包括来自地址,Gas 限制和 Gas 价格的交易。此函数返回一个 Promise,它在新部署的地址处解析为合约抽象的新实例。
MyContract.at(address)
此函数创建一个新的合约抽象实例,表示传入地址中的合约。返回一个“thenable”的对象(为了向后兼容,还不是一个实际的 Promise)。在确保代码存在于指定地址后,解析为合约抽象实例。
MyContract.deployed()
在其部署的地址创建表示合约的合约抽象实例。部署的地址是给予 truffle-contract 的特殊值,当设置时,在内部保存地址,以便可以从所使用的给定以太网网络推断部署的地址。这允许编写引用特定已部署合同的代码,而无需自己管理这些地址。就像 at()
一样,deployed()
是 可以的(thenable),并且在确保代码存在于该位置并且该地址存在于正在使用的网络上之后将解析为表示已部署合约的合约抽象实例。
奇怪的是,在普通的 nodejs 脚本中会失败,提示网络中并没有部署,而实际上是已经部署了的。但是,在 Truffle 的部署脚本中却可以正常正常调用。
(node:3896) UnhandledPromiseRejectionWarning: Error: MyContract has not been dep
loyed to detected network (network/artifact mismatch)
at D:\working\bitbucket\zblockchain\ethereumcodes\windows\truffle\examples\M
yContract\node_modules\truffle-contract\contract.js:454:17
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
(node:3896) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function without a catch
block, or by rejecting a promise which was not handled with .catch(). (rejection
id: 1)
(node:3896) [DEP0018] DeprecationWarning: Unhandled promise rejections are depre
cated. In the future, promise rejections that are not handled will terminate the
Node.js process with a non-zero exit code.
MyContract.link(instance)
将合约抽象实例表示的库链接到 MyContract。必须首先部署库并设置其已部署的地址。名称和部署地址将从合约抽象实例中推断出来。当使用这种形式的 MyContract.link()
时,MyContract 将使用所有链接库的事件,并且能够报告在交易结果期间发生的那些事件。
库可以多次链接,并覆盖以前的链接。
注意:此方法有两种其他形式,但建议使用此形式。
MyContract.link(name, address)
将具有特定名称和地址的库链接到 MyContract。使用此形式不会使用库的事件。
MyContract.link(object)
将 object 表示的多个库链接到 MyContract。键必须是表示库名称的字符串,值必须是表示地址的字符串。如上所述,使用此形式不会消耗库的事件。
MyContract.networks()
查看此合约抽象已设置为表示的网络 ID 列表。
MyContract.setProvider(provider)
设置此合约抽象将用于进行交易的 web3 provider。
MyContract.setNetwork(network_id)
设置 MyContract 当前表示的网络。
MyContract.hasNetwork(network_id)
返回一个布尔值,表示此合约抽象是否设置为表示特定网络。
MyContract.defaults([new_defaults])
获取并选择为从此抽象创建的所有实例设置交易默认值。如果在没有任何参数的情况下调用它,它将只返回表示当前默认值的 object。如果传递了一个 object,则会设置新的默认值。可以设置的默认交易值示例如下:
MyContract.defaults({
from: ...,
gas: ...,
gasPrice: ...,
value: ...
})
例如,当我们有一个合约抽象时,设置一个默认的 from
地址是有用的,打算代表一个用户(即一个地址)。
MyContract.clone(network_id)
克隆合约抽象以获取管理相同合同工件的另一个对象,但使用不同的 network_id
。如果我们想在不同的网络上管理相同的合约,这非常有用。使用此功能时,请不要忘记以后设置正确的 provider。
var MyOtherContract = MyContract.clone(1337);
Contract Instance API
每个合约实例根据源 Solidity 合约而不同,并且 API 是动态创建的。出于本文档的目的,让我们使用以下 Solidity 源代码:
contract MyContract {
uint public value;
event ValueSet(uint val);
function setValue(uint val) {
value = val;
ValueSet(value);
}
function getValue() view returns (uint) {
return value;
}
}
从 Javascript 的角度来看,这个合约有三个函数:setValue
,getValue
和 value
。这是因为 value
是公共的并且自动为它创建一个 getter 函数。
Making a transaction via a contract function
当我们调用 setValue()
时,这会创建一个交易。来自 Javascript:
instance.setValue(5).then(function(result) {
// result object contains import information about the transaction
console.log("Value was set to", result.logs[0].args.val);
});
返回的结果对象如下所示:
{
tx: "0x6cb0bbb6466b342ed7bc4a9816f1da8b92db1ccf197c3f91914fc2c721072ebd",
receipt: {
// The return value from web3.eth.getTransactionReceipt(hash)
// See https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgettransactionreceipt
},
logs: [
{
address: "0x13274fe19c0178208bcbee397af8167a7be27f6f",
args: {
val: BigNumber(5),
},
blockHash: "0x2f0700b5d039c6ea7cdcca4309a175f97826322beb49aca891bf6ea82ce019e6",
blockNumber: 40,
event: "ValueSet",
logIndex: 0,
transactionHash: "0x6cb0bbb6466b342ed7bc4a9816f1da8b92db1ccf197c3f91914fc2c721072ebd",
transactionIndex: 0,
type:"mined",
},
],
}
请注意,如果交易中正在执行的函数具有返回值,则不会在此结果中得到该返回值。我们必须使用事件(如 ValueSet
)在 logs
数组中并查找结果。
即,如果函数本身有返回值,则返回函数的返回值。如果函数本身没有返回值,而且函数内部不会触发日志,但返回空。如果函数本身没有返回值,但是函数内部有触发日志,则返回函数的日志。如果函数会触发多个日志呢?
Explicitly making a call instead of a transaction
我们可以通过显式使用 .call
来调用 setValue()
而无需创建交易:
instance.setValue.call(5).then(...);
在这种情况下上述方法不是很有用,因为 setValue()
设置了东西,我们传递的值将不会被保存,因为我们没有创建交易。
Calling getters
但是,我们可以使用 getValue()
使用 .call()
获取值。call() 始终是免费的,不需要任何以太币,因此它们适用于调用从区块链读取数据的函数:
instance.getValue.call().then(function(val) {
// val reprsents the `value` storage object in the solidity contract
// since the contract returns that value.
});
更有帮助的是,当一个函数被标记为 view
或 pure
时,我们甚至不需要使用 .call
,因为 truffle-contract
会自动知道该函数只能通过 call() 进行交互:
instance.getValue().then(function(val) {
// val reprsents the `value` storage object in the solidity contract
// since the contract returns that value.
});
处理交易结果
当进行交易时,将获得一个 result
对象,该对象为我们提供有关交易的大量信息。得到的交易有(result.tx
),解码的事件(也称为日志; result.logs
)和交易回执(result.receipt
)。在下面的示例中,将收到 ValueSet()
事件,因为使用 setValue()
函数触发了事件:
instance.setValue(5).then(function(result) {
// result.tx => transaction hash, string
// result.logs => array of trigger events (1 item in this case)
// result.receipt => receipt object
});
发送以太币/触发回退函数(fallback function)
可以通过向此函数发送交易来触发回退功能:
instance.sendTransaction({...}).then(function(result) {
// Same result object as above.
});
这与所有可用的合约实例函数一样被默认,并且具有与没有回调的 web3.eth.sendTransaction
相同的 API。 to
值将自动填写。
如果只想将以太币发送给合约,可以使用速记:
instance.send(web3.toWei(1, "ether")).then(function(result) {
// Same result object as above.
});
估算 Gas 消耗
运行此函数以估算 Gas 使用情况:
instance.setValue.estimateGas(5).then(function(result) {
// result => estimated gas for this transaction
});
Testing
该软件包是将 EtherPudding 分解为多个模块的结果。测试目前位于truffle-artifactor,但很快就会移到此处。
Reference
- https://github.com/trufflesuite/truffle/tree/next/packages/truffle-contract
- https://github.com/trufflesuite/truffle-artifactor
- https://github.com/trufflesuite/truffle-contract
Contributor
- Windstamp, https://github.com/windstamp
网友评论