Ethereum Contracts with Truffle Framework

Recently, I have been looking for tutorials on how to create a simple Ethereum Smart Contract. As it turned out, most of blog posts and articles I found are quite outdated and have mistakes or issues related to the newer versions of the Ethereum tooling. At some point, I found myself constantly jumping between the stackexchange website, GitHub Issues and random tutorials to simply move forward and complete the originally picked up tutorial for voting.

This shows that the area of Blockchain is evolving very fast, so that existing tooling is constantly getting broken by newer versions.

In this blog post, we will implement a simple Ethereum contract using Truffle framework and Solidity. Then, we will deploy it to the test network Rinkeby. Hopefully, this tutorial will be much cleaner and last longer.

Smart Contract Subject

From our goal perspective, the main subject of the smart contract does not really matter. However, let’s choose something self-explanatory like a Bookmarks app. In our example contract, we will maintain a state with arrays of URLs and their user-defined names.

Create Dev Project

Let’s create a project directory and change into it:

mkdir Bookmarks
cd Bookmarks

We are going to use Truffle framework and its template to set up a project structure. First, we need NPM installed on our local environment. It can be installed through a package manager. For example, we can use brew on macOS:

brew install node

Now we need to install the Truffle framework:

npm install -g truffle

In my case, I installed Truffle version 5.0.3. Be aware that newer versions may have breaking changes, so that this tutorial may not work in the future.

The two commands above are not necessary to be executed inside the Bookmarks directory.

Now, let’s use Truffle to create a bare project with all required folders included. Run this command:

truffle init

If the command is executed successfully, then we should have the following file structure in Bookmarks directory:

> tree

├── README.md
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js

Congratulations, the first checkpoint is done!

Compiling the Contract

Our contract code looks like this:

pragma solidity >= 0.5.0;

contract Bookmarks {
    address public owner;
    Entry[100] public entries;
    uint count;

    struct Entry {
        string url;
        string name;
    }

    constructor() public {
        owner = msg.sender;
    }

    modifier restricted() {
        require(
            msg.sender == owner,
            "Only owner can call this."
        );
        _;
    }

    function getBookmarksCount() public view restricted returns (uint) {
        return count;
    }

    function getBookmarkAt(uint index) public view restricted returns (string memory, string memory) {
        return (entries[index].url, entries[index].name);
    }

    function addBookmark(string memory _url, string memory _name) restricted public {
        entries[count] = Entry(_url, _name);
        count++;
    }

    function setBookmark(uint index, string memory _url, string memory _name) restricted public {
        require(index < count, "index must exist");
        entries[index] = Entry(_url, _name);
    }

    function removeBookmark(uint index) restricted public {
        require(index < count, "index must exist");
        entries[index] = Entry("", "");
        count--;
    }
}

Save it to the contract folder with the name Bookmarks.sol.

We are going to use Truffle to compile above contract using the command:

truffle compile

It should print something like this as output:

Compiling ./contracts/Bookmarks.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

One more checkpoint achieved.

Deploying the Contract

The deployment process in Truffle is called migration. It consists of a folder with scripted tasks in JavaScript. As we ran truffle init earlier, we got a folder, called migrations. It contains a default script to deploy Migrations.sol, which is an auxiliary contract to maintain the state of the latest migration number on the Blockchain. If you’ve ever been working with a database, this might be familiar. Migration allows to evolve existing applications and deploy new version of a contract. It keeps track, which scripts are not yet applied. We will get back to it again later.

The Truffle init command has already prepared a script for a migration contract. However, we need to add our own contract. This can be done as a separate script.

Let’s create a file at migrations/2_deploy_contracts.js:

var Bookmarks = artifacts.require("./Bookmarks.sol");
module.exports = function(deployer) {
  deployer.deploy(Bookmarks);
};

In order to deploy our contract, run:

truffle migrate --network my_local

Oops … we got an error!

Compiling ./contracts/Bookmarks.sol...
Writing artifacts to ./build/contracts


Could not connect to your Ethereum client with the following parameters:
    - host       > 127.0.0.1
    - port       > 7545
    - network_id > 5777
Please check that your Ethereum client:
    - is running
    - is accepting RPC connections (i.e., "--rpc" option is used in geth)
    - is accessible over the network
    - is properly configured in your Truffle configuration file (truffle-config.js)

Truffle v5.0.3 (core: 5.0.3)
Node v11.9.0

You might have noticed that we didn’t mention ‘where’ we are going to deploy the contract. The Ethereum blockchain has several networks to choose from in order to deploy a smart contract. Truffle’s output above also gives a tip on what needs to be specified to make the migration/deployment successful.

As part of this tutorial, we want to deploy our Bookmarks contract to the Ethereum test network Rinkeby, using Truffle framework. To make this happen, we have several options to choose from:

  1. Ganache
    Ganache is one more tool from the Truffle’s suite. It is a personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests. It comes with test accounts and fake Ether. Since our goal is to deploy a contract to the real test network, we won’t go with this option.

  2. Local Ethereum client: Geth or other
    Geth (Go Ethereum) is one of the solutions to connect to the main and test Ethereum network. Geth can be run as an Ethereum full node, which allows to mine new blocks.

  3. Infura
    Infura is an API platform, which exposes JSON-RPC API to connect to the Ethereum network. Basically, Infura, or a similar platform, is providing an infrastructure, so that one does not need to maintain a local node on its own.

We will go with Geth Local node to be connected to the Ethereum network directly. However, this would require us to download the whole blockchain from the very first block up to the current block.

Setting up Geth

Install Geth using the installation instructions based on your OS.

