pragma solidity ^0.4.11;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev modifier to allow actions only when the contract IS paused
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev modifier to allow actions only when the contract IS NOT paused
*/
modifier whenPaused {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused returns (bool) {
paused = true;
Pause();
return true;
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused returns (bool) {
paused = false;
Unpause();
return true;
}
}
contract IMintableToken {
function mint(address _to, uint256 _amount) returns (bool);
function finishMinting() returns (bool);
}
contract PricingStrategy {
using SafeMath for uint;
uint public rate0;
uint public rate1;
uint public rate2;
uint public threshold1;
uint public threshold2;
uint public minimumWeiAmount;
function PricingStrategy(
uint _rate0,
uint _rate1,
uint _rate2,
uint _minimumWeiAmount,
uint _threshold1,
uint _threshold2
) {
require(_rate0 > 0);
require(_rate1 > 0);
require(_rate2 > 0);
require(_minimumWeiAmount > 0);
require(_threshold1 > 0);
require(_threshold2 > 0);
rate0 = _rate0;
rate1 = _rate1;
rate2 = _rate2;
minimumWeiAmount = _minimumWeiAmount;
threshold1 = _threshold1;
threshold2 = _threshold2;
}
/** Interface declaration. */
function isPricingStrategy() public constant returns (bool) {
return true;
}
/** Calculate the current price for buy in amount. */
function calculateTokenAmount(uint weiAmount) public constant returns (uint tokenAmount) {
uint bonusRate = 0;
if (weiAmount >= minimumWeiAmount) {
bonusRate = rate0;
}
if (weiAmount >= threshold1) {
bonusRate = rate1;
}
if (weiAmount >= threshold2) {
bonusRate = rate2;
}
return weiAmount.mul(bonusRate);
}
}
contract Reservation is Pausable {
using SafeMath for uint;
/* Max investment count when we are still allowed to change the multisig address */
uint public MAX_INVESTMENTS_BEFORE_MULTISIG_CHANGE = 500;
/* The token we are selling */
IMintableToken public token;
/* How we are going to price our offering */
PricingStrategy public pricingStrategy;
/* tokens will be transfered from this address */
address public multisigWallet;
/* if the funding goal is not reached, investors may withdraw their funds */
uint public minimumFundingGoal;
/* the UNIX timestamp start date of the reservation */
uint public startsAt;
/* the UNIX timestamp end date of the reservation */
uint public endsAt;
/* Maximum amount of tokens this reservation can sell. */
uint public tokensHardCap;
/* the number of tokens already sold through this contract*/
uint public tokensSold = 0;
/* How many wei of funding we have raised */
uint public weiRaised = 0;
/* How many distinct addresses have invested */
uint public investorCount = 0;
/* How much wei we have returned back to the contract after a failed crowdfund. */
uint public loadedRefund = 0;
/* How much wei we have given back to investors.*/
uint public weiRefunded = 0;
/** How much ETH each address has invested to this reservation */
mapping (address => uint256) public investedAmountOf;
/** How much tokens this reservation has credited for each investor address */
mapping (address => uint256) public tokenAmountOf;
/** Addresses that are allowed to invest even before ICO offical opens. Only for testing purpuses. */
mapping (address => bool) public earlyParticipantWhitelist;
/** State machine
*
* - Preparing: All contract initialization calls and variables have not been set yet
* - Prefunding: We have not passed start time yet
* - Funding: Active reservation
* - Success: Minimum funding goal reached
* - Failure: Minimum funding goal not reached before ending time
* - Refunding: Refunds are loaded on the contract for reclaim.
*/
enum State{Unknown, Preparing, PreFunding, Funding, Success, Failure, Refunding}
// A new investment was made
event Invested(address investor, uint weiAmount, uint tokenAmount);
// Refund was processed for a contributor
event Refund(address investor, uint weiAmount);
// Address early participation whitelist status changed
event Whitelisted(address addr, bool status);
// Reservation end time has been changed
event EndsAtChanged(uint endsAt);
function Reservation(
address _token,
address _pricingStrategy,
address _multisigWallet,
uint _start,
uint _end,
uint _tokensHardCap,
uint _minimumFundingGoal
) {
require(_token != 0);
require(_pricingStrategy != 0);
require(_multisigWallet != 0);
require(_start != 0);
require(_end != 0);
require(_start < _end);
require(_tokensHardCap != 0);
token = IMintableToken(_token);
setPricingStrategy(_pricingStrategy);
multisigWallet = _multisigWallet;
startsAt = _start;
endsAt = _end;
tokensHardCap = _tokensHardCap;
minimumFundingGoal = _minimumFundingGoal;
}
/**
* Buy tokens
*/
function() payable {
invest(msg.sender);
}
/**
* Make an investment.
*
* Reservation must be running for one to invest.
* We must have not pressed the emergency brake.
*
* @param receiver The Ethereum address who receives the tokens
*/
function invest(address receiver) whenNotPaused payable {
// Determine if it's a good time to accept investment from this participant
if (getState() == State.PreFunding) {
// Are we whitelisted for early deposit
require(earlyParticipantWhitelist[receiver]);
} else {
require(getState() == State.Funding);
}
uint weiAmount = msg.value;
// Account reservation sales separately, so that they do not count against pricing tranches
uint tokenAmount = pricingStrategy.calculateTokenAmount(weiAmount);
// Dust transaction
require(tokenAmount > 0);
if (investedAmountOf[receiver] == 0) {
// A new investor
investorCount++;
}
// Update investor
investedAmountOf[receiver] = investedAmountOf[receiver].add(weiAmount);
tokenAmountOf[receiver] = tokenAmountOf[receiver].add(tokenAmount);
// Update totals
weiRaised = weiRaised.add(weiAmount);
tokensSold = tokensSold.add(tokenAmount);
// Check that we did not bust the cap
require(!isBreakingCap(tokensSold));
token.mint(receiver, tokenAmount);
// Pocket the money
multisigWallet.transfer(weiAmount);
// Tell us invest was success
Invested(receiver, weiAmount, tokenAmount);
}
/**
* Allow addresses to do early participation.
*
*/
function setEarlyParicipantWhitelist(address addr, bool status) onlyOwner {
earlyParticipantWhitelist[addr] = status;
Whitelisted(addr, status);
}
/**
* Allow reservation owner to close early or extend the reservation.
*
* This is useful e.g. for a manual soft cap implementation:
* - after X amount is reached determine manual closing
*
* This may put the reservation to an invalid state,
* but we trust owners know what they are doing.
*
*/
function setEndsAt(uint time) onlyOwner {
require(now <= time);
endsAt = time;
EndsAtChanged(endsAt);
}
/**
* Allow to (re)set pricing strategy.
*
* Design choice: no state restrictions on the set, so that we can fix fat finger mistakes.
*/
function setPricingStrategy(address _pricingStrategy) onlyOwner {
pricingStrategy = PricingStrategy(_pricingStrategy);
// Don't allow setting bad agent
require(pricingStrategy.isPricingStrategy());
}
/**
* Allow to change the team multisig address in the case of emergency.
*
* This allows to save a deployed reservation wallet in the case the reservation has not yet begun
* (we have done only few test transactions). After the reservation is going
* then multisig address stays locked for the safety reasons.
*/
function setMultisig(address addr) public onlyOwner {
require(investorCount <= MAX_INVESTMENTS_BEFORE_MULTISIG_CHANGE);
multisigWallet = addr;
}
/**
* Allow load refunds back on the contract for the refunding.
*
* The team can transfer the funds back on the smart contract in the case the minimum goal was not reached..
*/
function loadRefund() public payable inState(State.Failure) {
require(msg.value > 0);
loadedRefund = loadedRefund.add(msg.value);
}
/**
* Investors can claim refund.
*
* Note that any refunds from proxy buyers should be handled separately,
* and not through this contract.
*/
function refund() public inState(State.Refunding) {
uint256 weiValue = investedAmountOf[msg.sender];
require(weiValue > 0);
investedAmountOf[msg.sender] = 0;
weiRefunded = weiRefunded.add(weiValue);
Refund(msg.sender, weiValue);
msg.sender.transfer(weiValue);
}
/**
* Crowdfund state machine management.
*
* We make it a function and do not assign the result to a variable, so there is no chance of the variable being stale.
*/
function getState() public constant returns (State) {
if (address(pricingStrategy) == 0)
return State.Preparing;
else if (block.timestamp < startsAt)
return State.PreFunding;
else if (block.timestamp <= endsAt && !isReservationFull())
return State.Funding;
else if (isMinimumGoalReached())
return State.Success;
else if (!isMinimumGoalReached() && weiRaised > 0 && loadedRefund >= weiRaised)
return State.Refunding;
else
return State.Failure;
}
/**
* @return true if the reservation has raised enough money to be a successful.
*/
function isMinimumGoalReached() public constant returns (bool reached) {
return weiRaised >= minimumFundingGoal;
}
/**
* Called from invest() to confirm if the curret investment does not break our cap rule.
*/
function isBreakingCap(uint tokensSoldTotal) constant returns (bool) {
return tokensSoldTotal > tokensHardCap;
}
function isReservationFull() public constant returns (bool) {
return tokensSold >= tokensHardCap;
}
//
// Modifiers
//
/** Modified allowing execution only if the reservation is currently running. */
modifier inState(State state) {
require(getState() == state);
_;
}
}
{
"compilationTarget": {
"Reservation.sol": "Reservation"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"receiver","type":"address"}],"name":"invest","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"endsAt","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"minimumFundingGoal","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getState","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"investedAmountOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"weiRaised","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_pricingStrategy","type":"address"}],"name":"setPricingStrategy","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"tokensSold","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"refund","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"weiRefunded","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"MAX_INVESTMENTS_BEFORE_MULTISIG_CHANGE","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"time","type":"uint256"}],"name":"setEndsAt","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"pricingStrategy","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"loadedRefund","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"isMinimumGoalReached","outputs":[{"name":"reached","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"loadRefund","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"multisigWallet","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"tokenAmountOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"isReservationFull","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"startsAt","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"tokensHardCap","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"earlyParticipantWhitelist","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"investorCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"tokensSoldTotal","type":"uint256"}],"name":"isBreakingCap","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"status","type":"bool"}],"name":"setEarlyParicipantWhitelist","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"setMultisig","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"_token","type":"address"},{"name":"_pricingStrategy","type":"address"},{"name":"_multisigWallet","type":"address"},{"name":"_start","type":"uint256"},{"name":"_end","type":"uint256"},{"name":"_tokensHardCap","type":"uint256"},{"name":"_minimumFundingGoal","type":"uint256"}],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"investor","type":"address"},{"indexed":false,"name":"weiAmount","type":"uint256"},{"indexed":false,"name":"tokenAmount","type":"uint256"}],"name":"Invested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"investor","type":"address"},{"indexed":false,"name":"weiAmount","type":"uint256"}],"name":"Refund","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"addr","type":"address"},{"indexed":false,"name":"status","type":"bool"}],"name":"Whitelisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"endsAt","type":"uint256"}],"name":"EndsAtChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}]