与合约进行交互

介绍

如果我们为了与合约进行(测试)交互而每次都向波场网络进行原始请求,我们很快就会意识到编写这些请求是笨重而繁琐的。 同样,我们可能会发现管理每个请求的状态是 复杂的。 幸运的是,TronBox 为我们处理这种复杂性,使我们与合约的互动变得轻而易举。

数据的读和写

波场网络区分将数据写入网络和从网络读取数据,在编写应用程序我们需要关注这个区别。 通常,写入数据称为交易 transaction,而读取数据称为 调用 call交易调用的处理方式是截然不同的,下面介绍:

交易 Transactions

交易是改变了网络的状态。 交易可以像 发送 TRX 到一个帐户一样简单,也可以像执行合约函数或向网络部署新合约一样复杂。交易的特征是它写入(或更改)数据。 一个交易需要耗费能量运行,称为 “ENERGY”,交易同样需要(较长)时间来处理。 当我们通过交易执行合约的函数时,我们无法接收该函数的返回值,因为交易不会立即处理。 通常,通过交易执行的函数不会返回值,仅仅是返回一个交易 ID。 可总结交易的特征如下:

  • 消耗 ENERGYBANDWIDTH 费用(可通过冻结 TRX 获得)
  • 会更改网络状态
  • 不会立即执行(需要等待网络超级代表打包)
  • 没有执行返回值(只是一个交易 ID)。

调用 Call

调用则不同,调用依然可以在网络上执行合约代码,但不会永久更改任何数据(如状态变量)。调用的特征是读取数据。 当我们通过调用执行合约函数时,我们可以立刻获取到返回值。 可总结调用的特点:

  • 免费(不消耗 ENERGYBANDWIDTH
  • 不改变网络状态
  • 立即执行
  • 有返回值

选择使用 交易 还是 调用 关键是看 读取数据 还是需要写入数据。

什么是合约抽象

合约抽象( 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];
    }
}

除了构造函数之外,这个合约还有三个方法(sendCoingetConvertedBalancegetBalance),这三种方法都可以作为交易调用来执行。

现在让我们来看看 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) - 交易哈希 hash
  • log (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