The following is an example of a simple ERC20 Crowdsale.
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
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
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)