Dutch auction
The price falls linearly from a start price to a floor. The first buyer to pay the current price wins, and any overpayment is refunded in the same transaction.
dutch-auction.lithe
(* The price falls linearly from a start price to a floor. The first buyer at or
above the current price wins, and any overpayment is returned. *)
import "std/address";
import "std/evm";
error AlreadyStarted;
error Sold;
error TooLow of record {
paid : wei;
price : wei;
}
event Bought of record {
indexed buyer : address;
price : wei;
}
storage is record {
seller : address;
startPrice : wei;
floor : wei;
startedAt : timestamp;
duration : uint;
sold : bool;
}
external function start(const startPrice : wei; const floor : wei; const duration : uint)
raises AlreadyStarted
is {
assert(storage.seller = Address.zero, AlreadyStarted);
storage.seller := Evm.sender();
storage.startPrice := startPrice;
storage.floor := floor;
storage.startedAt := Evm.blockTimestamp();
storage.duration := duration;
}
view function currentPrice() : wei is {
const elapsed : uint := Evm.blockTimestamp() - storage.startedAt;
var price : wei := storage.floor;
if elapsed < storage.duration then {
const spread : uint := uint(storage.startPrice - storage.floor);
price := storage.startPrice - wei(spread * elapsed / storage.duration);
}
} with price
external view function price() : wei is
currentPrice()
external payable nonreentrant function buy()
raises Sold | TooLow
is {
assert(not storage.sold, Sold);
const price : wei := currentPrice();
assert(Evm.value() >= price, TooLow { paid = Evm.value(); price = price; });
storage.sold := true;
emit Bought { buyer = Evm.sender(); price = price; };
defer Evm.transfer(storage.seller, price);
if Evm.value() > price then {
defer Evm.transfer(Evm.sender(), Evm.value() - price);
}
}Both transfers are deferred, and they run in order after sold is set: first the seller is paid, then the buyer is refunded.

