Blockhash Lottery Slot Auction

blockhash-lottery-slot-auction · auction-allocation · 2957 B runtime · funds-movement

scores

source

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

contract BlockhashAuction {
    uint256 public immutable START_BLOCK;
    uint256 public immutable END_BLOCK;
    uint256 public constant MAX_BIDS = 64;

    struct Bid {
        address payable bidder;
        uint96 amount;
    }

    Bid[] private _bids;
    mapping(address => uint256) private _bidIndex;
    mapping(address => bool) public claimed;
    bool public settled;
    address payable public winner;
    uint256 public pot;

    event BidSubmitted(address indexed bidder, uint256 amount);
    event AuctionSettled(address indexed winner, uint256 pot);
    event WinningsClaimed(address indexed winner, uint256 amount);
    event Refunded(address indexed bidder, uint256 amount);

    constructor(uint256 duration) {
        require(duration > 0, "duration 0");
        START_BLOCK = block.number;
        END_BLOCK = block.number + duration;
    }

    function bid() external payable {
        require(block.number >= START_BLOCK && block.number < END_BLOCK, "not open");
        require(msg.value > 0, "zero bid");
        require(_bids.length < MAX_BIDS, "max bids");
        require(_bidIndex[msg.sender] == 0, "already bid");

        _bidIndex[msg.sender] = _bids.length + 1;
        _bids.push(Bid(payable(msg.sender), uint96(msg.value)));

        emit BidSubmitted(msg.sender, msg.value);
    }

    function settle() external {
        require(block.number > END_BLOCK, "not ended");
        require(!settled, "settled");
        require(_bids.length > 0, "no bids");

        bytes32 bhash = blockhash(END_BLOCK);
        require(bhash != bytes32(0), "blockhash expired");

        uint256 minHash = type(uint256).max;
        uint256 winnerIndex;
        uint256 total;

        for (uint256 i = 0; i < _bids.length; i++) {
            uint256 h = uint256(keccak256(abi.encodePacked(_bids[i].bidder, _bids[i].amount, bhash)));
            total += _bids[i].amount;
            if (h < minHash) {
                minHash = h;
                winnerIndex = i;
            }
        }

        winner = _bids[winnerIndex].bidder;
        pot = total;
        settled = true;

        emit AuctionSettled(winner, pot);
    }

    function claimWinnings() external {
        require(settled, "not settled");
        require(msg.sender == winner, "not winner");
        require(!claimed[msg.sender], "claimed");

        claimed[msg.sender] = true;
        uint256 amount = pot;
        emit WinningsClaimed(msg.sender, amount);
        _transfer(winner, amount);
    }

    function claimRefund() external {
        require(settled, "not settled");
        require(msg.sender != winner, "winner");
        uint256 idx = _bidIndex[msg.sender];
        require(idx != 0, "no bid");
        require(!claimed[msg.sender], "claimed");

        claimed[msg.sender] = true;
        uint256 amount = _bids[idx - 1].amount;
        emit Refunded(msg.sender, amount);
        _transfer(payable(msg.sender), amount);
    }

    function _transfer(address payable to, uint256 amount) private {
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "transfer failed");
    }
}