Simple ERC20 Crowdsale

The following is an example of a simple ERC20 Crowdsale.

:warning: The following code has not been tested nor audited.
Seek appropriate advice on regulatory compliance and your solution should have appropriate testing and auditing

Recommend reading: Points to consider when creating a fungible token (ERC20, ERC777)

Setup

:warning: Crowdsales are included in OpenZeppelin Contracts 2.x, they are not in OpenZeppelin Contracts 3.x.

To install:

npm install @openzeppelin/contracts@2.5.1

Documentation: https://docs.openzeppelin.com/contracts/2.x/crowdsales
Documentation on why Crowdsales are not included in OpenZeppelin Contracts 3.x: https://docs.openzeppelin.com/contracts/2.x/crowdsales

SimpleToken.sol

// contracts/SimpleToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.5;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";

/**
 * @title SimpleToken
 * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `ERC20` functions.
 */
contract SimpleToken is Context, ERC20, ERC20Detailed {
    /**
     * @dev Constructor that gives _msgSender() all of existing tokens.
     */
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) public ERC20Detailed(name, symbol, 18) {
        _mint(_msgSender(), initialSupply);
    }
}

SimpleCrowdsale.sol

// contracts/SimpleCrowdsale.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.5;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";

/**
 * @title SimpleCrowdsale
 * @dev This is an example of a fully fledged crowdsale.
 */
contract SimpleCrowdsale is Crowdsale {
    constructor(
        uint256 rate,
        address payable wallet,
        IERC20 token
    ) public Crowdsale(rate, wallet, token) {}
}

2_deploy.js

Migrations script that deploys the token and crowdsale, then transfers the total supply of tokens to the crowdsale.

_From: https://docs.openzeppelin.com/contracts/2.x/crowdsales#token-emission_
In the default scenario, your crowdsale must own the tokens that are sold.

// migrations/2_deploy.js
// SPDX-License-Identifier: MIT
const SimpleToken = artifacts.require("SimpleToken");
const SimpleCrowdsale = artifacts.require("SimpleCrowdsale");

module.exports = async function (deployer, network, accounts) {
  await deployer.deploy(SimpleToken, 'Simple Token', 'SIM', '10000000000000000000000');
  const token = await SimpleToken.deployed();
  
  await deployer.deploy(SimpleCrowdsale, 1, accounts[0], token.address);
  const crowdsale = await SimpleCrowdsale.deployed();

  token.transfer(crowdsale.address, await token.totalSupply())
};

Truffle develop

Interact with the crowdsale using the Truffle console

$ npx truffle develop
Truffle Develop started at http://127.0.0.1:9545/
...
truffle(develop)> migrate

Compiling your contracts...
===========================
...
Starting migrations...
======================
> Network name:    'develop'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
...
2_deploy.js
===========

   Deploying 'SimpleToken'
...
   Deploying 'SimpleCrowdsale'
