diff --git a/docs/tutorials/see-all-tutorials.md b/docs/tutorials/see-all-tutorials.md index d6f58116..eeecdd27 100644 --- a/docs/tutorials/see-all-tutorials.md +++ b/docs/tutorials/see-all-tutorials.md @@ -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. diff --git a/examples/BlindAuction.sol b/examples/BlindAuction.sol index c54a2e47..7f01c15f 100644 --- a/examples/BlindAuction.sol +++ b/examples/BlindAuction.sol @@ -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, @@ -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]; @@ -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); @@ -165,7 +200,8 @@ 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; @@ -173,7 +209,8 @@ contract BlindAuction is Ownable2Step, GatewayCaller { 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]); @@ -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); _; diff --git a/examples/Counter.sol b/examples/Counter.sol index 3f353ef6..03a66058 100644 --- a/examples/Counter.sol +++ b/examples/Counter.sol @@ -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; } diff --git a/examples/EncryptedERC20.sol b/examples/EncryptedERC20.sol index 016d56e7..7c96b0d7 100644 --- a/examples/EncryptedERC20.sol +++ b/examples/EncryptedERC20.sol @@ -5,44 +5,58 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; +/// @notice This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. +/// @dev It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. contract EncryptedERC20 is Ownable2Step { + /// @notice Emitted when tokens are transferred event Transfer(address indexed from, address indexed to); + /// @notice Emitted when a spender is approved to spend tokens on behalf of an owner event Approval(address indexed owner, address indexed spender); + /// @notice Emitted when new tokens are minted event Mint(address indexed to, uint64 amount); + /// @dev Stores the total supply of the token uint64 private _totalSupply; + /// @dev Name of the token (e.g., "Confidential Token") string private _name; + /// @dev Symbol of the token (e.g., "CTK") string private _symbol; + /// @notice Number of decimal places for the token uint8 public constant decimals = 6; - // A mapping from address to an encrypted balance. + /// @dev A mapping from address to an encrypted balance - tracks encrypted balances of each address mapping(address => euint64) internal balances; - // A mapping of the form mapping(owner => mapping(spender => allowance)). + /// @dev Mapping to manage encrypted allowance - of the form mapping(owner => mapping(spender => allowance)). mapping(address => mapping(address => euint64)) internal allowances; + /// @notice Constructor to initialize the token's name and symbol, and set up the owner + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { - TFHE.setFHEVM(FHEVMConfig.defaultConfig()); + TFHE.setFHEVM(FHEVMConfig.defaultConfig()); // Set up the FHEVM configuration for this contract _name = name_; _symbol = symbol_; } - // Returns the name of the token. + /// @notice Returns the name of the token. function name() public view virtual returns (string memory) { return _name; } - // Returns the symbol of the token, usually a shorter version of the name. + /// @notice Returns the symbol of the token, usually a shorter version of the name. function symbol() public view virtual returns (string memory) { return _symbol; } - // Returns the total supply of the token + /// @notice Returns the total supply of the token function totalSupply() public view virtual returns (uint64) { return _totalSupply; } - // Sets the balance of the owner to the given encrypted balance. + /// @notice Mints new tokens and assigns them to the owner, increasing the total supply. + /// @dev Only the contract owner can call this function. + /// @param mintedAmount The amount of tokens to mint function mint(uint64 mintedAmount) public virtual onlyOwner { balances[owner()] = TFHE.add(balances[owner()], mintedAmount); // overflow impossible because of next line TFHE.allowThis(balances[owner()]); @@ -51,33 +65,49 @@ contract EncryptedERC20 is Ownable2Step { emit Mint(owner(), mintedAmount); } - // Transfers an encrypted amount from the message sender address to the `to` address. + /// @notice Transfers an encrypted amount from the message sender address to the `to` address. + /// @param to The recipient address + /// @param encryptedAmount The encrypted amount to transfer + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the transfer function transfer(address to, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { transfer(to, TFHE.asEuint64(encryptedAmount, inputProof)); return true; } - // Transfers an amount from the message sender address to the `to` address. + /// @notice Transfers an encrypted amount from the message sender address to the `to` address. + /// @param to The recipient address + /// @param amount The encrypted amount to transfer + /// @return bool indicating success of the transfer function transfer(address to, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); - // makes sure the owner has enough tokens + /// @dev Makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[msg.sender]); _transfer(msg.sender, to, amount, canTransfer); return true; } - // Returns the balance handle of the caller. + /// @notice Returns the balance handle (encrypted) of a specific address. + /// @param wallet The address to check the balance of + /// @return euint64 The encrypted balance of the address function balanceOf(address wallet) public view virtual returns (euint64) { return balances[wallet]; } - // Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens. + /// @notice Sets the allowance of `spender` to use a specific encrypted amount of the caller's tokens. + /// @param spender The address authorized to spend + /// @param encryptedAmount The encrypted amount to approve + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the approval function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { approve(spender, TFHE.asEuint64(encryptedAmount, inputProof)); return true; } - // Sets the `amount` as the allowance of `spender` over the caller's tokens. + /// @notice Sets the allowance of `spender` to use a specific amount of the caller's tokens. + /// @param spender The address authorized to spend + /// @param amount The amount to approve + /// @return bool indicating success of the approval function approve(address spender, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); address owner = msg.sender; @@ -86,13 +116,20 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Returns the remaining number of tokens that `spender` is allowed to spend - // on behalf of the caller. + /// @notice Returns the remaining number of tokens that `spender` is allowed to spend on behalf of the caller. + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @return euint64 The remaining allowance function allowance(address owner, address spender) public view virtual returns (euint64) { return _allowance(owner, spender); } - // Transfers `encryptedAmount` tokens using the caller's allowance. + /// @notice Transfers `encryptedAmount` tokens using the caller's allowance. + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param encryptedAmount The encrypted amount to transfer + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the transfer function transferFrom( address from, address to, @@ -103,7 +140,11 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Transfers `amount` tokens using the caller's allowance. + /// @notice Transfers `amount` tokens using the caller's allowance. + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param amount The amount to transfer + /// @return bool indicating success of the transfer function transferFrom(address from, address to, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); address spender = msg.sender; @@ -112,6 +153,11 @@ contract EncryptedERC20 is Ownable2Step { return true; } + /// @notice Internal function to approve a spender to use a specific amount. + /// @dev Updates the allowance mapping and sets appropriate permissions + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @param amount The amount to approve function _approve(address owner, address spender, euint64 amount) internal virtual { allowances[owner][spender] = amount; TFHE.allowThis(amount); @@ -119,24 +165,39 @@ contract EncryptedERC20 is Ownable2Step { TFHE.allow(amount, spender); } + /// @notice Returns the internal allowance of a spender for a specific owner. + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @return euint64 The current allowance function _allowance(address owner, address spender) internal view virtual returns (euint64) { return allowances[owner][spender]; } + /// @notice Updates the allowance after a transfer and returns whether it is valid. + /// @dev Checks if the transfer is allowed based on current allowance and balance + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @param amount The amount of the proposed transfer + /// @return ebool indicating whether the transfer is allowed function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) { euint64 currentAllowance = _allowance(owner, spender); - // makes sure the allowance suffices + /// @dev Makes sure the allowance suffices ebool allowedTransfer = TFHE.le(amount, currentAllowance); - // makes sure the owner has enough tokens + /// @dev Makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[owner]); ebool isTransferable = TFHE.and(canTransfer, allowedTransfer); _approve(owner, spender, TFHE.select(isTransferable, TFHE.sub(currentAllowance, amount), currentAllowance)); return isTransferable; } - // Transfers an encrypted amount. + /// @notice Internal function to handle the transfer of tokens between addresses. + /// @dev Updates balances and sets appropriate permissions + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param amount The amount to transfer + /// @param isTransferable Boolean indicating if the transfer is allowed function _transfer(address from, address to, euint64 amount, ebool isTransferable) internal virtual { - // Add to the balance of `to` and subract from the balance of `from`. + /// @dev Add to the balance of `to` and subract from the balance of `from`. euint64 transferValue = TFHE.select(isTransferable, amount, TFHE.asEuint64(0)); euint64 newBalanceTo = TFHE.add(balances[to], transferValue); balances[to] = newBalanceTo; diff --git a/examples/PaymentLimit.sol b/examples/PaymentLimit.sol index 85d9adff..d484ca06 100644 --- a/examples/PaymentLimit.sol +++ b/examples/PaymentLimit.sol @@ -6,14 +6,19 @@ import "../lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import "../payment/Payment.sol"; +/// @title PaymentLimit +/// @notice A contract to demonstrate FHE gas limits in different scenarios contract PaymentLimit { + /// @notice Constructor that sets up FHE configuration and deposits initial value + /// @dev Payable to allow initial deposit constructor() payable { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); Payment.depositForThis(msg.value); } + /// @notice Performs a small number of FHE operations + /// @dev Should pass if it's the only transaction in a block function wayunderBlockFHEGasLimit() external { - // should pass if only tx in block euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 3; i++) { @@ -21,8 +26,9 @@ contract PaymentLimit { } } + /// @notice Performs a moderate number of FHE operations + /// @dev Should pass if it's the only transaction in a block function underBlockFHEGasLimit() external { - // should pass if only tx in block euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 15; i++) { @@ -30,8 +36,9 @@ contract PaymentLimit { } } + /// @notice Performs a large number of FHE operations + /// @dev Should revert due to exceeding the block FHE gas limit function aboveBlockFHEGasLimit() external { - // should revert due to exceeding block fheGas limit euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 16; i++) { diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..fa611c4d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,84 @@ +# FhEVM smart contract examples + +This directory contains example contracts that demonstrate the usage of the fhEVM (Fully Homomorphic Encryption Virtual Machine) smart contract library. These contracts showcase various features and functionalities of encrypted computations on the blockchain, enabling privacy-preserving operations and opening up new possibilities for confidential blockchain applications. + +## Quick overview + +| Contract Name | Description | +| -------------------- | ---------------------------------------- | +| EncryptedERC20.sol | ERC20-like token with encrypted balances | +| TestAsyncDecrypt.sol | Asynchronous decryption testing | +| BlindAuction.sol | Blind auction using encrypted bids | + +## Usage + +These contracts serve as examples and can be used as references when building your own fhEVM-compatible smart contracts. Make sure to have the necessary fhEVM library and dependencies set up in your development environment. + +For more information, refer to the [fhEVM documentation](https://docs.zama.ai/fhevm). + +## Contract summaries + +### 1. **EncryptedERC20.sol** + +An implementation of an ERC20-like token with encrypted balances and transfers. This contract demonstrates: + +- Encrypted token balances +- Private transfer operations +- Allowance management with encryption + +It showcases how traditional token systems can be made confidential using FHE techniques, allowing for private balance management on a public blockchain. + +```mermaid +graph TD + subgraph User Inputs + X1(Encrypted Amount) + X2(Encrypted Allowance) + end + subgraph Contract Logic + Y1[Check Allowance & Balance] + Y2[Update Encrypted Allowance] + Y3[Transfer Encrypted Amount] + end + X1 --> Y1 + X2 --> Y1 + Y1 --> Y2 + Y1 --> Y3 +``` + +### 2. **TestAsyncDecrypt.sol** + +Tests asynchronous decryption of various encrypted data types using the Gateway. This contract is essential for understanding how to safely decrypt data when needed, without compromising the overall security of the encrypted system. + +### 3. **BlindAuction.sol** + +Implements a blind auction system using encrypted bids. Key features include: + +- Encrypted bid submission +- Timed auction periods +- Winner determination without revealing losing bids +- Claim and withdrawal mechanisms + +This contract showcases how FHE can be used to create fair and private auction systems on the blockchain, ensuring bid confidentiality until the auction ends. + +```mermaid +graph TD + subgraph Bidding Phase + A[User Submits Encrypted Bid] + B[Contract Stores Encrypted Bid] + C[Update Highest Bid & Winning Ticket] + end + subgraph Auction End + D[Decrypt Winning Ticket] + E[Winner Claims Prize] + F[Non-Winners Withdraw Bids] + G[Transfer Highest Bid to Beneficiary] + end + A --> B + B --> C + C --> |Auction Ends| D + D --> E + D --> F + D --> G +``` + +This diagram illustrates the main processes in the BlindAuction contract, from bid submission to the final distribution of funds and prizes. diff --git a/examples/Rand.sol b/examples/Rand.sol index a02f118a..7593abbd 100644 --- a/examples/Rand.sol +++ b/examples/Rand.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +/// @notice Contract for generating random encrypted numbers contract Rand { + /// @notice Encrypted unsigned integers of various sizes ebool public valueb; euint4 public value4; euint8 public value8; @@ -18,10 +20,12 @@ contract Rand { ebytes128 public value1024; ebytes256 public value2048; + /// @notice Constructor to set FHE configuration constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); } + /// @notice Generate random 8-bit encrypted unsigned integer function generateBool() public { valueb = TFHE.randEbool(); TFHE.allowThis(valueb); @@ -42,31 +46,40 @@ contract Rand { TFHE.allowThis(value8); } + /// @notice Generate random 8-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate8UpperBound(uint8 upperBound) public { value8 = TFHE.randEuint8(upperBound); TFHE.allowThis(value8); } + /// @notice Generate random 16-bit encrypted unsigned integer function generate16() public { value16 = TFHE.randEuint16(); TFHE.allowThis(value16); } + /// @notice Generate random 16-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate16UpperBound(uint16 upperBound) public { value16 = TFHE.randEuint16(upperBound); TFHE.allowThis(value16); } + /// @notice Generate random 32-bit encrypted unsigned integer function generate32() public { value32 = TFHE.randEuint32(); TFHE.allowThis(value32); } + /// @notice Generate random 32-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate32UpperBound(uint32 upperBound) public { value32 = TFHE.randEuint32(upperBound); TFHE.allowThis(value32); } + /// @notice Generate random 64-bit encrypted unsigned integer function generate64() public { value64 = TFHE.randEuint64(); TFHE.allowThis(value64); @@ -77,12 +90,15 @@ contract Rand { TFHE.allowThis(value64); } + /// @notice Generate random 64-bit encrypted unsigned integer with error handling + /// @dev This function attempts a failing call and then generates a bounded random number function generate64Reverting() public { try this.failingCall() {} catch {} value64Bounded = TFHE.randEuint64(1024); TFHE.allowThis(value64Bounded); } + // Function that always reverts after generating a random number function failingCall() public { value64 = TFHE.randEuint64(); TFHE.allowThis(value64); diff --git a/examples/Reencrypt.sol b/examples/Reencrypt.sol index 1c6175a3..949ceea6 100644 --- a/examples/Reencrypt.sol +++ b/examples/Reencrypt.sol @@ -4,12 +4,19 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +/// @notice Contract for demonstrating reencryption of various FHE data types contract Reencrypt { + /// @dev Encrypted boolean ebool public xBool; + /// @dev Encrypted 4-bit unsigned integer euint4 public xUint4; + /// @dev Encrypted 8-bit unsigned integer euint8 public xUint8; + /// @dev Encrypted 16-bit unsigned integer euint16 public xUint16; + /// @dev Encrypted 32-bit unsigned integer euint32 public xUint32; + /// @dev Encrypted 64-bit unsigned integer euint64 public xUint64; euint128 public xUint128; eaddress public xAddress; @@ -18,29 +25,37 @@ contract Reencrypt { ebytes128 public yBytes128; ebytes256 public yBytes256; + /// @notice Constructor to initialize encrypted values and set permissions constructor() { + // Set default FHE configuration TFHE.setFHEVM(FHEVMConfig.defaultConfig()); + // Initialize and set permissions for xBool xBool = TFHE.asEbool(true); TFHE.allowThis(xBool); TFHE.allow(xBool, msg.sender); + // Initialize and set permissions for xUint4 xUint4 = TFHE.asEuint4(4); TFHE.allowThis(xUint4); TFHE.allow(xUint4, msg.sender); + // Initialize and set permissions for xUint8 xUint8 = TFHE.asEuint8(42); TFHE.allowThis(xUint8); TFHE.allow(xUint8, msg.sender); + // Initialize and set permissions for xUint16 xUint16 = TFHE.asEuint16(16); TFHE.allowThis(xUint16); TFHE.allow(xUint16, msg.sender); + // Initialize and set permissions for xUint32 xUint32 = TFHE.asEuint32(32); TFHE.allowThis(xUint32); TFHE.allow(xUint32, msg.sender); + // Initialize and set permissions for xUint64 xUint64 = TFHE.asEuint64(18446744073709551600); TFHE.allowThis(xUint64); TFHE.allow(xUint64, msg.sender); diff --git a/examples/Regression1.sol b/examples/Regression1.sol index b8ae90eb..2bf26a2f 100644 --- a/examples/Regression1.sol +++ b/examples/Regression1.sol @@ -1,21 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; +/// @notice Contract for managing services and metadata contract Regression1 { error IndexOutOfBound(); + + /// @dev Struct to store metadata information struct Metadata { uint256 created; uint256 lastUpdated; uint256 versionId; } + + /// @dev Struct to represent a service struct Service { bytes32 id; string serviceType; string serviceEndpoint; } + + /// @notice Mapping to store metadata for each address mapping(address id => Metadata) public metadata; + + /// @dev Mapping to store services for each address mapping(address id => Service[] service) private _services; + /// @notice Function to add services function addServices(Service[] calldata services) public { for (uint256 i = 0; i < services.length; i++) { _services[msg.sender].push(services[i]); @@ -25,6 +35,7 @@ contract Regression1 { metadata[msg.sender].versionId = 1; } + /// @notice Function to remove a service function removeService(uint256 serviceIndex) public { if (serviceIndex >= _services[msg.sender].length) revert IndexOutOfBound(); for (uint256 i = serviceIndex; i < _services[msg.sender].length - 1; i++) { @@ -35,6 +46,9 @@ contract Regression1 { metadata[msg.sender].versionId++; } + /// @notice Function to get services for a given address + /// @param id The address to get services for + /// @return An array of Service structs function getServices(address id) public view returns (Service[] memory) { return _services[id]; } diff --git a/examples/SmartAccount.sol b/examples/SmartAccount.sol index b7749eac..9655dfc0 100644 --- a/examples/SmartAccount.sol +++ b/examples/SmartAccount.sol @@ -3,19 +3,26 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/access/Ownable2Step.sol"; +/// @notice Smart account contract with batch transaction execution contract SmartAccount is Ownable2Step { + /// @dev Structure to represent a transaction struct Transaction { address target; uint256 value; bytes data; } + /// @notice Constructor to set the initial owner constructor() Ownable(msg.sender) {} + /// @notice Function to execute multiple transactions in a batch + /// @param transactions Array of transactions to execute function executeBatch(Transaction[] memory transactions) public payable onlyOwner { for (uint i = 0; i < transactions.length; i++) { Transaction memory transaction = transactions[i]; + /// @dev Execute the transaction (bool success, ) = transaction.target.call{value: transaction.value}(transaction.data); + /// @dev Ensure the transaction was successful require(success, "Transaction failed"); } } diff --git a/examples/TFHEExecutorUpgradedExample.sol b/examples/TFHEExecutorUpgradedExample.sol index cf439ebe..e0e83211 100644 --- a/examples/TFHEExecutorUpgradedExample.sol +++ b/examples/TFHEExecutorUpgradedExample.sol @@ -4,17 +4,20 @@ pragma solidity ^0.8.24; import "../lib/TFHEExecutor.sol"; +/// @title TFHEExecutorUpgradedExample +/// @dev Contract that extends TFHEExecutor with version information contract TFHEExecutorUpgradedExample is TFHEExecutor { - /// @notice Name of the contract + /// @dev Name of the contract string private constant CONTRACT_NAME = "TFHEExecutor"; - /// @notice Version of the contract + /// @dev Version numbers uint256 private constant MAJOR_VERSION = 0; uint256 private constant MINOR_VERSION = 2; uint256 private constant PATCH_VERSION = 0; - /// @notice Getter for the name and version of the contract - /// @return string representing the name and the version of the contract + /// @notice Returns the full version string of the contract + /// @dev Concatenates the contract name and version numbers + /// @return A string representing the full version of the contract function getVersion() external pure virtual override returns (string memory) { return string( diff --git a/examples/TestAsyncDecrypt.sol b/examples/TestAsyncDecrypt.sol index a393863f..ce1fab68 100644 --- a/examples/TestAsyncDecrypt.sol +++ b/examples/TestAsyncDecrypt.sol @@ -5,7 +5,9 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "../gateway/GatewayCaller.sol"; +/// @notice Contract for testing asynchronous decryption using the Gateway contract TestAsyncDecrypt is GatewayCaller { + /// @dev Encrypted state variables ebool xBool; euint4 xUint4; euint8 xUint8; @@ -19,6 +21,7 @@ contract TestAsyncDecrypt is GatewayCaller { eaddress xAddress2; euint256 xUint256; + /// @dev Decrypted state variables bool public yBool; uint8 public yUint4; uint8 public yUint8; @@ -35,11 +38,15 @@ contract TestAsyncDecrypt is GatewayCaller { bytes public yBytes128; bytes public yBytes256; + /// @dev Tracks the latest decryption request ID uint256 public latestRequestID; + /// @notice Constructor to initialize the contract and set up encrypted values constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); Gateway.setGateway(Gateway.defaultGatewayAddress()); + + /// @dev Initialize encrypted variables with sample values xBool = TFHE.asEbool(true); TFHE.allowThis(xBool); xUint4 = TFHE.asEuint4(4); @@ -66,12 +73,14 @@ contract TestAsyncDecrypt is GatewayCaller { TFHE.allowThis(xAddress2); } + /// @notice Function to request decryption of a boolean value with an infinite loop in the callback function requestBoolInfinite() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBoolInfinite.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for the infinite loop decryption request (WARNING: This function will never complete) function callbackBoolInfinite(uint256 /*requestID*/, bool decryptedInput) public onlyGateway returns (bool) { uint256 i = 0; while (true) { @@ -81,22 +90,27 @@ contract TestAsyncDecrypt is GatewayCaller { return yBool; } + /// @notice Function to request decryption with an excessive delay (should revert) function requestBoolAboveDelay() public { - // should revert + /// @dev This should revert due to the excessive delay uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 2 days, false); } + /// @notice Request decryption of a boolean value function requestBool() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); + /// @dev Request decryption with a 100-second deadline and non-trustless mode Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } + /// @notice Request decryption of a boolean value in trustless mode function requestBoolTrustless() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); + /// @dev Request decryption with a 100-second deadline and trustless mode (true) uint256 requestID = Gateway.requestDecryption( cts, this.callbackBoolTrustless.selector, @@ -105,84 +119,114 @@ contract TestAsyncDecrypt is GatewayCaller { true ); latestRequestID = requestID; + /// @dev Save the requested handles for later verification saveRequestedHandles(requestID, cts); } + /// @notice Attempt to request decryption of a fake boolean value (should revert) function requestFakeBool() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000000); - Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); // this should revert because previous ebool is not honestly obtained + /// @dev This should revert because the previous ebool is not honestly obtained + Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for non-trustless boolean decryption function callbackBool(uint256, bool decryptedInput) public onlyGateway returns (bool) { yBool = decryptedInput; return yBool; } + /// @notice Callback function for trustless boolean decryption function callbackBoolTrustless( uint256 requestID, bool decryptedInput, bytes[] memory signatures ) public onlyGateway returns (bool) { + /// @dev Verify that the requestID matches the latest request require(latestRequestID == requestID, "wrong requestID passed by Gateway"); + /// @dev Load the previously saved handles for verification uint256[] memory requestedHandles = loadRequestedHandles(latestRequestID); + /// @dev Verify the signatures provided by the KMS (Key Management Service) bool isKMSVerified = Gateway.verifySignatures(requestedHandles, signatures); require(isKMSVerified, "KMS did not verify this decryption result"); + /// @dev If verification passes, store the decrypted value yBool = decryptedInput; return yBool; } + /// @notice Request decryption of a 4-bit unsigned integer function requestUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint4); Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); } + /// @notice Attempt to request decryption of a fake 4-bit unsigned integer (should revert) function requestFakeUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000100); - Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 4-bit unsigned integer decryption + /// @param decryptedInput The decrypted 4-bit unsigned integer + /// @return The decrypted value function callbackUint4(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint4 = decryptedInput; return decryptedInput; } + /// @notice Request decryption of an 8-bit unsigned integer function requestUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint8); Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); } + /// @notice Attempt to request decryption of a fake 8-bit unsigned integer (should revert) function requestFakeUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000200); - Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 8-bit unsigned integer decryption + /// @param decryptedInput The decrypted 8-bit unsigned integer + /// @return The decrypted value function callbackUint8(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint8 = decryptedInput; return decryptedInput; } + /// @notice Request decryption of a 16-bit unsigned integer function requestUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint16); Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); } + /// @notice Attempt to request decryption of a fake 16-bit unsigned integer (should revert) function requestFakeUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000300); - Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 16-bit unsigned integer decryption + /// @param decryptedInput The decrypted 16-bit unsigned integer + /// @return The decrypted value function callbackUint16(uint256, uint16 decryptedInput) public onlyGateway returns (uint16) { yUint16 = decryptedInput; return decryptedInput; } + /// @notice Request decryption of a 32-bit unsigned integer with additional inputs + /// @param input1 First additional input + /// @param input2 Second additional input function requestUint32(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint32); @@ -197,12 +241,18 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } + /// @notice Attempt to request decryption of a fake 32-bit unsigned integer (should revert) function requestFakeUint32() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000400); - Gateway.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 32-bit unsigned integer decryption + /// @param requestID The ID of the decryption request + /// @param decryptedInput The decrypted 32-bit unsigned integer + /// @return The result of the computation function callbackUint32(uint256 requestID, uint32 decryptedInput) public onlyGateway returns (uint32) { uint256[] memory params = getParamsUint256(requestID); unchecked { @@ -212,18 +262,24 @@ contract TestAsyncDecrypt is GatewayCaller { } } + /// @notice Request decryption of a 64-bit unsigned integer function requestUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint64); Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } + /// @notice Attempt to request decryption of a fake 64-bit unsigned integer (should revert) function requestFakeUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000500); - Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } + /// @notice Request decryption of a non-trivial 64-bit unsigned integer + /// @param inputHandle The input handle for the encrypted value + /// @param inputProof The input proof for the encrypted value function requestUint64NonTrivial(einput inputHandle, bytes calldata inputProof) public { euint64 inputNonTrivial = TFHE.asEuint64(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -231,6 +287,9 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 64-bit unsigned integer decryption + /// @param decryptedInput The decrypted 64-bit unsigned integer + /// @return The decrypted value function callbackUint64(uint256, uint64 decryptedInput) public onlyGateway returns (uint64) { yUint64 = decryptedInput; return decryptedInput; @@ -324,17 +383,22 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackBytes256.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for 256-bit encrypted bytes decryption + /// @param decryptedInput The decrypted 256-bit bytes + /// @return The decrypted value function callbackBytes256(uint256, bytes calldata decryptedInput) public onlyGateway returns (bytes memory) { yBytes256 = decryptedInput; return decryptedInput; } + /// @notice Request decryption of an encrypted address function requestAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xAddress); Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); } + /// @notice Request decryption of multiple encrypted addresses function requestSeveralAddresses() public { uint256[] memory cts = new uint256[](2); cts[0] = Gateway.toUint256(xAddress); @@ -342,6 +406,10 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackAddresses.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for multiple address decryption + /// @param decryptedInput1 The first decrypted address + /// @param decryptedInput2 The second decrypted address + /// @return The first decrypted address function callbackAddresses( uint256 /*requestID*/, address decryptedInput1, @@ -352,17 +420,26 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput1; } + /// @notice Attempt to request decryption of a fake address (should revert) function requestFakeAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000700); - Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for address decryption + /// @param decryptedInput The decrypted address + /// @return The decrypted address function callbackAddress(uint256, address decryptedInput) public onlyGateway returns (address) { yAddress = decryptedInput; return decryptedInput; } + /// @notice Request decryption of multiple encrypted data types + /// @dev This function demonstrates how to request decryption for various encrypted data types in a single call + /// @param input1 First additional input parameter for the callback function + /// @param input2 Second additional input parameter for the callback function function requestMixed(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](10); cts[0] = Gateway.toUint256(xBool); @@ -386,6 +463,20 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } + /// @notice Callback function for mixed data type decryption + /// @dev Processes the decrypted values and performs some basic checks + /// @param requestID The ID of the decryption request + /// @param decBool_1 First decrypted boolean + /// @param decBool_2 Second decrypted boolean + /// @param decUint4 Decrypted 4-bit unsigned integer + /// @param decUint8 Decrypted 8-bit unsigned integer + /// @param decUint16 Decrypted 16-bit unsigned integer + /// @param decUint32 Decrypted 32-bit unsigned integer + /// @param decUint64_1 First decrypted 64-bit unsigned integer + /// @param decUint64_2 Second decrypted 64-bit unsigned integer + /// @param decUint64_3 Third decrypted 64-bit unsigned integer + /// @param decAddress Decrypted address + /// @return The decrypted 4-bit unsigned integer function callbackMixed( uint256 requestID, bool decBool_1, @@ -415,6 +506,10 @@ contract TestAsyncDecrypt is GatewayCaller { return yUint4; } + /// @notice Request decryption of mixed data types including 256-bit encrypted bytes + /// @dev Demonstrates how to include encrypted bytes256 in a mixed decryption request + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestMixedBytes256(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](4); @@ -426,6 +521,11 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackMixedBytes256.selector, 0, block.timestamp + 100, false); } + /// @notice Callback function for mixed data type decryption including 256-bit encrypted bytes + /// @dev Processes and stores the decrypted values + /// @param decBool Decrypted boolean + /// @param decAddress Decrypted address + /// @param bytesRes Decrypted 256-bit bytes function callbackMixedBytes256( uint256, bool decBool, @@ -439,6 +539,10 @@ contract TestAsyncDecrypt is GatewayCaller { yBytes64 = bytesRes2; } + /// @notice Request trustless decryption of non-trivial 256-bit encrypted bytes + /// @dev Demonstrates how to request trustless decryption for complex encrypted bytes256 + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestEbytes256NonTrivialTrustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 inputNonTrivial = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -454,6 +558,12 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } + /// @notice Callback function for trustless decryption of 256-bit encrypted bytes + /// @dev Verifies the decryption result using KMS signatures + /// @param requestID The ID of the decryption request + /// @param decryptedInput The decrypted bytes256 value + /// @param signatures The signatures from the KMS for verification + /// @return The decrypted bytes256 value function callbackBytes256Trustless( uint256 requestID, bytes calldata decryptedInput, @@ -467,13 +577,16 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput; } + /// @notice Request trustless decryption of mixed data types including 256-bit encrypted bytes + /// @dev Demonstrates how to request trustless decryption for multiple data types + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestMixedBytes256Trustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](3); cts[0] = Gateway.toUint256(xBool); cts[1] = Gateway.toUint256(xBytes256); cts[2] = Gateway.toUint256(xAddress); - Gateway.requestDecryption(cts, this.callbackMixedBytes256Trustless.selector, 0, block.timestamp + 100, true); uint256 requestID = Gateway.requestDecryption( cts, this.callbackMixedBytes256Trustless.selector, @@ -485,6 +598,13 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } + /// @notice Callback function for trustless decryption of mixed data types including 256-bit encrypted bytes + /// @dev Verifies and processes the decrypted values + /// @param requestID The ID of the decryption request + /// @param decBool Decrypted boolean + /// @param bytesRes Decrypted 256-bit bytes + /// @param decAddress Decrypted address + /// @param signatures The signatures from the KMS for verification function callbackMixedBytes256Trustless( uint256 requestID, bool decBool, diff --git a/examples/TracingSubCalls.sol b/examples/TracingSubCalls.sol index 65f9c243..f1c2e587 100644 --- a/examples/TracingSubCalls.sol +++ b/examples/TracingSubCalls.sol @@ -3,72 +3,100 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +/// @notice Main contract for testing various subcalls and their behaviors contract TracingSubCalls { + /// @notice Executes a series of subcalls to test different scenarios + /// @dev This function attempts various contract creations and function calls, + /// catching any errors to ensure the main execution continues function subCalls() external { - try new SubContractCreate(400) {} catch {} // 0K + try new SubContractCreate(400) {} catch {} // OK try new SubContractCreateFail(500) {} catch {} SubContract subc = new SubContract(); try subc.succeed() {} catch {} // OK 601 try subc.fail() {} catch {} try subc.succeedFail() {} catch {} // OK only for first 603 try subc.failSucceed() {} catch {} - try subc.oogFail{gas: 100000}() {} catch {} // this should fail out-of-gas + try subc.oogFail{gas: 100000}() {} catch {} // This should fail out-of-gas try subc.succeed2() {} catch {} // OK 608 try subc.invalidFail() {} catch {} try subc.succeedStop() {} catch {} // OK 610 try subc.succeedSelfDestruct() {} catch {} // OK 611 - try new SubContractCreate{salt: keccak256("aaa")}(700) {} catch {} // 0K 700 + try new SubContractCreate{salt: keccak256("aaa")}(700) {} catch {} // OK 700 try new SubContractCreateFail{salt: keccak256("aaa")}(800) {} catch {} } } +/// @notice Contract that creates a new instance with an encrypted input contract SubContractCreate { + /// @dev Constructor that encrypts the input + /// @param input The value to be encrypted constructor(uint256 input) { TFHE.asEuint64(input); } } +/// @notice Contract that attempts to create a new instance but always fails contract SubContractCreateFail { + /// @dev Constructor that encrypts the input and then fails + /// @param input The value to be encrypted before failing constructor(uint256 input) { TFHE.asEuint64(input); - require(false); + require(false, "This constructor always fails"); } } +/// @notice Contract with various test functions for success and failure scenarios contract SubContract { + /// @notice Function that always succeeds + /// @dev Encrypts a specific value (601) function succeed() external { TFHE.asEuint64(601); } + /// @notice Function that always fails + /// @dev Encrypts a value (602) before failing function fail() external { TFHE.asEuint64(602); - require(false); + require(false, "This function always fails"); } + /// @notice Internal function that fails with a custom input + /// @dev Encrypts the input before failing + /// @param input The value to be encrypted before failing function fail2(uint input) external { TFHE.asEuint64(input); - require(false); + require(false, "This function always fails with custom input"); } + /// @notice Function that succeeds and then calls a failing function + /// @dev Encrypts a value (603) and then attempts to call fail2 function succeedFail() external { TFHE.asEuint64(603); try this.fail2(604) {} catch {} } + /// @notice Function that attempts to fail and then succeed + /// @dev Calls fail2 and then attempts to encrypt a value (606) function failSucceed() external { this.fail2(605); TFHE.asEuint64(606); } + /// @notice Function that runs out of gas + /// @dev Encrypts a value (607) and then enters an infinite loop function oogFail() external { TFHE.asEuint64(607); while (true) {} } + /// @notice Another function that always succeeds + /// @dev Encrypts a specific value (608) function succeed2() external { TFHE.asEuint64(608); } + /// @notice Function that fails with an invalid operation + /// @dev Encrypts a value (609) and then executes an invalid operation function invalidFail() external { TFHE.asEuint64(609); assembly { @@ -76,6 +104,8 @@ contract SubContract { } } + /// @notice Function that succeeds and then stops execution + /// @dev Encrypts a value (610) and then stops the execution function succeedStop() external { TFHE.asEuint64(610); assembly { @@ -83,6 +113,8 @@ contract SubContract { } } + /// @notice Function that succeeds and then self-destructs the contract + /// @dev Encrypts a value (611) and then self-destructs the contract function succeedSelfDestruct() external { TFHE.asEuint64(611); selfdestruct(payable(address(1)));