Interacting with Deployed Ethereum Contracts in Truffle

Bernard Peh
4 min readApr 15, 2018

--

The best way to query a deployed ethereum contract is via the web3.js library and the ethereum wiki has very good documentation on it.

Web3 talks to the Ethereum node via RPC calls. However, most Ethereum developers (including myself) are more familiar with the truffle framework which abstracts our interaction with the web3 library. To get an instance of a deployed contract in truffle, we tend to do this:

var myContract = artifacts.require("MyContract");myContract.deployed()

The code above will return the deployed MyContract promise object for us to work with. To appreciate what is going on, Truffle has done a lot of heavy lifting for us by allowing us to use the truffle-artifacts library which is itself a wrapper around the truffle-contract library which in turns wraps around the web3.js library. Hence, we are actually interacting with web3.js from a very high level. The artifact is a json representation of the contract from the “truffle compile” command. We can usually see all our contracts in json format under the “/build/contracts/” folder and they follow the truffle-contract-schema standard closely.

“myContract.deployed()” returns an instance of the deployed promise MyContract object, meaning that the deployed address is known and recorded in the json file already. This is not a problem if the contract is deployed from the local machine. What if we want to query a deployed contract not deployed by us?

For example, 0x10d4b24938f6de7ae4048c7273f09c50d5caf4d contains a smart contract of a certification registry in the main net. To find out the number of certified students, we would query getCertifiedStudentsCount(). However, Etherscan does a good job of making us lazy by telling us the answer if we read the contract.

For users who prefers Myetherwallet.com,

So 56 certified students in the contract at the time of writing - No development work needed.

It’s all good until what want to call getCertifiedStudentsCount() in our script to do more advance things. Something so simple suddenly requires a few more ingredients. Here are the challenges:

  1. We run a dummy testnet for development most of the time. Now we need to access the main net, what’s the best way to do it without downloading the the entire chain?

Ans: Thanks to infura.io, they have allowed everyone to query their Ethereum node and we no longer need to download gigs of data to run a live node just to talk to the main net.

2. We will need the deployed contract’s abi to be able to access the contract’s function. Where do we get the abi?

Ans: If the contract source code is available, then the abi can be retrieved easily in etherscan (if published) or by running truffle compile . If the source code is not available, then we need to try to get the abi from the bytecode by decompiling it. A good tool for this use case would be Mythril.

3. If getCertifiedStudentsCount() is payable and it requires someone to pay some ethereum in order view the results, then our call need to be connected to a wallet and need to spend some ether when calling the function.

Ans: We could create a new account in a simple HD wallet like metamask, then use the 12-word mnemonic phrase as inputs to sign the transaction when making the call. truffle-hdwallet-provider is a good library for this use case.

How do we do all these without leaving truffle?

In truffle.js, we created different environments so that we could switch between them easily when needed. Note that we have used dotenv library as well so that we could store our infura credentials in the .env file instead.

require('dotenv').config();
var HDWalletProvider = require("truffle-hdwallet-provider");

module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*", // Match any network id
gas: 4700000
},
ropsten: {
provider: function() {
return new HDWalletProvider(process.env.INFURA_ROPSTEN_MNEMONIC, "https://ropsten.infura.io/"+process.env.INFURA_KEY)
},
network_id: "3"
},
main: {
provider: function() {
return new HDWalletProvider(process.env.INFURA_MAINNET_MNEMONIC, "https://mainnet.infura.io/"+process.env.INFURA_KEY)
},
network_id: "1"
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
};

We now need to create CertificationDb.json so that we could interact with it as an artifact. In build/contracts/CertificationDb.json, we add the following information:

{
"contractName": "CertificationDb",
"abi": [{"constant":true,"inputs": ...}]
}

The contract abi could be copied from etherscan. I have left out the full abi here for the sake of simplicity.

Next, let us start the query in query.js.

var contract = artifacts.require("CertificationDB");

var contract_address = '0x10d4b24938f6de7ae4048c7273f09c50d5caf4d9';

module.exports = function() {

async function getCertifiedStudentsCount() {
let ins = await contract.at(contract_address);
let res = await ins.getCertifiedStudentsCount();
console.log('no. of passed students is '+res.toString());
}
getCertifiedStudentsCount();
}

To test that everything is working, we run the truffle exec command.

> truffle exec query.js --network mainUsing network 'main'.no. of certified students: 56

Cool, I got the answer I want. The source code for this article can be found in my repo.

Finally, for users who want to stick to web3,

var contract_address = '0x10d4b24938f6de7ae4048c7273f09c50d5caf4d9';module.exports = function() {  // If using web3 straight away, we need the abi
var abi = [{"constant":true,"inputs":...];
// web3.eth.contract(abi).at(contract_address) is the magic line to get the contract object.
let contract = web3.eth.contract(abi).at(contract_address);
contract.getCertifiedStudentsCount((err, res)=>{
console.log('no. of certified students: '+res.toString());
});
}

Have fun.

--

--