Skip to content

Instantly share code, notes, and snippets.

@Georgi87
Created September 9, 2017 11:14
Show Gist options
  • Save Georgi87/c8f98bd00e8f1fbefcf893cb1894720e to your computer and use it in GitHub Desktop.
Save Georgi87/c8f98bd00e8f1fbefcf893cb1894720e to your computer and use it in GitHub Desktop.
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