Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions contracts/crowdsale/CompositeCrowdsale.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
pragma solidity ^0.4.11;

import '../token/MintableToken.sol';
import '../math/SafeMath.sol';
import './TokenDistributionStrategy.sol';

/**
* @title CompositeCrowdsale
* @dev CompositeCrowdsale is a base contract for managing a token crowdsale.
* Contrary to a classic crowdsale, it favours composition over inheritance.
*
* Crowdsale behaviour can be modified by specifying TokenDistributionStrategy
* which is a dedicated smart contract that delegates all of the logic managing
* token distribution.
*
* CompositeCrowdsale is at the WIP stage and is meant to illustrate composition
* approach for managing crowdsale logic. It shouldn't be used in production code
* before necessary upgrades and testing.
*/
contract CompositeCrowdsale {
using SafeMath for uint256;

// The token being sold
TokenDistributionStrategy public tokenDistribution;

// start and end timestamps where investments are allowed (both inclusive)
uint256 public startTime;
uint256 public endTime;

// address where funds are collected
address public wallet;

// how many token units a buyer gets per wei
uint256 public rate;

// amount of raised money in wei
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);


function CompositeCrowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, TokenDistributionStrategy _tokenDistribution) {
require(_startTime >= now);
require(_endTime >= _startTime);
require(_rate > 0);
require(_wallet != 0x0);

tokenDistribution = _tokenDistribution;
tokenDistribution.initializeDistribution(this);

startTime = _startTime;
endTime = _endTime;
rate = _rate;
wallet = _wallet;
}


// fallback function can be used to buy tokens
function () payable {
buyTokens(msg.sender);
}

// low level token purchase function
function buyTokens(address beneficiary) payable {
require(beneficiary != 0x0);
require(validPurchase());

uint256 weiAmount = msg.value;

// calculate token amount to be created
uint256 tokens = weiAmount.mul(rate);

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

tokenDistribution.distributeTokens(beneficiary, tokens);
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);

forwardFunds();
}

// send ether to the fund collection wallet
// override to create custom fund forwarding mechanisms
function forwardFunds() internal {
wallet.transfer(msg.value);
}

// @return true if the transaction can buy tokens
function validPurchase() internal constant returns (bool) {
bool withinPeriod = now >= startTime && now <= endTime;
bool nonZeroPurchase = msg.value != 0;
return withinPeriod && nonZeroPurchase;
}

// @return true if crowdsale event has ended
function hasEnded() public constant returns (bool) {
return now > endTime;
}


}
42 changes: 42 additions & 0 deletions contracts/crowdsale/FixedPoolTokenDistributionStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
pragma solidity ^0.4.11;

import '../examples/SimpleToken.sol';
import './TokenDistributionStrategy.sol';
import '../token/ERC20.sol';
import '../math/SafeMath.sol';

/**
* @title FixedRateTokenDistributionStrategy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not the good title

* @dev Strategy that distributes a fixed number of tokens among the contributors.
* It's done in two steps. First, it registers all of the contributions while the sale is active.
* After the crowdsale has ended the contract compensate buyers proportionally to their contributions.
*/
contract FixedPoolTokenDistributionStrategy is TokenDistributionStrategy {
using SafeMath for uint256;

// The token being sold
ERC20 token;
mapping(address => uint256) contributions;
uint256 totalContributed;

function FixedPoolTokenDistributionStrategy(ERC20 _token) {
token = _token;
}

function distributeTokens(address _beneficiary, uint256 _amount) onlyCrowdsale {
contributions[_beneficiary] = contributions[_beneficiary].add(_amount);
totalContributed = totalContributed.add(_amount);
}

function compensate(address _beneficiary) {
require(crowdsale.hasEnded());
uint256 amount = contributions[_beneficiary].mul(token.totalSupply()).div(totalContributed);
if (token.transfer(_beneficiary, amount)) {
contributions[_beneficiary] = 0;
}
}

function getToken() constant returns(ERC20) {
return token;
}
}
30 changes: 30 additions & 0 deletions contracts/crowdsale/FixedRateTokenDistributionStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pragma solidity ^0.4.11;

import '../token/MintableToken.sol';
import './TokenDistributionStrategy.sol';
import '../token/ERC20.sol';

/**
* @title FixedRateTokenDistributionStrategy
* @dev Strategy that grants a fixed number of tokens per donation value
* Final number of tokens is not defined as it depends on the total amount
* of contributions that are collected during the crowdsale.
*/
contract FixedRateTokenDistributionStrategy is TokenDistributionStrategy {

// The token being sold
MintableToken token;

function initializeDistribution(CompositeCrowdsale _crowdsale) {
super.initializeDistribution(_crowdsale);
token = new MintableToken();
}

function distributeTokens(address beneficiary, uint amount) onlyCrowdsale {
token.mint(beneficiary, amount);
}

function getToken() constant returns(ERC20) {
return token;
}
}
Copy link
Contributor

