POSTED 3 months ago

Truffle: Smart Contract Compilation & Deployment

Intro

Earlier in the series, we took a look at how to manually deploy and interact with our Bounties.sol smart contract on a local development blockchain.

We also briefly touched on development frameworks which hide the complexity of these repetitive tasks and allow us to focus on developing dApps.

This article will walk through the steps required to setup Truffle and use it to compile, deploy and interact with our Bounties.sol smart contract. You should see that this is a much easier process than the manual steps we learned in the previous article.

The source code used in this tutorial can be found here

What is Truffle?

Just to recap, Truffle is a Node based development framework which is currently the most used and actively maintained in the space.

https://truffleframework.com

Documentation

Installing Truffle

You will need to have NodeJS 5.0+ installed

npm install -g truffle

Read more on installing truffle here

Solc Compiler

When compiling our smart contracts truffle uses the solc compiler, earlier in the series we learnt have to install the solc compiler and compile our smart contract manually. However, Truffle already comes prepackaged with a version of the solc compiler:

$ truffle version
Truffle v4.1.13 (core: 4.1.13)
Solidity v0.4.24 (solc-js)

Above we see truffle version v4.1.13 comes packaged with solc compiler v0.2.24.

Creating a Truffle Project

To use most Truffle commands, you need to run them against an existing Truffle project. So the first step is to create a Truffle project:

$ mkdir dapp-series-bounties
$ cd dapp-series-bounties
$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

The truffle init command sets up a truffle project with the standard project directory structure:

  • contracts/: store original codes of the smart contract. We will place our Bounties.sol file here.
  • migrations/: instructions for deploying the smart contract(s) in the “contracts” folder.
  • test/: tests for your smart contract(s), truffle supports tests written in both Javascript and Solidity, well learn about writing tests in the next article
  • truffle.js: configuration file.
  • truffle-config.js: configuration document for windows user.

Now let's create a Bounties.sol file in the contracts folder and copy the contents of Bounties.sol which we previously developed.

Compile

We’re now ready to compile our smart contract.

$truffle compile
Compiling ./contracts/Bounties.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

That's it! The 2 smart contracts in the contracts folder:

  • Bounties.sol
  • Migrations.sol

We’re both compiled and the artifacts were written to ./build/contracts

If you review the Bounties.json file, you will find it is similar to the output we got when we manually compiled our Bounties.sol smart contract the previous article. It stores the ABI and also the bytecode for deployment and linking, however, this truffle artifact contains additional features that make interacting with and deploying smart contracts using truffle a smoother experience. You can read more about the truffle-artifactor here.

Deployment

Development Blockchain: Ganache-CLI

In order to deploy our smart contracts, we’re going to need an Ethereum environment to deploy to. For this, we will use Ganache-CLI to run a local development environment.

Installing Ganache-CLI

npm install -g ganache-cli

So let's start our local development blockchain environment:

$ ganache-cli
Ganache CLI v6.1.3 (ganache-core: 2.1.2)
Available Accounts
==================
(0) 0x11541c020ab6d85cb46124e1754790319f6d8c75
(1) 0xc44914b445ced4d36f7974ec9b07545c6b39d58d
(2) 0x443078060573a942bbf61dcdae9c711c9e0f3426
(3) 0x509fc43d6b95b570d31bd1e76b57046131e815ab
(4) 0xaf3464e80e8981e868907e39df4db9116518b0b8
(5) 0x9894e6253606ee0ce9e0f32ae98cacf9cedc580c
(6) 0x8dc4480b3d868bbeb6caf7848f60ff2866d5e98d
(7) 0x5da85775ca3cdf0048bff35e664a885ed2e02ff7
(8) 0x1acc13d7d69ac44a96c7ee60aeee13af6b001783
(9) 0x9c112d3a812b47909c2054a14fefbbb7a87fb721
Private Keys
==================
(0) 08f8aaea81590cea30e780666c8bdc6a3a17144130dcf20b07b55229b2d5996b
(1) b8ef92de39bcaf83eb7622ba62c2dd055f0d0c62053ab381aa5902fdd8698f91
(2) 8d0a626a420f68c6a1c99025fe4c17e02b8853feefd82a908bebdb994c589e31
(3) 7d6a122d935f9244b47919a24e218d9bb6d54eff63de5eb120405e3194bf7658
(4) 738d3ddcd659cc45ddf4044bc512ff841717af3cd0f27f77338bc759d6a9769d
(5) e9f82c125a8b9ca386b7cd59101ba4105a7c25d30727fdb937391798a01211ef
(6) 2c70bd342bf610cbc974b24ec6f11260cebd537cdde65d7922971a7d4858cc5b
(7) 8f27ce51b5b4784a75cddc2428861dc07c3dd4ceac81c2f32eb4d8f86ff51ca0
(8) 377ab95e5c5fbe97f8a298b4a108062b063e9ce5fa7e513691494f5458419f7a
(9) 4919c7b8934160a1ec197cf19474d326486d63ced25dfb65f0a692bdba3d2208
HD Wallet
==================
Mnemonic:      attend frost dignity wheat shell field comic tooth include enter border theory
Base HD Path:  m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on localhost:8545

