使用truffle來與合約互動

使用truffle來與合約互動


測試 1a5d7ba5727df069f8e2e1eb598e73f0.png

195b14f1a5013ba216066469cacc2ffc.png

1.config

啟動local節點測試 9e3f3eb019b370d34eb65a0ea7c643ac.png 8c732e72f4710f05dddc703ea509576c.png

設定truffle的network port 917c6ab62e7f9bd4afbb304b2b5d2f63.png 29c8315af3eba43ef696db1afb372351.png => f87eda9ea5a4589ce14d2faa2aa8f77d.png

2.compile

truffle compile

6874be0cd1b050871159e25e382aa63c.png 21f5f6186e054a70644f9400422d9943.png

3.test

truffle test

f3b492916308b150e051745a82b11dfc.png 4893cd37e948ff7f3caa637cd53f7936.png 0dd119a5de020fee7ba5e554dd4bcda7.png

3.1.Test with solidity

pragma solidity >=0.4.25 <0.7.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";

contract TestMetaCoin {

  function testInitialBalanceUsingDeployedContract() public {
    MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

}

3.2.Test with js

const MetaCoin = artifacts.require("MetaCoin");

contract('MetaCoin', (accounts) => {
  it('should put 10000 MetaCoin in the first account', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const balance = await metaCoinInstance.getBalance.call(accounts[0]);

    assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
  });
  it('should call a function that depends on a linked library', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const metaCoinBalance = (await metaCoinInstance.getBalance.call(accounts[0])).toNumber();
    const metaCoinEthBalance = (await metaCoinInstance.getBalanceInEth.call(accounts[0])).toNumber();

    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, 'Library function returned unexpected function, linkage may be broken');
  });
  it('should send coin correctly', async () => {
    const metaCoinInstance = await MetaCoin.deployed();

    // Setup 2 accounts.
    const accountOne = accounts[0];
    const accountTwo = accounts[1];

    // Get initial balances of first and second account.
    const accountOneStartingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoStartingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();

    // Make transaction from first account to second.
    const amount = 10;
    await metaCoinInstance.sendCoin(accountTwo, amount, { from: accountOne });

    // Get balances of first and second account after the transactions.
    const accountOneEndingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoEndingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();


    assert.equal(accountOneEndingBalance, accountOneStartingBalance - amount, "Amount wasn't correctly taken from the sender");
    assert.equal(accountTwoEndingBalance, accountTwoStartingBalance + amount, "Amount wasn't correctly sent to the receiver");
  });
});

4.migrate

b69f10faac8ca563c5397ad5af51aa60.png 1_initial_migration.js: 68f6068da5c926c7eff0e938acb2a63f.png 2_deploy_contracts.js: f7d96545a8580d81340cd7eea3fccc62.png

truffle migrate

output: 97c657cf41f436c2fa1613cb0444b7eb.png 641b7ec27ff4a26fed8293a12baad332.png 0eab6caf17035fc673cd3c461daa0a24.png local節點output ac7a6749a8e1d9b2edd9846b7e18d722.png


5.與合約互動

開啟console c9883fdbf32537d74bf4af129f559207.png

> let instance = await MetaCoin.deployed()
> instance

60727b4aeb518515fe27110d94f3d997.png ... 08ca6b7956403165bce69d540a50babb.png

5.1.進行合約交易Making a transaction

> let accounts = await web3.eth.getAccounts()
> instance.sendCoin(accounts[1], 10, {from: accounts[0]})

b66661bff8bdd038788a7846b2813252.png

