Files
Fuma Nama eea1b224a6 refactor(fhe): unify API - reveal() replaces getDecryptResult()
- Update to unified FHE decryption API
- Update dependencies to @luxfi/contracts
2025-12-31 21:11:35 -08:00

207 lines
6.7 KiB
Solidity

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity >=0.8.13 <0.9.0;
import { Permissioned, Permission } from "@luxfi/contracts/fhe/access/Permissioned.sol";
import { Euint32, euint32, FHE } from "@luxfi/contracts/fhe/FHE.sol";
import { IFHERC20 } from "./IFHERC20.sol";
import "./ConfAddress.sol";
struct HistoryEntry {
euint32 amount;
bool refunded;
}
contract Auction is Permissioned {
address payable public auctioneer;
mapping(address => HistoryEntry) internal auctionHistory;
euint32 internal CONST_0_ENCRYPTED;
euint32 internal highestBid;
Eaddress internal defaultAddress;
Eaddress internal highestBidder;
euint32 internal eMaxEuint32;
uint256 public auctionEndTime;
IFHERC20 internal _wluxfhe;
address internal NO_BID_ADDRESS;
// When auction is ended this will contain the PLAINTEXT winner address
address public winnerAddress;
event AuctionEnded(address winner, uint32 bid);
constructor(address wluxfhe, uint256 biddingTime) payable {
_wluxfhe = IFHERC20(wluxfhe);
auctioneer = payable(msg.sender);
auctionEndTime = block.timestamp + biddingTime;
CONST_0_ENCRYPTED = FHE.asEuint32(0);
highestBid = CONST_0_ENCRYPTED;
NO_BID_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
for (uint i = 0; i < 5; i++) {
defaultAddress.values[i] = CONST_0_ENCRYPTED;
highestBidder.values[i] = CONST_0_ENCRYPTED;
}
eMaxEuint32 = FHE.asEuint32(0xFFFFFFFF);
}
// Modifiers
modifier onlyAuctioneer() {
require(msg.sender == auctioneer, "Only auctioneer can perform this action");
_;
}
modifier afterAuctionEnds() {
require(block.timestamp >= auctionEndTime, "Auction ongoing");
_;
}
modifier auctionNotEnded() {
require(winnerAddress == address(0), "Auction not ended");
_;
}
modifier auctionEnded() {
require(winnerAddress != address(0), "Auction already ended");
_;
}
modifier notWinner() {
require(msg.sender != winnerAddress, "Winner cannot perform this action");
_;
}
function updateHistory(address addr, euint32 currentBid) internal returns (euint32) {
// Check for overflow, if such, just don't change the actualBid
// NOTE: overflow is most likely an abnormal action so the funds WON'T be refunded!
if (!FHE.isInitialized(auctionHistory[addr].amount)) {
HistoryEntry memory entry;
entry.amount = currentBid;
entry.refunded = false;
auctionHistory[addr] = entry;
return auctionHistory[addr].amount;
}
// Checking overflow here is optional as in real-life precision would be accounted for.
ebool hadOverflow = FHE.lt(FHE.sub(eMaxEuint32, currentBid), auctionHistory[addr].amount);
euint32 actualBid = FHE.select(hadOverflow, CONST_0_ENCRYPTED, currentBid);
// Add the actual bid to the previous bid
// If there was no bid it will work because the default value of uint32 is encrypted 02
auctionHistory[addr].amount = FHE.add(auctionHistory[addr].amount, actualBid);
return auctionHistory[addr].amount;
}
function bid(Euint32 calldata amount)
external
auctionNotEnded
{
euint32 spent = _wluxfhe.transferFromEncrypted(msg.sender, address(this), amount);
euint32 newBid = updateHistory(msg.sender, spent);
// Can't update here highestBid directly because we need and indication whether the highestBid was changed
// if we will change here the highestBid
// we will have an edge case when the current bid will be equal to the highestBid
euint32 newHeighestBid = FHE.max(newBid, highestBid);
Eaddress memory eaddr = ConfAddress.toEaddress(payable(msg.sender));
ebool wasBidChanged = newHeighestBid.gt(highestBid);
highestBidder = ConfAddress.conditionalUpdate(wasBidChanged, highestBidder, eaddr);
highestBid = newHeighestBid;
}
/// @notice Request decryption of a user's bid (must wait for result)
function requestMyBidDecrypt(address account) external {
FHE.decrypt(auctionHistory[account].amount);
}
/// @notice Get previously requested bid decryption result
function getMyBidDebug(address account)
external
view
returns (uint32) {
return FHE.reveal(auctionHistory[account].amount);
}
/// @notice Get previously requested bid decryption result (with permission)
function getMyBid(address account, Permission memory auth)
external
view
onlyPermitted(auth, account)
returns (uint32) {
return FHE.reveal(auctionHistory[account].amount);
}
function getWinner()
external
view
auctionEnded
returns (address) {
return winnerAddress;
}
/// @notice Request decryption of the winning bid
function requestWinningBidDecrypt() external auctionEnded {
FHE.decrypt(highestBid);
}
function getWinningBid()
external
view
auctionEnded
returns (uint32, address) {
return (FHE.reveal(highestBid), winnerAddress);
}
function endAuction()
external
onlyAuctioneer
afterAuctionEnds
auctionNotEnded
{
winnerAddress = ConfAddress.unsafeToAddress(highestBidder);
if (winnerAddress == address(0)) {
winnerAddress = NO_BID_ADDRESS;
}
// Request decrypt for the highest bid
FHE.decrypt(highestBid);
// Get the decrypted result (requires decrypt to have been processed)
uint32 winningBid = FHE.reveal(highestBid);
// The cards can be revealed now, we can safely reveal the bidder
emit AuctionEnded(winnerAddress, winningBid);
}
// just for debugging purposes
function debugEndAuction()
public
onlyAuctioneer
auctionNotEnded
{
winnerAddress = ConfAddress.unsafeToAddress(highestBidder);
if (winnerAddress == address(0)) {
winnerAddress = NO_BID_ADDRESS;
}
// Request decrypt for the highest bid
FHE.decrypt(highestBid);
// Get the decrypted result (requires decrypt to have been processed)
uint32 winningBid = FHE.reveal(highestBid);
// The cards can be revealed now, we can safely reveal the bidder
emit AuctionEnded(winnerAddress, winningBid);
}
function redeemFunds()
external
notWinner
auctionEnded
{
require(!auctionHistory[msg.sender].refunded, "Already refunded");
euint32 toBeRedeemed = auctionHistory[msg.sender].amount;
auctionHistory[msg.sender].refunded = true;
_wluxfhe.transferEncrypted(msg.sender, toBeRedeemed);
}
}