The above output shows ganache-cli has started and is listening on localhost:8545

Migrations

In order to deploy to our local development environment, we’ll need to configure truffle:

If we take a look at the existing file 1initialmigration.js

var Migrations = artifacts.require("./Migrations.sol");

module.exports = function(deployer) {
  deployer.deploy(Migrations);
};

This will be the first step in the migration or deployment process. It tells truffle to first deploy the Migrations contract.

Migrations contract record the history of previous run migrations/deployments on chain, this enables truffle to incrementally update deployments to a specified environment.

You can read more about truffle migrations here

Configuring Truffle

  1. First, we need to create a file in the migrations folder with the name 2deploycontracts.js

The 2 indicates that this is the second step to be run in the migration process.

Copy the following extract into the 2deploycontracts.js file:

var Bounties = artifacts.require("./Bounties.sol");

module.exports = function(deployer) {
  deployer.deploy(Bounties);
};

  1. Update the truffle.js configuration file with the following extract:
module.exports = {
  networks: {
    development: {
      network_id: "*",
      host: 'localhost',
      port: 8545
    }
  }
};

This tells truffle that the default development environment to deploy to is located at host: localhost port: 8545 this is the address of our ganache-cli local development environment.

That's it, Truffle is now configured to deploy to your local ganache-cli development environment.

Deploy

To deploy simply run the truffle migrate command:

$ truffle migrate

Using network 'development'.
Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xbdbed14cedeb7be5b96436dd9d146639848903f8f55a6e6c256190bd2c8f9348
  Migrations: 0xffee96c8bb017051581b926af7c7e48a998b0d6c
Saving successful migration to network...
  ... 0x979ec31f9c60190f7b6412929bb6d99eff895aab27db8e8f619ce100b6702c2c
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Bounties...
  ... 0x1cfa32323e31aa262ea61580cb544772a47b05c2b498544a1805d00eb530a27a
  Bounties: 0x306ef500843eca5c7d8c3044a475c18b3b79091f
Saving successful migration to network...
  ... 0x671e77e36ba4c91ea545890506e8d36e8df6743e99bc652fe99a7e9a44083c9a
Saving artifacts...

The above output show the transactionHash of the deployment of the Bounties.sol contract as:

0x1cfa32323e31aa262ea61580cb544772a47b05c2b498544a1805d00eb530a27a

Also the address of the Bounties smart contract as:

0x306ef500843eca5c7d8c3044a475c18b3b79091f

We can double check the transaction receipt via the truffle console

$ truffle console