{
  tx: '0x0f56c4b1cef3532f89b1e8e5262ba44a8eac712d0cb0fc3ca6aa1995edd4cdbb',
  receipt: {
    transactionHash: '0x0f56c4b1cef3532f89b1e8e5262ba44a8eac712d0cb0fc3ca6aa1995edd4cdbb',
    transactionIndex: 0,
    blockHash: '0x5ec6ae6b5d382e0380d59b972fbb28feda806e37b436c77fe231d39e4e6120c5',
    blockNumber: 12,
    from: '0xeede7f1261c872e2e72ae5a5d1cf94c961ea37d9',
    to: '0x78164c8d2c823d7a755f9a7a6b52af78fa8245ad',
    gasUsed: 51508,
    cumulativeGasUsed: 51508,
    contractAddress: null,
    logs: [ [Object] ],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000001000000000000000000000000002000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000000000000020000000000000000000',
    rawLogs: [ [Object] ]
  },
  logs: [
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x0f56c4b1cef3532f89b1e8e5262ba44a8eac712d0cb0fc3ca6aa1995edd4cdbb',
      blockHash: '0x5ec6ae6b5d382e0380d59b972fbb28feda806e37b436c77fe231d39e4e6120c5',
      blockNumber: 12,
      address: '0x78164C8d2c823D7a755f9A7a6B52Af78fA8245ad',
      type: 'mined',
      removed: false,
      id: 'log_a17f2f00',
      event: 'Transfer',
      args: [Result]
    }
  ]
}

We called the abstraction's sendCoin function directly. This will result in a transaction by default (i.e, writing data) instead of call.

We passed an object as the third parameter to sendCoin. Note that the sendCoin function in our Solidity contract doesn't have a third parameter. What you see above is a special object that can always be passed as the last parameter to a function that lets you edit specific details about the transaction ("transaction params"). Here, we set the from address ensuring this transaction came from accounts[0]. The transaction params that you can set correspond to the fields in an Ethereum transaction: from, to, gas, gasPrice, value, data, nonce

5.2.進行合約呼叫Making a call

Continuing with MetaCoin, notice the getBalance function is a great candidate for reading data from the network. It doesn't need to make any changes, as it just returns the MetaCoin balance of the address passed to it. Let's give it a shot:

> let balance = await instance.getBalance(accounts[0])
> balance.toNumber()

33df343338de671265ff51bd2281e223.png

What's interesting here:

We received a return value. Note that since the Ethereum network can handle very large numbers, we're given a BN object which we then convert to a number.

5.3.處理交易結果Processing transaction results

When you make a transaction, you're given a result object that gives you a wealth of information about the transaction.

> let result = await instance.sendCoin(accounts[1], 10, {from: accounts[0]})
> result

594a685630db07c482abfceda06d0cf5.png

> result.tx
> result.receipt
> result.logs

tx: 8259918f1abd93145db652ccfeacbcd1.png receipt: b9216dc79c637a53792cd251e13c8595.png

logs: 57b3b3db1455f6f36cde4923d8559d2c.png 35e5ac2e10bea60f37211fc2e640040a.png

5.4.捕捉事件Catching events

Your contracts can fire events that you can catch to gain more insight into what your contracts are doing. The easiest way to handle events is by processing the logs array contained within result object of the transaction that triggered the event.

If we explicitly output the first log entry we can see the details of the event that was emitted as part of the sendCoin call (Transfer(msg.sender, receiver, amount);).

> result.logs[0]

69d45a055d0d784fe2f87f1a2fab644e.png

5.5.向網絡添加新合約Add a new contract to the network

In all of the above cases, we've been using a contract abstraction that has already been deployed. We can deploy our own version to the network using the .new() function:

> let newInstance = await MetaCoin.new()
> newInstance.address

fa825a40d8da8b8d44fd6090509ca26d.png

與先前的比較: 4c42db2c1365b0e3e50271f5c9024232.png

5.6.在特定地址使用合約Use a contract at a specific address

If you already have an address for a contract, you can create a new abstraction to represent the contract at that address

> let specificInstance = await MetaCoin.at("0x78164C8d2c823D7a755f9A7a6B52Af78fA8245ad"); // put address

1652a6da0854b7ec0305fd189010f90f.png

5f14b940e617ac9d07df7bb9e303b183.png

5.7.向合約發送以太幣Sending ether to a contract

You may simply want to send Ether directly to a contract, or trigger a contract's fallback function. You can do so using one of the following two options.