...
truffle(develop)> token = await SimpleToken.deployed()
undefined
truffle(develop)> crowdsale = await SimpleCrowdsale.deployed()
undefined
truffle(develop)> (await token.balanceOf(crowdsale.address)).toString()
'10000000000000000000000'
truffle(develop)> (await token.totalSupply()).toString()
'10000000000000000000000'
truffle(develop)> await crowdsale.buyTokens(accounts[1], {value: web3.utils.toWei('1')})
{ tx:
...
truffle(develop)> (await token.balanceOf(accounts[1])).toString()
'1000000000000000000'
truffle(develop)> (await token.balanceOf(crowdsale.address)).toString()
'9999000000000000000000'

Testing

Based on: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/test/examples/SampleCrowdsale.test.js

SimpleToken.test.js

// test/SimpleToken.test.js
// SPDX-License-Identifier: MIT

// Based on https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.5.1/test/examples/SimpleToken.test.js

const { expect } = require('chai');

// Import utilities from Test Helpers
const { BN, expectEvent, expectRevert, constants } = require('@openzeppelin/test-helpers');

// Load compiled artifacts
const SimpleToken = artifacts.require('SimpleToken');

// Start test block
contract('SimpleToken', function ([ creator, other ]) {

  const NAME = 'SimpleToken';
  const SYMBOL = 'SIM';
  const TOTAL_SUPPLY = new BN('10000000000000000000000');

  beforeEach(async function () {
    this.token = await SimpleToken.new(NAME, SYMBOL, TOTAL_SUPPLY, { from: creator });
  });

  it('has a total supply', async function () {
    // Use large integer comparisons
    expect(await this.token.totalSupply()).to.be.bignumber.equal(TOTAL_SUPPLY);
  });

  it('has a name', async function () {
    expect(await this.token.name()).to.be.equal(NAME);
  });

  it('has a symbol', async function () {
    expect(await this.token.symbol()).to.be.equal(SYMBOL);
  });

  it('assigns the initial total supply to the creator', async function () {
    expect(await this.token.balanceOf(creator)).to.be.bignumber.equal(TOTAL_SUPPLY);
  });
});

SimpleCrowdsale.test.js

// test/SimpleCrowdsale.test.js
// SPDX-License-Identifier: MIT

// Based on https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.5.1/test/examples/SimpleToken.test.js

const { expect } = require('chai');

// Import utilities from Test Helpers
const { BN, ether, expectEvent, expectRevert, constants } = require('@openzeppelin/test-helpers');

// Load compiled artifacts
const SimpleToken = artifacts.require('SimpleToken');
const SimpleCrowdsale = artifacts.require('SimpleCrowdsale');

// Start test block
contract('SimpleCrowdsale', function ([ creator, investor, wallet ]) {

  const NAME = 'SimpleToken';
  const SYMBOL = 'SIM';
  const TOTAL_SUPPLY = new BN('10000000000000000000000');
  const RATE = new BN(10);

  beforeEach(async function () {
    this.token = await SimpleToken.new(NAME, SYMBOL, TOTAL_SUPPLY, { from: creator });
    this.crowdsale = await SimpleCrowdsale.new(RATE, wallet, this.token.address);
    this.token.transfer(this.crowdsale.address, await this.token.totalSupply());
  });

  it('should create crowdsale with correct parameters', async function () {
    expect(await this.crowdsale.rate()).to.be.bignumber.equal(RATE);
    expect(await this.crowdsale.wallet()).to.be.equal(wallet);
    expect(await this.crowdsale.token()).to.be.equal(this.token.address);
  });

  it('should accept payments', async function () {
    const investmentAmount = ether('1');
    const expectedTokenAmount = RATE.mul(investmentAmount);

    await this.crowdsale.buyTokens(investor, { value: investmentAmount, from: investor });

    expect(await this.token.balanceOf(investor)).to.be.bignumber.equal(expectedTokenAmount);
  });
});

Run Tests

$ npx truffle test
Using network 'test'.


Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



  Contract: SimpleCrowdsale
    ✓ should create crowdsale with correct parameters (206ms)
    ✓ should accept payments (154ms)

  Contract: SimpleToken
    ✓ has a total supply
    ✓ has a name (44ms)
    ✓ has a symbol (52ms)
    ✓ assigns the initial total supply to the creator


  6 passing (2s)
5 Likes
Create token sale 1:1 ETH for token
Simple ERC20 token example
How ERC20 approve function works?
Simple tests for Crowdsale contract?
ERC20 Token - is it possible to transfer part of tokens to a smart contract?
Buy direct with Crowdsale without using the buyTokens function
Interacting directly with an ERC20 token's contract for trading
Custom Crowdsale purchase fails
Crowdsale.sol Operator != not compatible with types contract ERC20
Use other addresses in OpenZeppelin Test Environment tests
CrowdSale Contract for Need Help
Trying to Create the Crowdsale Contract
Crowdsale buyTokens fails with error 'SafeERC20: low-level call failed'
Implement ERC20 token with burn and mint functions?
Send token when receiving BNB
Crown sale bep20 token
Creating ERC20 Error
Crowdsale?
Crowdsale contract with token already deployed
Contract Crowdsale SafeMath: subtraction overflow", "data" error
What is code coverage?
Yarn add OpenZeppelin Contracts 2.5.1
Crowdsale: DeclarationError: Identifier not found or not unique. ERC20 _token, ^---^
Fail with error 'SafeERC20: low-level call failed' (only with other decimals than 18)
Testing buyToken in crowdsale and investor token balanceOf returns 0
Recommended way to split ERC20 initial token supply
How to build a buyable ERC777?
Crowdsale send BNB fail
Crowdsale contract shows error in SafeERC20
Help with bep20 crowdsale token
Help wanted: Implement crowdsale to sell 1 TOKEN for 1 ETH
Help me write an erc20 token and a crowdsale contract
Help me write an erc20 token and a crowdsale contract
Crowdsale deployed to ganache doesn't send token
Revert SafeERC20: low-level call failed when trying to run buyTokens function in a Crowdsale
How to combine two ERC20 contracts
Compiling ERC20: Warning: The "extcodehash" instruction is not supported by the VM version "byzantium"
Crowdsale buyTokens fails with error 'SafeERC20: low-level call failed'
Where are crowdsales?
'safeTransfer' in Crowdsale errors but works with 'transfer'
Getting a "revert" processing transaction on "transfer" call
Creating ERC20 Error
ERC20 initial contract and crowdsale test check

3 posts were split to a new topic: Use other addresses in tests

A post was split to a new topic: Import OpenZeppelin contracts when inherited contract uses a higher compiler version?

2 posts were split to a new topic: Where are crowdsales?

Hello @abcoathup or anyone who can help,

I've been trying to edit your 2_deploy.js to allow me to run a crowdsale on a previously created token. I stripped out the SimpleToken and tried to hardcode the existing contract address in. Something like so:

const CROWDSALE2 = artifacts.require("CROWDSALE2");

module.exports = async function (deployer, network, accounts) {

    const token = 'TOKEN_CONTRACT_ADDRESS';

    await deployer.deploy(CROWDSALE2, 1, accounts[0], token.address);
    const crowdsale = await CROWDSALE2.deployed();
  
    token.transfer(crowdsale.address, await token.totalSupply())
};

Is this even possible to create a crowdsale on a previously deployed token?

The error I get when deploying is:

 *** Deployment Failed ***

"CROWDSALE2" -- invalid address (argument="address", value=undefined, code=INVALID_ARGUMENT, version=address/5.0.5) (argument="token", value=undefined, code=INVALID_ARGUMENT, version=abi/5.0.7).

Help would be much appreciated.

Best regards,

I just stumbled again over this crowdsale contract and wondering again why it was abandoned in v3.

" All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the v3.0.0 release due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6."

If OZ finds it too complex to migrate it to solc v0.6, should I touch and try that ?
Any information what would have been so "complex" ?

1 Like