truffle(development)> web3.eth.getTransactionReceipt("0x1cfa32323e31aa262ea61580cb544772a47b05c2b498544a1805d00eb530a27a")
{ transactionHash: '0x1cfa32323e31aa262ea61580cb544772a47b05c2b498544a1805d00eb530a27a',
  transactionIndex: 0,
  blockHash: '0x812bd871bdf0b22d1c5183e1a0583a8337cca81996a51c740068a51a41ebfd65',
  blockNumber: 9,
  gasUsed: 1156264,
  cumulativeGasUsed: 1156264,
  contractAddress: '0x306ef500843eca5c7d8c3044a475c18b3b79091f',
  logs: [],
  status: '0x1',
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' }

Interacting With Our Contract

We can use the truffle console to interact with our deployed smart contract.

Lets attempt to issue a bounty. To do this we’ll need to set the string _data argument to some string “some requirements” and set the uint64 _deadline argument to a unix timestamp in the future e.g “1691452800” August 8th 2023.

Bounties.deployed().then(function(instance) { instance.issueBounty("some requirements","1691452800",{ from: web3.eth.accounts[0], value: web3.toWei(1, "ether"), gas: 3000000 }).then(function(tx) { console.log(tx) }) });
truffle(development)> { tx: '0x0dbd2f15f2bd943e18a54bbad5c52a143ccd663f313e99f3408896c0f1c2bf83',
  receipt:
   { transactionHash: '0x0dbd2f15f2bd943e18a54bbad5c52a143ccd663f313e99f3408896c0f1c2bf83',
     transactionIndex: 0,
     blockHash: '0x86ca1a41ca500858f265e101d90046c865b87e5399e17bc76d5d72f280fc8547',
     blockNumber: 14,
     gasUsed: 118660,
     cumulativeGasUsed: 118660,
     contractAddress: null,
     logs: [ [Object] ],
     status: '0x1',
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000040000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000' },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash: '0x0dbd2f15f2bd943e18a54bbad5c52a143ccd663f313e99f3408896c0f1c2bf83',
       blockHash: '0x86ca1a41ca500858f265e101d90046c865b87e5399e17bc76d5d72f280fc8547',
       blockNumber: 14,
       address: '0x306ef500843eca5c7d8c3044a475c18b3b79091f',
       type: 'mined',
       event: 'BountyIssued',
       args: [Object] } ] }

In the above extract we use the Bounties.deployed() method to return an instance of the latest deployed Bounties contract on the network.

Bounties.deployed().then(function(instance) {});

We can then call the issueBounty function on the retrieved instance.

instance.issueBounty("some requirements","1691452800",{ from: web3.eth.accounts[0], value: web3.toWei(1, "ether"), gas: 3000000 }).then(function(tx) { console.log(tx) });

We can call the bounties function with bountyId of 0 to double check the issueBounty function stored our data correctly:

truffle(development)> Bounties.deployed().then(function(instance) { instance.bounties.call(0).then(function(result) { console.log(result) }) })
truffle(development)> [ '0xc74a4fba809c8f0e6b410b349f2908a4dbb88123',
  { [String: '1691452800'] s: 1, e: 9, c: [ 1691452800 ] },
  'some requirements',
  { [String: '0'] s: 1, e: 0, c: [ 0 ] },
  { [String: '1000000000000000000'] s: 1, e: 18, c: [ 10000 ] } ]

Test Network: Rinkeby

We can also configure truffle to deploy to one of the public test Ethereum networks rather than a local development environment. Earlier in the series, we introduced the following public Ethereum test networks:

  • Rinkeby
  • Kovan
  • Ropsten

This part of the article will discuss deployment to the Rinkeby environment, however, the instructions can be used to deploy to either Kovan or Ropsten also.

Infura

In order to send transactions to a public network, you need access to a network node. Infura is a public hosted Ethereum node cluster, which provides access to its nodes via an API

https://infura.io

If you do not already have an Infura account, the first thing you need to do is register for an account.

Once logged in, create a new project to generate an API key, this allows you to track the usage of each individual dApp you deploy.

Once your project is created, select the environment we will be deploying to, in this case Rinkeby, from the Endpoint drop down and copy the endpoint URL for future reference:

Make sure you save this token and keep it private!

HDWallet Provider

Infura, for security reasons, does not manage your private keys.We need to add the Truffle HDWallet Provider so that Truffle can sign deployment transactions before sending them to an Infura node.

https://github.com/trufflesuite/truffle-hdwallet-provider

We can install the HDWallet Priovider via npm

npm install truffle-hdwallet-provider --save

Generate Mnemonic

