Create token contract and time-limited Crowdsale contract with whitelisting in solidity

Tadej Oremuž
Coinmonks
Published in
6 min readJul 6, 2018

--

How to create whitelisted crowdsale with different rates by cap reached using open zeppelin. We will use open zeppelin templates and add / modify a few functions and contracts in order to achieve our goal.

In this article we will use following dependencies and tools:

  • node
  • truffle
  • zeppelin-solidity
  • metamask
  • oracles-combine-solidity

For installation of this dependencies and tools follow this: LINK

We will start with creating our contract directory and initializing our dependencies:

Now we will start creating our contracts

Create token contract

First, we will create a Token contract. In this example we will use MintableToken which allows us to create tokens when someone buys them / when we need them.

Open the editor (I’m using Atom) and create a file named exampleToken.sol

pragma solidity 0.4.23;import 'zeppelin-solidity/contracts/token/ERC20/MintableToken.sol';contract exampleToken is MintableToken {
string public name;
string public symbol;
uint8 public decimals;
constructor(string _name, string _symbol, uint8 _decimals)
MintableToken() public {
name = _name;
symbol = _symbol;
decimals = _decimals;
owner = msg.sender;
}
}

Create CrowdSale contract

For this example we will use MintedCrowdsale and TimedCrowdsale contract template from open-zeepelin.

With including this templates and using the word “is”, we will basically inherit the contract code at:

node_modules/zeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol and

node_modules/zeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol and

node_modules/zeppelin-solidity/contracts/crowdsale/validation/WhitelistedCrowdsale.sol

If we take a look at the MintedCrowdsale.sol (https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/crowdsale/emission/MintedCrowdsale.sol)

pragma solidity ^0.4.24;

import "../Crowdsale.sol";
import "../../token/ERC20/MintableToken.sol";


/**
* @title MintedCrowdsale
* @dev Extension of Crowdsale contract whose tokens are minted in each purchase.
* Token ownership should be transferred to MintedCrowdsale for minting.
*/
contract MintedCrowdsale is Crowdsale {

/**
* @dev Overrides delivery by minting tokens upon purchase.
* @param _beneficiary Token purchaser
* @param _tokenAmount Number of tokens to be minted
*/
function _deliverTokens(
address _beneficiary,
uint256 _tokenAmount
)
internal
{
require(MintableToken(token).mint(_beneficiary, _tokenAmount));
}
}

We can see that there is no constructor function included so we must take a look at inherited contract Crowdsale.sol to see which arguments are needed to initialise the contract(https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/crowdsale/Crowdsale.sol).

pragma solidity ^0.4.24;

import "../token/ERC20/ERC20.sol";
import "../math/SafeMath.sol";
import "../token/ERC20/SafeERC20.sol";


/**
* @title Crowdsale
* @dev Crowdsale is a base contract for managing a token crowdsale,
* allowing investors to purchase tokens with ether. This contract implements
* such functionality in its most fundamental form and can be extended to provide additional
* functionality and/or custom behavior.
* The external interface represents the basic interface for purchasing tokens, and conform
* the base architecture for crowdsales. They are *not* intended to be modified / overriden.
* The internal interface conforms the extensible and modifiable surface of crowdsales. Override
* the methods to add functionality. Consider using 'super' where appropiate to concatenate
* behavior.
*/
contract Crowdsale {
using SafeMath for uint256;
using SafeERC20 for ERC20;

// The token being sold
ERC20 public token;

// Address where funds are collected
address public wallet;

// How many token units a buyer gets per wei.
// The rate is the conversion between wei and the smallest and indivisible token unit.
// So, if you are using a rate of 1 with a DetailedERC20 token with 3 decimals called TOK
// 1 wei will give you 1 unit, or 0.001 TOK.
uint256 public rate;

// Amount of wei raised
uint256 public weiRaised;

/**
* Event for token purchase logging
* @param purchaser who paid for the tokens
* @param beneficiary who got the tokens
* @param value weis paid for purchase
* @param amount amount of tokens purchased
*/
event TokenPurchase(
address indexed purchaser,
address indexed beneficiary,
uint256 value,
uint256 amount
);

/**
* @param _rate Number of token units a buyer gets per wei
* @param _wallet Address where collected funds will be forwarded to
* @param _token Address of the token being sold
*/
constructor(uint256 _rate, address _wallet, ERC20 _token) public {
require(_rate > 0);
require(_wallet != address(0));
require(_token != address(0));

rate = _rate;
wallet = _wallet;
token = _token;
}
...

We can see that constructor function of contract Crowdsale requires following arguments:

  • uint256 _rate
  • address _wallet
  • ERC20 _token

Since we are also inheriting TimedCrowdsale.sol, we also must take a look at that contract to see which arguments are needed to initialise the contract(https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/crowdsale/validation/TimedCrowdsale.sol).

pragma solidity ^0.4.24;

import "../../math/SafeMath.sol";
import "../Crowdsale.sol";


/**
* @title TimedCrowdsale
* @dev Crowdsale accepting contributions only within a time frame.
*/
contract TimedCrowdsale is Crowdsale {
using SafeMath for uint256;

uint256 public openingTime;
uint256 public closingTime;

/**
* @dev Reverts if not in crowdsale time range.
*/
modifier onlyWhileOpen {
// solium-disable-next-line security/no-block-members
require(block.timestamp >= openingTime && block.timestamp <= closingTime);
_;
}

/**
* @dev Constructor, takes crowdsale opening and closing times.
* @param _openingTime Crowdsale opening time
* @param _closingTime Crowdsale closing time
*/
constructor(uint256 _openingTime, uint256 _closingTime) public {
// solium-disable-next-line security/no-block-members
require(_openingTime >= block.timestamp);
require(_closingTime >= _openingTime);

openingTime = _openingTime;
closingTime = _closingTime;
}

We can see that TimedCrowdsale takes following input arguments:

  • uint256 openingTime
  • uint256 closingTime

If we take a look at WhitelistedCrowdsale contract at(https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/validation/WhitelistedCrowdsale.sol), we can see that it takes no constructor arguments.

We now have all the information about which arguments we need to deploy our contract. I’ll list the arguments below for easier understanding:

uint256 _openingTime
uint256 _closingTime
uint256 _rate
address _wallet
MintableToken _token

Now we can create our Crowdsale contract:

import './exampleToken.sol';
import 'zeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol';
import 'zeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol';
import 'zeppelin-solidity/contracts/crowdsale/validation/WhitelistedCrowdsale.sol';
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract exampleCrowdsale is TimedCrowdsale, MintedCrowdsale, WhitelistedCrowdsale {function exampleCrowdsale
(
uint256 _openingTime,
uint256 _closingTime,
uint256 _rate,
address _wallet,
MintableToken _token
)
public
Crowdsale(_rate, _wallet, _token)
TimedCrowdsale(_openingTime, _closingTime)
WhitelistedCrowdsale() {
}
}

Compiling the code

We can now compile our code to see if we have any errors. We will use truffle compiler (we run this command inside root of our project dir):

$truffle compile

For now everything is ok.

If we want to deploy our contracts using for example remix IDE we need to combine all contracts. For this we will run “solidity-flattener”, to which we will pass the path of the exampleCrowdsale.sol as an argument.

$ npm start ../article/contracts/exampleCrowdsale.sol

Flat file will be generated inside “out” directory. If we copy and paste the code inside remix IDE we will get an error saying:

browser/ballot.sol:106:27: TypeError: Definition of base has to precede definition of derived contract
contract StandardToken is ERC20, BasicToken

This is only because “solidity-flattener” positioned contract ERC20 too low in the code, so we must just copy it an put it just under ERC20Basic contract. The error will be gone.

Ok we’re now ready to test our contracts.

In this tutorial, we will use Rinkeby testnet and remix IDE for deploying and communicating with our contracts.

In remix IDE we select tab run and select environment Injected Web3. IMPORTANT: You must have metamask installed and select Rinkeby testnet and also you must have some test ETH on this account.

If no accounts are available, just refresh the page.

First thing is to deploy exampleToken contract because its address is required for deploying exampleCrowdsale contract.

For deploying exampleToken contract we must provide 3 arguments:

After we press deploy, MetaMask will ask for approval and after we confirm it and transaction is mined we will see something like this:

Ok! We have our token contract published so all we need to do now is publish our Crowdsale contract.

For our exampleCrowdsale we must provide next arguments:

uint256 _openingTime, Timestamp in epoch format (online calculator: https://www.unixtimestamp.com/index.php) 
uint256 _closingTime,
uint256 _rate, //in this example is 1000 (1000 EXP for 1 ETH)
address _wallet, //address of the wallet that receives funds
MintableToken _token //address of token that we created earlier

For example input argument for deploying exampleCrowdsale could be like this:

"1530915909", "1540915909", "1000", "0x350be6a66145036dce4367e99b1f9f893ad4aa1a", "0xc693703cc36602cd5fc4d2bacfc42eb8ec866b44"

After exampleCrowdsale is deployed we now have to change owner of our token so exampleCrowdsale contract will have control over EXT token.

In remix we call function transferOwnership inside exampleToken contract:

That’s it!

Be aware that you whitelist addresses from which you want to invest. You can do that by calling function addToWhitelist(address investor)

If you have any questions, I’ll be happy to help.

--

--