/** *Submitted for verification at Etherscan.io on 2017-04-18 */ pragma solidity 0.4.10; /// @title Abstract token contract - Functions to be implemented by token contracts. contract Token { function transfer(address to, uint256 value) returns (bool success); function transferFrom(address from, address to, uint256 value) returns (bool success); function approve(address spender, uint256 value) returns (bool success); // This is not an abstract function, because solc won't recognize generated getter functions for public variables as functions. function totalSupply() constant returns (uint256 supply) {} function balanceOf(address owner) constant returns (uint256 balance); function allowance(address owner, address spender) constant returns (uint256 remaining); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } /// @title Dutch auction contract - distribution of Gnosis tokens using an auction. /// @author Stefan George - <[email protected]> contract DutchAuction { /* * Events */ event BidSubmission(address indexed sender, uint256 amount); /* * Constants */ uint constant public MAX_TOKENS_SOLD = 9000000 * 10**18; // 9M uint constant public WAITING_PERIOD = 7 days; /* * Storage */ Token public gnosisToken; address public wallet; address public owner; uint public ceiling; uint public priceFactor; uint public startBlock; uint public endTime; uint public totalReceived; uint public finalPrice; mapping (address => uint) public bids; Stages public stage; /* * Enums */ enum Stages { AuctionDeployed, AuctionSetUp, AuctionStarted, AuctionEnded, TradingStarted } /* * Modifiers */ modifier atStage(Stages _stage) { if (stage != _stage) // Contract not in expected state throw; _; } modifier isOwner() { if (msg.sender != owner) // Only owner is allowed to proceed throw; _; } modifier isWallet() { if (msg.sender != wallet) // Only wallet is allowed to proceed throw; _; } modifier isValidPayload() { if (msg.data.length != 4 && msg.data.length != 36) throw; _; } modifier timedTransitions() { if (stage == Stages.AuctionStarted && calcTokenPrice() <= calcStopPrice()) finalizeAuction(); if (stage == Stages.AuctionEnded && now > endTime + WAITING_PERIOD) stage = Stages.TradingStarted; _; } /* * Public functions */ /// @dev Contract constructor function sets owner. /// @param _wallet Gnosis wallet. /// @param _ceiling Auction ceiling. /// @param _priceFactor Auction price factor. function DutchAuction(address _wallet, uint _ceiling, uint _priceFactor) public { if (_wallet == 0 || _ceiling == 0 || _priceFactor == 0) // Arguments are null. throw; owner = msg.sender; wallet = _wallet; ceiling = _ceiling; priceFactor = _priceFactor; stage = Stages.AuctionDeployed; } /// @dev Setup function sets external contracts' addresses. /// @param _gnosisToken Gnosis token address. function setup(address _gnosisToken) public isOwner atStage(Stages.AuctionDeployed) { if (_gnosisToken == 0) // Argument is null. throw; gnosisToken = Token(_gnosisToken); // Validate token balance if (gnosisToken.balanceOf(this) != MAX_TOKENS_SOLD) throw; stage = Stages.AuctionSetUp; } /// @dev Starts auction and sets startBlock. function startAuction() public isWallet atStage(Stages.AuctionSetUp) { stage = Stages.AuctionStarted; startBlock = block.number; } /// @dev Changes auction ceiling and start price factor before auction is started. /// @param _ceiling Updated auction ceiling. /// @param _priceFactor Updated start price factor. function changeSettings(uint _ceiling, uint _priceFactor) public isWallet atStage(Stages.AuctionSetUp) { ceiling = _ceiling; priceFactor = _priceFactor; } /// @dev Calculates current token price. /// @return Returns token price. function calcCurrentTokenPrice() public timedTransitions returns (uint) { if (stage == Stages.AuctionEnded || stage == Stages.TradingStarted) return finalPrice; return calcTokenPrice(); } /// @dev Returns correct stage, even if a function with timedTransitions modifier has not yet been called yet. /// @return Returns current auction stage. function updateStage() public timedTransitions returns (Stages) { return stage; } /// @dev Allows to send a bid to the auction. /// @param receiver Bid will be assigned to this address if set. function bid(address receiver) public payable isValidPayload timedTransitions atStage(Stages.AuctionStarted) returns (uint amount) { // If a bid is done on behalf of a user via ShapeShift, the receiver address is set. if (receiver == 0) receiver = msg.sender; amount = msg.value; // Prevent that more than 90% of tokens are sold. Only relevant if cap not reached. uint maxWei = (MAX_TOKENS_SOLD / 10**18) * calcTokenPrice() - totalReceived; uint maxWeiBasedOnTotalReceived = ceiling - totalReceived; if (maxWeiBasedOnTotalReceived < maxWei) maxWei = maxWeiBasedOnTotalReceived; // Only invest maximum possible amount. if (amount > maxWei) { amount = maxWei; // Send change back to receiver address. In case of a ShapeShift bid the user receives the change back directly. if (!receiver.send(msg.value - amount)) // Sending failed throw; } // Forward funding to ether wallet if (amount == 0 || !wallet.send(amount)) // No amount sent or sending failed throw; bids[receiver] += amount; totalReceived += amount; if (maxWei == amount) // When maxWei is equal to the big amount the auction is ended and finalizeAuction is triggered. finalizeAuction(); BidSubmission(receiver, amount); } /// @dev Claims tokens for bidder after auction. /// @param receiver Tokens will be assigned to this address if set. function claimTokens(address receiver) public isValidPayload timedTransitions atStage(Stages.TradingStarted) { if (receiver == 0) receiver = msg.sender; uint tokenCount = bids[receiver] * 10**18 / finalPrice; bids[receiver] = 0; gnosisToken.transfer(receiver, tokenCount); } /// @dev Calculates stop price. /// @return Returns stop price. function calcStopPrice() constant public returns (uint) { return totalReceived * 10**18 / MAX_TOKENS_SOLD + 1; } /// @dev Calculates token price. /// @return Returns token price. function calcTokenPrice() constant public returns (uint) { return priceFactor * 10**18 / (block.number - startBlock + 7500) + 1; } /* * Private functions */ function finalizeAuction() private { stage = Stages.AuctionEnded; if (totalReceived == ceiling) finalPrice = calcTokenPrice(); else finalPrice = calcStopPrice(); uint soldTokens = totalReceived * 10**18 / finalPrice; // Auction contract transfers all unsold tokens to Gnosis inventory multisig gnosisToken.transfer(wallet, MAX_TOKENS_SOLD - soldTokens); endTime = now; } }