A Companion Guide to “Real World Crowdsale Contracts”

1. Introduction

I am doing a survey on token and crowdsale contracts, and found this great article The 2018 guide to writing (and testing) real world crowdsale contracts by Sandeep Panda (January 2018). By using OpenZeppelin library and Truffle development framework, Sandeep built two self-explanatory Solidity codes showing how to build and test token and crowdsale contracts.

In his work the test script was done on Truffle automatically. As usual I am interested to dig out more how things really work under the hood. With help of geth (the Go Ethereum client) I can access the detail in contracts and how things are interacting one another. Here I repeat the test script in Sandeep’s article and do some exploration on each item, and how they are executed per predefined contracts.

I hope this can serve as a companion guide to make Sandeep’s work even greater.

2. A Note on the Original Work

When I first tried Sandeep’s setup, I encountered some hiccups. It is not due to Sandeep’s original work, but the ever changing Ethereum world we are in. I just put up some note in case you may have encountered similar matters.

Keep the code version on Zeppelin-Solidity 1.5.0. I have tried the latest version (which is 1.8.0 if I do not specify anything). Both the contract and the directory are enhanced and reorganized. Sandeep’s original setup works perfectly with 1.5.0. Therefore, instead of everything reworked with the latest zeppelin library, I would suggest keep using 1.5.0 and enjoy what Sandeep has prepared for us.

During truffle compile, there are quite many warning messages. It is due to change in event emission and balance handling on Solidity code. Since this does not affect the result of compilation, we simply ignore them.

3. Setup

3.1 Screen Setup

Everything I showed here are on command line, and my screen is splitted into three part.

  • command line for truffle command: we simply run truffle compile, migrate and test here
  • ganache-cli: we run the in-memory ethereum environment for our testing and demonstration
  • geth command line: it is the main terminal we will interact with contract

3.2 Run Testing Ethereum Network ganache-cli

In any contract deployment we need an ethereum network. Sandeep used the ganache which provides a graphic user interface. Here I am using ganache-cli, the command line version of ganache. Note that I use mnemonic. With mnemonic, ganache-cli can always give me back the same set of ten accounts. This is in particularly useful for testing.

Also note that the RPC port for this ethereum network (localhost:8545). We will use this when accessing it from geth console.

Meanwhile, ganache-cli is almost the same as TestRPC. I also use TestRPC and everything works well.

3.3 Truffle Compile and Migrate

We run truffle compile and truffle migrate as suggested in Sandeep’s work. The code should be successfully compiled and migrated. In particular we need the contract address of the deployed contract.

Note that, according to the design, only the HashnodeCrowdsale.sol (crowdsale) contract is deployed from Truffle. The HashnodeToken.sol (token) contract is deployed within crowdsale contract. Therefore we only see the deployed address for crowdsale contract in truffle migrate, and we will locate the token contract later through crowdsale contract.

3.4 Prepare Geth Console

We finally work with Geth console. We first launch geth console accessing the ganache-cli ethereum network through the rpc port (i.e. localhost:8545):

In order to let geth console access and interact with the deployed contracts, two pieces of important information are needed:

  • contract deployed address, for both crowdsale and token contracts
  • contract application binary interface (ABI)

We have the deployed contract addresses of crowdsale contract already (see previous part), and the address of token contract is obtained later through crowdsale contract.

The ABI of both contracts can be found in build/contracts directory, which is created and updated after truffle compile command.

Last, while we can issue various geth commands to inspect everything, I have come up a tiny function that can show useful information during the demonstration. Here is the function checkEverything():

