使用truffle來與合約互動
使用truffle來與合約互動
測試
1.config
啟動local節點測試
設定truffle的network port =>
2.compile
truffle compile
3.test
truffle test
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
1_initial_migration.js: 2_deploy_contracts.js:
truffle migrate
output: local節點output
5.與合約互動
開啟console
> let instance = await MetaCoin.deployed()
> instance
...
5.1.進行合約交易Making a transaction
> let accounts = await web3.eth.getAccounts()
> instance.sendCoin(accounts[1], 10, {from: accounts[0]})
{
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 thesendCoin
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 thefrom
address ensuring this transaction came fromaccounts[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()
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
> result.tx
> result.receipt
> result.logs
tx: receipt:
logs:
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]
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
與先前的比較:
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
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.
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.
sendTransaction
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.