Files
self/contracts/script/MigratePCR0Manager.s.sol
Evi Nova bc4e52bb1e Refactor/multitiered multisig roles (#1483)
* refactor: switch to multitiered governance with multisigs

* feat: add scripts for assisting with upgrading contracts and

* test: add tests for governance upgrade

* chore: install Foundry with Hardhat compatability

* fix: add separate intializeGovernance function for upgrading

Uses reinitializer modifier for proper security around function call

* feat: migrate new function to AccessControl governance

* test: full end to end upgrade typescript test

* chore: add hardhat-upgrade

* chore: add foundry outputs to gitignore

* test: add Foundry upgrade script and test for deployed contracts

* refactor: update PCR0 inputs to be 32 bytes for GCP image hashes

Still pad to 48 bytes to ensure compatibility with mobile app.

* feat: add PCR0 migration script + test file

* fix: use custom natspec to prevent constructor warnings on upgrade

* test: cleanup tests and add role transfer to upgrade script

* test: add deployed libraries to foundry.toml for proper library linking

* chore: add /contracts/broadcast to gitignore for foundry deployments

* fix: set variable in initializer instead of defining in declaration

* test: improve upgrade test script to check all state variables

* docs: better explain safety behind using unsafeSkipStorageCheck

* doc: add guide for upgrading to AccessControl governance

* style: change multisig role names

CRITICAL_ROLE -> SECURITY_ROLE (3/5)
STANDARD_ROLE -> OPERATIONRS_ROLE (2/5)

* refactor: change OFAC + CSCA root update functions to 2/5 multisig

* fix: package version clashes + outdated code from old ver of packages

OpenZeppelin v5.5.0 no longer requires __UUPS_Upgradeable_Init, new OZ version requires opcodes that need cancun evmVersion, hard defining @noble/hashes led to clashes with other dependencies

* fix: fix PCR0 tests broken from change in byte size

* feat: add contract upgrade tooling with Safe multisig integration

- Add unified 'upgrade' Hardhat task with automatic safety checks
- Add deployment registry for version tracking
- Add Safe SDK integration for auto-proposing upgrades
- Update UPGRADE_GUIDE.md with new workflow documentation
- Validate version increments, reinitializer, and storage layout

* fix: revert fix on Hub V1 contract that is not supported

* style: update upgraded contracts to not use custom:version-history

* fix: V1 test requires old style as well

* fix: correct registry currentVersion to reflect actual deployed versions

On-chain verification confirmed all contracts are using OLD Ownable2StepUpgradeable:
- Hub: 2.11.0 (was incorrectly 2.12.0)
- Registry: 1.1.0 (was incorrectly 1.2.0)
- IdCard: 1.1.0 (was incorrectly 1.2.0)
- Aadhaar: 1.1.0 (was incorrectly 1.2.0)

Owner address: 0xcaee7aaf115f04d836e2d362a7c07f04db436bd0

* fix: upgrade script now correctly handles pre-defined versions in registry

When upgrading to a version that already exists in registry.json (like 2.12.0),
the script now uses that version's initializerVersion instead of incrementing
from the latest version. This fixes the reinitializer validation for the
governance upgrade.

* fix: upgrade script handles Ownable contracts and outputs transaction data

- Detect Ownable pattern before creating Safe proposals
- Output transaction data for owner direct execution in --prepare-only mode
- Use initializerFunction from registry (initializeGovernance) instead of constructing names
- Skip Safe proposal creation for initial Ownable → AccessControl upgrade
- After upgrade, owner grants SECURITY_ROLE to Safe for future upgrades

* feat: IdentityVerificationHub v2.12.0 deployed on Celo

- Implementation: 0x05FB9D7830889cc389E88198f6A224eA87F01151
- Changelog: Governance upgrade

* feat: IdentityRegistryIdCard v1.2.0 deployed on Celo

- Implementation: 0x7d5e4b7D4c3029aF134D50642674Af8F875118a4
- Changelog: Governance upgrade

* feat: IdentityRegistryAadhaar v1.2.0 deployed on Celo

- Implementation: 0xbD861A9cecf7B0A9631029d55A8CE1155e50697c
- Changelog: Governance upgrade

* feat: IdentityRegistry v1.2.0 deployed on Celo

- Implementation: 0x81E7F74560FAF7eE8DE3a36A5a68B6cbc429Cd36
- Changelog: Governance upgrade

* feat: add multisig addresses to registry

* feat: PCR0Manager v1.2.0 deployed on Celo

- Implementation: 0x9743fe2C1c3D2b068c56dE314e9B10DA9c904717
- Changelog: Governance upgrade

* refactor: cleanup old scripts

* chore: yarn prettier formatting
2025-12-10 17:30:50 +10:00

158 lines
7.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";
import {PCR0Manager} from "../contracts/utils/PCR0Manager.sol";
/**
* @title MigratePCR0Manager
* @notice Foundry script to deploy and initialize new PCR0Manager with AccessControl governance
*
* This script:
* 1. Deploys new PCR0Manager (V2 with AccessControl)
* 2. Adds all 7 finalized PCR0 values
* 3. Transfers roles to multisigs
* 4. Deployer renounces all roles
* 5. Verifies final state
*
* Usage:
* - Set in .env file:
* SECURITY_GOVERNANCE_ADDRESS=0x...
* OPERATIONS_GOVERNANCE_ADDRESS=0x...
* - Dry run: forge script script/MigratePCR0Manager.s.sol --fork-url $CELO_RPC_URL -vvv
* - Execute: forge script script/MigratePCR0Manager.s.sol --rpc-url https://forno.celo.org --broadcast --verify -vvv
*/
contract MigratePCR0Manager is Script {
// Governance roles
bytes32 public constant SECURITY_ROLE = keccak256("SECURITY_ROLE");
bytes32 public constant OPERATIONS_ROLE = keccak256("OPERATIONS_ROLE");
// Multisig addresses (from environment)
address securityMultisig;
address operationsMultisig;
function run() external returns (address newPCR0Manager) {
console2.log("================================================================================");
console2.log("PCR0MANAGER DEPLOYMENT: Fresh deployment with AccessControl");
console2.log("================================================================================");
console2.log("\nDeployer:", msg.sender);
console2.log("Chain ID:", block.chainid);
// Get multisig addresses from .env
securityMultisig = vm.envAddress("SECURITY_GOVERNANCE_ADDRESS");
operationsMultisig = vm.envAddress("OPERATIONS_GOVERNANCE_ADDRESS");
require(securityMultisig != address(0), "SECURITY_GOVERNANCE_ADDRESS not set in .env");
require(operationsMultisig != address(0), "OPERATIONS_GOVERNANCE_ADDRESS not set in .env");
console2.log("\nGovernance addresses:");
console2.log(" Critical Multisig:", securityMultisig);
console2.log(" Standard Multisig:", operationsMultisig);
// Get finalized PCR0 values
bytes[] memory pcr0Values = getFinalizedPCR0Values();
console2.log("\nPCR0 values to add:", pcr0Values.length);
vm.startBroadcast();
// Step 1: Deploy PCR0Manager
console2.log("\n=== Step 1: Deploy PCR0Manager ===");
PCR0Manager pcr0Manager = new PCR0Manager();
newPCR0Manager = address(pcr0Manager);
console2.log("Deployed at:", newPCR0Manager);
// Step 2: Add PCR0 values
console2.log("\n=== Step 2: Add PCR0 Values ===");
for (uint256 i = 0; i < pcr0Values.length; i++) {
pcr0Manager.addPCR0(pcr0Values[i]);
console2.log(" Added PCR0", i + 1, "of", pcr0Values.length);
}
// Step 3: Transfer roles to multisigs
console2.log("\n=== Step 3: Transfer Roles to Multisigs ===");
pcr0Manager.grantRole(SECURITY_ROLE, securityMultisig);
pcr0Manager.grantRole(OPERATIONS_ROLE, operationsMultisig);
console2.log(" Granted SECURITY_ROLE to:", securityMultisig);
console2.log(" Granted OPERATIONS_ROLE to:", operationsMultisig);
// Step 4: Deployer renounces roles
console2.log("\n=== Step 4: Deployer Renounces All Roles ===");
pcr0Manager.renounceRole(SECURITY_ROLE, msg.sender);
pcr0Manager.renounceRole(OPERATIONS_ROLE, msg.sender);
console2.log(" Deployer renounced SECURITY_ROLE");
console2.log(" Deployer renounced OPERATIONS_ROLE");
vm.stopBroadcast();
// Step 5: Verify final state
console2.log("\n=== Step 5: Verify Final State ===");
verifyFinalState(pcr0Manager, pcr0Values);
console2.log("\n================================================================================");
console2.log("DEPLOYMENT COMPLETE!");
console2.log("================================================================================");
console2.log("\nNew PCR0Manager:", newPCR0Manager);
console2.log("Total PCR0 values:", pcr0Values.length);
console2.log("Governance:");
console2.log(" Critical Multisig:", securityMultisig);
console2.log(" Standard Multisig:", operationsMultisig);
console2.log("\nNext steps:");
console2.log("1. Update Hub to point to new PCR0Manager");
console2.log("2. Update documentation with new address");
console2.log("3. Verify contract on Celoscan");
return newPCR0Manager;
}
/**
* @notice Returns finalized PCR0 values (32-byte format)
* @dev These will be padded to 48 bytes by PCR0Manager (prefixed with 16 zero bytes)
*/
function getFinalizedPCR0Values() internal pure returns (bytes[] memory) {
bytes[] memory pcr0s = new bytes[](7);
pcr0s[0] = hex"eb71776987d5f057030823f591d160c9d5d5e0a96c9a2a826778be1da2b8302a";
pcr0s[1] = hex"d2221a0ee83901980c607ceff2edbedf3f6ce5f437eafa5d89be39e9e7487c04";
pcr0s[2] = hex"4458aeb87796e92700be2d9c2984e376bce42bd80a4bf679e060d3bdaa6de119";
pcr0s[3] = hex"aa3deefa408710420e8b4ffe5b95f1dafeb4f06cb16ea44ec7353944671c660a";
pcr0s[4] = hex"b31e0df12cd52b961590796511d91a26364dd963c4aa727246b40513e470c232";
pcr0s[5] = hex"26bc53c698f78016ad7c326198d25d309d1487098af3f28fc55e951f903e9596";
pcr0s[6] = hex"b62720bdb510c2830cf9d58caa23912d0b214d6c278bf22e90942a6b69d272af";
return pcr0s;
}
/**
* @notice Verifies the final state of the deployed PCR0Manager
*/
function verifyFinalState(PCR0Manager pcr0Manager, bytes[] memory pcr0Values) internal view {
// Verify all PCR0 values are set (need 48-byte format for checking)
for (uint256 i = 0; i < pcr0Values.length; i++) {
// Pad to 48 bytes
bytes memory padded = abi.encodePacked(new bytes(16), pcr0Values[i]);
bool isSet = pcr0Manager.isPCR0Set(padded);
require(isSet, "PCR0 value not set");
}
console2.log(" [PASS] All", pcr0Values.length, "PCR0 values verified");
// Verify deployer has no roles
bool deployerHasCritical = pcr0Manager.hasRole(SECURITY_ROLE, msg.sender);
bool deployerHasStandard = pcr0Manager.hasRole(OPERATIONS_ROLE, msg.sender);
require(!deployerHasCritical, "Deployer still has SECURITY_ROLE");
require(!deployerHasStandard, "Deployer still has OPERATIONS_ROLE");
console2.log(" [PASS] Deployer has no roles");
// Verify multisigs have roles
bool criticalHasRole = pcr0Manager.hasRole(SECURITY_ROLE, securityMultisig);
bool standardHasRole = pcr0Manager.hasRole(OPERATIONS_ROLE, operationsMultisig);
require(criticalHasRole, "Critical multisig missing SECURITY_ROLE");
require(standardHasRole, "Standard multisig missing OPERATIONS_ROLE");
console2.log(" [PASS] Multisigs have correct roles");
console2.log("\n [SUCCESS] All verifications passed!");
}
}