Start Geth with the HTTP RPC server enabled; this will be used by Truffle.

geth --rinkeby --syncmode "fast" --rpc --rpcapi="db,eth,net,web3,personal" --cache=1024  --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*" --port "35555"

Once it is started, you should see block synchronization messages. Above command starts Geth in fast mode. This mode makes synchronization much faster than running a full synchronization. However, this will still take some time. In my case (2,2 GHz Intel Core i7, SSD disk), it took less than an hour to sync with entire blockchain.

Connecting Truffle to Geth

Add below configuration to the Bookmarks/truffle-config.js file inside the networks section.

my_local: {
  host: "127.0.0.1",     
  port: 8545,            
  network_id: "4", // Rinkeby's id          
  from: "..." // your Ethereum account address here, we will get back to this soon
},
...

Above configuration points to localhost and port 8545; the same port we used to run Geth earlier.

An important configuration is network, which in our case points to 4 Rinkeby, the public Geth PoA testnet

The property from needs to be set to your account address, which will be used to make transaction in the Ethereum network. That means this account needs to have some amount of Ether to deploy the contract and later interact with it. In case you already have a test Rinkeby account, just put it in truffle-config.js networks.my_local.from instead of “…”. Otherwise, follow the two sections below to create an account and request to test Ether.

Creating an Ethereum account (Optional)

In case we do not have an account yet, we can use the Geth console to create one.

Let the already existing Geth process run. Start a new terminal window and attach to existing Geth process via:

geth attach http://localhost:8545

In the Geth console, we can use the JavaScript API to create and unlock a new account:

personal.newAccount("put_your_secret_passphrase_here")

It will output the new account address, in my case it was: “0x560ae6e63f0e79c9027a590900291399cc954f00”. In your case, it will be a different account.

Now the account needs to be unlocked before it can be used to make a transaction. In the same Geth console where we created an account, run below command:

personal.unlockAccount("your_new_address_here", "put_your_secret_passphrase_here", 600)

Do not forget to put the correct account number, which was given by the first command. Also, replace the pass phrase, which you have used to create an account.

600 is number of seconds for how long this account needs to be unlocked. It means, that after 600 seconds an account will be locked again. Make sure to run the next steps, which requires an unlocked account, within this duration. Otherwise unlock it again, when needed.

Request free Ether (Optional)

Since we have created new account, we need to have some amount of Ether to deploy a contract and interact with it. Ethereum network provides, so called faucets to ask for fake Ether for testing purposes. Go to https://faucet.rinkeby.io and request an Ether using the short instruction given there. Fake Ether will be transferred to your account within several seconds, once you post the required HTTP link to a social media account.

Deploy with Truffle

Finally, we can run the migration scripts to deploy our Bookmarks contract:

truffle migrate --network my_local

Truffle will print a detailed output of what is actually being executed. For each deployment script from the migrations folder, we should see the following:

  • transaction hash
  • contract address
  • amount of gas used and its price
  • total cost in ETH

We can also see contract creation transactions on https://rinkeby.etherscan.io

test_transactions

More on the Migration

The last file index will be saved on the chain in the Migrations contract. We can see its address in the Truffle logs above and then call a getter function for the last_completed_migration property. Right after the initial deployment, this function should reply 2, because the last file in a sequence of the migrations folder is 2_deploy_contracts.js. This approach prevents us to run the same scripts again, if we launch the above migration command. In case we need to deploy a new contract version, we need to create a new script with a file prefix = last_completed_migration + 1. For example, a file with name migrations/3_deploy_contracts.js would be the next version of our application. Of course, we can copy paste the previous file. The Truffle migration API has a lot of features to control the deployment process, so that we can script our process in any way we need.

If you have trouble understanding which contract address is the Migrations contract, and which one is the main application contract after running several migration per day, you can decompile contract byte code, even online.

Congratulations, the hardest part is completed!

Call with Truffle

There are a couple of options we can choose from to call the deployed contract. In this tutorial, we will do that programmatically using Truffle console. One could also work with a contract via IDE like Remix.

Let’s run the Truffle console and call the deployed contract in the Rinkeby test network:

truffle console --network my_local
// inside console

// change below contract address to the one you received after the deployment
> let bookmarksInst = await Bookmarks.at("0x6ae5C0950800A86CEf2Be9dd67d778e69cD2BAd7") 

> let count = await bookmarksInst.getBookmarksCount()
> count.toNumber()
0

We get 0 back as expected, when we call getBookmarksCount on empty an Bookmarks contract. Let’s add one bookmark and try to read it.

// inside the same console
> bookmarksInst.addBookmark("https://remix.ethereum.org", "Solidity IDE")

> count = await bookmarksInst.getBookmarksCount()
> count.toNumber()
1

> bookmarksInst.getBookmarkAt(0)
Result { '0': 'https://remix.ethereum.org', '1': 'Solidity IDE' }

If the addBookmark transaction is completed successfully, then we should get 1 back.

Summary

We have developed a new contract from scratch and deployed it to the test network Rinkeby. Deployment to the main network will be similar, but we will be using real Ether (i.e. real money) there to create a contract and make transactions. Our example contract can be improved a lot in terms of Gas usage efficiency, so that the contract creation and state modification function call would be cheaper. One of the ideas is to simply make our internal array a dynamic one, so that we do not need to create a fixed size array in advance. We could also tackle struct types to be more space efficient.

Using Truffle framework, we simplified our project creation process, compilation and deployment. Hopefully, we will see more useful and stable development tools for Ethereum blockchain in future.

TAGS

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen