English auction
Each bid must beat the current highest. Outbid bidders withdraw their refunds themselves.
english-auction.lithe
(* Highest bid wins. Outbid bidders withdraw their own refunds: sending them
inside `bid` would let one bidder's failing receiver block everyone else. *)
import "std/address";
import "std/evm";
error NotSeller;
error AlreadyStarted;
error NotStarted;
error Ended;
error NotEnded;
error BidTooLow of record {
bid : wei;
highest : wei;
}
error AlreadySettled;
event Bid of record {
indexed bidder : address;
amount : wei;
}
event Settled of record {
indexed winner : address;
amount : wei;
}
storage is record {
seller : address;
endsAt : timestamp;
highestBidder : address;
highestBid : wei;
settled : bool;
refunds : map(address, wei);
}
external function start(const reserve : wei; const duration : uint)
raises AlreadyStarted
is {
assert(storage.seller = Address.zero, AlreadyStarted);
storage.seller := Evm.sender();
storage.highestBid := reserve;
storage.endsAt := Evm.blockTimestamp() + duration;
}
external payable function bid()
raises NotStarted | Ended | BidTooLow
is {
assert(storage.seller <> Address.zero, NotStarted);
assert(Evm.blockTimestamp() < storage.endsAt, Ended);
assert(
Evm.value() > storage.highestBid,
BidTooLow { bid = Evm.value(); highest = storage.highestBid; }
);
if storage.highestBidder <> Address.zero then {
storage.refunds[storage.highestBidder] :=
storage.refunds[storage.highestBidder] + storage.highestBid;
}
storage.highestBidder := Evm.sender();
storage.highestBid := Evm.value();
emit Bid { bidder = Evm.sender(); amount = Evm.value(); };
}
external nonreentrant function withdraw() is {
const amount : wei := storage.refunds[Evm.sender()];
storage.refunds[Evm.sender()] := 0wei;
defer Evm.transfer(Evm.sender(), amount);
}
external nonreentrant function settle()
raises NotEnded | AlreadySettled
is {
assert(Evm.blockTimestamp() >= storage.endsAt, NotEnded);
assert(not storage.settled, AlreadySettled);
storage.settled := true;
emit Settled { winner = storage.highestBidder; amount = storage.highestBid; };
if storage.highestBidder <> Address.zero then {
defer Evm.transfer(storage.seller, storage.highestBid);
}
}Refunds are pulled, not pushed. If bid sent the previous bidder's refund itself, a bidder whose receiver always reverts could block every later bid.