Option 1: Send a transaction directly to a contract via instance.sendTransaction(). This is promisified like all available contract instance functions, and has the same API as web3.eth.sendTransaction but without the callback. The to value will be automatically filled in for you if not specified.

instance.sendTransaction({...}).then(function(result) {
  // Same transaction result object as above.
});

Option 2: There's also shorthand for just sending Ether directly:

instance.send(web3.utils.toWei(1, "ether")).then(function(result) {
  // Same result object as above.
});

5.8.Truffle 合約對象的特殊方法Special methods on Truffle contract objects

There are a couple of special functions that you can find on the actual contract methods of your contract abstractions:

  • estimateGas
  • sendTransaction
  • call
  • request

The first special method mentioned above is the estimateGas method. This, as you probably can guess, estimates the amount of gas that a transaction will require. If we wanted to estimate the gas for a transaction, we would call it on the contract method itself. It would look something like the following:

const instance = await MyContract.deployed();
const amountOfGas = await instance.sendTokens.estimateGas(4, myAccount);

This will give us an estimate of how much gas it will take to run the transaction specified.

Note that the arguments above (4 and myAccount) correspond to whatever the signature of the contract method happens to be.

Another useful thing to note is that you can also call this on a contract's new method to see how much gas it will take to deploy. So you would do Contract.new.estimateGas() to get the gas estimate for the contract's deployment.

ex.

ce5fe6408af952d447c22a2163741a11.png 06cfedf9f8e9d381443bb116b554ecfe.png

The next mentioned method is sendTransaction. In general, if you execute a contract method, Truffle will intelligently figure out whether it needs to make a transaction or a call. If your function can be executed as a call, then Truffle will do so and you will be able to avoid gas costs.

There may be some scenarios, however, where you want to force Truffle to make a transaction. In these cases, you can use the sendTransaction method found on the method itself. This would look something like instance.myMethod.sendTransaction().

For example, suppose I have a contract instance with the method getTokenBalance. I could do the following to force a transaction to take place while executing getTokenBalance:

const instance = await MyContract.deployed();
const result = await instance.getTokenBalance.sendTransaction(myAccount);

The result variable above will be the same kind of result you would get from executing any normal transaction in Truffle. It will contain the transaction hash, the logs, etc.

ex.

11d560db470730d3d72d7adb414c353c.png sendTransaction 7ae726df41750c5d3a370faf95f9c3f0.png 22010fd61a9daeca626d0c316ccda719.png

The next method is call and the syntax is exactly the same as for sendTransaction. If you want to explicitly make a call, you can use the call method found on your contract abstraction's method. So you would write something that looks like const result = await instance.myMethod.call().

The last method is request. This method does not perform a transaction or call, but rather returns an object that can be passed to web3.eth.sendTransaction or web3.eth.call if you want to perform the transaction or call yourself. It has the same syntax as the others, and like with estimateGas, you can also do Contract.new.request() if you want to perform a manual deployment.

5.9.調用重載方法Invoking overloaded methods

The current implementation of Truffle's contract abstraction can mistakenly infer the signature of an overloaded method even though it exists in the contract ABI.

Therefore, some methods may not be accessible through the contract's instance, but their accessors can be invoked explicitly via the .methods property of the contract.

5.10.使用枚舉Using enumerations

Contract abstractions can also be used to access Solidity enumerations defined within that contract. For instance, suppose we have the following Solidity contract:

contract ExampleContract {
  enum ExampleEnum {
    ExampleOption0,
    ExampleOption1,
    ExampleOption2
  }

  // ...
}

One could then use ExampleContract.ExampleEnum.ExampleOption0 to access that enum value; in this case, that is equal to 0, but using this allows one to pass in enums to contract methods without having to worry about their numerical value.

A contract's enums are also available under .enums, so in this case, one could also write ExampleContract.enums.ExampleEnum.ExampleOption0.


Ref: truffle/contract Interacting with your contracts