mirror of
https://github.com/selfxyz/self.git
synced 2026-01-07 22:04:03 -05:00
* 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
230 lines
8.8 KiB
TypeScript
230 lines
8.8 KiB
TypeScript
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
|
|
}
|
|
});
|
|
});
|
|
});
|