Skip to content

Commit 115e7af

Browse files
authored
Merge pull request #342 from jakub-wojciechowski/master
Add complex crowdsale example #331
2 parents de0e6ba + a9e1fcd commit 115e7af

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
pragma solidity ^0.4.11;
2+
3+
import "../crowdsale/CappedCrowdsale.sol";
4+
import "../crowdsale/RefundableCrowdsale.sol";
5+
import "../token/MintableToken.sol";
6+
7+
/**
8+
* @title SampleCrowdsaleToken
9+
* @dev Very simple ERC20 Token that can be minted.
10+
* It is meant to be used in a crowdsale contract.
11+
*/
12+
contract SampleCrowdsaleToken is MintableToken {
13+
14+
string public constant name = "Sample Crowdsale Token";
15+
string public constant symbol = "SCT";
16+
uint8 public constant decimals = 18;
17+
18+
}
19+
20+
/**
21+
* @title SampleCrowdsale
22+
* @dev This is an example of a fully fledged crowdsale.
23+
* The way to add new features to a base crowdsale is by multiple inheritance.
24+
* In this example we are providing following extensions:
25+
* CappedCrowdsale - sets a max boundary for raised funds
26+
* RefundableCrowdsale - set a min goal to be reached and returns funds if it's not met
27+
*
28+
* After adding multiple features it's good practice to run integration tests
29+
* to ensure that subcontracts works together as intended.
30+
*/
31+
contract SampleCrowdsale is CappedCrowdsale, RefundableCrowdsale {
32+
33+
function SampleCrowdsale(uint256 _startBlock, uint256 _endBlock, uint256 _rate, uint256 _goal, uint256 _cap, address _wallet)
34+
CappedCrowdsale(_cap)
35+
FinalizableCrowdsale()
36+
RefundableCrowdsale(_goal)
37+
Crowdsale(_startBlock, _endBlock, _rate, _wallet)
38+
{
39+
//As goal needs to be met for a successful crowdsale
40+
//the value needs to less or equal than a cap which is limit for accepted funds
41+
require(_goal <= _cap);
42+
}
43+
44+
function createTokenContract() internal returns (MintableToken) {
45+
return new SampleCrowdsaleToken();
46+
}
47+
48+
}

test/SampleCrowdsale.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import ether from './helpers/ether'
2+
import advanceToBlock from './helpers/advanceToBlock'
3+
import EVMThrow from './helpers/EVMThrow'
4+
5+
const BigNumber = web3.BigNumber;
6+
7+
const should = require('chai')
8+
.use(require('chai-as-promised'))
9+
.use(require('chai-bignumber')(BigNumber))
10+
.should();
11+
12+
const SampleCrowdsale = artifacts.require('SampleCrowdsale');
13+
const SampleCrowdsaleToken = artifacts.require('SampleCrowdsaleToken');
14+
15+
contract('Crowdsale', function ([owner, wallet, investor]) {
16+
17+
const RATE = new BigNumber(10);
18+
const GOAL = ether(10);
19+
const CAP = ether(20);
20+
21+
beforeEach(async function () {
22+
this.startBlock = web3.eth.blockNumber + 10;
23+
this.endBlock = web3.eth.blockNumber + 20;
24+
25+
this.crowdsale = await SampleCrowdsale.new(this.startBlock, this.endBlock, RATE, GOAL, CAP, wallet);
26+
this.token = SampleCrowdsaleToken.at(await this.crowdsale.token());
27+
});
28+
29+
30+
it('should create crowdsale with correct parameters', async function () {
31+
this.crowdsale.should.exist;
32+
this.token.should.exist;
33+
34+
(await this.crowdsale.startBlock()).should.be.bignumber.equal(this.startBlock);
35+
(await this.crowdsale.endBlock()).should.be.bignumber.equal(this.endBlock);
36+
(await this.crowdsale.rate()).should.be.bignumber.equal(RATE);
37+
(await this.crowdsale.wallet()).should.be.equal(wallet);
38+
(await this.crowdsale.goal()).should.be.bignumber.equal(GOAL);
39+
(await this.crowdsale.cap()).should.be.bignumber.equal(CAP);
40+
});
41+
42+
it('should not accept payments before start', async function () {
43+
await this.crowdsale.send(ether(1)).should.be.rejectedWith(EVMThrow);
44+
await this.crowdsale.buyTokens(investor, {from: investor, value: ether(1)}).should.be.rejectedWith(EVMThrow);
45+
});
46+
47+
it('should accept payments during the sale', async function () {
48+
const investmentAmount = ether(1);
49+
const expectedTokenAmount = RATE.mul(investmentAmount);
50+
51+
await advanceToBlock(this.startBlock - 1);
52+
await this.crowdsale.buyTokens(investor, {value: investmentAmount, from: investor}).should.be.fulfilled;
53+
54+
(await this.token.balanceOf(investor)).should.be.bignumber.equal(expectedTokenAmount);
55+
(await this.token.totalSupply()).should.be.bignumber.equal(expectedTokenAmount);
56+
});
57+
58+
it('should reject payments after end', async function () {
59+
await advanceToBlock(this.endBlock);
60+
await this.crowdsale.send(ether(1)).should.be.rejectedWith(EVMThrow);
61+
await this.crowdsale.buyTokens(investor, {value: ether(1), from: investor}).should.be.rejectedWith(EVMThrow);
62+
});
63+
64+
it('should reject payments over cap', async function () {
65+
await advanceToBlock(this.startBlock - 1);
66+
await this.crowdsale.send(CAP);
67+
await this.crowdsale.send(1).should.be.rejectedWith(EVMThrow);
68+
});
69+
70+
it('should allow finalization and transfer funds to wallet if the goal is reached', async function () {
71+
await advanceToBlock(this.endBlock - 1);
72+
await this.crowdsale.send(GOAL);
73+
74+
const beforeFinalization = web3.eth.getBalance(wallet);
75+
await this.crowdsale.finalize({from: owner});
76+
const afterFinalization = web3.eth.getBalance(wallet);
77+
78+
afterFinalization.minus(beforeFinalization).should.be.bignumber.equal(GOAL);
79+
});
80+
81+
it('should allow refunds if the goal is not reached', async function () {
82+
const balanceBeforeInvestment = web3.eth.getBalance(investor);
83+
84+
await advanceToBlock(this.startBlock - 1);
85+
await this.crowdsale.sendTransaction({value: ether(1), from: investor, gasPrice: 0});
86+
await advanceToBlock(this.endBlock);
87+
88+
await this.crowdsale.finalize({from: owner});
89+
await this.crowdsale.claimRefund({from: investor, gasPrice: 0}).should.be.fulfilled;
90+
91+
const balanceAfterRefund = web3.eth.getBalance(investor);
92+
balanceBeforeInvestment.should.be.bignumber.equal(balanceAfterRefund);
93+
});
94+
95+
});

0 commit comments

Comments
 (0)