Skip to content

Commit

Permalink
Crowdsale refactor and add new models (#744)
Browse files Browse the repository at this point in the history
* Basic idea

* Fine tuning idea

* Add comments / tidy up Crowdsale base class

* fixed TimedCrowdsale constructor

* added simple crowdsale test

* added HODL directory under home to store unused contracts. ugly hack to solve Crowdsale selection in tests, better way?

* Capped no longer inherits from Timed, added capReached() method (replacing hasEnded())

* added SafeMath in TimedCrowdsale for safety, CHECK whether it is inherited from Crowdsale

* several fixes related to separating Capped from Timed. functions renamed, mocks changed. Capped tests passing

* added TimedCrowdsaleImpl.sol, TimedCrowdsale tests, passed

* added Whitelisted implementation and test, passed.

* removed unnecessary super constructor call in WhitelistedCrowdsale, removed unused dependencies in tests

* renamed UserCappedCrowdsale to IndividuallyCappedCrowdsale, implemented IndividuallyCappedCrowdsaleImpl.sol and corresponding tests, passed.

* homogeneized use of using SafeMath for uint256 across validation crowdsales. checked that it IS indeed inherited, but leaving it there as per Frans suggestion.

* adding questions.md where I track questions, bugs and progress

* modified VariablePriceCrowdsale, added Impl.

* finished VariablePrice, fixed sign, added test, passing.

* changed VariablePrice to IncreasingPrice, added corresponding require()

* MintedCrowdsale done, mock implemented, test passing

* PremintedCrowdsale done, mocks, tests passing

* checked FinalizableCrowdsale

* PostDeliveryCrowdsale done, mock, tests passing.

* RefundableCrowdsale done. Detached Vault. modified mock and test, passing

* renamed crowdsale-refactor to crowdsale in contracts and test

* deleted HODL old contracts

* polished variable names in tests

* fixed typos and removed comments in tests

* Renamed 'crowdsale-refactor' to 'crowdsale' in all imports

* Fix minor param naming issues in Crowdsale functions and added documentation to Crowdsale.sol

* Added documentation to Crowdsale extensions

* removed residual comments and progress tracking files

* added docs for validation crowdsales

* Made user promises in PostDeliveryCrowdsale public so that users can query their promised token balance.

* added docs for distribution crowdsales

* renamed PremintedCrowdsale to AllowanceCrowdsale

* added allowance check function and corresponding test. fixed filename in AllowanceCrowdsale mock.

* spilt Crowdsale _postValidatePurchase in _postValidatePurchase and _updatePurchasingState. changed IndividuallyCappedCrowdsale accordingly.

* polished tests for linter, salve Travis

* polished IncreasingPriceCrowdsale.sol for linter.

* renamed and polished for linter WhitelistedCrowdsale test.

* fixed indentation in IncreasingPriceCrowdsaleImpl.sol for linter

* fixed ignoring token.mint return value in MintedCrowdsale.sol

* expanded docs throughout, fixed minor issues

* extended test coverage for IndividuallyCappedCrowdsale

* Extended WhitelistedCrwodsale test coverage

* roll back decoupling of RefundVault in RefundableCrowdsale

* moved cap exceedance checks in Capped and IndividuallyCapped crowdsales to _preValidatePurchase to save gas

* revert name change, IndividuallyCapped to UserCapped

* extended docs.

* added crowd whitelisting with tests

* added group capping, plus tests

* added modifiers in TimedCrowdsale and WhitelistedCrowdsale

* polished tests for linter

* moved check of whitelisted to modifier, mainly for testing coverage

* fixed minor ordering/polishingafter review

* modified TimedCrowdsale modifier/constructor ordering

* unchanged truffle-config.js

* changed indentation of visibility modifier in mocks

* changed naming of modifier and function to use Open/Closed for TimedCrowdsale

* changed ordering of constructor calls in SampleCrowdsale

* changed startTime and endTime to openingTime and closingTime throughout

* fixed exceeding line lenght for linter

* renamed _emitTokens to _deliverTokens

* renamed addCrowdToWhitelist to addManyToWhitelist

* renamed UserCappedCrowdsale to IndividuallyCappedCrowdsale
  • Loading branch information
fiiiu authored and maraoz committed Feb 20, 2018
1 parent 108d5f3 commit c05918c
Show file tree
Hide file tree
Showing 36 changed files with 1,319 additions and 283 deletions.
35 changes: 0 additions & 35 deletions contracts/crowdsale/CappedCrowdsale.sol

This file was deleted.

147 changes: 99 additions & 48 deletions contracts/crowdsale/Crowdsale.sol
Original file line number Diff line number Diff line change
@@ -1,107 +1,158 @@
pragma solidity ^0.4.18;

import "../token/ERC20/MintableToken.sol";
import "../token/ERC20/ERC20.sol";
import "../math/SafeMath.sol";


/**
* @title Crowdsale
* @dev Crowdsale is a base contract for managing a token crowdsale.
* Crowdsales have a start and end timestamps, where investors can make
* token purchases and the crowdsale will assign them tokens based
* on a token per ETH rate. Funds collected are forwarded to a wallet
* as they arrive. The contract requires a MintableToken that will be
* minted as contributions arrive, note that the crowdsale contract
* must be owner of the token in order to be able to mint it.
* @dev Crowdsale is a base contract for managing a token crowdsale,
* allowing investors to purchase tokens with ether. This contract implements
* such functionality in its most fundamental form and can be extended to provide additional
* functionality and/or custom behavior.
* The external interface represents the basic interface for purchasing tokens, and conform
* the base architecture for crowdsales. They are *not* intended to be modified / overriden.
* The internal interface conforms the extensible and modifiable surface of crowdsales. Override
* the methods to add functionality. Consider using 'super' where appropiate to concatenate
* behavior.
*/

contract Crowdsale {
using SafeMath for uint256;

// The token being sold
MintableToken public token;

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

// address where funds are collected
// Address where funds are collected
address public wallet;

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

// amount of raised money in wei
// Amount of wei raised
uint256 public weiRaised;

/**
* event for token purchase logging
* 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 Crowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, MintableToken _token) public {
require(_startTime >= now);
require(_endTime >= _startTime);
/**
* @param _rate Number of token units a buyer gets per wei
* @param _wallet Address where collected funds will be forwarded to
* @param _token Address of the token being sold
*/
function Crowdsale(uint256 _rate, address _wallet, ERC20 _token) public {
require(_rate > 0);
require(_wallet != address(0));
require(_token != address(0));

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

// fallback function can be used to buy tokens
// -----------------------------------------
// Crowdsale external interface
// -----------------------------------------

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

// low level token purchase function
function buyTokens(address beneficiary) public payable {
require(beneficiary != address(0));
require(validPurchase());
/**
* @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);
uint256 tokens = _getTokenAmount(weiAmount);

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

token.mint(beneficiary, tokens);
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
_processPurchase(_beneficiary, tokens);
TokenPurchase(msg.sender, _beneficiary, weiAmount, tokens);

_updatePurchasingState(_beneficiary, weiAmount);

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

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

/**
* @dev Validation of an incoming purchase. Use require statemens 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);
}

forwardFunds();
/**
* @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
}

// @return true if crowdsale event has ended
function hasEnded() public view returns (bool) {
return now > endTime;
/**
* @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.transfer(_beneficiary, _tokenAmount);
}

// Override this method to have a way to add business logic to your crowdsale when buying
function getTokenAmount(uint256 weiAmount) internal view returns(uint256) {
return weiAmount.mul(rate);
/**
* @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);
}

// send ether to the fund collection wallet
// override to create custom fund forwarding mechanisms
function forwardFunds() internal {
wallet.transfer(msg.value);
/**
* @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
}

// @return true if the transaction can buy tokens
function validPurchase() internal view returns (bool) {
bool withinPeriod = now >= startTime && now <= endTime;
bool nonZeroPurchase = msg.value != 0;
return withinPeriod && nonZeroPurchase;
/**
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
pragma solidity ^0.4.18;

import "../math/SafeMath.sol";
import "../ownership/Ownable.sol";
import "./Crowdsale.sol";

import "../../math/SafeMath.sol";
import "../../ownership/Ownable.sol";
import "../validation/TimedCrowdsale.sol";

/**
* @title FinalizableCrowdsale
* @dev Extension of Crowdsale where an owner can do extra work
* after finishing.
*/
contract FinalizableCrowdsale is Crowdsale, Ownable {
contract FinalizableCrowdsale is TimedCrowdsale, Ownable {
using SafeMath for uint256;

bool public isFinalized = false;
Expand All @@ -23,7 +22,7 @@ contract FinalizableCrowdsale is Crowdsale, Ownable {
*/
function finalize() onlyOwner public {
require(!isFinalized);
require(hasEnded());
require(hasClosed());

finalization();
Finalized();
Expand Down
35 changes: 35 additions & 0 deletions contracts/crowdsale/distribution/PostDeliveryCrowdsale.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity ^0.4.18;

import "../validation/TimedCrowdsale.sol";
import "../../token/ERC20/ERC20.sol";
import "../../math/SafeMath.sol";

/**
* @title PostDeliveryCrowdsale
* @dev Crowdsale that locks tokens from withdrawal until it ends.
*/
contract PostDeliveryCrowdsale is TimedCrowdsale {
using SafeMath for uint256;

mapping(address => uint256) public balances;

/**
* @dev Overrides parent by storing balances instead of issuing tokens right away.
* @param _beneficiary Token purchaser
* @param _tokenAmount Amount of tokens purchased
*/
function _processPurchase(address _beneficiary, uint256 _tokenAmount) internal {
balances[_beneficiary] = balances[_beneficiary].add(_tokenAmount);
}

/**
* @dev Withdraw tokens only after crowdsale ends.
*/
function withdrawTokens() public {
require(hasClosed());
uint256 amount = balances[msg.sender];
require(amount > 0);
balances[msg.sender] = 0;
_deliverTokens(msg.sender, amount);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pragma solidity ^0.4.18;


import "../math/SafeMath.sol";
import "../../math/SafeMath.sol";
import "./FinalizableCrowdsale.sol";
import "./RefundVault.sol";
import "./utils/RefundVault.sol";


/**
Expand All @@ -21,25 +21,37 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
// refund vault used to hold funds while crowdsale is running
RefundVault public vault;

/**
* @dev Constructor, creates RefundVault.
* @param _goal Funding goal
*/
function RefundableCrowdsale(uint256 _goal) public {
require(_goal > 0);
vault = new RefundVault(wallet);
goal = _goal;
}

// if crowdsale is unsuccessful, investors can claim refunds here
/**
* @dev Investors can claim refunds here if crowdsale is unsuccessful
*/
function claimRefund() public {
require(isFinalized);
require(!goalReached());

vault.refund(msg.sender);
}

/**
* @dev Checks whether funding goal was reached.
* @return Whether funding goal was reached
*/
function goalReached() public view returns (bool) {
return weiRaised >= goal;
}

// vault finalization task, called when owner calls finalize()
/**
* @dev vault finalization task, called when owner calls finalize()
*/
function finalization() internal {
if (goalReached()) {
vault.close();
Expand All @@ -50,10 +62,10 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
super.finalization();
}

// We're overriding the fund forwarding from Crowdsale.
// In addition to sending the funds, we want to call
// the RefundVault deposit function
function forwardFunds() internal {
/**
* @dev Overrides Crowdsale fund forwarding, sending funds to vault.
*/
function _forwardFunds() internal {
vault.deposit.value(msg.value)(msg.sender);
}

Expand Down

0 comments on commit c05918c

Please sign in to comment.