Last active
January 28, 2018 14:36
-
-
Save bitdollarfund/ec7cda15df9bf8e0ec17c597becc9ee3 to your computer and use it in GitHub Desktop.
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
pragma solidity 0.4.18; | |
import './StandardToken.sol'; | |
contract BTD is StandardToken { | |
// FIELDS | |
string public name = "Bitdollar Fund Token"; | |
string public symbol = "BTD"; | |
uint256 public decimals = 18; | |
string public version = "1.0"; | |
uint256 public tokenCap = 100000000 * 10**18; | |
// crowdsale parameters | |
uint256 public fundingStartBlock; | |
uint256 public fundingEndBlock; | |
// vesting fields | |
address public vestingContract; | |
bool private vestingSet = false; | |
// root control | |
address public fundWallet; | |
// control of liquidity and limited control of updatePrice | |
address public controlWallet; | |
// time to wait between controlWallet price updates | |
uint256 public waitTime = 5 hours; | |
// fundWallet controlled state variables | |
// halted: halt buying due to emergency, tradeable: signal that assets have been acquired | |
bool public halted = false; | |
bool public tradeable = false; | |
// -- totalSupply defined in StandardToken | |
// -- mapping to token balances done in StandardToken | |
uint256 public previousUpdateTime = 0; | |
Price public currentPrice; | |
uint256 public minAmount = 0.04 ether; | |
// map participant address to a withdrawal request | |
mapping (address => Withdrawal) public withdrawals; | |
// maps previousUpdateTime to the next price | |
mapping (uint256 => Price) public prices; | |
// maps addresses | |
mapping (address => bool) public whitelist; | |
// TYPES | |
struct Price { // tokensPerEth | |
uint256 numerator; | |
uint256 denominator; | |
} | |
struct Withdrawal { | |
uint256 tokens; | |
uint256 time; // time for each withdrawal is set to the previousUpdateTime | |
} | |
// EVENTS | |
event Buy(address indexed participant, address indexed beneficiary, uint256 ethValue, uint256 amountTokens); | |
event AllocatePresale(address indexed participant, uint256 amountTokens); | |
event Whitelist(address indexed participant); | |
event PriceUpdate(uint256 numerator, uint256 denominator); | |
event AddLiquidity(uint256 ethAmount); | |
event RemoveLiquidity(uint256 ethAmount); | |
event WithdrawRequest(address indexed participant, uint256 amountTokens); | |
event Withdraw(address indexed participant, uint256 amountTokens, uint256 etherAmount); | |
// MODIFIERS | |
modifier isTradeable { // exempt vestingContract and fundWallet to allow dev allocations | |
require(tradeable || msg.sender == fundWallet || msg.sender == vestingContract); | |
_; | |
} | |
modifier onlyWhitelist { | |
require(whitelist[msg.sender]); | |
_; | |
} | |
modifier onlyFundWallet { | |
require(msg.sender == fundWallet); | |
_; | |
} | |
modifier onlyManagingWallets { | |
require(msg.sender == controlWallet || msg.sender == fundWallet); | |
_; | |
} | |
modifier only_if_controlWallet { | |
if (msg.sender == controlWallet) _; | |
} | |
modifier require_waited { | |
require(safeSub(now, waitTime) >= previousUpdateTime); | |
_; | |
} | |
modifier only_if_increase (uint256 newNumerator) { | |
if (newNumerator > currentPrice.numerator) _; | |
} | |
// CONSTRUCTOR | |
function BTD(address controlWalletInput, uint256 priceNumeratorInput, uint256 startBlockInput, uint256 endBlockInput) public { | |
require(controlWalletInput != address(0)); | |
require(priceNumeratorInput > 0); | |
require(endBlockInput > startBlockInput); | |
fundWallet = msg.sender; | |
controlWallet = controlWalletInput; | |
whitelist[fundWallet] = true; | |
whitelist[controlWallet] = true; | |
currentPrice = Price(priceNumeratorInput, 1000); // 1 token = 1 usd at ICO start | |
fundingStartBlock = startBlockInput; | |
fundingEndBlock = endBlockInput; | |
previousUpdateTime = now; | |
} | |
// METHODS | |
function setVestingContract(address vestingContractInput) external onlyFundWallet { | |
require(vestingContractInput != address(0)); | |
vestingContract = vestingContractInput; | |
whitelist[vestingContract] = true; | |
vestingSet = true; | |
} | |
// allows controlWallet to update the price within a time contstraint, allows fundWallet complete control | |
function updatePrice(uint256 newNumerator) external onlyManagingWallets { | |
require(newNumerator > 0); | |
require_limited_change(newNumerator); | |
// either controlWallet command is compliant or transaction came from fundWallet | |
currentPrice.numerator = newNumerator; | |
// maps time to new Price (if not during ICO) | |
prices[previousUpdateTime] = currentPrice; | |
previousUpdateTime = now; | |
PriceUpdate(newNumerator, currentPrice.denominator); | |
} | |
function require_limited_change (uint256 newNumerator) | |
private | |
only_if_controlWallet | |
require_waited | |
only_if_increase(newNumerator) | |
view | |
{ | |
uint256 percentage_diff = 0; | |
percentage_diff = safeMul(newNumerator, 100) / currentPrice.numerator; | |
percentage_diff = safeSub(percentage_diff, 100); | |
// controlWallet can only increase price by max 20% and only every waitTime | |
require(percentage_diff <= 20); | |
} | |
function updatePriceDenominator(uint256 newDenominator) external onlyFundWallet { | |
require(block.number > fundingEndBlock); | |
require(newDenominator > 0); | |
currentPrice.denominator = newDenominator; | |
// maps time to new Price | |
prices[previousUpdateTime] = currentPrice; | |
previousUpdateTime = now; | |
PriceUpdate(currentPrice.numerator, newDenominator); | |
} | |
function allocateTokens(address participant, uint256 amountTokens) private { | |
require(vestingSet); | |
// 15% of total allocated for PR, Marketing, Team, Advisors | |
uint256 developmentAllocation = safeMul(amountTokens, 17647058823529400) / 100000000000000000; | |
// check that token cap is not exceeded | |
uint256 newTokens = safeAdd(amountTokens, developmentAllocation); | |
require(safeAdd(totalSupply, newTokens) <= tokenCap); | |
// increase token supply, assign tokens to participant | |
totalSupply = safeAdd(totalSupply, newTokens); | |
balances[participant] = safeAdd(balances[participant], amountTokens); | |
balances[vestingContract] = safeAdd(balances[vestingContract], developmentAllocation); | |
} | |
function allocatePresaleTokens(address participant, uint amountTokens) external onlyFundWallet { | |
require(block.number < fundingEndBlock); | |
require(participant != address(0)); | |
whitelist[participant] = true; // automatically whitelist accepted presale | |
allocateTokens(participant, amountTokens); | |
Whitelist(participant); | |
AllocatePresale(participant, amountTokens); | |
} | |
function verifyParticipant(address participant) external onlyManagingWallets { | |
whitelist[participant] = true; | |
Whitelist(participant); | |
} | |
function buy() external payable { | |
buyTo(msg.sender); | |
} | |
function buyTo(address participant) public payable onlyWhitelist { | |
require(!halted); | |
require(participant != address(0)); | |
require(msg.value >= minAmount); | |
require(block.number >= fundingStartBlock && block.number < fundingEndBlock); | |
uint256 icoDenominator = icoDenominatorPrice(); | |
uint256 tokensToBuy = safeMul(msg.value, currentPrice.numerator) / icoDenominator; | |
allocateTokens(participant, tokensToBuy); | |
// send ether to fundWallet | |
fundWallet.transfer(msg.value); | |
Buy(msg.sender, participant, msg.value, tokensToBuy); | |
} | |
// time based on blocknumbers, assuming a blocktime of 30s | |
function icoDenominatorPrice() public constant returns (uint256) { | |
uint256 icoDuration = safeSub(block.number, fundingStartBlock); | |
uint256 denominator; | |
if (icoDuration < 2880) { // #blocks = 24*60*60/30 = 2880 | |
return currentPrice.denominator; | |
} else if (icoDuration < 80640 ) { // #blocks = 4*7*24*60*60/30 = 80640 | |
denominator = safeMul(currentPrice.denominator, 105) / 100; | |
return denominator; | |
} else { | |
denominator = safeMul(currentPrice.denominator, 110) / 100; | |
return denominator; | |
} | |
} | |
function requestWithdrawal(uint256 amountTokensToWithdraw) external isTradeable onlyWhitelist { | |
require(block.number > fundingEndBlock); | |
require(amountTokensToWithdraw > 0); | |
address participant = msg.sender; | |
require(balanceOf(participant) >= amountTokensToWithdraw); | |
require(withdrawals[participant].tokens == 0); // participant cannot have outstanding withdrawals | |
balances[participant] = safeSub(balances[participant], amountTokensToWithdraw); | |
withdrawals[participant] = Withdrawal({tokens: amountTokensToWithdraw, time: previousUpdateTime}); | |
WithdrawRequest(participant, amountTokensToWithdraw); | |
} | |
function withdraw() external { | |
address participant = msg.sender; | |
uint256 tokens = withdrawals[participant].tokens; | |
require(tokens > 0); // participant must have requested a withdrawal | |
uint256 requestTime = withdrawals[participant].time; | |
// obtain the next price that was set after the request | |
Price storage price = prices[requestTime]; | |
require(price.numerator > 0); // price must have been set | |
uint256 withdrawValue = safeMul(tokens, price.denominator) / price.numerator; | |
// if contract ethbal > then send + transfer tokens to fundWallet, otherwise give tokens back | |
withdrawals[participant].tokens = 0; | |
if (this.balance >= withdrawValue) | |
enact_withdrawal_greater_equal(participant, withdrawValue, tokens); | |
else | |
enact_withdrawal_less(participant, withdrawValue, tokens); | |
} | |
function enact_withdrawal_greater_equal(address participant, uint256 withdrawValue, uint256 tokens) | |
private | |
{ | |
assert(this.balance >= withdrawValue); | |
balances[fundWallet] = safeAdd(balances[fundWallet], tokens); | |
participant.transfer(withdrawValue); | |
Withdraw(participant, tokens, withdrawValue); | |
} | |
function enact_withdrawal_less(address participant, uint256 withdrawValue, uint256 tokens) | |
private | |
{ | |
assert(this.balance < withdrawValue); | |
balances[participant] = safeAdd(balances[participant], tokens); | |
Withdraw(participant, tokens, 0); // indicate a failed withdrawal | |
} | |
function checkWithdrawValue(uint256 amountTokensToWithdraw) constant public returns (uint256 etherValue) { | |
require(amountTokensToWithdraw > 0); | |
require(balanceOf(msg.sender) >= amountTokensToWithdraw); | |
uint256 withdrawValue = safeMul(amountTokensToWithdraw, currentPrice.denominator) / currentPrice.numerator; | |
require(this.balance >= withdrawValue); | |
return withdrawValue; | |
} | |
// allow fundWallet or controlWallet to add ether to contract | |
function addLiquidity() external onlyManagingWallets payable { | |
require(msg.value > 0); | |
AddLiquidity(msg.value); | |
} | |
// allow fundWallet to remove ether from contract | |
function removeLiquidity(uint256 amount) external onlyManagingWallets { | |
require(amount <= this.balance); | |
fundWallet.transfer(amount); | |
RemoveLiquidity(amount); | |
} | |
function changeFundWallet(address newFundWallet) external onlyFundWallet { | |
require(newFundWallet != address(0)); | |
fundWallet = newFundWallet; | |
} | |
function changeControlWallet(address newControlWallet) external onlyFundWallet { | |
require(newControlWallet != address(0)); | |
controlWallet = newControlWallet; | |
} | |
function changeWaitTime(uint256 newWaitTime) external onlyFundWallet { | |
waitTime = newWaitTime; | |
} | |
function updateFundingStartBlock(uint256 newFundingStartBlock) external onlyFundWallet { | |
require(block.number < fundingStartBlock); | |
require(block.number < newFundingStartBlock); | |
fundingStartBlock = newFundingStartBlock; | |
} | |
function updateFundingEndBlock(uint256 newFundingEndBlock) external onlyFundWallet { | |
require(block.number < fundingEndBlock); | |
require(block.number < newFundingEndBlock); | |
fundingEndBlock = newFundingEndBlock; | |
} | |
function halt() external onlyFundWallet { | |
halted = true; | |
} | |
function unhalt() external onlyFundWallet { | |
halted = false; | |
} | |
function enableTrading() external onlyFundWallet { | |
require(block.number > fundingEndBlock); | |
tradeable = true; | |
} | |
// fallback function | |
function() public payable { | |
require(tx.origin == msg.sender); | |
buyTo(msg.sender); | |
} | |
function claimTokens(address _token) external onlyFundWallet { | |
require(_token != address(0)); | |
Token token = Token(_token); | |
uint256 balance = token.balanceOf(this); | |
token.transfer(fundWallet, balance); | |
} | |
// prevent transfers until trading allowed | |
function transfer(address _to, uint256 _value) isTradeable public returns (bool success) { | |
return super.transfer(_to, _value); | |
} | |
function transferFrom(address _from, address _to, uint256 _value) isTradeable public returns (bool success) { | |
return super.transferFrom(_from, _to, _value); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment