Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save agileone/1f3047d6a0db512c2067f8aab7da0482 to your computer and use it in GitHub Desktop.
Save agileone/1f3047d6a0db512c2067f8aab7da0482 to your computer and use it in GitHub Desktop.
pragma solidity ^0.4.0;
import "SafeMath.sol";
import "IERC20Token.sol";
/**
* @dev Implements a capped token sale using a second-price auction.
*
* @author Nick Johnson <arachnid@notdot.net>
*
* WARNING WARNING WARNING
* This contract code is intended as a proof-of-concept demonstration. No
* testing has been performed other than verifying that it compiles. DO NOT under
* any circumstances use this in its current state for any reason.
* WARNING WARNING WARNING
*
* Users can deposits funds for the auction any time before endTimestamp. After
* doing so, they send a signed message to the party running the auction with a bid.
* Bids are `(price, quantity)` tuples, where `price` is the maximum amount of
* ether a user is willing to pay per token, and `quantity` is the number of
* tokens the user wants to buy.
*
* At the end of the bidding period, the seller sets a 'strike price'. All bids
* with a price at least as high as the strike price are filled, and all bids
* under this strike price are returned. The strike price is calculated
* offchain by the seller. Once set, anyone - bidder, seller, or third party -
* can submit the signed bid to the contract, which will issue tokens to winning
* bidders.
*
* A hard cap on the amount of ether raised is specified at the time the
* contract is deployed, with extra funds being returned to users.
*
* A week after the end of the contract, users can unilaterally withdraw any
* remaining funds.
*/
contract TokenSale is SafeMath {
event Deposit(address indexed bidder, uint price);
event StrikePriceSet(uint strikePrice);
mapping(address=>uint) public deposits;
uint public depositCount; // Count of bids
address public owner;
IERC20Token token;
uint public salesTarget; // Total number of ether to be raised
uint public strikePrice; // Price at which tokens are sold, set after sale completes
uint public strikePricePct; // Percentage of total qty to sell to bidders who bid exactly the strike price
uint public endTimestamp; // Time at which sale ends
modifier beforeSale {
require(now <= endTimestamp);
_;
}
modifier afterSale {
require(endTimestamp <= now && strikePrice != 0);
_;
}
modifier afterTimeout {
require(endTimestamp + 1 weeks <= now);
_;
}
modifier ownerOnly {
require(msg.sender == owner);
_;
}
function TokenSale(IERC20Token _token, uint _salesTarget, uint _startTimestamp, uint _endTimestamp) {
token = _token;
owner = msg.sender;
salesTarget = _salesTarget;
endTimestamp = _endTimestamp;
}
function deposit() beforeSale payable {
deposits[msg.sender] += msg.value;
depositCount += 1;
Deposit(msg.sender, msg.value);
}
function() payable {
deposit();
}
function setStrikePrice(uint128 price, uint pct) ownerOnly {
require(endTimestamp <= now && strikePrice == 0 && price != 0);
strikePrice = price;
strikePricePct == pct;
StrikePriceSet(price);
}
function acceptBid(uint price, uint quantity, uint8 v, bytes32 r, bytes32 s) afterSale {
var tokensLeft = token.balanceOf(this);
var bidHash = sha3(address(this), price, quantity);
var bidder = ecrecover(bidHash, v, r, s);
var total = safeMul(price, quantity);
require(total <= deposits[bidder]);
uint filledQuantity = quantity;
// If the user bid under the strike price, they get no tokens
if(price < strikePrice) {
filledQuantity = 0;
} else if(price == strikePrice) {
// For bidders at exactly the strike price, part-fill their bids
filledQuantity = (filledQuantity * strikePricePct) / 100;
}
// If there's not enough tokens left, they get the remaining tokens
if(tokensLeft < filledQuantity) {
filledQuantity = tokensLeft;
}
// If there's not enough funds left under the cap, sell them the remainder
var filledTotal = safeMul(strikePrice, filledQuantity);
if(filledTotal > salesTarget) {
filledQuantity = salesTarget / strikePrice;
filledTotal = safeMul(strikePrice, filledQuantity);
}
if(filledTotal > 0) {
// Sell the user the tokens
owner.transfer(filledTotal);
salesTarget -= filledTotal;
tokensLeft -= filledQuantity;
}
if(deposits[bidder] > filledTotal) {
// Send back any extra funds
bidder.transfer(deposits[bidder] - filledTotal);
deposits[bidder] = 0;
}
depositCount -= 1;
token.transfer(bidder, filledQuantity);
}
function withdrawTokens() afterTimeout {
var total = deposits[msg.sender];
deposits[msg.sender] = 0;
msg.sender.transfer(total);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment