Upgrade/update celo sepolia hub kyc (#1725)

* fix: update celo-sepolia in upgrade script to correct chain ID

* feat: add celo-sepolia deployed contracts to registry.json

* fix: upgrade script now reads version from contract directly

* fix: add library linking for kyc related contracts in upgrade script

* feat: IdentityVerificationHub v2.13.0 deployed on Celo Sepolia

- Implementation: 0x244c93516Abd58E1952452d3D8C4Ce7D454776B8
- Code-only upgrade (no new initializer), adds KYC support
- Tx: 0xf24d1c2cd4fd0055237f406a9850ce6e24f538ed09e57ff09755ed142fcc903c

* chore: yarn prettier

* Feat/new gcp verifier (#1719)

* feat: new gcp jwt verifier

* lint: contracts

---------

Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com>
This commit is contained in:
Evi Nova
2026-02-10 18:44:50 +10:00
committed by GitHub
parent 2a583f72c7
commit fa253fcf2d
3 changed files with 114 additions and 42 deletions

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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<boolean> {
*/
const CHAIN_CONFIG: Record<SupportedNetwork, { chainId: number; safePrefix: string }> = {
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<string, string> = {};
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