Skip to content

Instantly share code, notes, and snippets.

@Arachnid
Created June 20, 2017 20:24
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save Arachnid/b9886ef91d5b47c31d2e3c8022eeea27 to your computer and use it in GitHub Desktop.
Save Arachnid/b9886ef91d5b47c31d2e3c8022eeea27 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);
}
}
@rmerom
Copy link

rmerom commented Jun 21, 2017

At L90, I believe you want to change == to =

@paulfeaviour
Copy link

paulfeaviour commented Jul 2, 2017

Wouldn't it make sense to check that pct is !=0 in function setStrikePrice? Assuming that bidders who bid exactly the strike price get something?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment