Skip to content

Commit

Permalink
Added All Example Contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
DevSwayam committed Nov 14, 2024
1 parent fb70706 commit 352398a
Show file tree
Hide file tree
Showing 15 changed files with 1,012 additions and 211 deletions.
238 changes: 238 additions & 0 deletions contracts/BlindAuction.sol
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

Check failure on line 31 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Line length must be no more than 120 but current length is 123
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));

Check failure on line 80 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
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);

Check failure on line 92 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Delete ········
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));

Check failure on line 119 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
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)

Check failure on line 125 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Line length must be no more than 120 but current length is 214
} 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));

Check failure on line 139 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
TFHE.allow(winningTicket,address(this));

Check failure on line 140 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
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

Check failure on line 183 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Line length must be no more than 120 but current length is 199
/// @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));

Check failure on line 195 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
euint64 newBid = TFHE.select(canClaim, TFHE.asEuint64(0), bids[msg.sender]);
bids[msg.sender] = newBid;
TFHE.allow(bids[msg.sender],address(this));

Check failure on line 198 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Insert ·
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;

Check warning on line 220 in contracts/BlindAuction.sol

View workflow job for this annotation

GitHub Actions / ci

Possible reentrancy vulnerabilities. Avoid state changes after transfer
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);
_;
}
}
40 changes: 40 additions & 0 deletions contracts/DecrytionExample.sol
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;
}
}
30 changes: 30 additions & 0 deletions contracts/HiddenRandomCards.sol
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);
}
}
Loading

0 comments on commit 352398a

Please sign in to comment.