介绍
如果我们为了与合约进行(测试)交互而每次都向波场网络进行原始请求,我们很快就会意识到编写这些请求是笨重而繁琐的。 同样,我们可能会发现管理每个请求的状态是 复杂的。 幸运的是,TronBox 为我们处理这种复杂性,使我们与合约的互动变得轻而易举。
数据的读和写
波场网络区分将数据写入网络和从网络读取数据,在编写应用程序我们需要关注这个区别。 通常,写入数据称为交易 transaction,而读取数据称为 调用 call。 交易
和调用
的处理方式是截然不同的,下面介绍:
交易 Transactions
交易
是改变了网络的状态。 交易
可以像 发送 TRX 到一个帐户一样简单,也可以像执行合约函数或向网络部署新合约一样复杂。交易
的特征是它写入(或更改)数据。 一个交易
需要耗费能量运行,称为 “ENERGY”,交易
同样需要(较长)时间来处理。 当我们通过交易
执行合约的函数时,我们无法接收该函数的返回值,因为交易不会立即处理。 通常,通过交易执行的函数不会返回值,仅仅是返回一个交易 ID。 可总结交易
的特征如下:
- 消耗
ENERGY
和BANDWIDTH
费用(可通过冻结 TRX 获得) - 会更改网络状态
- 不会立即执行(需要等待网络超级代表打包)
- 没有执行返回值(只是一个交易 ID)。
调用 Call
调用
则不同,调用
依然可以在网络上执行合约代码,但不会永久更改任何数据(如状态变量)。调用
的特征是读取数据。 当我们通过调用
执行合约函数时,我们可以立刻获取到返回值。 可总结调用
的特点:
- 免费(不消耗
ENERGY
和BANDWIDTH
) - 不改变网络状态
- 立即执行
- 有返回值
选择使用 交易
还是 调用
关键是看 读取数据 还是需要写入数据。
什么是合约抽象
合约抽象( Contract abstraction)是通过 Javascript 与波场合约交互的基础。 简单说,合约抽象是一种代码封装,让我们可以轻松地与合约进行交互,从而让我们忘记在引擎盖下执行的引擎和齿轮。TronBox 通过 tronbox-contract 模块使用合约抽象,下面会介绍。
在这里,我们通过一个例子 metacoin
,来介绍合约抽象的作用,通过 TronBox,执行 tronbox unbox metacoin
使用 MetaCoin 合约,下面合约代码:
pragma solidity >=0.4.25 <0.6.0;
import "./ConvertLib.sol";
contract MetaCoin {
mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
constructor() public {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount);
return true;
}
function getConvertedBalance(address addr) public view returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) public view returns(uint) {
return balances[addr];
}
}
除了构造函数之外,这个合约还有三个方法(sendCoin
,getConvertedBalance
和 getBalance
),这三种方法都可以作为交易
或调用
来执行。
现在让我们来看看 TronBox 为我们提供的名为 “MetaCoin” 的 Javascript 对象,它可以在 TronBox 控制台访问,如:
tronbox(development) > MetaCoin.deployed().then(instance => console.log(instance));
// outputs:
//
// Contract
// - address: "0xa9f441a487754e6b27ba044a5a8eb2eec77f6b92"
// - allEvents: ()
// - getBalance: ()
// - getConvertedBalance: ()
// - sendCoin: ()
// ...
注意: 合约抽象包含与合约中完全相同的函数。 它还包含一个指向 MetaCoin 合约 部署版本的地址。
执行合约函数
使用 tronbox 的合约抽象,我们可以轻松地在波场网络上执行合约函数。
执行交易 Transactions
我们可以执行 MetaCoin 合约上的三个函数。 如果我们分析每一个函数,会发现 sendCoin
是唯一一个会改变网络状态的函数。sendCoin
的作用是从一个帐户“发送”一些 Meta coins 到另一个帐户,这个变化是需要持续保存的。
当调用 sendCoin
时,我们需要它作为一个交易
执行。 如在下面的示例中,使用交易
调用的方式从一个帐户向另一个帐户发送 10 个币:
tronbox(development) > MetaCoin.deployed().then(res => res.sendCoin(tronWeb._accounts[1], 500, { from: tronWeb._accounts[0] }));
以上代码有一些有趣的事情:
- 我们直接调用了
抽象合约
的sendCoin
函数。 它默认使用交易
的方式去执行,而不是使用调用
。 - 我们还用一个对象作为第三个参数传递给
sendCoin
函数。 注意,在 Solidity 合约中的sendCoin
函数没有第三个参数。 这是一个特殊对象,它始终可以作为最后一个参数传递给函数,该函数允许我们编辑有关交易
的特定信息。 在这里,我们设置了from
地址,确保此交易来自accounts [0]
。
执行调用 call
继续使用 MetaCoin,注意 getBalance
函数是从网络读取数据的理想选择。 它不需要进行任何更改,因为它只返回地址参数的 MetaCoin 余额。 让我们试一试:
tronbox(development) > MetaCoin.deployed().then(res => res.getBalance(tronWeb._accounts[0]));
处理交易结果
当我们进行交易
时,我们会得到一个 result
对象,它为我们提供了大量有关交易
的信息。
tronbox(development) > MetaCoin.deployed().then(res => res.sendCoin(tronWeb._accounts[1], 500, { from: tronWeb._accounts[0] }));
// outputs:
// 475d5006b56685cc40e07b36498f4d4809f2ce4fa9c678c108476dfe4e02c59e
具体来说,通过交易 id,我们将获得以下内容:
tronbox(development) > tronWeb.trx.getTransactionInfo('475d5006b56685cc40e07b36498f4d4809f2ce4fa9c678c108476dfe4e02c59e');
//outputs:
// {
// id: '475d5006b56685cc40e07b36498f4d4809f2ce4fa9c678c108476dfe4e02c59e',
// fee: 1253000,
// blockNumber: 64403,
// blockTimeStamp: 1667550996000,
// contractResult: [
// '0000000000000000000000000000000000000000000000000000000000000001'
// ],
// contract_address: '419c1aad134258c42af129822814098333ba251e52',
// receipt: {
// energy_fee: 1253000,
// energy_usage_total: 12530,
// net_usage: 346,
// result: 'SUCCESS'
// },
// log: [
// {
// address: '9c1aad134258c42af129822814098333ba251e52',
// topics: [Array],
// data: '000000000000000000000000989be99ce2c707c16b3d390e943b85c76381718600000000000000000000000093668cda1f0dce82c808d7c1134c3e85d7fc71d200000000000000000000000000000000000000000000000000000000000001f4'
// }
// ]
// }
id
(string) - 交易哈希 hashlog
(array) - 解码过的事件 (日志)receipt
(object) - 交易收据 receipt(包括使用的 ENERGY)
获取事件 events
通过获取合约触发交易中的事件,可以更深入地了解合约正在做什么。Solidity 使用 LOG 指令在交易信息 TransactionInfo 中记录事件信息。事件信息位于 TransactionInfo 的 log 字段,下面通过 gettransactioninfobyid API 获取到一个 TransactionInfo 来说明 event 的结构。
该 event 是 sendCoin
函数触发事件(Transfer(msg.sender,receiver,amount)
)。
{
"id": "475d5006b56685cc40e07b36498f4d4809f2ce4fa9c678c108476dfe4e02c59e",
......
"log": [
{
"address": "FEQNQt6AQsPiaFJGhrWbss6MjSXC9Prq2",
"topics": [
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
],
"data": "000000000000000000000000989be99ce2c707c16b3d390e943b85c76381718600000000000000000000000093668cda1f0dce82c808d7c1134c3e85d7fc71d200000000000000000000000000000000000000000000000000000000000001f4"
}
]
}
log: [
{
address: '9c1aad134258c42af129822814098333ba251e52',
topics: [Array],
data: '000000000000000000000000989be99ce2c707c16b3d390e943b85c76381718600000000000000000000000093668cda1f0dce82c808d7c1134c3e85d7fc71d200000000000000000000000000000000000000000000000000000000000001f4'
}
]
部署新合约
我们还可以使用 .new()
函数把自己的合约部署到网络:
tronbox(development)> MetaCoin.new('50').then(res => {console.log(res.address)})
41449ca55809f15910db70e67f63c875fcc33e04ac