The contract abstraction API is something that makes truffle-contract very special compared to web3.js. Here is why it's special:
- It will automatically fetch default values, such as library addresses, contract addresses, and so on, depending on which network it's connected to; therefore, you don't have to edit the source code every time you change the network.
- You may choose to listen to certain events in certain networks only.
- It makes it easy to link libraries to contract's byte code at runtime. There are several other benefits you will find out once you have explored how to use the API.
Before we get into how to create a contract abstraction and its methods, let's write a sample contract, which the contract abstraction will represent. Here is the sample contract:
pragma Solidity ^0.4.0;
import "github.com/pipermerriam/ethereum-string-utils/contracts/StringLib.sol";
contract Sample
{
using StringLib for *;
event ping(string status);
function Sample()
{
uint a = 23;
bytes32 b = a.uintToBytes();
bytes32 c = "12";
uint d = c.bytesToUInt();
ping("Conversion Done");
}
}
This contract converts uint into bytes32 and bytes32 into uint using the StringLib library. StringLib is available at the 0xcca8353a18e7ab7b3d094ee1f9ddc91bdf2ca6a4 address on the main network, but on other networks, we need to deploy it to test the contract. Before you proceed further, compile it using browser Solidity, as you will need the ABI and byte code.
Now let's create a contract abstraction representing the Sample contract and the StringLib library. Here is the code for this. Place it in the HTML file:
var provider = new Web3.providers.HttpProvider("http://localhost:8545");
var web3 = new Web3(provider);
var SampleContract = TruffleContract({
abi: [{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"status","type":"string"}],"name":"ping","type":"event"}],
unlinked_binary: "6060604052341561000c57fe5b5b6000600060006000601793508373__StringLib__6394e8767d90916000604051602001526040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018082815260200191505060206040518083038186803b151561008b57fe5b60325a03f4151561009857fe5b5050506040518051905092507f31320000000000000000000000000000000000000000000000000000000000009150816000191673__StringLib__6381a33a6f90916000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180826000191660001916815260200191505060206040518083038186803b151561014557fe5b60325a03f4151561015257fe5b5050506040518051905090507f3adb191b3dee3c3ccbe8c657275f608902f13e3a020028b12c0d825510439e5660405180806020018281038252600f8152602001807f436f6e76657273696f6e20446f6e65000000000000000000000000000000000081525060200191505060405180910390a15b505050505b6033806101da6000396000f30060606040525bfe00a165627a7a7230582056ebda5c1e4ba935e5ad61a271ce8d59c95e0e4bca4ad20e7f07d804801e95c60029",
networks: {
1: {
links: {
"StringLib": "0xcca8353a18e7ab7b3d094ee1f9ddc91bdf2ca6a4"
},
events: {
"0x3adb191b3dee3c3ccbe8c657275f608902f13e3a020028b12c0d825510439e56": {
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "status",
"type": "string"
}
],
"name": "ping",
"type": "event"
}
}
},
10: {
events: {
"0x3adb191b3dee3c3ccbe8c657275f608902f13e3a020028b12c0d825510439e56": {
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "status",
"type": "string"
}
],
"name": "ping",
"type": "event"
}
}
}
},
contract_name: "SampleContract",
});
SampleContract.setProvider(provider);
SampleContract.detectNetwork();
SampleContract.defaults({
from: web3.eth.accounts[0],
gas: "900000",
gasPrice: web3.eth.gasPrice,
})
var StringLib = TruffleContract({
abi: [{"constant":true,"inputs":[{"name":"v","type":"bytes32"}],"name":"bytesToUInt","outputs":[{"name":"ret","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"v","type":"uint256"}],"name":"uintToBytes","outputs":[{"name":"ret","type":"bytes32"}],"payable":false,"type":"function"}],
unlinked_binary: "6060604052341561000c57fe5b5b6102178061001c6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806381a33a6f1461004657806394e8767d14610076575bfe5b6100606004808035600019169060200190919050506100aa565b6040518082815260200191505060405180910390f35b61008c6004808035906020019091905050610140565b60405180826000191660001916815260200191505060405180910390f35b6000600060006000600102846000191614156100c557610000565b600090505b60208110156101355760ff81601f0360080260020a85600190048115156100ed57fe5b0416915060008214156100ff57610135565b603082108061010e5750603982115b1561011857610000565b5b600a8302925060308203830192505b80806001019150506100ca565b8292505b5050919050565b60006000821415610173577f300000000000000000000000000000000000000000000000000000000000000090506101e2565b5b60008211156101e157610100816001900481151561018e57fe5b0460010290507f01000000000000000000000000000000000000000000000000000000000000006030600a848115156101c357fe5b06010260010281179050600a828115156101d957fe5b049150610174565b5b8090505b9190505600a165627a7a72305820d2897c98df4e1a3a71aefc5c486aed29c47c80cfe77e38328ef5f4cb5efcf2f10029",
networks: {
1: {
address: "0xcca8353a18e7ab7b3d094ee1f9ddc91bdf2ca6a4"
}
},
contract_name: "StringLib",
})
StringLib.setProvider(provider);
StringLib.detectNetwork();
StringLib.defaults({
from: web3.eth.accounts[0],
gas: "900000",
gasPrice: web3.eth.gasPrice,
})
Here is how the preceding code works:
- At first, we create a provider. Using this provider, truffle-contract will communicate with the node.
- Then, we create a contract abstraction for the Sample contract. To create a contract abstraction, we use the TruffleContract function. This function takes an object, which contains various kinds of information about the contract. This object can be termed as an artifacts object. The abi and unlinked_binary properties are compulsory. The other properties of the object are optional. The abi property points to the ABI of the contract, whereas the unlinked_binary property points to the unlinked binary code of the contract.
- Then, we have a property network that indicates various kinds of information about the contract in various networks. Here, we are saying that in network ID 1, the StringLib dependency is deployed at the 0xcca8353a18e7ab7b3d094ee1f9ddc91bdf2ca6a4 address so that at the time of deploying the Sample contract in network 1, it will link it automatically. Under a network object, we can also put an address property, indicating that the contract is already deployed to this network and this is the contract address. We also have an events objects in the networks object, which specifies the events of the contract we are interested in catching. The keys of the events object are topics of events and the values are the ABI of events.
- Then, we call the setProvider method of the SampleContract object by passing a new provider instance. This is a way to pass the provider so that truffle-contract can communicate with the node. The truffle-contract API doesn't provide a way to set the provider globally; instead, you need to set a provider for every contract abstraction. This is a feature that lets us connect and work on multiple networks at once with ease.
- Then, we call the detectNetwork method of the SampleContract object. This is the way to set the network ID that the contract abstraction is currently representing; that is, during all the operations on the contract abstraction, the values mapped to this network ID are used. This method will automatically detect which network ID our node is connected to and will set it automatically. If you want to manually set the network ID or change it at runtime, then you can use SampleContract.setNetwork(network_id). If you change the network ID, then make sure that the provider is also pointing to the node of the same network since truffle-contract won't be able to map the network ID with correct links, addresses, and events otherwise.
- Then, we set default values for transactions made for SampleContract. This method gets and, optionally, sets transaction defaults. If called without any parameters, it will simply return an object representing the current defaults. If an object is passed, this will set new defaults.
- We did the same for the StringLib library in order to create a contract abstraction for it.