Skip to content

Commit

Permalink
Create a simple ICO contract #39
Browse files Browse the repository at this point in the history
  • Loading branch information
RostyslavBortman committed Jul 19, 2018
1 parent 582ca3a commit 2015102
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 28 deletions.
186 changes: 186 additions & 0 deletions contracts/ICO/SimpleICO.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
pragma solidity ^0.4.23;

import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "zeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
import "zeppelin-solidity/contracts/math/SafeMath.sol";


contract SimpleICO {
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
*/
constructor(uint256 _rate, address _wallet, address _tokenAddress) public {
require(_rate > 0);
require(_wallet != address(0));

rate = _rate;
wallet = _wallet;
token = ERC20(_tokenAddress);
}

// -----------------------------------------
// Crowdsale external interface
// -----------------------------------------

/**
* @dev fallback function ***DO NOT OVERRIDE***
*/
function () external payable {
buyTokens(msg.sender);
}

/**
* @dev low level token purchase ***DO NOT OVERRIDE***
* @param _beneficiary Address performing the token purchase
*/
function buyTokens(address _beneficiary) public payable {

uint256 weiAmount = msg.value;
_preValidatePurchase(_beneficiary, weiAmount);

// calculate token amount to be created
uint256 tokens = _getTokenAmount(weiAmount);

// update state
weiRaised = weiRaised.add(weiAmount);

_processPurchase(_beneficiary, tokens);
emit TokenPurchase(
msg.sender,
_beneficiary,
weiAmount,
tokens
);

_updatePurchasingState(_beneficiary, weiAmount);

_forwardFunds();
_postValidatePurchase(_beneficiary, weiAmount);
}

// -----------------------------------------
// Internal interface (extensible)
// -----------------------------------------

/**
* @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met. Use super to concatenate validations.
* @param _beneficiary Address performing the token purchase
* @param _weiAmount Value in wei involved in the purchase
*/
function _preValidatePurchase(
address _beneficiary,
uint256 _weiAmount
)
internal
{
require(_beneficiary != address(0));
require(_weiAmount != 0);
}

/**
* @dev Validation of an executed purchase. Observe state and use revert statements to undo rollback when valid conditions are not met.
* @param _beneficiary Address performing the token purchase
* @param _weiAmount Value in wei involved in the purchase
*/
function _postValidatePurchase(
address _beneficiary,
uint256 _weiAmount
)
internal
{
// optional override
}

/**
* @dev Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends its tokens.
* @param _beneficiary Address performing the token purchase
* @param _tokenAmount Number of tokens to be emitted
*/
function _deliverTokens(
address _beneficiary,
uint256 _tokenAmount
)
internal
{
token.safeTransfer(_beneficiary, _tokenAmount);
}

/**
* @dev Executed when a purchase has been validated and is ready to be executed. Not necessarily emits/sends tokens.
* @param _beneficiary Address receiving the tokens
* @param _tokenAmount Number of tokens to be purchased
*/
function _processPurchase(
address _beneficiary,
uint256 _tokenAmount
)
internal
{
_deliverTokens(_beneficiary, _tokenAmount);
}

/**
* @dev Override for extensions that require an internal state to check for validity (current user contributions, etc.)
* @param _beneficiary Address receiving the tokens
* @param _weiAmount Value in wei involved in the purchase
*/
function _updatePurchasingState(
address _beneficiary,
uint256 _weiAmount
)
internal
{
// optional override
}

/**
* @dev Override to extend the way in which ether is converted to tokens.
* @param _weiAmount Value in wei to be converted into tokens
* @return Number of tokens that can be purchased with the specified _weiAmount
*/
function _getTokenAmount(uint256 _weiAmount)
internal view returns (uint256)
{
return _weiAmount.mul(rate);
}

/**
* @dev Determines how ETH is stored/forwarded on purchases.
*/
function _forwardFunds() internal {
wallet.transfer(msg.value);
}
}
8 changes: 5 additions & 3 deletions contracts/ICO/SimpleICOWithOwnTokens.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pragma solidity ^0.4.23;

import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "zeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
import "zeppelin-solidity/contracts/math/SafeMath.sol";


contract Token is StandardToken {
Expand All @@ -14,11 +16,11 @@ contract Token is StandardToken {
symbol = _symbol;
decimals = _decimals;
totalSupply_ = _totalSupply;
balances[msg.sender] = totalSupply_;
balances[msg.sender] = _totalSupply;
}
}

contract Crowdsale {
contract SimpleICOWithOwnTokens {
using SafeMath for uint256;
using SafeERC20 for ERC20;

Expand Down Expand Up @@ -148,7 +150,7 @@ contract Crowdsale {
)
internal
{
token.transfer(_beneficiary, _tokenAmount);
token.safeTransfer(_beneficiary, _tokenAmount);
}

/**
Expand Down
85 changes: 85 additions & 0 deletions test/crowdsale.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const ether = require('./utils/ether');

const pify = require('pify');
const ethAsync = pify(web3.eth);
const ethGetBalance = ethAsync.getBalance;

const BigNumber = web3.BigNumber;

const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should();

const Crowdsale = artifacts.require('SimpleICO');
const StdDaoToken = artifacts.require('StdDaoToken');

contract('SimpleICO', function ([_, investor, wallet, purchaser]) {
const rate = new BigNumber(1);
const value = ether(5);
const tokenSupply = new BigNumber('1e22');
const expectedTokenAmount = rate.mul(value);

beforeEach(async function () {
this.token = await StdDaoToken.new("StdToken","STDT",18, true, false, tokenSupply);
this.crowdsale = await Crowdsale.new(rate, wallet, this.token.address);
await this.token.mintFor(this.crowdsale.address, tokenSupply);
});

describe('accepting payments', function () {
it('should accept payments', async function () {
await this.crowdsale.send(value);
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
});
});

describe('high-level purchase', function () {
it('should log purchase', async function () {
const { logs } = await this.crowdsale.sendTransaction({ value: value, from: investor });
const event = logs.find(e => e.event === 'TokenPurchase');
should.exist(event);
event.args.purchaser.should.equal(investor);
event.args.beneficiary.should.equal(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
});

it('should assign tokens to sender', async function () {
await this.crowdsale.sendTransaction({ value: value, from: investor });
let balance = await this.token.balanceOf(investor);
balance.should.be.bignumber.equal(expectedTokenAmount);
});

it('should forward funds to wallet', async function () {
const pre = await ethGetBalance(wallet);
await this.crowdsale.sendTransaction({ value, from: investor });
const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value);
});
});

describe('low-level purchase', function () {
it('should log purchase', async function () {
const { logs } = await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
const event = logs.find(e => e.event === 'TokenPurchase');
should.exist(event);
event.args.purchaser.should.equal(purchaser);
event.args.beneficiary.should.equal(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
});

it('should assign tokens to beneficiary', async function () {
await this.crowdsale.buyTokens(investor, { value, from: purchaser });
const balance = await this.token.balanceOf(investor);
balance.should.be.bignumber.equal(expectedTokenAmount);
});

it('should forward funds to wallet', async function () {
const pre = await ethGetBalance(wallet);
await this.crowdsale.buyTokens(investor, { value, from: purchaser });
const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value);
});
});
});
28 changes: 17 additions & 11 deletions test/CrowdsaleTest.js → test/crowdsale_with_own_token.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
var ether = require('./utils/ether');
var ethGetBalance = require('./utils/web3');
var abi = require('./utils/TokenABI');

const pify = require('pify');
const ethAsync = pify(web3.eth);
const ethGetBalance = ethAsync.getBalance;

const BigNumber = web3.BigNumber;

Expand All @@ -8,23 +12,25 @@ const should = require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();

const Crowdsale = artifacts.require('Crowdsale');
const Crowdsale = artifacts.require('SimpleICOWithOwnTokens');


contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
contract('SimpleICOWithOwnTokens', function ([_, investor, wallet, purchaser]) {
const rate = new BigNumber(1);
const value = ether(42);
const value = ether(5);
const tokenSupply = new BigNumber('1e22');
const expectedTokenAmount = rate.mul(value);

beforeEach(async function () {
this.crowdsale = await Crowdsale.new(rate, wallet, "Test", "tst", 18, 10000000000);
this.crowdsale = await Crowdsale.new(rate, wallet, "Test", "tst", 18, 10e25);
let address = await this.crowdsale.token();
this.token = await web3.eth.contract(abi).at(address);
});

describe('accepting payments', function () {
it('should accept payments', async function () {
await this.crowdsale.send(value).should.be.fulfilled;
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser }).should.be.fulfilled;
await this.crowdsale.send(value);
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
});
});

Expand All @@ -46,9 +52,9 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
});

it('should forward funds to wallet', async function () {
const pre = await ethGetBalance.ethGetBalance(wallet);
const pre = await ethGetBalance(wallet);
await this.crowdsale.sendTransaction({ value, from: investor });
const post = await ethGetBalance.ethGetBalance(wallet);
const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value);
});
});
Expand All @@ -71,9 +77,9 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
});

it('should forward funds to wallet', async function () {
const pre = await ethGetBalance.ethGetBalance(wallet);
const pre = await ethGetBalance(wallet);
await this.crowdsale.buyTokens(investor, { value, from: purchaser });
const post = await ethGetBalance.ethGetBalance(wallet);
const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value);
});
});
Expand Down
Loading

0 comments on commit 2015102

Please sign in to comment.