-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,012 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
|
||
pragma solidity ^0.8.24; | ||
|
||
import "fhevm/lib/TFHE.sol"; | ||
import "fhevm/gateway/GatewayCaller.sol"; | ||
import "@openzeppelin/contracts/access/Ownable2Step.sol"; | ||
import "./ConfidentialERC20.sol"; | ||
|
||
/// @notice Main contract for the blind auction | ||
contract BlindAuction is Ownable2Step, GatewayCaller { | ||
/// @notice Auction end time | ||
uint256 public endTime; | ||
|
||
/// @notice Address of the beneficiary | ||
address public beneficiary; | ||
|
||
/// @notice Current highest bid | ||
euint64 private highestBid; | ||
|
||
/// @notice Ticket corresponding to the highest bid | ||
/// @dev Used during reencryption to know if a user has won the bid | ||
euint64 private winningTicket; | ||
|
||
/// @notice Decryption of winningTicket | ||
/// @dev Can be requested by anyone after auction ends | ||
uint64 private decryptedWinningTicket; | ||
|
||
/// @notice Ticket randomly sampled for each user | ||
/// @dev WARNING: We assume probability of duplicated tickets is null | ||
/// @dev An improved implementation could sample 4 random euint64 tickets per user for negligible collision probability | ||
mapping(address account => euint64 ticket) private userTickets; | ||
|
||
/// @notice Mapping from bidder to their bid value | ||
mapping(address account => euint64 bidAmount) private bids; | ||
|
||
/// @notice Number of bids | ||
uint256 public bidCounter; | ||
|
||
/// @notice The token contract used for encrypted bids | ||
ConfidentialERC20 public tokenContract; | ||
|
||
/// @notice Flag indicating whether the auction object has been claimed | ||
/// @dev WARNING : If there is a draw, only the first highest bidder will get the prize | ||
/// An improved implementation could handle this case differently | ||
ebool private objectClaimed; | ||
|
||
/// @notice Flag to check if the token has been transferred to the beneficiary | ||
bool public tokenTransferred; | ||
|
||
/// @notice Flag to determine if the auction can be stopped manually | ||
bool public stoppable; | ||
|
||
/// @notice Flag to check if the auction has been manually stopped | ||
bool public manuallyStopped = false; | ||
|
||
/// @notice Error thrown when a function is called too early | ||
/// @dev Includes the time when the function can be called | ||
error TooEarly(uint256 time); | ||
|
||
/// @notice Error thrown when a function is called too late | ||
/// @dev Includes the time after which the function cannot be called | ||
error TooLate(uint256 time); | ||
|
||
/// @notice Constructor to initialize the auction | ||
/// @param _beneficiary Address of the beneficiary who will receive the highest bid | ||
/// @param _tokenContract Address of the EncryptedERC20 token contract used for bidding | ||
/// @param biddingTime Duration of the auction in seconds | ||
/// @param isStoppable Flag to determine if the auction can be stopped manually | ||
constructor( | ||
address _beneficiary, | ||
ConfidentialERC20 _tokenContract, | ||
uint256 biddingTime, | ||
bool isStoppable | ||
) Ownable(msg.sender) { | ||
beneficiary = _beneficiary; | ||
tokenContract = _tokenContract; | ||
endTime = block.timestamp + biddingTime; | ||
objectClaimed = TFHE.asEbool(false); | ||
TFHE.allow(objectClaimed,address(this)); | ||
tokenTransferred = false; | ||
bidCounter = 0; | ||
stoppable = isStoppable; | ||
} | ||
|
||
/// @notice Submit a bid with an encrypted value | ||
/// @dev Transfers tokens from the bidder to the contract | ||
/// @param encryptedValue The encrypted bid amount | ||
/// @param inputProof Proof for the encrypted input | ||
function bid(einput encryptedValue, bytes calldata inputProof) external onlyBeforeEnd { | ||
euint64 value = TFHE.asEuint64(encryptedValue, inputProof); | ||
|
||
euint64 sentBalance; | ||
if (TFHE.isInitialized(bids[msg.sender])) { | ||
euint64 existingBid = bids[msg.sender]; | ||
euint64 balanceBefore = tokenContract.balanceOf(address(this)); | ||
ebool isHigher = TFHE.lt(existingBid, value); | ||
euint64 toTransfer = TFHE.sub(value, existingBid); | ||
|
||
// Transfer only if bid is higher, also to avoid overflow from previous line | ||
euint64 amount = TFHE.select(isHigher, toTransfer, TFHE.asEuint64(0)); | ||
TFHE.allowTransient(amount, address(tokenContract)); | ||
tokenContract.transferFrom(msg.sender, address(this), amount); | ||
|
||
euint64 balanceAfter = tokenContract.balanceOf(address(this)); | ||
sentBalance = TFHE.sub(balanceAfter, balanceBefore); | ||
euint64 newBid = TFHE.add(existingBid, sentBalance); | ||
bids[msg.sender] = newBid; | ||
} else { | ||
bidCounter++; | ||
euint64 balanceBefore = tokenContract.balanceOf(address(this)); | ||
TFHE.allowTransient(value, address(tokenContract)); | ||
tokenContract.transferFrom(msg.sender, address(this), value); | ||
euint64 balanceAfter = tokenContract.balanceOf(address(this)); | ||
sentBalance = TFHE.sub(balanceAfter, balanceBefore); | ||
bids[msg.sender] = sentBalance; | ||
} | ||
euint64 currentBid = bids[msg.sender]; | ||
TFHE.allow(currentBid,address(this)); | ||
TFHE.allow(currentBid, msg.sender); | ||
|
||
euint64 randTicket = TFHE.randEuint64(); | ||
euint64 userTicket; | ||
if (TFHE.isInitialized(highestBid)) { | ||
userTicket = TFHE.select(TFHE.ne(sentBalance, 0), randTicket, userTickets[msg.sender]); // don't update ticket if sentBalance is null (or else winner sending an additional zero bid would lose the prize) | ||
} else { | ||
userTicket = randTicket; | ||
} | ||
userTickets[msg.sender] = userTicket; | ||
|
||
if (!TFHE.isInitialized(highestBid)) { | ||
highestBid = currentBid; | ||
winningTicket = userTicket; | ||
} else { | ||
ebool isNewWinner = TFHE.lt(highestBid, currentBid); | ||
highestBid = TFHE.select(isNewWinner, currentBid, highestBid); | ||
winningTicket = TFHE.select(isNewWinner, userTicket, winningTicket); | ||
} | ||
TFHE.allow(highestBid,address(this)); | ||
TFHE.allow(winningTicket,address(this)); | ||
TFHE.allow(userTicket, msg.sender); | ||
} | ||
|
||
/// @notice Get the encrypted bid of a specific account | ||
/// @dev Can be used in a reencryption request | ||
/// @param account The address of the bidder | ||
/// @return The encrypted bid amount | ||
function getBid(address account) external view returns (euint64) { | ||
return bids[account]; | ||
} | ||
|
||
/// @notice Manually stop the auction | ||
/// @dev Can only be called by the owner and if the auction is stoppable | ||
function stop() external onlyOwner { | ||
require(stoppable); | ||
manuallyStopped = true; | ||
} | ||
|
||
/// @notice Get the encrypted ticket of a specific account | ||
/// @dev Can be used in a reencryption request | ||
/// @param account The address of the bidder | ||
/// @return The encrypted ticket | ||
function ticketUser(address account) external view returns (euint64) { | ||
return userTickets[account]; | ||
} | ||
|
||
/// @notice Initiate the decryption of the winning ticket | ||
/// @dev Can only be called after the auction ends | ||
function decryptWinningTicket() public onlyAfterEnd { | ||
uint256[] memory cts = new uint256[](1); | ||
cts[0] = Gateway.toUint256(winningTicket); | ||
Gateway.requestDecryption(cts, this.setDecryptedWinningTicket.selector, 0, block.timestamp + 100, false); | ||
} | ||
|
||
/// @notice Callback function to set the decrypted winning ticket | ||
/// @dev Can only be called by the Gateway | ||
/// @param resultDecryption The decrypted winning ticket | ||
function setDecryptedWinningTicket(uint256, uint64 resultDecryption) public onlyGateway { | ||
decryptedWinningTicket = resultDecryption; | ||
} | ||
|
||
/// @notice Get the decrypted winning ticket | ||
/// @dev Can only be called after the winning ticket has been decrypted - if `userTickets[account]` is an encryption of decryptedWinningTicket, then `account` won and can call `claim` succesfully | ||
/// @return The decrypted winning ticket | ||
function getDecryptedWinningTicket() external view returns (uint64) { | ||
require(decryptedWinningTicket != 0, "Winning ticket has not been decrypted yet"); | ||
return decryptedWinningTicket; | ||
} | ||
|
||
/// @notice Claim the auction object | ||
/// @dev Succeeds only if the caller was the first to get the highest bid | ||
function claim() public onlyAfterEnd { | ||
ebool canClaim = TFHE.and(TFHE.eq(winningTicket, userTickets[msg.sender]), TFHE.not(objectClaimed)); | ||
objectClaimed = TFHE.or(canClaim, objectClaimed); | ||
TFHE.allow(objectClaimed,address(this)); | ||
euint64 newBid = TFHE.select(canClaim, TFHE.asEuint64(0), bids[msg.sender]); | ||
bids[msg.sender] = newBid; | ||
TFHE.allow(bids[msg.sender],address(this)); | ||
TFHE.allow(bids[msg.sender], msg.sender); | ||
} | ||
|
||
/// @notice Transfer the highest bid to the beneficiary | ||
/// @dev Can only be called once after the auction ends | ||
function auctionEnd() public onlyAfterEnd { | ||
require(!tokenTransferred); | ||
tokenTransferred = true; | ||
TFHE.allowTransient(highestBid, address(tokenContract)); | ||
tokenContract.transfer(beneficiary, highestBid); | ||
} | ||
|
||
/// @notice Withdraw a bid from the auction | ||
/// @dev Can only be called after the auction ends and by non-winning bidders | ||
function withdraw() public onlyAfterEnd { | ||
euint64 bidValue = bids[msg.sender]; | ||
ebool canWithdraw = TFHE.ne(winningTicket, userTickets[msg.sender]); | ||
euint64 amount = TFHE.select(canWithdraw, bidValue, TFHE.asEuint64(0)); | ||
TFHE.allowTransient(amount, address(tokenContract)); | ||
tokenContract.transfer(msg.sender, amount); | ||
euint64 newBid = TFHE.select(canWithdraw, TFHE.asEuint64(0), bids[msg.sender]); | ||
bids[msg.sender] = newBid; | ||
TFHE.allow(newBid,address(this)); | ||
TFHE.allow(newBid, msg.sender); | ||
} | ||
|
||
/// @notice Modifier to ensure function is called before auction ends | ||
/// @dev Reverts if called after the auction end time or if manually stopped | ||
modifier onlyBeforeEnd() { | ||
if (block.timestamp >= endTime || manuallyStopped == true) revert TooLate(endTime); | ||
_; | ||
} | ||
|
||
/// @notice Modifier to ensure function is called after auction ends | ||
/// @dev Reverts if called before the auction end time and not manually stopped | ||
modifier onlyAfterEnd() { | ||
if (block.timestamp < endTime && manuallyStopped == false) revert TooEarly(endTime); | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "fhevm/lib/TFHE.sol"; | ||
import "fhevm/gateway/GatewayCaller.sol"; | ||
|
||
contract DecryptionExample is GatewayCaller { | ||
// Store multiple encrypted random numbers | ||
euint8 public randomEncryptedNumber; | ||
euint8 public randomEncryptedNumber1; | ||
|
||
uint8 public randomNumber; | ||
uint8 public randomNumber1; | ||
|
||
function generateRandomNumber() external { | ||
randomEncryptedNumber = TFHE.randEuint8(); | ||
randomEncryptedNumber1 = TFHE.randEuint8(); | ||
|
||
TFHE.allow(randomEncryptedNumber, address(this)); | ||
TFHE.allow(randomEncryptedNumber1, address(this)); | ||
|
||
// Request decryption of the final vote tallies | ||
uint256[] memory cts = new uint256[](2); | ||
cts[0] = Gateway.toUint256(randomEncryptedNumber); | ||
cts[1] = Gateway.toUint256(randomEncryptedNumber1); | ||
|
||
Gateway.requestDecryption(cts, this.decryptionCallback.selector, 0, block.timestamp + 100, false); | ||
} | ||
|
||
function decryptionCallback( | ||
uint256 /*requestID*/, | ||
uint8 decryptedRandomNumber1, | ||
uint8 decryptedRandomNumber2 | ||
) public onlyGateway returns (bool) { | ||
// Update plaintext tallies with decrypted values | ||
randomNumber = decryptedRandomNumber1; | ||
randomNumber1 = decryptedRandomNumber2; | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "fhevm/lib/TFHE.sol"; | ||
|
||
contract HiddenRandomCards { | ||
// Store multiple encrypted random numbers | ||
euint8 public randomEncryptedNumber; | ||
euint8 public randomEncryptedNumber1; | ||
euint8 public randomEncryptedNumber2; | ||
|
||
function generateRandomNumber() external { | ||
randomEncryptedNumber = TFHE.randEuint8(); | ||
randomEncryptedNumber1 = TFHE.randEuint8(); | ||
randomEncryptedNumber2 = TFHE.randEuint8(); | ||
|
||
TFHE.allow(randomEncryptedNumber, address(this)); | ||
TFHE.allow(randomEncryptedNumber1, address(this)); | ||
TFHE.allow(randomEncryptedNumber2, address(this)); | ||
|
||
TFHE.allow(randomEncryptedNumber, msg.sender); | ||
TFHE.allow(randomEncryptedNumber1, msg.sender); | ||
TFHE.allow(randomEncryptedNumber2, msg.sender); | ||
} | ||
|
||
// Function to view encrypted random numbers | ||
function viewEncryptedRandomNumber() public view returns (euint8, euint8, euint8) { | ||
return (randomEncryptedNumber, randomEncryptedNumber1, randomEncryptedNumber2); | ||
} | ||
} |
Oops, something went wrong.