/**
* Source Code first verified at https://etherscan.io on Wednesday, March 20, 2019
(UTC) */
pragma solidity 0.5.3;
// ---------------------------------------------------------------------------
// RICO
// ---------------------------------------------------------------------------
// File: contracts/interfaces/IERC20.sol
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function totalSupply() external view returns (uint256);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: contracts/helpers/SafeMath.sol
/**
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
}
// File: contracts/helpers/ReentrancyGuard.sol
/**
* @title Helps contracts guard against reentrancy attacks.
* @author Remco Bloemen <[email protected]π.com>, Eenae <[email protected]>
* @dev If you mark a function `nonReentrant`, you should also
* mark it `external`.
*/
contract ReentrancyGuard {
/// @dev counter to allow mutex lock with only one SSTORE operation
uint256 private _guardCounter;
constructor () internal {
// The counter starts at one to prevent changing it from zero to a non-zero
// value, which is a more expensive operation.
_guardCounter = 1;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_guardCounter += 1;
uint256 localCounter = _guardCounter;
_;
require(localCounter == _guardCounter);
}
}
// File: contracts/ownerships/ClusterRole.sol
contract ClusterRole {
address payable private _cluster;
/**
* @dev Throws if called by any account other than the cluster.
*/
modifier onlyCluster() {
require(isCluster(), "onlyCluster: only cluster can call this method.");
_;
}
/**
* @dev The Cluster Role sets the original `cluster` of the contract to the sender
* account.
*/
constructor () internal {
_cluster = msg.sender;
}
/**
* @return the address of the cluster contract.
*/
function cluster() public view returns (address payable) {
return _cluster;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isCluster() public view returns (bool) {
return msg.sender == _cluster;
}
}
// File: contracts/ownerships/Ownable.sol
contract OperatorRole {
address payable private _operator;
event OwnershipTransferred(address indexed previousOperator, address indexed newOperator);
/**
* @dev Throws if called by any account other than the operator.
*/
modifier onlyOperator() {
require(isOperator(), "onlyOperator: only the operator can call this method.");
_;
}
/**
* @dev The OperatorRole constructor sets the original `operator` of the contract to the sender
* account.
*/
constructor (address payable operator) internal {
_operator = operator;
emit OwnershipTransferred(address(0), operator);
}
/**
* @dev Allows the current operator to transfer control of the contract to a newOperator.
* @param newOperator The address to transfer ownership to.
*/
function transferOwnership(address payable newOperator) external onlyOperator {
_transferOwnership(newOperator);
}
/**
* @dev Transfers control of the contract to a newOperator.
* @param newOperator The address to transfer ownership to.
*/
function _transferOwnership(address payable newOperator) private {
require(newOperator != address(0), "_transferOwnership: the address of new operator is not valid.");
emit OwnershipTransferred(_operator, newOperator);
_operator = newOperator;
}
/**
* @return the address of the operator.
*/
function operator() public view returns (address payable) {
return _operator;
}
/**
* @return true if `msg.sender` is the operator of the contract.
*/
function isOperator() public view returns (bool) {
return msg.sender == _operator;
}
}
// File: contracts/Crowdsale.sol
/**
* @title Crowdsale
* @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 / overridden.
* The internal interface conforms the extensible and modifiable surface of crowdsales. Override
* the methods to add functionality. Consider using 'super' where appropriate to concatenate
* behavior.
*/
contract Crowdsale is ReentrancyGuard, ClusterRole, OperatorRole {
using SafeMath for uint256;
IERC20 internal _token;
// Crowdsale constant details
uint256 private _fee;
uint256 private _rate;
uint256 private _minInvestmentAmount;
// Crowdsale purchase state
uint256 internal _weiRaised;
uint256 internal _tokensSold;
// Emergency transfer variables
address private _newContract;
bool private _emergencyExitCalled;
address[] private _investors;
// Get Investor token/eth balances by address
struct Investor {
uint256 eth;
uint256 tokens;
uint256 withdrawnEth;
uint256 withdrawnTokens;
bool refunded;
}
mapping (address => Investor) internal _balances;
// Bonuses state
struct Bonus {
uint256 amount;
uint256 finishTimestamp;
}
Bonus[] private _bonuses;
event Deposited(address indexed beneficiary, uint256 indexed weiAmount, uint256 indexed tokensAmount, uint256 fee);
event EthTransfered(address indexed beneficiary,uint256 weiAmount);
event TokensTransfered(address indexed beneficiary, uint256 tokensAmount);
event Refunded(address indexed beneficiary, uint256 indexed weiAmount);
event EmergencyExitCalled(address indexed newContract, uint256 indexed tokensAmount, uint256 indexed weiAmount);
/**
* @dev 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 ERC20Detailed token
* with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
* @param token Address of the token being sold
*/
constructor (
uint256 rate,
address token,
address payable operator,
uint256[] memory bonusFinishTimestamp,
uint256[] memory bonuses,
uint256 minInvestmentAmount,
uint256 fee
) internal OperatorRole(operator) {
if (bonuses.length > 0) {
for (uint256 i = 0; i < bonuses.length; i++) {
if (i != 0) {
require(bonusFinishTimestamp[i] > bonusFinishTimestamp[i - 1], "Crowdsale: invalid bonus finish timestamp.");
}
Bonus memory bonus = Bonus(bonuses[i], bonusFinishTimestamp[i]);
_bonuses.push(bonus);
}
}
_rate = rate;
_token = IERC20(token);
_minInvestmentAmount = minInvestmentAmount;
_fee = fee;
}
// -----------------------------------------
// EXTERNAL
// -----------------------------------------
/**
* @dev fallback function ***DO NOT OVERRIDE***
* Note that other contracts will transfer fund with a base gas stipend
* of 2300, which is not enough to call buyTokens. Consider calling
* buyTokens directly when purchasing tokens from a contract.
*/
function () external payable {
buyTokens(msg.sender);
}
/**
* @dev low level token purchase ***DO NOT OVERRIDE***
* This function has a non-reentrancy guard, so it shouldn't be called by
* another `nonReentrant` function.
* @param beneficiary Recipient of the token purchase
*/
function buyTokens(address beneficiary) public nonReentrant payable {
uint256 weiAmount = msg.value;
_preValidatePurchase(beneficiary, weiAmount);
// calculating the fee from weiAmount
uint256 fee = _calculatePercent(weiAmount, _fee);
// calculate token amount to be created
uint256 tokensAmount = _calculateTokensAmount(weiAmount);
// removing the fee amount from main value
weiAmount = weiAmount.sub(fee);
_processPurchase(beneficiary, weiAmount, tokensAmount);
// transfer the fee to cluster contract
cluster().transfer(fee);
emit Deposited(beneficiary, weiAmount, tokensAmount, fee);
}
/**
* @dev transfer all funds (ETH/Tokens) to another contract, if this crowdsale has some issues
* @param newContract address of receiver contract
*/
function emergencyExit(address payable newContract) public {
require(newContract != address(0), "emergencyExit: invalid new contract address.");
require(isCluster() || isOperator(), "emergencyExit: only operator or cluster can call this method.");
if (isCluster()) {
_emergencyExitCalled = true;
_newContract = newContract;
} else if (isOperator()) {
require(_emergencyExitCalled == true, "emergencyExit: the cluster need to call this method first.");
require(_newContract == newContract, "emergencyExit: the newContract address is not the same address with clusters newContract.");
uint256 allLockedTokens = _token.balanceOf(address(this));
_withdrawTokens(newContract, allLockedTokens);
uint256 allLocketETH = address(this).balance;
_withdrawEther(newContract, allLocketETH);
emit EmergencyExitCalled(newContract, allLockedTokens, allLocketETH);
}
}
// -----------------------------------------
// INTERNAL
// -----------------------------------------
/**
* @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
* @param beneficiary Address performing the token purchase
* @param weiAmount Value in wei involved in the purchase
*/
function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
require(weiAmount >= _minInvestmentAmount, "_preValidatePurchase: msg.amount should be bigger then _minInvestmentAmount.");
require(beneficiary != address(0), "_preValidatePurchase: invalid beneficiary address.");
require(_emergencyExitCalled == false, "_preValidatePurchase: the crowdsale contract address was transfered.");
}
/**
* @dev Calculate the fee amount from msg.value
*/
function _calculatePercent(uint256 amount, uint256 percent) internal pure returns (uint256) {
return amount.mul(percent).div(100);
}
/**
* @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 _calculateTokensAmount(uint256 weiAmount) internal view returns (uint256) {
uint256 tokensAmount = weiAmount.mul(_rate);
for (uint256 i = 0; i < _bonuses.length; i++) {
if (block.timestamp <= _bonuses[i].finishTimestamp) {
uint256 bonusAmount = _calculatePercent(tokensAmount, _bonuses[i].amount);
tokensAmount = tokensAmount.add(bonusAmount);
break;
}
}
return tokensAmount;
}
/**
* @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send tokens.
* @param beneficiary Address receiving the tokens
* @param tokenAmount Number of tokens to be purchased
*/
function _processPurchase(address beneficiary, uint256 weiAmount, uint256 tokenAmount) internal {
// updating the purchase state
_weiRaised = _weiRaised.add(weiAmount);
_tokensSold = _tokensSold.add(tokenAmount);
// if investor is new pushing his/her address to investors list
if (_balances[beneficiary].eth == 0 && _balances[beneficiary].refunded == false) {
_investors.push(beneficiary);
}
_balances[beneficiary].eth = _balances[beneficiary].eth.add(weiAmount);
_balances[beneficiary].tokens = _balances[beneficiary].tokens.add(tokenAmount);
}
// -----------------------------------------
// FUNDS INTERNAL
// -----------------------------------------
function _withdrawTokens(address beneficiary, uint256 amount) internal {
_token.transfer(beneficiary, amount);
emit TokensTransfered(beneficiary, amount);
}
function _withdrawEther(address payable beneficiary, uint256 amount) internal {
beneficiary.transfer(amount);
emit EthTransfered(beneficiary, amount);
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
/**
* @return the details of this crowdsale
*/
function getCrowdsaleDetails() public view returns (uint256, address, uint256, uint256, uint256[] memory finishTimestamps, uint256[] memory bonuses) {
finishTimestamps = new uint256[](_bonuses.length);
bonuses = new uint256[](_bonuses.length);
for (uint256 i = 0; i < _bonuses.length; i++) {
finishTimestamps[i] = _bonuses[i].finishTimestamp;
bonuses[i] = _bonuses[i].amount;
}
return (
_rate,
address(_token),
_minInvestmentAmount,
_fee,
finishTimestamps,
bonuses
);
}
/**
* @dev getInvestorBalances returns the eth/tokens balances of investor also withdrawn history of eth/tokens
*/
function getInvestorBalances(address investor) public view returns (uint256, uint256, uint256, uint256, bool) {
return (
_balances[investor].eth,
_balances[investor].tokens,
_balances[investor].withdrawnEth,
_balances[investor].withdrawnTokens,
_balances[investor].refunded
);
}
/**
* @dev getInvestorsArray returns the array of addresses of investors
*/
function getInvestorsArray() public view returns (address[] memory investors) {
uint256 investorsAmount = _investors.length;
investors = new address[](investorsAmount);
for (uint256 i = 0; i < investorsAmount; i++) {
investors[i] = _investors[i];
}
return investors;
}
/**
* @return the amount of wei raised.
*/
function getRaisedWei() public view returns (uint256) {
return _weiRaised;
}
/**
* @return the amount of tokens sold.
*/
function getSoldTokens() public view returns (uint256) {
return _tokensSold;
}
/**
* @dev isInvestor check if the address is investor or not
*/
function isInvestor(address sender) public view returns (bool) {
return _balances[sender].eth != 0 && _balances[sender].tokens != 0;
}
}
// File: contracts/TimedCrowdsale.sol
/**
* @title TimedCrowdsale
* @dev Crowdsale accepting contributions only within a time frame.
*/
contract TimedCrowdsale is Crowdsale {
uint256 private _openingTime;
uint256 private _closingTime;
/**
* @dev Reverts if not in crowdsale time range.
*/
modifier onlyWhileOpen() {
require(isOpen(), "onlyWhileOpen: investor can call this method only when crowdsale is open.");
_;
}
/**
* @dev Constructor, takes crowdsale opening and closing times.
* @param openingTime Crowdsale opening time
* @param closingTime Crowdsale closing time
*/
constructor (
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address payable operator,
uint256[] memory bonusFinishTimestamp,
uint256[] memory bonuses,
uint256 minInvestmentAmount,
uint256 fee
) internal Crowdsale(rate, token, operator, bonusFinishTimestamp, bonuses, minInvestmentAmount, fee) {
if (bonusFinishTimestamp.length > 0) {
require(bonusFinishTimestamp[0] >= openingTime, "TimedCrowdsale: the opening time is smaller then the first bonus timestamp.");
require(bonusFinishTimestamp[bonusFinishTimestamp.length - 1] <= closingTime, "TimedCrowdsale: the closing time is smaller then the last bonus timestamp.");
}
_openingTime = openingTime;
_closingTime = closingTime;
}
// -----------------------------------------
// INTERNAL
// -----------------------------------------
/**
* @dev Extend parent behavior requiring to be within contributing period
* @param beneficiary Token purchaser
* @param weiAmount Amount of wei contributed
*/
function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal onlyWhileOpen view {
super._preValidatePurchase(beneficiary, weiAmount);
}
// -----------------------------------------
// EXTERNAL
// -----------------------------------------
/**
* @dev refund the investments to investor while crowdsale is open
*/
function refundETH() external onlyWhileOpen {
require(isInvestor(msg.sender), "refundETH: only the active investors can call this method.");
uint256 weiAmount = _balances[msg.sender].eth;
uint256 tokensAmount = _balances[msg.sender].tokens;
_balances[msg.sender].eth = 0;
_balances[msg.sender].tokens = 0;
if (_balances[msg.sender].refunded == false) {
_balances[msg.sender].refunded = true;
}
_weiRaised = _weiRaised.sub(weiAmount);
_tokensSold = _tokensSold.sub(tokensAmount);
msg.sender.transfer(weiAmount);
emit Refunded(msg.sender, weiAmount);
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
/**
* @return the crowdsale opening time.
*/
function getOpeningTime() public view returns (uint256) {
return _openingTime;
}
/**
* @return the crowdsale closing time.
*/
function getClosingTime() public view returns (uint256) {
return _closingTime;
}
/**
* @return true if the crowdsale is open, false otherwise.
*/
function isOpen() public view returns (bool) {
return block.timestamp >= _openingTime && block.timestamp <= _closingTime;
}
/**
* @dev Checks whether the period in which the crowdsale is open has already elapsed.
* @return Whether crowdsale period has elapsed
*/
function hasClosed() public view returns (bool) {
return block.timestamp > _closingTime;
}
}
// File: contracts/ResponsibleCrowdsale.sol
/**
* @title ResponsibleCrowdsale
* @dev Main crowdsale contract
*/
contract ResponsibleCrowdsale is TimedCrowdsale {
uint256 private _cycleId;
uint256 private _milestoneId;
uint256 private constant _timeForDisputs = 3 days;
uint256 private _allCyclesTokensPercent;
uint256 private _allCyclesEthPercent;
bool private _operatorTransferedTokens;
enum MilestoneStatus { PENDING, DISPUTS_PERIOD, APPROVED }
enum InvestorDisputeState { NO_DISPUTES, SUBMITTED, CLOSED, WINNED }
struct Cycle {
uint256 tokenPercent;
uint256 ethPercent;
bytes32[] milestones;
}
struct Dispute {
uint256 activeDisputes;
address[] winnedAddressList;
mapping (address => InvestorDisputeState) investorDispute;
}
struct Milestone {
bytes32 name;
uint256 startTimestamp;
uint256 disputesOpeningTimestamp;
uint256 cycleId;
uint256 tokenPercent;
uint256 ethPercent;
Dispute disputes;
bool operatorWasWithdrawn;
bool validHash;
mapping (address => bool) userWasWithdrawn;
}
// Mapping of circes by id
mapping (uint256 => Cycle) private _cycles;
// Mapping of milestones with order
mapping (uint256 => bytes32) private _milestones;
// Get detail of each milestone by Hash
mapping (bytes32 => Milestone) private _milestoneDetails;
event MilestoneInvestmentsWithdrawn(bytes32 indexed milestoneHash, uint256 weiAmount, uint256 tokensAmount);
event MilestoneResultWithdrawn(bytes32 indexed milestoneHash, address indexed investor, uint256 weiAmount, uint256 tokensAmount);
constructor (
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address payable operator,
uint256[] memory bonusFinishTimestamp,
uint256[] memory bonuses,
uint256 minInvestmentAmount,
uint256 fee
)
public TimedCrowdsale(rate, token, openingTime, closingTime, operator, bonusFinishTimestamp, bonuses, minInvestmentAmount, fee)
{}
// -----------------------------------------
// OPERATOR FEATURES
// -----------------------------------------
function addCycle(
uint256 tokenPercent,
uint256 ethPercent,
bytes32[] memory milestonesNames,
uint256[] memory milestonesTokenPercent,
uint256[] memory milestonesEthPercent,
uint256[] memory milestonesStartTimestamps
)
public onlyOperator returns (bool)
{
// Checking incoming values
require(tokenPercent > 0 && tokenPercent <= 100, "addCycle: the Token percent of the cycle should be bigger then 0 and smaller then 100.");
require(ethPercent > 0 && ethPercent <= 100, "addCycle: the ETH percent of the cycle should be bigger then 0 and smaller then 100.");
require(milestonesNames.length > 0, "addCycle: the milestones length should be bigger than 0.");
require(milestonesTokenPercent.length == milestonesNames.length, "addCycle: the milestonesTokenPercent length should be equal to milestonesNames length.");
require(milestonesEthPercent.length == milestonesTokenPercent.length, "addCycle: the milestonesEthPercent length should be equal to milestonesTokenPercent length.");
require(milestonesStartTimestamps.length == milestonesEthPercent.length, "addCycle: the milestonesFinishTimestamps length should be equal to milestonesEthPercent length.");
// Checking the calculated amount of percentages of all cycles
require(_allCyclesTokensPercent + tokenPercent <= 100, "addCycle: the calculated amount of token percents is bigger then 100.");
require(_allCyclesEthPercent + ethPercent <= 100, "addCycle: the calculated amount of eth percents is bigger then 100.");
_cycles[_cycleId] = Cycle(
tokenPercent,
ethPercent,
new bytes32[](0)
);
uint256 allMilestonesTokensPercent;
uint256 allMilestonesEthPercent;
for (uint256 i = 0; i < milestonesNames.length; i++) {
// checking if the percentages (token/eth) in this milestones is bigger then 0 and smaller/equal to 100
require(milestonesTokenPercent[i] > 0 && milestonesTokenPercent[i] <= 100, "addCycle: the token percent of milestone should be bigger then 0 and smaller from 100.");
require(milestonesEthPercent[i] > 0 && milestonesEthPercent[i] <= 100, "addCycle: the ETH percent of milestone should be bigger then 0 and smaller from 100.");
if (i == 0 && _milestoneId == 0) {
// checking the first milestone of the first cycle
require(milestonesStartTimestamps[i] > getClosingTime(), "addCycle: the first milestone timestamp should be bigger then crowdsale closing time.");
require(milestonesTokenPercent[i] <= 25 && milestonesEthPercent[i] <= 25, "addCycle: for security reasons for the first milestone the operator can withdraw only less than 25 percent of investments.");
} else if (i == 0 && _milestoneId > 0) {
// checking if the first milestone starts after the last milestone of the previous cycle
uint256 previousCycleLastMilestoneStartTimestamp = _milestoneDetails[_milestones[_milestoneId - 1]].startTimestamp;
require(milestonesStartTimestamps[i] > previousCycleLastMilestoneStartTimestamp, "addCycle: the first timestamp of this milestone should be bigger then his previus milestons last timestamp.");
require(milestonesStartTimestamps[i] >= block.timestamp + _timeForDisputs, "addCycle: the second cycle should be start a minimum 3 days after this transaction.");
} else if (i != 0) {
// checking if the each next milestone finish timestamp is bigger than his previous one finish timestamp
require(milestonesStartTimestamps[i] > milestonesStartTimestamps[i - 1], "addCycle: each timestamp should be bigger then his previus one.");
}
// generating the unique hash for each milestone
bytes32 hash = _generateHash(
milestonesNames[i],
milestonesStartTimestamps[i]
);
// before starting the next milestone investors can open disputes within 3 days
uint256 disputesOpeningTimestamp = milestonesStartTimestamps[i] - _timeForDisputs;
// The first milestone of the first cycle can not have disputes
if (i == 0 && _milestoneId == 0) {
disputesOpeningTimestamp = milestonesStartTimestamps[i];
}
// updating the state
_cycles[_cycleId].milestones.push(hash);
_milestones[i + _milestoneId] = hash;
_milestoneDetails[hash] = Milestone(
milestonesNames[i], // Milestone name
milestonesStartTimestamps[i], // Milestone finish timestamp
disputesOpeningTimestamp, // Miliestone submit timestamp (it will be updated once when operator calls the submit milestone method)
_cycleId, // cycle Id for detecting token and eth percent for this cycle
milestonesTokenPercent[i], // Token percent of this milestone
milestonesEthPercent[i], // ETH percent of this milestone
Dispute(0, new address[](0)), // Disputs state initialization
false, // Operator does not withdrawn this milestone investments yet
true // Milestone hash is valid
);
allMilestonesTokensPercent += milestonesTokenPercent[i];
allMilestonesEthPercent += milestonesEthPercent[i];
}
// checking if the calculated amount of all percentages (token/eth) in this milestones equal to 100
require(allMilestonesTokensPercent == 100, "addCycle: the calculated amount of Token percent should be equal to 100.");
require(allMilestonesEthPercent == 100, "addCycle: the calculated amount of ETH percent should be equal to 100.");
_allCyclesTokensPercent += tokenPercent;
_allCyclesEthPercent += ethPercent;
_cycleId++;
_milestoneId += milestonesNames.length;
return true;
}
function collectMilestoneInvestment(bytes32 hash) public onlyOperator {
require(_milestoneDetails[hash].validHash, "collectMilestoneInvestment: the milestone hash is not valid.");
require(_milestoneDetails[hash].operatorWasWithdrawn == false, "collectMilestoneInvestment: the operator already withdrawn his funds.");
require(getMilestoneStatus(hash) == MilestoneStatus.APPROVED, "collectMilestoneInvestment: the time for collecting funds is not started yet.");
require(isMilestoneHasActiveDisputes(hash) == false, "collectMilestoneInvestment: the milestone has unsolved disputes.");
require(_hadOperatorTransferredTokens(), "collectMilestoneInvestment: the operator need to transfer sold tokens to this contract for receiving investments.");
_milestoneDetails[hash].operatorWasWithdrawn = true;
uint256 milestoneRefundedTokens;
uint256 milestoneInvestmentWei = _calculateEthAmountByMilestone(getRaisedWei(), hash);
uint256 winnedDisputesAmount = _milestoneDetails[hash].disputes.winnedAddressList.length;
if (winnedDisputesAmount > 0) {
for (uint256 i = 0; i < winnedDisputesAmount; i++) {
address winnedInvestor = _milestoneDetails[hash].disputes.winnedAddressList[i];
uint256 investorWeiForMilestone = _calculateEthAmountByMilestone(_balances[winnedInvestor].eth, hash);
uint256 investorTokensForMilestone = _calculateTokensAmountByMilestone(_balances[winnedInvestor].tokens, hash);
milestoneInvestmentWei = milestoneInvestmentWei.sub(investorWeiForMilestone);
milestoneRefundedTokens = milestoneRefundedTokens.add(investorTokensForMilestone);
}
}
_withdrawEther(operator(), milestoneInvestmentWei);
if (milestoneRefundedTokens != 0) {
_withdrawTokens(operator(), milestoneRefundedTokens);
}
emit MilestoneInvestmentsWithdrawn(hash, milestoneInvestmentWei, milestoneRefundedTokens);
}
// -----------------------------------------
// DISPUTS FEATURES
// -----------------------------------------
function openDispute(bytes32 hash, address investor) external onlyCluster returns (bool) {
_milestoneDetails[hash].disputes.investorDispute[investor] = InvestorDisputeState.SUBMITTED;
_milestoneDetails[hash].disputes.activeDisputes++;
return true;
}
function solveDispute(bytes32 hash, address investor, bool investorWins) external onlyCluster {
require(isMilestoneHasActiveDisputes(hash) == true, "solveDispute: no active disputs available.");
if (investorWins == true) {
_milestoneDetails[hash].disputes.investorDispute[investor] = InvestorDisputeState.WINNED;
_milestoneDetails[hash].disputes.winnedAddressList.push(investor);
} else {
_milestoneDetails[hash].disputes.investorDispute[investor] = InvestorDisputeState.CLOSED;
}
_milestoneDetails[hash].disputes.activeDisputes--;
}
// -----------------------------------------
// INVESTOR FEATURES
// -----------------------------------------
function collectMilestoneResult(bytes32 hash) public {
require(isInvestor(msg.sender), "collectMilestoneResult: only the active investors can call this method.");
require(_milestoneDetails[hash].validHash, "collectMilestoneResult: the milestone hash is not valid.");
require(getMilestoneStatus(hash) == MilestoneStatus.APPROVED, "collectMilestoneResult: the time for collecting funds is not started yet.");
require(didInvestorWithdraw(hash, msg.sender) == false, "collectMilestoneResult: the investor already withdrawn his tokens.");
require(_milestoneDetails[hash].disputes.investorDispute[msg.sender] != InvestorDisputeState.SUBMITTED, "collectMilestoneResult: the investor has unsolved disputes.");
require(_hadOperatorTransferredTokens(), "collectMilestoneInvestment: the operator need to transfer sold tokens to this contract for receiving investments.");
_milestoneDetails[hash].userWasWithdrawn[msg.sender] = true;
uint256 investorBalance;
uint256 tokensToSend;
uint256 winnedWeis;
if (_milestoneDetails[hash].disputes.investorDispute[msg.sender] != InvestorDisputeState.WINNED) {
investorBalance = _balances[msg.sender].tokens;
tokensToSend = _calculateTokensAmountByMilestone(investorBalance, hash);
// transfering tokens to investor
_withdrawTokens(msg.sender, tokensToSend);
_balances[msg.sender].withdrawnTokens += tokensToSend;
} else {
investorBalance = _balances[msg.sender].eth;
winnedWeis = _calculateEthAmountByMilestone(investorBalance, hash);
// transfering disputs ETH investor
_withdrawEther(msg.sender, winnedWeis);
_balances[msg.sender].withdrawnEth += winnedWeis;
}
emit MilestoneResultWithdrawn(hash, msg.sender, winnedWeis, tokensToSend);
}
// -----------------------------------------
// INTERNAL
// -----------------------------------------
function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
require(_cycleId > 0, "_preValidatePurchase: the cycles/milestones is not setted.");
super._preValidatePurchase(beneficiary, weiAmount);
}
function _generateHash(bytes32 name, uint256 timestamp) private view returns (bytes32) {
// generating the unique hash for milestone using the name, start timestamp and the address of this crowdsale
return keccak256(abi.encodePacked(name, timestamp, address(this)));
}
function _calculateEthAmountByMilestone(uint256 weiAmount, bytes32 milestone) private view returns (uint256) {
uint256 cycleId = _milestoneDetails[milestone].cycleId;
uint256 cyclePercent = _cycles[cycleId].ethPercent;
uint256 milestonePercent = _milestoneDetails[milestone].ethPercent;
uint256 amount = _calculatePercent(milestonePercent, _calculatePercent(weiAmount, cyclePercent));
return amount;
}
function _calculateTokensAmountByMilestone(uint256 tokens, bytes32 milestone) private view returns (uint256) {
uint256 cycleId = _milestoneDetails[milestone].cycleId;
uint256 cyclePercent = _cycles[cycleId].tokenPercent;
uint256 milestonePercent = _milestoneDetails[milestone].tokenPercent;
uint256 amount = _calculatePercent(milestonePercent, _calculatePercent(tokens, cyclePercent));
return amount;
}
function _hadOperatorTransferredTokens() private returns (bool) {
// the first time when the investor/operator want to withdraw the funds
if (_token.balanceOf(address(this)) == getSoldTokens()) {
_operatorTransferedTokens = true;
return true;
} else if (_operatorTransferedTokens == true) {
return true;
} else {
return false;
}
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
function getCyclesAmount() external view returns (uint256) {
return _cycleId;
}
function getCycleDetails(uint256 cycleId) external view returns (uint256, uint256, bytes32[] memory) {
return (
_cycles[cycleId].tokenPercent,
_cycles[cycleId].ethPercent,
_cycles[cycleId].milestones
);
}
function getMilestonesHashes() external view returns (bytes32[] memory milestonesHashArray) {
milestonesHashArray = new bytes32[](_milestoneId);
for (uint256 i = 0; i < _milestoneId; i++) {
milestonesHashArray[i] = _milestones[i];
}
return milestonesHashArray;
}
function getMilestoneDetails(bytes32 hash) external view returns (bytes32, uint256, uint256, uint256, uint256, uint256, uint256, MilestoneStatus status) {
Milestone memory mil = _milestoneDetails[hash];
status = getMilestoneStatus(hash);
return (
mil.name,
mil.startTimestamp,
mil.disputesOpeningTimestamp,
mil.cycleId,
mil.tokenPercent,
mil.ethPercent,
mil.disputes.activeDisputes,
status
);
}
function getMilestoneStatus(bytes32 hash) public view returns (MilestoneStatus status) {
// checking if the time for collecting milestone funds was comes
if (block.timestamp >= _milestoneDetails[hash].startTimestamp) {
return MilestoneStatus.APPROVED;
} else if (block.timestamp > _milestoneDetails[hash].disputesOpeningTimestamp) {
return MilestoneStatus.DISPUTS_PERIOD;
} else {
return MilestoneStatus.PENDING;
}
}
function getCycleTotalPercents() external view returns (uint256, uint256) {
return (
_allCyclesTokensPercent,
_allCyclesEthPercent
);
}
function canInvestorOpenNewDispute(bytes32 hash, address investor) public view returns (bool) {
InvestorDisputeState state = _milestoneDetails[hash].disputes.investorDispute[investor];
return state == InvestorDisputeState.NO_DISPUTES || state == InvestorDisputeState.CLOSED;
}
function isMilestoneHasActiveDisputes(bytes32 hash) public view returns (bool) {
return _milestoneDetails[hash].disputes.activeDisputes > 0;
}
function didInvestorOpenedDisputeBefore(bytes32 hash, address investor) public view returns (bool) {
return _milestoneDetails[hash].disputes.investorDispute[investor] != InvestorDisputeState.NO_DISPUTES;
}
function didInvestorWithdraw(bytes32 hash, address investor) public view returns (bool) {
return _milestoneDetails[hash].userWasWithdrawn[investor];
}
}
// File: contracts/deployers/CrowdsaleDeployer.sol
library CrowdsaleDeployer {
function addCrowdsale(
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address payable operator,
uint256[] calldata bonusFinishTimestamp,
uint256[] calldata bonuses,
uint256 minInvestmentAmount,
uint256 fee
) external returns (address) {
return address(new ResponsibleCrowdsale(rate, token, openingTime, closingTime, operator, bonusFinishTimestamp, bonuses, minInvestmentAmount, fee));
}
}
// ---------------------------------------------------------------------------
// ARBITERS POOL
// ---------------------------------------------------------------------------
// File: contracts/ownerships/Roles.sol
library Roles {
struct Role {
mapping (address => bool) bearer;
}
/**
* @dev give an account access to this role
*/
function add(Role storage role, address account) internal {
require(account != address(0));
require(!has(role, account));
role.bearer[account] = true;
}
/**
* @dev remove an account's access to this role
*/
function remove(Role storage role, address account) internal {
require(account != address(0));
require(has(role, account));
role.bearer[account] = false;
}
/**
* @dev check if an account has this role
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0));
return role.bearer[account];
}
}
// File: contracts/ownerships/ArbiterRole.sol
contract ArbiterRole is ClusterRole {
using Roles for Roles.Role;
uint256 private _arbitersAmount;
event ArbiterAdded(address indexed arbiter);
event ArbiterRemoved(address indexed arbiter);
Roles.Role private _arbiters;
modifier onlyArbiter() {
require(isArbiter(msg.sender), "onlyArbiter: only arbiter can call this method.");
_;
}
// -----------------------------------------
// EXTERNAL
// -----------------------------------------
function addArbiter(address arbiter) public onlyCluster {
_addArbiter(arbiter);
_arbitersAmount++;
}
function removeArbiter(address arbiter) public onlyCluster {
_removeArbiter(arbiter);
_arbitersAmount--;
}
// -----------------------------------------
// INTERNAL
// -----------------------------------------
function _addArbiter(address arbiter) private {
_arbiters.add(arbiter);
emit ArbiterAdded(arbiter);
}
function _removeArbiter(address arbiter) private {
_arbiters.remove(arbiter);
emit ArbiterRemoved(arbiter);
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
function isArbiter(address account) public view returns (bool) {
return _arbiters.has(account);
}
function getArbitersAmount() external view returns (uint256) {
return _arbitersAmount;
}
}
// File: contracts/interfaces/ICluster.sol
interface ICluster {
function withdrawEth() external;
function addArbiter(address newArbiter) external;
function removeArbiter(address arbiter) external;
function addCrowdsale(
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address payable operator,
uint256[] calldata bonusFinishTimestamp,
uint256[] calldata bonuses,
uint256 minInvestmentAmount,
uint256 fee
) external returns (address);
function emergencyExit(address crowdsale, address payable newContract) external;
function openDispute(address crowdsale, bytes32 hash, string calldata reason) external payable returns (uint256);
function solveDispute(address crowdsale, bytes32 hash, address investor, bool investorWins) external;
function getArbitersPoolAddress() external view returns (address);
function getAllCrowdsalesAddresses() external view returns (address[] memory crowdsales);
function getCrowdsaleMilestones(address crowdsale) external view returns(bytes32[] memory milestonesHashArray);
function getOperatorCrowdsaleAddresses(address operator) external view returns (address[] memory crowdsales);
function owner() external view returns (address payable);
function isOwner() external view returns (bool);
function transferOwnership(address payable newOwner) external;
function isBackEnd(address account) external view returns (bool);
function addBackEnd(address account) external;
function removeBackEnd(address account) external;
}
// File: contracts/ArbitersPool.sol
contract ArbitersPool is ArbiterRole {
uint256 private _disputsAmount;
uint256 private constant _necessaryVoices = 3;
enum DisputeStatus { WAITING, SOLVED }
enum Choice { OPERATOR_WINS, INVESTOR_WINS }
ICluster private _clusterInterface;
struct Vote {
address arbiter;
Choice choice;
}
struct Dispute {
address investor;
address crowdsale;
bytes32 milestoneHash;
string reason;
uint256 votesAmount;
DisputeStatus status;
mapping (address => bool) hasVoted;
mapping (uint256 => Vote) choices;
}
mapping (uint256 => Dispute) private _disputesById;
mapping (address => uint256[]) private _disputesByInvestor;
mapping (bytes32 => uint256[]) private _disputesByMilestone;
event Voted(uint256 indexed disputeId, address indexed arbiter, Choice choice);
event NewDisputeCreated(uint256 disputeId, address indexed crowdsale, bytes32 indexed hash, address indexed investor);
event DisputeSolved(uint256 disputeId, Choice choice, address indexed crowdsale, bytes32 indexed hash, address indexed investor);
constructor () public {
_clusterInterface = ICluster(msg.sender);
}
function createDispute(bytes32 milestoneHash, address crowdsale, address investor, string calldata reason) external onlyCluster returns (uint256) {
Dispute memory dispute = Dispute(
investor,
crowdsale,
milestoneHash,
reason,
0,
DisputeStatus.WAITING
);
uint256 thisDisputeId = _disputsAmount;
_disputsAmount++;
_disputesById[thisDisputeId] = dispute;
_disputesByMilestone[milestoneHash].push(thisDisputeId);
_disputesByInvestor[investor].push(thisDisputeId);
emit NewDisputeCreated(thisDisputeId, crowdsale, milestoneHash, investor);
return thisDisputeId;
}
function voteDispute(uint256 id, Choice choice) public onlyArbiter {
require(_disputsAmount > id, "voteDispute: invalid number of dispute.");
require(_disputesById[id].hasVoted[msg.sender] == false, "voteDispute: arbiter was already voted.");
require(_disputesById[id].status == DisputeStatus.WAITING, "voteDispute: dispute was already closed.");
require(_disputesById[id].votesAmount < _necessaryVoices, "voteDispute: dispute was already voted and finished.");
_disputesById[id].hasVoted[msg.sender] = true;
// updating the votes amount
_disputesById[id].votesAmount++;
// storing info about this vote
uint256 votesAmount = _disputesById[id].votesAmount;
_disputesById[id].choices[votesAmount] = Vote(msg.sender, choice);
// checking, if the second arbiter voted the same result with the 1st voted arbiter, then dispute will be solved without 3rd vote
if (_disputesById[id].votesAmount == 2 && _disputesById[id].choices[0].choice == choice) {
_executeDispute(id, choice);
} else if (_disputesById[id].votesAmount == _necessaryVoices) {
Choice winner = _calculateWinner(id);
_executeDispute(id, winner);
}
emit Voted(id, msg.sender, choice);
}
// -----------------------------------------
// INTERNAL
// -----------------------------------------
function _calculateWinner(uint256 id) private view returns (Choice choice) {
uint256 votesForInvestor = 0;
for (uint256 i = 0; i < _necessaryVoices; i++) {
if (_disputesById[id].choices[i].choice == Choice.INVESTOR_WINS) {
votesForInvestor++;
}
}
return votesForInvestor >= 2 ? Choice.INVESTOR_WINS : Choice.OPERATOR_WINS;
}
function _executeDispute(uint256 id, Choice choice) private {
_disputesById[id].status = DisputeStatus.SOLVED;
_clusterInterface.solveDispute(
_disputesById[id].crowdsale,
_disputesById[id].milestoneHash,
_disputesById[id].investor,
choice == Choice.INVESTOR_WINS
);
emit DisputeSolved(
id,
choice,
_disputesById[id].crowdsale,
_disputesById[id].milestoneHash,
_disputesById[id].investor
);
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
function getDisputesAmount() external view returns (uint256) {
return _disputsAmount;
}
function getDisputeDetails(uint256 id) external view returns (bytes32, address, address, string memory, uint256, DisputeStatus status) {
Dispute memory dispute = _disputesById[id];
return (
dispute.milestoneHash,
dispute.crowdsale,
dispute.investor,
dispute.reason,
dispute.votesAmount,
dispute.status
);
}
function getMilestoneDisputes(bytes32 hash) external view returns (uint256[] memory disputesIDs) {
uint256 disputesLength = _disputesByMilestone[hash].length;
disputesIDs = new uint256[](disputesLength);
for (uint256 i = 0; i < disputesLength; i++) {
disputesIDs[i] = _disputesByMilestone[hash][i];
}
return disputesIDs;
}
function getInvestorDisputes(address investor) external view returns (uint256[] memory disputesIDs) {
uint256 disputesLength = _disputesByInvestor[investor].length;
disputesIDs = new uint256[](disputesLength);
for (uint256 i = 0; i < disputesLength; i++) {
disputesIDs[i] = _disputesByInvestor[investor][i];
}
return disputesIDs;
}
function getDisputeVotes(uint256 id) external view returns(address[] memory arbiters, Choice[] memory choices) {
uint256 votedArbitersAmount = _disputesById[id].votesAmount;
arbiters = new address[](votedArbitersAmount);
choices = new Choice[](votedArbitersAmount);
for (uint256 i = 0; i < votedArbitersAmount; i++) {
arbiters[i] = _disputesById[id].choices[i].arbiter;
choices[i] = _disputesById[id].choices[i].choice;
}
return (
arbiters,
choices
);
}
function hasDisputeSolved(uint256 id) external view returns (bool) {
return _disputesById[id].status == DisputeStatus.SOLVED;
}
function hasArbiterVoted(uint256 id, address arbiter) external view returns (bool) {
return _disputesById[id].hasVoted[arbiter];
}
}
// ---------------------------------------------------------------------------
// CLUSTER CONTRACT
// ---------------------------------------------------------------------------
// File: contracts/interfaces/IArbitersPool.sol
interface IArbitersPool {
enum DisputeStatus { WAITING, SOLVED }
enum Choice { OPERATOR_WINS, INVESTOR_WINS }
function createDispute(bytes32 milestoneHash, address crowdsale, address investor, string calldata reason) external returns (uint256);
function voteDispute(uint256 id, Choice choice) external;
function addArbiter(address newArbiter) external;
function renounceArbiter(address arbiter) external;
function getDisputesAmount() external view returns (uint256);
function getDisputeDetails(uint256 id) external view returns (bytes32, address, address, string memory, uint256, DisputeStatus status);
function getMilestoneDisputes(bytes32 hash) external view returns (uint256[] memory disputesIDs);
function getInvestorDisputes(address investor) external view returns (uint256[] memory disputesIDs);
function getDisputeVotes(uint256 id) external view returns(address[] memory arbiters, Choice[] memory choices);
function getArbitersAmount() external view returns (uint256);
function isArbiter(address account) external view returns (bool);
function hasDisputeSolved(uint256 id) external view returns (bool);
function hasArbiterVoted(uint256 id, address arbiter) external view returns (bool);
function cluster() external view returns (address payable);
function isCluster() external view returns (bool);
}
// File: contracts/interfaces/IRICO.sol
interface IRICO {
enum MilestoneStatus { PENDING, DISPUTS_PERIOD, APPROVED }
function addCycle(
uint256 tokenPercent,
uint256 ethPercent,
bytes32[] calldata milestonesNames,
uint256[] calldata milestonesTokenPercent,
uint256[] calldata milestonesEthPercent,
uint256[] calldata milestonesStartTimestamps
) external returns (bool);
function collectMilestoneInvestment(bytes32 hash) external;
function collectMilestoneResult(bytes32 hash) external;
function getCyclesAmount() external view returns (uint256);
function getCycleDetails(uint256 cycleId) external view returns (uint256, uint256, bytes32[] memory);
function getMilestonesHashes() external view returns (bytes32[] memory milestonesHashArray);
function getMilestoneDetails(bytes32 hash) external view returns (bytes32, uint256, uint256, uint256, uint256, uint256, uint256, MilestoneStatus status);
function getMilestoneStatus(bytes32 hash) external view returns (MilestoneStatus status);
function getCycleTotalPercents() external view returns (uint256, uint256);
function canInvestorOpenNewDispute(bytes32 hash, address investor) external view returns (bool);
function isMilestoneHasActiveDisputes(bytes32 hash) external view returns (bool);
function didInvestorOpenedDisputeBefore(bytes32 hash, address investor) external view returns (bool);
function didInvestorWithdraw(bytes32 hash, address investor) external view returns (bool);
function buyTokens(address beneficiary) external payable;
function isInvestor(address sender) external view returns (bool);
function openDispute(bytes32 hash, address investor) external returns (bool);
function solveDispute(bytes32 hash, address investor, bool investorWins) external;
function emergencyExit(address payable newContract) external;
function getCrowdsaleDetails() external view returns (uint256, address, uint256, uint256, uint256[] memory finishTimestamps, uint256[] memory bonuses);
function getInvestorBalances(address investor) external view returns (uint256, uint256, uint256, uint256, bool);
function getInvestorsArray() external view returns (address[] memory investors);
function getRaisedWei() external view returns (uint256);
function getSoldTokens() external view returns (uint256);
function refundETH() external;
function getOpeningTime() external view returns (uint256);
function getClosingTime() external view returns (uint256);
function isOpen() external view returns (bool);
function hasClosed() external view returns (bool);
function cluster() external view returns (address payable);
function isCluster() external view returns (bool);
function operator() external view returns (address payable);
function isOperator() external view returns (bool);
}
// File: contracts/ownerships/Ownable.sol
contract Ownable {
address payable private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address payable) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "onlyOwner: only the owner can call this method.");
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return 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 payable newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address payable newOwner) private {
require(newOwner != address(0), "_transferOwnership: the address of new operator is not valid.");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: contracts/ownerships/BackEndRole.sol
contract BackEndRole is Ownable {
using Roles for Roles.Role;
event BackEndAdded(address indexed account);
event BackEndRemoved(address indexed account);
Roles.Role private _backEnds;
modifier onlyBackEnd() {
require(isBackEnd(msg.sender), "onlyBackEnd: only back end address can call this method.");
_;
}
function isBackEnd(address account) public view returns (bool) {
return _backEnds.has(account);
}
function addBackEnd(address account) public onlyOwner {
_addBackEnd(account);
}
function removeBackEnd(address account) public onlyOwner {
_removeBackEnd(account);
}
function _addBackEnd(address account) private {
_backEnds.add(account);
emit BackEndAdded(account);
}
function _removeBackEnd(address account) private {
_backEnds.remove(account);
emit BackEndRemoved(account);
}
}
// File: contracts/Cluster.sol
contract Cluster is BackEndRole {
uint256 private constant _feeForMoreDisputes = 1 ether;
address private _arbitersPoolAddress;
address[] private _crowdsales;
mapping (address => address[]) private _operatorsContracts;
IArbitersPool private _arbitersPool;
event WeiFunded(address indexed sender, uint256 indexed amount);
event CrowdsaleCreated(
address crowdsale,
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address operator,
uint256[] bonusFinishTimestamp,
uint256[] bonuses,
uint256 minInvestmentAmount,
uint256 fee
);
// -----------------------------------------
// CONSTRUCTOR
// -----------------------------------------
constructor () public {
_arbitersPoolAddress = address(new ArbitersPool());
_arbitersPool = IArbitersPool(_arbitersPoolAddress);
}
function() external payable {
emit WeiFunded(msg.sender, msg.value);
}
// -----------------------------------------
// OWNER FEATURES
// -----------------------------------------
function withdrawEth() external onlyOwner {
owner().transfer(address(this).balance);
}
function addArbiter(address newArbiter) external onlyBackEnd {
require(newArbiter != address(0), "addArbiter: invalid type of address.");
_arbitersPool.addArbiter(newArbiter);
}
function removeArbiter(address arbiter) external onlyBackEnd {
require(arbiter != address(0), "removeArbiter: invalid type of address.");
_arbitersPool.renounceArbiter(arbiter);
}
function addCrowdsale(
uint256 rate,
address token,
uint256 openingTime,
uint256 closingTime,
address payable operator,
uint256[] calldata bonusFinishTimestamp,
uint256[] calldata bonuses,
uint256 minInvestmentAmount,
uint256 fee
) external onlyBackEnd returns (address) {
require(rate != 0, "addCrowdsale: the rate should be bigger then 0.");
require(token != address(0), "addCrowdsale: invalid token address.");
require(openingTime >= block.timestamp, "addCrowdsale: invalid opening time.");
require(closingTime > openingTime, "addCrowdsale: invalid closing time.");
require(operator != address(0), "addCrowdsale: the address of operator is not valid.");
require(bonusFinishTimestamp.length == bonuses.length, "addCrowdsale: the length of bonusFinishTimestamp and bonuses is not equal.");
address crowdsale = CrowdsaleDeployer.addCrowdsale(
rate,
token,
openingTime,
closingTime,
operator,
bonusFinishTimestamp,
bonuses,
minInvestmentAmount,
fee
);
// Updating the state
_crowdsales.push(crowdsale);
_operatorsContracts[operator].push(crowdsale);
emit CrowdsaleCreated(
crowdsale,
rate,
token,
openingTime,
closingTime,
operator,
bonusFinishTimestamp,
bonuses,
minInvestmentAmount,
fee
);
return crowdsale;
}
// -----------------------------------------
// OPERATOR FEATURES
// -----------------------------------------
function emergencyExit(address crowdsale, address payable newContract) external onlyOwner {
IRICO(crowdsale).emergencyExit(newContract);
}
// -----------------------------------------
// INVESTOR FEATURES
// -----------------------------------------
function openDispute(address crowdsale, bytes32 hash, string calldata reason) external payable returns (uint256) {
require(IRICO(crowdsale).isInvestor(msg.sender) == true, "openDispute: sender is not an investor.");
require(IRICO(crowdsale).canInvestorOpenNewDispute(hash, msg.sender) == true, "openDispute: investor cannot open a new dispute.");
require(IRICO(crowdsale).getMilestoneStatus(hash) == IRICO.MilestoneStatus.DISPUTS_PERIOD, "openDispute: the period for opening new disputes was finished.");
if (IRICO(crowdsale).didInvestorOpenedDisputeBefore(hash, msg.sender) == true) {
require(msg.value == _feeForMoreDisputes, "openDispute: for the second and other disputes investor need to pay 1 ETH fee.");
}
IRICO(crowdsale).openDispute(hash, msg.sender);
uint256 disputeID = _arbitersPool.createDispute(hash, crowdsale, msg.sender, reason);
return disputeID;
}
// -----------------------------------------
// ARBITERSPOOL FEATURES
// -----------------------------------------
function solveDispute(address crowdsale, bytes32 hash, address investor, bool investorWins) external {
require(msg.sender == _arbitersPoolAddress, "solveDispute: the sender is not arbiters pool contract.");
IRICO(crowdsale).solveDispute(hash, investor, investorWins);
}
// -----------------------------------------
// GETTERS
// -----------------------------------------
function getArbitersPoolAddress() external view returns (address) {
return _arbitersPoolAddress;
}
function getAllCrowdsalesAddresses() external view returns (address[] memory crowdsales) {
crowdsales = new address[](_crowdsales.length);
for (uint256 i = 0; i < _crowdsales.length; i++) {
crowdsales[i] = _crowdsales[i];
}
return crowdsales;
}
function getCrowdsaleMilestones(address crowdsale) external view returns(bytes32[] memory milestonesHashArray) {
return IRICO(crowdsale).getMilestonesHashes();
}
function getOperatorCrowdsaleAddresses(address operator) external view returns (address[] memory crowdsales) {
crowdsales = new address[](_operatorsContracts[operator].length);
for (uint256 i = 0; i < _operatorsContracts[operator].length; i++) {
crowdsales[i] = _operatorsContracts[operator][i];
}
return crowdsales;
}
}