-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7c34886
commit 4940161
Showing
3 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
pragma solidity ^0.4.18; | ||
|
||
import 'common/SafeMath.sol'; | ||
import 'common/Buyable.sol'; | ||
import 'common/token/ERC20.sol'; | ||
import 'common/ownership/Ownable.sol'; | ||
|
||
/// @title SilentNotary token sale contract | ||
contract SilentNotaryTokenSale is Ownable, SafeMath, Buyable { | ||
|
||
/// State machine | ||
/// Preparing: Waiting for ICO start | ||
/// Selling: Active sale | ||
/// ProlongedSelling: Prolonged active sale | ||
/// TokenShortage: ICO period isn't over yet, but there are no tokens on the contract | ||
/// Finished: ICO has finished | ||
enum Status {Unknown, Preparing, Selling, ProlongedSelling, TokenShortage, Finished} | ||
|
||
/// A new investment was made | ||
event Invested(address investor, uint weiAmount, uint tokenAmount); | ||
|
||
/// Contract owner withdrew some tokens to team wallet | ||
event Withdraw(uint tokenAmount); | ||
|
||
/// Token unit price changed | ||
event TokenPriceChanged(uint newTokenPrice); | ||
|
||
/// SNTR token address | ||
ERC20 public token; | ||
|
||
/// wallet address to transfer invested ETH | ||
address public ethMultisigWallet; | ||
|
||
/// wallet address to withdraw unused tokens | ||
address public tokenMultisigWallet; | ||
|
||
/// ICO start time | ||
uint public startTime; | ||
|
||
/// ICO duration in seconds | ||
uint public duration; | ||
|
||
/// Prolonged ICO duration in seconds, 0 if no prolongation is planned | ||
uint public prolongedDuration; | ||
|
||
/// Token price in wei | ||
uint public tokenPrice; | ||
|
||
/// Minimal investment amount in wei | ||
uint public minInvestment; | ||
|
||
/// List of addresses allowed to send ETH to this contract, empty if anyone is allowed | ||
address[] public allowedSenders; | ||
|
||
/// The number of tokens already sold through this contract | ||
uint public tokensSoldAmount = 0; | ||
|
||
/// How many wei of funding we have raised | ||
uint public weiRaisedAmount = 0; | ||
|
||
/// How many distinct addresses have invested | ||
uint public investorCount = 0; | ||
|
||
/// Was prolongation permitted by owner or not | ||
bool public prolongationPermitted; | ||
|
||
/// How much ETH each address has invested to this crowdsale | ||
mapping (address => uint256) public investedAmountOf; | ||
|
||
/// How much tokens this crowdsale has credited for each investor address | ||
mapping (address => uint256) public tokenAmountOf; | ||
|
||
/// Multiplier for token value | ||
uint public tokenValueMultiplier; | ||
|
||
/// Stop trigger in excess | ||
bool public stopped; | ||
|
||
/// @dev Constructor | ||
/// @param _token SNTR token address | ||
/// @param _ethMultisigWallet wallet address to transfer invested ETH | ||
/// @param _tokenMultisigWallet wallet address to withdraw unused tokens | ||
/// @param _startTime ICO start time | ||
/// @param _duration ICO duration in seconds | ||
/// @param _prolongedDuration Prolonged ICO duration in seconds, 0 if no prolongation is planned | ||
/// @param _tokenPrice Token price in wei | ||
/// @param _minInvestment Minimal investment amount in wei | ||
/// @param _allowedSenders List of addresses allowed to send ETH to this contract, empty if anyone is allowed | ||
function SilentNotaryTokenSale(address _token, address _ethMultisigWallet, address _tokenMultisigWallet, | ||
uint _startTime, uint _duration, uint _prolongedDuration, uint _tokenPrice, uint _minInvestment, address[] _allowedSenders) public { | ||
require(_token != 0); | ||
require(_ethMultisigWallet != 0); | ||
require(_tokenMultisigWallet != 0); | ||
require(_duration > 0); | ||
require(_tokenPrice > 0); | ||
require(_minInvestment > 0); | ||
|
||
token = ERC20(_token); | ||
ethMultisigWallet = _ethMultisigWallet; | ||
tokenMultisigWallet = _tokenMultisigWallet; | ||
startTime = _startTime; | ||
duration = _duration; | ||
prolongedDuration = _prolongedDuration; | ||
tokenPrice = _tokenPrice; | ||
minInvestment = _minInvestment; | ||
allowedSenders = _allowedSenders; | ||
tokenValueMultiplier = 10 ** token.decimals(); | ||
} | ||
|
||
/// @dev Sell tokens to specified address | ||
/// @param receiver receiver of bought tokens | ||
function buy (address receiver) public payable { | ||
require(!stopped); | ||
require(getCurrentStatus() == Status.Selling || getCurrentStatus() == Status.ProlongedSelling); | ||
require(msg.value >= minInvestment); | ||
|
||
// Check if current sender is allowed to participate in this crowdsale | ||
var senderAllowed = false; | ||
if (allowedSenders.length > 0) { | ||
for (uint i = 0; i < allowedSenders.length; i++) | ||
if (allowedSenders[i] == receiver) { | ||
senderAllowed = true; | ||
break; | ||
} | ||
} | ||
else | ||
senderAllowed = true; | ||
|
||
assert(senderAllowed); | ||
|
||
uint weiAmount = msg.value; | ||
uint tokenAmount = safeDiv(safeMul(weiAmount, tokenValueMultiplier), tokenPrice); | ||
assert(tokenAmount > 0); | ||
|
||
uint changeWei = 0; | ||
var currentContractTokens = token.balanceOf(address(this)); | ||
if (currentContractTokens < tokenAmount) { | ||
var changeTokenAmount = safeSub(tokenAmount, currentContractTokens); | ||
changeWei = safeDiv(safeMul(changeTokenAmount, tokenPrice), tokenValueMultiplier); | ||
tokenAmount = currentContractTokens; | ||
weiAmount = safeSub(weiAmount, changeWei); | ||
} | ||
|
||
if(investedAmountOf[receiver] == 0) { | ||
// A new investor | ||
investorCount++; | ||
} | ||
// Update investor-amount mappings | ||
investedAmountOf[receiver] = safeAdd(investedAmountOf[receiver], weiAmount); | ||
tokenAmountOf[receiver] = safeAdd(tokenAmountOf[receiver], tokenAmount); | ||
// Update totals | ||
weiRaisedAmount = safeAdd(weiRaisedAmount, weiAmount); | ||
tokensSoldAmount = safeAdd(tokensSoldAmount, tokenAmount); | ||
|
||
// Transfer the invested ETH to the multisig wallet; | ||
ethMultisigWallet.transfer(weiAmount); | ||
|
||
// Transfer the bought tokens to the ETH sender | ||
var transferSuccess = token.transfer(receiver, tokenAmount); | ||
assert(transferSuccess); | ||
|
||
// Return change if any | ||
if (changeWei > 0) { | ||
receiver.transfer(changeWei); | ||
} | ||
|
||
// Tell us the investment succeeded | ||
Invested(receiver, weiAmount, tokenAmount); | ||
} | ||
|
||
/// @dev Sell tokens to ETH sender | ||
function() public payable { | ||
buy(msg.sender); | ||
} | ||
|
||
/// @dev Token sale state machine management. | ||
/// @return Status current status | ||
function getCurrentStatus() public constant returns (Status) { | ||
if (startTime > now) | ||
return Status.Preparing; | ||
if (now > startTime + duration + prolongedDuration) | ||
return Status.Finished; | ||
if (now > startTime + duration && !prolongationPermitted) | ||
return Status.Finished; | ||
if (token.balanceOf(address(this)) <= 0) | ||
return Status.TokenShortage; | ||
if (now > startTime + duration) | ||
return Status.ProlongedSelling; | ||
if (now >= startTime) | ||
return Status.Selling; | ||
return Status.Unknown; | ||
} | ||
|
||
/// @dev Withdraw remaining tokens to the team wallet | ||
/// @param value Amount of tokens to withdraw | ||
function withdrawTokens(uint value) public onlyOwner { | ||
require(value <= token.balanceOf(address(this))); | ||
// Return the specified amount of tokens to team wallet | ||
token.transfer(tokenMultisigWallet, value); | ||
Withdraw(value); | ||
} | ||
|
||
/// @dev Change current token price | ||
/// @param newTokenPrice New token unit price in wei | ||
function changeTokenPrice(uint newTokenPrice) public onlyOwner { | ||
require(newTokenPrice > 0); | ||
|
||
tokenPrice = newTokenPrice; | ||
TokenPriceChanged(newTokenPrice); | ||
} | ||
|
||
/// @dev Prolong ICO if owner decides to do it | ||
function prolong() public onlyOwner { | ||
require(!prolongationPermitted && prolongedDuration > 0); | ||
prolongationPermitted = true; | ||
} | ||
|
||
/// @dev Called by the owner on excess, triggers stopped state | ||
function stopSale() public onlyOwner { | ||
stopped = true; | ||
} | ||
|
||
/// @dev Called by the owner on end of excess, returns to normal state | ||
function resumeSale() public onlyOwner { | ||
require(stopped); | ||
stopped = false; | ||
} | ||
|
||
/// @dev Called by the owner to destroy contract | ||
function kill() public onlyOwner { | ||
selfdestruct(owner); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
pragma solidity ^0.4.18; | ||
|
||
import 'common/Buyable.sol'; | ||
import 'common/ownership/Ownable.sol'; | ||
|
||
contract SilentNotaryTokenSaleProxy is Ownable { | ||
|
||
/// Target contract | ||
Buyable public targetContract; | ||
|
||
/// Gas limit for buy transaction | ||
uint public buyGasLimit = 200000; | ||
|
||
/// Is sale stopped or not | ||
bool public stopped = false; | ||
|
||
/// Total volume of weis passed through this proxy | ||
uint public totalWeiVolume = 0; | ||
|
||
/// @dev Constructor | ||
/// @param _targetAddress Address of the target Buyable contract | ||
function SilentNotaryTokenSaleProxy(address _targetAddress) public { | ||
require(_targetAddress > 0); | ||
targetContract = Buyable(_targetAddress); | ||
} | ||
|
||
/// @dev Fallback function - forward investment request to the target contract | ||
function() public payable { | ||
require(msg.value > 0); | ||
require(!stopped); | ||
totalWeiVolume += msg.value; | ||
targetContract.buy.value(msg.value).gas(buyGasLimit)(msg.sender); | ||
} | ||
|
||
/// @dev Change target address where investment requests are forwarded | ||
/// @param newTargetAddress New target address to forward investments | ||
function changeTargetAddress(address newTargetAddress) public onlyOwner { | ||
require(newTargetAddress > 0); | ||
targetContract = Buyable(newTargetAddress); | ||
} | ||
|
||
/// @dev Change gas limit for buy() method call | ||
/// @param newGasLimit New gas limit | ||
function changeGasLimit(uint newGasLimit) public onlyOwner { | ||
require(newGasLimit > 0); | ||
buyGasLimit = newGasLimit; | ||
} | ||
|
||
/// @dev Stop the sale | ||
function stop() public onlyOwner { | ||
require(!stopped); | ||
stopped = true; | ||
} | ||
|
||
/// @dev Resume the sale | ||
function resume() public onlyOwner { | ||
require(stopped); | ||
stopped = false; | ||
} | ||
} |
Oops, something went wrong.