Created
September 9, 2017 11:14
-
-
Save Georgi87/c8f98bd00e8f1fbefcf893cb1894720e 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
contract Token { | |
function transfer(address to, uint value) public returns (bool); | |
function transferFrom(address from, address to, uint value) public returns (bool); | |
} | |
contract DutchExchange { | |
event SellOrderSubmission(address sender, uint amount, Token sellToken, Token buyToken, uint startDate); | |
event SellOrderCancellation(address sender, uint amount, Token sellToken, Token buyToken, uint startDate); | |
event BuyOrderSubmission(address sender, uint amount, Token sellToken, Token buyToken, uint startDate); | |
event AuctionFinalization(Token sellToken, Token buyToken, uint startDate, uint finalPrice); | |
event UnsoldTokenRedemption(address sender, uint amount, Token sellToken, Token buyToken, uint startDate); | |
event TokenRedemption(address sender, uint amount, Token sellToken, Token buyToken, uint startDate); | |
mapping (bytes32 => Auction) auctions; | |
struct Auction { | |
State state; | |
uint startDate; | |
uint startPrice; | |
uint finalPrice; | |
uint totalSellOrders; | |
uint totalBuyOrders; | |
mapping (address => uint) sellOrders; | |
mapping (address => uint) buyOrders; | |
} | |
enum State { | |
NotInitialized, | |
ReceivingSellOrders, | |
ReceivingBuyOrders, | |
EndedSuccessfully, | |
EndedUnsuccessfully | |
} | |
function initialSellOrder(Token sellToken, Token buyToken, uint amount, uint startPrice) | |
public | |
{ | |
uint startDate = nextAuctionStartDate(); | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
// Set start date and start price for new auction | |
if (auction.state == State.NotInitialized) { | |
auction.startDate = startDate; | |
bytes32 previousAuctionHash = calcAuctionHash(sellToken, buyToken, previousAuctionStartDate()); | |
// ToDo how to caluclate price based on previous auction? | |
// Maybe set start price, such that final price of previous auction will always be reached after 12h? | |
auction.startPrice = auctions[previousAuctionHash].finalPrice * 2; | |
if (auction.startPrice == 0) | |
auction.startPrice = startPrice; | |
auction.state = State.ReceivingSellOrders; | |
} | |
// Execute sell order | |
sellOrder(sellToken, buyToken, amount); | |
} | |
function sellOrder(Token sellToken, Token buyToken, uint amount) | |
public | |
{ | |
// Tokens to sell can be transferred | |
require(sellToken.transferFrom(msg.sender, this, amount)); | |
uint startDate = nextAuctionStartDate(); | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
// There was an initial sell order already | |
require(auction.state == State.ReceivingSellOrders); | |
auction.sellOrders[msg.sender] += amount; | |
auction.totalSellOrders += amount; | |
SellOrderSubmission(msg.sender, amount, sellToken, buyToken, startDate); | |
} | |
function cancelSellOrder(Token sellToken, Token buyToken) | |
public | |
{ | |
uint startDate = nextAuctionStartDate(); | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
require(auction.state == State.ReceivingSellOrders); | |
// Cancel order | |
uint amount = auction.sellOrders[msg.sender]; | |
auction.sellOrders[msg.sender] = 0; | |
auction.totalSellOrders -= amount; | |
// Cancelled tokens can be tranferred back | |
require(sellToken.transfer(msg.sender, amount)); | |
SellOrderCancellation(msg.sender, amount, sellToken, buyToken, startDate); | |
} | |
function buyOrder(Token sellToken, Token buyToken, uint amount) | |
public | |
{ | |
uint startDate = currentAuctionStartDate(); | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
if (auction.state == State.ReceivingSellOrders) | |
auction.state = State.ReceivingBuyOrders; | |
// The acution is still running | |
require (auction.state == State.ReceivingBuyOrders); | |
// Calculate maximum amount | |
uint currentPrice = calcCurrentPrice(auction.startDate, auction.startPrice); | |
if (currentPrice > 0) { | |
uint totalSold = auction.totalBuyOrders / currentPrice; | |
require(totalSold < auction.totalSellOrders); | |
uint maxAmount = (auction.totalSellOrders - totalSold) * currentPrice; | |
if (amount > maxAmount) | |
amount = maxAmount; | |
// Execute buy order | |
if (amount > 0) { | |
require(buyToken.transferFrom(msg.sender, this, amount)); | |
auction.buyOrders[msg.sender] += amount; | |
auction.totalBuyOrders += amount; | |
BuyOrderSubmission(msg.sender, amount, sellToken, buyToken, startDate); | |
} | |
} | |
// Finalize auction if possible | |
if (amount == maxAmount || currentPrice == 0) | |
finalizeAuction(sellToken, buyToken, startDate); | |
} | |
function finalizeAuction(Token sellToken, Token buyToken, uint startDate) | |
public | |
{ | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
require(auction.state == State.ReceivingBuyOrders); | |
uint currentPrice = calcCurrentPrice(auction.startDate, auction.startPrice); | |
if (currentPrice > 0) { | |
uint totalSold = auction.totalBuyOrders / currentPrice; | |
require(totalSold >= auction.totalSellOrders); | |
// ToDo prices can be fixed point numbers: totalSellOrders > totalBuyOrders | |
auction.finalPrice = auction.totalBuyOrders / auction.totalSellOrders; | |
auction.state = State.EndedSuccessfully; | |
} | |
else | |
auction.state = State.EndedUnsuccessfully; | |
AuctionFinalization(sellToken, buyToken, startDate, auction.finalPrice); | |
} | |
function redeemUnsoldTokens(Token sellToken, Token buyToken, uint startDate) | |
public | |
returns (uint amount) | |
{ | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
require(auction.state == State.EndedUnsuccessfully); | |
amount = auction.sellOrders[msg.sender]; | |
auction.sellOrders[msg.sender] = 0; | |
require(sellToken.transfer(msg.sender, amount)); | |
UnsoldTokenRedemption(msg.sender, amount, sellToken, buyToken, startDate); | |
} | |
function redeemTokens(Token sellToken, Token buyToken, uint startDate) | |
public | |
returns (uint amount) | |
{ | |
bytes32 auctionHash = calcAuctionHash(sellToken, buyToken, startDate); | |
Auction storage auction = auctions[auctionHash]; | |
require(auction.state == State.EndedSuccessfully); | |
amount = auction.buyOrders[msg.sender] / auction.finalPrice; | |
auction.buyOrders[msg.sender] = 0; | |
require(sellToken.transfer(msg.sender, amount)); | |
TokenRedemption(msg.sender, amount, sellToken, buyToken, startDate); | |
} | |
function calcCurrentPrice(uint startDate, uint startPrice) | |
public | |
returns (uint) | |
{ | |
if (1 days < (now - startDate)) | |
return 0; | |
// Linear price decrease | |
return startPrice * (1 days - (now - startDate)) / 1 days; | |
} | |
function calcAuctionHash(Token sellToken, Token buyToken, uint startDate) | |
public | |
returns (bytes32) | |
{ | |
return keccak256(sellToken, buyToken, startDate); | |
} | |
function previousAuctionStartDate() | |
public | |
constant | |
returns (uint) | |
{ | |
// Return UTC midnight of last day | |
return currentAuctionStartDate() - 1 days; | |
} | |
function nextAuctionStartDate() | |
public | |
constant | |
returns (uint) | |
{ | |
// Return UTC midnight of next day | |
return currentAuctionStartDate() + 1 days; | |
} | |
function currentAuctionStartDate() | |
public | |
constant | |
returns (uint) | |
{ | |
// Return UTC midnight of today | |
return now - (now % 1 days); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment