Lowest Unique Bid Auction

lowest-unique-bid-auction · auction-allocation · 2655 B runtime · funds-movement

scores

source

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

contract LowestUniqueBidAuction {
    uint256 public constant MIN_BID = 1;
    uint256 public constant MAX_BID = 100;

    uint256 public commitEnd;
    uint256 public revealEnd;
    bool public ended;

    address public winner;
    uint256 public winningBid;

    mapping(address => bytes32) private commitments;
    mapping(address => uint256) private bids;
    mapping(uint256 => uint256) private bidCount;
    mapping(uint256 => address) private uniqueBidder;
    mapping(address => bool) public refunded;

    event Committed(address indexed bidder, bytes32 commitment);
    event Revealed(address indexed bidder, uint256 bid);
    event Finalized(address winner, uint256 winningBid);
    event Refunded(address indexed bidder, uint256 amount);

    constructor(uint256 commitDuration, uint256 revealDuration) {
        require(commitDuration > 0 && revealDuration > 0, "durations");
        commitEnd = block.timestamp + commitDuration;
        revealEnd = commitEnd + revealDuration;
    }

    function commit(bytes32 commitment) external {
        require(block.timestamp < commitEnd, "commit closed");
        require(commitments[msg.sender] == bytes32(0), "already committed");
        require(commitment != bytes32(0), "zero commitment");
        commitments[msg.sender] = commitment;
        emit Committed(msg.sender, commitment);
    }

    function reveal(uint256 bid, bytes32 secret) external payable {
        require(block.timestamp >= commitEnd, "commit not ended");
        require(block.timestamp < revealEnd, "reveal closed");
        require(bid >= MIN_BID && bid <= MAX_BID, "bid out of range");
        require(msg.value == bid, "payment must equal bid");

        bytes32 commitment = commitments[msg.sender];
        require(commitment != bytes32(0), "no commitment");
        require(keccak256(abi.encodePacked(bid, secret)) == commitment, "invalid reveal");
        require(bids[msg.sender] == 0, "already revealed");

        bids[msg.sender] = bid;

        uint256 count = bidCount[bid] + 1;
        bidCount[bid] = count;
        if (count == 1) {
            uniqueBidder[bid] = msg.sender;
        }

        emit Revealed(msg.sender, bid);
    }

    function finalize() external {
        require(block.timestamp >= revealEnd, "reveal phase open");
        require(!ended, "already finalized");
        ended = true;

        for (uint256 v = MIN_BID; v <= MAX_BID; v++) {
            if (bidCount[v] == 1) {
                winningBid = v;
                winner = uniqueBidder[v];
                break;
            }
        }

        emit Finalized(winner, winningBid);
    }

    function refund() external {
        require(ended, "not finalized");
        require(msg.sender != winner, "winner cannot refund");

        uint256 amount = bids[msg.sender];
        require(amount != 0, "no bid");
        require(!refunded[msg.sender], "already refunded");

        refunded[msg.sender] = true;
        (bool ok, ) = payable(msg.sender).call{value: amount}("");
        require(ok, "refund failed");

        emit Refunded(msg.sender, amount);
    }
}