To configure the HDWallet Provider we need to provide a mnemonic which generates the account to be used for deployment.

If you already have a mnemonic, feel free to skip this part.

You can generate a mnemonic using an online mnemonic generator.

https://iancoleman.io/bip39

In the BIP39 Mnemonic code form:

  1. Select “ETH — Ethereum” from the “Coin” drop down
  2. Select a minimum of “12” words
  3. Click the “Generate” button to generate the mnemonic
  4. Copy and save the mnemonic located in the field “BIP39”, remember to keep this private as it is the seed that can generate and derive the private keys to your ETH accounts

  1. Scroll down the page to the Derived Addresses section and copy and save the Address this will be your Ethereum deployment account.

NOTE: Your private key will be displayed here, please keep this private.

Above the address we’ll be using is: 0x56fB94c8C667D7F612C0eC19616C39F3A50C3435

Configure Truffle For Rinkeby

Now we have all the pieces set up, we need to configure truffle to use the HDWallet Provider to deploy to the Rinkeby environment. To do this we will need to edit the truffle.js configuration file.

First let's create a secrets.json file, this file will store your mnemonic and Infura API key so that it can be loaded by the hdwallet provider.

NOTE: Remember not to check this file into any public repository!

Next in the truffle.js configuration file add the following lines to define HDWalletProvider and load our mnemonic from our secrets.json file:

const HDWalletProvider = require('truffle-hdwallet-provider');
const fs = require('fs');
let secrets;
if (fs.existsSync('secrets.json')) {
 secrets = JSON.parse(fs.readFileSync('secrets.json', 'utf8'));
}

Next also in truffle.js we add a new network configuration so Truffle understands where to find the Rinkeby network.

rinkeby: {
      provider: new HDWalletProvider(secrets.mnemonic, 'https://rinkeby.infura.io/v3/'+secrets.infuraApiKey),
      network_id: '4'
}

Here we define a provider, which instantiates a HDWalletProvider for the Rinkeby network. The HDWalletProvider takes two arguments:

  1. mnemonic: The mnemonic required to derive the private key to the deployment account
  2. network endpoint: The http endpoint of the required network

We also set the network ID of the environment, in this case we set it to 4 which is Rinkeby.

Fund Your Account

We’re almost ready to deploy! However we need to make sure we have enough funds in our account to complete the transaction. We can fund our Rinkeby test account using the Rinkeby ETH faucet:

To request ETH from the faucet we need to complete the following steps:

  1. Post publicly our Ethereum deployment address from one of the following social network accounts: Twitter, Google+or Facebook, in this example we’ll be using Twitter

  2. Copy the link to the social media post

  1. Paste the link into the Rinkeby ETH faucet and select the amount of ETH to be sent

  1. Check the Rinkeby etherscan for the status of the transaction

    https://rinkeby.etherscan.io/address/ ETHEREUM DEPLOYMENT ADDRESS>

Deploy

To deploy simply run the truffle migrate command whilst specifying the network to deploy to. The networks are defined in the truffle.js configuration file we configured earlier in this article:

$ truffle migrate --network rinkeby

Using network 'rinkeby'.
Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x2e62ab8ab4537304a2621b9917710ffcc84752cdcb7d135f577ce76821a35c64
  Migrations: 0x518766aecf468511fd6fddf30561d99e38560fea
Saving successful migration to network...
  ... 0x23c6d11a1c826129dbb70a80785d8d24e77fb57c3c04dc4e86a03556c88b57bd
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Bounties...
  ... 0xe53c1ea77c369077647a45d118948a33f72111078221fdb754696505630dbfc1
  Bounties: 0xb7d93273e0149e6a299753a0f9318fb15ce98c6f
Saving successful migration to network...
  ... 0x52421230c72c17ff26f08d7fe16494121a621228190ad042f7488562490a5a55
Saving artifacts...

And that's it! We have now finally deployed our Bounties.sol contract to the public testnet environment Rinkeby.

Later in the series, we’ll discuss how to write tests within the Truffle framework, and how we can also add a frontend to our dApp so users can interact with our smart contract on the public network!