@SylTi SylTi Nov 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably will need to dissociate ownership from minting right as we talked about in slack.
We can't ever finish the minting with the current version.

29 changes: 29 additions & 0 deletions contracts/crowdsale/TokenDistributionStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
pragma solidity ^0.4.11;

import '../token/ERC20.sol';
import './CompositeCrowdsale.sol';


/**
* @title TokenDistributionStrategy
* @dev Base abstract contract defining methods that control token distribution
*/
contract TokenDistributionStrategy {

CompositeCrowdsale crowdsale;

modifier onlyCrowdsale() {
require(msg.sender == address(crowdsale));
_;
}

function initializeDistribution(CompositeCrowdsale _crowdsale) {
require(crowdsale == address(0));
require(_crowdsale != address(0));
crowdsale = _crowdsale;
}

function distributeTokens(address beneficiary, uint amount) onlyCrowdsale {}

function getToken() constant returns(ERC20);
}
91 changes: 91 additions & 0 deletions test/CompositeCrowdsale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import ether from './helpers/ether'
import {advanceBlock} from './helpers/advanceToBlock'
import {increaseTimeTo, duration} from './helpers/increaseTime'
import latestTime from './helpers/latestTime'

const BigNumber = web3.BigNumber

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

const CompositeCrowdsale = artifacts.require('CompositeCrowdsale')
const FixedRateTokenDistribution = artifacts.require('FixedRateTokenDistributionStrategy')
const Token = artifacts.require('ERC20')

const FixedPoolTokenDistribution = artifacts.require('FixedPoolTokenDistributionStrategy')
const SimpleToken = artifacts.require('SimpleToken')

contract('CompositeCrowdsale', function ([_, investor, wallet]) {

const RATE = new BigNumber(1000);

before(async function() {
//Advance to the next block to correctly read time in the solidity "now" function interpreted by testrpc
await advanceBlock();
})

describe('Fixed Rate Distribution', function () {

beforeEach(async function () {
this.startTime = latestTime() + duration.weeks(1);
this.endTime = this.startTime + duration.weeks(1);
this.afterEndTime = this.endTime + duration.seconds(1);

this.tokenDistribution = await FixedRateTokenDistribution.new();
this.crowdsale = await CompositeCrowdsale.new(this.startTime, this.endTime, RATE, wallet, this.tokenDistribution.address)
this.token = Token.at(await this.tokenDistribution.getToken.call());
})

beforeEach(async function () {
await increaseTimeTo(this.startTime)
})

it('should accept payments and mint tokens during the sale', async function () {
const investmentAmount = ether(1);
const expectedTokenAmount = RATE.mul(investmentAmount);

let tx = await this.crowdsale.buyTokens(investor, {value: investmentAmount, from: investor}).should.be.fulfilled;
console.log("*** COMPOSITION FIXED RATE: " + tx.receipt.gasUsed + " gas used.");

(await this.token.balanceOf(investor)).should.be.bignumber.equal(expectedTokenAmount);
(await this.token.totalSupply()).should.be.bignumber.equal(expectedTokenAmount);
});

});

describe('Fixed Pool Distribution', function () {

beforeEach(async function () {
this.startTime = latestTime() + duration.weeks(1);
this.endTime = this.startTime + duration.weeks(1);
this.afterEndTime = this.endTime + duration.seconds(1);

const fixedPoolToken = await SimpleToken.new();
const totalSupply = await fixedPoolToken.totalSupply();
this.tokenDistribution = await FixedPoolTokenDistribution.new(fixedPoolToken.address);
await fixedPoolToken.transfer(this.tokenDistribution.address, totalSupply);
this.crowdsale = await CompositeCrowdsale.new(this.startTime, this.endTime, RATE, wallet, this.tokenDistribution.address)
this.token = Token.at(await this.tokenDistribution.getToken.call());
})

beforeEach(async function () {
await increaseTimeTo(this.startTime)
})

it('should buy tokens and compensate contributors after the sale', async function () {
const investmentAmount = ether(1);
let tx = await this.crowdsale.buyTokens(investor, {value: investmentAmount, from: investor}).should.be.fulfilled;
console.log("*** COMPOSITION FIXED POOL: " + tx.receipt.gasUsed + " gas used.");

await increaseTimeTo(this.afterEndTime);
await this.tokenDistribution.compensate(investor).should.be.fulfilled;
const totalSupply = await this.token.totalSupply();
(await this.token.balanceOf(investor)).should.be.bignumber.equal(totalSupply);
})


});

})
6 changes: 6 additions & 0 deletions test/Crowdsale.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.fulfilled
})

it('should measure buyTokens tx costs', async function () {
await increaseTimeTo(this.startTime)
let tx = await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.fulfilled
console.log("*** INHERITANCE: " + tx.receipt.gasUsed + " gas used.");
})

it('should reject payments after end', async function () {
await increaseTimeTo(this.afterEndTime)
await this.crowdsale.send(value).should.be.rejectedWith(EVMThrow)
Expand Down