function checkEverything() {
var i =0;
var state = crowdsale.stage();
if (state == 0) {console.log("Stage: Pre-ICO\n")} else {console.log("Stage: ICO\n")};
web3.eth.accounts.forEach( function(e){
console.log("  eth.accounts["+i+"]: " + " \t token balance: " + token.balanceOf(e) / 1000000000000000000 + " \tbalance: " + web3.fromWei(eth.getBalance(e), "ether") + " ether");
console.log("\n  vault balance: " + web3.fromWei(eth.getBalance(crowdsale.vault()), "ether") + " ether\n");

4. Configure Geth Console

Now we are ready to configure the geth console such that we can interact with the contracts.

The format of command is quite straightforward.

> object = eth.account(<abi>).at(<deployed_contract_address>)

Here the crowdsale is the object we can access the deployed contract in truffle migration. Note that address is taken from the output of truffle migrate (see section 3.3).

If we take a look in both Crowdsale.sol and HashnodeCrowdsale.sol, we see that a token object token (line 49 in Crowdsale.sol) is created in Crowdsale constructor, which is a new HashnodeToken contract to be deployed (line 46 in HashnodeCrowdsale.sol).

Crowdsale.sol (excerpt)
HashnodeCrowdsale.sol (excerpt)

So after crowdsale contract is deployed, we can access token object inside crowdsale object and get the address of this deployed token contract.

We now can create another object token with the token contract ABI and this deployed contract.

To cross check if the token object is successfully deployed, we can check some functions defined in HashnodeToken.sol, such as,

Finally we place our checkEverything() function on our geth console and test how it works.

This function lets us know the stage of ICO (either PreICO or ICO), the token and ether balance of each predefined account. Also during ICO stage ethereum the ethers received is stored in Vault account and the balance is also shown here.

We keep calling this function to check the balance update.

5. Test Items

Sandeep has created a very comprehensive testing script using Truffle test framework. This is good for test standardization and automation, which is important in production-grade application development.

The nine items shown in TestCrowdsale.js file can be grouped into four steps.

  • Preparation (item 1): The Hashnode Token contract should be deployed after Hashnode Crowdsale contract is deployed.
  • Pre ICO Stage (item 2–5): During Pre ICO stage, one can purchase token per Pre-ICO rate, and the ether received is paid to Beneficiary account.
  • ICO Stage (item 6–8): During ICO stage, one can purchase token per ICO rate, and the ether is kept in vault account.
  • ICO Closing (item 9): When the ICO finishes, vault account is sent to Beneficiary account, and unminted and unsold tokens are distributed according to the predefined policy.

We will walk through the same set of test items, but instead of using it in truffle test, we perform them using geth console.

5.1 Preparation

We have done this already when we define token object (section 4). Once we defined crowdsale object, we can access crowdsale.token(). The return address (0x6ffb7963…) means the token contract successfully deployed.

5.2 Pre ICO Stage

First set the crowdsale stage. The default is already in stage 0 (i.e., Pre ICO). But note that this function requires owner to execute. Therefore, we need to specify the account (the owner is eth.accounts[0]).

Then we buy the tokens. In Crowdsale.sol it is a fallback function (a function of no function name) called for token buying (line 64 in Crowdsale.sol).

That means that if we send ethers directly to the crowdsale contract address, the ethers are used to buy token. And we may need to increase the gas limit as this transaction consumes more than default.

> eth.sendTransaction({from: <account>, to: <crowdsale address>, value: <in wei>, gas: <gas limit>})

Here we let eth.accounts[7] send 1 ether to crowdsale contract.

Some observation from the balance after sending this 1 ether to contract address.

  • eth.accounts[7] has spent slight more than 1 ether. That excess portion is used as gas to fund the transaction.
  • eth.accounts[7] in return gets 5 tokens. This meets the predefined Pre ICO rate (5 tokens / ether).
  • The Beneficiary account eth.accounts[9] (defined in the migration file) immediately get this 1 ether (now balance is 101 ethers).
  • This ether does not go to Vault account per design.

And we can check the total amount raised (in ethers) and total token minted so far on the Pre ICO stage. 1 ether is raised and total 5 tokens are minted.

5.3 ICO Stage

We first set the stage to ICO.

Now we buy token in ICO stage. We let eth.accounts[2] spend 1.5 ethers to purchase tokens. Note that the ICO rate is 2 tokens / ether per design.

Here is some observation.

  • 1.5 ether is deducted on eth.accounts[2] and now 3 tokens are deposited. This matches the ICO rate (2 tokens / ether).
  • This 1.5 ether does not go to beneficiary account (i.e. eth.accounts[9]). Instead per the contract design this goes to Vault account.

5.4 ICO Close

The crowdsale contract allows contract owner to stop ICO by executing finish() function (line 111 in HashnodeCrowdsale.sol).

Note that per design, when finish() is executed, contract owner needs to specify the accounts for team, ecosystem and bounty. Let us revisit the distribution plan (in HashnodeCrowdsale.sol).

It can be represented in the following chart.

And the predefined rule set is like this,

  • Total tokens to be minted is 100.
  • 40 tokens are reserved for specific purpose: 20 tokens for ecosystem, 10 for team, and 10 for bounty.
  • Sales for PreICO is up to 20 tokens.
  • Sales for both ICO and PreICO is up to 60 tokens.
  • Those unsold tokens go to eco-system.

Now we trigger the closing of ICO with finish(), and specify roles of the following accounts:

  • eth.accounts[0]: team
  • eth.accounts[1]: ecosystem
  • eth.accounts[2]: bounty

And here is the calculation for each number shown

  • 10 tokens go to team account. Therefore token balance of eth.accounts[0] is 10 tokens.
  • 10 tokens go to bounty account. As eth.accounts[2] has 3 tokens purchased, total token balance is now 13 tokens.
  • So far we have total 8 tokens sold (in both PreICO and ICO stages). Therefore the unsold / unminted tokens are 52 tokens.
  • 20 tokens go to ecosystem account. Beside, all unsold tokens go to ecosystem account as well. Therefore token balance of eth.accounts[1] is 72 tokens.
  • After closing, Vault balance goes to beneficiary accounts. Therefore Vault balance is 0.
  • The beneficiary account, eth.accounts[9] gets the 1.5 ethers from Vault, and the final ether balance is 102.5 ethers.

6 Summary

We have followed instruction from Sandeep’s work and deploy the crowdsale contract on ganache-cli ethereum environment. Instead of test the contract with truffle test framework, we implement the same testing procedure using geth console, and keep track how token amount and ether amount is changed in the tests.

Once again, thanks to Sandeep’s work and hope this small work can contribute a bit on the bigger picture.

Hope you enjoy this. Don’t forget to visit the Sandeep’s original work, and also give me some claps. You can reach me here.