Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds comments to example contracts, improve example contracts documentation #570

Merged
merged 9 commits into from
Oct 22, 2024
6 changes: 4 additions & 2 deletions docs/tutorials/see-all-tutorials.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

### Code examples in GitHub

- [ERC-20](https://github.com/zama-ai/fhevm/blob/main/examples/EncryptedERC20.sol): A variation of the standard ERC20 smart contract that incorporates encrypted balances, providing additional privacy for token holders. -
- [ERC-20](https://github.com/zama-ai/fhevm/blob/main/examples/EncryptedERC20.sol): A variation of the standard ERC20 smart contract that incorporates encrypted balances, providing additional privacy for token holders.
- [Blind Auction](https://github.com/zama-ai/fhevm/blob/main/examples/BlindAuction.sol): A smart contract for conducting blind auctions where bids are encrypted and the winning bid remains private.

#### Legacy
For more information on individual contracts, see the [README](https://github.com/zama-ai/fhevm/blob/main/examples/README.md) in the examples folder.

#### Legacy - Not compatible with latest fhEVM

- [Governor DAO](https://github.com/zama-ai/fhevm/tree/main/examples/legacy/Governor): A DAO smart contract that facilitates governance decisions through encrypted voting
- [Decentralized ID](https://github.com/zama-ai/fhevm/tree/main/examples/legacy/Identity): A blockchain-based identity management system using smart contracts to store and manage encrypted personal data.
Expand Down
87 changes: 64 additions & 23 deletions examples/BlindAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,66 @@ import "./EncryptedERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "../gateway/GatewayCaller.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;

// Current highest bid.
/// @notice Current highest bid
euint64 private highestBid;

// ticket corresponding to the highest bid, used during reencryption to know if a user has won the bid
/// @notice Ticket corresponding to the highest bid
/// @dev Used during reencryption to know if a user has won the bid
euint64 private winningTicket;
uint64 private decryptedWinningTicket; // decryption of winningTicket, can be requested by anyone after auction ends

// ticket randomly sampled for each user
// WARNING : we assume probability of duplicated tickets is null (an improved implementation could simply sample 4 random euint64 tickets per user for negligible collision probability)
/// @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;

// Mapping from bidder to their bid value.
/// @notice Mapping from bidder to their bid value
mapping(address account => euint64 bidAmount) private bids;

// Number of bid
/// @notice Number of bids
uint256 public bidCounter;

// The token contract used for encrypted bids.
/// @notice The token contract used for encrypted bids
EncryptedERC20 public tokenContract;

// Whether the auction object has been claimed.
// WARNING : if there is a draw, only first highest bidder will get the prize (an improved implementation could handle this case differently)
/// @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;

// If the token has been transferred to the beneficiary
/// @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;

// The function has been called too early.
// Try again at `time`.
/// @notice Error thrown when a function is called too early
/// @dev Includes the time when the function can be called
error TooEarly(uint256 time);
// The function has been called too late.
// It cannot be called after `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,
EncryptedERC20 _tokenContract,
Expand All @@ -68,7 +85,10 @@ contract BlindAuction is Ownable2Step, GatewayCaller {
stoppable = isStoppable;
}

// Bid an `encryptedValue`.
/// @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 existingBid = bids[msg.sender];
Expand Down Expand Up @@ -122,39 +142,54 @@ contract BlindAuction is Ownable2Step, GatewayCaller {
TFHE.allow(userTicket, msg.sender);
}

// Returns the `account`'s encrypted bid, can be used in a reencryption request
/// @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;
}

// Returns the `account`'s encrypted ticket, can be used in a reencryption request, then compared to
// `decryptedWinningTicket` when auction ends, so the user could learn if he won the auction
/// @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;
}

// if `userTickets[account]` is an encryption of decryptedWinningTicket, then `account` won and can call `claim` succesfully
/// @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;
}

// Claim the object. Succeeds only if the caller was the first to get the highest bid.
/// @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);
Expand All @@ -165,15 +200,17 @@ contract BlindAuction is Ownable2Step, GatewayCaller {
TFHE.allow(bids[msg.sender], msg.sender);
}

// Transfer token to beneficiary
/// @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);
}

// Withdraw a bid from the auction to the caller once the auction has stopped.
/// @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]);
Expand All @@ -186,11 +223,15 @@ contract BlindAuction is Ownable2Step, GatewayCaller {
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);
_;
Expand Down
8 changes: 7 additions & 1 deletion examples/Counter.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;

// dummy contract for dummy transactions just to advance blocks
/// @notice A simple contract that maintains a single state variable 'value'
/// @dev This contract provides functionality to increment the 'value' and read its current value
contract Counter {
/// @notice State variable to keep track of the count
/// @dev Stored as a uint32 to save gas
uint32 value;

/// @notice Increases the value by 1 each time this function is called
function increment() public {
value += 1;
}

/// @notice Returns the current value of the counter
/// @return The current value as a uint32
function currentValue() public view returns (uint32) {
return value;
}
Expand Down
Loading
Loading