From a3ec81b8fa64282964c6faa3841f451a55a01110 Mon Sep 17 00:00:00 2001 From: Evi Nova <66773372+Tranquil-Flow@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:43:00 +1000 Subject: [PATCH] Fix/contract test fixes (#1490) * fix: remove outdated tests * fix: failing tests fixed and updated * fix: proper gitcommit entry with metadata * chore: yarn prettier --- common/src/utils/aadhaar/utils.ts | 2 +- .../IdentityVerificationHubImplV2.sol | 73 +-- .../IVcAndDiscloseCircuitVerifier.sol | 18 + .../Verifier_register_aadhaar.sol | 37 +- contracts/hardhat.config.ts | 2 +- contracts/tasks/upgrade/upgrade.ts | 23 +- contracts/tasks/upgrade/utils.ts | 19 + .../governance/FullUpgradeIntegration.test.ts | 481 ------------------ .../test/governance/GovernanceUpgrade.test.ts | 454 ----------------- .../governance/StorageLayoutUpgrade.test.ts | 267 ---------- .../test/governance/UpgradeSafety.test.ts | 229 --------- contracts/test/integration/endToEnd.test.ts | 207 ++++++-- .../test/integration/vcAndDisclose.test.ts | 75 ++- contracts/test/integration/verifyAll.test.ts | 7 +- contracts/test/unit/IdentityRegistry.test.ts | 20 +- contracts/test/v2/registerAadhaar.test.ts | 14 +- 16 files changed, 343 insertions(+), 1585 deletions(-) delete mode 100644 contracts/test/governance/FullUpgradeIntegration.test.ts delete mode 100644 contracts/test/governance/GovernanceUpgrade.test.ts delete mode 100644 contracts/test/governance/StorageLayoutUpgrade.test.ts delete mode 100644 contracts/test/governance/UpgradeSafety.test.ts diff --git a/common/src/utils/aadhaar/utils.ts b/common/src/utils/aadhaar/utils.ts index 994797439..f86a913ad 100644 --- a/common/src/utils/aadhaar/utils.ts +++ b/common/src/utils/aadhaar/utils.ts @@ -398,7 +398,7 @@ export async function getAadharRegistrationWindow() { } export function returnNewDateString(timestamp?: string): string { - const newDate = timestamp ? new Date(+timestamp) : new Date(); + const newDate = timestamp ? new Date(+timestamp * 1000) : new Date(); // Convert the UTC date to IST by adding 5 hours and 30 minutes const offsetHours = 5; diff --git a/contracts/contracts/IdentityVerificationHubImplV2.sol b/contracts/contracts/IdentityVerificationHubImplV2.sol index 57b1a5cd9..b2379c02f 100644 --- a/contracts/contracts/IdentityVerificationHubImplV2.sol +++ b/contracts/contracts/IdentityVerificationHubImplV2.sol @@ -705,15 +705,11 @@ contract IdentityVerificationHubImplV2 is ImplRoot { _performUserIdentifierCheck(userContextData, vcAndDiscloseProof, header.attestationId, indices); } - // Scope 2: Root and date checks + // Scope 2: Root, OFAC, and current date checks { _performRootCheck(header.attestationId, vcAndDiscloseProof, indices); _performOfacCheck(header.attestationId, vcAndDiscloseProof, indices); - if (header.attestationId == AttestationId.AADHAAR) { - _performNumericCurrentDateCheck(vcAndDiscloseProof, indices); - } else { - _performCurrentDateCheck(vcAndDiscloseProof, indices); - } + _performCurrentDateCheck(header.attestationId, vcAndDiscloseProof, indices); } // Scope 3: Groth16 proof verification @@ -1009,41 +1005,56 @@ contract IdentityVerificationHubImplV2 is ImplRoot { } /** - * @notice Performs current date validation + * @notice Performs current date validation with format-aware parsing + * @dev Handles three date formats: + * - E_PASSPORT/EU_ID_CARD: 6 ASCII chars (YYMMDD) + * - SELFRICA_ID_CARD: 8 ASCII digits (YYYYMMDD) + * - AADHAAR: 3 numeric signals (year, month, day) + * @param attestationId The attestation type to determine date format + * @param vcAndDiscloseProof The proof containing date information + * @param indices Circuit-specific indices for extracting date values */ function _performCurrentDateCheck( + bytes32 attestationId, GenericProofStruct memory vcAndDiscloseProof, CircuitConstantsV2.DiscloseIndices memory indices ) internal view { - uint256[6] memory dateNum; - for (uint256 i = 0; i < 6; i++) { - dateNum[i] = vcAndDiscloseProof.pubSignals[indices.currentDateIndex + i]; + uint256 currentTimestamp; + uint256 startIndex = indices.currentDateIndex; + + if (attestationId == AttestationId.E_PASSPORT || attestationId == AttestationId.EU_ID_CARD) { + // E_PASSPORT, EU_ID_CARD: 6 ASCII chars (YYMMDD) + uint256[6] memory dateNum; + unchecked { + for (uint256 i; i < 6; ++i) { + dateNum[i] = vcAndDiscloseProof.pubSignals[startIndex + i]; + } + } + currentTimestamp = Formatter.proofDateToUnixTimestamp(dateNum); + } else { + // AADHAAR: 3 numeric signals [year, month, day] + currentTimestamp = Formatter.proofDateToUnixTimestampNumeric( + [ + vcAndDiscloseProof.pubSignals[startIndex], + vcAndDiscloseProof.pubSignals[startIndex + 1], + vcAndDiscloseProof.pubSignals[startIndex + 2] + ] + ); } - uint256 currentTimestamp = Formatter.proofDateToUnixTimestamp(dateNum); - uint256 startOfDay = _getStartOfDayTimestamp(); - uint256 endOfDay = startOfDay + 1 days - 1; - - if (currentTimestamp < startOfDay - 1 days + 1 || currentTimestamp > endOfDay + 1 days) { - revert CurrentDateNotInValidRange(); - } + _validateDateInRange(currentTimestamp); } - function _performNumericCurrentDateCheck( - GenericProofStruct memory vcAndDiscloseProof, - CircuitConstantsV2.DiscloseIndices memory indices - ) internal view { - // date is going to be 2025, 12, 13 - uint256[3] memory dateNum; - dateNum[0] = vcAndDiscloseProof.pubSignals[indices.currentDateIndex]; - dateNum[1] = vcAndDiscloseProof.pubSignals[indices.currentDateIndex + 1]; - dateNum[2] = vcAndDiscloseProof.pubSignals[indices.currentDateIndex + 2]; + /** + * @notice Validates that a timestamp is within the acceptable range + * @param currentTimestamp The timestamp to validate + */ + function _validateDateInRange(uint256 currentTimestamp) internal view { + // Calculate the timestamp for the start of current date by subtracting the remainder of block.timestamp modulo 1 day + uint256 startOfDay = block.timestamp - (block.timestamp % 1 days); - uint256 currentTimestamp = Formatter.proofDateToUnixTimestampNumeric(dateNum); - uint256 startOfDay = _getStartOfDayTimestamp(); - uint256 endOfDay = startOfDay + 1 days - 1; - - if (currentTimestamp < startOfDay - 1 days + 1 || currentTimestamp > endOfDay + 1 days) { + // Check if timestamp is within range + if (currentTimestamp < startOfDay - 1 days + 1 || currentTimestamp > startOfDay + 1 days - 1) { revert CurrentDateNotInValidRange(); } } diff --git a/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol b/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol index 9a40c177c..030c032aa 100644 --- a/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol +++ b/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol @@ -49,3 +49,21 @@ interface IVcAndDiscloseAadhaarCircuitVerifier { uint256[19] calldata pubSignals ) external view returns (bool); } + +interface IVcAndDiscloseSelfricaCircuitVerifier { + /** + * @notice Verifies a given VC and Disclose zero-knowledge proof. + * @dev This function checks the validity of the provided proof parameters. + * @param a The 'a' component of the proof. + * @param b The 'b' component of the proof. + * @param c The 'c' component of the proof. + * @param pubSignals The public signals associated with the proof. + * @return A boolean value indicating whether the proof is valid (true) or not (false). + */ + function verifyProof( + uint256[2] calldata a, + uint256[2][2] calldata b, + uint256[2] calldata c, + uint256[30] calldata pubSignals + ) external view returns (bool); +} diff --git a/contracts/contracts/verifiers/register_aadhaar/Verifier_register_aadhaar.sol b/contracts/contracts/verifiers/register_aadhaar/Verifier_register_aadhaar.sol index 02c7a1d16..2bc795c48 100644 --- a/contracts/contracts/verifiers/register_aadhaar/Verifier_register_aadhaar.sol +++ b/contracts/contracts/verifiers/register_aadhaar/Verifier_register_aadhaar.sol @@ -22,17 +22,17 @@ pragma solidity >=0.7.0 <0.9.0; contract Verifier_register_aadhaar { // Scalar field size - uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Base field size - uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // Verification Key data - uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; - uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; - uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; - uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; - uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; - uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; @@ -42,7 +42,6 @@ contract Verifier_register_aadhaar { uint256 constant deltay1 = 18162968189172780580333095558539690618880186036957545736311404283407493778880; uint256 constant deltay2 = 7343682947937413219111184190299798376421430633278379635221606345245958931239; - uint256 constant IC0x = 18984838814932147425072354846429508676387686524229308734161716095463360490134; uint256 constant IC0y = 11220857659665071811279473081460089783437970319349511357818317231596300603739; @@ -58,14 +57,18 @@ contract Verifier_register_aadhaar { uint256 constant IC4x = 17894574390662839711557891944994831304061930223868407277717041388786423798517; uint256 constant IC4y = 10747262533817845366080322542335489573224940900925614580771284497946454436591; - // Memory data uint16 constant pVk = 0; uint16 constant pPairing = 128; uint16 constant pLastMem = 896; - function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[4] calldata _pubSignals) public view returns (bool) { + function verifyProof( + uint[2] calldata _pA, + uint[2][2] calldata _pB, + uint[2] calldata _pC, + uint[4] calldata _pubSignals + ) public view returns (bool) { assembly { function checkField(v) { if iszero(lt(v, r)) { @@ -117,7 +120,6 @@ contract Verifier_register_aadhaar { g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) - // -A mstore(_pPairing, calldataload(pA)) mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) @@ -142,7 +144,6 @@ contract Verifier_register_aadhaar { mstore(add(_pPairing, 384), mload(add(pMem, pVk))) mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) - // gamma2 mstore(add(_pPairing, 448), gammax1) mstore(add(_pPairing, 480), gammax2) @@ -159,7 +160,6 @@ contract Verifier_register_aadhaar { mstore(add(_pPairing, 704), deltay1) mstore(add(_pPairing, 736), deltay2) - let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) isOk := and(success, mload(_pPairing)) @@ -178,12 +178,11 @@ contract Verifier_register_aadhaar { checkField(calldataload(add(_pubSignals, 96))) - // Validate all evaluations let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) mstore(0, isValid) - return(0, 0x20) - } - } - } + return(0, 0x20) + } + } +} diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index b784faef3..390475265 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -22,7 +22,7 @@ const config: HardhatUserConfig = { evmVersion: "cancun", optimizer: { enabled: true, - runs: 100000, + runs: 200, }, }, }, diff --git a/contracts/tasks/upgrade/upgrade.ts b/contracts/tasks/upgrade/upgrade.ts index e3f586073..41741079d 100644 --- a/contracts/tasks/upgrade/upgrade.ts +++ b/contracts/tasks/upgrade/upgrade.ts @@ -29,6 +29,7 @@ import { readContractVersion, getContractFilePath, addVersion, + updateVersionGitCommit, getExplorerUrl, shortenAddress, createGitTag, @@ -628,7 +629,16 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" const committed = gitCommit(commitMessage); if (committed) { const newGitCommit = getGitCommitShort(); - log.success(`Committed: ${newGitCommit}`); + + // Update registry with the actual commit hash and amend the commit + try { + updateVersionGitCommit(contractId, network, newVersion, newGitCommit); + execSync("git add -A && git commit --amend --no-edit", { cwd: process.cwd(), stdio: "pipe" }); + log.success(`Committed: ${newGitCommit} (with gitCommit in registry)`); + } catch (e: any) { + log.warning(`Could not update gitCommit in registry: ${e.message}`); + log.success(`Committed: ${newGitCommit}`); + } const tagName = `${contractId.toLowerCase()}-v${newVersion}`; try { @@ -648,6 +658,17 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" } else { log.warning("Could not create git commit - please commit manually"); } + } else { + // If skipping commit, record current HEAD as reference + const currentCommit = getGitCommitShort(); + if (currentCommit !== "unknown") { + try { + updateVersionGitCommit(contractId, network, newVersion, currentCommit); + log.info(`Recorded current commit reference: ${currentCommit}`); + } catch { + // Non-critical, ignore + } + } } // ======================================================================== diff --git a/contracts/tasks/upgrade/utils.ts b/contracts/tasks/upgrade/utils.ts index ed73e0867..b343b3271 100644 --- a/contracts/tasks/upgrade/utils.ts +++ b/contracts/tasks/upgrade/utils.ts @@ -246,6 +246,25 @@ export function addVersion( writeRegistry(registry); } +/** + * Update gitCommit for a specific version deployment + */ +export function updateVersionGitCommit( + contractId: string, + network: SupportedNetwork, + version: string, + gitCommit: string, +): void { + const registry = readRegistry(); + + if (!registry.versions[contractId]?.[version]?.deployments?.[network]) { + throw new Error(`Deployment not found: ${contractId} v${version} on ${network}`); + } + + registry.versions[contractId][version].deployments[network].gitCommit = gitCommit; + writeRegistry(registry); +} + /** * Update proxy address for a contract on a network */ diff --git a/contracts/test/governance/FullUpgradeIntegration.test.ts b/contracts/test/governance/FullUpgradeIntegration.test.ts deleted file mode 100644 index 47eee08e0..000000000 --- a/contracts/test/governance/FullUpgradeIntegration.test.ts +++ /dev/null @@ -1,481 +0,0 @@ -import { expect } from "chai"; -import { ethers, upgrades } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { - MockOwnableHub, - MockOwnableRegistry, - IdentityVerificationHubImplV2, - IdentityRegistryImplV1, - IdentityRegistryIdCardImplV1, - PCR0Manager, - CustomVerifier, - PoseidonT3, -} from "../../typechain-types"; - -/** - * FULL PRODUCTION UPGRADE INTEGRATION TEST - * - * This test simulates upgrading production contracts from Ownable to AccessControl governance. - * - * PRODUCTION SCENARIO: - * - Current: IdentityVerificationHubImplV2 with OLD ImplRoot (uses Ownable2StepUpgradeable) - * - Current: IdentityRegistryImplV1 with OLD ImplRoot (uses Ownable2StepUpgradeable) - * - Current: IdentityRegistryIdCardImplV1 with OLD ImplRoot (uses Ownable2StepUpgradeable) - * - Current: PCR0Manager with Ownable - * - * UPGRADE TO: - * - New: IdentityVerificationHubImplV2 with NEW ImplRoot (uses AccessControlUpgradeable) - * - New: IdentityRegistryImplV1 with NEW ImplRoot (uses AccessControlUpgradeable) - * - New: IdentityRegistryIdCardImplV1 with NEW ImplRoot (uses AccessControlUpgradeable) - * - New: PCR0Manager with AccessControlUpgradeable - * - * Test Flow: - * 1. Deploy OLD contracts with Ownable (MockOwnableHub = V2 with old ImplRoot) - * 2. Populate with production data - * 3. Upgrade to NEW contracts with AccessControl (MockUpgradedHub = V2 with new ImplRoot) - * 4. Verify state preservation (no data loss) - * 5. Transfer roles to multisigs - * 6. Verify multisig control and deployer has no control - * 7. Verify all functionality still works - * - * Note: MockOwnableHub/MockUpgradedHub represent IdentityVerificationHubImplV2 - * with old ImplRoot vs new ImplRoot. We use mocks because the real contracts are - * already compiled with the new ImplRoot. - */ -describe("πŸš€ PRODUCTION UPGRADE: Ownable β†’ AccessControl Governance", function () { - this.timeout(120000); - - let deployer: SignerWithAddress; - let securityMultisig: SignerWithAddress; - let operationsMultisig: SignerWithAddress; - let user1: SignerWithAddress; - let user2: SignerWithAddress; - - // Contracts - let oldHubProxy: MockOwnableHub; - let upgradedHub: IdentityVerificationHubImplV2; - let oldRegistryProxy: MockOwnableRegistry; - let upgradedRegistry: IdentityRegistryImplV1; - let idCardRegistryProxy: IdentityRegistryIdCardImplV1; - let pcr0Manager: PCR0Manager; - - // Libraries - let customVerifier: CustomVerifier; - let poseidonT3: PoseidonT3; - - // Test constants - const SECURITY_ROLE = ethers.keccak256(ethers.toUtf8Bytes("SECURITY_ROLE")); - const OPERATIONS_ROLE = ethers.keccak256(ethers.toUtf8Bytes("OPERATIONS_ROLE")); - - // Sample production data - const SAMPLE_CSCA_ROOT = "0x1111111111111111111111111111111111111111111111111111111111111111"; - const SAMPLE_PCR0 = "0x" + "22".repeat(48); - - before(async function () { - [deployer, securityMultisig, operationsMultisig, user1, user2] = await ethers.getSigners(); - - console.log("\n🎯 Production Upgrade Simulation"); - console.log(` Deployer: ${deployer.address}`); - console.log(` Critical Multisig: ${securityMultisig.address}`); - console.log(` Standard Multisig: ${operationsMultisig.address}`); - console.log("\nπŸ“ Scenario: Upgrade IdentityVerificationHubImplV2 & Registries"); - console.log(" From: Ownable2StepUpgradeable (old ImplRoot)"); - console.log(" To: AccessControlUpgradeable (new ImplRoot)"); - }); - - describe("πŸ“¦ Phase 1: Deploy Current Production State (with Ownable)", function () { - it("should deploy libraries", async function () { - console.log("\nπŸ“š Deploying libraries..."); - - const CustomVerifierFactory = await ethers.getContractFactory("CustomVerifier"); - customVerifier = await CustomVerifierFactory.deploy(); - await customVerifier.waitForDeployment(); - console.log(` βœ… CustomVerifier: ${await customVerifier.getAddress()}`); - - const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3"); - poseidonT3 = await PoseidonT3Factory.deploy(); - await poseidonT3.waitForDeployment(); - console.log(` βœ… PoseidonT3: ${await poseidonT3.getAddress()}`); - }); - - it("should deploy HubV2 with Ownable (current production)", async function () { - console.log("\n🏒 Deploying HubV2 (Ownable)..."); - console.log(" (Simulates current production: V2 with old ImplRoot)"); - - const MockOwnableHubFactory = await ethers.getContractFactory("MockOwnableHub"); - oldHubProxy = (await upgrades.deployProxy(MockOwnableHubFactory, [], { - kind: "uups", - initializer: "initialize", - unsafeAllow: ["constructor", "state-variable-immutable", "state-variable-assignment"], - })) as unknown as MockOwnableHub; - - await oldHubProxy.waitForDeployment(); - console.log(` βœ… HubV2: ${await oldHubProxy.getAddress()}`); - expect(await oldHubProxy.owner()).to.equal(deployer.address); - console.log(` βœ… Current owner: ${deployer.address}`); - }); - - it("should deploy Registry with Ownable (current production)", async function () { - console.log("\nπŸ“ Deploying Registry (Ownable)..."); - console.log(" (Simulates current production: IdentityRegistryImplV1 with old ImplRoot)"); - - const MockOwnableRegistryFactory = await ethers.getContractFactory("MockOwnableRegistry"); - - oldRegistryProxy = (await upgrades.deployProxy( - MockOwnableRegistryFactory, - [ethers.ZeroAddress], // hubAddress - { - kind: "uups", - initializer: "initialize", - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "external-library-linking", - ], - }, - )) as unknown as MockOwnableRegistry; - - await oldRegistryProxy.waitForDeployment(); - console.log(` βœ… Registry: ${await oldRegistryProxy.getAddress()}`); - expect(await oldRegistryProxy.owner()).to.equal(deployer.address); - console.log(` βœ… Current owner: ${deployer.address}`); - }); - - it("should configure contracts", async function () { - console.log("\nπŸ”— Configuring contracts..."); - await oldHubProxy.updateRegistry(await oldRegistryProxy.getAddress()); - await oldRegistryProxy.setHub(await oldHubProxy.getAddress()); - console.log(" βœ… Hub ← β†’ Registry configured"); - }); - }); - - describe("πŸ“Š Phase 2: Populate with Production Data", function () { - it("should add production data", async function () { - console.log("\nπŸ“Š Adding production data..."); - - // Add CSCA root to registry - await oldRegistryProxy.updateCscaRoot(SAMPLE_CSCA_ROOT); - expect(await oldRegistryProxy.getCscaRoot()).to.equal(SAMPLE_CSCA_ROOT); - console.log(" βœ… CSCA root: " + SAMPLE_CSCA_ROOT.substring(0, 20) + "..."); - - // Set circuit version in hub - await oldHubProxy.updateCircuitVersion(2); - expect(await oldHubProxy.getCircuitVersion()).to.equal(2); - console.log(" βœ… Circuit version: 2"); - }); - }); - - describe("⚑ Phase 3: CRITICAL - Execute Governance Upgrade", function () { - let registryAddressBefore: string; - let cscaRootBefore: string; - let circuitVersionBefore: bigint; - - before(async function () { - console.log("\nπŸ’Ύ Capturing pre-upgrade state..."); - registryAddressBefore = await oldHubProxy.getRegistry(); - cscaRootBefore = await oldRegistryProxy.getCscaRoot(); - circuitVersionBefore = await oldHubProxy.getCircuitVersion(); - console.log(` Registry address: ${registryAddressBefore}`); - console.log(` CSCA root: ${cscaRootBefore.substring(0, 20)}...`); - console.log(` Circuit version: ${circuitVersionBefore}`); - }); - - it("should upgrade HubV2 to AccessControl governance", async function () { - console.log("\n⚑ CRITICAL: Upgrading HubV2 governance..."); - console.log(" From: MockOwnableHub (simulates V2 with old Ownable ImplRoot)"); - console.log(" To: IdentityVerificationHubImplV2 (real contract with new AccessControl ImplRoot)"); - - const HubV2Factory = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { CustomVerifier: await customVerifier.getAddress() }, - }); - - upgradedHub = (await upgrades.upgradeProxy(await oldHubProxy.getAddress(), HubV2Factory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for Ownable β†’ AccessControl - unsafeAllow: ["constructor", "external-library-linking"], - call: { fn: "initializeGovernance", args: [] }, // Initialize governance roles (reinitializer) - })) as unknown as IdentityVerificationHubImplV2; - - console.log(` βœ… Upgraded to: ${await upgradedHub.getAddress()}`); - - // Verify proxy address unchanged - expect(await upgradedHub.getAddress()).to.equal(await oldHubProxy.getAddress()); - console.log(" βœ… Same proxy address (in-place upgrade)"); - - // Verify governance roles initialized - expect(await upgradedHub.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - console.log(" βœ… Governance roles initialized (deployer has both roles)"); - - // Verify ALL state preserved - expect(await upgradedHub.getRegistry()).to.equal(registryAddressBefore); - expect(await upgradedHub.getCircuitVersion()).to.equal(circuitVersionBefore); - console.log(" βœ… ALL STATE PRESERVED - NO DATA LOSS!"); - }); - - it("should upgrade Registry to AccessControl governance", async function () { - console.log("\n⚑ CRITICAL: Upgrading Registry governance..."); - console.log(" From: MockOwnableRegistry (simulates Registry with old Ownable ImplRoot)"); - console.log(" To: IdentityRegistryImplV1 (real contract with new AccessControl ImplRoot)"); - - const RegistryFactory = await ethers.getContractFactory("IdentityRegistryImplV1", { - libraries: { PoseidonT3: await poseidonT3.getAddress() }, - }); - - upgradedRegistry = (await upgrades.upgradeProxy(await oldRegistryProxy.getAddress(), RegistryFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for Ownable β†’ AccessControl - unsafeAllow: ["constructor", "external-library-linking"], - call: { fn: "initializeGovernance", args: [] }, // Initialize governance roles - })) as unknown as IdentityRegistryImplV1; - - console.log(` βœ… Upgraded to: ${await upgradedRegistry.getAddress()}`); - - // Verify proxy address unchanged - expect(await upgradedRegistry.getAddress()).to.equal(await oldRegistryProxy.getAddress()); - console.log(" βœ… Same proxy address (in-place upgrade)"); - - // Verify governance roles initialized - expect(await upgradedRegistry.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await upgradedRegistry.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - console.log(" βœ… Governance roles initialized (deployer has both roles)"); - - // Verify ALL state preserved - expect(await upgradedRegistry.getCscaRoot()).to.equal(cscaRootBefore); - expect(await upgradedRegistry.hub()).to.equal(await upgradedHub.getAddress()); - console.log(" βœ… ALL STATE PRESERVED - NO DATA LOSS!"); - }); - }); - - describe("πŸ†• Phase 4: Deploy Additional Contracts with New Governance", function () { - it("should deploy ID Card Registry with AccessControl", async function () { - console.log("\nπŸ†” Deploying ID Card Registry (AccessControl from start)..."); - - const IdCardRegistryFactory = await ethers.getContractFactory("IdentityRegistryIdCardImplV1", { - libraries: { PoseidonT3: await poseidonT3.getAddress() }, - }); - - idCardRegistryProxy = (await upgrades.deployProxy(IdCardRegistryFactory, [await upgradedHub.getAddress()], { - kind: "uups", - initializer: "initialize", - unsafeAllow: ["constructor", "external-library-linking"], - })) as unknown as IdentityRegistryIdCardImplV1; - - await idCardRegistryProxy.waitForDeployment(); - console.log(` βœ… ID Card Registry: ${await idCardRegistryProxy.getAddress()}`); - expect(await idCardRegistryProxy.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - console.log(" βœ… Deployer has governance roles"); - }); - - it("should deploy PCR0Manager with AccessControl", async function () { - console.log("\nπŸ”§ Deploying PCR0Manager (AccessControl from start)..."); - - const PCR0ManagerFactory = await ethers.getContractFactory("PCR0Manager"); - pcr0Manager = await PCR0ManagerFactory.deploy(); - await pcr0Manager.waitForDeployment(); - console.log(` βœ… PCR0Manager: ${await pcr0Manager.getAddress()}`); - expect(await pcr0Manager.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - console.log(" βœ… Deployer has governance roles"); - }); - }); - - describe("πŸ”‘ Phase 5: Transfer Roles to Multisigs", function () { - it("should transfer HubV2 roles to multisigs and remove deployer", async function () { - console.log("\nπŸ”‘ Transferring HubV2 roles to multisigs..."); - - await upgradedHub.grantRole(SECURITY_ROLE, securityMultisig.address); - await upgradedHub.grantRole(OPERATIONS_ROLE, operationsMultisig.address); - console.log(" βœ… Granted roles to multisigs"); - - await upgradedHub.renounceRole(SECURITY_ROLE, deployer.address); - await upgradedHub.renounceRole(OPERATIONS_ROLE, deployer.address); - console.log(" βœ… Deployer renounced roles"); - - expect(await upgradedHub.hasRole(SECURITY_ROLE, securityMultisig.address)).to.be.true; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, operationsMultisig.address)).to.be.true; - expect(await upgradedHub.hasRole(SECURITY_ROLE, deployer.address)).to.be.false; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.false; - console.log(" βœ… HubV2 now controlled by multisigs only"); - }); - - it("should transfer Registry roles to multisigs and remove deployer", async function () { - console.log("\nπŸ”‘ Transferring Registry roles to multisigs..."); - - await upgradedRegistry.grantRole(SECURITY_ROLE, securityMultisig.address); - await upgradedRegistry.grantRole(OPERATIONS_ROLE, operationsMultisig.address); - await upgradedRegistry.renounceRole(SECURITY_ROLE, deployer.address); - await upgradedRegistry.renounceRole(OPERATIONS_ROLE, deployer.address); - - expect(await upgradedRegistry.hasRole(SECURITY_ROLE, securityMultisig.address)).to.be.true; - expect(await upgradedRegistry.hasRole(OPERATIONS_ROLE, operationsMultisig.address)).to.be.true; - expect(await upgradedRegistry.hasRole(SECURITY_ROLE, deployer.address)).to.be.false; - console.log(" βœ… Registry now controlled by multisigs only"); - }); - - it("should transfer ID Card Registry roles to multisigs", async function () { - console.log("\nπŸ”‘ Transferring ID Card Registry roles to multisigs..."); - - await idCardRegistryProxy.grantRole(SECURITY_ROLE, securityMultisig.address); - await idCardRegistryProxy.grantRole(OPERATIONS_ROLE, operationsMultisig.address); - await idCardRegistryProxy.renounceRole(SECURITY_ROLE, deployer.address); - await idCardRegistryProxy.renounceRole(OPERATIONS_ROLE, deployer.address); - - expect(await idCardRegistryProxy.hasRole(SECURITY_ROLE, securityMultisig.address)).to.be.true; - expect(await idCardRegistryProxy.hasRole(SECURITY_ROLE, deployer.address)).to.be.false; - console.log(" βœ… ID Card Registry now controlled by multisigs only"); - }); - - it("should transfer PCR0Manager roles to multisigs", async function () { - console.log("\nπŸ”‘ Transferring PCR0Manager roles to multisigs..."); - - await pcr0Manager.grantRole(SECURITY_ROLE, securityMultisig.address); - await pcr0Manager.grantRole(OPERATIONS_ROLE, operationsMultisig.address); - await pcr0Manager.renounceRole(SECURITY_ROLE, deployer.address); - await pcr0Manager.renounceRole(OPERATIONS_ROLE, deployer.address); - - expect(await pcr0Manager.hasRole(SECURITY_ROLE, securityMultisig.address)).to.be.true; - expect(await pcr0Manager.hasRole(SECURITY_ROLE, deployer.address)).to.be.false; - console.log(" βœ… PCR0Manager now controlled by multisigs only"); - }); - }); - - describe("βœ… Phase 6: Verify Multisig Control Works", function () { - it("should allow critical multisig to update HubV2", async function () { - console.log("\nβœ… Testing HubV2 multisig control..."); - const newRegistry = user1.address; - await upgradedHub.connect(securityMultisig).updateRegistry(newRegistry); - expect(await upgradedHub.getRegistry()).to.equal(newRegistry); - console.log(" βœ… Critical multisig can update HubV2"); - }); - - it("should allow operations multisig to update Registry CSCA root", async function () { - console.log("\nβœ… Testing Registry multisig control..."); - const newRoot = "0x" + "33".repeat(32); - await upgradedRegistry.connect(operationsMultisig).updateCscaRoot(newRoot); - expect(await upgradedRegistry.getCscaRoot()).to.equal(newRoot); - console.log(" βœ… Operations multisig can update Registry CSCA root"); - }); - - it("should allow critical multisig to manage PCR0", async function () { - console.log("\nβœ… Testing PCR0Manager multisig control..."); - await pcr0Manager.connect(securityMultisig).addPCR0(SAMPLE_PCR0); - expect(await pcr0Manager.isPCR0Set(SAMPLE_PCR0)).to.be.true; - console.log(" βœ… Critical multisig can manage PCR0"); - }); - }); - - describe("🚫 Phase 7: Verify Deployer Has ZERO Control", function () { - it("should prevent deployer from updating HubV2", async function () { - console.log("\n🚫 Verifying deployer CANNOT update HubV2..."); - await expect(upgradedHub.connect(deployer).updateRegistry(ethers.ZeroAddress)).to.be.revertedWithCustomError( - upgradedHub, - "AccessControlUnauthorizedAccount", - ); - console.log(" βœ… Deployer blocked βœ“"); - }); - - it("should prevent deployer from updating Registry", async function () { - console.log("\n🚫 Verifying deployer CANNOT update Registry..."); - await expect( - upgradedRegistry.connect(deployer).updateCscaRoot("0x" + "44".repeat(32)), - ).to.be.revertedWithCustomError(upgradedRegistry, "AccessControlUnauthorizedAccount"); - console.log(" βœ… Deployer blocked βœ“"); - }); - - it("should prevent deployer from managing PCR0", async function () { - console.log("\n🚫 Verifying deployer CANNOT manage PCR0..."); - await expect(pcr0Manager.connect(deployer).addPCR0("0x" + "55".repeat(48))).to.be.revertedWithCustomError( - pcr0Manager, - "AccessControlUnauthorizedAccount", - ); - console.log(" βœ… Deployer blocked βœ“"); - }); - - it("should prevent ANY unauthorized user from operations", async function () { - console.log("\n🚫 Verifying unauthorized users blocked..."); - await expect(upgradedHub.connect(user2).updateRegistry(ethers.ZeroAddress)).to.be.revertedWithCustomError( - upgradedHub, - "AccessControlUnauthorizedAccount", - ); - - await expect( - upgradedRegistry.connect(user2).updateCscaRoot("0x" + "66".repeat(32)), - ).to.be.revertedWithCustomError(upgradedRegistry, "AccessControlUnauthorizedAccount"); - - await expect(pcr0Manager.connect(user2).addPCR0("0x" + "77".repeat(48))).to.be.revertedWithCustomError( - pcr0Manager, - "AccessControlUnauthorizedAccount", - ); - console.log(" βœ… All unauthorized access blocked βœ“"); - }); - }); - - describe("🎯 Phase 8: Final Functionality Verification", function () { - it("should verify HubV2 is fully functional with new governance", async function () { - console.log("\n🎯 Final HubV2 verification..."); - - const newRegistry = user2.address; - await upgradedHub.connect(securityMultisig).updateRegistry(newRegistry); - expect(await upgradedHub.getRegistry()).to.equal(newRegistry); - - await upgradedHub.connect(securityMultisig).updateCircuitVersion(3); - expect(await upgradedHub.getCircuitVersion()).to.equal(3); - console.log(" βœ… HubV2 fully functional with multisig control"); - }); - - it("should verify Registry is fully functional with new governance", async function () { - console.log("\n🎯 Final Registry verification..."); - - const finalRoot = "0x" + "99".repeat(32); - await upgradedRegistry.connect(operationsMultisig).updateCscaRoot(finalRoot); - expect(await upgradedRegistry.getCscaRoot()).to.equal(finalRoot); - console.log(" βœ… Registry fully functional with multisig control"); - }); - - it("should verify PCR0Manager is fully functional with new governance", async function () { - console.log("\n🎯 Final PCR0Manager verification..."); - - const newPCR0 = "0x" + "88".repeat(48); - await pcr0Manager.connect(securityMultisig).addPCR0(newPCR0); - expect(await pcr0Manager.isPCR0Set(newPCR0)).to.be.true; - console.log(" βœ… PCR0Manager fully functional with multisig control"); - }); - }); - - describe("πŸŽ‰ Phase 9: Success Summary", function () { - it("should print comprehensive upgrade success report", async function () { - console.log("\n" + "=".repeat(80)); - console.log("πŸŽ‰ PRODUCTION GOVERNANCE UPGRADE: 100% SUCCESSFUL"); - console.log("=".repeat(80)); - - console.log("\nπŸ“‹ Upgraded Contracts:"); - console.log(` βœ“ IdentityVerificationHubImplV2: ${await upgradedHub.getAddress()}`); - console.log(` βœ“ IdentityRegistryImplV1 (Passport): ${await upgradedRegistry.getAddress()}`); - console.log(` βœ“ IdentityRegistryIdCardImplV1: ${await idCardRegistryProxy.getAddress()}`); - console.log(` βœ“ PCR0Manager: ${await pcr0Manager.getAddress()}`); - - console.log("\nβœ… Verification Checklist:"); - console.log(" βœ“ Upgraded from Ownable2StepUpgradeable to AccessControlUpgradeable"); - console.log(" βœ“ ALL production data preserved (zero data loss)"); - console.log(" βœ“ Proxy addresses unchanged (in-place upgrade)"); - console.log(" βœ“ Multi-tier governance active (Critical + Standard roles)"); - console.log(" βœ“ Roles transferred to multisigs"); - console.log(" βœ“ Deployer has ZERO control"); - console.log(" βœ“ Multisigs have full control"); - console.log(" βœ“ All contract functionality verified working"); - console.log(" βœ“ Access control properly enforced"); - console.log(" βœ“ Unauthorized access blocked"); - console.log(" βœ“ NO storage corruption"); - - console.log("\nπŸ”‘ Governance Configuration:"); - console.log(` Critical Multisig (3/5): ${securityMultisig.address}`); - console.log(` Standard Multisig (2/5): ${operationsMultisig.address}`); - console.log(" Critical Role: Upgrades, critical parameters, role management"); - console.log(" Standard Role: Standard operational parameters"); - - console.log("\nβœ… PRODUCTION UPGRADE IS SAFE TO EXECUTE"); - console.log("=".repeat(80) + "\n"); - }); - }); -}); diff --git a/contracts/test/governance/GovernanceUpgrade.test.ts b/contracts/test/governance/GovernanceUpgrade.test.ts deleted file mode 100644 index f39ede2ee..000000000 --- a/contracts/test/governance/GovernanceUpgrade.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { expect } from "chai"; -import { ethers, upgrades } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { - IdentityVerificationHubImplV2, - IdentityRegistryImplV1, - PCR0Manager, - VerifyAll, - MockImplRoot, - CustomVerifier, - PoseidonT3, -} from "../../typechain-types"; - -describe("Governance Upgrade Tests", function () { - let deployer: SignerWithAddress; - let securityMultisig: SignerWithAddress; - let operationsMultisig: SignerWithAddress; - let user: SignerWithAddress; - - // Contract instances for testing - let hubProxy: IdentityVerificationHubImplV2; - let registryProxy: IdentityRegistryImplV1; - let pcr0Manager: PCR0Manager; - let verifyAll: VerifyAll; - let testProxy: MockImplRoot; - - // Libraries - let customVerifier: CustomVerifier; - let poseidonT3: PoseidonT3; - - // Test constants - const SECURITY_ROLE = ethers.keccak256(ethers.toUtf8Bytes("SECURITY_ROLE")); - const OPERATIONS_ROLE = ethers.keccak256(ethers.toUtf8Bytes("OPERATIONS_ROLE")); - const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; - - beforeEach(async function () { - // Set up test signers representing different roles in the governance system - [deployer, securityMultisig, operationsMultisig, user] = await ethers.getSigners(); - - // Deploy CustomVerifier library once for reuse across tests - const CustomVerifierFactory = await ethers.getContractFactory("CustomVerifier"); - customVerifier = await CustomVerifierFactory.deploy(); - await customVerifier.waitForDeployment(); - - // Deploy PoseidonT3 library once for reuse across tests - const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3"); - poseidonT3 = await PoseidonT3Factory.deploy(); - await poseidonT3.waitForDeployment(); - }); - - describe("Hub Upgrade to Governance", function () { - beforeEach(async function () { - // Deploy initial hub implementation (V2 without governance) - // This simulates an existing deployed contract that needs to be upgraded - const IdentityVerificationHubV2 = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - // Deploy implementation and proxy manually to bypass OpenZeppelin validation - const implementation = await IdentityVerificationHubV2.deploy(); - await implementation.waitForDeployment(); - - // Encode the initialize call (empty for now, will initialize after upgrade) - const initData = "0x"; // No initialization data - - // Deploy proxy manually - const ProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const proxy = await ProxyFactory.deploy(await implementation.getAddress(), initData); - await proxy.waitForDeployment(); - - // Attach the interface to the proxy - hubProxy = IdentityVerificationHubV2.attach(await proxy.getAddress()) as unknown as IdentityVerificationHubImplV2; - - // Initialize the proxy with the old Ownable pattern (simulate existing deployment) - await hubProxy.initialize(); - - // Force import the proxy into OpenZeppelin's system for upgrade management - await upgrades.forceImport(await proxy.getAddress(), IdentityVerificationHubV2, { - kind: "uups", - }); - }); - - it("should successfully upgrade hub to governance system", async function () { - // Test: Verify that we can upgrade from an existing Ownable contract to AccessControl governance - // This simulates upgrading a production contract to the new governance system - - // Verify initial state - deployer should have roles after initialization - expect(await hubProxy.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - - // Deploy new implementation with governance using the same library instance - const IdentityVerificationHubV3 = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - // Upgrade to governance system (no initialization call needed for upgrades) - const upgradedHub = await upgrades.upgradeProxy(await hubProxy.getAddress(), IdentityVerificationHubV3, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking"], - }); - - // After upgrade, the contract now has governance capabilities - const hubWithGovernance = upgradedHub as unknown as IdentityVerificationHubImplV2; - - // For this test, we'll simulate that the migration script has already run - // In production, this would be done by a separate migration transaction - try { - await hubWithGovernance.grantRole(SECURITY_ROLE, deployer.address); - await hubWithGovernance.grantRole(OPERATIONS_ROLE, deployer.address); - - // Verify governance roles are set correctly - expect(await hubWithGovernance.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await hubWithGovernance.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - - // Verify role hierarchy (set up during __ImplRoot_init) - expect(await hubWithGovernance.getRoleAdmin(SECURITY_ROLE)).to.equal(SECURITY_ROLE); - expect(await hubWithGovernance.getRoleAdmin(OPERATIONS_ROLE)).to.equal(SECURITY_ROLE); - } catch (error) { - // If role setup fails, it might mean the roles are already set up or the contract doesn't support it yet - console.log("Role setup skipped:", (error as Error).message); - expect(true).to.be.true; // Pass the test - upgrade was successful - } - }); - - it("should validate upgrade safety", async function () { - // Test: Verify that the upgrade process validates storage layout compatibility - // This ensures that upgrading won't corrupt existing contract state - - const IdentityVerificationHubV3 = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - // The upgrade should succeed without throwing storage layout errors - // OpenZeppelin's upgrades plugin validates storage compatibility automatically - const upgradedContract = await upgrades.upgradeProxy(await hubProxy.getAddress(), IdentityVerificationHubV3, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking"], - }); - - // Verify the upgrade was successful - expect(await upgradedContract.getAddress()).to.equal(await hubProxy.getAddress()); - }); - - it("should preserve contract state after upgrade", async function () { - // Test: Verify that contract state (roles, storage variables) is preserved during upgrade - // This is critical for production upgrades to maintain existing permissions and data - - // Verify initial state - check that roles are preserved - const initialHasCriticalRole = await hubProxy.hasRole(SECURITY_ROLE, deployer.address); - - // Upgrade using the same library instance to avoid redeployment - const IdentityVerificationHubV3 = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - await upgrades.upgradeProxy(await hubProxy.getAddress(), IdentityVerificationHubV3, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking"], - }); - - // Verify state is preserved - roles should still exist - const finalHasCriticalRole = await hubProxy.hasRole(SECURITY_ROLE, deployer.address); - expect(finalHasCriticalRole).to.equal(initialHasCriticalRole); - }); - }); - - describe("Registry Upgrade to Governance", function () { - beforeEach(async function () { - // Deploy initial registry implementation using the shared library instance - // This simulates upgrading an existing registry contract to governance - const IdentityRegistryV1 = await ethers.getContractFactory("IdentityRegistryImplV1", { - libraries: { - PoseidonT3: await poseidonT3.getAddress(), - }, - }); - - // Deploy implementation and proxy manually to bypass OpenZeppelin validation - const implementation = await IdentityRegistryV1.deploy(); - await implementation.waitForDeployment(); - - // Encode the initialize call - const initData = IdentityRegistryV1.interface.encodeFunctionData("initialize", [ethers.ZeroAddress]); - - // Deploy proxy manually - const ProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const proxy = await ProxyFactory.deploy(await implementation.getAddress(), initData); - await proxy.waitForDeployment(); - - // Attach the interface to the proxy - registryProxy = IdentityRegistryV1.attach(await proxy.getAddress()) as unknown as IdentityRegistryImplV1; - - // Force import the proxy into OpenZeppelin's system for upgrade management - await upgrades.forceImport(await proxy.getAddress(), IdentityRegistryV1, { - kind: "uups", - }); - }); - - it("should successfully upgrade registry to governance system", async function () { - // Test: Verify that the registry contract can be upgraded to use role-based governance - // This ensures the registry upgrade process works similarly to the hub upgrade - - // Verify initial state - deployer should have roles after initialization - expect(await registryProxy.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - - // Upgrade to governance using the shared library instance - const IdentityRegistryV2 = await ethers.getContractFactory("IdentityRegistryImplV1", { - libraries: { - PoseidonT3: await poseidonT3.getAddress(), - }, - }); - - await upgrades.upgradeProxy(await registryProxy.getAddress(), IdentityRegistryV2, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking"], - }); - - // After upgrade, the contract now has governance capabilities - // For this test, we'll simulate that the migration script has already run - try { - await registryProxy.grantRole(SECURITY_ROLE, deployer.address); - await registryProxy.grantRole(OPERATIONS_ROLE, deployer.address); - - // Verify governance roles are set correctly - expect(await registryProxy.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await registryProxy.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - } catch (error) { - // If role setup fails, it might mean the roles are already set up or the contract doesn't support it yet - console.log("Role setup skipped:", (error as Error).message); - expect(true).to.be.true; // Pass the test - upgrade was successful - } - }); - }); - - describe("New Utility Contracts with Governance", function () { - beforeEach(async function () { - // Deploy new utility contracts that are designed with governance from the start - // These contracts use AccessControl instead of Ownable from deployment - - // Deploy PCR0Manager with built-in governance - const PCR0Manager = await ethers.getContractFactory("PCR0Manager"); - pcr0Manager = await PCR0Manager.deploy(); - await pcr0Manager.waitForDeployment(); - - // Deploy VerifyAll with mock addresses for hub and registry - // In production, these would be real contract addresses - const mockHub = ethers.Wallet.createRandom().address; - const mockRegistry = ethers.Wallet.createRandom().address; - - const VerifyAll = await ethers.getContractFactory("VerifyAll"); - verifyAll = await VerifyAll.deploy(mockHub, mockRegistry); - await verifyAll.waitForDeployment(); - }); - - it("should deploy PCR0Manager with deployer having initial roles", async function () { - // Test: Verify that PCR0Manager is deployed with the deployer having both governance roles - // This follows the pattern where deployer gets initial control before transferring to multisigs - expect(await pcr0Manager.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await pcr0Manager.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - }); - - it("should deploy VerifyAll with deployer having initial roles", async function () { - // Test: Verify that VerifyAll is deployed with the deployer having both governance roles - // This ensures consistent role initialization across all governance contracts - expect(await verifyAll.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await verifyAll.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - }); - - it("should allow role transfer and then critical multisig to manage PCR0", async function () { - // Test: Verify the complete workflow of transferring roles and using them for PCR0 management - // This simulates the production process of deploying, transferring roles, and operating the contract - - // First transfer roles to multisigs (simulating production deployment workflow) - await pcr0Manager.connect(deployer).grantRole(SECURITY_ROLE, securityMultisig.address); - await pcr0Manager.connect(deployer).grantRole(OPERATIONS_ROLE, operationsMultisig.address); - - const testPCR0 = "0x" + "00".repeat(48); // 48 zero bytes (valid PCR0 format) - - // Critical multisig should be able to add PCR0 (testing governance functionality) - await expect(pcr0Manager.connect(securityMultisig).addPCR0(testPCR0)).to.emit(pcr0Manager, "PCR0Added"); - - // Verify PCR0 was added successfully - expect(await pcr0Manager.isPCR0Set(testPCR0)).to.be.true; - - // Critical multisig should be able to remove PCR0 (testing full CRUD operations) - await expect(pcr0Manager.connect(securityMultisig).removePCR0(testPCR0)).to.emit(pcr0Manager, "PCR0Removed"); - - // Verify PCR0 was removed successfully - expect(await pcr0Manager.isPCR0Set(testPCR0)).to.be.false; - }); - - it("should prevent unauthorized access to PCR0 functions", async function () { - // Test: Verify that access control is properly enforced for PCR0 management functions - // This ensures that only authorized roles can modify the PCR0 registry - - const testPCR0 = "0x" + "00".repeat(48); - - // Random user should not be able to add PCR0 (testing access control enforcement) - await expect(pcr0Manager.connect(user).addPCR0(testPCR0)).to.be.revertedWithCustomError( - pcr0Manager, - "AccessControlUnauthorizedAccount", - ); - }); - - it("should allow role transfer and then critical multisig to update VerifyAll addresses", async function () { - // Test: Verify that VerifyAll contract addresses can be updated by the critical multisig - // This tests the governance of contract dependencies and configuration updates - - // First transfer roles to multisigs (following production deployment pattern) - await verifyAll.connect(deployer).grantRole(SECURITY_ROLE, securityMultisig.address); - await verifyAll.connect(deployer).grantRole(OPERATIONS_ROLE, operationsMultisig.address); - - // Generate new addresses for testing (simulating contract upgrades or migrations) - const newHubAddress = ethers.Wallet.createRandom().address; - const newRegistryAddress = ethers.Wallet.createRandom().address; - - // Critical multisig should be able to update hub address - await expect(verifyAll.connect(securityMultisig).setHub(newHubAddress)).to.not.be.reverted; - - // Critical multisig should be able to update registry address - await expect(verifyAll.connect(securityMultisig).setRegistry(newRegistryAddress)).to.not.be.reverted; - - // Verify addresses were updated correctly - expect(await verifyAll.hub()).to.equal(newHubAddress); - expect(await verifyAll.registry()).to.equal(newRegistryAddress); - }); - - it("should prevent unauthorized access to VerifyAll functions", async function () { - // Test: Verify that VerifyAll access control prevents unauthorized configuration changes - // This ensures that only critical multisig can modify contract dependencies - - const newHubAddress = ethers.Wallet.createRandom().address; - - // Random user should not be able to update hub address (testing access control) - await expect(verifyAll.connect(user).setHub(newHubAddress)).to.be.revertedWithCustomError( - verifyAll, - "AccessControlUnauthorizedAccount", - ); - }); - }); - - describe("Role Management", function () { - beforeEach(async function () { - // Deploy a fresh PCR0Manager for role management testing - // This ensures clean state for testing role hierarchy and permissions - const PCR0Manager = await ethers.getContractFactory("PCR0Manager"); - pcr0Manager = await PCR0Manager.deploy(); - await pcr0Manager.waitForDeployment(); - - // Grant roles to multisigs (deployer has initial roles from constructor) - await pcr0Manager.connect(deployer).grantRole(SECURITY_ROLE, securityMultisig.address); - await pcr0Manager.connect(deployer).grantRole(OPERATIONS_ROLE, operationsMultisig.address); - }); - - it("should allow critical multisig to manage roles", async function () { - // Test: Verify that SECURITY_ROLE can manage other roles (role hierarchy) - // This tests the admin functionality where critical multisig manages all roles - - const newStandardUser = user.address; - - // Critical multisig (admin) should be able to grant standard role - await expect(pcr0Manager.connect(securityMultisig).grantRole(OPERATIONS_ROLE, newStandardUser)).to.not.be - .reverted; - - // Verify role was granted successfully - expect(await pcr0Manager.hasRole(OPERATIONS_ROLE, newStandardUser)).to.be.true; - - // Critical multisig should be able to revoke role (testing full role management) - await expect(pcr0Manager.connect(securityMultisig).revokeRole(OPERATIONS_ROLE, newStandardUser)).to.not.be - .reverted; - - // Verify role was revoked successfully - expect(await pcr0Manager.hasRole(OPERATIONS_ROLE, newStandardUser)).to.be.false; - }); - - it("should prevent non-admin from managing roles", async function () { - // Test: Verify that only SECURITY_ROLE can manage roles (enforce role hierarchy) - // This ensures that standard multisig and regular users cannot escalate privileges - - // Standard multisig should not be able to grant roles (lacks admin privileges) - await expect( - pcr0Manager.connect(operationsMultisig).grantRole(OPERATIONS_ROLE, user.address), - ).to.be.revertedWithCustomError(pcr0Manager, "AccessControlUnauthorizedAccount"); - - // Random user should not be able to grant roles (no privileges at all) - await expect(pcr0Manager.connect(user).grantRole(OPERATIONS_ROLE, user.address)).to.be.revertedWithCustomError( - pcr0Manager, - "AccessControlUnauthorizedAccount", - ); - }); - }); - - describe("Upgrade Authorization", function () { - beforeEach(async function () { - // Deploy MockImplRoot contract for testing upgrade authorization - // This contract inherits from ImplRoot and exposes the upgrade functionality for testing - const TestContract = await ethers.getContractFactory("MockImplRoot"); - - testProxy = await upgrades.deployProxy(TestContract, [], { - kind: "uups", - initializer: "exposed__ImplRoot_init()", - }); - - await testProxy.waitForDeployment(); - }); - - it("should allow critical multisig to authorize upgrades", async function () { - // Test: Verify that SECURITY_ROLE can authorize contract upgrades - // This is essential for secure upgrade governance in production - - // Grant SECURITY_ROLE to securityMultisig for this test - await testProxy.connect(deployer).grantRole(SECURITY_ROLE, securityMultisig.address); - - // Deploy new implementation for upgrade testing - const NewImplementation = await ethers.getContractFactory("MockImplRoot"); - - // The upgrade should succeed when called by critical multisig - const upgradeTx = await upgrades.upgradeProxy(await testProxy.getAddress(), NewImplementation, { - kind: "uups", - }); - - await upgradeTx.waitForDeployment(); - expect(await upgradeTx.getAddress()).to.equal(await testProxy.getAddress()); - }); - - it("should prevent non-critical roles from authorizing upgrades", async function () { - // Test: Verify that only SECURITY_ROLE can authorize upgrades - // This prevents unauthorized upgrades by standard multisig or regular users - - // Grant OPERATIONS_ROLE to operationsMultisig (but not SECURITY_ROLE) - await testProxy.connect(deployer).grantRole(OPERATIONS_ROLE, operationsMultisig.address); - - // The upgrade should fail when attempted without SECURITY_ROLE - // This tests the _authorizeUpgrade function's access control directly - await expect( - testProxy.connect(operationsMultisig).exposed_authorizeUpgrade(ethers.ZeroAddress), - ).to.be.revertedWithCustomError(testProxy, "AccessControlUnauthorizedAccount"); - }); - }); -}); diff --git a/contracts/test/governance/StorageLayoutUpgrade.test.ts b/contracts/test/governance/StorageLayoutUpgrade.test.ts deleted file mode 100644 index d10e2eeab..000000000 --- a/contracts/test/governance/StorageLayoutUpgrade.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { expect } from "chai"; -import { ethers, upgrades } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { MockOwnableHub, MockUpgradedHub, MockOwnableRegistry } from "../../typechain-types"; - -/** - * ERC-7201 Namespaced Storage Upgrade Tests - * - * These tests demonstrate OpenZeppelin's storage validation for upgrades from - * Ownable2StepUpgradeable to AccessControlUpgradeable. - * - * Key insights: - * 1. OpenZeppelin v5+ uses ERC-7201 namespaced storage (calculated hash locations) - * 2. Storage validation correctly detects namespace changes during upgrades - * 3. For production: The real contracts already use AccessControlUpgradeable (ImplRoot) - * 4. This test shows what would happen if upgrading from old Ownable contracts - * - * Production Reality: - * - Current contracts already inherit from ImplRoot (AccessControlUpgradeable) - * - No actual Ownable β†’ AccessControl upgrade needed in production - * - This test validates OpenZeppelin's safety mechanisms work correctly - */ - -describe("ERC-7201 Namespaced Storage Upgrade Tests", function () { - let deployer: SignerWithAddress; - let securityMultisig: SignerWithAddress; - let operationsMultisig: SignerWithAddress; - let user: SignerWithAddress; - - // Test constants - const SECURITY_ROLE = ethers.keccak256(ethers.toUtf8Bytes("SECURITY_ROLE")); - const OPERATIONS_ROLE = ethers.keccak256(ethers.toUtf8Bytes("OPERATIONS_ROLE")); - - beforeEach(async function () { - [deployer, securityMultisig, operationsMultisig, user] = await ethers.getSigners(); - }); - - describe("Ownable to AccessControl Upgrade (Storage Validation Demo)", function () { - let ownableHubProxy: MockOwnableHub; - let upgradedHub: MockUpgradedHub; - - beforeEach(async function () { - // Step 1: Deploy the OLD Ownable-based Hub (simulates production state) - // OpenZeppelin v5+ uses ERC-7201 namespaced storage, making this upgrade inherently safe - console.log("πŸ“¦ Deploying OLD Ownable Hub (simulating production)..."); - - const MockOwnableHubFactory = await ethers.getContractFactory("MockOwnableHub"); - - // Deploy as upgradeable proxy using OpenZeppelin - ownableHubProxy = (await upgrades.deployProxy(MockOwnableHubFactory, [], { - kind: "uups", - initializer: "initialize", - unsafeAllow: ["constructor", "state-variable-immutable", "state-variable-assignment"], - })) as unknown as MockOwnableHub; - - await ownableHubProxy.waitForDeployment(); - console.log(` βœ… OLD Hub deployed at: ${await ownableHubProxy.getAddress()}`); - - // Verify initial state - const owner = await ownableHubProxy.owner(); - expect(owner).to.equal(deployer.address); - console.log(` βœ… Initial owner: ${owner}`); - - // Set some state to verify it's preserved - await ownableHubProxy.updateRegistry(user.address); - expect(await ownableHubProxy.getRegistry()).to.equal(user.address); - console.log(` βœ… Initial registry set: ${user.address}`); - }); - - it("should demonstrate storage validation for Ownable to AccessControl upgrade", async function () { - console.log("\nπŸ”„ Testing Ownable β†’ AccessControl upgrade (demonstrates storage validation)..."); - - // Step 2: Upgrade to the NEW AccessControl-based Hub - const MockUpgradedHubFactory = await ethers.getContractFactory("MockUpgradedHub"); - - console.log(" πŸ“¦ Deploying NEW AccessControl implementation..."); - - // Perform the upgrade - upgradedHub = (await upgrades.upgradeProxy(await ownableHubProxy.getAddress(), MockUpgradedHubFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for test: simulates Ownableβ†’AccessControl upgrade - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "missing-public-upgradeto", - "missing-initializer", - ], - })) as unknown as MockUpgradedHub; - - console.log(` βœ… Upgrade completed to: ${await upgradedHub.getAddress()}`); - - // Step 3: Initialize governance (this sets up AccessControl) - console.log(" πŸ”§ Initializing governance..."); - await upgradedHub.initialize(); - console.log(" βœ… Governance initialized"); - - // Step 4: Verify storage preservation - console.log("\nπŸ” Verifying storage preservation..."); - - // Check that old state is preserved - const preservedRegistry = await upgradedHub.getRegistry(); - expect(preservedRegistry).to.equal(user.address); - console.log(` βœ… Registry preserved: ${preservedRegistry}`); - - // Check that new governance is working - expect(await upgradedHub.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - console.log(" βœ… New governance roles active"); - - // Verify the upgrade worked (unsafeSkipStorageCheck allows this test scenario) - // In production, this upgrade path would require careful namespace management - console.log(" βœ… Test upgrade completed (with storage validation bypassed)"); - }); - - it("should allow governance functions to work after upgrade", async function () { - // Perform the upgrade first - const MockUpgradedHubFactory = await ethers.getContractFactory("MockUpgradedHub"); - upgradedHub = (await upgrades.upgradeProxy(await ownableHubProxy.getAddress(), MockUpgradedHubFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for test: simulates Ownableβ†’AccessControl upgrade - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "missing-public-upgradeto", - "missing-initializer", - ], - })) as unknown as MockUpgradedHub; - - await upgradedHub.initialize(); - - // Test that governance functions work - const newRegistry = securityMultisig.address; - await upgradedHub.updateRegistry(newRegistry); - expect(await upgradedHub.getRegistry()).to.equal(newRegistry); - - // Test that circuit version can be updated - await upgradedHub.updateCircuitVersion(2); - expect(await upgradedHub.getCircuitVersion()).to.equal(2); - }); - - it("should prevent unauthorized access after upgrade", async function () { - // Perform the upgrade first - const MockUpgradedHubFactory = await ethers.getContractFactory("MockUpgradedHub"); - upgradedHub = (await upgrades.upgradeProxy(await ownableHubProxy.getAddress(), MockUpgradedHubFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for test: simulates Ownableβ†’AccessControl upgrade - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "missing-public-upgradeto", - "missing-initializer", - ], - })) as unknown as MockUpgradedHub; - - await upgradedHub.initialize(); - - // Test that unauthorized users cannot call governance functions - await expect(upgradedHub.connect(user).updateRegistry(user.address)).to.be.revertedWithCustomError( - upgradedHub, - "AccessControlUnauthorizedAccount", - ); - - await expect(upgradedHub.connect(user).updateCircuitVersion(3)).to.be.revertedWithCustomError( - upgradedHub, - "AccessControlUnauthorizedAccount", - ); - }); - - it("should allow role transfer to multisigs", async function () { - // Perform the upgrade first - const MockUpgradedHubFactory = await ethers.getContractFactory("MockUpgradedHub"); - upgradedHub = (await upgrades.upgradeProxy(await ownableHubProxy.getAddress(), MockUpgradedHubFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for test: simulates Ownableβ†’AccessControl upgrade - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "missing-public-upgradeto", - "missing-initializer", - ], - })) as unknown as MockUpgradedHub; - - await upgradedHub.initialize(); - - // Transfer roles to multisigs - await upgradedHub.grantRole(SECURITY_ROLE, securityMultisig.address); - await upgradedHub.grantRole(OPERATIONS_ROLE, operationsMultisig.address); - - // Verify multisigs have roles - expect(await upgradedHub.hasRole(SECURITY_ROLE, securityMultisig.address)).to.be.true; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, operationsMultisig.address)).to.be.true; - - // Test that multisig can perform governance functions - await upgradedHub.connect(securityMultisig).updateRegistry(securityMultisig.address); - expect(await upgradedHub.getRegistry()).to.equal(securityMultisig.address); - - // Renounce deployer roles - await upgradedHub.renounceRole(SECURITY_ROLE, deployer.address); - await upgradedHub.renounceRole(OPERATIONS_ROLE, deployer.address); - - // Verify deployer no longer has roles - expect(await upgradedHub.hasRole(SECURITY_ROLE, deployer.address)).to.be.false; - expect(await upgradedHub.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.false; - }); - }); - - describe("Storage Validation Analysis", function () { - it("should demonstrate OpenZeppelin's storage validation mechanisms", async function () { - console.log("\nπŸ“Š OpenZeppelin Storage Validation Analysis"); - console.log("This test shows how OpenZeppelin detects storage layout changes during upgrades"); - - // Deploy Ownable version - const MockOwnableHubFactory = await ethers.getContractFactory("MockOwnableHub"); - const ownableHub = (await upgrades.deployProxy(MockOwnableHubFactory, [], { - kind: "uups", - initializer: "initialize", - unsafeAllow: ["constructor", "state-variable-immutable", "state-variable-assignment"], - })) as unknown as MockOwnableHub; - - await ownableHub.waitForDeployment(); - - // Set some state - await ownableHub.updateRegistry(user.address); - - console.log("πŸ“‹ BEFORE UPGRADE:"); - console.log(` Owner (namespaced): ${await ownableHub.owner()}`); - console.log(` Registry: ${await ownableHub.getRegistry()}`); - console.log(` Circuit Version: ${await ownableHub.getCircuitVersion()}`); - - // Upgrade - const MockUpgradedHubFactory = await ethers.getContractFactory("MockUpgradedHub"); - const upgradedHub = (await upgrades.upgradeProxy(await ownableHub.getAddress(), MockUpgradedHubFactory, { - kind: "uups", - unsafeSkipStorageCheck: true, // Required for test: simulates Ownableβ†’AccessControl upgrade - unsafeAllow: [ - "constructor", - "state-variable-immutable", - "state-variable-assignment", - "missing-public-upgradeto", - "missing-initializer", - ], - })) as unknown as MockUpgradedHub; - - await upgradedHub.initialize(); - - console.log("\nπŸ“‹ AFTER UPGRADE:"); - console.log(` Registry (preserved): ${await upgradedHub.getRegistry()}`); - console.log(` Circuit Version (preserved): ${await upgradedHub.getCircuitVersion()}`); - console.log(` Has SECURITY_ROLE: ${await upgradedHub.hasRole(SECURITY_ROLE, deployer.address)}`); - console.log(` Has OPERATIONS_ROLE: ${await upgradedHub.hasRole(OPERATIONS_ROLE, deployer.address)}`); - - // Verify storage preservation - application state is preserved - expect(await upgradedHub.getRegistry()).to.equal(user.address); - expect(await upgradedHub.getCircuitVersion()).to.equal(1); - - console.log("\n🎯 Key Insights:"); - console.log(" β€’ OpenZeppelin detected namespace deletion during upgrade"); - console.log(" β€’ ERC-7201 storage prevents collisions but requires namespace management"); - console.log(" β€’ Production contracts already use AccessControlUpgradeable (ImplRoot)"); - console.log(" β€’ This test validates OpenZeppelin's safety mechanisms work correctly"); - }); - }); -}); diff --git a/contracts/test/governance/UpgradeSafety.test.ts b/contracts/test/governance/UpgradeSafety.test.ts deleted file mode 100644 index a510268b4..000000000 --- a/contracts/test/governance/UpgradeSafety.test.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { expect } from "chai"; -import { ethers, upgrades } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; - -describe("Upgrade Safety Validation Tests", function () { - let deployer: SignerWithAddress; - let securityMultisig: SignerWithAddress; - let operationsMultisig: SignerWithAddress; - - beforeEach(async function () { - [deployer, securityMultisig, operationsMultisig] = await ethers.getSigners(); - }); - - describe("Storage Layout Validation", function () { - it("should validate storage layout compatibility using OpenZeppelin", async function () { - // Deploy CustomVerifier library - const CustomVerifier = await ethers.getContractFactory("CustomVerifier"); - const customVerifier = await CustomVerifier.deploy(); - await customVerifier.waitForDeployment(); - - // Test IdentityVerificationHub storage layout validation - const IdentityVerificationHub = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - // OpenZeppelin's validateImplementation should pass for our contracts - await expect( - upgrades.validateImplementation(IdentityVerificationHub, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeAllow: ["constructor", "external-library-linking"], - }), - ).to.not.be.reverted; - - // Deploy PoseidonT3 library for IdentityRegistry - const PoseidonT3 = await ethers.getContractFactory("PoseidonT3"); - const poseidonT3 = await PoseidonT3.deploy(); - await poseidonT3.waitForDeployment(); - - // Test IdentityRegistry storage layout validation - const IdentityRegistry = await ethers.getContractFactory("IdentityRegistryImplV1", { - libraries: { - PoseidonT3: await poseidonT3.getAddress(), - }, - }); - await expect( - upgrades.validateImplementation(IdentityRegistry, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeAllow: ["constructor", "external-library-linking"], - }), - ).to.not.be.reverted; - }); - }); - - describe("Implementation Validation", function () { - it("should validate PCR0Manager implementation", async function () { - const PCR0Manager = await ethers.getContractFactory("PCR0Manager"); - - // PCR0Manager is not upgradeable, but we can still validate it's safe - await expect(PCR0Manager.deploy()).to.not.be.reverted; - }); - - it("should validate VerifyAll implementation", async function () { - const VerifyAll = await ethers.getContractFactory("VerifyAll"); - const mockHub = ethers.Wallet.createRandom().address; - const mockRegistry = ethers.Wallet.createRandom().address; - - // VerifyAll is not upgradeable, but we can still validate deployment - await expect(VerifyAll.deploy(mockHub, mockRegistry)).to.not.be.reverted; - }); - }); - - describe("Library Compatibility", function () { - it("should validate CustomVerifier library is upgrade-safe", async function () { - const CustomVerifier = await ethers.getContractFactory("CustomVerifier"); - - // Libraries should deploy without issues - await expect(CustomVerifier.deploy()).to.not.be.reverted; - }); - - it("should validate library linking in upgraded contracts", async function () { - // Deploy library - const CustomVerifier = await ethers.getContractFactory("CustomVerifier"); - const customVerifier = await CustomVerifier.deploy(); - await customVerifier.waitForDeployment(); - - // Deploy contract with library - const IdentityVerificationHub = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - // Should deploy successfully with library linking - const proxy = await upgrades.deployProxy(IdentityVerificationHub, [], { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeAllowConstructors: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking", "storage-check"], - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - await expect(proxy.waitForDeployment()).to.not.be.reverted; - }); - }); - - describe("Initialization Safety", function () { - it("should validate governance initialization is safe", async function () { - const PCR0Manager = await ethers.getContractFactory("PCR0Manager"); - - // Should initialize with valid addresses - const pcr0Manager = await PCR0Manager.deploy(); - - await pcr0Manager.waitForDeployment(); - - // Verify initialization worked correctly - const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; - const OPERATIONS_ROLE = ethers.keccak256(ethers.toUtf8Bytes("OPERATIONS_ROLE")); - - // PCR0Manager now grants initial roles to deployer - const SECURITY_ROLE = ethers.keccak256(ethers.toUtf8Bytes("SECURITY_ROLE")); - expect(await pcr0Manager.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - expect(await pcr0Manager.hasRole(OPERATIONS_ROLE, deployer.address)).to.be.true; - }); - - it("should prevent initialization with zero addresses", async function () { - const PCR0Manager = await ethers.getContractFactory("PCR0Manager"); - - // PCR0Manager no longer takes constructor arguments, so this test is no longer relevant - // The contract now grants initial roles to msg.sender (deployer) - const pcr0Manager = await PCR0Manager.deploy(); - await pcr0Manager.waitForDeployment(); - - // Verify deployer has initial roles - const SECURITY_ROLE = ethers.keccak256(ethers.toUtf8Bytes("SECURITY_ROLE")); - expect(await pcr0Manager.hasRole(SECURITY_ROLE, deployer.address)).to.be.true; - }); - }); - - describe("Proxy Compatibility", function () { - it("should validate UUPS proxy compatibility", async function () { - // Deploy a test contract that inherits from ImplRoot - const TestContract = await ethers.getContractFactory("MockImplRoot"); - - // Should deploy as UUPS proxy successfully - const proxy = await upgrades.deployProxy(TestContract, [], { - kind: "uups", - initializer: "exposed__ImplRoot_init()", - unsafeAllowConstructors: true, - unsafeSkipStorageCheck: true, - }); - - await expect(proxy.waitForDeployment()).to.not.be.reverted; - }); - - it("should validate proxy admin functions work correctly", async function () { - const TestContract = await ethers.getContractFactory("MockImplRoot"); - - const proxy = await upgrades.deployProxy(TestContract, [], { - kind: "uups", - initializer: "exposed__ImplRoot_init()", - unsafeAllowConstructors: true, - unsafeSkipStorageCheck: true, - }); - - await proxy.waitForDeployment(); - - // Verify proxy admin functions are accessible - const proxyAddress = await proxy.getAddress(); - expect(proxyAddress).to.not.equal(ethers.ZeroAddress); - }); - }); - - describe("Gas Usage Validation", function () { - it("should validate upgrade gas costs are reasonable", async function () { - // Deploy initial implementation - const CustomVerifier = await ethers.getContractFactory("CustomVerifier"); - const customVerifier = await CustomVerifier.deploy(); - await customVerifier.waitForDeployment(); - - const IdentityVerificationHub = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - const proxy = await upgrades.deployProxy(IdentityVerificationHub, [], { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeAllowConstructors: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking", "storage-check"], - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - await proxy.waitForDeployment(); - - // Upgrade and measure gas - const NewImplementation = await ethers.getContractFactory("IdentityVerificationHubImplV2", { - libraries: { - CustomVerifier: await customVerifier.getAddress(), - }, - }); - - const upgradeTx = await upgrades.upgradeProxy(await proxy.getAddress(), NewImplementation, { - kind: "uups", - unsafeAllowLinkedLibraries: true, - unsafeAllowConstructors: true, - unsafeSkipStorageCheck: true, - unsafeAllow: ["constructor", "external-library-linking", "storage-check"], - }); - - const receipt = await upgradeTx.deploymentTransaction()?.wait(); - - // Verify gas usage is reasonable (adjust threshold as needed) - if (receipt) { - expect(receipt.gasUsed).to.be.lessThan(ethers.parseUnits("5000000", "wei")); // 5M gas limit - } - }); - }); -}); diff --git a/contracts/test/integration/endToEnd.test.ts b/contracts/test/integration/endToEnd.test.ts index 7cf10c1f3..8ac17b780 100644 --- a/contracts/test/integration/endToEnd.test.ts +++ b/contracts/test/integration/endToEnd.test.ts @@ -2,27 +2,55 @@ import { expect } from "chai"; import { BigNumberish, TransactionReceipt } from "ethers"; import { ethers } from "hardhat"; import { poseidon2 } from "poseidon-lite"; +import { createHash } from "crypto"; import { CIRCUIT_CONSTANTS, DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants"; import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs"; import { castFromScope } from "@selfxyz/common/utils/circuits/uuid"; import { ATTESTATION_ID } from "../utils/constants"; -import { deploySystemFixtures } from "../utils/deployment"; +import { deploySystemFixturesV2 } from "../utils/deploymentV2"; import BalanceTree from "../utils/example/balance-tree"; import { Formatter } from "../utils/formatter"; import { generateDscProof, generateRegisterProof, generateVcAndDiscloseProof } from "../utils/generateProof"; -import { LeanIMT } from "@openpassport/zk-kit-lean-imt"; import serialized_dsc_tree from "../../../common/pubkeys/serialized_dsc_tree.json"; -import { DeployedActors, VcAndDiscloseHubProof } from "../utils/types"; +import { DeployedActorsV2 } from "../utils/types"; import { generateRandomFieldElement, splitHexFromBack } from "../utils/utils"; +// Helper function to calculate user identifier hash +function calculateUserIdentifierHash(userContextData: string): string { + const sha256Hash = createHash("sha256") + .update(Buffer.from(userContextData.slice(2), "hex")) + .digest(); + const ripemdHash = createHash("ripemd160").update(sha256Hash).digest(); + return "0x" + ripemdHash.toString("hex").padStart(40, "0"); +} + +// Helper function to create V2 proof data format for verifySelfProof +function createV2ProofData(proof: any, userAddress: string, userData: string = "airdrop-user-data") { + const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32); + const userContextData = ethers.solidityPacked( + ["bytes32", "bytes32", "bytes"], + [destChainId, ethers.zeroPadValue(userAddress, 32), ethers.toUtf8Bytes(userData)], + ); + + const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(ATTESTATION_ID.E_PASSPORT)), 32); + const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode( + ["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"], + [[proof.a, proof.b, proof.c, proof.pubSignals]], + ); + + const proofData = ethers.solidityPacked(["bytes32", "bytes"], [attestationId, encodedProof]); + + return { proofData, userContextData }; +} + describe("End to End Tests", function () { this.timeout(0); - let deployedActors: DeployedActors; + let deployedActors: DeployedActorsV2; let snapshotId: string; before(async () => { - deployedActors = await deploySystemFixtures(); + deployedActors = await deploySystemFixturesV2(); snapshotId = await ethers.provider.send("evm_snapshot", []); }); @@ -32,7 +60,10 @@ describe("End to End Tests", function () { }); it("register dsc key commitment, register identity commitment, verify commitment and disclose attrs and claim airdrop", async () => { - const { hub, registry, mockPassport, owner, user1 } = deployedActors; + const { hub, registry, mockPassport, owner, user1, testSelfVerificationRoot, poseidonT3 } = deployedActors; + + // V2 hub requires attestationId as bytes32 + const attestationIdBytes32 = ethers.zeroPadValue(ethers.toBeHex(BigInt(ATTESTATION_ID.E_PASSPORT)), 32); // register dsc key // To increase test performance, we will just set one dsc key with groth16 proof @@ -45,7 +76,11 @@ describe("End to End Tests", function () { if (BigInt(dscKeys[0][i]) == dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]) { const previousRoot = await registry.getDscKeyCommitmentMerkleRoot(); const previousSize = await registry.getDscKeyCommitmentTreeSize(); - registerDscTx = await hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof); + registerDscTx = await hub.registerDscKeyCommitment( + attestationIdBytes32, + DscVerifierId.dsc_sha256_rsa_65537_4096, + dscProof, + ); const receipt = (await registerDscTx.wait()) as TransactionReceipt; const event = receipt?.logs.find( (log) => log.topics[0] === registry.interface.getEvent("DscKeyCommitmentRegistered").topicHash, @@ -90,7 +125,8 @@ describe("End to End Tests", function () { const imt = new LeanIMT(hashFunction); await imt.insert(BigInt(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX])); - const tx = await hub.registerPassportCommitment( + const tx = await hub.registerCommitment( + attestationIdBytes32, RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096, registerProof, ); @@ -104,7 +140,7 @@ describe("End to End Tests", function () { registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX], ); const identityNullifier = await registry.nullifiers( - ATTESTATION_ID.E_PASSPORT, + attestationIdBytes32, registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX], ); @@ -134,11 +170,25 @@ describe("End to End Tests", function () { reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))), ); + // Get the scope from testSelfVerificationRoot + const testRootScope = await testSelfVerificationRoot.scope(); + + // Calculate user identifier hash for verification + const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32); + const user1Address = await user1.getAddress(); + const userData = ethers.toUtf8Bytes("test-user-data"); + const tempUserContextData = ethers.solidityPacked( + ["bytes32", "bytes32", "bytes"], + [destChainId, ethers.zeroPadValue(user1Address, 32), userData], + ); + const userIdentifierHash = calculateUserIdentifierHash(tempUserContextData); + + // Generate proof for V2 verification const vcAndDiscloseProof = await generateVcAndDiscloseProof( registerSecret, BigInt(ATTESTATION_ID.E_PASSPORT).toString(), mockPassport, - "test-scope", + testRootScope.toString(), new Array(88).fill("1"), "1", imt, @@ -148,59 +198,114 @@ describe("End to End Tests", function () { undefined, undefined, forbiddenCountriesList, - (await user1.getAddress()).slice(2), + userIdentifierHash, ); - const vcAndDiscloseHubProof: VcAndDiscloseHubProof = { + // Set up verification config for testSelfVerificationRoot + const verificationConfigV2 = { olderThanEnabled: true, olderThan: "20", forbiddenCountriesEnabled: true, - forbiddenCountriesListPacked: countriesListPacked, + forbiddenCountriesListPacked: countriesListPacked as [BigNumberish, BigNumberish, BigNumberish, BigNumberish], ofacEnabled: [true, true, true] as [boolean, boolean, boolean], - vcAndDiscloseProof: vcAndDiscloseProof, }; - const result = await hub.verifyVcAndDisclose(vcAndDiscloseHubProof); + await testSelfVerificationRoot.setVerificationConfig(verificationConfigV2); - expect(result.identityCommitmentRoot).to.equal( - vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX], + // Create V2 proof format and verify via testSelfVerificationRoot + const { proofData, userContextData: verifyUserContextData } = createV2ProofData( + vcAndDiscloseProof, + user1Address, + "test-user-data", ); - expect(result.revealedDataPacked).to.have.lengthOf(3); - expect(result.nullifier).to.equal(vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX]); - expect(result.attestationId).to.equal( - vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX], + + // Reset test state before verification + await testSelfVerificationRoot.resetTestState(); + + // Verify the proof through V2 architecture + await testSelfVerificationRoot.connect(user1).verifySelfProof(proofData, verifyUserContextData); + + // Check verification was successful + expect(await testSelfVerificationRoot.verificationSuccessful()).to.equal(true); + + // Get the verification output and verify it + const lastOutput = await testSelfVerificationRoot.lastOutput(); + expect(lastOutput).to.not.equal("0x"); + + // Verify attestationId matches both the expected bytes32 and the proof pubSignals + expect(lastOutput.attestationId).to.equal(attestationIdBytes32); + expect(lastOutput.attestationId).to.equal( + ethers.zeroPadValue( + ethers.toBeHex(vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]), + 32, + ), ); - expect(result.userIdentifier).to.equal( - vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX], + + // Verify nullifier matches the proof pubSignals + expect(lastOutput.nullifier).to.equal( + vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX], ); - expect(result.scope).to.equal(vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_SCOPE_INDEX]); - for (let i = 0; i < 4; i++) { - expect(result.forbiddenCountriesListPacked[i]).to.equal(BigInt(countriesListPacked[i])); - } + + // Verify userIdentifier is set + expect(lastOutput.userIdentifier).to.not.equal(0n); + + // Verify olderThan value + expect(lastOutput.olderThan).to.equal(20n); const tokenFactory = await ethers.getContractFactory("AirdropToken"); const token = await tokenFactory.connect(owner).deploy(); await token.waitForDeployment(); const airdropFactory = await ethers.getContractFactory("Airdrop"); - const airdrop = await airdropFactory.connect(owner).deploy( - hub.target, - castFromScope("test-scope"), - ATTESTATION_ID.E_PASSPORT, - token.target, - true, - 20, - // @ts-expect-error - true, - countriesListPacked as [BigNumberish, BigNumberish, BigNumberish, BigNumberish], - [true, true, true], - ); + const airdrop = await airdropFactory.connect(owner).deploy(hub.target, "test-scope", token.target); await airdrop.waitForDeployment(); + // Set up verification config for the airdrop + const configTx = await hub.connect(owner).setVerificationConfigV2(verificationConfigV2); + const configReceipt = await configTx.wait(); + const configId = configReceipt!.logs[0].topics[1]; + + // Set the config ID in the airdrop contract + await airdrop.connect(owner).setConfigId(configId); + await token.connect(owner).mint(airdrop.target, BigInt(1000000000000000000)); + // Generate proof with the airdrop's actual scope + const airdropScope = await airdrop.scope(); + + // Calculate the user identifier hash for the airdrop proof + const airdropUserData = ethers.toUtf8Bytes("airdrop-user-data"); + const airdropTempUserContextData = ethers.solidityPacked( + ["bytes32", "bytes32", "bytes"], + [destChainId, ethers.zeroPadValue(user1Address, 32), airdropUserData], + ); + const airdropUserIdentifierHash = calculateUserIdentifierHash(airdropTempUserContextData); + + const airdropVcAndDiscloseProof = await generateVcAndDiscloseProof( + registerSecret, + BigInt(ATTESTATION_ID.E_PASSPORT).toString(), + mockPassport, + airdropScope.toString(), + new Array(88).fill("1"), + "1", + imt, + "20", + undefined, + undefined, + undefined, + undefined, + forbiddenCountriesList, + airdropUserIdentifierHash, + ); + await airdrop.connect(owner).openRegistration(); - await airdrop.connect(user1).verifySelfProof(vcAndDiscloseProof); + + // Create V2 proof format for verifySelfProof + const { proofData: airdropProofData, userContextData: airdropUserContextData } = createV2ProofData( + airdropVcAndDiscloseProof, + await user1.getAddress(), + ); + await airdrop.connect(user1).verifySelfProof(airdropProofData, airdropUserContextData); await airdrop.connect(owner).closeRegistration(); const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]); @@ -228,19 +333,13 @@ describe("End to End Tests", function () { const isClaimed = await airdrop.claimed(await user1.getAddress()); expect(isClaimed).to.be.true; - const readableData = await hub.getReadableRevealedData( - [result.revealedDataPacked[0], result.revealedDataPacked[1], result.revealedDataPacked[2]], - ["0", "1", "2", "3", "4", "5", "6", "7", "8"], - ); - - expect(readableData[0]).to.equal("FRA"); - expect(readableData[1]).to.deep.equal(["ALPHONSE HUGHUES ALBERT", "DUPONT"]); - expect(readableData[2]).to.equal("15AA81234"); - expect(readableData[3]).to.equal("FRA"); - expect(readableData[4]).to.equal("31-01-94"); - expect(readableData[5]).to.equal("M"); - expect(readableData[6]).to.equal("31-10-40"); - expect(readableData[7]).to.equal(20n); - expect(readableData[8]).to.equal(1n); + // Verify disclosed attributes from lastOutput + expect(lastOutput.issuingState).to.equal("FRA"); + expect(lastOutput.idNumber).to.equal("15AA81234"); + expect(lastOutput.nationality).to.equal("FRA"); + expect(lastOutput.dateOfBirth).to.equal("31-01-94"); + expect(lastOutput.gender).to.equal("M"); + expect(lastOutput.expiryDate).to.equal("31-10-40"); + expect(lastOutput.olderThan).to.equal(20n); }); }); diff --git a/contracts/test/integration/vcAndDisclose.test.ts b/contracts/test/integration/vcAndDisclose.test.ts index 90a005fbb..4a6292ea4 100644 --- a/contracts/test/integration/vcAndDisclose.test.ts +++ b/contracts/test/integration/vcAndDisclose.test.ts @@ -11,8 +11,9 @@ import { BigNumberish } from "ethers"; import { generateRandomFieldElement, getStartOfDayTimestamp, splitHexFromBack } from "../utils/utils"; import { Formatter, CircuitAttributeHandler } from "../utils/formatter"; import { formatCountriesList, reverseBytes, reverseCountryBytes } from "@selfxyz/common/utils/circuits/formatInputs"; -import { getPackedForbiddenCountries } from "@selfxyz/common/utils/sanctions"; +import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries"; import { countries, Country3LetterCode } from "@selfxyz/common/constants/countries"; +import { castFromScope } from "@selfxyz/common/utils/circuits/uuid"; import path from "path"; describe("VC and Disclose", () => { @@ -100,7 +101,7 @@ describe("VC and Disclose", () => { registerSecret, BigInt(ATTESTATION_ID.E_PASSPORT).toString(), deployedActors.mockPassport, - "test-scope", + castFromScope("test-scope"), new Array(88).fill("1"), "1", imt, @@ -110,7 +111,7 @@ describe("VC and Disclose", () => { undefined, undefined, forbiddenCountriesList, - (await deployedActors.user1.getAddress()).slice(2), + await deployedActors.user1.getAddress(), ); snapshotId = await ethers.provider.send("evm_snapshot", []); }); @@ -439,6 +440,7 @@ describe("VC and Disclose", () => { const { hub, registry, owner, mockPassport } = deployedActors; const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]); + const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT); const imt = new LeanIMT(hashFunction); imt.insert(BigInt(commitment)); @@ -448,7 +450,7 @@ describe("VC and Disclose", () => { registerSecret, BigInt(ATTESTATION_ID.E_PASSPORT).toString(), mockPassport, - "test-scope", + castFromScope("test-scope"), new Array(88).fill("1"), "1", imt, @@ -746,43 +748,60 @@ describe("VC and Disclose", () => { it("should parse forbidden countries with CircuitAttributeHandler", async () => { const { hub } = deployedActors; - const forbiddenCountriesListPacked = splitHexFromBack( - reverseCountryBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))), - ); + const localForbiddenCountriesList = ["AFG", "ABC", "CBA"] as const; + const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]); const readableForbiddenCountries = await hub.getReadableForbiddenCountries(forbiddenCountriesListPacked); - expect(readableForbiddenCountries[0]).to.equal(forbiddenCountriesList[0]); - expect(readableForbiddenCountries[1]).to.equal(forbiddenCountriesList[1]); - expect(readableForbiddenCountries[2]).to.equal(forbiddenCountriesList[2]); + expect(readableForbiddenCountries[0]).to.equal(localForbiddenCountriesList[0]); + expect(readableForbiddenCountries[1]).to.equal(localForbiddenCountriesList[1]); + expect(readableForbiddenCountries[2]).to.equal(localForbiddenCountriesList[2]); }); it("should return maximum length of forbidden countries", async () => { const { hub } = deployedActors; - const forbiddenCountriesList = ["AAA", "FRA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA"]; - const forbiddenCountriesListPacked = splitHexFromBack( - reverseCountryBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))), - ); + const localForbiddenCountriesList = [ + "AAA", + "FRA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + ] as const; + const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]); const readableForbiddenCountries = await hub.getReadableForbiddenCountries(forbiddenCountriesListPacked); expect(readableForbiddenCountries.length).to.equal(40); - expect(readableForbiddenCountries[0]).to.equal(forbiddenCountriesList[0]); - expect(readableForbiddenCountries[1]).to.equal(forbiddenCountriesList[1]); - expect(readableForbiddenCountries[2]).to.equal(forbiddenCountriesList[2]); - expect(readableForbiddenCountries[3]).to.equal(forbiddenCountriesList[3]); - expect(readableForbiddenCountries[4]).to.equal(forbiddenCountriesList[4]); - expect(readableForbiddenCountries[5]).to.equal(forbiddenCountriesList[5]); - expect(readableForbiddenCountries[6]).to.equal(forbiddenCountriesList[6]); - expect(readableForbiddenCountries[7]).to.equal(forbiddenCountriesList[7]); - expect(readableForbiddenCountries[8]).to.equal(forbiddenCountriesList[8]); - expect(readableForbiddenCountries[9]).to.equal(forbiddenCountriesList[9]); + expect(readableForbiddenCountries[0]).to.equal(localForbiddenCountriesList[0]); + expect(readableForbiddenCountries[1]).to.equal(localForbiddenCountriesList[1]); + expect(readableForbiddenCountries[2]).to.equal(localForbiddenCountriesList[2]); + expect(readableForbiddenCountries[3]).to.equal(localForbiddenCountriesList[3]); + expect(readableForbiddenCountries[4]).to.equal(localForbiddenCountriesList[4]); + expect(readableForbiddenCountries[5]).to.equal(localForbiddenCountriesList[5]); + expect(readableForbiddenCountries[6]).to.equal(localForbiddenCountriesList[6]); + expect(readableForbiddenCountries[7]).to.equal(localForbiddenCountriesList[7]); + expect(readableForbiddenCountries[8]).to.equal(localForbiddenCountriesList[8]); + expect(readableForbiddenCountries[9]).to.equal(localForbiddenCountriesList[9]); }); it("should fail when getReadableForbiddenCountries is called by non-proxy", async () => { const { hubImpl } = deployedActors; - const forbiddenCountriesList = ["AAA", "FRA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA", "CBA"]; - const forbiddenCountriesListPacked = splitHexFromBack( - reverseCountryBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))), - ); + const localForbiddenCountriesList = [ + "AAA", + "FRA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + "CBA", + ] as const; + const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]); await expect(hubImpl.getReadableForbiddenCountries(forbiddenCountriesListPacked)).to.be.revertedWithCustomError( hubImpl, "UUPSUnauthorizedCallContext", diff --git a/contracts/test/integration/verifyAll.test.ts b/contracts/test/integration/verifyAll.test.ts index 2d63c9472..bb5d7aac4 100644 --- a/contracts/test/integration/verifyAll.test.ts +++ b/contracts/test/integration/verifyAll.test.ts @@ -10,6 +10,7 @@ import { poseidon2 } from "poseidon-lite"; import { generateVcAndDiscloseProof, parseSolidityCalldata } from "../utils/generateProof"; import { Formatter } from "../utils/formatter"; import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs"; +import { stringToBigInt } from "@selfxyz/common/utils/scope"; import { VerifyAll } from "../../typechain-types"; import { getSMTs } from "../utils/generateProof"; import { Groth16Proof, PublicSignals, groth16 } from "snarkjs"; @@ -102,7 +103,7 @@ describe("VerifyAll", () => { registerSecret, BigInt(ATTESTATION_ID.E_PASSPORT).toString(), deployedActors.mockPassport, - "test-scope", + stringToBigInt("test-scope").toString(), new Array(88).fill("1"), "1", imt, @@ -112,7 +113,7 @@ describe("VerifyAll", () => { undefined, undefined, forbiddenCountriesList, - (await deployedActors.user1.getAddress()).slice(2), + await deployedActors.user1.getAddress(), ); snapshotId = await ethers.provider.send("evm_snapshot", []); }); @@ -293,7 +294,7 @@ describe("VerifyAll", () => { registerSecret, BigInt(ATTESTATION_ID.E_PASSPORT).toString(), deployedActors.mockPassport, - "test-scope", + stringToBigInt("test-scope").toString(), new Array(88).fill("1"), "1", imt, diff --git a/contracts/test/unit/IdentityRegistry.test.ts b/contracts/test/unit/IdentityRegistry.test.ts index 82f038b86..b70f6a3f9 100644 --- a/contracts/test/unit/IdentityRegistry.test.ts +++ b/contracts/test/unit/IdentityRegistry.test.ts @@ -357,7 +357,7 @@ describe("Unit Tests for IdentityRegistry", () => { await expect(registry.connect(user1).updateHub(newHubAddress)).to.be.revertedWithCustomError( registry, - "OwnableUnauthorizedAccount", + "AccessControlUnauthorizedAccount", ); }); @@ -498,7 +498,7 @@ describe("Unit Tests for IdentityRegistry", () => { await expect( registry.connect(user1).devAddIdentityCommitment(attestationId, nullifier, commitment), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not add commitment if caller is not proxy", async () => { @@ -546,7 +546,7 @@ describe("Unit Tests for IdentityRegistry", () => { const newCommitment = generateRandomFieldElement(); await expect( registry.connect(user1).devUpdateCommitment(commitment, newCommitment, []), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not update commitment if caller is not proxy", async () => { @@ -592,7 +592,7 @@ describe("Unit Tests for IdentityRegistry", () => { await registry.devAddIdentityCommitment(attestationId, nullifier, commitment); await expect(registry.connect(user1).devRemoveCommitment(commitment, [])).to.be.revertedWithCustomError( registry, - "OwnableUnauthorizedAccount", + "AccessControlUnauthorizedAccount", ); }); @@ -632,7 +632,7 @@ describe("Unit Tests for IdentityRegistry", () => { const dscCommitment = generateRandomFieldElement(); await expect(registry.connect(user1).devAddDscKeyCommitment(dscCommitment)).to.be.revertedWithCustomError( registry, - "OwnableUnauthorizedAccount", + "AccessControlUnauthorizedAccount", ); }); @@ -673,7 +673,7 @@ describe("Unit Tests for IdentityRegistry", () => { await registry.devAddDscKeyCommitment(dscCommitment); await expect( registry.connect(user1).devUpdateDscKeyCommitment(dscCommitment, newDscCommitment, []), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not update dsc key commitment if caller is not proxy", async () => { @@ -711,7 +711,7 @@ describe("Unit Tests for IdentityRegistry", () => { await registry.devAddDscKeyCommitment(dscCommitment); await expect(registry.connect(user1).devRemoveDscKeyCommitment(dscCommitment, [])).to.be.revertedWithCustomError( registry, - "OwnableUnauthorizedAccount", + "AccessControlUnauthorizedAccount", ); }); @@ -751,7 +751,7 @@ describe("Unit Tests for IdentityRegistry", () => { const nullifier = generateRandomFieldElement(); await expect( registry.connect(user1).devChangeNullifierState(attestationId, nullifier, false), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not change nullifier state if caller is not proxy", async () => { @@ -789,7 +789,7 @@ describe("Unit Tests for IdentityRegistry", () => { const state = true; await expect( registry.connect(user1).devChangeDscKeyCommitmentState(dscCommitment, state), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not change dsc key commitment state if caller is not proxy", async () => { @@ -915,7 +915,7 @@ describe("Unit Tests for IdentityRegistry", () => { await expect( registry.connect(user1).upgradeToAndCall(registryV2Implementation.target, "0x"), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + ).to.be.revertedWithCustomError(registry, "AccessControlUnauthorizedAccount"); }); it("should not allow implementation contract to be initialized directly", async () => { diff --git a/contracts/test/v2/registerAadhaar.test.ts b/contracts/test/v2/registerAadhaar.test.ts index 062ab2508..71ab60de0 100644 --- a/contracts/test/v2/registerAadhaar.test.ts +++ b/contracts/test/v2/registerAadhaar.test.ts @@ -149,9 +149,11 @@ describe("Aadhaar Registration test", function () { }); it("should not fail if timestamp is within 20 minutes", async () => { - // Fix the AADHAAR_REGISTRATION_WINDOW that was incorrectly set to 0 await deployedActors.hub.setAadhaarRegistrationWindow(20); + const latestBlock = await ethers.provider.getBlock("latest"); + const blockTimestamp = latestBlock!.timestamp; + const newAadhaarData = prepareAadhaarRegisterTestData( privateKeyPem, pubkeyPem, @@ -161,8 +163,7 @@ describe("Aadhaar Registration test", function () { "M", "110051", "WB", - //timestamp 10 minutes ago and converted to timestamp string - new Date(Date.now() - 10 * 60 * 1000).getTime().toString(), + (blockTimestamp - 10 * 60).toString(), ); const newRegisterProof = await generateRegisterAadhaarProof(registerSecret, newAadhaarData.inputs); @@ -171,9 +172,11 @@ describe("Aadhaar Registration test", function () { }); it("should fail with InvalidUidaiTimestamp when UIDAI timestamp is not within 20 minutes of current time", async () => { - // Fix the AADHAAR_REGISTRATION_WINDOW that was incorrectly set to 0 await deployedActors.hub.setAadhaarRegistrationWindow(20); + const latestBlock = await ethers.provider.getBlock("latest"); + const blockTimestamp = latestBlock!.timestamp; + const newAadhaarData = prepareAadhaarRegisterTestData( privateKeyPem, pubkeyPem, @@ -183,8 +186,7 @@ describe("Aadhaar Registration test", function () { "M", "110051", "WB", - //timestamp 30 minutes ago and converted to timestamp string - new Date(Date.now() - 30 * 60 * 1000).getTime().toString(), + (blockTimestamp - 30 * 60).toString(), ); const newRegisterProof = await generateRegisterAadhaarProof(registerSecret, newAadhaarData.inputs);