Latency-Bonded Slot Auction

latency-bonded-slot-auction · auction-allocation · 2652 B runtime · funds-movement

scores

source

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract LatencyBondedSlotAuction {
    address payable public immutable owner;

    struct Slot {
        address payable bidder;
        uint256 delay;
        uint256 stake;
        bool allocated;
        bool confirmed;
        uint256 allocatedAt;
    }

    mapping(uint256 => Slot) public slots;

    event Bid(uint256 indexed slotId, address indexed bidder, uint256 delay, uint256 stake);
    event Allocated(uint256 indexed slotId, address indexed bidder, uint256 delay);
    event Confirmed(uint256 indexed slotId, address indexed bidder);
    event Slashed(uint256 indexed slotId, address indexed bidder, uint256 stake);

    modifier onlyOwner() {
        require(msg.sender == owner, "only owner");
        _;
    }

    constructor() {
        owner = payable(msg.sender);
    }

    // Place or replace a bid for a slot with a lower delay.
    // Sends msg.value as new stake; refunds previous bidder's stake if replaced.
    function bid(uint256 slotId, uint256 delay) external payable {
        require(msg.value > 0, "stake required");
        require(delay > 0, "delay must be positive");
        Slot storage s = slots[slotId];
        require(!s.allocated, "slot already allocated");
        require(s.bidder == address(0) || delay < s.delay, "must offer a lower delay");

        address payable prevBidder = s.bidder;
        uint256 prevStake = s.stake;

        s.bidder = payable(msg.sender);
        s.delay = delay;
        s.stake = msg.value;

        if (prevBidder != address(0)) {
            (bool ok, ) = prevBidder.call{value: prevStake}("");
            require(ok, "refund failed");
        }

        emit Bid(slotId, msg.sender, delay, msg.value);
    }

    // Lock the current bidder as the winner of the slot.
    function allocate(uint256 slotId) external onlyOwner {
        Slot storage s = slots[slotId];
        require(s.bidder != address(0), "no bidder");
        require(!s.allocated, "already allocated");
        s.allocated = true;
        s.allocatedAt = block.timestamp;
        emit Allocated(slotId, s.bidder, s.delay);
    }

    // Winner confirms within the promised delay and gets their stake back.
    function confirm(uint256 slotId) external {
        Slot storage s = slots[slotId];
        require(s.allocated, "not allocated");
        require(!s.confirmed, "already confirmed");
        require(msg.sender == s.bidder, "not the winner");
        require(block.timestamp <= s.allocatedAt + s.delay, "confirmation too late");

        s.confirmed = true;
        uint256 stake = s.stake;
        s.stake = 0;

        (bool ok, ) = msg.sender.call{value: stake}("");
        require(ok, "transfer failed");
        emit Confirmed(slotId, msg.sender);
    }

    // Anyone can slash the winner's stake after the promised delay has passed.
    function slash(uint256 slotId) external {
        Slot storage s = slots[slotId];
        require(s.allocated, "not allocated");
        require(!s.confirmed, "already confirmed");
        require(block.timestamp > s.allocatedAt + s.delay, "too early to slash");

        address oldBidder = s.bidder;
        uint256 stake = s.stake;

        delete s.bidder;
        s.delay = 0;
        s.stake = 0;
        s.allocated = false;
        s.confirmed = false;
        s.allocatedAt = 0;

        (bool ok, ) = owner.call{value: stake}("");
        require(ok, "transfer failed");
        emit Slashed(slotId, oldBidder, stake);
    }
}