From b4dba1f64ea860b6e104590777e40e7581f69691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joseph-Andr=C3=A9=20Turk?= Date: Tue, 23 Apr 2024 17:29:00 +0200 Subject: [PATCH] chore: updated to fhevm 0.4.0 --- .env.example | 3 + contracts/asyncDecrypt/TestAsyncDecrypt.sol | 166 ++++++++ deploy/deploy.ts | 18 - hardhat.config.ts | 48 ++- launch-fhevm.sh | 22 ++ package.json | 12 +- pnpm-lock.yaml | 46 ++- tasks/deployERC20.ts | 10 - tasks/mint.ts | 16 - tasks/taskOracleRelayer.ts | 145 +++++++ test/asyncDecrypt.ts | 408 ++++++++++++++++++++ test/encryptedERC20/EncryptedERC20.ts | 4 +- test/generated.ts | 2 - test/instance.ts | 11 +- test/oracleDecrypt/testAsyncDecrypt.ts | 234 +++++++++++ 15 files changed, 1058 insertions(+), 87 deletions(-) create mode 100644 contracts/asyncDecrypt/TestAsyncDecrypt.sol delete mode 100644 deploy/deploy.ts create mode 100755 launch-fhevm.sh delete mode 100644 tasks/deployERC20.ts delete mode 100644 tasks/mint.ts create mode 100644 tasks/taskOracleRelayer.ts create mode 100644 test/asyncDecrypt.ts delete mode 100644 test/generated.ts create mode 100644 test/oracleDecrypt/testAsyncDecrypt.ts diff --git a/.env.example b/.env.example index de32d86..dbba210 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ export INFURA_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" export MNEMONIC="adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer" +export PRIVATE_KEY_ORACLE_DEPLOYER="717fd99986df414889fd8b51069d4f90a50af72e542c58ee065f5883779099c6" +export PRIVATE_KEY_ORACLE_OWNER="717fd99986df414889fd8b51069d4f90a50af72e542c58ee065f5883779099c6" +export PRIVATE_KEY_ORACLE_RELAYER="7ec931411ad75a7c201469a385d6f18a325d4923f9f213bd882bbea87e160b67" # Block explorer API keys export ARBISCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" diff --git a/contracts/asyncDecrypt/TestAsyncDecrypt.sol b/contracts/asyncDecrypt/TestAsyncDecrypt.sol new file mode 100644 index 0000000..6bb8232 --- /dev/null +++ b/contracts/asyncDecrypt/TestAsyncDecrypt.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.20; + +import "fhevm/lib/TFHE.sol"; +import "fhevm/oracle/OracleCaller.sol"; + +contract TestAsyncDecrypt is OracleCaller { + ebool xBool; + euint4 xUint4; + euint8 xUint8; + euint16 xUint16; + euint32 xUint32; + euint64 xUint64; + eaddress xAddress; + + bool public yBool; + uint8 public yUint4; + uint8 public yUint8; + uint16 public yUint16; + uint32 public yUint32; + uint64 public yUint64; + address public yAddress; + + constructor() { + xBool = TFHE.asEbool(true); + xUint4 = TFHE.asEuint4(4); + xUint8 = TFHE.asEuint8(42); + xUint16 = TFHE.asEuint16(16); + xUint32 = TFHE.asEuint32(32); + xUint64 = TFHE.asEuint64(64); + xAddress = TFHE.asEaddress(0x8ba1f109551bD432803012645Ac136ddd64DBA72); + } + + function requestBoolAboveDelay() public { + // should revert + ebool[] memory cts = new ebool[](1); + cts[0] = xBool; + Oracle.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 2 days); + } + + function requestBool() public { + ebool[] memory cts = new ebool[](1); + cts[0] = xBool; + Oracle.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100); + } + + function requestFakeBool() public { + ebool[] memory cts = new ebool[](1); + cts[0] = ebool.wrap(42); + Oracle.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackBool(uint256 /*requestID*/, bool decryptedInput) public onlyOracle returns (bool) { + yBool = decryptedInput; + return yBool; + } + + function requestUint4() public { + euint4[] memory cts = new euint4[](1); + cts[0] = xUint4; + Oracle.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100); + } + + function requestFakeUint4() public { + euint4[] memory cts = new euint4[](1); + cts[0] = euint4.wrap(42); + Oracle.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackUint4(uint256 /*requestID*/, uint8 decryptedInput) public onlyOracle returns (uint8) { + yUint4 = decryptedInput; + return decryptedInput; + } + + function requestUint8() public { + euint8[] memory cts = new euint8[](1); + cts[0] = xUint8; + Oracle.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100); + } + + function requestFakeUint8() public { + euint8[] memory cts = new euint8[](1); + cts[0] = euint8.wrap(42); + Oracle.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackUint8(uint256 /*requestID*/, uint8 decryptedInput) public onlyOracle returns (uint8) { + yUint8 = decryptedInput; + return decryptedInput; + } + + function requestUint16() public { + euint16[] memory cts = new euint16[](1); + cts[0] = xUint16; + Oracle.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100); + } + + function requestFakeUint16() public { + euint16[] memory cts = new euint16[](1); + cts[0] = euint16.wrap(42); + Oracle.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackUint16(uint256 /*requestID*/, uint16 decryptedInput) public onlyOracle returns (uint16) { + yUint16 = decryptedInput; + return decryptedInput; + } + + function requestUint32(uint32 input1, uint32 input2) public { + euint32[] memory cts = new euint32[](1); + cts[0] = xUint32; + uint256 requestID = Oracle.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100); + addParamsUint(requestID, input1); + addParamsUint(requestID, input2); + } + + function requestFakeUint32() public { + euint32[] memory cts = new euint32[](1); + cts[0] = euint32.wrap(42); + Oracle.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackUint32(uint256 requestID, uint32 decryptedInput) public onlyOracle returns (uint32) { + uint256[] memory params = getParamsUint(requestID); + unchecked { + uint32 result = uint32(params[0]) + uint32(params[1]) + decryptedInput; + yUint32 = result; + return result; + } + } + + function requestUint64() public { + euint64[] memory cts = new euint64[](1); + cts[0] = xUint64; + Oracle.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100); + } + + function requestFakeUint64() public { + euint64[] memory cts = new euint64[](1); + cts[0] = euint64.wrap(42); + Oracle.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackUint64(uint256 /*requestID*/, uint64 decryptedInput) public onlyOracle returns (uint64) { + yUint64 = decryptedInput; + return decryptedInput; + } + + function requestAddress() public { + eaddress[] memory cts = new eaddress[](1); + cts[0] = xAddress; + Oracle.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100); + } + + function requestFakeAddress() public { + eaddress[] memory cts = new eaddress[](1); + cts[0] = eaddress.wrap(42); + Oracle.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100); // this should revert because previous ebool is not honestly obtained + } + + function callbackAddress(uint256 /*requestID*/, address decryptedInput) public onlyOracle returns (address) { + yAddress = decryptedInput; + return decryptedInput; + } +} \ No newline at end of file diff --git a/deploy/deploy.ts b/deploy/deploy.ts deleted file mode 100644 index f67d887..0000000 --- a/deploy/deploy.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DeployFunction } from "hardhat-deploy/types"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { deployer } = await hre.getNamedAccounts(); - const { deploy } = hre.deployments; - - const deployed = await deploy("EncryptedERC20", { - from: deployer, - args: ["Naraggara", "NARA"], - log: true, - }); - - console.log(`EncryptedERC20 contract: `, deployed.address); -}; -export default func; -func.id = "deploy_encryptedERC20"; // id required to prevent reexecution -func.tags = ["EncryptedERC20"]; diff --git a/hardhat.config.ts b/hardhat.config.ts index dc106ea..8edd0d9 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,5 +1,5 @@ import "@nomicfoundation/hardhat-toolbox"; -import { config as dotenvConfig } from "dotenv"; +import dotenv from "dotenv"; import * as fs from "fs"; import "hardhat-deploy"; import "hardhat-preprocessor"; @@ -9,12 +9,10 @@ import { task } from "hardhat/config"; import type { NetworkUserConfig } from "hardhat/types"; import { resolve } from "path"; import * as path from "path"; -import "solidity-docgen"; import "./tasks/accounts"; -import "./tasks/deployERC20"; import "./tasks/getEthereumAddress"; -import "./tasks/mint"; +import "./tasks/taskOracleRelayer"; function getAllSolidityFiles(dir: string, fileList: string[] = []): string[] { fs.readdirSync(dir).forEach((file) => { @@ -48,7 +46,7 @@ task("coverage-mock", "Run coverage after running pre-process task").setAction(a }); const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; -dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); +dotenv.config({ path: resolve(__dirname, dotenvConfigPath) }); const mnemonic: string | undefined = process.env.MNEMONIC; if (!mnemonic) { @@ -99,17 +97,47 @@ function getChainConfig(chain: keyof typeof chainIds): NetworkUserConfig { }; } +task("test", async (taskArgs, hre, runSuper) => { + // Run modified test task + + if (network === "hardhat") { + // in fhevm mode all this block is done when launching the node via `pnmp fhevm:start` + const privKeyDeployer = process.env.PRIVATE_KEY_ORACLE_DEPLOYER; + const privKeyOwner = process.env.PRIVATE_KEY_ORACLE_OWNER; + const privKeyRelayer = process.env.PRIVATE_KEY_ORACLE_RELAYER; + const deployerAddress = new hre.ethers.Wallet(privKeyDeployer!).address; + const ownerAddress = new hre.ethers.Wallet(privKeyOwner!).address; + const relayerAddress = new hre.ethers.Wallet(privKeyRelayer!).address; + + await hre.run("task:computePredeployAddress", { privateKey: privKeyDeployer }); + + const bal = "0x1000000000000000000000000000000000000000"; + const p1 = hre.network.provider.send("hardhat_setBalance", [deployerAddress, bal]); + const p2 = hre.network.provider.send("hardhat_setBalance", [ownerAddress, bal]); + const p3 = hre.network.provider.send("hardhat_setBalance", [relayerAddress, bal]); + await Promise.all([p1, p2, p3]); + await hre.run("compile"); + await hre.run("task:deployOracle", { privateKey: privKeyDeployer, ownerAddress: ownerAddress }); + + const parsedEnv = dotenv.parse(fs.readFileSync("node_modules/fhevm/oracle/.env.oracle")); + const oraclePredeployAddress = parsedEnv.ORACLE_CONTRACT_PREDEPLOY_ADDRESS; + + await hre.run("task:addRelayer", { + privateKey: privKeyOwner, + oracleAddress: oraclePredeployAddress, + relayerAddress: relayerAddress, + }); + } + + await runSuper(); +}); + const config: HardhatUserConfig = { - docgen: { - pages: "files", - }, preprocess: { eachLine: () => ({ transform: (line: string) => { if (network === "hardhat") { - // checks if HARDHAT_NETWORK env variable is set to "hardhat" to use the remapping for the mocked version of TFHE.sol if (line.match(/".*.sol";$/)) { - // match all lines with `".sol";` for (const [from, to] of getRemappings()) { if (line.includes(from)) { line = line.replace(from, to); diff --git a/launch-fhevm.sh b/launch-fhevm.sh new file mode 100755 index 0000000..ba76e84 --- /dev/null +++ b/launch-fhevm.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +PRIVATE_KEY_ORACLE_DEPLOYER=$(grep PRIVATE_KEY_ORACLE_DEPLOYER .env | cut -d '"' -f 2) + +npx hardhat task:computePredeployAddress --private-key "$PRIVATE_KEY_ORACLE_DEPLOYER" + +PRIVATE_KEY_ORACLE_RELAYER=$(grep PRIVATE_KEY_ORACLE_RELAYER .env | cut -d '"' -f 2) + +ORACLE_CONTRACT_PREDEPLOY_ADDRESS=$(grep ORACLE_CONTRACT_PREDEPLOY_ADDRESS node_modules/fhevm/oracle/.env.oracle | cut -d '=' -f2) + +docker run -d -i -p 8545:8545 --rm --name fhevm \ + -e PRIVATE_KEY_ORACLE_RELAYER="$PRIVATE_KEY_ORACLE_RELAYER" \ + -e ORACLE_CONTRACT_PREDEPLOY_ADDRESS="$ORACLE_CONTRACT_PREDEPLOY_ADDRESS" \ + ghcr.io/zama-ai/ethermint-dev-node:v0.4.2 + +sleep 10 + +npx hardhat compile + +npx hardhat task:launchFhevm + +docker attach fhevm \ No newline at end of file diff --git a/package.json b/package.json index 2afa4da..516bbde 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^8.10.0", "ethers": "^6.11.1", - "fhevm": "0.4.0-5", - "fhevmjs": "0.4.0-7", + "fhevm": "^0.4.0", + "fhevmjs": "^0.4.0-7", "fs-extra": "^10.1.0", - "hardhat": "^2.21.0", + "hardhat": "2.21.0", "hardhat-deploy": "^0.11.45", "hardhat-gas-reporter": "^1.0.10", "hardhat-preprocessor": "^0.1.5", @@ -42,7 +42,7 @@ "rimraf": "^4.4.1", "solhint": "^3.6.2", "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.8.11", + "solidity-coverage": "0.8.6", "solidity-docgen": "0.6.0-beta.36", "ts-generator": "^0.1.1", "ts-node": "^10.9.2", @@ -81,10 +81,8 @@ "test:mock": "HARDHAT_NETWORK=hardhat npx hardhat test --network hardhat", "coverage:mock": "HARDHAT_NETWORK=hardhat npx hardhat coverage-mock --network hardhat", "task:getEthereumAddress": "hardhat task:getEthereumAddress", - "task:mint": "hardhat task:mint", - "task:deployERC20": "hardhat task:deployERC20", "task:accounts": "hardhat task:accounts", - "fhevm:start": "docker run -i -p 8545:8545 -p 8546:8546 --rm --name fhevm ghcr.io/zama-ai/ethermint-dev-node:v0.3.0-5", + "fhevm:start": "./launch-fhevm.sh", "fhevm:stop": "docker rm -f fhevm", "fhevm:restart": "fhevm:stop && fhevm:start", "fhevm:faucet": "npm run fhevm:faucet:alice && sleep 5 && npm run fhevm:faucet:bob && sleep 5 && npm run fhevm:faucet:carol && sleep 5 && npm run fhevm:faucet:dave && sleep 5 && npm run fhevm:faucet:eve", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe2bf94..b7fc87d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,7 +21,7 @@ devDependencies: version: 1.0.10(hardhat@2.21.0) '@nomicfoundation/hardhat-toolbox': specifier: ^3.0.0 - version: 3.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.6)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@1.1.1)(@typechain/ethers-v6@0.4.3)(@typechain/hardhat@8.0.3)(@types/chai@4.3.12)(@types/mocha@10.0.6)(@types/node@18.19.23)(chai@4.4.1)(ethers@6.11.1)(hardhat-gas-reporter@1.0.10)(hardhat@2.21.0)(solidity-coverage@0.8.11)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.4.2) + version: 3.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.6)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@1.1.1)(@typechain/ethers-v6@0.4.3)(@typechain/hardhat@8.0.3)(@types/chai@4.3.12)(@types/mocha@10.0.6)(@types/node@18.19.23)(chai@4.4.1)(ethers@6.11.1)(hardhat-gas-reporter@1.0.10)(hardhat@2.21.0)(solidity-coverage@0.8.6)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.4.2) '@nomicfoundation/hardhat-verify': specifier: ^1.1.1 version: 1.1.1(hardhat@2.21.0) @@ -71,16 +71,16 @@ devDependencies: specifier: ^6.11.1 version: 6.11.1 fhevm: - specifier: 0.4.0-5 - version: 0.4.0-5(hardhat@2.21.0) + specifier: ^0.4.0 + version: 0.4.0(hardhat@2.21.0) fhevmjs: - specifier: 0.4.0-7 + specifier: ^0.4.0-7 version: 0.4.0-7 fs-extra: specifier: ^10.1.0 version: 10.1.0 hardhat: - specifier: ^2.21.0 + specifier: 2.21.0 version: 2.21.0(ts-node@10.9.2)(typescript@5.4.2) hardhat-deploy: specifier: ^0.11.45 @@ -113,8 +113,8 @@ devDependencies: specifier: ^0.0.5 version: 0.0.5(prettier-plugin-solidity@1.3.1)(prettier@2.8.8) solidity-coverage: - specifier: ^0.8.11 - version: 0.8.11(hardhat@2.21.0) + specifier: 0.8.6 + version: 0.8.6(hardhat@2.21.0) solidity-docgen: specifier: 0.6.0-beta.36 version: 0.6.0-beta.36(hardhat@2.21.0) @@ -944,7 +944,7 @@ packages: hardhat: 2.21.0(ts-node@10.9.2)(typescript@5.4.2) dev: true - /@nomicfoundation/hardhat-toolbox@3.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.6)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@1.1.1)(@typechain/ethers-v6@0.4.3)(@typechain/hardhat@8.0.3)(@types/chai@4.3.12)(@types/mocha@10.0.6)(@types/node@18.19.23)(chai@4.4.1)(ethers@6.11.1)(hardhat-gas-reporter@1.0.10)(hardhat@2.21.0)(solidity-coverage@0.8.11)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.4.2): + /@nomicfoundation/hardhat-toolbox@3.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.6)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@1.1.1)(@typechain/ethers-v6@0.4.3)(@typechain/hardhat@8.0.3)(@types/chai@4.3.12)(@types/mocha@10.0.6)(@types/node@18.19.23)(chai@4.4.1)(ethers@6.11.1)(hardhat-gas-reporter@1.0.10)(hardhat@2.21.0)(solidity-coverage@0.8.6)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.4.2): resolution: {integrity: sha512-MsteDXd0UagMksqm9KvcFG6gNKYNa3GGNCy73iQ6bEasEgg2v8Qjl6XA5hjs8o5UD5A3153B6W2BIVJ8SxYUtA==} peerDependencies: '@nomicfoundation/hardhat-chai-matchers': ^2.0.0 @@ -978,7 +978,7 @@ packages: ethers: 6.11.1 hardhat: 2.21.0(ts-node@10.9.2)(typescript@5.4.2) hardhat-gas-reporter: 1.0.10(hardhat@2.21.0) - solidity-coverage: 0.8.11(hardhat@2.21.0) + solidity-coverage: 0.8.6(hardhat@2.21.0) ts-node: 10.9.2(@types/node@18.19.23)(typescript@5.4.2) typechain: 8.3.2(typescript@5.4.2) typescript: 5.4.2 @@ -1575,6 +1575,11 @@ packages: hasBin: true dev: true + /address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + dev: true + /adm-zip@0.4.16: resolution: {integrity: sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==} engines: {node: '>=0.3.0'} @@ -2321,6 +2326,16 @@ packages: engines: {node: '>= 0.8'} dev: true + /detect-port@1.5.1: + resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==} + hasBin: true + dependencies: + address: 1.2.2 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -2861,8 +2876,8 @@ packages: reusify: 1.0.4 dev: true - /fhevm@0.4.0-5(hardhat@2.21.0): - resolution: {integrity: sha512-P2qmcj1a+9XGXFhoIxqRSNlU9ZOmkSnNe7bHAdJbXSbLnz17I3Jl4y87KSb2eYvJ6WgesDlDpJTKtmHcaqN9uA==} + /fhevm@0.4.0(hardhat@2.21.0): + resolution: {integrity: sha512-UuOYMgAodOoGX69G6EXzb7QRes80KeUzDOhRNEqXh7l4npC/0JZXVNoqKwSRe0hGQJE6Qwx+FforPhs5rPzXtg==} dependencies: '@openzeppelin/contracts': 5.0.2 hardhat-preprocessor: 0.1.5(hardhat@2.21.0) @@ -4576,7 +4591,7 @@ packages: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true dependencies: - glob: 7.2.0 + glob: 7.2.3 dev: true /rimraf@3.0.2: @@ -4860,8 +4875,8 @@ packages: resolution: {integrity: sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==} dev: true - /solidity-coverage@0.8.11(hardhat@2.21.0): - resolution: {integrity: sha512-yy0Yk+olovBbXn0Me8BWULmmv7A69ZKkP5aTOJGOO8u61Tu2zS989erfjtFlUjDnfWtxRAVkd8BsQD704yLWHw==} + /solidity-coverage@0.8.6(hardhat@2.21.0): + resolution: {integrity: sha512-vV03mA/0nNMskOdVwNarUcqk0N/aYdelxAbf6RZ5l84FcYHbqDTr2JXyeYMp4bT48qHtAQjnKrygW1FrECyWNw==} hasBin: true peerDependencies: hardhat: ^2.11.0 @@ -4870,6 +4885,7 @@ packages: '@solidity-parser/parser': 0.18.0 chalk: 2.4.2 death: 1.1.0 + detect-port: 1.5.1 difflib: 0.2.4 fs-extra: 8.1.0 ghost-testrpc: 0.0.2 @@ -4886,6 +4902,8 @@ packages: semver: 7.6.0 shelljs: 0.8.5 web3-utils: 1.10.4 + transitivePeerDependencies: + - supports-color dev: true /solidity-docgen@0.6.0-beta.36(hardhat@2.21.0): diff --git a/tasks/deployERC20.ts b/tasks/deployERC20.ts deleted file mode 100644 index c1a1344..0000000 --- a/tasks/deployERC20.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { task } from "hardhat/config"; -import type { TaskArguments } from "hardhat/types"; - -task("task:deployERC20").setAction(async function (taskArguments: TaskArguments, { ethers }) { - const signers = await ethers.getSigners(); - const erc20Factory = await ethers.getContractFactory("EncryptedERC20"); - const encryptedERC20 = await erc20Factory.connect(signers[0]).deploy("Naraggara", "NARA"); // City of Zama's battle); - await encryptedERC20.waitForDeployment(); - console.log("EncryptedERC20 deployed to: ", await encryptedERC20.getAddress()); -}); diff --git a/tasks/mint.ts b/tasks/mint.ts deleted file mode 100644 index edc0564..0000000 --- a/tasks/mint.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { task } from "hardhat/config"; -import type { TaskArguments } from "hardhat/types"; - -task("task:mint") - .addParam("mint", "Tokens to mint") - .setAction(async function (taskArguments: TaskArguments, hre) { - const { ethers, deployments } = hre; - const signers = await ethers.getSigners(); - const EncryptedERC20 = await deployments.get("EncryptedERC20"); - - const encryptedERC20 = await ethers.getContractAt("EncryptedERC20", EncryptedERC20.address); - - await encryptedERC20.connect(signers[0]).mint(+taskArguments.mint); - - console.log("Mint done: ", taskArguments.mint); - }); diff --git a/tasks/taskOracleRelayer.ts b/tasks/taskOracleRelayer.ts new file mode 100644 index 0000000..ea199f6 --- /dev/null +++ b/tasks/taskOracleRelayer.ts @@ -0,0 +1,145 @@ +import { exec as oldExec } from "child_process"; +import dotenv from "dotenv"; +import fs from "fs"; +import { task } from "hardhat/config"; +import type { TaskArguments } from "hardhat/types"; +import path from "path"; +import { promisify } from "util"; + +const exec = promisify(oldExec); + +task("task:deployOracle") + .addParam("privateKey", "The deployer private key") + .addParam("ownerAddress", "The owner address") + .setAction(async function (taskArguments: TaskArguments, { ethers }) { + const deployer = new ethers.Wallet(taskArguments.privateKey).connect(ethers.provider); + const oracleFactory = await ethers.getContractFactory("OraclePredeploy"); + const oracle = await oracleFactory.connect(deployer).deploy(taskArguments.ownerAddress); + await oracle.waitForDeployment(); + const oraclePredeployAddress = await oracle.getAddress(); + const envConfig = dotenv.parse(fs.readFileSync("node_modules/fhevm/oracle/.env.oracle")); + if (oraclePredeployAddress !== envConfig.ORACLE_CONTRACT_PREDEPLOY_ADDRESS) { + throw new Error( + `The nonce of the deployer account is not null. Please use another deployer private key or relaunch a clean instance of the fhEVM`, + ); + } + console.log("OraclePredeploy was deployed at address: ", oraclePredeployAddress); + }); + +const getCoin = async (address: string) => { + const containerName = process.env["TEST_CONTAINER_NAME"] || "fhevm"; + const response = await exec(`docker exec -i ${containerName} faucet ${address} | grep height`); + const res = JSON.parse(response.stdout); + if (res.raw_log.match("account sequence mismatch")) await getCoin(address); +}; + +task("task:computePredeployAddress") + .addParam("privateKey", "The deployer private key") + .setAction(async function (taskArguments: TaskArguments, { ethers }) { + const deployerAddress = new ethers.Wallet(taskArguments.privateKey).address; + const oraclePredeployAddressPrecomputed = ethers.getCreateAddress({ + from: deployerAddress, + nonce: 0, // deployer is supposed to have nonce 0 when deploying OraclePredeploy + }); + const envFilePath = path.join(__dirname, "../node_modules/fhevm/oracle/.env.oracle"); + const content = `ORACLE_CONTRACT_PREDEPLOY_ADDRESS=${oraclePredeployAddressPrecomputed}\n`; + try { + fs.writeFileSync(envFilePath, content, { flag: "w" }); + console.log("oraclePredeployAddress written to node_modules/fhevm/oracle/.env.oracle successfully!"); + } catch (err) { + console.error("Failed to write to node_modules/fhevm/oracle/.env.oracle:", err); + } + + const solidityTemplate = `// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.20; + +address constant ORACLE_CONTRACT_PREDEPLOY_ADDRESS = ${oraclePredeployAddressPrecomputed}; + `; + + try { + fs.writeFileSync("./node_modules/fhevm/oracle/lib/PredeployAddress.sol", solidityTemplate, { + encoding: "utf8", + flag: "w", + }); + console.log("node_modules/fhevm/oracle/lib/PredeployAddress.sol file has been generated successfully."); + } catch (error) { + console.error("Failed to write node_modules/fhevm/oracle/lib/PredeployAddress.sol", error); + } + }); + +task("task:addRelayer") + .addParam("privateKey", "The owner private key") + .addParam("oracleAddress", "The OraclePredeploy address") + .addParam("relayerAddress", "The relayer address") + .setAction(async function (taskArguments: TaskArguments, { ethers }) { + const codeAtAddress = await ethers.provider.getCode(taskArguments.oracleAddress); + if (codeAtAddress === "0x") { + throw Error(`${taskArguments.oracleAddress} is not a smart contract`); + } + const owner = new ethers.Wallet(taskArguments.privateKey).connect(ethers.provider); + const oracle = await ethers.getContractAt("OraclePredeploy", taskArguments.oracleAddress, owner); + const tx = await oracle.addRelayer(taskArguments.relayerAddress); + const rcpt = await tx.wait(); + if (rcpt!.status === 1) { + console.log(`Account ${taskArguments.relayerAddress} was succesfully added as an oracle relayer`); + } else { + console.log("Adding relayer failed"); + } + }); + +task("task:removeRelayer") + .addParam("privateKey", "The owner private key") + .addParam("oracleAddress", "The OraclePredeploy address") + .addParam("relayerAddress", "The relayer address") + .setAction(async function (taskArguments: TaskArguments, { ethers }) { + const codeAtAddress = await ethers.provider.getCode(taskArguments.oracleAddress); + if (codeAtAddress === "0x") { + throw Error(`${taskArguments.oracleAddress} is not a smart contract`); + } + const owner = new ethers.Wallet(taskArguments.privateKey).connect(ethers.provider); + const oracle = await ethers.getContractAt("OraclePredeploy", taskArguments.oracleAddress, owner); + const tx = await oracle.removeRelayer(taskArguments.relayerAddress); + const rcpt = await tx.wait(); + if (rcpt!.status === 1) { + console.log(`Account ${taskArguments.relayerAddress} was succesfully removed from authorized relayers`); + } else { + console.log("Removing relayer failed"); + } + }); + +task("task:launchFhevm").setAction(async function (taskArgs, hre) { + const privKeyDeployer = process.env.PRIVATE_KEY_ORACLE_DEPLOYER; + const privKeyOwner = process.env.PRIVATE_KEY_ORACLE_OWNER; + const privKeyRelayer = process.env.PRIVATE_KEY_ORACLE_RELAYER; + const deployerAddress = new hre.ethers.Wallet(privKeyDeployer!).address; + const ownerAddress = new hre.ethers.Wallet(privKeyOwner!).address; + const relayerAddress = new hre.ethers.Wallet(privKeyRelayer!).address; + const p1 = getCoin(deployerAddress); + const p2 = getCoin(ownerAddress); + const p3 = getCoin(relayerAddress); + await Promise.all([p1, p2, p3]); + await new Promise((res) => setTimeout(res, 5000)); // wait 5 seconds + await hre.run("task:deployOracle", { privateKey: privKeyDeployer, ownerAddress: ownerAddress }); + + const parsedEnv = dotenv.parse(fs.readFileSync("node_modules/fhevm/oracle/.env.oracle")); + const oraclePredeployAddress = parsedEnv.ORACLE_CONTRACT_PREDEPLOY_ADDRESS; + + await hre.run("task:addRelayer", { + privateKey: privKeyOwner, + oracleAddress: oraclePredeployAddress, + relayerAddress: relayerAddress, + }); +}); + +task("task:getBalances").setAction(async function (taskArgs, hre) { + const privKeyDeployer = process.env.PRIVATE_KEY_ORACLE_DEPLOYER; + const privKeyOwner = process.env.PRIVATE_KEY_ORACLE_OWNER; + const privKeyRelayer = process.env.PRIVATE_KEY_ORACLE_RELAYER; + const deployerAddress = new hre.ethers.Wallet(privKeyDeployer!).address; + const ownerAddress = new hre.ethers.Wallet(privKeyOwner!).address; + const relayerAddress = new hre.ethers.Wallet(privKeyRelayer!).address; + console.log(await hre.ethers.provider.getBalance(deployerAddress)); + console.log(await hre.ethers.provider.getBalance(ownerAddress)); + console.log(await hre.ethers.provider.getBalance(relayerAddress)); +}); \ No newline at end of file diff --git a/test/asyncDecrypt.ts b/test/asyncDecrypt.ts new file mode 100644 index 0000000..bee5804 --- /dev/null +++ b/test/asyncDecrypt.ts @@ -0,0 +1,408 @@ +import dotenv from "dotenv"; +import fs from "fs"; +import { ethers } from "hardhat"; + +import { OraclePredeploy } from "../../types"; +import { waitNBlocks } from "./utils"; + +const network = process.env.HARDHAT_NETWORK; + +const currentTime = (): string => { + const now = new Date(); + return now.toLocaleTimeString("en-US", { hour12: true, hour: "numeric", minute: "numeric", second: "numeric" }); +}; + +const parsedEnv = dotenv.parse(fs.readFileSync("node_modules/fhevm/oracle/.env.oracle")); +const privKeyRelayer = process.env.PRIVATE_KEY_ORACLE_RELAYER; +const relayer = new ethers.Wallet(privKeyRelayer!, ethers.provider); + +const argEvents = + "(uint256 indexed requestID, uint256[] cts, address contractCaller, bytes4 callbackSelector, uint256 msgValue, uint256 maxTimestamp)"; +const ifaceEventDecryptionEBool = new ethers.Interface(["event EventDecryptionEBool" + argEvents]); +const ifaceEventDecryptionEUint4 = new ethers.Interface(["event EventDecryptionEUint4" + argEvents]); +const ifaceEventDecryptionEUint8 = new ethers.Interface(["event EventDecryptionEUint8" + argEvents]); +const ifaceEventDecryptionEUint16 = new ethers.Interface(["event EventDecryptionEUint16" + argEvents]); +const ifaceEventDecryptionEUint32 = new ethers.Interface(["event EventDecryptionEUint32" + argEvents]); +const ifaceEventDecryptionEUint64 = new ethers.Interface(["event EventDecryptionEUint64" + argEvents]); +const ifaceEventDecryptionEAddress = new ethers.Interface(["event EventDecryptionEAddress" + argEvents]); + +const argEvents2 = "(uint256 indexed requestID, bool success, bytes result)"; +const ifaceResultCallbackBool = new ethers.Interface(["event ResultCallbackBool" + argEvents2]); +const ifaceResultCallbackUint4 = new ethers.Interface(["event ResultCallbackUint4" + argEvents2]); +const ifaceResultCallbackUint8 = new ethers.Interface(["event ResultCallbackUint8" + argEvents2]); +const ifaceResultCallbackUint16 = new ethers.Interface(["event ResultCallbackUint16" + argEvents2]); +const ifaceResultCallbackUint32 = new ethers.Interface(["event ResultCallbackUint32" + argEvents2]); +const ifaceResultCallbackUint64 = new ethers.Interface(["event ResultCallbackUint64" + argEvents2]); +const ifaceResultcallbackAddress = new ethers.Interface(["event ResultcallbackAddress" + argEvents2]); + +let oracle: OraclePredeploy; +let firstBlockListening: number; + +export const asyncDecrypt = async (): Promise => { + firstBlockListening = await ethers.provider.getBlockNumber(); + // this function will emit logs for every request and fulfilment of a decryption + oracle = await ethers.getContractAt("OraclePredeploy", parsedEnv.ORACLE_CONTRACT_PREDEPLOY_ADDRESS); + oracle.on( + "EventDecryptionEBool", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Requested ebool decrypt on block ${blockNumber} (requestID ${requestID})`); + }, + ); + oracle.on("ResultCallbackBool", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled ebool decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + oracle.on( + "EventDecryptionEUint4", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Requested euint4 decrypt on block ${blockNumber} (requestID ${requestID})`); + }, + ); + oracle.on("ResultCallbackUint4", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled euint4 decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + oracle.on( + "EventDecryptionEUint8", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Requested euint8 decrypt on block ${blockNumber} (requestID ${requestID})`); + }, + ); + oracle.on("ResultCallbackUint8", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled euint8 decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + oracle.on( + "EventDecryptionEUint16", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log( + `${await currentTime()} - Requested euint16 decrypt on block ${blockNumber} (requestID ${requestID})`, + ); + }, + ); + oracle.on("ResultCallbackUint16", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled euint16 decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + oracle.on( + "EventDecryptionEUint32", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log( + `${await currentTime()} - Requested euint32 decrypt on block ${blockNumber} (requestID ${requestID})`, + ); + }, + ); + oracle.on("ResultCallbackUint32", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled euint32 decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + oracle.on( + "EventDecryptionEUint64", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log( + `${await currentTime()} - Requested euint64 decrypt on block ${blockNumber} (requestID ${requestID})`, + ); + }, + ); + oracle.on("ResultCallbackUint64", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled euint64 decrypt on block ${blockNumber} (requestID ${requestID})`); + }); + + oracle.on( + "EventDecryptionEAddress", + async (requestID, cts, contractCaller, callbackSelector, msgValue, maxTimestamp, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log( + `${await currentTime()} - Requested eaddress decrypt on block ${blockNumber} (requestID ${requestID})`, + ); + }, + ); + oracle.on("ResultcallbackAddress", async (requestID, success, result, eventData) => { + const blockNumber = eventData.log.blockNumber; + console.log(`${await currentTime()} - Fulfilled eaddress decrypt on block ${blockNumber} (requestID ${requestID})`); + }); +}; + +export const awaitAllDecryptionResults = async (): Promise => { + oracle = await ethers.getContractAt("OraclePredeploy", parsedEnv.ORACLE_CONTRACT_PREDEPLOY_ADDRESS); + await fulfillAllPastRequestsIds(network === "hardhat"); + firstBlockListening = await ethers.provider.getBlockNumber(); +}; + +const getAlreadyFulfilledDecryptions = async (): Promise<[bigint]> => { + let results = []; + const eventDecryptionResultBool = await oracle.filters.ResultCallbackBool().getTopicFilter(); + const filterDecryptionResultBool = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultBool, + }; + const pastResultsEbool = await ethers.provider.getLogs(filterDecryptionResultBool); + results = results.concat(pastResultsEbool.map((result) => ifaceResultCallbackBool.parseLog(result).args[0])); + + const eventDecryptionResultEUint4 = await oracle.filters.ResultCallbackUint4().getTopicFilter(); + const filterDecryptionResultUint4 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEUint4, + }; + const pastResultsEuint4 = await ethers.provider.getLogs(filterDecryptionResultUint4); + results = results.concat(pastResultsEuint4.map((result) => ifaceResultCallbackUint4.parseLog(result).args[0])); + + const eventDecryptionResultEUint8 = await oracle.filters.ResultCallbackUint8().getTopicFilter(); + const filterDecryptionResultUint8 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEUint8, + }; + const pastResultsEuint8 = await ethers.provider.getLogs(filterDecryptionResultUint8); + results = results.concat(pastResultsEuint8.map((result) => ifaceResultCallbackUint8.parseLog(result).args[0])); + + const eventDecryptionResultEUint16 = await oracle.filters.ResultCallbackUint16().getTopicFilter(); + const filterDecryptionResultUint16 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEUint16, + }; + const pastResultsEuint16 = await ethers.provider.getLogs(filterDecryptionResultUint16); + results = results.concat(pastResultsEuint16.map((result) => ifaceResultCallbackUint16.parseLog(result).args[0])); + + const eventDecryptionResultEUint32 = await oracle.filters.ResultCallbackUint32().getTopicFilter(); + const filterDecryptionResultUint32 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEUint32, + }; + const pastResultsEuint32 = await ethers.provider.getLogs(filterDecryptionResultUint32); + results = results.concat(pastResultsEuint32.map((result) => ifaceResultCallbackUint32.parseLog(result).args[0])); + + const eventDecryptionResultEUint64 = await oracle.filters.ResultCallbackUint64().getTopicFilter(); + const filterDecryptionResultUint64 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEUint64, + }; + const pastResultsEuint64 = await ethers.provider.getLogs(filterDecryptionResultUint64); + results = results.concat(pastResultsEuint64.map((result) => ifaceResultCallbackUint64.parseLog(result).args[0])); + + const eventDecryptionResultEAddress = await oracle.filters.ResultcallbackAddress().getTopicFilter(); + const filterDecryptionResultAddress = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionResultEAddress, + }; + const pastResultsEaddress = await ethers.provider.getLogs(filterDecryptionResultAddress); + results = results.concat(pastResultsEaddress.map((result) => ifaceResultcallbackAddress.parseLog(result).args[0])); + + return results; +}; + +const fulfillAllPastRequestsIds = async (mocked: boolean) => { + const eventDecryptionEBool = await oracle.filters.EventDecryptionEBool().getTopicFilter(); + const results = await getAlreadyFulfilledDecryptions(); + const filterDecryptionEBool = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEBool, + }; + const pastRequestsEbool = await ethers.provider.getLogs(filterDecryptionEBool); + for (const request of pastRequestsEbool) { + const event = ifaceEventDecryptionEBool.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestBool( + requestID, + cts.map((value) => value === 1n), + { value: msgValue }, + ); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEUint4 = await oracle.filters.EventDecryptionEUint4().getTopicFilter(); + const filterDecryptionEUint4 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEUint4, + }; + const pastRequestsEUint4 = await ethers.provider.getLogs(filterDecryptionEUint4); + for (const request of pastRequestsEUint4) { + const event = ifaceEventDecryptionEUint4.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestUint4(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEUint8 = await oracle.filters.EventDecryptionEUint8().getTopicFilter(); + const filterDecryptionEUint8 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEUint8, + }; + const pastRequestsEUint8 = await ethers.provider.getLogs(filterDecryptionEUint8); + for (const request of pastRequestsEUint8) { + const event = ifaceEventDecryptionEUint8.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestUint8(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEUint16 = await oracle.filters.EventDecryptionEUint16().getTopicFilter(); + const filterDecryptionEUint16 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEUint16, + }; + const pastRequestsEUint16 = await ethers.provider.getLogs(filterDecryptionEUint16); + for (const request of pastRequestsEUint16) { + const event = ifaceEventDecryptionEUint16.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestUint16(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEUint32 = await oracle.filters.EventDecryptionEUint32().getTopicFilter(); + const filterDecryptionEUint32 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEUint32, + }; + const pastRequestsEUint32 = await ethers.provider.getLogs(filterDecryptionEUint32); + for (const request of pastRequestsEUint32) { + const event = ifaceEventDecryptionEUint32.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestUint32(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEUint64 = await oracle.filters.EventDecryptionEUint64().getTopicFilter(); + const filterDecryptionEUint64 = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEUint64, + }; + const pastRequestsEUint64 = await ethers.provider.getLogs(filterDecryptionEUint64); + for (const request of pastRequestsEUint64) { + const event = ifaceEventDecryptionEUint64.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestUint64(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } + + const eventDecryptionEAddress = await oracle.filters.EventDecryptionEAddress().getTopicFilter(); + const filterDecryptionEAddress = { + address: process.env.ORACLE_CONTRACT_PREDEPLOY_ADDRESS, + fromBlock: firstBlockListening, + toBlock: "latest", + topics: eventDecryptionEAddress, + }; + const pastRequestsEAddress = await ethers.provider.getLogs(filterDecryptionEAddress); + for (const request of pastRequestsEAddress) { + const event = ifaceEventDecryptionEAddress.parseLog(request); + const requestID = event.args[0]; + const cts = event.args[1]; + const msgValue = event.args[4]; + if (!results.includes(requestID)) { + // if request is not already fulfilled + if (mocked) { + // in mocked mode, we trigger the decryption fulfillment manually + const tx = await oracle.connect(relayer).fulfillRequestAddress(requestID, [...cts], { value: msgValue }); + await tx.wait(); + } else { + // in fhEVM mode we must wait until the oracle service relayer submits the decryption fulfillment tx + await waitNBlocks(1); + await fulfillAllPastRequestsIds(mocked); + } + } + } +}; diff --git a/test/encryptedERC20/EncryptedERC20.ts b/test/encryptedERC20/EncryptedERC20.ts index 4a2dd03..bd144a2 100644 --- a/test/encryptedERC20/EncryptedERC20.ts +++ b/test/encryptedERC20/EncryptedERC20.ts @@ -125,9 +125,7 @@ describe("EncryptedERC20", function () { expect(await this.erc20.balanceOfMe()).to.be.eq(1000n); } else { // fhevm node mode (real handle) - expect(await this.erc20.balanceOfMe()).to.be.eq( - 42886855740009186301312685209120323787138419884243836762205742602803093210845n, - ); + expect(await this.erc20.balanceOfMe()).to.be.gt(0); } }); diff --git a/test/generated.ts b/test/generated.ts deleted file mode 100644 index d8508aa..0000000 --- a/test/generated.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const FHE_LIB_ADDRESS = "0x000000000000000000000000000000000000005d"; -export const OPTIMISTIC_REQUIRES_ENABLED: boolean = true; diff --git a/test/instance.ts b/test/instance.ts index 4f1a98f..36f27ba 100644 --- a/test/instance.ts +++ b/test/instance.ts @@ -1,9 +1,8 @@ import { toBufferBE } from "bigint-buffer"; import { Signer } from "ethers"; -import fhevmjs, { FhevmInstance } from "fhevmjs"; +import fhevmjs, { FhevmInstance, getPublicKeyCallParams } from "fhevmjs"; import { ethers as hethers } from "hardhat"; -import { FHE_LIB_ADDRESS } from "./generated"; import type { Signers } from "./signers"; import { FhevmInstances } from "./types"; @@ -40,11 +39,7 @@ export const createInstance = async (contractAddress: string, account: Signer, e chainId = +network.chainId.toString(); // Need to be a number try { // Get blockchain public key - const ret = await provider.call({ - to: FHE_LIB_ADDRESS, - // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library - data: "0xd9d47bb001", - }); + const ret = await provider.call(getPublicKeyCallParams()); const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret); publicKey = decoded[0]; } catch (e) { @@ -60,7 +55,9 @@ export const createInstance = async (contractAddress: string, account: Signer, e instance.encrypt16 = createUintToUint8ArrayFunction(16); instance.encrypt32 = createUintToUint8ArrayFunction(32); instance.encrypt64 = createUintToUint8ArrayFunction(64); + instance.encryptAddress = createUintToUint8ArrayFunction(160); instance.decrypt = (_, hexadecimalString) => BigInt(hexadecimalString); + instance.decryptAddress = (_, hexadecimalString) => ethers.getAddress(hexadecimalString.slice(26, 66)); } await generatePublicKey(contractAddress, account, instance); diff --git a/test/oracleDecrypt/testAsyncDecrypt.ts b/test/oracleDecrypt/testAsyncDecrypt.ts new file mode 100644 index 0000000..9eb7435 --- /dev/null +++ b/test/oracleDecrypt/testAsyncDecrypt.ts @@ -0,0 +1,234 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; + +import { asyncDecrypt, awaitAllDecryptionResults } from "../asyncDecrypt"; +import { getSigners, initSigners } from "../signers"; + +describe("TestAsyncDecrypt", function () { + before(async function () { + await asyncDecrypt(); + await initSigners(3); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const contractFactory = await ethers.getContractFactory("TestAsyncDecrypt"); + this.contract = await contractFactory.connect(this.signers.alice).deploy(); + }); + + it("test async decrypt bool would fail if maxTimestamp is above 1 day", async function () { + if (network.name === "hardhat") { + // mocked mode + await expect(this.contract.connect(this.signers.carol).requestBoolAboveDelay()).to.be.revertedWith( + "maxTimestamp exceeded MAX_DELAY", + ); + } else { + // fhevm-mode + const tx = await this.contract.connect(this.signers.carol).requestBoolAboveDelay({ gasLimit: 1_000_000 }); + await expect(tx.wait()).to.throw; + } + }); + + it("test async decrypt bool", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestBool({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yBool(); + expect(y).to.equal(true); + }); + + it("test async decrypt FAKE bool", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeBool.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt uint4", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestUint4({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yUint4(); + expect(y).to.equal(4); + }); + + it("test async decrypt FAKE uint4", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeUint4.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt uint8", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestUint8({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yUint8(); + expect(y).to.equal(42); + }); + + it("test async decrypt FAKE uint8", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeUint8.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt uint16", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestUint16({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yUint16(); + expect(y).to.equal(16); + }); + + it("test async decrypt FAKE uint16", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeUint16.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt uint32", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestUint32(5, 15, { gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yUint32(); + expect(y).to.equal(52); // 5+15+32 + }); + + it("test async decrypt FAKE uint32", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeUint32.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt uint64", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestUint64({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yUint64(); + expect(y).to.equal(64); + }); + + it("test async decrypt FAKE uint64", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeUint64.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); + + it("test async decrypt address", async function () { + const tx2 = await this.contract.connect(this.signers.carol).requestAddress({ gasLimit: 500_000 }); + await tx2.wait(); + await awaitAllDecryptionResults(); + const y = await this.contract.yAddress(); + expect(y).to.equal("0x8ba1f109551bD432803012645Ac136ddd64DBA72"); + }); + + it("test async decrypt FAKE address", async function () { + if (network.name !== "hardhat") { + // only in fhevm mode + const txObject = await this.contract + .connect(this.signers.carol) + .requestFakeAddress.populateTransaction({ gasLimit: 5_000_000 }); + const tx = await this.signers.carol.sendTransaction(txObject); + let receipt = null; + let waitTime = 0; + while (receipt === null && waitTime < 15000) { + receipt = await ethers.provider.getTransactionReceipt(tx.hash); + if (receipt === null) { + console.log("Trying again to fetch txn receipt...."); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait for 5 seconds + waitTime += 5000; + } + } + expect(waitTime >= 15000).to.be.true; + } + }); +});