├── .github └── workflows │ ├── docgen.yml │ └── release.yml ├── .gitignore ├── .solhint.json ├── .solhintignore ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── contracts ├── FHE.sol ├── FheOS.sol ├── access │ ├── EIP712.sol │ ├── Permissioned.sol │ └── PermissionedV2.sol ├── experimental │ ├── token │ │ ├── FHERC20 │ │ │ ├── FHERC20.sol │ │ │ └── IFHERC20.sol │ │ └── FHERC721 │ │ │ ├── FHERC721.sol │ │ │ ├── IFHERC721.sol │ │ │ └── mocks │ │ │ ├── CallReceiverMock.sol │ │ │ └── ERC721ReceiverMock.sol │ └── utils │ │ └── BytesLib.sol ├── package.json ├── test │ ├── PermissionedV2Counter.sol │ ├── PermissionedV2Validators.sol │ └── TypedSealedOutputsTest.sol └── utils │ └── debug │ ├── Console.sol │ └── MockFheOps.sol ├── docs └── .gitplaceholder ├── hardhat.config.ts ├── package.json ├── pnpm-lock.yaml ├── scripts └── genConsole.ts ├── test ├── experimental │ └── token │ │ ├── FHERC20 │ │ ├── ERC20.behavior.ts │ │ ├── ERC20.test.ts │ │ ├── FHERC20.behavior.ts │ │ └── FHERC20.test.ts │ │ └── FHERC721 │ │ ├── ERC721.behavior.ts │ │ ├── ERC721.test.ts │ │ └── FHERC721.behavior.ts ├── fhe │ └── typedSealedOutputs.ts ├── helpers │ ├── SupportsInterface.behavior.ts │ ├── customError.ts │ └── enums.ts └── permitV2 │ ├── PermissionedV2.test.ts │ ├── chain-utils.ts │ └── permit-v2-utils.ts └── tsconfig.json /.github/workflows/docgen.yml: -------------------------------------------------------------------------------- 1 | name: Generate Docs 2 | 3 | on: 4 | push: 5 | branches: main 6 | 7 | jobs: 8 | doc: 9 | runs-on: ubuntu-latest 10 | 11 | permissions: 12 | contents: write 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.pull_request.head.ref }} 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: '20.x' 21 | registry-url: 'https://registry.npmjs.org' 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | version: 9 27 | run_install: false 28 | 29 | - name: Install dependencies 30 | run: pnpm install --frozen-lockfile 31 | - name: Generate Docs 32 | run: | 33 | pnpm compile && pnpm docgen 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: . 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: 20.x 13 | - name: Install pnpm 14 | uses: pnpm/action-setup@v4 15 | with: 16 | version: latest 17 | run_install: false 18 | - name: Install deps 19 | run: pnpm install --frozen-lockfile 20 | - name: Set publishing config 21 | run: cd contracts && pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | - run: cp README.md contracts/README.md 25 | 26 | - name: Read package version 27 | id: package_version 28 | run: echo "VERSION=$(jq -r .version < ./contracts/package.json)" >> $GITHUB_ENV 29 | 30 | - name: Determine prerelease tag 31 | id: prerelease_check 32 | run: | 33 | if [[ "${{ env.VERSION }}" =~ \-(alpha|beta)\.[0-9]+$ ]]; then 34 | echo "PRERELEASE=--tag beta" >> $GITHUB_ENV 35 | else 36 | echo "PRERELEASE=" >> $GITHUB_ENV 37 | fi 38 | - run: cd contracts && pnpm publish --no-git-checks ${{ env.PRERELEASE }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .coverage_artifacts 3 | .coverage_cache 4 | .coverage_contracts 5 | artifacts 6 | build 7 | cache 8 | coverage 9 | dist 10 | node_modules 11 | types 12 | deployments 13 | .idea 14 | contracts-exposed 15 | .vscode 16 | 17 | # files 18 | *.env 19 | *.log 20 | .DS_Store 21 | .pnp.* 22 | coverage.json 23 | package-lock.json 24 | yarn.lock 25 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 8], 6 | "compiler-version": ["error", ">=0.8.4"], 7 | "func-visibility": ["error", { "ignoreConstructors": true }], 8 | "max-line-length": ["error", 120], 9 | "named-parameters-mapping": "off", 10 | "no-global-import": "off", 11 | "no-console": "off", 12 | "not-rely-on-time": "off", 13 | "prettier/prettier": [ 14 | "error", 15 | { 16 | "endOfLine": "auto" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | # directories 2 | **/artifacts 3 | **/node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.0-beta.3 2 | 3 | * The contract `Permission.sol` is now named `Permissioned.sol` 4 | * The function `onlySignedPublicKey` is now named `onlySender` 5 | * The function `onlySignedPublicKeyOwner` is now named `onlyPermitted` 6 | * Fixed bugs when casting between types 7 | * Added support for .seal() function for euint types 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FHE Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install 2 | install: 3 | npm i 4 | 5 | .PHONY: gen_console 6 | gen_console: install 7 | npx tsc ./scripts/genConsole.ts 8 | 9 | 10 | .PHONY: gen 11 | gen: gen_console 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fhenix Contracts [![NPM Package][npm-badge]][npm] [![Github Actions][gha-badge]][gha] [![License: MIT][license-badge]][license] 2 | 3 | [npm]: https://www.npmjs.org/package/@fhenixprotcol/contracts 4 | [npm-badge]: https://img.shields.io/npm/v/@fhenixprotocol/contracts.svg 5 | [gha]: https://github.com/fhenixprotocol/fheos/actions 6 | [gha-badge]: https://github.com/fhenixprotocol/fheos/actions/workflows/Test.yml/badge.svg 7 | [license]: https://opensource.org/licenses/MIT 8 | [license-badge]: https://img.shields.io/badge/License-MIT-blue.svg 9 | 10 | Solidity contracts for working with FHE smart contract on Fhenix. 11 | 12 | Need help getting started? Check out the [fhenix documentation](https://docs.fhenix.io)! 13 | 14 | These contracts are still under heavy constructions and will be changing frequently. Consider binding your contracts to a specific version, and keep an eye on the [changelog](https://github.com/FhenixProtocol/fhenix-contracts/CHANGELOG.md) 15 | 16 | ## Install 17 | 18 | ``` 19 | npm install @fhenixprotocol/contracts 20 | ``` 21 | 22 | ## Usage 23 | 24 | Import `FHE.sol` or any of the helper contracts 25 | 26 | ```solidity 27 | import "@fhenixprotocol/contracts/FHE.sol"; 28 | ``` 29 | 30 | ## Example 31 | 32 | ```solidity 33 | pragma solidity ^0.8.20; 34 | 35 | import {FHE, euint8, inEuint8} from "@fhenixprotocol/contracts/FHE.sol"; 36 | 37 | contract Example { 38 | 39 | euint8 _output; 40 | 41 | function setOutput(inEuint8 calldata _encryptedNumber) public { 42 | _output = FHE.asEuint8(_encryptedNumber); 43 | } 44 | 45 | function getOutputEncrypted(bytes32 publicKey) public view returns (bytes memory) { 46 | return _output.seal(publicKey); 47 | } 48 | } 49 | ``` 50 | 51 | ## License 52 | 53 | This project is licensed under MIT. 54 | -------------------------------------------------------------------------------- /contracts/FheOS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause-Clear 2 | // solhint-disable one-contract-per-file 3 | pragma solidity >=0.8.13 <0.9.0; 4 | 5 | library Precompiles { 6 | //solhint-disable const-name-snakecase 7 | address public constant Fheos = address(128); 8 | } 9 | 10 | interface FheOps { 11 | function log(string memory s) external pure; 12 | function add(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 13 | function verify(uint8 utype, bytes memory input, int32 securityZone) external pure returns (bytes memory); 14 | function sealOutput(uint8 utype, bytes memory ctHash, bytes memory pk) external pure returns (string memory); 15 | function decrypt(uint8 utype, bytes memory input, uint256 defaultValue) external pure returns (uint256); 16 | function lte(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 17 | function sub(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 18 | function mul(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 19 | function lt(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 20 | function select(uint8 utype, bytes memory controlHash, bytes memory ifTrueHash, bytes memory ifFalseHash) external pure returns (bytes memory); 21 | function req(uint8 utype, bytes memory input) external pure returns (bytes memory); 22 | function cast(uint8 utype, bytes memory input, uint8 toType) external pure returns (bytes memory); 23 | function trivialEncrypt(bytes memory input, uint8 toType, int32 securityZone) external pure returns (bytes memory); 24 | function div(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 25 | function gt(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 26 | function gte(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 27 | function rem(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 28 | function and(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 29 | function or(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 30 | function xor(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 31 | function eq(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 32 | function ne(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 33 | function min(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 34 | function max(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 35 | function shl(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 36 | function shr(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 37 | function rol(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 38 | function ror(uint8 utype, bytes memory lhsHash, bytes memory rhsHash) external pure returns (bytes memory); 39 | function not(uint8 utype, bytes memory value) external pure returns (bytes memory); 40 | function random(uint8 utype, uint64 seed, int32 securityZone) external pure returns (bytes memory); 41 | function getNetworkPublicKey(int32 securityZone) external pure returns (bytes memory); 42 | function square(uint8 utype, bytes memory value) external pure returns (bytes memory); 43 | } -------------------------------------------------------------------------------- /contracts/access/EIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol) 3 | 4 | pragma solidity ^0.8.20; 5 | 6 | import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; 7 | import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol"; 8 | import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol"; 9 | 10 | /** 11 | * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. 12 | * 13 | * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose 14 | * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract 15 | * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to 16 | * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. 17 | * 18 | * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding 19 | * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA 20 | * ({_hashTypedDataV4}). 21 | * 22 | * The implementation of the domain separator was designed to be as efficient as possible while still properly updating 23 | * the chain id to protect against replay attacks on an eventual fork of the chain. 24 | * 25 | * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method 26 | * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. 27 | * 28 | * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain 29 | * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the 30 | * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. 31 | * 32 | * @custom:oz-upgrades-unsafe-allow state-variable-immutable 33 | * 34 | * NOTE: Fhenix read access Permits are intended to be used in multiple contracts simultaneously, therefor the 35 | * `verifyingContract` which is by default address(this) has been replaced with address(0). 36 | */ 37 | abstract contract EIP712 is IERC5267 { 38 | using ShortStrings for *; 39 | 40 | bytes32 private constant TYPE_HASH = 41 | keccak256( 42 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 43 | ); 44 | 45 | // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to 46 | // invalidate the cached domain separator if the chain id changes. 47 | bytes32 private immutable _cachedDomainSeparator; 48 | uint256 private immutable _cachedChainId; 49 | 50 | bytes32 private immutable _hashedName; 51 | bytes32 private immutable _hashedVersion; 52 | 53 | ShortString private immutable _name; 54 | ShortString private immutable _version; 55 | string private _nameFallback; 56 | string private _versionFallback; 57 | 58 | /** 59 | * @dev Initializes the domain separator and parameter caches. 60 | * 61 | * The meaning of `name` and `version` is specified in 62 | * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: 63 | * 64 | * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. 65 | * - `version`: the current major version of the signing domain. 66 | * 67 | * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart 68 | * contract upgrade]. 69 | */ 70 | constructor(string memory name, string memory version) { 71 | _name = name.toShortStringWithFallback(_nameFallback); 72 | _version = version.toShortStringWithFallback(_versionFallback); 73 | _hashedName = keccak256(bytes(name)); 74 | _hashedVersion = keccak256(bytes(version)); 75 | 76 | _cachedChainId = block.chainid; 77 | _cachedDomainSeparator = _buildDomainSeparator(); 78 | } 79 | 80 | /** 81 | * @dev Returns the domain separator for the current chain. 82 | */ 83 | function _domainSeparatorV4() internal view returns (bytes32) { 84 | if (block.chainid == _cachedChainId) { 85 | return _cachedDomainSeparator; 86 | } else { 87 | return _buildDomainSeparator(); 88 | } 89 | } 90 | 91 | function _buildDomainSeparator() private view returns (bytes32) { 92 | return 93 | keccak256( 94 | abi.encode( 95 | TYPE_HASH, 96 | _hashedName, 97 | _hashedVersion, 98 | block.chainid, 99 | address(0) 100 | ) 101 | ); 102 | } 103 | 104 | /** 105 | * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this 106 | * function returns the hash of the fully encoded EIP712 message for this domain. 107 | * 108 | * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: 109 | * 110 | * ```solidity 111 | * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( 112 | * keccak256("Mail(address to,string contents)"), 113 | * mailTo, 114 | * keccak256(bytes(mailContents)) 115 | * ))); 116 | * address signer = ECDSA.recover(digest, signature); 117 | * ``` 118 | */ 119 | function _hashTypedDataV4( 120 | bytes32 structHash 121 | ) internal view virtual returns (bytes32) { 122 | return 123 | MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); 124 | } 125 | 126 | /** 127 | * @dev See {IERC-5267}. 128 | */ 129 | function eip712Domain() 130 | public 131 | view 132 | virtual 133 | returns ( 134 | bytes1 fields, 135 | string memory name, 136 | string memory version, 137 | uint256 chainId, 138 | address verifyingContract, 139 | bytes32 salt, 140 | uint256[] memory extensions 141 | ) 142 | { 143 | return ( 144 | hex"0f", // 01111 145 | _EIP712Name(), 146 | _EIP712Version(), 147 | block.chainid, 148 | address(0), 149 | bytes32(0), 150 | new uint256[](0) 151 | ); 152 | } 153 | 154 | /** 155 | * @dev The name parameter for the EIP712 domain. 156 | * 157 | * NOTE: By default this function reads _name which is an immutable value. 158 | * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). 159 | */ 160 | // solhint-disable-next-line func-name-mixedcase 161 | function _EIP712Name() internal view returns (string memory) { 162 | return _name.toStringWithFallback(_nameFallback); 163 | } 164 | 165 | /** 166 | * @dev The version parameter for the EIP712 domain. 167 | * 168 | * NOTE: By default this function reads _version which is an immutable value. 169 | * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). 170 | */ 171 | // solhint-disable-next-line func-name-mixedcase 172 | function _EIP712Version() internal view returns (string memory) { 173 | return _version.toStringWithFallback(_versionFallback); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /contracts/access/Permissioned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause-Clear 2 | pragma solidity >=0.8.19 <0.9.0; 3 | 4 | import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; 6 | 7 | /// @title Permissioned Access Control Contract 8 | /// @notice Abstract contract that provides EIP-712 based signature verification for access control 9 | /// @dev This contract should be inherited by other contracts to provide EIP-712 signature validated access control 10 | abstract contract Permissioned is EIP712 { 11 | /// @notice Emitted when the signer is not the message sender 12 | error SignerNotMessageSender(); 13 | 14 | /// @notice Emitted when the signer is not the specified owner 15 | error SignerNotOwner(); 16 | 17 | /// @dev Constructor that initializes EIP712 domain separator with a name and version 18 | /// solhint-disable-next-line func-visibility, no-empty-blocks 19 | constructor() EIP712("Fhenix Permission", "1.0") {} 20 | 21 | /// @notice Modifier that requires the provided signature to be signed by the message sender 22 | /// @param permission Data structure containing the public key and the signature to be verified 23 | modifier onlySender(Permission memory permission) { 24 | bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( 25 | keccak256("Permissioned(bytes32 publicKey)"), 26 | permission.publicKey 27 | ))); 28 | address signer = ECDSA.recover(digest, permission.signature); 29 | if (signer != msg.sender) 30 | revert SignerNotMessageSender(); 31 | _; 32 | } 33 | 34 | /// @notice Modifier that requires the provided signature to be signed by a specific owner address 35 | /// @param permission Data structure containing the public key and the signature to be verified 36 | /// @param owner The expected owner of the public key to match against the recovered signer 37 | modifier onlyPermitted(Permission memory permission, address owner) { 38 | bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( 39 | keccak256("Permissioned(bytes32 publicKey)"), 40 | permission.publicKey 41 | ))); 42 | address signer = ECDSA.recover(digest, permission.signature); 43 | if (signer != owner) 44 | revert SignerNotOwner(); 45 | _; 46 | } 47 | 48 | /// @notice Modifier that requires the provided signature to be signed by one of two specific addresses 49 | /// @param permission Data structure containing the public key and the signature to be verified 50 | /// @param owner The expected owner of the public key to match against the recovered signer 51 | /// @param permitted A second permitted owner of the public key to match against the recovered signer 52 | modifier onlyBetweenPermitted(Permission memory permission, address owner, address permitted) { 53 | bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( 54 | keccak256("Permissioned(bytes32 publicKey)"), 55 | permission.publicKey 56 | ))); 57 | address signer = ECDSA.recover(digest, permission.signature); 58 | if (signer != owner && signer != permitted) 59 | revert SignerNotOwner(); 60 | _; 61 | } 62 | } 63 | 64 | /// @title Struct for holding signature information 65 | /// @notice Used to pass both the public key and signature data within transactions 66 | /// @dev Should be used with Signature-based modifiers for access control 67 | struct Permission { 68 | bytes32 publicKey; 69 | bytes signature; 70 | } 71 | -------------------------------------------------------------------------------- /contracts/access/PermissionedV2.sol: -------------------------------------------------------------------------------- 1 | // solhint-disable func-name-mixedcase 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.9.0; 4 | 5 | import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; 6 | import {EIP712} from "./EIP712.sol"; 7 | 8 | /** 9 | * @dev Permission body that must be passed to a contract to allow access to sensitive data. 10 | * 11 | * The minimum permission to access a user's own data requires the fields 12 | * < issuer, expiration, contracts, projects, sealingKey, issuerSignature > 13 | * 14 | * `contracts` and `projects` are lists defining which contracts can be accessed with this permission. 15 | * `projects` is a list of strings, each of which defines and provides access to a group of contracts. 16 | * 17 | * --- 18 | * 19 | * If not sharing the permission, `issuer` signs a signature using the fields: 20 | * < issuer, expiration, contracts, projects, recipient, validatorId, validatorContract, sealingKey > 21 | * This signature can now be used by `issuer` to access their own encrypted data. 22 | * 23 | * --- 24 | * 25 | * Sharing a permission is a two step process: `issuer` completes step 1, and `recipient` completes step 2. 26 | * 27 | * 1: 28 | * `issuer` creates a permission with `recipient` populated with the address of the user to give access to. 29 | * `issuer` does not include a `sealingKey` in the permission, it will be populated by the `recipient`. 30 | * `issuer` is still responsible for defining the permission's access through `contracts` and `projects`. 31 | * `issuer` signs a signature including the fields: (note: `sealingKey` is absent in this signature) 32 | * < issuer, expiration, contracts, projects, recipient, validatorId, validatorContract > 33 | * `issuer` packages the permission data and `issuerSignature` and shares it with `recipient` 34 | * ** None of this data is sensitive, and can be shared as cleartext ** 35 | * 36 | * 2: 37 | * `recipient` adds their `sealingKey` to the data received from `issuer` 38 | * `recipient` signs a signature including the fields: 39 | * < sealingKey, issuerSignature > 40 | * `recipient` can now use the completed Permission to access `issuer`s encrypted data. 41 | * 42 | * --- 43 | * 44 | * `validatorId` and `validatorContract` are optional and can be used together to 45 | * increase security and control by disabling a permission after it has been created. 46 | * Useful when sharing permits to provide external access to sensitive data (eg auditors). 47 | */ 48 | struct PermissionV2 { 49 | // (base) User that initially created the permission, target of data fetching 50 | address issuer; 51 | // (base) Expiration timestamp 52 | uint64 expiration; 53 | // (base) List of contract addresses that can be accessed with this permission 54 | address[] contracts; 55 | // (base) List of project identifiers (strings) that can be accessed 56 | string[] projects; 57 | // (sharing) The user that this permission will be shared with 58 | // ** optional, use `address(0)` to disable ** 59 | address recipient; 60 | // (issuer defined validation) An id used to query a contract to check this permissions validity 61 | // ** optional, use `0` to disable ** 62 | uint256 validatorId; 63 | // (issuer defined validation) The contract to query to determine permission validity 64 | // ** optional, user `address(0)` to disable ** 65 | address validatorContract; 66 | // (base) The publicKey of a sealingPair used to re-encrypt `issuer`s confidential data 67 | // (non-sharing) Populated by `issuer` 68 | // (sharing) Populated by `recipient` 69 | bytes32 sealingKey; 70 | // (base) `signTypedData` signature created by `issuer`. 71 | // (base) Shared- and Self- permissions differ in signature format: (`sealingKey` absent in shared signature) 72 | // (non-sharing) < issuer, expiration, contracts, projects, recipient, validatorId, validatorContract, sealingKey > 73 | // (sharing) < issuer, expiration, contracts, projects, recipient, validatorId, validatorContract > 74 | bytes issuerSignature; 75 | // (sharing) `signTypedData` signature created by `recipient` with format: 76 | // (sharing) < sealingKey, issuerSignature> 77 | // ** required for shared permits ** 78 | bytes recipientSignature; 79 | } 80 | 81 | 82 | /// @dev Minimum required interface to create a custom permission validator. 83 | /// Permission validators are optional, and provide extra security and control when sharing permits. 84 | interface IPermissionValidator { 85 | /// @dev Checks whether a permission is valid, returning `false` disables the permission. 86 | function disabled( 87 | address issuer, 88 | uint256 id 89 | ) external view returns (bool); 90 | } 91 | 92 | /// @dev Uses a modified version of openzeppelin's EIP712 contract which disables the `verifyingContract`. 93 | /// Instead, access is checked using the `contracts` and `projects` fields of the `PermissionV2` struct. 94 | /// This allows a single signed permission to be used to access multiple contracts encrypted data. 95 | contract PermissionedV2 is EIP712 { 96 | using PermissionV2Utils for PermissionV2; 97 | 98 | /// @notice Version of the fhenix permission signature 99 | string public version = "v2.0.0"; 100 | 101 | /// @notice This contract's project identifier string. Used in permissions to grant access to all contracts with this identifier. 102 | string public project; 103 | 104 | /// @dev Constructor that initializes the EIP712 domain. The EIP712 implementation used has `verifyingContract` disabled 105 | /// by replacing it with `address(0)`. Ensure that `verifyingContract` is the ZeroAddress when creating a user's signature. 106 | /// 107 | /// @param proj The project identifier string to be associated with this contract. Any Permission with this project identifier 108 | /// in `permission.projects` list will be granted access to this contract's data. Use an empty string for no project identifier. 109 | constructor( 110 | string memory proj 111 | ) EIP712(string.concat("Fhenix Permission ", version), version) { 112 | project = proj; 113 | } 114 | 115 | /// @dev Emitted when `project` is not in `permission.projects` nor `address(this)` in `permission.contracts` 116 | error PermissionInvalid_ContractUnauthorized(); 117 | 118 | /// @dev Emitted when `permission.expiration` is in the past (< block.timestamp) 119 | error PermissionInvalid_Expired(); 120 | 121 | /// @dev Emitted when `issuerSignature` is malformed or was not signed by `permission.issuer` 122 | error PermissionInvalid_IssuerSignature(); 123 | 124 | /// @dev Emitted when `recipientSignature` is malformed or was not signed by `permission.recipient` 125 | error PermissionInvalid_RecipientSignature(); 126 | 127 | /// @dev Emitted when `validatorContract` returned `false` indicating that this permission has been externally disabled 128 | error PermissionInvalid_Disabled(); 129 | 130 | /// @dev Validate's a `permissions` access of sensitive data. 131 | /// `permission` may be invalid or unauthorized for the following reasons: 132 | /// - Contract unauthorized: `project` is not in `permission.projects` nor address(this) in `permission.contracts` 133 | /// - Expired: `permission.expiration` is in the past (< block.timestamp) 134 | /// - Issuer signature: `issuerSignature` is malformed or was not signed by `permission.issuer` 135 | /// - Recipient signature: `recipientSignature` is malformed or was not signed by `permission.recipient` 136 | /// - Disabled: `validatorContract` returned `false` indicating that this permission has been externally disabled 137 | /// @param permission PermissionV2 struct containing data necessary to validate data access and seal for return. 138 | /// 139 | /// NOTE: Functions protected by `withPermission` should return ONLY the sensitive data of `permission.issuer`. 140 | /// !! Returning data of `msg.sender` will leak sensitive values - `msg.sender` cannot be trusted in view functions !! 141 | modifier withPermission(PermissionV2 memory permission) { 142 | // Access 143 | if (!permission.satisfies(address(this), project)) 144 | revert PermissionInvalid_ContractUnauthorized(); 145 | 146 | // Expiration 147 | if (permission.expiration < block.timestamp) 148 | revert PermissionInvalid_Expired(); 149 | 150 | // Issuer signature 151 | if ( 152 | !SignatureChecker.isValidSignatureNow( 153 | permission.issuer, 154 | _hashTypedDataV4(permission.issuerHash()), 155 | permission.issuerSignature 156 | ) 157 | ) revert PermissionInvalid_IssuerSignature(); 158 | 159 | // (if applicable) Recipient signature 160 | if ( 161 | permission.recipient != address(0) && 162 | !SignatureChecker.isValidSignatureNow( 163 | permission.recipient, 164 | _hashTypedDataV4(permission.recipientHash()), 165 | permission.recipientSignature 166 | ) 167 | ) revert PermissionInvalid_RecipientSignature(); 168 | 169 | // (if applicable) Externally disabled 170 | if ( 171 | permission.validatorId != 0 && 172 | permission.validatorContract != address(0) && 173 | IPermissionValidator(permission.validatorContract).disabled( 174 | permission.issuer, 175 | permission.validatorId 176 | ) 177 | ) revert PermissionInvalid_Disabled(); 178 | 179 | _; 180 | } 181 | 182 | /// @dev Utility function used to check whether a permission provides access to this contract. 183 | /// Intended to be used before real data is fetched to preempt a reversion, but is not necessary to use. 184 | function checkPermissionSatisfies( 185 | PermissionV2 memory permission 186 | ) external view returns (bool) { 187 | return permission.satisfies(address(this), project); 188 | } 189 | } 190 | 191 | /// @dev Internal utility library to improve the readability of PermissionedV2 192 | /// Primarily focused on signature type hashes 193 | library PermissionV2Utils { 194 | function issuerHash( 195 | PermissionV2 memory permission 196 | ) internal pure returns (bytes32) { 197 | if (permission.recipient == address(0)) 198 | return issuerSelfHash(permission); 199 | return issuerSharedHash(permission); 200 | } 201 | 202 | function issuerSelfHash( 203 | PermissionV2 memory permission 204 | ) internal pure returns (bytes32) { 205 | return 206 | keccak256( 207 | abi.encode( 208 | keccak256( 209 | "PermissionedV2IssuerSelf(address issuer,uint64 expiration,address[] contracts,string[] projects,address recipient,uint256 validatorId,address validatorContract,bytes32 sealingKey)" 210 | ), 211 | permission.issuer, 212 | permission.expiration, 213 | encodeArray(permission.contracts), 214 | encodeArray(permission.projects), 215 | permission.recipient, 216 | permission.validatorId, 217 | permission.validatorContract, 218 | permission.sealingKey 219 | ) 220 | ); 221 | } 222 | 223 | function issuerSharedHash( 224 | PermissionV2 memory permission 225 | ) internal pure returns (bytes32) { 226 | return 227 | keccak256( 228 | abi.encode( 229 | keccak256( 230 | "PermissionedV2IssuerShared(address issuer,uint64 expiration,address[] contracts,string[] projects,address recipient,uint256 validatorId,address validatorContract)" 231 | ), 232 | permission.issuer, 233 | permission.expiration, 234 | encodeArray(permission.contracts), 235 | encodeArray(permission.projects), 236 | permission.recipient, 237 | permission.validatorId, 238 | permission.validatorContract 239 | ) 240 | ); 241 | } 242 | 243 | function recipientHash( 244 | PermissionV2 memory permission 245 | ) internal pure returns (bytes32) { 246 | return 247 | keccak256( 248 | abi.encode( 249 | keccak256( 250 | "PermissionedV2Recipient(bytes32 sealingKey,bytes issuerSignature)" 251 | ), 252 | permission.sealingKey, 253 | keccak256(permission.issuerSignature) 254 | ) 255 | ); 256 | } 257 | 258 | function satisfies( 259 | PermissionV2 memory permission, 260 | address addr, 261 | string memory proj 262 | ) internal pure returns (bool) { 263 | for (uint256 i = 0; i < permission.projects.length; i++) { 264 | if ( 265 | keccak256(abi.encodePacked(proj)) == 266 | keccak256(abi.encodePacked(permission.projects[i])) 267 | ) return true; 268 | } 269 | for (uint256 i = 0; i < permission.contracts.length; i++) { 270 | if (addr == permission.contracts[i]) return true; 271 | } 272 | return false; 273 | } 274 | 275 | function encodeArray( 276 | address[] memory items 277 | ) internal pure returns (bytes32) { 278 | return keccak256(abi.encodePacked(items)); 279 | } 280 | 281 | function encodeArray( 282 | string[] memory items 283 | ) internal pure returns (bytes32) { 284 | bytes32[] memory result = new bytes32[](items.length); 285 | for (uint256 i = 0; i < items.length; i++) { 286 | result[i] = keccak256(bytes(items[i])); 287 | } 288 | return keccak256(abi.encodePacked(result)); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC20/FHERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.9.0; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import { FHE, euint128, inEuint128 } from "../../../FHE.sol"; 6 | import { Permissioned, Permission } from "../../../access/Permissioned.sol"; 7 | 8 | import { IFHERC20 } from "./IFHERC20.sol"; 9 | 10 | error ErrorInsufficientFunds(); 11 | error ERC20InvalidApprover(address); 12 | error ERC20InvalidSpender(address); 13 | 14 | contract FHERC20 is IFHERC20, ERC20, Permissioned { 15 | 16 | // A mapping from address to an encrypted balance. 17 | mapping(address => euint128) internal _encBalances; 18 | // A mapping from address (owner) to a mapping of address (spender) to an encrypted amount. 19 | mapping(address => mapping(address => euint128)) internal _allowed; 20 | euint128 internal totalEncryptedSupply = FHE.asEuint128(0); 21 | 22 | constructor( 23 | string memory name, 24 | string memory symbol 25 | ) ERC20(name, symbol) {} 26 | 27 | function _allowanceEncrypted(address owner, address spender) internal view returns (euint128) { 28 | return _allowed[owner][spender]; 29 | } 30 | 31 | function allowanceEncrypted( 32 | address owner, 33 | address spender, 34 | Permission calldata permission 35 | ) public view virtual onlyBetweenPermitted(permission, owner, spender) returns (string memory) { 36 | return FHE.sealoutput(_allowanceEncrypted(owner, spender), permission.publicKey); 37 | } 38 | 39 | function approveEncrypted(address spender, inEuint128 calldata value) public virtual returns (bool) { 40 | _approve(msg.sender, spender, FHE.asEuint128(value)); 41 | return true; 42 | } 43 | 44 | function _approve(address owner, address spender, euint128 value) internal { 45 | if (owner == address(0)) { 46 | revert ERC20InvalidApprover(address(0)); 47 | } 48 | if (spender == address(0)) { 49 | revert ERC20InvalidSpender(address(0)); 50 | } 51 | _allowed[owner][spender] = value; 52 | } 53 | 54 | function _spendAllowance(address owner, address spender, euint128 value) internal virtual returns (euint128) { 55 | euint128 currentAllowance = _allowanceEncrypted(owner, spender); 56 | euint128 spent = FHE.min(currentAllowance, value); 57 | _approve(owner, spender, (currentAllowance - spent)); 58 | 59 | return spent; 60 | } 61 | 62 | function transferFromEncrypted(address from, address to, inEuint128 calldata value) public virtual returns (euint128) { 63 | return _transferFromEncrypted(from, to, FHE.asEuint128(value)); 64 | } 65 | 66 | function _transferFromEncrypted(address from, address to, euint128 value) public virtual returns (euint128) { 67 | euint128 val = value; 68 | euint128 spent = _spendAllowance(from, msg.sender, val); 69 | return _transferImpl(from, to, spent); 70 | } 71 | 72 | function wrap(uint32 amount) public { 73 | if (balanceOf(msg.sender) < amount) { 74 | revert ErrorInsufficientFunds(); 75 | } 76 | 77 | _burn(msg.sender, amount); 78 | euint128 eAmount = FHE.asEuint128(amount); 79 | _encBalances[msg.sender] = _encBalances[msg.sender] + eAmount; 80 | totalEncryptedSupply = totalEncryptedSupply + eAmount; 81 | } 82 | 83 | function unwrap(uint32 amount) public { 84 | euint128 encAmount = FHE.asEuint128(amount); 85 | 86 | euint128 amountToUnwrap = FHE.select(_encBalances[msg.sender].gte(encAmount), encAmount, FHE.asEuint128(0)); 87 | 88 | _encBalances[msg.sender] = _encBalances[msg.sender] - amountToUnwrap; 89 | totalEncryptedSupply = totalEncryptedSupply - amountToUnwrap; 90 | 91 | _mint(msg.sender, FHE.decrypt(amountToUnwrap)); 92 | } 93 | 94 | // function mint(uint256 amount) public { 95 | // _mint(msg.sender, amount); 96 | // } 97 | 98 | function _mintEncrypted(address to, inEuint128 memory encryptedAmount) internal { 99 | euint128 amount = FHE.asEuint128(encryptedAmount); 100 | _encBalances[to] = _encBalances[to] + amount; 101 | totalEncryptedSupply = totalEncryptedSupply + amount; 102 | } 103 | 104 | function transferEncrypted(address to, inEuint128 calldata encryptedAmount) public returns (euint128) { 105 | return _transferEncrypted(to, FHE.asEuint128(encryptedAmount)); 106 | } 107 | 108 | // Transfers an amount from the message sender address to the `to` address. 109 | function _transferEncrypted(address to, euint128 amount) public returns (euint128) { 110 | return _transferImpl(msg.sender, to, amount); 111 | } 112 | 113 | // Transfers an encrypted amount. 114 | function _transferImpl(address from, address to, euint128 amount) internal returns (euint128) { 115 | // Make sure the sender has enough tokens. 116 | euint128 amountToSend = FHE.select(amount.lte(_encBalances[from]), amount, FHE.asEuint128(0)); 117 | 118 | // Add to the balance of `to` and subract from the balance of `from`. 119 | _encBalances[to] = _encBalances[to] + amountToSend; 120 | _encBalances[from] = _encBalances[from] - amountToSend; 121 | 122 | return amountToSend; 123 | } 124 | 125 | function balanceOfEncrypted( 126 | address account, Permission memory auth 127 | ) virtual public view onlyPermitted(auth, account) returns (string memory) { 128 | return _encBalances[account].seal(auth.publicKey); 129 | } 130 | 131 | // // Returns the total supply of tokens, sealed and encrypted for the caller. 132 | // // todo: add a permission check for total supply readers 133 | // function getEncryptedTotalSupply( 134 | // Permission calldata permission 135 | // ) public view onlySender(permission) returns (bytes memory) { 136 | // return totalEncryptedSupply.seal(permission.publicKey); 137 | // } 138 | } 139 | -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC20/IFHERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.19 <0.9.0; 2 | 3 | // SPDX-License-Identifier: MIT 4 | // Fhenix Protocol (last updated v0.1.0) (token/FHERC20/IFHERC20.sol) 5 | // Inspired by OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts) (token/ERC20/IERC20.sol) 6 | 7 | import { Permission, Permissioned } from "../../../access/Permissioned.sol"; 8 | import { euint128, inEuint128 } from "../../../FHE.sol"; 9 | 10 | /** 11 | * @dev Interface of the ERC-20 standard as defined in the ERC. 12 | */ 13 | interface IFHERC20 { 14 | /** 15 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 16 | * another (`to`). 17 | * 18 | * Note that `value` may be zero. 19 | */ 20 | event TransferEncrypted(address indexed from, address indexed to); 21 | 22 | /** 23 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 24 | * a call to {approveEncrypted}. `value` is the new allowance. 25 | */ 26 | event ApprovalEncrypted(address indexed owner, address indexed spender); 27 | 28 | // /** 29 | // * @dev Returns the value of tokens in existence. 30 | // */ 31 | // function totalSupply() external view returns (uint256); 32 | 33 | /** 34 | * @dev Returns the value of tokens owned by `account`, sealed and encrypted for the caller. 35 | */ 36 | function balanceOfEncrypted(address account, Permission memory auth) external view returns (string memory); 37 | 38 | /** 39 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 40 | * Accepts the value as inEuint128, more convenient for calls from EOAs. 41 | * 42 | * Returns a boolean value indicating whether the operation succeeded. 43 | */ 44 | function transferEncrypted(address to, inEuint128 calldata value) external returns (euint128); 45 | 46 | /** 47 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 48 | * Accepts the value as euint128, more convenient for calls from other contracts 49 | * 50 | * Returns a boolean value indicating whether the operation succeeded. 51 | */ 52 | function _transferEncrypted(address to, euint128 value) external returns (euint128); 53 | 54 | /** 55 | * @dev Returns the remaining number of tokens that `spender` will be 56 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 57 | * zero by default. 58 | * 59 | * This value changes when {approve} or {transferFrom} are called. 60 | */ 61 | function allowanceEncrypted(address owner, address spender, Permission memory permission) external view returns (string memory); 62 | 63 | /** 64 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 65 | * caller's tokens. 66 | * 67 | * Returns a boolean value indicating whether the operation succeeded. 68 | * 69 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 70 | * that someone may use both the old and the new allowance by unfortunate 71 | * transaction ordering. One possible solution to mitigate this race 72 | * condition is to first reduce the spender's allowance to 0 and set the 73 | * desired value afterwards: 74 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 75 | * 76 | * Emits an {ApprovalEncrypted} event. 77 | */ 78 | function approveEncrypted(address spender, inEuint128 calldata value) external returns (bool); 79 | 80 | /** 81 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 82 | * allowance mechanism. `value` is then deducted from the caller's 83 | * allowance. Accepts the value as inEuint128, more convenient for calls from EOAs. 84 | * 85 | * Returns a boolean value indicating whether the operation succeeded. 86 | * 87 | * Emits a {TransferEncrypted} event. 88 | */ 89 | function transferFromEncrypted(address from, address to, inEuint128 calldata value) external returns (euint128); 90 | 91 | /** 92 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 93 | * allowance mechanism. `value` is then deducted from the caller's 94 | * allowance. Accepts the value as inEuint128, more convenient for calls 95 | * from other contracts. 96 | * 97 | * Returns a boolean value indicating whether the operation succeeded. 98 | * 99 | * Emits a {TransferEncrypted} event. 100 | */ 101 | function _transferFromEncrypted(address from, address to, euint128 value) external returns (euint128); 102 | } 103 | -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC721/FHERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.9.0; 3 | 4 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; 6 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 7 | 8 | import { FHE, euint256, inEuint256 } from "../../../FHE.sol"; 9 | import { Permissioned, Permission } from "../../../access/Permissioned.sol"; 10 | import { IFHERC721 } from "./IFHERC721.sol"; 11 | 12 | contract FHERC721 is IFHERC721, Permissioned, ERC721 { 13 | using Strings for uint256; 14 | 15 | mapping(uint256 => euint256) private _privateData; 16 | 17 | constructor( 18 | string memory name, 19 | string memory symbol 20 | ) ERC721(name, symbol) {} 21 | 22 | function _baseURI() internal pure override returns (string memory) { 23 | return ""; 24 | } 25 | 26 | function tokenURI(uint256 tokenId) public view override returns (string memory) { 27 | _requireOwned(tokenId); 28 | 29 | string memory baseURI = _baseURI(); 30 | return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : ""; 31 | } 32 | 33 | function _mint(address to, uint256 tokenId, inEuint256 calldata privateData) internal { 34 | super._mint(to, tokenId); 35 | _privateData[tokenId] = FHE.asEuint256(privateData); 36 | } 37 | 38 | function tokenPrivateData( 39 | uint256 tokenId, 40 | Permission memory auth 41 | ) external view onlyPermitted(auth, _ownerOf(tokenId)) returns (string memory) { 42 | return FHE.sealoutput(_privateData[tokenId], auth.publicKey); 43 | } 44 | 45 | /** 46 | * @dev See {IERC165-supportsInterface}. 47 | */ 48 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { 49 | return 50 | interfaceId == type(IFHERC721).interfaceId || 51 | super.supportsInterface(interfaceId); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC721/IFHERC721.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.19 <0.9.0; 2 | 3 | // SPDX-License-Identifier: MIT 4 | // Fhenix Protocol (last updated v0.1.0) (token/FHERC721/IFHERC721.sol) 5 | // Inspired by OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts) (token/FHERC721/IFHERC721.sol) 6 | 7 | import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | import { Permission, Permissioned } from "../../../access/Permissioned.sol"; 9 | 10 | /** 11 | * @dev Interface of the ERC-721 standard as defined in the ERC. 12 | */ 13 | interface IFHERC721 is IERC721 { 14 | /** 15 | * @dev Returns the private data associated with `tokenId` token, if the caller is allowed to view it. 16 | */ 17 | function tokenPrivateData(uint256 tokenId, Permission memory auth) external view returns (string memory); 18 | } -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC721/mocks/CallReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | contract CallReceiverMock { 6 | event MockFunctionCalled(); 7 | event MockFunctionCalledWithArgs(uint256 a, uint256 b); 8 | 9 | uint256[] private _array; 10 | 11 | function mockFunction() public payable returns (string memory) { 12 | emit MockFunctionCalled(); 13 | 14 | return "0x1234"; 15 | } 16 | 17 | function mockFunctionEmptyReturn() public payable { 18 | emit MockFunctionCalled(); 19 | } 20 | 21 | function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) { 22 | emit MockFunctionCalledWithArgs(a, b); 23 | 24 | return "0x1234"; 25 | } 26 | 27 | function mockFunctionNonPayable() public returns (string memory) { 28 | emit MockFunctionCalled(); 29 | 30 | return "0x1234"; 31 | } 32 | 33 | function mockStaticFunction() public pure returns (string memory) { 34 | return "0x1234"; 35 | } 36 | 37 | function mockFunctionRevertsNoReason() public payable { 38 | revert(); 39 | } 40 | 41 | function mockFunctionRevertsReason() public payable { 42 | revert("CallReceiverMock: reverting"); 43 | } 44 | 45 | function mockFunctionThrows() public payable { 46 | assert(false); 47 | } 48 | 49 | function mockFunctionOutOfGas() public payable { 50 | for (uint256 i = 0; ; ++i) { 51 | _array.push(i); 52 | } 53 | } 54 | 55 | function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) { 56 | assembly { 57 | sstore(slot, value) 58 | } 59 | return "0x1234"; 60 | } 61 | } 62 | 63 | contract CallReceiverMockTrustingForwarder is CallReceiverMock { 64 | address private _trustedForwarder; 65 | 66 | constructor(address trustedForwarder_) { 67 | _trustedForwarder = trustedForwarder_; 68 | } 69 | 70 | function isTrustedForwarder(address forwarder) public view virtual returns (bool) { 71 | return forwarder == _trustedForwarder; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/experimental/token/FHERC721/mocks/ERC721ReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | contract ERC721ReceiverMock { 6 | enum RevertType { 7 | None, 8 | RevertWithoutMessage, 9 | RevertWithMessage, 10 | RevertWithCustomError, 11 | Panic 12 | } 13 | 14 | bytes4 private immutable _retval; 15 | RevertType private immutable _error; 16 | 17 | event Received(address operator, address from, uint256 tokenId, bytes data, uint256 gas); 18 | error CustomError(bytes4); 19 | 20 | constructor(bytes4 retval, RevertType error) { 21 | _retval = retval; 22 | _error = error; 23 | } 24 | 25 | function onERC721Received( 26 | address operator, 27 | address from, 28 | uint256 tokenId, 29 | bytes memory data 30 | ) public returns (bytes4) { 31 | if (_error == RevertType.RevertWithoutMessage) { 32 | revert(); 33 | } else if (_error == RevertType.RevertWithMessage) { 34 | revert("ERC721ReceiverMock: reverting"); 35 | } else if (_error == RevertType.RevertWithCustomError) { 36 | revert CustomError(_retval); 37 | } else if (_error == RevertType.Panic) { 38 | uint256 a = uint256(0) / uint256(0); 39 | a; 40 | } 41 | 42 | emit Received(operator, from, tokenId, data, gasleft()); 43 | return _retval; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/experimental/utils/BytesLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | /* 3 | * @title Solidity Bytes Arrays Utils 4 | * @author Gonçalo Sá 5 | * 6 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 7 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 8 | */ 9 | pragma solidity >=0.8.20 <0.9.0; 10 | 11 | library BytesLib { 12 | function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { 13 | bytes memory tempBytes; 14 | 15 | assembly { 16 | // Get a location of some free memory and store it in tempBytes as 17 | // Solidity does for memory variables. 18 | tempBytes := mload(0x40) 19 | 20 | // Store the length of the first bytes array at the beginning of 21 | // the memory for tempBytes. 22 | let length := mload(_preBytes) 23 | mstore(tempBytes, length) 24 | 25 | // Maintain a memory counter for the current write location in the 26 | // temp bytes array by adding the 32 bytes for the array length to 27 | // the starting location. 28 | let mc := add(tempBytes, 0x20) 29 | // Stop copying when the memory counter reaches the length of the 30 | // first bytes array. 31 | let end := add(mc, length) 32 | 33 | for { 34 | // Initialize a copy counter to the start of the _preBytes data, 35 | // 32 bytes into its memory. 36 | let cc := add(_preBytes, 0x20) 37 | } lt(mc, end) { 38 | // Increase both counters by 32 bytes each iteration. 39 | mc := add(mc, 0x20) 40 | cc := add(cc, 0x20) 41 | } { 42 | // Write the _preBytes data into the tempBytes memory 32 bytes 43 | // at a time. 44 | mstore(mc, mload(cc)) 45 | } 46 | 47 | // Add the length of _postBytes to the current length of tempBytes 48 | // and store it as the new length in the first 32 bytes of the 49 | // tempBytes memory. 50 | length := mload(_postBytes) 51 | mstore(tempBytes, add(length, mload(tempBytes))) 52 | 53 | // Move the memory counter back from a multiple of 0x20 to the 54 | // actual end of the _preBytes data. 55 | mc := end 56 | // Stop copying when the memory counter reaches the new combined 57 | // length of the arrays. 58 | end := add(mc, length) 59 | 60 | for { 61 | let cc := add(_postBytes, 0x20) 62 | } lt(mc, end) { 63 | mc := add(mc, 0x20) 64 | cc := add(cc, 0x20) 65 | } { 66 | mstore(mc, mload(cc)) 67 | } 68 | 69 | // Update the free-memory pointer by padding our last write location 70 | // to 32 bytes: add 31 bytes to the end of tempBytes to move to the 71 | // next 32 byte block, then round down to the nearest multiple of 72 | // 32. If the sum of the length of the two arrays is zero then add 73 | // one before rounding down to leave a blank 32 bytes (the length block with 0). 74 | mstore( 75 | 0x40, 76 | and( 77 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), 78 | not(31) // Round down to the nearest 32 bytes. 79 | ) 80 | ) 81 | } 82 | 83 | return tempBytes; 84 | } 85 | 86 | function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { 87 | assembly { 88 | // Read the first 32 bytes of _preBytes storage, which is the length 89 | // of the array. (We don't need to use the offset into the slot 90 | // because arrays use the entire slot.) 91 | let fslot := sload(_preBytes.slot) 92 | // Arrays of 31 bytes or less have an even value in their slot, 93 | // while longer arrays have an odd value. The actual length is 94 | // the slot divided by two for odd values, and the lowest order 95 | // byte divided by two for even values. 96 | // If the slot is even, bitwise and the slot with 255 and divide by 97 | // two to get the length. If the slot is odd, bitwise and the slot 98 | // with -1 and divide by two. 99 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) 100 | let mlength := mload(_postBytes) 101 | let newlength := add(slength, mlength) 102 | // slength can contain both the length and contents of the array 103 | // if length < 32 bytes so let's prepare for that 104 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage 105 | switch add(lt(slength, 32), lt(newlength, 32)) 106 | case 2 { 107 | // Since the new array still fits in the slot, we just need to 108 | // update the contents of the slot. 109 | // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length 110 | sstore( 111 | _preBytes.slot, 112 | // all the modifications to the slot are inside this 113 | // next block 114 | add( 115 | // we can just add to the slot contents because the 116 | // bytes we want to change are the LSBs 117 | fslot, 118 | add( 119 | mul( 120 | div( 121 | // load the bytes from memory 122 | mload(add(_postBytes, 0x20)), 123 | // zero all bytes to the right 124 | exp(0x100, sub(32, mlength)) 125 | ), 126 | // and now shift left the number of bytes to 127 | // leave space for the length in the slot 128 | exp(0x100, sub(32, newlength)) 129 | ), 130 | // increase length by the double of the memory 131 | // bytes length 132 | mul(mlength, 2) 133 | ) 134 | ) 135 | ) 136 | } 137 | case 1 { 138 | // The stored value fits in the slot, but the combined value 139 | // will exceed it. 140 | // get the keccak hash to get the contents of the array 141 | mstore(0x0, _preBytes.slot) 142 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) 143 | 144 | // save new length 145 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) 146 | 147 | // The contents of the _postBytes array start 32 bytes into 148 | // the structure. Our first read should obtain the `submod` 149 | // bytes that can fit into the unused space in the last word 150 | // of the stored array. To get this, we read 32 bytes starting 151 | // from `submod`, so the data we read overlaps with the array 152 | // contents by `submod` bytes. Masking the lowest-order 153 | // `submod` bytes allows us to add that value directly to the 154 | // stored value. 155 | 156 | let submod := sub(32, slength) 157 | let mc := add(_postBytes, submod) 158 | let end := add(_postBytes, mlength) 159 | let mask := sub(exp(0x100, submod), 1) 160 | 161 | sstore( 162 | sc, 163 | add( 164 | and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), 165 | and(mload(mc), mask) 166 | ) 167 | ) 168 | 169 | for { 170 | mc := add(mc, 0x20) 171 | sc := add(sc, 1) 172 | } lt(mc, end) { 173 | sc := add(sc, 1) 174 | mc := add(mc, 0x20) 175 | } { 176 | sstore(sc, mload(mc)) 177 | } 178 | 179 | mask := exp(0x100, sub(mc, end)) 180 | 181 | sstore(sc, mul(div(mload(mc), mask), mask)) 182 | } 183 | default { 184 | // get the keccak hash to get the contents of the array 185 | mstore(0x0, _preBytes.slot) 186 | // Start copying to the last used word of the stored array. 187 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) 188 | 189 | // save new length 190 | sstore(_preBytes.slot, add(mul(newlength, 2), 1)) 191 | 192 | // Copy over the first `submod` bytes of the new data as in 193 | // case 1 above. 194 | let slengthmod := mod(slength, 32) 195 | let mlengthmod := mod(mlength, 32) 196 | let submod := sub(32, slengthmod) 197 | let mc := add(_postBytes, submod) 198 | let end := add(_postBytes, mlength) 199 | let mask := sub(exp(0x100, submod), 1) 200 | 201 | sstore(sc, add(sload(sc), and(mload(mc), mask))) 202 | 203 | for { 204 | sc := add(sc, 1) 205 | mc := add(mc, 0x20) 206 | } lt(mc, end) { 207 | sc := add(sc, 1) 208 | mc := add(mc, 0x20) 209 | } { 210 | sstore(sc, mload(mc)) 211 | } 212 | 213 | mask := exp(0x100, sub(mc, end)) 214 | 215 | sstore(sc, mul(div(mload(mc), mask), mask)) 216 | } 217 | } 218 | } 219 | 220 | function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { 221 | require(_length + 31 >= _length, "slice_overflow"); 222 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); 223 | 224 | bytes memory tempBytes; 225 | 226 | assembly { 227 | switch iszero(_length) 228 | case 0 { 229 | // Get a location of some free memory and store it in tempBytes as 230 | // Solidity does for memory variables. 231 | tempBytes := mload(0x40) 232 | 233 | // The first word of the slice result is potentially a partial 234 | // word read from the original array. To read it, we calculate 235 | // the length of that partial word and start copying that many 236 | // bytes into the array. The first word we copy will start with 237 | // data we don't care about, but the last `lengthmod` bytes will 238 | // land at the beginning of the contents of the new array. When 239 | // we're done copying, we overwrite the full first word with 240 | // the actual length of the slice. 241 | let lengthmod := and(_length, 31) 242 | 243 | // The multiplication in the next line is necessary 244 | // because when slicing multiples of 32 bytes (lengthmod == 0) 245 | // the following copy loop was copying the origin's length 246 | // and then ending prematurely not copying everything it should. 247 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 248 | let end := add(mc, _length) 249 | 250 | for { 251 | // The multiplication in the next line has the same exact purpose 252 | // as the one above. 253 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 254 | } lt(mc, end) { 255 | mc := add(mc, 0x20) 256 | cc := add(cc, 0x20) 257 | } { 258 | mstore(mc, mload(cc)) 259 | } 260 | 261 | mstore(tempBytes, _length) 262 | 263 | //update free-memory pointer 264 | //allocating the array padded to 32 bytes like the compiler does now 265 | mstore(0x40, and(add(mc, 31), not(31))) 266 | } 267 | //if we want a zero-length slice let's just return a zero-length array 268 | default { 269 | tempBytes := mload(0x40) 270 | //zero out the 32 bytes slice we are about to return 271 | //we need to do it because Solidity does not garbage collect 272 | mstore(tempBytes, 0) 273 | 274 | mstore(0x40, add(tempBytes, 0x20)) 275 | } 276 | } 277 | 278 | return tempBytes; 279 | } 280 | 281 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { 282 | require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); 283 | address tempAddress; 284 | 285 | assembly { 286 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 287 | } 288 | 289 | return tempAddress; 290 | } 291 | 292 | function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { 293 | require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); 294 | uint8 tempUint; 295 | 296 | assembly { 297 | tempUint := mload(add(add(_bytes, 0x1), _start)) 298 | } 299 | 300 | return tempUint; 301 | } 302 | 303 | function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { 304 | require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); 305 | uint16 tempUint; 306 | 307 | assembly { 308 | tempUint := mload(add(add(_bytes, 0x2), _start)) 309 | } 310 | 311 | return tempUint; 312 | } 313 | 314 | function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { 315 | require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); 316 | uint32 tempUint; 317 | 318 | assembly { 319 | tempUint := mload(add(add(_bytes, 0x4), _start)) 320 | } 321 | 322 | return tempUint; 323 | } 324 | 325 | function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { 326 | require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); 327 | uint64 tempUint; 328 | 329 | assembly { 330 | tempUint := mload(add(add(_bytes, 0x8), _start)) 331 | } 332 | 333 | return tempUint; 334 | } 335 | 336 | function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { 337 | require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); 338 | uint96 tempUint; 339 | 340 | assembly { 341 | tempUint := mload(add(add(_bytes, 0xc), _start)) 342 | } 343 | 344 | return tempUint; 345 | } 346 | 347 | function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { 348 | require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); 349 | uint128 tempUint; 350 | 351 | assembly { 352 | tempUint := mload(add(add(_bytes, 0x10), _start)) 353 | } 354 | 355 | return tempUint; 356 | } 357 | 358 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { 359 | require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); 360 | uint256 tempUint; 361 | 362 | assembly { 363 | tempUint := mload(add(add(_bytes, 0x20), _start)) 364 | } 365 | 366 | return tempUint; 367 | } 368 | 369 | function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { 370 | require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); 371 | bytes32 tempBytes32; 372 | 373 | assembly { 374 | tempBytes32 := mload(add(add(_bytes, 0x20), _start)) 375 | } 376 | 377 | return tempBytes32; 378 | } 379 | 380 | function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { 381 | bool success = true; 382 | 383 | assembly { 384 | let length := mload(_preBytes) 385 | 386 | // if lengths don't match the arrays are not equal 387 | switch eq(length, mload(_postBytes)) 388 | case 1 { 389 | // cb is a circuit breaker in the for loop since there's 390 | // no said feature for inline assembly loops 391 | // cb = 1 - don't breaker 392 | // cb = 0 - break 393 | let cb := 1 394 | 395 | let mc := add(_preBytes, 0x20) 396 | let end := add(mc, length) 397 | 398 | for { 399 | let cc := add(_postBytes, 0x20) 400 | // the next line is the loop condition: 401 | // while(uint256(mc < end) + cb == 2) 402 | } eq(add(lt(mc, end), cb), 2) { 403 | mc := add(mc, 0x20) 404 | cc := add(cc, 0x20) 405 | } { 406 | // if any of these checks fails then arrays are not equal 407 | if iszero(eq(mload(mc), mload(cc))) { 408 | // unsuccess: 409 | success := 0 410 | cb := 0 411 | } 412 | } 413 | } 414 | default { 415 | // unsuccess: 416 | success := 0 417 | } 418 | } 419 | 420 | return success; 421 | } 422 | 423 | function equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { 424 | bool success = true; 425 | 426 | assembly { 427 | let length := mload(_preBytes) 428 | 429 | // if lengths don't match the arrays are not equal 430 | switch eq(length, mload(_postBytes)) 431 | case 1 { 432 | // cb is a circuit breaker in the for loop since there's 433 | // no said feature for inline assembly loops 434 | // cb = 1 - don't breaker 435 | // cb = 0 - break 436 | let cb := 1 437 | 438 | let endMinusWord := add(_preBytes, length) 439 | let mc := add(_preBytes, 0x20) 440 | let cc := add(_postBytes, 0x20) 441 | 442 | for { 443 | // the next line is the loop condition: 444 | // while(uint256(mc < endWord) + cb == 2) 445 | } eq(add(lt(mc, endMinusWord), cb), 2) { 446 | mc := add(mc, 0x20) 447 | cc := add(cc, 0x20) 448 | } { 449 | // if any of these checks fails then arrays are not equal 450 | if iszero(eq(mload(mc), mload(cc))) { 451 | // unsuccess: 452 | success := 0 453 | cb := 0 454 | } 455 | } 456 | 457 | // Only if still successful 458 | // For <1 word tail bytes 459 | if gt(success, 0) { 460 | // Get the remainder of length/32 461 | // length % 32 = AND(length, 32 - 1) 462 | let numTailBytes := and(length, 0x1f) 463 | let mcRem := mload(mc) 464 | let ccRem := mload(cc) 465 | for { 466 | let i := 0 467 | // the next line is the loop condition: 468 | // while(uint256(i < numTailBytes) + cb == 2) 469 | } eq(add(lt(i, numTailBytes), cb), 2) { 470 | i := add(i, 1) 471 | } { 472 | if iszero(eq(byte(i, mcRem), byte(i, ccRem))) { 473 | // unsuccess: 474 | success := 0 475 | cb := 0 476 | } 477 | } 478 | } 479 | } 480 | default { 481 | // unsuccess: 482 | success := 0 483 | } 484 | } 485 | 486 | return success; 487 | } 488 | 489 | function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { 490 | bool success = true; 491 | 492 | assembly { 493 | // we know _preBytes_offset is 0 494 | let fslot := sload(_preBytes.slot) 495 | // Decode the length of the stored array like in concatStorage(). 496 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) 497 | let mlength := mload(_postBytes) 498 | 499 | // if lengths don't match the arrays are not equal 500 | switch eq(slength, mlength) 501 | case 1 { 502 | // slength can contain both the length and contents of the array 503 | // if length < 32 bytes so let's prepare for that 504 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage 505 | if iszero(iszero(slength)) { 506 | switch lt(slength, 32) 507 | case 1 { 508 | // blank the last byte which is the length 509 | fslot := mul(div(fslot, 0x100), 0x100) 510 | 511 | if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { 512 | // unsuccess: 513 | success := 0 514 | } 515 | } 516 | default { 517 | // cb is a circuit breaker in the for loop since there's 518 | // no said feature for inline assembly loops 519 | // cb = 1 - don't breaker 520 | // cb = 0 - break 521 | let cb := 1 522 | 523 | // get the keccak hash to get the contents of the array 524 | mstore(0x0, _preBytes.slot) 525 | let sc := keccak256(0x0, 0x20) 526 | 527 | let mc := add(_postBytes, 0x20) 528 | let end := add(mc, mlength) 529 | 530 | // the next line is the loop condition: 531 | // while(uint256(mc < end) + cb == 2) 532 | for { 533 | 534 | } eq(add(lt(mc, end), cb), 2) { 535 | sc := add(sc, 1) 536 | mc := add(mc, 0x20) 537 | } { 538 | if iszero(eq(sload(sc), mload(mc))) { 539 | // unsuccess: 540 | success := 0 541 | cb := 0 542 | } 543 | } 544 | } 545 | } 546 | } 547 | default { 548 | // unsuccess: 549 | success := 0 550 | } 551 | } 552 | 553 | return success; 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fhenixprotocol/contracts", 3 | "description": "Smart Contract Library for the Fhenix Blockchain with FHE primitives", 4 | "version": "0.3.2-alpha.2", 5 | "author": { 6 | "name": "FhenixProtocol", 7 | "url": "https://github.com/fhenixprotocol/fhenix-contracts" 8 | }, 9 | "files": [ 10 | "FHE.sol", 11 | "FheOS.sol", 12 | "access/**", 13 | "experimental/**", 14 | "utils/**" 15 | ], 16 | "keywords": [ 17 | "blockchain", 18 | "ethereum", 19 | "smart-contracts", 20 | "solidity", 21 | "FHE", 22 | "encryption", 23 | "privacy" 24 | ], 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "dependencies": { 29 | "@openzeppelin/contracts": "^5.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/test/PermissionedV2Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import "../FHE.sol"; 5 | import {PermissionedV2, PermissionV2} from "../access/PermissionedV2.sol"; 6 | 7 | contract PermissionedV2Counter is PermissionedV2 { 8 | mapping(address => euint32) private userCounter; 9 | address public owner; 10 | 11 | constructor() PermissionedV2("COUNTER") { 12 | owner = msg.sender; 13 | } 14 | 15 | function add(inEuint32 calldata encryptedValue) public { 16 | euint32 value = FHE.asEuint32(encryptedValue); 17 | userCounter[msg.sender] = userCounter[msg.sender] + value; 18 | } 19 | 20 | function getCounter(address user) public view returns (uint256) { 21 | return FHE.decrypt(userCounter[user]); 22 | } 23 | 24 | function getCounterPermit( 25 | PermissionV2 memory permission 26 | ) public view withPermission(permission) returns (uint256) { 27 | return FHE.decrypt(userCounter[permission.issuer]); 28 | } 29 | 30 | function getCounterPermitSealed( 31 | PermissionV2 memory permission 32 | ) public view withPermission(permission) returns (string memory) { 33 | return 34 | FHE.sealoutput( 35 | userCounter[permission.issuer], 36 | permission.sealingKey 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/test/PermissionedV2Validators.sol: -------------------------------------------------------------------------------- 1 | // solhint-disable func-name-mixedcase 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.9.0; 4 | 5 | import {IPermissionValidator, PermissionV2} from "../access/PermissionedV2.sol"; 6 | 7 | contract PermissionedV2RevokableValidator is IPermissionValidator { 8 | uint256 public rid = 1; 9 | mapping(uint256 => bool) public revoked; 10 | mapping(uint256 => address) public owner; 11 | 12 | function createRevokableId() public returns (uint256) { 13 | uint256 id = rid; 14 | rid += 1; 15 | 16 | revoked[id] = false; 17 | owner[id] = msg.sender; 18 | 19 | return id; 20 | } 21 | 22 | function revokeId(uint256 id) public { 23 | require(owner[id] == msg.sender, "Not owner of id"); 24 | revoked[id] = true; 25 | } 26 | 27 | function disabled(address, uint256 id) external view returns (bool) { 28 | return revoked[id]; 29 | } 30 | } 31 | 32 | contract PermissionedV2TimestampValidator is IPermissionValidator { 33 | mapping(address => uint256) public userLastRevokedTimestamp; 34 | 35 | function revokeExisting() public { 36 | userLastRevokedTimestamp[msg.sender] = block.timestamp; 37 | } 38 | 39 | function disabled(address issuer, uint256 id) external view returns (bool) { 40 | return id < userLastRevokedTimestamp[issuer]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/test/TypedSealedOutputsTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {FHE, BindingsEbool, BindingsEuint8, SealedBool, SealedUint, SealedAddress, ebool, euint8, euint16, euint32, euint64, euint128, euint256, eaddress} from "../FHE.sol"; 5 | import {PermissionedV2, PermissionV2} from "../access/PermissionedV2.sol"; 6 | 7 | contract TypedSealedOutputsTest is PermissionedV2 { 8 | using BindingsEuint8 for euint8; 9 | 10 | constructor() PermissionedV2("TEST") {} 11 | 12 | function getSealedEBool(PermissionV2 memory permission, bool value) public view withPermission(permission) returns (SealedBool memory, SealedBool memory) { 13 | return ( 14 | FHE.sealoutputTyped(FHE.asEbool(value), permission.sealingKey), 15 | FHE.asEbool(value).sealTyped(permission.sealingKey) 16 | ); 17 | } 18 | 19 | function getSealedEUint8(PermissionV2 memory permission, uint8 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 20 | return ( 21 | FHE.sealoutputTyped(FHE.asEuint8(value), permission.sealingKey), 22 | FHE.asEuint8(value).sealTyped(permission.sealingKey) 23 | ); 24 | } 25 | 26 | function getSealedEUint16(PermissionV2 memory permission, uint16 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 27 | return ( 28 | FHE.sealoutputTyped(FHE.asEuint16(value), permission.sealingKey), 29 | FHE.asEuint16(value).sealTyped(permission.sealingKey) 30 | ); 31 | } 32 | 33 | function getSealedEUint32(PermissionV2 memory permission, uint32 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 34 | return ( 35 | FHE.sealoutputTyped(FHE.asEuint32(value), permission.sealingKey), 36 | FHE.asEuint32(value).sealTyped(permission.sealingKey) 37 | ); 38 | } 39 | 40 | function getSealedEUint64(PermissionV2 memory permission, uint64 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 41 | return ( 42 | FHE.sealoutputTyped(FHE.asEuint64(value), permission.sealingKey), 43 | FHE.asEuint64(value).sealTyped(permission.sealingKey) 44 | ); 45 | } 46 | 47 | 48 | 49 | function getSealedEUint128(PermissionV2 memory permission, uint128 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 50 | return ( 51 | FHE.sealoutputTyped(FHE.asEuint128(value), permission.sealingKey), 52 | FHE.asEuint128(value).sealTyped(permission.sealingKey) 53 | ); 54 | } 55 | 56 | 57 | 58 | function getSealedEUint256(PermissionV2 memory permission, uint256 value) public view withPermission(permission) returns (SealedUint memory, SealedUint memory) { 59 | return ( 60 | FHE.sealoutputTyped(FHE.asEuint256(value), permission.sealingKey), 61 | FHE.asEuint256(value).sealTyped(permission.sealingKey) 62 | ); 63 | } 64 | 65 | function getSealedEAddress(PermissionV2 memory permission, address value) public view withPermission(permission) returns (SealedAddress memory, SealedAddress memory) { 66 | return ( 67 | FHE.sealoutputTyped(FHE.asEaddress(value), permission.sealingKey), 68 | FHE.asEaddress(value).sealTyped(permission.sealingKey) 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/utils/debug/Console.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.9.0; 3 | 4 | import {FheOps, Precompiles} from "../../FheOS.sol"; 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | library Console { 8 | function _logImpl(string memory payload) internal pure { 9 | FheOps(Precompiles.Fheos).log(payload); 10 | } 11 | 12 | function _logImpl2Params(string memory p0, string memory p1) internal pure { 13 | string memory payload = string(abi.encodePacked(string("p0: "), p0, string(" p1: "), p1)); 14 | _logImpl(payload); 15 | } 16 | 17 | function _logImpl3Params(string memory p0, string memory p1, string memory p2) internal pure { 18 | string memory payload = string(abi.encodePacked(string("p0: "), p0, string(" p1: "), 19 | p1, string(" p2: "), p2)); 20 | _logImpl(payload); 21 | } 22 | 23 | function _intToString(int _value) internal pure returns (string memory) { 24 | return Strings.toStringSigned(_value); 25 | } 26 | 27 | function _uintToString(uint _value) internal pure returns (string memory) { 28 | return Strings.toString(_value); 29 | } 30 | 31 | function _addressToString(address _addr) internal pure returns (string memory) { 32 | bytes memory alphabet = "0123456789abcdef"; 33 | bytes20 value = bytes20(_addr); 34 | bytes memory buffer = new bytes(42); // 2 characters for '0x' and 40 characters for the address 35 | buffer[0] = "0"; 36 | buffer[1] = "x"; 37 | for (uint256 i = 0; i < 20; i++) { 38 | buffer[2+i*2] = alphabet[uint8(value[i] >> 4)]; 39 | buffer[3+i*2] = alphabet[uint8(value[i] & 0x0f)]; 40 | } 41 | return string(buffer); 42 | } 43 | 44 | function _boolToString(bool val) internal pure returns (string memory) { 45 | return val ? "true" : "false"; 46 | } 47 | 48 | function _logInt(int256 p0) internal pure { 49 | _logImpl(_intToString(p0)); 50 | } 51 | 52 | function _logUint(uint256 p0) internal pure { 53 | _logImpl(_uintToString(p0)); 54 | } 55 | 56 | function _logBool(bool p0) internal pure { 57 | _logImpl(_boolToString(p0)); 58 | } 59 | 60 | function _logAddress(address p0) internal pure { 61 | _logImpl(_addressToString(p0)); 62 | } 63 | 64 | function logBytes(bytes memory p0) internal pure { 65 | _logImpl(string(p0)); 66 | } 67 | 68 | function log(int256 p0) internal pure { 69 | _logInt(p0); 70 | } 71 | 72 | function log(uint256 p0) internal pure { 73 | _logUint(p0); 74 | } 75 | 76 | function log(string memory p0) internal pure { 77 | _logImpl(p0); 78 | } 79 | 80 | function log(bool p0) internal pure { 81 | _logBool(p0); 82 | } 83 | 84 | function log(address p0) internal pure { 85 | _logAddress(p0); 86 | } 87 | function log(int256 p0, int256 p1) internal pure { 88 | _logImpl2Params(_intToString(p0), _intToString(p1)); 89 | } 90 | 91 | function log(int256 p0, uint256 p1) internal pure { 92 | _logImpl2Params(_intToString(p0), _uintToString(p1)); 93 | } 94 | 95 | function log(int256 p0, string memory p1) internal pure { 96 | _logImpl2Params(_intToString(p0), p1); 97 | } 98 | 99 | function log(int256 p0, bool p1) internal pure { 100 | _logImpl2Params(_intToString(p0), _boolToString(p1)); 101 | } 102 | 103 | function log(int256 p0, address p1) internal pure { 104 | _logImpl2Params(_intToString(p0), _addressToString(p1)); 105 | } 106 | 107 | function log(uint256 p0, int256 p1) internal pure { 108 | _logImpl2Params(_uintToString(p0), _intToString(p1)); 109 | } 110 | 111 | function log(uint256 p0, uint256 p1) internal pure { 112 | _logImpl2Params(_uintToString(p0), _uintToString(p1)); 113 | } 114 | 115 | function log(uint256 p0, string memory p1) internal pure { 116 | _logImpl2Params(_uintToString(p0), p1); 117 | } 118 | 119 | function log(uint256 p0, bool p1) internal pure { 120 | _logImpl2Params(_uintToString(p0), _boolToString(p1)); 121 | } 122 | 123 | function log(uint256 p0, address p1) internal pure { 124 | _logImpl2Params(_uintToString(p0), _addressToString(p1)); 125 | } 126 | 127 | function log(string memory p0, int256 p1) internal pure { 128 | _logImpl2Params(p0, _intToString(p1)); 129 | } 130 | 131 | function log(string memory p0, uint256 p1) internal pure { 132 | _logImpl2Params(p0, _uintToString(p1)); 133 | } 134 | 135 | function log(string memory p0, string memory p1) internal pure { 136 | _logImpl2Params(p0, p1); 137 | } 138 | 139 | function log(string memory p0, bool p1) internal pure { 140 | _logImpl2Params(p0, _boolToString(p1)); 141 | } 142 | 143 | function log(string memory p0, address p1) internal pure { 144 | _logImpl2Params(p0, _addressToString(p1)); 145 | } 146 | 147 | function log(bool p0, int256 p1) internal pure { 148 | _logImpl2Params(_boolToString(p0), _intToString(p1)); 149 | } 150 | 151 | function log(bool p0, uint256 p1) internal pure { 152 | _logImpl2Params(_boolToString(p0), _uintToString(p1)); 153 | } 154 | 155 | function log(bool p0, string memory p1) internal pure { 156 | _logImpl2Params(_boolToString(p0), p1); 157 | } 158 | 159 | function log(bool p0, bool p1) internal pure { 160 | _logImpl2Params(_boolToString(p0), _boolToString(p1)); 161 | } 162 | 163 | function log(bool p0, address p1) internal pure { 164 | _logImpl2Params(_boolToString(p0), _addressToString(p1)); 165 | } 166 | 167 | function log(address p0, int256 p1) internal pure { 168 | _logImpl2Params(_addressToString(p0), _intToString(p1)); 169 | } 170 | 171 | function log(address p0, uint256 p1) internal pure { 172 | _logImpl2Params(_addressToString(p0), _uintToString(p1)); 173 | } 174 | 175 | function log(address p0, string memory p1) internal pure { 176 | _logImpl2Params(_addressToString(p0), p1); 177 | } 178 | 179 | function log(address p0, bool p1) internal pure { 180 | _logImpl2Params(_addressToString(p0), _boolToString(p1)); 181 | } 182 | 183 | function log(address p0, address p1) internal pure { 184 | _logImpl2Params(_addressToString(p0), _addressToString(p1)); 185 | } 186 | 187 | function log(int256 p0, int256 p1, int256 p2) internal pure { 188 | _logImpl3Params(_intToString(p0), _intToString(p1), _intToString(p2)); 189 | } 190 | 191 | function log(int256 p0, int256 p1, uint256 p2) internal pure { 192 | _logImpl3Params(_intToString(p0), _intToString(p1), _uintToString(p2)); 193 | } 194 | 195 | function log(int256 p0, int256 p1, string memory p2) internal pure { 196 | _logImpl3Params(_intToString(p0), _intToString(p1), p2); 197 | } 198 | 199 | function log(int256 p0, int256 p1, bool p2) internal pure { 200 | _logImpl3Params(_intToString(p0), _intToString(p1), _boolToString(p2)); 201 | } 202 | 203 | function log(int256 p0, int256 p1, address p2) internal pure { 204 | _logImpl3Params(_intToString(p0), _intToString(p1), _addressToString(p2)); 205 | } 206 | 207 | function log(int256 p0, uint256 p1, int256 p2) internal pure { 208 | _logImpl3Params(_intToString(p0), _uintToString(p1), _intToString(p2)); 209 | } 210 | 211 | function log(int256 p0, uint256 p1, uint256 p2) internal pure { 212 | _logImpl3Params(_intToString(p0), _uintToString(p1), _uintToString(p2)); 213 | } 214 | 215 | function log(int256 p0, uint256 p1, string memory p2) internal pure { 216 | _logImpl3Params(_intToString(p0), _uintToString(p1), p2); 217 | } 218 | 219 | function log(int256 p0, uint256 p1, bool p2) internal pure { 220 | _logImpl3Params(_intToString(p0), _uintToString(p1), _boolToString(p2)); 221 | } 222 | 223 | function log(int256 p0, uint256 p1, address p2) internal pure { 224 | _logImpl3Params(_intToString(p0), _uintToString(p1), _addressToString(p2)); 225 | } 226 | 227 | function log(int256 p0, string memory p1, int256 p2) internal pure { 228 | _logImpl3Params(_intToString(p0), p1, _intToString(p2)); 229 | } 230 | 231 | function log(int256 p0, string memory p1, uint256 p2) internal pure { 232 | _logImpl3Params(_intToString(p0), p1, _uintToString(p2)); 233 | } 234 | 235 | function log(int256 p0, string memory p1, string memory p2) internal pure { 236 | _logImpl3Params(_intToString(p0), p1, p2); 237 | } 238 | 239 | function log(int256 p0, string memory p1, bool p2) internal pure { 240 | _logImpl3Params(_intToString(p0), p1, _boolToString(p2)); 241 | } 242 | 243 | function log(int256 p0, string memory p1, address p2) internal pure { 244 | _logImpl3Params(_intToString(p0), p1, _addressToString(p2)); 245 | } 246 | 247 | function log(int256 p0, bool p1, int256 p2) internal pure { 248 | _logImpl3Params(_intToString(p0), _boolToString(p1), _intToString(p2)); 249 | } 250 | 251 | function log(int256 p0, bool p1, uint256 p2) internal pure { 252 | _logImpl3Params(_intToString(p0), _boolToString(p1), _uintToString(p2)); 253 | } 254 | 255 | function log(int256 p0, bool p1, string memory p2) internal pure { 256 | _logImpl3Params(_intToString(p0), _boolToString(p1), p2); 257 | } 258 | 259 | function log(int256 p0, bool p1, bool p2) internal pure { 260 | _logImpl3Params(_intToString(p0), _boolToString(p1), _boolToString(p2)); 261 | } 262 | 263 | function log(int256 p0, bool p1, address p2) internal pure { 264 | _logImpl3Params(_intToString(p0), _boolToString(p1), _addressToString(p2)); 265 | } 266 | 267 | function log(int256 p0, address p1, int256 p2) internal pure { 268 | _logImpl3Params(_intToString(p0), _addressToString(p1), _intToString(p2)); 269 | } 270 | 271 | function log(int256 p0, address p1, uint256 p2) internal pure { 272 | _logImpl3Params(_intToString(p0), _addressToString(p1), _uintToString(p2)); 273 | } 274 | 275 | function log(int256 p0, address p1, string memory p2) internal pure { 276 | _logImpl3Params(_intToString(p0), _addressToString(p1), p2); 277 | } 278 | 279 | function log(int256 p0, address p1, bool p2) internal pure { 280 | _logImpl3Params(_intToString(p0), _addressToString(p1), _boolToString(p2)); 281 | } 282 | 283 | function log(int256 p0, address p1, address p2) internal pure { 284 | _logImpl3Params(_intToString(p0), _addressToString(p1), _addressToString(p2)); 285 | } 286 | 287 | function log(uint256 p0, int256 p1, int256 p2) internal pure { 288 | _logImpl3Params(_uintToString(p0), _intToString(p1), _intToString(p2)); 289 | } 290 | 291 | function log(uint256 p0, int256 p1, uint256 p2) internal pure { 292 | _logImpl3Params(_uintToString(p0), _intToString(p1), _uintToString(p2)); 293 | } 294 | 295 | function log(uint256 p0, int256 p1, string memory p2) internal pure { 296 | _logImpl3Params(_uintToString(p0), _intToString(p1), p2); 297 | } 298 | 299 | function log(uint256 p0, int256 p1, bool p2) internal pure { 300 | _logImpl3Params(_uintToString(p0), _intToString(p1), _boolToString(p2)); 301 | } 302 | 303 | function log(uint256 p0, int256 p1, address p2) internal pure { 304 | _logImpl3Params(_uintToString(p0), _intToString(p1), _addressToString(p2)); 305 | } 306 | 307 | function log(uint256 p0, uint256 p1, int256 p2) internal pure { 308 | _logImpl3Params(_uintToString(p0), _uintToString(p1), _intToString(p2)); 309 | } 310 | 311 | function log(uint256 p0, uint256 p1, uint256 p2) internal pure { 312 | _logImpl3Params(_uintToString(p0), _uintToString(p1), _uintToString(p2)); 313 | } 314 | 315 | function log(uint256 p0, uint256 p1, string memory p2) internal pure { 316 | _logImpl3Params(_uintToString(p0), _uintToString(p1), p2); 317 | } 318 | 319 | function log(uint256 p0, uint256 p1, bool p2) internal pure { 320 | _logImpl3Params(_uintToString(p0), _uintToString(p1), _boolToString(p2)); 321 | } 322 | 323 | function log(uint256 p0, uint256 p1, address p2) internal pure { 324 | _logImpl3Params(_uintToString(p0), _uintToString(p1), _addressToString(p2)); 325 | } 326 | 327 | function log(uint256 p0, string memory p1, int256 p2) internal pure { 328 | _logImpl3Params(_uintToString(p0), p1, _intToString(p2)); 329 | } 330 | 331 | function log(uint256 p0, string memory p1, uint256 p2) internal pure { 332 | _logImpl3Params(_uintToString(p0), p1, _uintToString(p2)); 333 | } 334 | 335 | function log(uint256 p0, string memory p1, string memory p2) internal pure { 336 | _logImpl3Params(_uintToString(p0), p1, p2); 337 | } 338 | 339 | function log(uint256 p0, string memory p1, bool p2) internal pure { 340 | _logImpl3Params(_uintToString(p0), p1, _boolToString(p2)); 341 | } 342 | 343 | function log(uint256 p0, string memory p1, address p2) internal pure { 344 | _logImpl3Params(_uintToString(p0), p1, _addressToString(p2)); 345 | } 346 | 347 | function log(uint256 p0, bool p1, int256 p2) internal pure { 348 | _logImpl3Params(_uintToString(p0), _boolToString(p1), _intToString(p2)); 349 | } 350 | 351 | function log(uint256 p0, bool p1, uint256 p2) internal pure { 352 | _logImpl3Params(_uintToString(p0), _boolToString(p1), _uintToString(p2)); 353 | } 354 | 355 | function log(uint256 p0, bool p1, string memory p2) internal pure { 356 | _logImpl3Params(_uintToString(p0), _boolToString(p1), p2); 357 | } 358 | 359 | function log(uint256 p0, bool p1, bool p2) internal pure { 360 | _logImpl3Params(_uintToString(p0), _boolToString(p1), _boolToString(p2)); 361 | } 362 | 363 | function log(uint256 p0, bool p1, address p2) internal pure { 364 | _logImpl3Params(_uintToString(p0), _boolToString(p1), _addressToString(p2)); 365 | } 366 | 367 | function log(uint256 p0, address p1, int256 p2) internal pure { 368 | _logImpl3Params(_uintToString(p0), _addressToString(p1), _intToString(p2)); 369 | } 370 | 371 | function log(uint256 p0, address p1, uint256 p2) internal pure { 372 | _logImpl3Params(_uintToString(p0), _addressToString(p1), _uintToString(p2)); 373 | } 374 | 375 | function log(uint256 p0, address p1, string memory p2) internal pure { 376 | _logImpl3Params(_uintToString(p0), _addressToString(p1), p2); 377 | } 378 | 379 | function log(uint256 p0, address p1, bool p2) internal pure { 380 | _logImpl3Params(_uintToString(p0), _addressToString(p1), _boolToString(p2)); 381 | } 382 | 383 | function log(uint256 p0, address p1, address p2) internal pure { 384 | _logImpl3Params(_uintToString(p0), _addressToString(p1), _addressToString(p2)); 385 | } 386 | 387 | function log(string memory p0, int256 p1, int256 p2) internal pure { 388 | _logImpl3Params(p0, _intToString(p1), _intToString(p2)); 389 | } 390 | 391 | function log(string memory p0, int256 p1, uint256 p2) internal pure { 392 | _logImpl3Params(p0, _intToString(p1), _uintToString(p2)); 393 | } 394 | 395 | function log(string memory p0, int256 p1, string memory p2) internal pure { 396 | _logImpl3Params(p0, _intToString(p1), p2); 397 | } 398 | 399 | function log(string memory p0, int256 p1, bool p2) internal pure { 400 | _logImpl3Params(p0, _intToString(p1), _boolToString(p2)); 401 | } 402 | 403 | function log(string memory p0, int256 p1, address p2) internal pure { 404 | _logImpl3Params(p0, _intToString(p1), _addressToString(p2)); 405 | } 406 | 407 | function log(string memory p0, uint256 p1, int256 p2) internal pure { 408 | _logImpl3Params(p0, _uintToString(p1), _intToString(p2)); 409 | } 410 | 411 | function log(string memory p0, uint256 p1, uint256 p2) internal pure { 412 | _logImpl3Params(p0, _uintToString(p1), _uintToString(p2)); 413 | } 414 | 415 | function log(string memory p0, uint256 p1, string memory p2) internal pure { 416 | _logImpl3Params(p0, _uintToString(p1), p2); 417 | } 418 | 419 | function log(string memory p0, uint256 p1, bool p2) internal pure { 420 | _logImpl3Params(p0, _uintToString(p1), _boolToString(p2)); 421 | } 422 | 423 | function log(string memory p0, uint256 p1, address p2) internal pure { 424 | _logImpl3Params(p0, _uintToString(p1), _addressToString(p2)); 425 | } 426 | 427 | function log(string memory p0, string memory p1, int256 p2) internal pure { 428 | _logImpl3Params(p0, p1, _intToString(p2)); 429 | } 430 | 431 | function log(string memory p0, string memory p1, uint256 p2) internal pure { 432 | _logImpl3Params(p0, p1, _uintToString(p2)); 433 | } 434 | 435 | function log(string memory p0, string memory p1, string memory p2) internal pure { 436 | _logImpl3Params(p0, p1, p2); 437 | } 438 | 439 | function log(string memory p0, string memory p1, bool p2) internal pure { 440 | _logImpl3Params(p0, p1, _boolToString(p2)); 441 | } 442 | 443 | function log(string memory p0, string memory p1, address p2) internal pure { 444 | _logImpl3Params(p0, p1, _addressToString(p2)); 445 | } 446 | 447 | function log(string memory p0, bool p1, int256 p2) internal pure { 448 | _logImpl3Params(p0, _boolToString(p1), _intToString(p2)); 449 | } 450 | 451 | function log(string memory p0, bool p1, uint256 p2) internal pure { 452 | _logImpl3Params(p0, _boolToString(p1), _uintToString(p2)); 453 | } 454 | 455 | function log(string memory p0, bool p1, string memory p2) internal pure { 456 | _logImpl3Params(p0, _boolToString(p1), p2); 457 | } 458 | 459 | function log(string memory p0, bool p1, bool p2) internal pure { 460 | _logImpl3Params(p0, _boolToString(p1), _boolToString(p2)); 461 | } 462 | 463 | function log(string memory p0, bool p1, address p2) internal pure { 464 | _logImpl3Params(p0, _boolToString(p1), _addressToString(p2)); 465 | } 466 | 467 | function log(string memory p0, address p1, int256 p2) internal pure { 468 | _logImpl3Params(p0, _addressToString(p1), _intToString(p2)); 469 | } 470 | 471 | function log(string memory p0, address p1, uint256 p2) internal pure { 472 | _logImpl3Params(p0, _addressToString(p1), _uintToString(p2)); 473 | } 474 | 475 | function log(string memory p0, address p1, string memory p2) internal pure { 476 | _logImpl3Params(p0, _addressToString(p1), p2); 477 | } 478 | 479 | function log(string memory p0, address p1, bool p2) internal pure { 480 | _logImpl3Params(p0, _addressToString(p1), _boolToString(p2)); 481 | } 482 | 483 | function log(string memory p0, address p1, address p2) internal pure { 484 | _logImpl3Params(p0, _addressToString(p1), _addressToString(p2)); 485 | } 486 | 487 | function log(bool p0, int256 p1, int256 p2) internal pure { 488 | _logImpl3Params(_boolToString(p0), _intToString(p1), _intToString(p2)); 489 | } 490 | 491 | function log(bool p0, int256 p1, uint256 p2) internal pure { 492 | _logImpl3Params(_boolToString(p0), _intToString(p1), _uintToString(p2)); 493 | } 494 | 495 | function log(bool p0, int256 p1, string memory p2) internal pure { 496 | _logImpl3Params(_boolToString(p0), _intToString(p1), p2); 497 | } 498 | 499 | function log(bool p0, int256 p1, bool p2) internal pure { 500 | _logImpl3Params(_boolToString(p0), _intToString(p1), _boolToString(p2)); 501 | } 502 | 503 | function log(bool p0, int256 p1, address p2) internal pure { 504 | _logImpl3Params(_boolToString(p0), _intToString(p1), _addressToString(p2)); 505 | } 506 | 507 | function log(bool p0, uint256 p1, int256 p2) internal pure { 508 | _logImpl3Params(_boolToString(p0), _uintToString(p1), _intToString(p2)); 509 | } 510 | 511 | function log(bool p0, uint256 p1, uint256 p2) internal pure { 512 | _logImpl3Params(_boolToString(p0), _uintToString(p1), _uintToString(p2)); 513 | } 514 | 515 | function log(bool p0, uint256 p1, string memory p2) internal pure { 516 | _logImpl3Params(_boolToString(p0), _uintToString(p1), p2); 517 | } 518 | 519 | function log(bool p0, uint256 p1, bool p2) internal pure { 520 | _logImpl3Params(_boolToString(p0), _uintToString(p1), _boolToString(p2)); 521 | } 522 | 523 | function log(bool p0, uint256 p1, address p2) internal pure { 524 | _logImpl3Params(_boolToString(p0), _uintToString(p1), _addressToString(p2)); 525 | } 526 | 527 | function log(bool p0, string memory p1, int256 p2) internal pure { 528 | _logImpl3Params(_boolToString(p0), p1, _intToString(p2)); 529 | } 530 | 531 | function log(bool p0, string memory p1, uint256 p2) internal pure { 532 | _logImpl3Params(_boolToString(p0), p1, _uintToString(p2)); 533 | } 534 | 535 | function log(bool p0, string memory p1, string memory p2) internal pure { 536 | _logImpl3Params(_boolToString(p0), p1, p2); 537 | } 538 | 539 | function log(bool p0, string memory p1, bool p2) internal pure { 540 | _logImpl3Params(_boolToString(p0), p1, _boolToString(p2)); 541 | } 542 | 543 | function log(bool p0, string memory p1, address p2) internal pure { 544 | _logImpl3Params(_boolToString(p0), p1, _addressToString(p2)); 545 | } 546 | 547 | function log(bool p0, bool p1, int256 p2) internal pure { 548 | _logImpl3Params(_boolToString(p0), _boolToString(p1), _intToString(p2)); 549 | } 550 | 551 | function log(bool p0, bool p1, uint256 p2) internal pure { 552 | _logImpl3Params(_boolToString(p0), _boolToString(p1), _uintToString(p2)); 553 | } 554 | 555 | function log(bool p0, bool p1, string memory p2) internal pure { 556 | _logImpl3Params(_boolToString(p0), _boolToString(p1), p2); 557 | } 558 | 559 | function log(bool p0, bool p1, bool p2) internal pure { 560 | _logImpl3Params(_boolToString(p0), _boolToString(p1), _boolToString(p2)); 561 | } 562 | 563 | function log(bool p0, bool p1, address p2) internal pure { 564 | _logImpl3Params(_boolToString(p0), _boolToString(p1), _addressToString(p2)); 565 | } 566 | 567 | function log(bool p0, address p1, int256 p2) internal pure { 568 | _logImpl3Params(_boolToString(p0), _addressToString(p1), _intToString(p2)); 569 | } 570 | 571 | function log(bool p0, address p1, uint256 p2) internal pure { 572 | _logImpl3Params(_boolToString(p0), _addressToString(p1), _uintToString(p2)); 573 | } 574 | 575 | function log(bool p0, address p1, string memory p2) internal pure { 576 | _logImpl3Params(_boolToString(p0), _addressToString(p1), p2); 577 | } 578 | 579 | function log(bool p0, address p1, bool p2) internal pure { 580 | _logImpl3Params(_boolToString(p0), _addressToString(p1), _boolToString(p2)); 581 | } 582 | 583 | function log(bool p0, address p1, address p2) internal pure { 584 | _logImpl3Params(_boolToString(p0), _addressToString(p1), _addressToString(p2)); 585 | } 586 | 587 | function log(address p0, int256 p1, int256 p2) internal pure { 588 | _logImpl3Params(_addressToString(p0), _intToString(p1), _intToString(p2)); 589 | } 590 | 591 | function log(address p0, int256 p1, uint256 p2) internal pure { 592 | _logImpl3Params(_addressToString(p0), _intToString(p1), _uintToString(p2)); 593 | } 594 | 595 | function log(address p0, int256 p1, string memory p2) internal pure { 596 | _logImpl3Params(_addressToString(p0), _intToString(p1), p2); 597 | } 598 | 599 | function log(address p0, int256 p1, bool p2) internal pure { 600 | _logImpl3Params(_addressToString(p0), _intToString(p1), _boolToString(p2)); 601 | } 602 | 603 | function log(address p0, int256 p1, address p2) internal pure { 604 | _logImpl3Params(_addressToString(p0), _intToString(p1), _addressToString(p2)); 605 | } 606 | 607 | function log(address p0, uint256 p1, int256 p2) internal pure { 608 | _logImpl3Params(_addressToString(p0), _uintToString(p1), _intToString(p2)); 609 | } 610 | 611 | function log(address p0, uint256 p1, uint256 p2) internal pure { 612 | _logImpl3Params(_addressToString(p0), _uintToString(p1), _uintToString(p2)); 613 | } 614 | 615 | function log(address p0, uint256 p1, string memory p2) internal pure { 616 | _logImpl3Params(_addressToString(p0), _uintToString(p1), p2); 617 | } 618 | 619 | function log(address p0, uint256 p1, bool p2) internal pure { 620 | _logImpl3Params(_addressToString(p0), _uintToString(p1), _boolToString(p2)); 621 | } 622 | 623 | function log(address p0, uint256 p1, address p2) internal pure { 624 | _logImpl3Params(_addressToString(p0), _uintToString(p1), _addressToString(p2)); 625 | } 626 | 627 | function log(address p0, string memory p1, int256 p2) internal pure { 628 | _logImpl3Params(_addressToString(p0), p1, _intToString(p2)); 629 | } 630 | 631 | function log(address p0, string memory p1, uint256 p2) internal pure { 632 | _logImpl3Params(_addressToString(p0), p1, _uintToString(p2)); 633 | } 634 | 635 | function log(address p0, string memory p1, string memory p2) internal pure { 636 | _logImpl3Params(_addressToString(p0), p1, p2); 637 | } 638 | 639 | function log(address p0, string memory p1, bool p2) internal pure { 640 | _logImpl3Params(_addressToString(p0), p1, _boolToString(p2)); 641 | } 642 | 643 | function log(address p0, string memory p1, address p2) internal pure { 644 | _logImpl3Params(_addressToString(p0), p1, _addressToString(p2)); 645 | } 646 | 647 | function log(address p0, bool p1, int256 p2) internal pure { 648 | _logImpl3Params(_addressToString(p0), _boolToString(p1), _intToString(p2)); 649 | } 650 | 651 | function log(address p0, bool p1, uint256 p2) internal pure { 652 | _logImpl3Params(_addressToString(p0), _boolToString(p1), _uintToString(p2)); 653 | } 654 | 655 | function log(address p0, bool p1, string memory p2) internal pure { 656 | _logImpl3Params(_addressToString(p0), _boolToString(p1), p2); 657 | } 658 | 659 | function log(address p0, bool p1, bool p2) internal pure { 660 | _logImpl3Params(_addressToString(p0), _boolToString(p1), _boolToString(p2)); 661 | } 662 | 663 | function log(address p0, bool p1, address p2) internal pure { 664 | _logImpl3Params(_addressToString(p0), _boolToString(p1), _addressToString(p2)); 665 | } 666 | 667 | function log(address p0, address p1, int256 p2) internal pure { 668 | _logImpl3Params(_addressToString(p0), _addressToString(p1), _intToString(p2)); 669 | } 670 | 671 | function log(address p0, address p1, uint256 p2) internal pure { 672 | _logImpl3Params(_addressToString(p0), _addressToString(p1), _uintToString(p2)); 673 | } 674 | 675 | function log(address p0, address p1, string memory p2) internal pure { 676 | _logImpl3Params(_addressToString(p0), _addressToString(p1), p2); 677 | } 678 | 679 | function log(address p0, address p1, bool p2) internal pure { 680 | _logImpl3Params(_addressToString(p0), _addressToString(p1), _boolToString(p2)); 681 | } 682 | 683 | function log(address p0, address p1, address p2) internal pure { 684 | _logImpl3Params(_addressToString(p0), _addressToString(p1), _addressToString(p2)); 685 | } 686 | 687 | } 688 | -------------------------------------------------------------------------------- /contracts/utils/debug/MockFheOps.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.9.0; 3 | 4 | library Precompiles { 5 | address public constant Fheos = address(128); 6 | } 7 | 8 | contract MockFheOps { 9 | /* 10 | FheUint8 = 0 11 | FheUint16 = 1 12 | FheUint32 = 2 13 | FheUint64 = 3 14 | FheUint128 = 4 15 | FheUint256 = 5 16 | FheInt8 = 6 17 | FheInt16 = 7 18 | FheInt32 = 8 19 | FheInt64 = 9 20 | FheInt128 = 10 21 | FheInt256 = 11 22 | FheAddress = 12 23 | FheBool = 13 24 | */ 25 | 26 | function maxValue(uint8 utype) public pure returns (uint256) { 27 | uint256 result = 0; 28 | if (utype == 0) { 29 | result = uint256(type(uint8).max) + 1; 30 | } else if (utype == 1) { 31 | result = uint256(type(uint16).max) + 1; 32 | } else if (utype == 2) { 33 | result = uint256(type(uint32).max) + 1; 34 | } else if (utype == 3) { 35 | result = uint256(type(uint64).max) + 1; 36 | } else if (utype == 4) { 37 | result = uint256(type(uint128).max) + 1; 38 | } else if (utype == 5) { 39 | result = 1; 40 | } else if (utype == 12) { 41 | result = uint256(type(uint160).max) + 1; //address 42 | } else if (utype == 13) { 43 | result = 1; //bool (we want anything non-zero to be true) 44 | } else { 45 | revert("Unsupported type"); 46 | } 47 | 48 | return result; 49 | } 50 | 51 | function bytes32ToBytes( 52 | bytes32 input, 53 | uint8 54 | ) internal pure returns (bytes memory) { 55 | return bytes.concat(input); 56 | } 57 | 58 | //For converting back to bytes 59 | function uint256ToBytes(uint256 value) public pure returns (bytes memory) { 60 | bytes memory result = new bytes(32); 61 | 62 | assembly { 63 | mstore(add(result, 32), value) 64 | } 65 | 66 | return result; 67 | } 68 | 69 | function boolToBytes(bool value) public pure returns (bytes memory) { 70 | bytes memory result = new bytes(1); 71 | 72 | if (value) { 73 | result[0] = 0x01; 74 | } else { 75 | result[0] = 0x00; 76 | } 77 | 78 | return result; 79 | } 80 | 81 | //for unknown size of bytes - we could instead just encode it as bytes32 because it's always uint256 but for now lets keep it like this 82 | function bytesToUint( 83 | bytes memory b 84 | ) internal pure virtual returns (uint256) { 85 | require(b.length <= 32, "Bytes length exceeds 32."); 86 | return 87 | abi.decode( 88 | abi.encodePacked(new bytes(32 - b.length), b), 89 | (uint256) 90 | ); 91 | } 92 | 93 | function bytesToBool(bytes memory b) internal pure virtual returns (bool) { 94 | require(b.length <= 32, "Bytes length exceeds 32."); 95 | uint8 value = uint8(b[0]); 96 | return value != 0; 97 | } 98 | 99 | function trivialEncrypt( 100 | bytes memory input, 101 | uint8 toType, 102 | int32 103 | ) external pure returns (bytes memory) { 104 | bytes32 result = bytes32(input); 105 | return bytes32ToBytes(result, toType); 106 | } 107 | 108 | function add( 109 | uint8 utype, 110 | bytes memory lhsHash, 111 | bytes memory rhsHash 112 | ) external pure returns (bytes memory) { 113 | uint256 result = (bytesToUint(lhsHash) + bytesToUint(rhsHash)) % 114 | maxValue(utype); 115 | return uint256ToBytes(result); 116 | } 117 | 118 | function sealOutput( 119 | uint8, 120 | bytes memory ctHash, 121 | bytes memory 122 | ) external pure returns (string memory) { 123 | return string(ctHash); 124 | } 125 | 126 | function verify( 127 | uint8, 128 | bytes memory input, 129 | int32 130 | ) external pure returns (bytes memory) { 131 | return input; 132 | } 133 | 134 | function cast( 135 | uint8, 136 | bytes memory input, 137 | uint8 toType 138 | ) external pure returns (bytes memory) { 139 | bytes32 result = bytes32(input); 140 | return bytes32ToBytes(result, toType); 141 | } 142 | 143 | function log(string memory s) external pure {} 144 | 145 | function decrypt( 146 | uint8, 147 | bytes memory input, 148 | uint256 149 | ) external pure returns (uint256) { 150 | uint256 result = bytesToUint(input); 151 | return result; 152 | } 153 | 154 | function lte( 155 | uint8 utype, 156 | bytes memory lhsHash, 157 | bytes memory rhsHash 158 | ) external pure returns (bytes memory) { 159 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) <= 160 | (bytesToUint(rhsHash) % maxValue(utype)); 161 | return boolToBytes(result); 162 | } 163 | 164 | function sub( 165 | uint8 utype, 166 | bytes memory lhsHash, 167 | bytes memory rhsHash 168 | ) external pure returns (bytes memory) { 169 | uint256 result = (bytesToUint(lhsHash) - bytesToUint(rhsHash)) % 170 | maxValue(utype); 171 | return uint256ToBytes(result); 172 | } 173 | 174 | function mul( 175 | uint8 utype, 176 | bytes memory lhsHash, 177 | bytes memory rhsHash 178 | ) external pure returns (bytes memory) { 179 | uint256 result = (bytesToUint(lhsHash) * bytesToUint(rhsHash)) % 180 | maxValue(utype); 181 | return uint256ToBytes(result); 182 | } 183 | 184 | function lt( 185 | uint8 utype, 186 | bytes memory lhsHash, 187 | bytes memory rhsHash 188 | ) external pure returns (bytes memory) { 189 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) < 190 | (bytesToUint(rhsHash) % maxValue(utype)); 191 | return boolToBytes(result); 192 | } 193 | 194 | function select( 195 | uint8, 196 | bytes memory controlHash, 197 | bytes memory ifTrueHash, 198 | bytes memory ifFalseHash 199 | ) external pure returns (bytes memory) { 200 | bool control = bytesToBool(controlHash); 201 | if (control) return ifTrueHash; 202 | return ifFalseHash; 203 | } 204 | 205 | function req( 206 | uint8, 207 | bytes memory input 208 | ) external pure returns (bytes memory) { 209 | bool condition = (bytesToUint(input) != 0); 210 | require(condition); 211 | return input; 212 | } 213 | 214 | function div( 215 | uint8 utype, 216 | bytes memory lhsHash, 217 | bytes memory rhsHash 218 | ) external pure returns (bytes memory) { 219 | uint256 result = (bytesToUint(lhsHash) / bytesToUint(rhsHash)) % 220 | maxValue(utype); 221 | return uint256ToBytes(result); 222 | } 223 | 224 | function gt( 225 | uint8 utype, 226 | bytes memory lhsHash, 227 | bytes memory rhsHash 228 | ) external pure returns (bytes memory) { 229 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) > 230 | (bytesToUint(rhsHash) % maxValue(utype)); 231 | return boolToBytes(result); 232 | } 233 | 234 | function gte( 235 | uint8 utype, 236 | bytes memory lhsHash, 237 | bytes memory rhsHash 238 | ) external pure returns (bytes memory) { 239 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) >= 240 | (bytesToUint(rhsHash) % maxValue(utype)); 241 | return boolToBytes(result); 242 | } 243 | 244 | function rem( 245 | uint8 utype, 246 | bytes memory lhsHash, 247 | bytes memory rhsHash 248 | ) external pure returns (bytes memory) { 249 | uint256 result = (bytesToUint(lhsHash) % bytesToUint(rhsHash)) % 250 | maxValue(utype); 251 | return uint256ToBytes(result); 252 | } 253 | 254 | function and( 255 | uint8 utype, 256 | bytes memory lhsHash, 257 | bytes memory rhsHash 258 | ) external pure returns (bytes memory) { 259 | bytes32 result = bytes32(lhsHash) & bytes32(rhsHash); 260 | return bytes32ToBytes(result, utype); 261 | } 262 | 263 | function or( 264 | uint8 utype, 265 | bytes memory lhsHash, 266 | bytes memory rhsHash 267 | ) external pure returns (bytes memory) { 268 | bytes32 result = bytes32(lhsHash) | bytes32(rhsHash); 269 | return bytes32ToBytes(result, utype); 270 | } 271 | 272 | function xor( 273 | uint8 utype, 274 | bytes memory lhsHash, 275 | bytes memory rhsHash 276 | ) external pure returns (bytes memory) { 277 | bytes32 result = bytes32(lhsHash) ^ bytes32(rhsHash); 278 | return bytes32ToBytes(result, utype); 279 | } 280 | 281 | function eq( 282 | uint8 utype, 283 | bytes memory lhsHash, 284 | bytes memory rhsHash 285 | ) external pure returns (bytes memory) { 286 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) == 287 | (bytesToUint(rhsHash) % maxValue(utype)); 288 | return boolToBytes(result); 289 | } 290 | 291 | function ne( 292 | uint8 utype, 293 | bytes memory lhsHash, 294 | bytes memory rhsHash 295 | ) external pure returns (bytes memory) { 296 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) != 297 | (bytesToUint(rhsHash) % maxValue(utype)); 298 | return boolToBytes(result); 299 | } 300 | 301 | function min( 302 | uint8 utype, 303 | bytes memory lhsHash, 304 | bytes memory rhsHash 305 | ) external pure returns (bytes memory) { 306 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) >= 307 | (bytesToUint(rhsHash) % maxValue(utype)); 308 | if (result == true) { 309 | return rhsHash; 310 | } 311 | return lhsHash; 312 | } 313 | 314 | function max( 315 | uint8 utype, 316 | bytes memory lhsHash, 317 | bytes memory rhsHash 318 | ) external pure returns (bytes memory) { 319 | bool result = (bytesToUint(lhsHash) % maxValue(utype)) >= 320 | (bytesToUint(rhsHash) % maxValue(utype)); 321 | if (result == true) { 322 | return lhsHash; 323 | } 324 | return rhsHash; 325 | } 326 | 327 | function shl( 328 | uint8, 329 | bytes memory lhsHash, 330 | bytes memory rhsHash 331 | ) external pure returns (bytes memory) { 332 | uint256 lhs = bytesToUint(lhsHash); 333 | uint256 rhs = bytesToUint(rhsHash); 334 | 335 | uint256 result = lhs << rhs; 336 | 337 | return uint256ToBytes(result); 338 | } 339 | 340 | function shr( 341 | uint8, 342 | bytes memory lhsHash, 343 | bytes memory rhsHash 344 | ) external pure returns (bytes memory) { 345 | uint256 lhs = bytesToUint(lhsHash); 346 | uint256 rhs = bytesToUint(rhsHash); 347 | 348 | uint256 result = lhs >> rhs; 349 | 350 | return uint256ToBytes(result); 351 | } 352 | 353 | function not( 354 | uint8 utype, 355 | bytes memory value 356 | ) external pure returns (bytes memory) { 357 | bytes32 result = ~bytes32(value); 358 | return bytes32ToBytes(result, utype); 359 | } 360 | 361 | function getNetworkPublicKey(int32) external pure returns (bytes memory) { 362 | string 363 | memory x = "((-(-_(-_-)_-)-)) You've stepped into the wrong neighborhood pal."; 364 | return bytes(x); 365 | } 366 | 367 | function random( 368 | uint8 utype, 369 | uint64, 370 | int32 371 | ) external view returns (bytes memory) { 372 | return 373 | uint256ToBytes( 374 | uint(keccak256(abi.encode(block.timestamp))) % maxValue(utype) 375 | ); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /docs/.gitplaceholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FhenixProtocol/fhenix-contracts/6dc59ffd338ffa4357dde98a107d9604ff10261d/docs/.gitplaceholder -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | const docgen = require('solidity-docgen') 2 | 3 | import '@nomicfoundation/hardhat-toolbox' 4 | 5 | require('@nomiclabs/hardhat-truffle5') 6 | require('hardhat-exposed') 7 | import '@nomicfoundation/hardhat-ethers' 8 | import 'fhenix-hardhat-plugin' 9 | 10 | const config = { 11 | defaultNetwork: 'localfhenix', 12 | networks: { 13 | localfhenix: { 14 | url: 'http://localhost:42069', 15 | // chainId: TESTNET_CHAIN_ID, 16 | // gas: "auto", 17 | // gasMultiplier: 1.2, 18 | // gasPrice: "auto", 19 | // httpHeaders: {}, 20 | timeout: 10_000, 21 | accounts: { 22 | mnemonic: 'demand hotel mass rally sphere tiger measure sick spoon evoke fashion comfort', 23 | path: "m/44'/60'/0'/0", 24 | count: 10, 25 | // passphrase: "", 26 | }, 27 | }, 28 | }, 29 | paths: { 30 | artifacts: './artifacts', 31 | cache: './cache', 32 | sources: './contracts', 33 | }, 34 | solidity: { 35 | version: '0.8.20', 36 | }, 37 | docgen: { 38 | pages: 'items', 39 | }, 40 | exposed: { 41 | imports: true, 42 | initializers: true, 43 | exclude: ['vendor/**/*'], 44 | }, 45 | typechain: { 46 | outDir: 'types', 47 | target: 'ethers-v6', 48 | }, 49 | } 50 | 51 | module.exports = config 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fhenixprotocol/fhe", 3 | "description": "", 4 | "version": "0.0.1", 5 | "author": { 6 | "name": "Fhenix", 7 | "url": "https://github.com/fhenixprotocol/fhenix-contracts" 8 | }, 9 | "scripts": { 10 | "docgen": "hardhat docgen", 11 | "compile": "hardhat compile", 12 | "test": "hardhat test" 13 | }, 14 | "files": [ 15 | "contracts", 16 | "cofhe-contracts" 17 | ], 18 | "keywords": [ 19 | "blockchain", 20 | "ethereum", 21 | "smart-contracts", 22 | "solidity" 23 | ], 24 | "publishConfig": { 25 | "access": "public" 26 | }, 27 | "devDependencies": { 28 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 29 | "@nomicfoundation/hardhat-network-helpers": "^1.0.10", 30 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 31 | "@nomiclabs/hardhat-truffle5": "^2.0.5", 32 | "@openzeppelin/contracts": "^5.0.2", 33 | "@openzeppelin/test-helpers": "^0.5.13", 34 | "@typechain/ethers-v6": "^0.5.1", 35 | "@typechain/hardhat": "^9.1.0", 36 | "@types/chai": "^4.3.11", 37 | "@types/mocha": "^10.0.6", 38 | "chai": "^4.3.7", 39 | "ethers": "^6.11.1", 40 | "fhenix-hardhat-docker": "0.3.0-alpha.2", 41 | "fhenix-hardhat-plugin": "0.3.0-alpha.2", 42 | "fhenixjs": "0.4.0-alpha.6", 43 | "hardhat-exposed": "^0.3.13", 44 | "hardhat": "2.19.3", 45 | "mocha": "^10.3.0", 46 | "solidity-docgen": "0.6.0-beta.36", 47 | "ts-node": "^10.9.2", 48 | "typechain": "^8.3.2", 49 | "typescript": "^5.5.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/genConsole.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "fs"; 2 | 3 | // Define the parameter types to iterate over 4 | const types = ["int256", "uint256", "string memory", "bool", "address"]; 5 | 6 | // Function to return the conversion function based on the type 7 | const toStringFunc = (type: string): string => { 8 | switch (type) { 9 | case "int256": 10 | return "_intToString("; 11 | case "uint256": 12 | return "_uintToString("; 13 | case "string memory": 14 | return ""; 15 | case "bool": 16 | return "_boolToString("; 17 | case "address": 18 | return "_addressToString("; 19 | default: 20 | throw new Error("Unknown type"); 21 | } 22 | }; 23 | 24 | const toStringFuncEnding = (type: string): string => { 25 | switch (type) { 26 | case "int256": 27 | return ")"; 28 | case "uint256": 29 | return ")"; 30 | case "string memory": 31 | return ""; 32 | case "bool": 33 | return ")"; 34 | case "address": 35 | return ")"; 36 | default: 37 | throw new Error("Unknown type"); 38 | } 39 | }; 40 | 41 | // Function to generate log functions for a given number of parameters (n) 42 | const generateLogFunctions = (n: number): string => { 43 | let output = ""; 44 | 45 | // Generate all combinations of parameter types for n parameters 46 | const combinations = generateCombinations(types, n); 47 | combinations.forEach((combo) => { 48 | const params = combo.map((type, i) => `${type} p${i}`).join(", "); 49 | const conversion = combo 50 | .map((type, i) => `${toStringFunc(type)}p${i}${toStringFuncEnding(type)}`) 51 | .join(", "); 52 | const methodName = `log(${params})`; 53 | 54 | // Generating external pure function strings 55 | output += 56 | `\tfunction ${methodName} external pure {\n` + 57 | ` \t_logImpl${n}Params(${conversion});\n` + 58 | `\t}\n\n`; 59 | }); 60 | 61 | return output; 62 | }; 63 | 64 | // Helper function to generate combinations recursively 65 | function generateCombinations(types: string[], n: number): string[][] { 66 | if (n === 1) return types.map((type) => [type]); 67 | 68 | const combos: string[][] = []; 69 | const smallerCombos = generateCombinations(types, n - 1); 70 | 71 | types.forEach((type) => { 72 | smallerCombos.forEach((combo) => { 73 | combos.push([type].concat(combo)); 74 | }); 75 | }); 76 | 77 | return combos; 78 | } 79 | 80 | let output = `// SPDX-License-Identifier: MIT 81 | pragma solidity >=0.8.19 <0.9.0; 82 | 83 | import {FheOps, Precompiles} from "./FheOS.sol"; 84 | import "@openzeppelin/contracts/utils/Strings.sol"; 85 | 86 | library Console { 87 | function _logImpl(string memory payload) internal pure { 88 | FheOps(Precompiles.Fheos).log(payload); 89 | } 90 | 91 | function _logImpl2Params(string memory p0, string memory p1) internal pure { 92 | string memory payload = string(abi.encodePacked(string("p0: "), p0, string(" p1: "), p1)); 93 | _logImpl(payload); 94 | } 95 | 96 | function _logImpl3Params(string memory p0, string memory p1, string memory p2) internal pure { 97 | string memory payload = string(abi.encodePacked(string("p0: "), p0, string(" p1: "), 98 | p1, string(" p2: "), p2)); 99 | _logImpl(payload); 100 | } 101 | 102 | function _intToString(int _value) internal pure returns (string memory) { 103 | return Strings.toStringSigned(_value); 104 | } 105 | 106 | function _uintToString(uint _value) internal pure returns (string memory) { 107 | return Strings.toString(_value); 108 | } 109 | 110 | function _addressToString(address _addr) internal pure returns (string memory) { 111 | bytes memory alphabet = "0123456789abcdef"; 112 | bytes20 value = bytes20(_addr); 113 | bytes memory buffer = new bytes(42); // 2 characters for '0x' and 40 characters for the address 114 | buffer[0] = "0"; 115 | buffer[1] = "x"; 116 | for (uint256 i = 0; i < 20; i++) { 117 | buffer[2+i*2] = alphabet[uint8(value[i] >> 4)]; 118 | buffer[3+i*2] = alphabet[uint8(value[i] & 0x0f)]; 119 | } 120 | return string(buffer); 121 | } 122 | 123 | function _boolToString(bool val) internal pure returns (string memory) { 124 | return val ? "true" : "false"; 125 | } 126 | 127 | function _logInt(int256 p0) internal pure { 128 | _logImpl(_intToString(p0)); 129 | } 130 | 131 | function _logUint(uint256 p0) internal pure { 132 | _logImpl(_uintToString(p0)); 133 | } 134 | 135 | function _logBool(bool p0) internal pure { 136 | _logImpl(_boolToString(p0)); 137 | } 138 | 139 | function _logAddress(address p0) internal pure { 140 | _logImpl(_addressToString(p0)); 141 | } 142 | 143 | function logBytes(bytes memory p0) external pure { 144 | _logImpl(string(p0)); 145 | } 146 | 147 | function log(int256 p0) external pure { 148 | _logInt(p0); 149 | } 150 | 151 | function log(uint256 p0) external pure { 152 | _logUint(p0); 153 | } 154 | 155 | function log(string memory p0) external pure { 156 | _logImpl(p0); 157 | } 158 | 159 | function log(bool p0) external pure { 160 | _logBool(p0); 161 | } 162 | 163 | function log(address p0) external pure { 164 | _logAddress(p0); 165 | } 166 | `; 167 | // Generate and print the log functions for 2 to 4 parameters 168 | for (let i = 2; i <= 3; i++) { 169 | output += generateLogFunctions(i); 170 | } 171 | output += "}"; 172 | 173 | writeFileSync("./contracts/Console.sol", output); 174 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC20/ERC20.behavior.ts: -------------------------------------------------------------------------------- 1 | const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); 2 | const { expect } = require('chai'); 3 | const { ZERO_ADDRESS, MAX_UINT256 } = constants; 4 | 5 | const { expectRevertCustomError } = require('../../../helpers/customError'); 6 | 7 | function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { 8 | const [initialHolder, recipient, anotherAccount] = accounts; 9 | const { forcedApproval } = opts; 10 | 11 | describe('total supply', function () { 12 | it('returns the total token value', async function () { 13 | expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); 14 | }); 15 | }); 16 | 17 | describe('balanceOf', function () { 18 | describe('when the requested account has no tokens', function () { 19 | it('returns zero', async function () { 20 | expect(await this.token.balanceOf(anotherAccount)).to.be.bignumber.equal('0'); 21 | }); 22 | }); 23 | 24 | describe('when the requested account has some tokens', function () { 25 | it('returns the total token value', async function () { 26 | expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(initialSupply); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('transfer', function () { 32 | shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { 33 | return this.token.transfer(to, value, { from }); 34 | }); 35 | }); 36 | 37 | describe('transfer from', function () { 38 | const spender = recipient; 39 | 40 | describe('when the token owner is not the zero address', function () { 41 | const tokenOwner = initialHolder; 42 | 43 | describe('when the recipient is not the zero address', function () { 44 | const to = anotherAccount; 45 | 46 | describe('when the spender has enough allowance', function () { 47 | beforeEach(async function () { 48 | await this.token.approve(spender, initialSupply, { from: initialHolder }); 49 | }); 50 | 51 | describe('when the token owner has enough balance', function () { 52 | const value = initialSupply; 53 | 54 | it('transfers the requested value', async function () { 55 | await this.token.transferFrom(tokenOwner, to, value, { from: spender }); 56 | 57 | expect(await this.token.balanceOf(tokenOwner)).to.be.bignumber.equal('0'); 58 | 59 | expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value); 60 | }); 61 | 62 | it('decreases the spender allowance', async function () { 63 | await this.token.transferFrom(tokenOwner, to, value, { from: spender }); 64 | 65 | expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal('0'); 66 | }); 67 | 68 | it('emits a transfer event', async function () { 69 | expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Transfer', { 70 | from: tokenOwner, 71 | to: to, 72 | value: value, 73 | }); 74 | }); 75 | 76 | if (forcedApproval) { 77 | it('emits an approval event', async function () { 78 | expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Approval', { 79 | owner: tokenOwner, 80 | spender: spender, 81 | value: await this.token.allowance(tokenOwner, spender), 82 | }); 83 | }); 84 | } else { 85 | it('does not emit an approval event', async function () { 86 | expectEvent.notEmitted( 87 | await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 88 | 'Approval', 89 | ); 90 | }); 91 | } 92 | }); 93 | 94 | describe('when the token owner does not have enough balance', function () { 95 | const value = initialSupply; 96 | 97 | beforeEach('reducing balance', async function () { 98 | await this.token.transfer(to, 1, { from: tokenOwner }); 99 | }); 100 | 101 | it('reverts', async function () { 102 | await expectRevertCustomError( 103 | this.token.transferFrom(tokenOwner, to, value, { from: spender }), 104 | 'ERC20InsufficientBalance', 105 | [tokenOwner, value - 1, value], 106 | ); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('when the spender does not have enough allowance', function () { 112 | const allowance = initialSupply.subn(1); 113 | 114 | beforeEach(async function () { 115 | await this.token.approve(spender, allowance, { from: tokenOwner }); 116 | }); 117 | 118 | describe('when the token owner has enough balance', function () { 119 | const value = initialSupply; 120 | 121 | it('reverts', async function () { 122 | await expectRevertCustomError( 123 | this.token.transferFrom(tokenOwner, to, value, { from: spender }), 124 | 'ERC20InsufficientAllowance', 125 | [spender, allowance, value], 126 | ); 127 | }); 128 | }); 129 | 130 | describe('when the token owner does not have enough balance', function () { 131 | const value = allowance; 132 | 133 | beforeEach('reducing balance', async function () { 134 | await this.token.transfer(to, 2, { from: tokenOwner }); 135 | }); 136 | 137 | it('reverts', async function () { 138 | await expectRevertCustomError( 139 | this.token.transferFrom(tokenOwner, to, value, { from: spender }), 140 | 'ERC20InsufficientBalance', 141 | [tokenOwner, value - 1, value], 142 | ); 143 | }); 144 | }); 145 | }); 146 | 147 | describe('when the spender has unlimited allowance', function () { 148 | beforeEach(async function () { 149 | await this.token.approve(spender, MAX_UINT256, { from: initialHolder }); 150 | }); 151 | 152 | it('does not decrease the spender allowance', async function () { 153 | await this.token.transferFrom(tokenOwner, to, 1, { from: spender }); 154 | 155 | expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal(MAX_UINT256); 156 | }); 157 | 158 | it('does not emit an approval event', async function () { 159 | expectEvent.notEmitted(await this.token.transferFrom(tokenOwner, to, 1, { from: spender }), 'Approval'); 160 | }); 161 | }); 162 | }); 163 | 164 | describe('when the recipient is the zero address', function () { 165 | const value = initialSupply; 166 | const to = ZERO_ADDRESS; 167 | 168 | beforeEach(async function () { 169 | await this.token.approve(spender, value, { from: tokenOwner }); 170 | }); 171 | 172 | it('reverts', async function () { 173 | await expectRevertCustomError( 174 | this.token.transferFrom(tokenOwner, to, value, { from: spender }), 175 | 'ERC20InvalidReceiver', 176 | [ZERO_ADDRESS], 177 | ); 178 | }); 179 | }); 180 | }); 181 | 182 | describe('when the token owner is the zero address', function () { 183 | const value = 0; 184 | const tokenOwner = ZERO_ADDRESS; 185 | const to = recipient; 186 | 187 | it('reverts', async function () { 188 | await expectRevertCustomError( 189 | this.token.transferFrom(tokenOwner, to, value, { from: spender }), 190 | 'ERC20InvalidApprover', 191 | [ZERO_ADDRESS], 192 | ); 193 | }); 194 | }); 195 | }); 196 | 197 | describe('approve', function () { 198 | shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) { 199 | return this.token.approve(spender, value, { from: owner }); 200 | }); 201 | }); 202 | } 203 | 204 | function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) { 205 | describe('when the recipient is not the zero address', function () { 206 | describe('when the sender does not have enough balance', function () { 207 | const value = balance.addn(1); 208 | 209 | it('reverts', async function () { 210 | await expectRevertCustomError(transfer.call(this, from, to, value), 'ERC20InsufficientBalance', [ 211 | from, 212 | balance, 213 | value, 214 | ]); 215 | }); 216 | }); 217 | 218 | describe('when the sender transfers all balance', function () { 219 | const value = balance; 220 | 221 | it('transfers the requested value', async function () { 222 | await transfer.call(this, from, to, value); 223 | 224 | expect(await this.token.balanceOf(from)).to.be.bignumber.equal('0'); 225 | 226 | expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value); 227 | }); 228 | 229 | it('emits a transfer event', async function () { 230 | expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); 231 | }); 232 | }); 233 | 234 | describe('when the sender transfers zero tokens', function () { 235 | const value = new BN('0'); 236 | 237 | it('transfers the requested value', async function () { 238 | await transfer.call(this, from, to, value); 239 | 240 | expect(await this.token.balanceOf(from)).to.be.bignumber.equal(balance); 241 | 242 | expect(await this.token.balanceOf(to)).to.be.bignumber.equal('0'); 243 | }); 244 | 245 | it('emits a transfer event', async function () { 246 | expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); 247 | }); 248 | }); 249 | }); 250 | 251 | describe('when the recipient is the zero address', function () { 252 | it('reverts', async function () { 253 | await expectRevertCustomError(transfer.call(this, from, ZERO_ADDRESS, balance), 'ERC20InvalidReceiver', [ 254 | ZERO_ADDRESS, 255 | ]); 256 | }); 257 | }); 258 | } 259 | 260 | function shouldBehaveLikeERC20Approve(owner, spender, supply, approve) { 261 | describe('when the spender is not the zero address', function () { 262 | describe('when the sender has enough balance', function () { 263 | const value = supply; 264 | 265 | it('emits an approval event', async function () { 266 | expectEvent(await approve.call(this, owner, spender, value), 'Approval', { 267 | owner: owner, 268 | spender: spender, 269 | value: value, 270 | }); 271 | }); 272 | 273 | describe('when there was no approved value before', function () { 274 | it('approves the requested value', async function () { 275 | await approve.call(this, owner, spender, value); 276 | 277 | expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); 278 | }); 279 | }); 280 | 281 | describe('when the spender had an approved value', function () { 282 | beforeEach(async function () { 283 | await approve.call(this, owner, spender, new BN(1)); 284 | }); 285 | 286 | it('approves the requested value and replaces the previous one', async function () { 287 | await approve.call(this, owner, spender, value); 288 | 289 | expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); 290 | }); 291 | }); 292 | }); 293 | 294 | describe('when the sender does not have enough balance', function () { 295 | const value = supply.addn(1); 296 | 297 | it('emits an approval event', async function () { 298 | expectEvent(await approve.call(this, owner, spender, value), 'Approval', { 299 | owner: owner, 300 | spender: spender, 301 | value: value, 302 | }); 303 | }); 304 | 305 | describe('when there was no approved value before', function () { 306 | it('approves the requested value', async function () { 307 | await approve.call(this, owner, spender, value); 308 | 309 | expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); 310 | }); 311 | }); 312 | 313 | describe('when the spender had an approved value', function () { 314 | beforeEach(async function () { 315 | await approve.call(this, owner, spender, new BN(1)); 316 | }); 317 | 318 | it('approves the requested value and replaces the previous one', async function () { 319 | await approve.call(this, owner, spender, value); 320 | 321 | expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); 322 | }); 323 | }); 324 | }); 325 | }); 326 | 327 | describe('when the spender is the zero address', function () { 328 | it('reverts', async function () { 329 | await expectRevertCustomError(approve.call(this, owner, ZERO_ADDRESS, supply), `ERC20InvalidSpender`, [ 330 | ZERO_ADDRESS, 331 | ]); 332 | }); 333 | }); 334 | } 335 | 336 | module.exports = { 337 | shouldBehaveLikeERC20, 338 | shouldBehaveLikeERC20Transfer, 339 | shouldBehaveLikeERC20Approve, 340 | }; 341 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC20/ERC20.test.ts: -------------------------------------------------------------------------------- 1 | const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); 2 | const { expect } = require('chai'); 3 | 4 | import hre from "hardhat"; 5 | const { ZERO_ADDRESS } = constants; 6 | 7 | const { 8 | shouldBehaveLikeERC20, 9 | shouldBehaveLikeERC20Transfer, 10 | shouldBehaveLikeERC20Approve, 11 | } = require('./ERC20.behavior'); 12 | const { expectRevertCustomError } = require('../../../helpers/customError'); 13 | 14 | const TOKENS = [ 15 | { Token: artifacts.require('$FHERC20') }, 16 | ]; 17 | 18 | contract('FHERC20 as ERC20', function (accounts) { 19 | const [initialHolder, recipient] = accounts; 20 | 21 | const name = 'My Token'; 22 | const symbol = 'MTKN'; 23 | const initialSupply = new BN(100); 24 | 25 | for (const { Token, forcedApproval } of TOKENS) { 26 | describe(`using ${Token._json.contractName}`, function () { 27 | 28 | beforeEach(async function () { 29 | // get sufficient funds 30 | const { fhenixjs, ethers, deployments } = hre; 31 | const [signer, spender] = await ethers.getSigners(); 32 | 33 | // fund first account: owner 34 | if ((await ethers.provider.getBalance(signer.address)).toString() === "0") { 35 | await fhenixjs.getFunds(signer.address); 36 | } 37 | 38 | // fund second account as it is sometimes an allowed spender 39 | if ((await ethers.provider.getBalance(spender.address)).toString() === "0") { 40 | await fhenixjs.getFunds(spender.address); 41 | } 42 | 43 | this.token = await Token.new(name, symbol); 44 | await this.token.$_mint(initialHolder, initialSupply); 45 | }); 46 | 47 | shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval }); 48 | 49 | it('has a name', async function () { 50 | expect(await this.token.name()).to.equal(name); 51 | }); 52 | 53 | it('has a symbol', async function () { 54 | expect(await this.token.symbol()).to.equal(symbol); 55 | }); 56 | 57 | it('has 18 decimals', async function () { 58 | expect(await this.token.decimals()).to.be.bignumber.equal('18'); 59 | }); 60 | 61 | describe('_mint', function () { 62 | const value = new BN(50); 63 | it('rejects a null account', async function () { 64 | await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]); 65 | }); 66 | 67 | it('rejects overflow', async function () { 68 | const maxUint256 = new BN('2').pow(new BN(256)).subn(1); 69 | await expectRevert( 70 | this.token.$_mint(recipient, maxUint256), 71 | 'Returned error: execution reverted: arithmetic underflow or overflow', 72 | ); 73 | }); 74 | 75 | describe('for a non zero account', function () { 76 | beforeEach('minting', async function () { 77 | this.receipt = await this.token.$_mint(recipient, value); 78 | }); 79 | 80 | it('increments totalSupply', async function () { 81 | const expectedSupply = initialSupply.add(value); 82 | expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); 83 | }); 84 | 85 | it('increments recipient balance', async function () { 86 | expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); 87 | }); 88 | 89 | it('emits Transfer event', async function () { 90 | const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient }); 91 | 92 | expect(event.args.value).to.be.bignumber.equal(value); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('_burn', function () { 98 | it('rejects a null account', async function () { 99 | await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [ 100 | ZERO_ADDRESS, 101 | ]); 102 | }); 103 | 104 | describe('for a non zero account', function () { 105 | it('rejects burning more than balance', async function () { 106 | await expectRevertCustomError( 107 | this.token.$_burn(initialHolder, initialSupply.addn(1)), 108 | 'ERC20InsufficientBalance', 109 | [initialHolder, initialSupply, initialSupply.addn(1)], 110 | ); 111 | }); 112 | 113 | const describeBurn = function (description, value) { 114 | describe(description, function () { 115 | beforeEach('burning', async function () { 116 | this.receipt = await this.token.$_burn(initialHolder, value); 117 | }); 118 | 119 | it('decrements totalSupply', async function () { 120 | const expectedSupply = initialSupply.sub(value); 121 | expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); 122 | }); 123 | 124 | it('decrements initialHolder balance', async function () { 125 | const expectedBalance = initialSupply.sub(value); 126 | expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance); 127 | }); 128 | 129 | it('emits Transfer event', async function () { 130 | const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS }); 131 | 132 | expect(event.args.value).to.be.bignumber.equal(value); 133 | }); 134 | }); 135 | }; 136 | 137 | describeBurn('for entire balance', initialSupply); 138 | describeBurn('for less value than balance', initialSupply.subn(1)); 139 | }); 140 | }); 141 | 142 | describe('_update', function () { 143 | const value = new BN(1); 144 | 145 | it('from is the zero address', async function () { 146 | const balanceBefore = await this.token.balanceOf(initialHolder); 147 | const totalSupply = await this.token.totalSupply(); 148 | 149 | expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, value), 'Transfer', { 150 | from: ZERO_ADDRESS, 151 | to: initialHolder, 152 | value: value, 153 | }); 154 | expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(value)); 155 | expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(value)); 156 | }); 157 | 158 | it('to is the zero address', async function () { 159 | const balanceBefore = await this.token.balanceOf(initialHolder); 160 | const totalSupply = await this.token.totalSupply(); 161 | 162 | expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, value), 'Transfer', { 163 | from: initialHolder, 164 | to: ZERO_ADDRESS, 165 | value: value, 166 | }); 167 | expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(value)); 168 | expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(value)); 169 | }); 170 | 171 | it('from and to are the zero address', async function () { 172 | const totalSupply = await this.token.totalSupply(); 173 | 174 | await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value); 175 | 176 | expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); 177 | expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value), 'Transfer', { 178 | from: ZERO_ADDRESS, 179 | to: ZERO_ADDRESS, 180 | value: value, 181 | }); 182 | }); 183 | }); 184 | 185 | describe('_transfer', function () { 186 | shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { 187 | return this.token.$_transfer(from, to, value); 188 | }); 189 | 190 | describe('when the sender is the zero address', function () { 191 | it('reverts', async function () { 192 | await expectRevertCustomError( 193 | this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply), 194 | 'ERC20InvalidSender', 195 | [ZERO_ADDRESS], 196 | ); 197 | }); 198 | }); 199 | }); 200 | 201 | describe('_approve', function () { 202 | shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) { 203 | return this.token.$_approve(owner, spender, value); 204 | }); 205 | 206 | describe('when the owner is the zero address', function () { 207 | it('reverts', async function () { 208 | await expectRevertCustomError( 209 | this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply), 210 | 'ERC20InvalidApprover', 211 | [ZERO_ADDRESS], 212 | ); 213 | }); 214 | }); 215 | }); 216 | }); 217 | } 218 | }); 219 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC20/FHERC20.behavior.ts: -------------------------------------------------------------------------------- 1 | import {defineGetterMemoized} from "solidity-docgen/dist/utils/memoized-getter"; 2 | 3 | const { constants, expectEvent } = require('@openzeppelin/test-helpers'); 4 | const { expect } = require('chai'); 5 | const { ZERO_ADDRESS, MAX_UINT256 } = constants; 6 | 7 | const { expectRevertCustomError } = require('../../../helpers/customError'); 8 | 9 | function shouldBehaveLikeFHERC20(initialSupply, accounts, opts = {}) { 10 | const [initialHolder, recipient, anotherAccount] = accounts; 11 | const { forcedApproval } = opts; 12 | 13 | describe('balanceOfEncrypted', function () { 14 | describe('when the requested account has no tokens', function () { 15 | it('returns zero', async function () { 16 | const balanceEnc = await this.token.balanceOfEncrypted(anotherAccount, await this.getPermission(anotherAccount)) 17 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 18 | expect(balance).to.equal(0n); 19 | }); 20 | }); 21 | 22 | describe('when the requested account has some tokens', function () { 23 | it('returns the total token value', async function () { 24 | const balanceEnc = await this.token.balanceOfEncrypted(initialHolder, await this.getPermission(initialHolder)) 25 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 26 | expect(balance).to.equal(initialSupply); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('transferEncrypted', function () { 32 | shouldBehaveLikeFHERC20Transfer(initialHolder, recipient, initialSupply, async function (from, to, value) { 33 | const encryptedValue = await fhenixjs.encrypt_uint128(value); 34 | return this.token.transferEncrypted(to, encryptedValue, { from }); 35 | }); 36 | }); 37 | 38 | describe('transfer from encrypted', function () { 39 | const spender = recipient; 40 | 41 | describe('when the token owner is not the zero address', function () { 42 | const tokenOwner = initialHolder; 43 | 44 | describe('when the recipient is not the zero address', function () { 45 | const to = anotherAccount; 46 | 47 | describe('when the spender has enough allowance', function () { 48 | beforeEach(async function () { 49 | const initialSupplyEnc = await fhenixjs.encrypt_uint128(initialSupply); 50 | await this.token.approveEncrypted(spender, initialSupplyEnc, { from: initialHolder }); 51 | }); 52 | 53 | describe('when the token owner has enough balance', function () { 54 | const value = initialSupply; 55 | 56 | it('transfers the requested value', async function () { 57 | const valueEnc = await fhenixjs.encrypt_uint128(value); 58 | await this.token.transferFromEncrypted(tokenOwner, to, valueEnc, { from: spender }); 59 | 60 | const balanceEnc = await this.token.balanceOfEncrypted(tokenOwner, await this.getPermission(tokenOwner)) 61 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 62 | expect(balance).to.equal(0n); 63 | 64 | const balanceEncTo = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 65 | const balanceTo = fhenixjs.unseal(this.token.address, balanceEncTo); 66 | expect(balanceTo).to.equal(value); 67 | }); 68 | 69 | it('decreases the spender allowance', async function () { 70 | const valueEnc = await fhenixjs.encrypt_uint128(value); 71 | await this.token.transferFromEncrypted(tokenOwner, to, valueEnc, { from: spender }); 72 | 73 | const balanceEnc = await this.token.balanceOfEncrypted(tokenOwner, await this.getPermission(tokenOwner)) 74 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 75 | expect(balance).to.equal(0n); 76 | }); 77 | }); 78 | 79 | describe('when the token owner does not have enough balance', function () { 80 | const value = initialSupply; 81 | 82 | beforeEach('reducing balance', async function () { 83 | const valueEnc = await fhenixjs.encrypt_uint128(1n); 84 | await this.token.transferEncrypted(to, valueEnc, { from: tokenOwner }); 85 | }); 86 | 87 | it("doesn't transfer the requested value", async function () { 88 | const valueEnc = await fhenixjs.encrypt_uint128(value); 89 | this.token.transferFromEncrypted(tokenOwner, to, valueEnc, { from: spender }); 90 | 91 | const balanceEnc = await this.token.balanceOfEncrypted(tokenOwner, await this.getPermission(tokenOwner)) 92 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 93 | expect(balance).to.equal(value - 1n); 94 | 95 | const balanceEncTo = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 96 | const balanceTo = fhenixjs.unseal(this.token.address, balanceEncTo); 97 | expect(balanceTo).to.equal(1n); 98 | }); 99 | }); 100 | }); 101 | 102 | describe('when the spender does not have enough allowance', function () { 103 | const allowance = initialSupply - 1n; 104 | 105 | beforeEach(async function () { 106 | const allowanceEnc = await fhenixjs.encrypt_uint128(allowance); 107 | await this.token.approveEncrypted(spender, allowanceEnc, { from: tokenOwner }); 108 | }); 109 | 110 | describe('when the token owner has enough balance', function () { 111 | const value = initialSupply; 112 | 113 | it("doesn't transfer the requested value", async function () { 114 | const valueEnc = await fhenixjs.encrypt_uint128(value); 115 | this.token.transferFromEncrypted(tokenOwner, to, valueEnc, { from: spender }); 116 | 117 | const balanceEnc = await this.token.balanceOfEncrypted(tokenOwner, await this.getPermission(tokenOwner)) 118 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 119 | expect(balance).to.equal(value); 120 | 121 | const balanceEncTo = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 122 | const balanceTo = fhenixjs.unseal(this.token.address, balanceEncTo); 123 | expect(balanceTo).to.equal(0n); 124 | }); 125 | }); 126 | 127 | describe('when the token owner does not have enough balance', function () { 128 | const value = allowance; 129 | 130 | beforeEach('reducing balance', async function () { 131 | const reductionEnc = await fhenixjs.encrypt_uint128(2n); 132 | await this.token.transferEncrypted(to, reductionEnc, { from: tokenOwner }); 133 | }); 134 | 135 | it("doesn't transfer the requested value", async function () { 136 | const valueEnc = await fhenixjs.encrypt_uint128(value); 137 | this.token.transferFromEncrypted(tokenOwner, to, valueEnc, { from: spender }); 138 | 139 | const balanceEnc = await this.token.balanceOfEncrypted(tokenOwner, await this.getPermission(tokenOwner)) 140 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 141 | expect(balance).to.equal(value - 1n); 142 | 143 | const balanceEncTo = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 144 | const balanceTo = fhenixjs.unseal(this.token.address, balanceEncTo); 145 | expect(balanceTo).to.equal(2n); // because we sent 2 tokens here when lowering balance 146 | }); 147 | }); 148 | }); 149 | 150 | // FHERC20 does not have this functionality 151 | describe.skip('when the spender has unlimited allowance', function () { 152 | beforeEach(async function () { 153 | // todo if unskipped - encrypt amount 154 | await this.token.approve(spender, MAX_UINT256, { from: initialHolder }); 155 | }); 156 | 157 | it('does not decrease the spender allowance', async function () { 158 | const encryptedValue = await fhenixjs.encrypt_uint128(1n); 159 | await this.token.transferFromEncrypted(tokenOwner, to, encryptedValue, { from: spender }); 160 | 161 | expect(await this.token.allowanceEncrypted(tokenOwner, spender)).to.equal(MAX_UINT256); 162 | }); 163 | }); 164 | }); 165 | 166 | // FHERC20 does not have this functionality 167 | describe.skip('when the recipient is the zero address', function () { 168 | const value = initialSupply; 169 | const to = ZERO_ADDRESS; 170 | 171 | beforeEach(async function () { 172 | const encryptedValue = await fhenixjs.encrypt_uint128(value); 173 | await this.token.approveEncrypted(spender, encryptedValue, { from: tokenOwner }); 174 | }); 175 | 176 | it('reverts', async function () { 177 | await expectRevertCustomError( 178 | this.token.transferFromEncrypted(tokenOwner, to, value, { from: spender }), 179 | 'ERC20InvalidReceiver', 180 | [ZERO_ADDRESS], 181 | ); 182 | }); 183 | }); 184 | }); 185 | 186 | // FHERC20 does not have this functionality 187 | describe.skip('when the token owner is the zero address', function () { 188 | const value = 0; 189 | const tokenOwner = ZERO_ADDRESS; 190 | const to = recipient; 191 | 192 | it('reverts', async function () { 193 | await expectRevertCustomError( 194 | this.token.transferFromEncrypted(tokenOwner, to, value, { from: spender }), 195 | 'ERC20InvalidApprover', 196 | [ZERO_ADDRESS], 197 | ); 198 | }); 199 | }); 200 | }); 201 | 202 | describe('approve', function () { 203 | shouldBehaveLikeFHERC20Approve(initialHolder, recipient, initialSupply, async function (owner, spender, value) { 204 | const encryptedValue = await fhenixjs.encrypt_uint128(value); 205 | return this.token.approveEncrypted(spender, encryptedValue, { from: owner }); 206 | }); 207 | }); 208 | } 209 | 210 | function shouldBehaveLikeFHERC20Transfer(from, to, balance, transfer) { 211 | describe('when the recipient is not the zero address', function () { 212 | describe('when the sender does not have enough balance', function () { 213 | const value = balance + 1n; 214 | 215 | it("doesn't transfer value", async function () { 216 | await transfer.call(this, from, to, value); 217 | 218 | const balanceToEnc = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 219 | const balanceToAfter = fhenixjs.unseal(this.token.address, balanceToEnc); 220 | expect(balanceToAfter).to.equal(0n); 221 | 222 | const balanceEnc = await this.token.balanceOfEncrypted(from, await this.getPermission(from)) 223 | const balanceAfter = fhenixjs.unseal(this.token.address, balanceEnc); 224 | expect(balanceAfter).to.equal(balance); 225 | }); 226 | }); 227 | 228 | describe('when the sender transfers all balance', function () { 229 | const value = balance; 230 | 231 | it('transfers the requested value', async function () { 232 | await transfer.call(this, from, to, value); 233 | 234 | const balanceToEnc = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 235 | const balanceToAfter = fhenixjs.unseal(this.token.address, balanceToEnc); 236 | expect(balanceToAfter).to.equal(value); 237 | 238 | const balanceEnc = await this.token.balanceOfEncrypted(from, await this.getPermission(from)) 239 | const balanceAfter = fhenixjs.unseal(this.token.address, balanceEnc); 240 | expect(balanceAfter).to.equal(0n); 241 | }); 242 | }); 243 | 244 | describe('when the sender transfers zero tokens', function () { 245 | const value = 0n; 246 | 247 | it('transfers the requested value', async function () { 248 | await transfer.call(this, from, to, value); 249 | 250 | const balanceToEnc = await this.token.balanceOfEncrypted(to, await this.getPermission(to)) 251 | const balanceToAfter = fhenixjs.unseal(this.token.address, balanceToEnc); 252 | expect(balanceToAfter).to.equal(0n); 253 | 254 | const balanceEnc = await this.token.balanceOfEncrypted(from, await this.getPermission(from)) 255 | const balanceAfter = fhenixjs.unseal(this.token.address, balanceEnc); 256 | expect(balanceAfter).to.equal(balance); 257 | }); 258 | }); 259 | }); 260 | 261 | // currently not dealing with zero-addresses 262 | describe.skip('when the recipient is the zero address', function () { 263 | it('reverts', async function () { 264 | await expectRevertCustomError(transfer.call(this, from, ZERO_ADDRESS, balance), 'ERC20InvalidReceiver', [ 265 | ZERO_ADDRESS, 266 | ]); 267 | }); 268 | }); 269 | } 270 | 271 | function shouldBehaveLikeFHERC20Approve(owner, spender, supply, approve) { 272 | describe('when the spender is not the zero address', function () { 273 | describe('when the sender has enough balance', function () { 274 | const value = supply; 275 | 276 | describe('when there was no approved value before', function () { 277 | it('approves the requested value', async function () { 278 | await approve.call(this, owner, spender, value); 279 | 280 | const balanceEnc = await this.token.allowanceEncrypted(owner, spender, await this.getPermission(spender)) 281 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 282 | expect(balance).to.equal(value); 283 | }); 284 | }); 285 | 286 | describe('when the spender had an approved value', function () { 287 | beforeEach(async function () { 288 | await approve.call(this, owner, spender, 1n); 289 | }); 290 | 291 | it('approves the requested value and replaces the previous one', async function () { 292 | await approve.call(this, owner, spender, value); 293 | 294 | const balanceEnc = await this.token.allowanceEncrypted(owner, spender, await this.getPermission(spender)) 295 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 296 | expect(balance).to.equal(value); 297 | }); 298 | }); 299 | }); 300 | 301 | describe('when the sender does not have enough balance', function () { 302 | const value = supply + 1n; 303 | 304 | describe('when there was no approved value before', function () { 305 | it('approves the requested value', async function () { 306 | await approve.call(this, owner, spender, value); 307 | 308 | const balanceEnc = await this.token.allowanceEncrypted(owner, spender, await this.getPermission(spender)) 309 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 310 | expect(balance).to.equal(value); 311 | }); 312 | }); 313 | 314 | describe('when the spender had an approved value', function () { 315 | beforeEach(async function () { 316 | await approve.call(this, owner, spender, 1n); 317 | }); 318 | 319 | it('approves the requested value and replaces the previous one', async function () { 320 | await approve.call(this, owner, spender, value); 321 | 322 | const balanceEnc = await this.token.allowanceEncrypted(owner, spender, await this.getPermission(spender)) 323 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 324 | expect(balance).to.equal(value); 325 | }); 326 | }); 327 | }); 328 | }); 329 | 330 | describe('when the spender is the zero address', function () { 331 | it('reverts', async function () { 332 | await expectRevertCustomError(approve.call(this, owner, ZERO_ADDRESS, supply), `ERC20InvalidSpender`, [ 333 | ZERO_ADDRESS, 334 | ]); 335 | }); 336 | }); 337 | } 338 | 339 | module.exports = { 340 | shouldBehaveLikeFHERC20, 341 | shouldBehaveLikeFHERC20Transfer, 342 | shouldBehaveLikeFHERC20Approve, 343 | }; 344 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC20/FHERC20.test.ts: -------------------------------------------------------------------------------- 1 | const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); 2 | const { expect } = require('chai'); 3 | 4 | import hre from "hardhat"; 5 | const { ZERO_ADDRESS } = constants; 6 | 7 | const { 8 | shouldBehaveLikeFHERC20, 9 | shouldBehaveLikeFHERC20Transfer, 10 | shouldBehaveLikeFHERC20Approve, 11 | } = require('./FHERC20.behavior'); 12 | const { expectRevertCustomError } = require('../../../helpers/customError'); 13 | 14 | const TOKENS = [ 15 | { Token: artifacts.require('$FHERC20') }, 16 | ]; 17 | 18 | contract('FHERC20 encrypted', function (accounts) { 19 | const [initialHolder, recipient] = accounts; 20 | 21 | const name = 'My Token'; 22 | const symbol = 'MTKN'; 23 | const initialSupply = 100n; 24 | 25 | for (const { Token, forcedApproval } of TOKENS) { 26 | describe(`using ${Token._json.contractName}`, function () { 27 | before(async function () { 28 | this.signers = (await ethers.getSigners()).slice(0, 3); 29 | this.getPermission = async address => { 30 | const signer = this.signers.find(s => s.address === address); 31 | const permit = await fhenixjs.generatePermit(this.token.address, undefined, signer) 32 | return fhenixjs.extractPermitPermission(permit); 33 | } 34 | }); 35 | 36 | beforeEach(async function () { 37 | // get sufficient funds 38 | const { ethers, deployments } = hre; 39 | const [signer, spender] = this.signers; 40 | 41 | // fund first account: owner 42 | if ((await ethers.provider.getBalance(signer.address)).toString() === "0") { 43 | await fhenixjs.getFunds(signer.address); 44 | } 45 | 46 | // fund second account as it is sometimes an allowed spender 47 | if ((await ethers.provider.getBalance(spender.address)).toString() === "0") { 48 | await fhenixjs.getFunds(spender.address); 49 | } 50 | 51 | this.token = await Token.new(name, symbol); 52 | 53 | const encryptedInitialSupply = await fhenixjs.encrypt_uint128(initialSupply); 54 | await this.token.$_mintEncrypted(initialHolder, encryptedInitialSupply); 55 | }); 56 | 57 | shouldBehaveLikeFHERC20(initialSupply, accounts, { forcedApproval}); 58 | 59 | describe('_mintEncrypted', function () { 60 | const value = 50n; 61 | // FHERC20 doesn't have a special case for the zero address 62 | it.skip('rejects a null account', async function () { 63 | await expectRevertCustomError(this.token.$_mintEncrypted(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]); 64 | }); 65 | 66 | describe('for a non zero account', function () { 67 | beforeEach('minting', async function () { 68 | const encryptedValue = await fhenixjs.encrypt_uint128(value); 69 | this.receipt = await this.token.$_mintEncrypted(recipient, encryptedValue); 70 | }); 71 | 72 | // Not yet implmeneted: 73 | it.skip('increments totalSupply', async function () { 74 | const expectedSupply = initialSupply.add(value); 75 | expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); 76 | }); 77 | 78 | it('increments recipient balance', async function () { 79 | const balanceEnc = await this.token.balanceOfEncrypted(recipient, await this.getPermission(recipient)) 80 | const balance = fhenixjs.unseal(this.token.address, balanceEnc); 81 | expect(balance).to.equal(value); 82 | }); 83 | }); 84 | }); 85 | 86 | // FHERC20 doesn't have a special case for the zero address 87 | describe.skip('_transferImpl', function () { 88 | const value = 1n; 89 | 90 | it('from is the zero address', async function () { 91 | const balanceBefore = await this.token.balanceOf(initialHolder); 92 | 93 | await this.token.$_transferImpl(ZERO_ADDRESS, initialHolder, value); 94 | 95 | // total encrypted supply not implemented: 96 | // const totalSupply = await this.token.getEncryptedTotalSupply(); 97 | // expect(await this.token.getEncryptedTotalSupply()).to.be.bignumber.equal(totalSupply.add(value)); 98 | // expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(value)); 99 | }); 100 | 101 | it('to is the zero address', async function () { 102 | const balanceBefore = await this.token.balanceOf(initialHolder); 103 | // const totalSupply = await this.token.getTotalEncryptedSupply(); 104 | 105 | await this.token.$_transferImpl(initialHolder, ZERO_ADDRESS, value) 106 | // expect(await this.token.getTotalEncryptedSupply()).to.be.bignumber.equal(totalSupply.sub(value)); 107 | expect(await this.token.balanceOfEncrypted(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(value)); 108 | }); 109 | }); 110 | }); 111 | } 112 | }); 113 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC721/ERC721.test.ts: -------------------------------------------------------------------------------- 1 | const { shouldBehaveLikeERC721, shouldBehaveLikeERC721Metadata } = require('./ERC721.behavior'); 2 | const { shouldBehaveLikeFHERC721 } = require('./FHERC721.behavior'); 3 | 4 | const ERC721 = artifacts.require('$FHERC721'); 5 | 6 | contract('FHERC721 as ERC721', function (accounts) { 7 | const name = 'Non Fungible Token'; 8 | const symbol = 'NFT'; 9 | 10 | before(async function () { 11 | this.signers = (await ethers.getSigners()).slice(0, 6); 12 | this.getPermission = async address => { 13 | const signer = this.signers.find(s => s.address === address); 14 | const permit = await fhenixjs.generatePermit(this.token.address, undefined, signer) 15 | return fhenixjs.extractPermitPermission(permit); 16 | } 17 | 18 | // fund first account: owner 19 | if ((await ethers.provider.getBalance(this.signers[0].address)).toString() === "0") { 20 | await fhenixjs.getFunds(this.signers[0].address); 21 | } 22 | 23 | // fund sixth account: other 24 | if ((await ethers.provider.getBalance(this.signers[5].address)).toString() === "0") { 25 | await fhenixjs.getFunds(this.signers[5].address); 26 | } 27 | }); 28 | 29 | beforeEach(async function () { 30 | this.token = await ERC721.new(name, symbol); 31 | }); 32 | 33 | shouldBehaveLikeERC721(...accounts); 34 | shouldBehaveLikeFHERC721(...accounts); 35 | shouldBehaveLikeERC721Metadata(name, symbol, ...accounts); 36 | }); 37 | -------------------------------------------------------------------------------- /test/experimental/token/FHERC721/FHERC721.behavior.ts: -------------------------------------------------------------------------------- 1 | const { BN } = require('@openzeppelin/test-helpers'); 2 | const { expect } = require('chai'); 3 | 4 | const { shouldSupportInterfaces } = require('../../../helpers/SupportsInterface.behavior'); 5 | const { expectRevertCustomError } = require('../../../helpers/customError'); 6 | 7 | const firstTokenId = new BN('5042'); 8 | const secondTokenId = new BN('79217'); 9 | const nonExistentTokenId = new BN('13'); 10 | 11 | function shouldBehaveLikeFHERC721(owner, newOwner, approved, anotherApproved, operator, other) { 12 | shouldSupportInterfaces(['ERC165', 'ERC721']); 13 | 14 | context('with minted tokens', function () { 15 | beforeEach(async function () { 16 | const privateData1 = await fhenixjs.encrypt_uint128(111n); 17 | const privateData2 = await fhenixjs.encrypt_uint128(222n); 18 | await this.token.$_mint(owner, firstTokenId, privateData1); 19 | await this.token.$_mint(newOwner, secondTokenId, privateData2); 20 | this.toWhom = other; // default to other for toWhom in context-dependent tests 21 | }); 22 | 23 | describe('tokenPrivateData', function () { 24 | context('when the given address owns the token', function () { 25 | it('returns the private data of tokens owned by the given address', async function () { 26 | const encPrivData = await this.token.tokenPrivateData(firstTokenId, await this.getPermission(owner)) 27 | const privateData = fhenixjs.unseal(this.token.address, encPrivData); 28 | expect(privateData).to.equal(111n); 29 | }); 30 | }); 31 | 32 | context('when the given address does not own the token', function () { 33 | it('reverts', async function () { 34 | await expectRevertCustomError( 35 | this.token.tokenPrivateData(secondTokenId, await this.getPermission(owner)), 36 | 'SignerNotOwner', 37 | [] 38 | ); 39 | }); 40 | }); 41 | 42 | context('when the token does not exist', function () { 43 | it('reverts', async function () { 44 | await expectRevertCustomError( 45 | this.token.tokenPrivateData(nonExistentTokenId, await this.getPermission(owner)), 46 | 'SignerNotOwner', 47 | [] 48 | ); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }; 54 | 55 | module.exports = { 56 | shouldBehaveLikeFHERC721, 57 | }; 58 | -------------------------------------------------------------------------------- /test/fhe/typedSealedOutputs.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers } from 'hardhat' 3 | 4 | import { getTokensFromFaucet } from '../permitV2/chain-utils' 5 | import { generatePermitV2, extractPermissionV2 } from '../permitV2/permit-v2-utils' 6 | import { TypedSealedOutputsTest__factory } from '../../types' 7 | import { getAddress } from 'ethers' 8 | 9 | const EUINT8_TFHE = 0 10 | const EUINT16_TFHE = 1 11 | const EUINT32_TFHE = 2 12 | const EUINT64_TFHE = 3 13 | const EUINT128_TFHE = 4 14 | const EUINT256_TFHE = 5 15 | const EADDRESS_TFHE = 12 16 | const EBOOL_TFHE = 13 17 | 18 | const EUINT_TFHE = { 19 | 8: EUINT8_TFHE, 20 | 16: EUINT16_TFHE, 21 | 32: EUINT32_TFHE, 22 | 64: EUINT64_TFHE, 23 | 128: EUINT128_TFHE, 24 | 256: EUINT256_TFHE, 25 | } as const 26 | 27 | describe('TypedSealedOutputs', function () { 28 | async function typedSealedOutputsFixture() { 29 | const signer = (await ethers.getSigners())[0] 30 | await getTokensFromFaucet(signer.address) 31 | 32 | const typedSealedOutputsFactory = (await ethers.getContractFactory('TypedSealedOutputsTest')) as TypedSealedOutputsTest__factory 33 | const typedSealedOutputs = await typedSealedOutputsFactory.deploy() 34 | await typedSealedOutputs.waitForDeployment() 35 | const typedSealedOutputsAddress = await typedSealedOutputs.getAddress() 36 | 37 | // Signer permit and permission 38 | const permit = await generatePermitV2( 39 | { 40 | issuer: signer.address, 41 | projects: ['TEST'], 42 | }, 43 | ethers.provider, 44 | signer 45 | ) 46 | const permission = extractPermissionV2(permit) 47 | 48 | return { 49 | signer, 50 | typedSealedOutputs, 51 | typedSealedOutputsAddress, 52 | permit, 53 | permission, 54 | } 55 | } 56 | 57 | it('SealedBool', async () => { 58 | const { typedSealedOutputs, permit, permission } = await typedSealedOutputsFixture() 59 | const value = true 60 | 61 | const [fheOutput, bindingsOutput] = await typedSealedOutputs.getSealedEBool(permission, value) 62 | 63 | // NOTE: Unsealing a sealed bool returns a BigInt 64 | // This function converts a bigint into a boolean 65 | const bnToBoolean = (bn: BigInt) => Boolean(bn).valueOf() 66 | 67 | expect(fheOutput.utype).to.eq(EBOOL_TFHE, 'sealed bool utype (fhe)') 68 | expect(bnToBoolean(permit.sealingPair.unseal(fheOutput.data))).to.eq(value, 'unsealed bool match (fhe)') 69 | 70 | expect(bindingsOutput.utype).to.eq(EBOOL_TFHE, 'sealed bool utype (bindings)') 71 | expect(bnToBoolean(permit.sealingPair.unseal(bindingsOutput.data))).to.eq(value, 'unsealed bool match (bindings)') 72 | }) 73 | 74 | describe('SealedUint', async () => { 75 | ;([8, 16, 32, 64, 128, 256] as const).map((value) => 76 | it(`euint${value}`, async () => { 77 | const { typedSealedOutputs, permit, permission } = await typedSealedOutputsFixture() 78 | 79 | const [fheOutput, bindingsOutput] = await typedSealedOutputs[`getSealedEUint${value}`](permission, value) 80 | 81 | expect(fheOutput.utype).to.eq(EUINT_TFHE[value], `sealed uint${value} utype (fhe)`) 82 | expect(permit.sealingPair.unseal(fheOutput.data)).to.eq(value, `unsealed uint${value} match (fhe)`) 83 | 84 | expect(bindingsOutput.utype).to.eq(EUINT_TFHE[value], `sealed uint${value} utype (bindings)`) 85 | expect(permit.sealingPair.unseal(bindingsOutput.data)).to.eq(value, `unsealed uint${value} match (bindings)`) 86 | }) 87 | ) 88 | }) 89 | 90 | it('SealedAddress', async () => { 91 | const { typedSealedOutputs, permit, permission } = await typedSealedOutputsFixture() 92 | 93 | // Random address 94 | const value = '0x1BDB34f2cEA785317903eD9618F5282BD5Be5c75' 95 | 96 | const [fheOutput, bindingsOutput] = await typedSealedOutputs.getSealedEAddress(permission, value) 97 | 98 | // NOTE: Unsealing a sealed address returns a bigint 99 | // This function converts a bigint into a checksummed address (dependency:ethers) 100 | const bnToAddress = (bn: BigInt) => getAddress(`0x${bn.toString(16).slice(-40)}`) 101 | 102 | expect(fheOutput.utype).to.eq(EADDRESS_TFHE, 'sealed address utype (fhe)') 103 | expect(bnToAddress(permit.sealingPair.unseal(fheOutput.data))).to.eq(value, 'unsealed address match (fhe)') 104 | 105 | expect(bindingsOutput.utype).to.eq(EADDRESS_TFHE, 'sealed address utype (bindings)') 106 | expect(bnToAddress(permit.sealingPair.unseal(bindingsOutput.data))).to.eq(value, 'unsealed address match (bindings)') 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /test/helpers/SupportsInterface.behavior.ts: -------------------------------------------------------------------------------- 1 | const { makeInterfaceId } = require('@openzeppelin/test-helpers'); 2 | 3 | const { expect } = require('chai'); 4 | 5 | const INVALID_ID = '0xffffffff'; 6 | const INTERFACES = { 7 | ERC165: ['supportsInterface(bytes4)'], 8 | FHERC721: ['tokenPrivateData(uint256,(bytes32,bytes))'], 9 | ERC721: [ 10 | 'balanceOf(address)', 11 | 'ownerOf(uint256)', 12 | 'approve(address,uint256)', 13 | 'getApproved(uint256)', 14 | 'setApprovalForAll(address,bool)', 15 | 'isApprovedForAll(address,address)', 16 | 'transferFrom(address,address,uint256)', 17 | 'safeTransferFrom(address,address,uint256)', 18 | 'safeTransferFrom(address,address,uint256,bytes)', 19 | ], 20 | ERC721Enumerable: ['totalSupply()', 'tokenOfOwnerByIndex(address,uint256)', 'tokenByIndex(uint256)'], 21 | ERC721Metadata: ['name()', 'symbol()', 'tokenURI(uint256)'], 22 | ERC1155: [ 23 | 'balanceOf(address,uint256)', 24 | 'balanceOfBatch(address[],uint256[])', 25 | 'setApprovalForAll(address,bool)', 26 | 'isApprovedForAll(address,address)', 27 | 'safeTransferFrom(address,address,uint256,uint256,bytes)', 28 | 'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)', 29 | ], 30 | ERC1155Receiver: [ 31 | 'onERC1155Received(address,address,uint256,uint256,bytes)', 32 | 'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)', 33 | ], 34 | AccessControl: [ 35 | 'hasRole(bytes32,address)', 36 | 'getRoleAdmin(bytes32)', 37 | 'grantRole(bytes32,address)', 38 | 'revokeRole(bytes32,address)', 39 | 'renounceRole(bytes32,address)', 40 | ], 41 | AccessControlEnumerable: ['getRoleMember(bytes32,uint256)', 'getRoleMemberCount(bytes32)'], 42 | AccessControlDefaultAdminRules: [ 43 | 'defaultAdminDelay()', 44 | 'pendingDefaultAdminDelay()', 45 | 'defaultAdmin()', 46 | 'pendingDefaultAdmin()', 47 | 'defaultAdminDelayIncreaseWait()', 48 | 'changeDefaultAdminDelay(uint48)', 49 | 'rollbackDefaultAdminDelay()', 50 | 'beginDefaultAdminTransfer(address)', 51 | 'acceptDefaultAdminTransfer()', 52 | 'cancelDefaultAdminTransfer()', 53 | ], 54 | Governor: [ 55 | 'name()', 56 | 'version()', 57 | 'COUNTING_MODE()', 58 | 'hashProposal(address[],uint256[],bytes[],bytes32)', 59 | 'state(uint256)', 60 | 'proposalThreshold()', 61 | 'proposalSnapshot(uint256)', 62 | 'proposalDeadline(uint256)', 63 | 'proposalProposer(uint256)', 64 | 'proposalEta(uint256)', 65 | 'proposalNeedsQueuing(uint256)', 66 | 'votingDelay()', 67 | 'votingPeriod()', 68 | 'quorum(uint256)', 69 | 'getVotes(address,uint256)', 70 | 'getVotesWithParams(address,uint256,bytes)', 71 | 'hasVoted(uint256,address)', 72 | 'propose(address[],uint256[],bytes[],string)', 73 | 'queue(address[],uint256[],bytes[],bytes32)', 74 | 'execute(address[],uint256[],bytes[],bytes32)', 75 | 'cancel(address[],uint256[],bytes[],bytes32)', 76 | 'castVote(uint256,uint8)', 77 | 'castVoteWithReason(uint256,uint8,string)', 78 | 'castVoteWithReasonAndParams(uint256,uint8,string,bytes)', 79 | 'castVoteBySig(uint256,uint8,address,bytes)', 80 | 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)', 81 | ], 82 | ERC2981: ['royaltyInfo(uint256,uint256)'], 83 | }; 84 | 85 | const INTERFACE_IDS = {}; 86 | const FN_SIGNATURES = {}; 87 | for (const k of Object.getOwnPropertyNames(INTERFACES)) { 88 | INTERFACE_IDS[k] = makeInterfaceId.ERC165(INTERFACES[k]); 89 | for (const fnName of INTERFACES[k]) { 90 | // the interface id of a single function is equivalent to its function signature 91 | FN_SIGNATURES[fnName] = makeInterfaceId.ERC165([fnName]); 92 | } 93 | } 94 | 95 | function shouldSupportInterfaces(interfaces = []) { 96 | // todo (eshel) add support for IFHERC721 supportInterface 97 | describe('ERC165', function () { 98 | beforeEach(function () { 99 | this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl; 100 | }); 101 | 102 | describe('when the interfaceId is supported', function () { 103 | it('uses less than 30k gas', async function () { 104 | for (const k of interfaces) { 105 | const interfaceId = INTERFACE_IDS[k] ?? k; 106 | expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000); 107 | } 108 | }); 109 | 110 | it('returns true', async function () { 111 | for (const k of interfaces) { 112 | const interfaceId = INTERFACE_IDS[k] ?? k; 113 | expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true, `does not support ${k}`); 114 | } 115 | }); 116 | }); 117 | 118 | describe('when the interfaceId is not supported', function () { 119 | it('uses less thank 30k', async function () { 120 | expect(await this.contractUnderTest.supportsInterface.estimateGas(INVALID_ID)).to.be.lte(30000); 121 | }); 122 | 123 | it('returns false', async function () { 124 | expect(await this.contractUnderTest.supportsInterface(INVALID_ID)).to.be.equal(false, `supports ${INVALID_ID}`); 125 | }); 126 | }); 127 | 128 | it('all interface functions are in ABI', async function () { 129 | for (const k of interfaces) { 130 | // skip interfaces for which we don't have a function list 131 | if (INTERFACES[k] === undefined) continue; 132 | for (const fnName of INTERFACES[k]) { 133 | const fnSig = FN_SIGNATURES[fnName]; 134 | expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal( 135 | 1, 136 | `did not find ${fnName}`, 137 | ); 138 | } 139 | } 140 | }); 141 | }); 142 | } 143 | 144 | module.exports = { 145 | shouldSupportInterfaces, 146 | }; 147 | -------------------------------------------------------------------------------- /test/helpers/customError.ts: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | 3 | /** Revert handler that supports custom errors. */ 4 | async function expectRevertCustomError(promise, expectedErrorName, args) { 5 | if (!Array.isArray(args)) { 6 | expect.fail('Expected 3rd array parameter for error arguments'); 7 | } 8 | 9 | await promise.then( 10 | () => expect.fail("Expected promise to throw but it didn't"), 11 | ({ message }) => { 12 | // NOTE: When a tx revert in fhenix, the message is always the same, meaning we can't check the custom error name. 13 | // We could check the tx data, which is returned from the node on eth_calls, but it's stripped out of the error by 14 | // one of the web3 libraries. 15 | if (message !== "Returned error: execution reverted") { 16 | expect.fail(`Expected 'execution reverted' message`); 17 | } 18 | }, 19 | ); 20 | } 21 | 22 | module.exports = { 23 | expectRevertCustomError, 24 | }; 25 | -------------------------------------------------------------------------------- /test/helpers/enums.ts: -------------------------------------------------------------------------------- 1 | function Enum(...options) { 2 | return Object.fromEntries(options.map((key, i) => [key, i])); 3 | } 4 | 5 | module.exports = { 6 | Enum, 7 | ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), 8 | VoteType: Enum('Against', 'For', 'Abstain'), 9 | Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'), 10 | OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'), 11 | }; 12 | -------------------------------------------------------------------------------- /test/permitV2/PermissionedV2.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers, fhenixjs } from 'hardhat' 3 | import { time } from '@nomicfoundation/hardhat-network-helpers' 4 | 5 | import { createExpiration, getLastBlockTimestamp, getTokensFromFaucet } from './chain-utils' 6 | import { hours } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration' 7 | import { generatePermitV2, extractPermissionV2, reSignSharedPermit } from './permit-v2-utils' 8 | import { PermissionedV2Counter__factory, PermissionedV2RevokableValidator__factory, PermissionedV2TimestampValidator__factory } from '../../types' 9 | import { GenerateSealingKey } from 'fhenixjs' 10 | 11 | describe('PermissionedV2', function () { 12 | async function permissionedV2Fixture() { 13 | const signer = (await ethers.getSigners())[0] 14 | const bob = (await ethers.getSigners())[1] 15 | const ada = (await ethers.getSigners())[2] 16 | 17 | await getTokensFromFaucet(signer.address) 18 | await getTokensFromFaucet(bob.address) 19 | await getTokensFromFaucet(ada.address) 20 | 21 | // Deploy Counter1 22 | const counterFactory = (await ethers.getContractFactory('PermissionedV2Counter')) as PermissionedV2Counter__factory 23 | const counter1 = await counterFactory.deploy() 24 | await counter1.waitForDeployment() 25 | const counter1Address = await counter1.getAddress() 26 | 27 | // Deploy Counter2 28 | const counter2 = await counterFactory.deploy() 29 | await counter2.waitForDeployment() 30 | const counter2Address = await counter2.getAddress() 31 | 32 | // Deploy example revokable validator (id) 33 | const permissionRevokableValidatorFactory = (await ethers.getContractFactory( 34 | 'PermissionedV2RevokableValidator' 35 | )) as PermissionedV2RevokableValidator__factory 36 | const permissionRevokableValidator = await permissionRevokableValidatorFactory.deploy() 37 | await permissionRevokableValidator.waitForDeployment() 38 | const permissionRevokableValidatorAddress = await permissionRevokableValidator.getAddress() 39 | 40 | // Deploy example revokable validator (timestamp) 41 | const permissionTimestampValidatorFactory = (await ethers.getContractFactory( 42 | 'PermissionedV2TimestampValidator' 43 | )) as PermissionedV2TimestampValidator__factory 44 | const permissionTimestampValidator = await permissionTimestampValidatorFactory.deploy() 45 | await permissionTimestampValidator.waitForDeployment() 46 | const permissionTimestampValidatorAddress = await permissionTimestampValidator.getAddress() 47 | 48 | return { 49 | signer, 50 | bob, 51 | ada, 52 | counter1, 53 | counter1Address, 54 | counter2, 55 | counter2Address, 56 | permissionRevokableValidator, 57 | permissionRevokableValidatorAddress, 58 | permissionTimestampValidator, 59 | permissionTimestampValidatorAddress, 60 | } 61 | } 62 | 63 | // ================================= 64 | // GOLDEN PATHS 65 | // ================================= 66 | describe('Golden paths', async () => { 67 | it('Multi contract access (`permission.contracts`)', async () => { 68 | const { bob, counter1, counter1Address, counter2, counter2Address } = await permissionedV2Fixture() 69 | 70 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 71 | await counter2.connect(bob).add(await fhenixjs.encrypt_uint32(3)) 72 | 73 | expect(await counter1.getCounter(bob.address)).to.eq(5, "Bob's counter1 value should be added") 74 | expect(await counter2.getCounter(bob.address)).to.eq(3, "Bob's counter2 value should be added") 75 | 76 | // Single permission for multiple contracts 77 | const permit = await generatePermitV2( 78 | { 79 | issuer: bob.address, 80 | contracts: [counter1Address, counter2Address], 81 | }, 82 | ethers.provider, 83 | bob 84 | ) 85 | const permission = extractPermissionV2(permit) 86 | 87 | const c1sealed = await counter1.connect(bob).getCounterPermitSealed(permission) 88 | const c1unsealed = permit.sealingPair.unseal(c1sealed) 89 | expect(c1unsealed).to.eq(5, 'Bobs counter1 unsealed value should match') 90 | 91 | const c2sealed = await counter2.connect(bob).getCounterPermitSealed(permission) 92 | const c2unsealed = permit.sealingPair.unseal(c2sealed) 93 | expect(c2unsealed).to.eq(3, 'Bobs counter2 unsealed value should match') 94 | }) 95 | 96 | it('Multi contract access (`permission.projects`)', async () => { 97 | const { bob, counter1, counter2 } = await permissionedV2Fixture() 98 | 99 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 100 | await counter2.connect(bob).add(await fhenixjs.encrypt_uint32(3)) 101 | 102 | expect(await counter1.getCounter(bob.address)).to.eq(5, "Bob's counter1 value should be added") 103 | expect(await counter2.getCounter(bob.address)).to.eq(3, "Bob's counter2 value should be added") 104 | 105 | // Single permission for multiple contracts 106 | const permit = await generatePermitV2( 107 | { 108 | issuer: bob.address, 109 | projects: ['COUNTER'], 110 | }, 111 | ethers.provider, 112 | bob 113 | ) 114 | const permission = extractPermissionV2(permit) 115 | 116 | const c1sealed = await counter1.connect(bob).getCounterPermitSealed(permission) 117 | const c1unsealed = permit.sealingPair.unseal(c1sealed) 118 | expect(c1unsealed).to.eq(5, 'Bobs counter1 unsealed value should match') 119 | 120 | const c2sealed = await counter2.connect(bob).getCounterPermitSealed(permission) 121 | const c2unsealed = permit.sealingPair.unseal(c2sealed) 122 | expect(c2unsealed).to.eq(3, 'Bobs counter2 unsealed value should match') 123 | }) 124 | 125 | it('Access can revert', async () => { 126 | const { bob, counter1 } = await permissionedV2Fixture() 127 | 128 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 129 | 130 | const permit = await generatePermitV2( 131 | { 132 | issuer: bob.address, 133 | }, 134 | ethers.provider, 135 | bob 136 | ) 137 | const permission = extractPermissionV2(permit) 138 | 139 | await expect(counter1.connect(bob).getCounterPermitSealed(permission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_ContractUnauthorized') 140 | }) 141 | 142 | it('Expiration', async () => { 143 | const { bob, counter1 } = await permissionedV2Fixture() 144 | 145 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 146 | 147 | // Expiration date in future 148 | const futureExpiration = await createExpiration(hours(24)) 149 | const permitWithFutureExpiration = await generatePermitV2( 150 | { 151 | issuer: bob.address, 152 | projects: ['COUNTER'], 153 | expiration: futureExpiration, 154 | }, 155 | ethers.provider, 156 | bob 157 | ) 158 | const permissionWithFutureExpiration = extractPermissionV2(permitWithFutureExpiration) 159 | await counter1.connect(bob).getCounterPermitSealed(permissionWithFutureExpiration) 160 | 161 | // Expiration date in past 162 | const pastExpiration = await createExpiration(-1) 163 | const permitWithPastExpiration = await generatePermitV2( 164 | { 165 | issuer: bob.address, 166 | projects: ['COUNTER'], 167 | expiration: pastExpiration, 168 | }, 169 | ethers.provider, 170 | bob 171 | ) 172 | const permissionWithPastExpiration = extractPermissionV2(permitWithPastExpiration) 173 | 174 | await expect(counter1.connect(bob).getCounterPermitSealed(permissionWithPastExpiration)).to.be.revertedWithCustomError( 175 | counter1, 176 | 'PermissionInvalid_Expired' 177 | ) 178 | }) 179 | 180 | it('Sharing', async () => { 181 | const { bob, ada, counter1 } = await permissionedV2Fixture() 182 | 183 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 184 | 185 | // Self 186 | const bobPermitForSelf = await generatePermitV2( 187 | { 188 | issuer: bob.address, 189 | projects: ['COUNTER'], 190 | }, 191 | ethers.provider, 192 | bob 193 | ) 194 | const bobPermissionForSelf = extractPermissionV2(bobPermitForSelf) 195 | const bobCallSealed = await counter1.connect(bob).getCounterPermitSealed(bobPermissionForSelf) 196 | 197 | // Sharing 198 | const bobPermitForSharing = await generatePermitV2( 199 | { 200 | issuer: bob.address, 201 | projects: ['COUNTER'], 202 | recipient: ada.address, 203 | }, 204 | ethers.provider, 205 | bob 206 | ) 207 | const adaPermit = await reSignSharedPermit(bobPermitForSharing, ethers.provider, ada) 208 | const adaPermission = extractPermissionV2(adaPermit) 209 | 210 | // Expect not to revert 211 | const adaCallSealed = await counter1.connect(ada).getCounterPermitSealed(adaPermission) 212 | 213 | // Shared and Self results match 214 | const bobCallUnsealed = bobPermitForSelf.sealingPair.unseal(bobCallSealed) 215 | const adaCallUnsealed = adaPermit.sealingPair.unseal(adaCallSealed) 216 | expect(bobCallUnsealed).to.eq(adaCallUnsealed, "Bob and Ada can both use bob's permission") 217 | }) 218 | }) 219 | 220 | // ================================= 221 | // SIG REVERSIONS 222 | // ================================= 223 | describe('Signature reversions', async () => { 224 | it('`permission.issuerSignature` (not shared)', async () => { 225 | const { bob, ada, counter1, counter1Address, counter2, counter2Address } = await permissionedV2Fixture() 226 | 227 | // Valid permission 228 | const permit = await generatePermitV2( 229 | { 230 | issuer: bob.address, 231 | contracts: [counter1Address], 232 | }, 233 | ethers.provider, 234 | bob 235 | ) 236 | const permission = extractPermissionV2(permit) 237 | 238 | // Valid (sanity check) 239 | await counter1.getCounterPermitSealed(permission) 240 | 241 | // Invalid (access Ada's data) 242 | await expect( 243 | counter1.getCounterPermitSealed({ 244 | ...permission, 245 | issuer: ada.address, 246 | }) 247 | ).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_IssuerSignature') 248 | 249 | // Invalid (access counter2 data) 250 | await expect( 251 | counter2.getCounterPermitSealed({ 252 | ...permission, 253 | contracts: [counter2Address], 254 | }) 255 | ).to.be.revertedWithCustomError(counter2, 'PermissionInvalid_IssuerSignature') 256 | }) 257 | 258 | it('`permission.issuerSignature` (shared)', async () => { 259 | const { signer, bob, ada, counter1, counter1Address } = await permissionedV2Fixture() 260 | 261 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 262 | 263 | // Valid permission 264 | const bobPermit = await generatePermitV2( 265 | { 266 | issuer: bob.address, 267 | contracts: [counter1Address], 268 | recipient: signer.address, 269 | }, 270 | ethers.provider, 271 | bob 272 | ) 273 | const signerPermit = await reSignSharedPermit(bobPermit, ethers.provider, signer) 274 | const signerPermission = extractPermissionV2(signerPermit) 275 | 276 | // Valid (sanity check) 277 | const bobSealed = await counter1.getCounterPermitSealed(signerPermission) 278 | expect(signerPermit.sealingPair.unseal(bobSealed)).to.eq(5, "Returns Bob's data") 279 | 280 | // Invalid (Ada attempting to use Bob's permission shared with Signer) 281 | // Throws `PermissionInvalid_IssuerSignature` because the Permission data changed 282 | // by Ada is included in the fields signed by the original issuer. 283 | bobPermit.recipient = ada.address 284 | const adaPermit = await reSignSharedPermit({ ...bobPermit, recipient: ada.address }, ethers.provider, ada) 285 | const adaPermission = extractPermissionV2(adaPermit) 286 | await expect(counter1.getCounterPermitSealed(adaPermission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_IssuerSignature') 287 | }) 288 | 289 | it('`recipientSignature`', async () => { 290 | const { signer, bob, counter1, counter1Address } = await permissionedV2Fixture() 291 | 292 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 293 | 294 | // Valid permission 295 | const bobPermit = await generatePermitV2( 296 | { 297 | issuer: bob.address, 298 | contracts: [counter1Address], 299 | recipient: signer.address, 300 | }, 301 | ethers.provider, 302 | bob 303 | ) 304 | const signerPermit = await reSignSharedPermit(bobPermit, ethers.provider, signer) 305 | const signerPermission = extractPermissionV2(signerPermit) 306 | 307 | // Valid (sanity check) 308 | const bobSealed = await counter1.getCounterPermitSealed(signerPermission) 309 | expect(signerPermit.sealingPair.unseal(bobSealed)).to.eq(5, "Returns Bob's data") 310 | 311 | // Invalid (Ada attempting to use Signer's permission with her own sealingKey) 312 | // Throws `PermissionInvalid_RecipientSignature` because `sealingKey` is secured 313 | // as part of `recipientSignature` 314 | const adaKeypair = await GenerateSealingKey() 315 | await expect( 316 | counter1.getCounterPermitSealed({ 317 | ...signerPermission, 318 | sealingKey: `0x${adaKeypair.publicKey}`, 319 | }) 320 | ).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_RecipientSignature') 321 | }) 322 | }) 323 | 324 | // ================================= 325 | // EXTERNAL VALIDATORS 326 | // ================================= 327 | 328 | describe('External validators', async () => { 329 | it('revokable via id', async () => { 330 | const { bob, ada, counter1, permissionRevokableValidator, permissionRevokableValidatorAddress } = await permissionedV2Fixture() 331 | 332 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 333 | 334 | // BOB: Create revokableId 335 | await permissionRevokableValidator.connect(bob).createRevokableId() 336 | const revokableId = 1 337 | 338 | // BOB: Create self and sharable permits with validator 339 | const bobPermitSelf = await generatePermitV2( 340 | { 341 | issuer: bob.address, 342 | projects: ['COUNTER'], 343 | validatorId: revokableId, 344 | validatorContract: permissionRevokableValidatorAddress, 345 | }, 346 | ethers.provider, 347 | bob 348 | ) 349 | const bobPermission = extractPermissionV2(bobPermitSelf) 350 | const bobPermitShared = await generatePermitV2( 351 | { 352 | issuer: bob.address, 353 | projects: ['COUNTER'], 354 | validatorId: revokableId, 355 | validatorContract: permissionRevokableValidatorAddress, 356 | recipient: ada.address, 357 | }, 358 | ethers.provider, 359 | bob 360 | ) 361 | 362 | // ADA: Re-sign shared permit 363 | const adaPermit = await reSignSharedPermit(bobPermitShared, ethers.provider, ada) 364 | const adaPermission = extractPermissionV2(adaPermit) 365 | 366 | // Expect not to revert 367 | await counter1.connect(bob).getCounterPermitSealed(bobPermission) 368 | await counter1.connect(ada).getCounterPermitSealed(adaPermission) 369 | 370 | // BOB: Revoke id 371 | const tx = await permissionRevokableValidator.connect(bob).revokeId(revokableId) 372 | await tx.wait() 373 | 374 | // Expect reversion 375 | await expect(counter1.connect(bob).getCounterPermitSealed(bobPermission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_Disabled') 376 | await expect(counter1.connect(ada).getCounterPermitSealed(adaPermission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_Disabled') 377 | }) 378 | 379 | it('revokable via timestamp', async () => { 380 | const { bob, ada, counter1, permissionTimestampValidator, permissionTimestampValidatorAddress } = await permissionedV2Fixture() 381 | 382 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 383 | 384 | // BOB: Timestamp to use as validator id 385 | const timestamp = await getLastBlockTimestamp() 386 | 387 | // BOB: Create sharable permit with validator 388 | const bobPermitSelf = await generatePermitV2( 389 | { 390 | issuer: bob.address, 391 | projects: ['COUNTER'], 392 | validatorId: timestamp, 393 | validatorContract: permissionTimestampValidatorAddress, 394 | }, 395 | ethers.provider, 396 | bob 397 | ) 398 | const bobPermission = extractPermissionV2(bobPermitSelf) 399 | const bobPermitShared = await generatePermitV2( 400 | { 401 | issuer: bob.address, 402 | projects: ['COUNTER'], 403 | validatorId: timestamp, 404 | validatorContract: permissionTimestampValidatorAddress, 405 | recipient: ada.address, 406 | }, 407 | ethers.provider, 408 | bob 409 | ) 410 | 411 | // ADA: Re-sign shared permit 412 | const adaPermit = await reSignSharedPermit(bobPermitShared, ethers.provider, ada) 413 | const adaPermission = extractPermissionV2(adaPermit) 414 | 415 | // Expect not to revert 416 | await counter1.connect(bob).getCounterPermitSealed(bobPermission) 417 | await counter1.connect(ada).getCounterPermitSealed(adaPermission) 418 | 419 | // BOB: Update revoke before timestamp to revoke all existing permissions 420 | const tx = await permissionTimestampValidator.connect(bob).revokeExisting() 421 | await tx.wait() 422 | 423 | // Just passing the time 424 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 425 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 426 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 427 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 428 | 429 | // Expect reversion 430 | await expect(counter1.connect(bob).getCounterPermitSealed(bobPermission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_Disabled') 431 | await expect(counter1.connect(ada).getCounterPermitSealed(adaPermission)).to.be.revertedWithCustomError(counter1, 'PermissionInvalid_Disabled') 432 | }) 433 | }) 434 | 435 | // ================================= 436 | // MISC 437 | // ================================= 438 | 439 | describe('Misc', async () => { 440 | it('`msg.sender` irrelevant to returned data', async () => { 441 | const { signer, bob, ada, counter1, counter1Address } = await permissionedV2Fixture() 442 | 443 | await counter1.connect(bob).add(await fhenixjs.encrypt_uint32(5)) 444 | await counter1.connect(ada).add(await fhenixjs.encrypt_uint32(3)) 445 | 446 | // Bob permission - accesses bob's data 447 | const permit = await generatePermitV2( 448 | { 449 | issuer: bob.address, 450 | contracts: [counter1Address], 451 | }, 452 | ethers.provider, 453 | bob 454 | ) 455 | const permission = extractPermissionV2(permit) 456 | 457 | const sealedNoSigner = await counter1.getCounterPermitSealed(permission) 458 | expect(permit.sealingPair.unseal(sealedNoSigner)).to.eq(5, "No signer - Returns Bob's data") 459 | 460 | const sealedBobSigner = await counter1.connect(bob).getCounterPermitSealed(permission) 461 | expect(permit.sealingPair.unseal(sealedBobSigner)).to.eq(5, "Bob signer - Returns Bob's data") 462 | 463 | const sealedAdaSigner = await counter1.connect(ada).getCounterPermitSealed(permission) 464 | expect(permit.sealingPair.unseal(sealedAdaSigner)).to.eq(5, "Ada signer - Returns Bob's data") 465 | 466 | const sealedSignerSigner = await counter1.connect(signer).getCounterPermitSealed(permission) 467 | expect(permit.sealingPair.unseal(sealedSignerSigner)).to.eq(5, "Signer signer - Returns Bob's data") 468 | }) 469 | 470 | it('checkPermissionSatisfies', async () => { 471 | const { bob, counter1, counter1Address, counter2 } = await permissionedV2Fixture() 472 | 473 | const permitCounter1Address = await generatePermitV2( 474 | { 475 | issuer: bob.address, 476 | contracts: [counter1Address], 477 | }, 478 | ethers.provider, 479 | bob 480 | ) 481 | const permissionCounter1Address = extractPermissionV2(permitCounter1Address) 482 | 483 | expect(await counter1.checkPermissionSatisfies(permissionCounter1Address)).to.eq(true, 'Permission satisfies counter1 (`permission.contracts`)') 484 | expect(await counter2.checkPermissionSatisfies(permissionCounter1Address)).to.eq(false, 'Permission does not satisfy counter2 (`permission.contracts`)') 485 | 486 | const permitCounterProject = await generatePermitV2( 487 | { 488 | issuer: bob.address, 489 | projects: ['COUNTER'], 490 | }, 491 | ethers.provider, 492 | bob 493 | ) 494 | const permissionCounterProject = extractPermissionV2(permitCounterProject) 495 | 496 | expect(await counter1.checkPermissionSatisfies(permissionCounterProject)).to.eq(true, 'Permission satisfies counter1 (`permission.projects`)') 497 | expect(await counter2.checkPermissionSatisfies(permissionCounterProject)).to.eq(true, 'Permission satisfies counter2 (`permission.projects`)') 498 | 499 | const permitUniswapProject = await generatePermitV2( 500 | { 501 | issuer: bob.address, 502 | projects: ['UNISWAP'], 503 | }, 504 | ethers.provider, 505 | bob 506 | ) 507 | const permissionUniswapProject = extractPermissionV2(permitUniswapProject) 508 | 509 | expect(await counter1.checkPermissionSatisfies(permissionUniswapProject)).to.eq(false, 'Permission does not satisfy counter1 (`permission.projects`)') 510 | expect(await counter2.checkPermissionSatisfies(permissionUniswapProject)).to.eq(false, 'Permission does not satisfy counter2 (`permission.projects`)') 511 | }) 512 | }) 513 | }) 514 | -------------------------------------------------------------------------------- /test/permitV2/chain-utils.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | 3 | export async function getTokensFromFaucet(address: string) { 4 | if (hre.network.name === 'localfhenix') { 5 | if ((await hre.ethers.provider.getBalance(address)).toString() === '0') { 6 | await hre.fhenixjs.getFunds(address) 7 | } 8 | } 9 | } 10 | 11 | export const getLastBlockTimestamp = async () => { 12 | return (await hre.ethers.provider.getBlock('latest'))!.timestamp 13 | } 14 | 15 | export const createExpiration = async (duration: number) => { 16 | const timestamp = await getLastBlockTimestamp() 17 | return timestamp + duration 18 | } 19 | 20 | // = Fhenix = 21 | export const unsealMockFheOpsSealed = (sealed: string): bigint => { 22 | const byteArray = new Uint8Array(sealed.split('').map((c) => c.charCodeAt(0))) 23 | 24 | // Step 2: Convert the Uint8Array to BigInt 25 | let result = BigInt(0) 26 | for (let i = 0; i < byteArray.length; i++) { 27 | result = (result << BigInt(8)) + BigInt(byteArray[i]) // Shift and add each byte 28 | } 29 | 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /test/permitV2/permit-v2-utils.ts: -------------------------------------------------------------------------------- 1 | import { ZeroAddress } from 'ethers' 2 | import { SealingKey, PermitSigner, EIP712Domain, EIP712Types, SupportedProvider, GenerateSealingKey } from 'fhenixjs' 3 | import { PermissionV2Struct } from '../../types/contracts/access/PermissionedV2.sol/PermissionedV2' 4 | 5 | type FhenixJsPermitV2 = { 6 | issuer: string 7 | expiration: number 8 | contracts: string[] 9 | projects: string[] 10 | recipient: string 11 | validatorId: number 12 | validatorContract: string 13 | sealingPair: SealingKey 14 | issuerSignature: string 15 | recipientSignature: string 16 | } 17 | 18 | const sign = async (signer: PermitSigner, domain: EIP712Domain, types: EIP712Types, value: object): Promise => { 19 | if ('_signTypedData' in signer && typeof signer._signTypedData == 'function') { 20 | return await signer._signTypedData(domain, types, value) 21 | } else if ('signTypedData' in signer && typeof signer.signTypedData == 'function') { 22 | return await signer.signTypedData(domain, types, value) 23 | } 24 | throw new Error('Unsupported signer') 25 | } 26 | 27 | const determineRequestMethod = (provider: SupportedProvider): Function => { 28 | if ('request' in provider && typeof provider.request === 'function') { 29 | return (p: SupportedProvider, method: string, params?: unknown[]) => 30 | (p.request as ({ method, params }: { method: string; params?: unknown[] }) => Promise)({ method, params }) 31 | } else if ('send' in provider && typeof provider.send === 'function') { 32 | return (p: SupportedProvider, method: string, params?: unknown[]) => (p.send as (method: string, params?: unknown[]) => Promise)(method, params) 33 | } else { 34 | throw new Error("Received unsupported provider. 'send' or 'request' method not found") 35 | } 36 | } 37 | 38 | const determineRequestSigner = (provider: SupportedProvider): Function => { 39 | if ('getSigner' in provider && typeof provider.getSigner === 'function') { 40 | return (p: SupportedProvider) => (p.getSigner as () => unknown)() 41 | } else { 42 | throw new Error('The supplied provider cannot get a signer') 43 | } 44 | } 45 | 46 | export const generatePermitV2 = async ( 47 | options: { 48 | issuer: string 49 | contracts?: string[] 50 | projects?: string[] 51 | expiration?: number 52 | recipient?: string 53 | validatorId?: number 54 | validatorContract?: string 55 | }, 56 | provider: SupportedProvider, 57 | customSigner?: PermitSigner 58 | ): Promise => { 59 | const { 60 | issuer, 61 | contracts = [], 62 | projects = [], 63 | expiration = 1000000000000, 64 | recipient = ZeroAddress, 65 | validatorId = 0, 66 | validatorContract = ZeroAddress, 67 | } = options 68 | 69 | const isSharing = recipient !== ZeroAddress 70 | 71 | if (!provider) { 72 | throw new Error('Provider is undefined') 73 | } 74 | 75 | const requestMethod = determineRequestMethod(provider) 76 | 77 | let signer: PermitSigner 78 | if (!customSigner) { 79 | const getSigner = determineRequestSigner(provider) 80 | signer = await getSigner(provider) 81 | } else { 82 | signer = customSigner 83 | } 84 | 85 | const chainId = await requestMethod(provider, 'eth_chainId', []) 86 | 87 | const keypair = await GenerateSealingKey() 88 | 89 | let types: EIP712Types = { 90 | PermissionedV2IssuerSelf: [ 91 | { name: 'issuer', type: 'address' }, 92 | { name: 'expiration', type: 'uint64' }, 93 | { name: 'contracts', type: 'address[]' }, 94 | { name: 'projects', type: 'string[]' }, 95 | { name: 'recipient', type: 'address' }, 96 | { name: 'validatorId', type: 'uint256' }, 97 | { name: 'validatorContract', type: 'address' }, 98 | { name: 'sealingKey', type: 'bytes32' }, 99 | ], 100 | } 101 | let message: object = { 102 | issuer, 103 | sealingKey: `0x${keypair.publicKey}`, 104 | expiration: expiration.toString(), 105 | contracts, 106 | projects, 107 | recipient, 108 | validatorId, 109 | validatorContract, 110 | } 111 | 112 | if (isSharing) { 113 | types = { 114 | PermissionedV2IssuerShared: [ 115 | { name: 'issuer', type: 'address' }, 116 | { name: 'expiration', type: 'uint64' }, 117 | { name: 'contracts', type: 'address[]' }, 118 | { name: 'projects', type: 'string[]' }, 119 | { name: 'recipient', type: 'address' }, 120 | { name: 'validatorId', type: 'uint256' }, 121 | { name: 'validatorContract', type: 'address' }, 122 | ], 123 | } 124 | message = { 125 | issuer, 126 | expiration: expiration.toString(), 127 | contracts, 128 | projects, 129 | recipient, 130 | validatorId, 131 | validatorContract, 132 | } 133 | } 134 | 135 | const msgSig = await sign( 136 | signer, 137 | // Domain 138 | { 139 | name: 'Fhenix Permission v2.0.0', 140 | version: 'v2.0.0', 141 | chainId, 142 | verifyingContract: ZeroAddress, 143 | }, 144 | types, 145 | message 146 | ) 147 | 148 | const permit: FhenixJsPermitV2 = { 149 | issuer, 150 | expiration, 151 | sealingPair: keypair, 152 | contracts, 153 | projects, 154 | recipient: recipient, 155 | validatorId, 156 | validatorContract, 157 | issuerSignature: msgSig, 158 | recipientSignature: '0x', 159 | } 160 | 161 | return permit 162 | } 163 | 164 | export const reSignSharedPermit = async ( 165 | sharedPermit: FhenixJsPermitV2, 166 | provider: SupportedProvider, 167 | customSigner?: PermitSigner 168 | ): Promise => { 169 | if (!provider) { 170 | throw new Error('Provider is undefined') 171 | } 172 | 173 | const requestMethod = determineRequestMethod(provider) 174 | 175 | let signer: PermitSigner 176 | if (!customSigner) { 177 | const getSigner = determineRequestSigner(provider) 178 | signer = await getSigner(provider) 179 | } else { 180 | signer = customSigner 181 | } 182 | 183 | const chainId = await requestMethod(provider, 'eth_chainId', []) 184 | 185 | const keypair = await GenerateSealingKey() 186 | 187 | const recipientSignature = await sign( 188 | signer, 189 | // Domain 190 | { 191 | name: 'Fhenix Permission v2.0.0', 192 | version: 'v2.0.0', 193 | chainId, 194 | verifyingContract: ZeroAddress, 195 | }, 196 | // Types 197 | { 198 | PermissionedV2Recipient: [ 199 | { name: 'sealingKey', type: 'bytes32' }, 200 | { name: 'issuerSignature', type: 'bytes' }, 201 | ], 202 | }, 203 | // Message 204 | { 205 | sealingKey: `0x${keypair.publicKey}`, 206 | issuerSignature: sharedPermit.issuerSignature, 207 | } 208 | ) 209 | 210 | const permit: FhenixJsPermitV2 = { 211 | ...sharedPermit, 212 | sealingPair: keypair, 213 | recipientSignature, 214 | } 215 | 216 | return permit 217 | } 218 | 219 | export const extractPermissionV2 = ({ sealingPair, ...permission }: FhenixJsPermitV2): PermissionV2Struct => { 220 | return { 221 | ...permission, 222 | sealingKey: `0x${sealingPair.publicKey}`, 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["es2020"], 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "noImplicitAny": true, 13 | "removeComments": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "es2020" 18 | }, 19 | "exclude": ["node_modules"], 20 | "files": ["./hardhat.config.ts"], 21 | "include": ["contracts/**/*", "scripts/**/*", "test/**/*", "types/"] 22 | } 23 | --------------------------------------------------------------------------------