diff --git a/contracts/draft/FHERC20.sol b/contracts/draft/FHERC20.sol new file mode 100644 index 0000000..f851694 --- /dev/null +++ b/contracts/draft/FHERC20.sol @@ -0,0 +1,348 @@ +// solhint-disable no-empty-blocks +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19 <0.9.0; + +import {FHE, euint128, inEuint128} from "../FHE.sol"; +import {PermissionedV2, PermissionV2} from "./PermissionedV2.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IFHERC20} from "./IFHERC20.sol"; + +contract FHERC20 is IFHERC20, ERC20, PermissionedV2 { + // A mapping from address to an encrypted balance. + mapping(address => euint128) internal _encBalances; + // A mapping from address (owner) to a mapping of address (spender) to an encrypted amount. + mapping(address => mapping(address => euint128)) internal _encAllowances; + euint128 internal _encTotalSupply = FHE.asEuint128(0); + + constructor( + string memory name, + string memory symbol, + address permitV2 // TODO: Remove when PermitV2 deployed to fixed address + ) ERC20(name, symbol) PermissionedV2(permitV2, "FHERC20") {} + + /** + * @dev Returns the encrypted value of tokens in existence. + * + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encTotalSupply(uint256 _permitId) public virtual override withPermitRouter(_permitId) returns (euint128) { + return _encTotalSupply; + } + + /** + * @dev Returns the encrypted value of tokens in existence, sealed for the caller. + */ + function sealedTotalSupply( + PermissionV2 calldata permission + ) + public + view + virtual + override + withPermission(permission) + returns (string memory) + { + return _encTotalSupply.seal(permission.sealingKey); + } + + /** + * @dev Returns the value of the encrypted tokens owned by `account` + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encBalanceOf( + uint256 permitId + ) public virtual override withPermitRouter(permitId) returns (euint128) { + return _encBalances[permitIssuer]; + } + + /** + * @dev Returns the value of the encrypted tokens owned by the issuer of the PermitNft, sealed for the caller + */ + function sealedBalanceOf( + PermissionV2 calldata permission + ) + public + view + virtual + override + withPermission(permission) + returns (string memory) + { + return _encBalances[permission.issuer].seal(permission.sealingKey); + } + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * Accepts the value as inEuint128, more convenient for calls from EOAs. + * + * Returns a boolean value indicating whether the operation succeeded. + */ + function encTransfer( + address to, + inEuint128 calldata ieAmount + ) public virtual override returns (bool) { + _encTransfer(msg.sender, to, FHE.asEuint128(ieAmount)); + return true; + } + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + * + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encAllowance( + uint256 permitId, + address spender + ) public virtual override withPermitRouter(permitId) returns (euint128) { + return _encAllowances[permitIssuer][spender]; + } + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. Sealed for the caller. + * + * Permission issuer must be either the owner or spender. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function sealedAllowance( + PermissionV2 calldata permission, + address owner, + address spender + ) + public + view + virtual + override + withPermission(permission) + returns (string memory) + { + if (permission.issuer != owner && permission.issuer != spender) { + revert FHERC20NotOwnerOrSpender(); + } + return _encAllowances[owner][spender].seal(permission.sealingKey); + } + + /** + * @dev Sets `ieAmount` tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits an {EncApproved} event. + */ + function encApprove( + address spender, + inEuint128 calldata ieAmount + ) public virtual override returns (bool) { + _encApprove(msg.sender, spender, FHE.asEuint128(ieAmount)); + return true; + } + + /** + * @dev Moves `ieAmount` tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. Accepts the value as inEuint128, more convenient for calls from EOAs. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {EncTransfer} event. + */ + function encTransferFrom( + address from, + address to, + inEuint128 calldata ieAmount + ) public virtual override returns (bool) { + euint128 encSpent = _encSpendAllowance( + from, + msg.sender, + FHE.asEuint128(ieAmount) + ); + _encTransfer(from, to, encSpent); + return true; + } + + /** + * @dev Encrypts `amount` tokens, reducing the callers public balance by `amount`, + * and increasing their `encBalance` by `amount`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits an {Encrypted} event. + */ + function encrypt(uint128 amount) public virtual override returns (bool) { + _burn(msg.sender, amount); + _encMint(msg.sender, FHE.asEuint128(amount)); + + emit Encrypted(msg.sender, amount); + + return true; + } + + /** + * @dev Decrypts `amount` tokens, reducing the callers `encBalance` by `amount`, + * and increasing their public balance by `amount`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Decrypted} event. + */ + function decrypt(uint128 amount) public virtual override returns (bool) { + euint128 eAmount = _encBurn(msg.sender, FHE.asEuint128(amount)); + amount = FHE.decrypt(eAmount); + _mint(msg.sender, amount); + + emit Decrypted(msg.sender, amount); + + return true; + } + + /** + * @dev Moves `eAmount` tokens from the caller's account to `to`. + * Accepts the value as euint128, more convenient for calls from other contracts + * + * Returns an `euint128` of the true amount transferred. + * + * Emits an {EncTransfer} event. + */ + function _encTransfer( + address from, + address to, + euint128 eAmount + ) internal returns (euint128) { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + + // Make sure the sender has enough tokens. + eAmount = FHE.select( + eAmount.lte(_encBalances[from]), + eAmount, + FHE.asEuint128(0) + ); + + _encBeforeTokenTransfer(from, to, eAmount); + + // Add to the balance of `to` and subtract from the balance of `from`. + _encBalances[to] = _encBalances[to] + eAmount; + _encBalances[from] = _encBalances[from] - eAmount; + + emit EncTransfer(from, to); + + _encAfterTokenTransfer(from, to, eAmount); + + return eAmount; + } + + /** + * @dev Creates `eAmount` encrypted tokens and assigns them to `to`. + * Increases `encTotalSupply` by `eAmount` + * Accepts the value as euint128, more convenient for calls from other contracts + * + * Emits an {EncTransfer} event with `from` set to the zero address. + */ + function _encMint(address to, euint128 eAmount) internal { + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + + _encBeforeTokenTransfer(address(0), to, eAmount); + + _encBalances[to] = _encBalances[to] + eAmount; + _encTotalSupply = _encTotalSupply + eAmount; + + emit EncTransfer(address(0), to); + + _encAfterTokenTransfer(address(0), to, eAmount); + } + + /** + * @dev Destroys `eAmount` encrypted tokens from `to`. + * Decreases `encTotalSupply` by `eAmount` + * Accepts the value as euint128, more convenient for calls from other contracts + * + * Emits an {EncTransfer} event with `to` set to the zero address. + */ + function _encBurn( + address from, + euint128 eAmount + ) internal returns (euint128) { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + + eAmount = FHE.select( + _encBalances[msg.sender].gte(eAmount), + eAmount, + FHE.asEuint128(0) + ); + + _encBeforeTokenTransfer(from, address(0), eAmount); + + _encBalances[from] = _encBalances[from] - eAmount; + _encTotalSupply = _encTotalSupply - eAmount; + + emit EncTransfer(from, address(0)); + + _encAfterTokenTransfer(from, address(0), eAmount); + + return eAmount; + } + + function _encApprove( + address owner, + address spender, + euint128 eAmount + ) internal { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _encAllowances[owner][spender] = eAmount; + } + + function _encSpendAllowance( + address owner, + address spender, + euint128 eAmount + ) internal virtual returns (euint128) { + euint128 eCurrentAllowance = _encAllowances[owner][spender]; + euint128 eSpent = FHE.min(eCurrentAllowance, eAmount); + _encApprove(owner, spender, (eCurrentAllowance - eSpent)); + + return eSpent; + } + + /** + * @dev Hook that is called before any transfer of encrypted tokens. This includes + * minting and burning. + */ + function _encBeforeTokenTransfer( + address from, + address to, + euint128 eAmount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of encrypted tokens. This includes + * minting and burning. + */ + function _encAfterTokenTransfer( + address from, + address to, + euint128 eAmount + ) internal virtual {} +} diff --git a/contracts/draft/IFHERC20.sol b/contracts/draft/IFHERC20.sol new file mode 100644 index 0000000..7e9dc97 --- /dev/null +++ b/contracts/draft/IFHERC20.sol @@ -0,0 +1,167 @@ +pragma solidity >=0.8.19 <0.9.0; + +// SPDX-License-Identifier: MIT +// Fhenix Protocol (last updated v0.1.0) (token/FHERC20/IFHERC20.sol) +// Inspired by OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts) (token/ERC20/IERC20.sol) + +import {euint128, inEuint128} from "../FHE.sol"; +import {PermissionV2} from "./PermissionedV2.sol"; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + */ +interface IFHERC20 { + error FHERC20NotOwnerOrSpender(); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event EncTransfer(address indexed from, address indexed to); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approveEncrypted}. `value` is the new allowance. + */ + event EncApproved(address indexed owner, address indexed spender); + + /** + * @dev Emitted when `amount` tokens are transferred from `_balance[owner]` to `_encBalance[owner]` + * by a call to {encrypt}. + */ + event Encrypted(address indexed owner, uint256 amount); + + /** + * @dev Emitted when `amount` tokens are transferred from `_encBalance[owner]` to `_balance[owner]` + * by a call to {decrypt}. + */ + event Decrypted(address indexed owner, uint256 amount); + + /** + * @dev Returns the encrypted value of tokens in existence. + * + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encTotalSupply(uint256 permitId) external returns (euint128); + + /** + * @dev Returns the encrypted value of tokens in existence, sealed for the caller. + */ + function sealedTotalSupply( + PermissionV2 calldata permission + ) external view returns (string memory); + + /** + * @dev Returns the value of the encrypted tokens owned by `account` + * + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encBalanceOf(uint256 permitId) external returns (euint128); + + /** + * @dev Returns the value of the encrypted tokens owned by the issuer of the PermitNft, sealed for the caller + */ + function sealedBalanceOf( + PermissionV2 memory permission + ) external view returns (string memory); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * Accepts the value as inEuint128, more convenient for calls from EOAs. + * + * Returns a boolean value indicating whether the operation succeeded. + */ + function encTransfer( + address to, + inEuint128 calldata ieAmount + ) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + * + * @dev Designed to be used as part of a write tx + * @dev Can only be called by an authorized router (see PermitV2 documentation) + */ + function encAllowance( + uint256 permitId, + address spender + ) external returns (euint128); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. Sealed for the caller. + * + * Permission issuer must be either the owner or spender. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function sealedAllowance( + PermissionV2 memory permission, + address owner, + address spender + ) external view returns (string memory); + + /** + * @dev Sets `ieAmount` tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {ApprovalEncrypted} event. + */ + function encApprove( + address spender, + inEuint128 calldata ieAmount + ) external returns (bool); + + /** + * @dev Moves `ieAmount` tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. Accepts the value as inEuint128, more convenient for calls from EOAs. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {TransferEncrypted} event. + */ + function encTransferFrom( + address from, + address to, + inEuint128 calldata ieAmount + ) external returns (bool); + + /** + * @dev Encrypts `amount` tokens, reducing the callers public balance by `amount`, + * and increasing their `encBalance` by `amount`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Encrypted} event. + */ + function encrypt(uint128 amount) external returns (bool); + + /** + * @dev Decrypts `amount` tokens, reducing the callers `encBalance` by `amount`, + * and increasing their public balance by `amount`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Decrypted} event. + */ + function decrypt(uint128 amount) external returns (bool); +} diff --git a/contracts/draft/PermissionedV2.sol b/contracts/draft/PermissionedV2.sol index 3d47c36..c9756b1 100644 --- a/contracts/draft/PermissionedV2.sol +++ b/contracts/draft/PermissionedV2.sol @@ -46,16 +46,16 @@ abstract contract PermissionedV2 is IFhenixPermissionedV2 { } /// @dev Populated in txs when `withPermitRouter` is added as a fn modifier */ - address issuer = address(0); + address permitIssuer = address(0); /// @dev Ensures that the msg.sender is a router and user has granted router permissions - /// @dev Populates `issuer` with the owner of the permit + /// @dev Populates `permitIssuer` with the owner of the permit /// - /// @dev ** Only return the data of `issuer`! ** + /// @dev ** Only return the data of `permitIssuer`! ** modifier withPermitRouter(uint256 _permitId) { - issuer = PERMIT_V2.validatePermitRouter(_permitId, msg.sender); + permitIssuer = PERMIT_V2.validatePermitRouter(_permitId, msg.sender); _; - issuer = address(0); + permitIssuer = address(0); } // Utility functions to enable PermitV2 contract to be fully abstracted away