Defining functions

  1. Constructor

    • Constructor - Define the constructor to set the contract deployer as the token owner.

    // Constructor sets the deployer as the token owner and defines the token details
    constructor() Ownable(msg.sender) {
        _name = "Confidential USD";
        _symbol = "CUSD";
    }
  2. Minting Tokens:

    • The mint function creates new tokens and updates the owner's balance. We use TFHE operations to add tokens to the owner's balance.

    • _mint allows the contract owner to mint tokens in a confidential manner.

    // Mint function for the owner to create new tokens and update balance
    function mint(uint64 mintedAmount) public virtual onlyOwner {
        balances[owner()] = TFHE.add(balances[owner()], mintedAmount);
        TFHE.allow(balances[owner()], address(this));
        TFHE.allow(balances[owner()], owner());
        _totalSupply += mintedAmount;
        emit Mint(owner(), mintedAmount);
    }
    
    // _mint function allows owner to mint encrypted tokens in a confidential manner
    function _mint(einput encryptedAmount, bytes calldata inputProof) public virtual onlyOwner {
        balances[msg.sender] = TFHE.add(balances[msg.sender], TFHE.asEuint64(encryptedAmount, inputProof)); 
        TFHE.allow(balances[msg.sender], address(this));
        TFHE.allow(balances[msg.sender], owner());
        TFHE.allow(balances[msg.sender], msg.sender);
    }
  3. Transferring Tokens:

    The transfer function allows token holders to send tokens to another address. Two transfer() function versions exist, each with the same name but differing function signatures for different use cases.

    1. transfer(address, einput, bytes): This version is used by externally owned accounts (EOA) to call the function, forming einput and inputProof using the fhevmjs library on the client side. Learn more about this here.

    2. transfer(address, euint64): This version is used by other contracts to interact with our confidential ERC20 contract.

    // Transfer tokens to another address with encrypted amount (for EOAs)
    function transfer(address to, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) {
        transfer(to, TFHE.asEuint64(encryptedAmount, inputProof));
        return true;
    }
    
    // Transfer tokens to another address (for contracts)
    function transfer(address to, euint64 amount) public virtual returns (bool) {
        require(TFHE.isSenderAllowed(amount));
        ebool canTransfer = TFHE.le(amount, balances[msg.sender]);
        _transfer(msg.sender, to, amount, canTransfer);
        return true;
    }
    
    // Internal transfer function with encrypted conditional logic
    function _transfer(address from, address to, euint64 amount, ebool isTransferable) internal virtual {
        euint64 transferValue = TFHE.select(isTransferable, amount, TFHE.asEuint64(0));
        euint64 newBalanceTo = TFHE.add(balances[to], transferValue);
        balances[to] = newBalanceTo;
        TFHE.allow(newBalanceTo, address(this));
        TFHE.allow(newBalanceTo, to);
    
        euint64 newBalanceFrom = TFHE.sub(balances[from], transferValue);
        balances[from] = newBalanceFrom;
        TFHE.allow(newBalanceFrom, address(this));
        TFHE.allow(newBalanceFrom, from);
    
        emit Transfer(from, to);
    }
  4. Approving Tokens:

    The approve function allows a token holder to set an allowance, specifying the encryptedAmount that a spender is permitted to be used on behalf of the caller. There are two versions of approve() with the same name but different function signatures, similar to the transfer() function: one for EOAs and the other for contracts.

    // Sets encrypted allowance for a spender (for EOAs)
    function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) {
        approve(spender, TFHE.asEuint64(encryptedAmount, inputProof));
        return true;
    }
    
    // Sets allowance amount for a spender (for contracts)
    function approve(address spender, euint64 amount) public virtual returns (bool) {
        require(TFHE.isSenderAllowed(amount));
        _approve(msg.sender, spender, amount);
        emit Approval(msg.sender, spender);
        return true;
    }
    
    // Internal function to set allowance with necessary permissions
    function _approve(address owner, address spender, euint64 amount) internal virtual {
        allowances[owner][spender] = amount;
        TFHE.allow(amount, address(this));
        TFHE.allow(amount, owner);
        TFHE.allow(amount, spender);
    }
  5. Transferring Tokens from Another Address:

    The transferFrom function allows a spender to transfer tokens on behalf of another account, utilizing an approved allowance. There are two versions of transferFrom() with the same name but different function signatures, similar to the transfer() function: one for EOAs and the other for contracts.

    // Transfer tokens on behalf of another address (for EOAs)
    function transferFrom(address from, address to, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) {
        transferFrom(from, to, TFHE.asEuint64(encryptedAmount, inputProof));
        return true;
    }
    
    // Transfer tokens on behalf of another address (for contracts)
    function transferFrom(address from, address to, euint64 amount) public virtual returns (bool) {
        require(TFHE.isSenderAllowed(amount));
        ebool isTransferable = _updateAllowance(from, msg.sender, amount);
        _transfer(from, to, amount, isTransferable);
        return true;
    }
    
    // Internal function to check allowance and update it securely
    function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) {
        euint64 currentAllowance = _allowance(owner, spender);
        ebool allowedTransfer = TFHE.le(amount, currentAllowance);
        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;
    }
  6. Viewing encrypted balances and allowances:

    1. Balances:

      The balanceOf function returns the handle to the ciphertext representing a user's balance. This handle can be used on the client side to retrieve the actual balance by leveraging the fhevmJs library and making a call to the Gateway. Refer to this guide.

      // Returns the handle to the caller's encrypted balance
      function balanceOf(address wallet) public view virtual returns (euint64) {
          return balances[wallet];
      }
    2. Allowances:

      The allowance function returns the handle to the ciphertext representing the amount that a specified spender is allowed to spend on behalf of the owner. This handle can be used on the client side to retrieve the allowance amount by leveraging the fhevmJs library and making a call to the Gateway.Refer to this guide.

        // Returns the handle to the encrypted allowance for a spender
      function allowance(address owner, address spender) public view virtual returns (euint64) {
          return _allowance(owner, spender);
      }
  7. Decrypting Account Balances: Owner Access and Permissions

    The contract owner() or deployer can access any balance and request global decryption for user balances when needed.

    // Requests decryption of a user's balance, accessible by the owner
    function requestUserBalanceDecryption(address user) public onlyOwner returns (uint256) {
        euint64 encryptedBalance = balances[user];
        TFHE.allow(encryptedBalance, address(this));
    
        uint256[] memory cts = new uint256[](1);
        cts[0] = Gateway.toUint256(encryptedBalance);
    
        uint256 requestId = Gateway.requestDecryption(
            cts,
            this.onDecryptionCallback.selector,
            0,
            block.timestamp + 100,
            false
        );
        addParamsAddress(requestId, user);
        return requestId;
    }
    
    // Callback function to handle decrypted balance
    function onDecryptionCallback(uint256 requestId, uint64 decryptedAmount) public onlyGateway returns (bool) {
        address[] memory params = getParamsAddress(requestId);
        emit UserBalanceDecrypted(params[0], decryptedAmount);
        return true;
    }

Last updated