diff --git a/contracts/contracts/IdentityVerificationHubImplV2.sol b/contracts/contracts/IdentityVerificationHubImplV2.sol index f2fdc9e79..41e2fb608 100644 --- a/contracts/contracts/IdentityVerificationHubImplV2.sol +++ b/contracts/contracts/IdentityVerificationHubImplV2.sol @@ -29,7 +29,7 @@ import {console} from "hardhat/console.sol"; * @dev This contract orchestrates multi-step verification processes including document attestation, * zero-knowledge proofs, OFAC compliance, and attribute disclosure control. * - * @custom:version 2.12.0 + * @custom:version 2.13.0 */ contract IdentityVerificationHubImplV2 is ImplRoot { /// @custom:storage-location erc7201:self.storage.IdentityVerificationHub diff --git a/contracts/deployments/registry.json b/contracts/deployments/registry.json index 133ef0e65..be3f85f1f 100644 --- a/contracts/deployments/registry.json +++ b/contracts/deployments/registry.json @@ -1,6 +1,6 @@ { "$schema": "./registry.schema.json", - "lastUpdated": "2025-12-10T06:17:50.863Z", + "lastUpdated": "2026-02-02T14:47:21.978Z", "contracts": { "IdentityVerificationHub": { "source": "IdentityVerificationHubImplV2", @@ -73,6 +73,22 @@ } } }, + "celo-sepolia": { + "chainId": 11142220, + "governance": { + "securityMultisig": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B", + "operationsMultisig": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B", + "securityThreshold": "1/1", + "operationsThreshold": "1/1" + }, + "deployments": { + "IdentityVerificationHub": { + "proxy": "0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74", + "currentVersion": "2.13.0", + "currentImpl": "0x244c93516Abd58E1952452d3D8C4Ce7D454776B8" + } + } + }, "localhost": { "chainId": 31337, "governance": { @@ -97,6 +113,12 @@ "deployedAt": "2025-12-10T05:43:58.258Z", "deployedBy": "0xCaEe7aAF115F04D836E2D362A7c07F04db436bd0", "gitCommit": "" + }, + "celo-sepolia": { + "impl": "0x92d637c5e6EFa17320B663f97cc4d44176984dAd", + "deployedAt": "2026-02-02T13:39:44.500Z", + "deployedBy": "0x846F1cF04ec494303e4B90440b130bb01913E703", + "gitCommit": "61a41950" } } }, @@ -111,6 +133,26 @@ "deployedAt": "", "deployedBy": "", "gitCommit": "" + }, + "celo-sepolia": { + "impl": "0x48985ec4f71cBC8f387c5C77143110018560c7eD", + "deployedAt": "", + "deployedBy": "0x846f1cf04ec494303e4b90440b130bb01913e703", + "gitCommit": "" + } + } + }, + "2.13.0": { + "initializerVersion": 12, + "initializerFunction": "", + "changelog": "Upgrade to v2.13.0", + "gitTag": "identityverificationhub-v2.13.0", + "deployments": { + "celo-sepolia": { + "impl": "0x244c93516Abd58E1952452d3D8C4Ce7D454776B8", + "deployedAt": "2026-02-02T14:47:21.882Z", + "deployedBy": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B", + "gitCommit": "33bca485" } } } diff --git a/contracts/tasks/upgrade/upgrade.ts b/contracts/tasks/upgrade/upgrade.ts index 41741079d..376b7ddaf 100644 --- a/contracts/tasks/upgrade/upgrade.ts +++ b/contracts/tasks/upgrade/upgrade.ts @@ -37,7 +37,7 @@ import { getLatestVersionInfo, getVersionInfo, getGovernanceConfig, - validateReinitializerVersion, + readReinitializerVersion, } from "./utils"; import { execSync } from "child_process"; import * as readline from "readline"; @@ -65,7 +65,7 @@ async function promptYesNo(question: string): Promise { */ const CHAIN_CONFIG: Record = { celo: { chainId: 42220, safePrefix: "celo" }, - "celo-sepolia": { chainId: 44787, safePrefix: "celo" }, + "celo-sepolia": { chainId: 11142220, safePrefix: "celo" }, sepolia: { chainId: 11155111, safePrefix: "sep" }, localhost: { chainId: 31337, safePrefix: "eth" }, }; @@ -314,39 +314,54 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" // ======================================================================== log.step("Checking reinitializer version..."); - // Check if target version already exists in registry const targetVersionInfo = getVersionInfo(contractId, newVersion); const latestVersionInfo = getLatestVersionInfo(contractId); + const latestInitVersion = latestVersionInfo?.info.initializerVersion || 0; - // If target version exists, use its initializerVersion; otherwise increment latest - const expectedInitializerVersion = targetVersionInfo - ? targetVersionInfo.initializerVersion - : (latestVersionInfo?.info.initializerVersion || 0) + 1; + let actualReinitVersion: number | null = null; + let noNewInitializer = false; if (contractFilePath) { - const reinitValidation = validateReinitializerVersion(contractFilePath, expectedInitializerVersion); + actualReinitVersion = readReinitializerVersion(contractFilePath); - if (!reinitValidation.valid) { - log.error(reinitValidation.error!); + if (actualReinitVersion === null) { + log.error("Could not find reinitializer in contract file"); + return; + } + + // If target version already exists in registry, validate against its expected version + if (targetVersionInfo) { + const expected = targetVersionInfo.initializerVersion; + if (actualReinitVersion !== expected) { + log.error(`Reinitializer mismatch: expected ${expected}, found ${actualReinitVersion}`); + return; + } + } + + if (actualReinitVersion === latestInitVersion) { + // No new reinitializer — code-only upgrade + noNewInitializer = true; + log.success(`No new initialization needed (reinitializer stays at ${actualReinitVersion})`); + } else if (actualReinitVersion === latestInitVersion + 1) { + // Standard upgrade with new reinitializer + log.success(`Reinitializer version correct: reinitializer(${actualReinitVersion})`); + } else { + log.error( + `Unexpected reinitializer(${actualReinitVersion}). Expected ${latestInitVersion} (no-init) or ${latestInitVersion + 1} (with init)`, + ); log.box([ "REINITIALIZER VERSION MISMATCH", "═".repeat(50), "", - `Expected: reinitializer(${expectedInitializerVersion})`, - reinitValidation.actual !== null ? `Found: reinitializer(${reinitValidation.actual})` : "Found: none", + `Latest registry version has reinitializer: ${latestInitVersion}`, + `Contract file has reinitializer: ${actualReinitVersion}`, "", - "The initialize function must use the correct reinitializer version.", - "Each upgrade should increment the version by 1.", - "", - "Example pattern:", - ` function initialize(...) external reinitializer(${expectedInitializerVersion}) {`, - " // initialization logic", - " }", + "Valid options:", + ` ${latestInitVersion} — code-only upgrade (no new initialization)`, + ` ${latestInitVersion + 1} — upgrade with new initializer`, ]); return; } - - log.success(`Reinitializer version correct: reinitializer(${reinitValidation.actual})`); } else { log.warning("Could not locate contract file - skipping reinitializer check"); } @@ -389,14 +404,25 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" try { if (contractName === "IdentityVerificationHubImplV2") { - const CustomVerifier = await hre.ethers.getContractFactory("CustomVerifier"); - const customVerifier = await CustomVerifier.deploy(); - await customVerifier.waitForDeployment(); + const libraryNames = [ + "CustomVerifier", + "OutputFormatterLib", + "ProofVerifierLib", + "RegisterProofVerifierLib", + "DscProofVerifierLib", + "RootCheckLib", + "OfacCheckLib", + ]; + const libraries: Record = {}; + for (const libName of libraryNames) { + const LibFactory = await hre.ethers.getContractFactory(libName); + const lib = await LibFactory.deploy(); + await lib.waitForDeployment(); + libraries[libName] = await lib.getAddress(); + log.info(`Deployed library: ${libName} → ${libraries[libName]}`); + } - ContractFactory = await hre.ethers.getContractFactory(contractName, { - libraries: { CustomVerifier: await customVerifier.getAddress() }, - }); - log.info("Deployed CustomVerifier library for linking"); + ContractFactory = await hre.ethers.getContractFactory(contractName, { libraries }); } else if ( contractName === "IdentityRegistryImplV1" || contractName === "IdentityRegistryIdCardImplV1" || @@ -593,7 +619,7 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" log.step("Updating deployment registry..."); const latestVersion = getLatestVersionInfo(contractId); - const newInitializerVersion = (latestVersion?.info.initializerVersion || 0) + 1; + const newInitializerVersion = actualReinitVersion ?? (latestVersion?.info.initializerVersion || 0) + 1; const deployerAddress = (await hre.ethers.provider.getSigner()).address; addVersion( @@ -602,7 +628,7 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" newVersion, { initializerVersion: newInitializerVersion, - initializerFunction: "initialize", // Always "initialize" - version tracked via reinitializer(N) modifier + initializerFunction: noNewInitializer ? "" : "initialize", changelog: changelog || `Upgrade to v${newVersion}`, gitTag: `${contractId.toLowerCase()}-v${newVersion}`, }, @@ -680,18 +706,22 @@ task("upgrade", "Deploy new implementation and create Safe proposal for upgrade" // Encode initializer function call let initData = "0x"; - const targetVersionInfoForInit = getVersionInfo(contractId, newVersion); - const initializerName = targetVersionInfoForInit?.initializerFunction || `initializeV${newInitializerVersion}`; + if (!noNewInitializer) { + const targetVersionInfoForInit = getVersionInfo(contractId, newVersion); + const initializerName = targetVersionInfoForInit?.initializerFunction || `initializeV${newInitializerVersion}`; - try { - const iface = proxyContract.interface; - const initFragment = iface.getFunction(initializerName); - if (initFragment && initFragment.inputs.length === 0) { - initData = iface.encodeFunctionData(initializerName, []); - log.detail("Initializer", initializerName); + try { + const iface = proxyContract.interface; + const initFragment = iface.getFunction(initializerName); + if (initFragment && initFragment.inputs.length === 0) { + initData = iface.encodeFunctionData(initializerName, []); + log.detail("Initializer", initializerName); + } + } catch { + log.detail("Initializer", "None"); } - } catch { - log.detail("Initializer", "None"); + } else { + log.detail("Initializer", "None (code-only upgrade)"); } // Build upgrade transaction data