One of the most exciting features that the Ethereum blockchain brings to the table is the ability to write smart contracts! This has revolutionized the use of blockchain as a technology and has opened new doors for its adaptation. Typically, platforms like ethereum, hyperledger, eos, and few others now let you build smart contracts however, ethereum still remains the most popular platform for writing smart contracts, and being the smart contract pioneer, holds the most number of dapps.
A quick note on, what are smart contracts?
In a nutshell, smart contracts are programs that are executed on a blockchain. Now, what kinds of programs? Totally depends on the use-case and yes, it can literally be anything. From the digital agreement between two or more entities, virtual tokens to more advanced concepts like escrows, vaults. The possibilities are endless. Exactly why smart contracts are so powerful. How do these smart contracts work internally in EVM ( Ethereum virtual machine ), I will write a new blog post for this and is a topic for some other day. Since the smart contracts are “deployed” on the blockchain, once deployed, the code and thereby smart contract behavior cannot be changed. This makes smart contract development a bit different from normal algorithmic and functional development.
What is truffle suit ?
Exactly as its website or title of this post says, its a suit of sweet tools. Tools that are created to make dapps ( Distributed apps ) and smart contract development convenient. Sounds cool, right? Trust me it really is. From my experience as a smart contract developer, truffle makes life so much easier. Truffle suite contains the following tools at the time of writing this article:
Truffle
Truffle is a tool designed to make smart contract development streamlined. As per truffle docs, it provides many out of the box features:
- Built-in smart contract compilation, linking, deployment, and binary management.
- Automated contract testing for rapid development.
- Scriptable, extensible deployment & migrations framework.
- Network management for deploying to any number of public & private networks.
- Package management with EthPM & NPM, using the ERC190 standard.
- Interactive console for direct contract communication.
- Configurable build pipeline with support for tight integration.
- External script runner that executes scripts within a Truffle environment.
Simple hands-on intro: Using truffle for smart contract development
Lets divide this section in several topics:
- Initialising the new truffle project
- Overview of generated project and folders
- Add smart contract
- Compiling smart contract
- Truffle migrations
- Running migrations and deploying the contract
- Contract interaction
Before proceeding, lets make sure that, we have truffle installed in our system. Truffle is NPM package and to install truffle:
npm install -g truffle
1. Initialising the new truffle project
For creating empty truffle project:
mkdir truffle-tutorial
cd truffle-tutorial
truffle init
2. Overview of generated project and folders
Open the project in your favorite code editor. By your favorite editor, I mean VS Code. If it’s not your favorite code editor, then we have a problem. Just kidding, I have a very dogmatic belief that VS Code is by far the best code editor made on the earth. Moving ahead, without wasting much time, you will have the following project structure:
- contracts/: This is the folder where we will add smart contracts. You can directly add smart contracts or can create nested directories.
- migrations/: Directory where we add migration scripts. Don’t worry, we will get to what migrations are soon.
- test/: Obviously, directory for adding test cases.
- truffle-config.js: Truffle configuration file.
3. Add smart contract
Lets add a smart contract now:
pragma solidity ^0.7.0;
contract SimpleContract {
uint8 public count;
function setInteger(uint8 _value) public {
count = _value;
}
}
I know, very simple smart contract, but I will show off my smart contract development expertise in some other post, not here. So this contract simply exposes one public function to update the value of the variable count. Since count is a public variable, we don’t need a separate getter here. Go ahead, and add a smart contract in a new file under contracts/ . If you notice, we already have Migrations.sol file created under contracts/ directory. Don’t worry, leave it as it is for now. We will get back to it in a section about migrations.
4. Compiling smart contract
Truffle exposes a command to compile your smart contracts. Any solidity files under contracts/ will be picked up and compiled. As a result of the compilation, a new build folder is generated which has compiled contract artifacts or what we call abi ( application binary interface ). Here is the command to compile:
truffle compile --all
The above command will trigger compilation of all the smart contracts. If –all flag is not passed, truffle will intelligently pick up only those contracts which have been changed from the version which was compiled earlier. Note that our solidity compiler version is 0.7.0 . If your truffle by default has this solidity compiler version, then you are all set. Otherwise, you will see an error saying something like:
SimpleContract.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.12+commit.7709ece9.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.7.0;
^---------------------^
Basically, we will need to manually override compiler version used for compiling our contract. So find truffle-config.json file in the project. Find the compiler key in exported json object:
compilers: {
solc: {
// version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
And uncomment the version key and change it to “0.7.0“. So while compiling the contracts, truffle will fetch the required compiler version. Now give it shot again. See, I told you truffle is awsome.
5. Truffle Migrations
If you are aware of the general ethereum as a blockchain, deploying contracts is equivalent to sending transactions to the blockchain and hence requires gas fees to be paid. Migrations.sol contract keeps track of the latest version of migrations deployed. What are the migration versions? If you look into migrations/ folder, you will find 1_initial_migration.js file. Observe the number at the start of this filename. This is basically the migration version and version 1 indicates that this is the initial migration. If we look at the contents of 1_initial_migration.js file:
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
So you guessed it correctly, 1_initial_migration.js basically deploys Migration.sol contract. Now this arrangement keeps track of the latest version of migration deployed and contracts deployed under that migration. When we again run migration command, truffle will first determine the affected / changed contracts by looking at the built artifacts. And only the contracts which are changed are deployed again, saving you the gas and most importantly time required for redundant deployments. Cool, right?
Migration file needs to export the function which will at least have one parameter i.e. deployer. This deployer object can be used for staging various deployment tasks and build deployment pipelines in multi-contract system architecture. deployer function also takes a few more params. e.g. networks, accounts.
deployer.deploy(contract, networks, accounts, options)
options is an object where we can specify various configuration and transaction parameters e.g. account from which transaction should be made, gasLimit and gasPrice etc. This gives nice control over the contract deployment process. Okay, time to write the migration for our SimpleContract. Create new file and name it 2_simple_contract_migration.js .
const SimpleContract = artifacts.require("SimpleContract");
module.exports = function(deployer) {
deployer.deploy(SimpleContract);
};
6. Running migrations and deploying the contract
Okay, so far, we have written a contract, compiled it and added a migration script for deploying it. But where should we deploy it? There are multiple options here: either deploy on testnets like ropsten or kovan, deploy on local blockchain instance created using Ganache or if you are very rich, directly deploy on mainnet where you will be required to pay fees. For keeping things simple, we will use ganache instance. Again, as we already know, ganache is one of the tools in truffle suite. So quickly install ganache-cli and spin up the blockchain instance locally in seconds.
npm install -g ganache-cli
ganache-cli -d
This will start the local blockchain instance quickly. Don’t get confused here. Ganache also has well built GUI for better visibility. So, if you want you can install the GUI version as well. We are using cli version of ganache because, it easy to install, lightweight and gives more control over the resulting blockchain. In development mode, where we need a fixed set of accounts for ease of testing, it’s recommended to start ganache in deterministic mode i.e. -d flag. This basically starts the blockchain instance using the same set of parameters every time. One more thing to be aware of is: ganache expose the RPC endpoint to interact with the blockchain on the PORT 8545 by default. Nevertheless, you can override this to suit your needs. Now lets add network configuration in truffle-config.js :
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
You can add multiple network blocks. e.g. one for deploying on ropsten, one for deploying on ethereum. In case of local ganache setup, all the ethereum accounts are unlocked by default and it picks up the first account as the default account and is used for deploying the contracts. This needs to be managed more carefully in case of ropsten and mainnet. Okay, so now, when we run the migrations, it will pick up this network configuration and will deploy our contracts to ganache blockchain. Are you ready?
truffle migrate --network development
Hurray! If everything goes right, you should see the logs giving the details about the migrations. Something like this:
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'development'
> Network id: 1599409246134
> Block gas limit: 0x6691b7
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0xcc5263987ae5101992eab63df3159351357d5becdf5f3e7642e998709502ac8d
> Blocks: 0 Seconds: 0
> contract address: 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab
> block number: 1
> block timestamp: 1599409666
> account: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
> balance: 99.99626098
> gas used: 186951
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00373902 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00373902 ETH
2_simple_contract_migration.js
==============================
Deploying 'SimpleContract'
--------------------------
> transaction hash: 0xcc77b5b9f9d4e1196d6d7193d3a67878b4cf6e74b11534857afb63e95886cc4a
> Blocks: 0 Seconds: 0
> contract address: 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
> block number: 3
> block timestamp: 1599409668
> account: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
> balance: 99.99334422
> gas used: 103503
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00207006 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00207006 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.00580908 ETH
7. Contract interaction
If you ask me, I will say that this is the best part about using truffle. This so far so cool framework has in-built console. In real life, you will not be writing contracts as easy and straightforward as our example SImpleContract. Things will get complex and testing, debugging and iterating over the contracts become much much important. Thats where this console comes handy. You can start the console in two modes:
- Truffle Console: A basic interactive console connecting to any Ethereum client.
- Truffle Develop: An interactive console that also spawns a development blockchain.
Since, we have already spawned our ganache blockchain, lets start the console and connect to it.
truffle console --network development
This interactive console already have access to various key things like your ethereum accounts, web3 instance and most importanctly your deployed contracts. Lets access our already deployed contract here:
truffle(development)> let contract = await SimpleContract.deployed()
undefined
truffle(development)> contract
// TruffleContract {
// constructor: [Function: TruffleContract] {
// _constructorMethods: {
// configureNetwork: [Function: configureNetwork],
// setProvider: [Function: setProvider],
// ......
Remember, truffle’s interactive console is very powerful tool and it deserves all the respect. We will have an in-depth overview of truffle console in separate article. For now, lets use it to interact with our SimpleContract. Lets check the default value of the variable count:
truffle(development)> let result = await contract.count()
undefined
truffle(development)> result
BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }
truffle(development)> result.toString()
'0'
Observe that, result is a bignumber object, another library truffle console has access to out of the box. Now, lets try and change the value of the variable count :
truffle(development)> let transaction = await contract.setInteger(20)
undefined
truffle(development)> transaction
{
tx: '0xdad3545aa41d492b6e848998fbddcce067fce27fdaa7a16eb5c9906f4109f54e',
receipt: {
transactionHash: '0xdad3545aa41d492b6e848998fbddcce067fce27fdaa7a16eb5c9906f4109f54e',
transactionIndex: 0,
blockHash: '0x604827d7e6a54398633f4949c06672706eca10304e07239b106f8a95d6144dd5',
blockNumber: 5,
from: '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1',
to: '0xcfeb869f69431e42cdb54a4f4f105c19c080a601',
gasUsed: 42305,
cumulativeGasUsed: 42305,
contractAddress: null,
logs: [],
status: true,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: []
},
logs: []
}accounts
And now if you again query the value of count, it will say 20. Sleek, right?
I hope, by now we are in total agreement about the truffle being the sweet suit for dapp and smart contracts development. The scope of truffle and its functionalities is not limited to what we have covered here. We will dive deep into some advanced topics like writing test cases, event handling, gas estimations, and using truffle with other blockchains like hyperledger and quoram eventually. Hope this write up on truffle was useful. Any reactions, comments, feedback, and constructive criticism is welcome and appreciated! See you again, until then, enjoy, be happy, and be safe!
Leave a Reply