// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; import { FHE, euint64, euint8, ebool, eaddress, externalEuint64, externalEuint8, externalEbool, externalEaddress } from "@fhevm/solidity/lib/FHE.sol"; import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol"; /// @title Auction - Sealed Bid Auction /// @notice Encrypted auction where bids remain private until the auction ends /// @dev Generated with Lab-Z Composable Templates contract Auction is ZamaEthereumConfig { // ============ Errors ============ /// @dev Auction has already ended error AuctionEnded(); /// @dev Auction has not ended yet error AuctionNotEnded(); /// @dev Auction already finalized error AuctionAlreadyFinalized(); /// @dev User has already placed a bid error AlreadyBid(address bidder); /// @dev No bids were placed error NoBids(); /// @dev Winner reveal not requested yet error RevealNotRequested(); /// @dev Winner already revealed error AlreadyRevealed(); /// @dev Invalid decryption proof error InvalidDecryptionProof(); // ============ Events ============ /// @notice Emitted when a bid is placed (amount is encrypted) event BidPlaced(address indexed bidder); /// @notice Emitted when winner reveal is ready for decryption event WinnerReadyForReveal(); /// @notice Emitted when winner is revealed event WinnerRevealed(address indexed winner); /// @notice Emitted when auction ends (after winner claimed) event AuctionFinalized(address indexed winner); // ============ State Variables ============ /// @dev Auction item description string private _itemDescription; /// @dev When the auction ends uint256 private _auctionEndTime; /// @dev Encrypted highest bid euint64 private _highestBid; /// @dev Encrypted highest bidder address eaddress private _highestBidder; /// @dev Track encrypted bids per address mapping(address => euint64) private _bids; /// @dev Track if address has bid mapping(address => bool) private _hasBid; /// @dev Number of bidders uint256 private _bidderCount; /// @dev Whether auction is finalized bool private _finalized; /// @dev Whether reveal has been requested bool private _revealRequested; /// @dev Whether winner has been revealed bool private _revealed; /// @dev Winner address (set after finalization) address private _winner; // ============ Modifiers ============ /// @dev Ensures auction is still active modifier onlyDuringAuction() { if (block.timestamp > _auctionEndTime) { revert AuctionEnded(); } _; } /// @dev Ensures auction has ended modifier onlyAfterAuction() { if (block.timestamp <= _auctionEndTime) { revert AuctionNotEnded(); } _; } // ============ Constructor ============ constructor( string memory itemDescription, uint256 durationSeconds ) { _itemDescription = itemDescription; _auctionEndTime = block.timestamp + durationSeconds; _highestBid = FHE.asEuint64(0); _highestBidder = FHE.asEaddress(address(0)); FHE.allowThis(_highestBid); FHE.allowThis(_highestBidder); } // ============ External Functions ============ /// @notice Place an encrypted bid /// @param encryptedBid Encrypted bid amount /// @param inputProof Zero-knowledge proof for the input function bid( externalEuint64 encryptedBid, bytes calldata inputProof ) external onlyDuringAuction { if (_hasBid[msg.sender]) { revert AlreadyBid(msg.sender); } // Convert external input to internal encrypted value euint64 bidAmount = FHE.fromExternal(encryptedBid, inputProof); // Store the bid _bids[msg.sender] = bidAmount; _hasBid[msg.sender] = true; _bidderCount++; // Compare with current highest (encrypted comparison) ebool isHigher = FHE.gt(bidAmount, _highestBid); // Update highest bid if this bid is higher _highestBid = FHE.select(isHigher, bidAmount, _highestBid); _highestBidder = FHE.select(isHigher, FHE.asEaddress(msg.sender), _highestBidder); // Set ACL permissions FHE.allowThis(_bids[msg.sender]); FHE.allowThis(_highestBid); FHE.allowThis(_highestBidder); FHE.allow(_bids[msg.sender], msg.sender); emit BidPlaced(msg.sender); } /// @notice Request winner reveal via public decryption /// @dev Step 1 of 3-step async public decryption pattern function requestWinnerReveal() external onlyAfterAuction { if (_revealRequested) revert AlreadyRevealed(); if (_bidderCount == 0) revert NoBids(); _revealRequested = true; // Mark the encrypted winner address for public decryption FHE.makePubliclyDecryptable(_highestBidder); emit WinnerReadyForReveal(); } /// @notice Get encrypted winner handle for off-chain decryption /// @dev Step 2 is off-chain: use relayer-sdk to decrypt function getWinnerHandle() external view onlyAfterAuction returns (eaddress) { return _highestBidder; } /// @notice Finalize winner reveal with decryption proof /// @dev Step 3 of 3-step async public decryption pattern /// @param winnerAddress The decrypted winner address /// @param decryptionProof The proof from Zama KMS function finalizeWinnerReveal( address winnerAddress, bytes calldata decryptionProof ) external onlyAfterAuction { if (!_revealRequested) revert RevealNotRequested(); if (_revealed) revert AlreadyRevealed(); // Verify the decryption proof bytes32[] memory cts = new bytes32[](1); cts[0] = eaddress.unwrap(_highestBidder); bytes memory cleartexts = abi.encode(winnerAddress); // This reverts if proof is invalid FHE.checkSignatures(cts, cleartexts, decryptionProof); // Store the revealed winner _winner = winnerAddress; _revealed = true; emit WinnerRevealed(winnerAddress); } /// @notice Finalize the auction after winner is revealed function finalizeAuction() external { if (!_revealed) revert RevealNotRequested(); if (_finalized) revert AuctionAlreadyFinalized(); _finalized = true; emit AuctionFinalized(_winner); } // ============ View Functions ============ /// @notice Get auction item description /// @return The item description function itemDescription() external view returns (string memory) { return _itemDescription; } /// @notice Get auction end time /// @return The end timestamp function auctionEndTime() external view returns (uint256) { return _auctionEndTime; } /// @notice Check if an address has placed a bid /// @param bidder The address to check /// @return Whether the address has bid function hasBid(address bidder) external view returns (bool) { return _hasBid[bidder]; } /// @notice Get number of bidders /// @return The bidder count function bidderCount() external view returns (uint256) { return _bidderCount; } /// @notice Check if auction is finalized /// @return Whether the auction is finalized function isFinalized() external view returns (bool) { return _finalized; } /// @notice Check if reveal has been requested /// @return Whether reveal is requested function isRevealRequested() external view returns (bool) { return _revealRequested; } /// @notice Check if winner has been revealed /// @return Whether winner is revealed function isRevealed() external view returns (bool) { return _revealed; } /// @notice Get the revealed winner address /// @return The winner address (only valid after reveal) function getWinner() external view returns (address) { if (!_revealed) revert RevealNotRequested(); return _winner; } /// @notice Get encrypted bid for an address (requires ACL permission) /// @param bidder The bidder address /// @return The encrypted bid amount function getBid(address bidder) external view returns (euint64) { return _bids[bidder]; } /// @notice Get encrypted highest bid (requires ACL permission) /// @return The encrypted highest bid function getHighestBid() external view returns (euint64) { return _highestBid; } // ============ Internal Functions ============ }