diff --git a/contracts/package.json b/contracts/package.json index 0b55c343..871acc25 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@iden3/contracts", "description": "Smart Contract library for Solidity", - "version": "2.4.4", + "version": "2.4.5", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/state/State.sol b/contracts/state/State.sol index b17d15eb..dbb6f653 100644 --- a/contracts/state/State.sol +++ b/contracts/state/State.sol @@ -16,7 +16,7 @@ contract State is Ownable2StepUpgradeable, IState { /** * @dev Version of contract */ - string public constant VERSION = "2.6.0"; + string public constant VERSION = "2.6.1"; // This empty reserved space is put in place to allow future versions // of the State contract to inherit from other contracts without a risk of @@ -439,24 +439,21 @@ contract State is Ownable2StepUpgradeable, IState { * @param state State of the identity * @return replacedAt The timestamp when the state of the identity was replaced by another state */ - function getStateReplacedAt( - uint256 id, - uint256 state - ) external view returns (uint256 replacedAt) { - StateCrossChainStorage storage $ = _getStateCrossChainStorage(); - replacedAt = $._idToStateReplacedAt[id][state]; - if (replacedAt != 0) { - return replacedAt; - } - - if (_stateData.stateExists(id, state)) { - replacedAt = _stateData.getStateInfoByIdAndState(id, state).replacedAtTimestamp; + function getStateReplacedAt(uint256 id, uint256 state) external view returns (uint256) { + if (isIdTypeSupported(GenesisUtils.getIdType(id))) { + if (_stateData.stateExists(id, state)) { + return _stateData.getStateInfoByIdAndState(id, state).replacedAtTimestamp; + } else if (GenesisUtils.isGenesisState(id, state)) { + return 0; + } + revert("State entry not found"); } else { - if (GenesisUtils.isGenesisState(id, state)) { - replacedAt = 0; - } else { - revert("State entry not found"); + StateCrossChainStorage storage $ = _getStateCrossChainStorage(); + uint256 replacedAt = $._idToStateReplacedAt[id][state]; + if (replacedAt != 0) { + return replacedAt; } + revert("Cross-chain state not found"); } } @@ -466,21 +463,20 @@ contract State is Ownable2StepUpgradeable, IState { * @param root GIST root * @return replacedAt The timestamp when the GIST root was replaced by another root */ - function getGistRootReplacedAt( - bytes2 idType, - uint256 root - ) external view returns (uint256 replacedAt) { - StateCrossChainStorage storage $ = _getStateCrossChainStorage(); - replacedAt = $._rootToGistRootReplacedAt[idType][root]; - if (replacedAt != 0) { - return replacedAt; - } - - require(isIdTypeSupported(idType), "id type is not supported"); - if (!_gistData.rootExists(root)) { - revert("Gist root entry not found"); + function getGistRootReplacedAt(bytes2 idType, uint256 root) external view returns (uint256) { + if (isIdTypeSupported(idType)) { + if (_gistData.rootExists(root)) { + return _gistData.getRootInfo(root).replacedAtTimestamp; + } + revert("GIST root entry not found"); + } else { + StateCrossChainStorage storage $ = _getStateCrossChainStorage(); + uint256 replacedAt = $._rootToGistRootReplacedAt[idType][root]; + if (replacedAt != 0) { + return replacedAt; + } + revert("Cross-chain GIST root not found"); } - replacedAt = _gistData.getRootInfo(root).replacedAtTimestamp; } /** diff --git a/helpers/constants.ts b/helpers/constants.ts index d3bdf179..28d914f3 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -111,6 +111,7 @@ export const contractsInfo = Object.freeze({ }, UNIVERSAL_VERIFIER: { name: "UniversalVerifier", + version: "1.1.1", unifiedAddress: "0xfcc86A79fCb057A8e55C6B853dff9479C3cf607c", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.UniversalVerifier")), verificationOpts: { @@ -127,6 +128,7 @@ export const contractsInfo = Object.freeze({ }, STATE: { name: "State", + version: "2.6.1", unifiedAddress: "0x3C9acB2205Aa72A05F6D77d708b5Cf85FCa3a896", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.State")), verificationOpts: { @@ -142,6 +144,7 @@ export const contractsInfo = Object.freeze({ }, VALIDATOR_SIG: { name: "CredentialAtomicQuerySigV2Validator", + version: "2.1.0", unifiedAddress: "0x59B347f0D3dd4B98cc2E056Ee6C53ABF14F8581b", create2Calldata: ethers.hexlify( ethers.toUtf8Bytes("iden3.create2.CredentialAtomicQuerySigV2Validator"), @@ -159,6 +162,7 @@ export const contractsInfo = Object.freeze({ }, VALIDATOR_MTP: { name: "CredentialAtomicQueryMTPV2Validator", + version: "2.1.0", unifiedAddress: "0x27bDFFCeC5478a648f89764E22fE415486A42Ede", create2Calldata: ethers.hexlify( ethers.toUtf8Bytes("iden3.create2.CredentialAtomicQueryMTPV2Validator"), @@ -176,6 +180,7 @@ export const contractsInfo = Object.freeze({ }, VALIDATOR_V3: { name: "CredentialAtomicQueryV3Validator", + version: "2.1.0-beta.1", unifiedAddress: "0xd179f29d00Cd0E8978eb6eB847CaCF9E2A956336", create2Calldata: ethers.hexlify( ethers.toUtf8Bytes("iden3.create2.CredentialAtomicQueryV3Validator"), @@ -193,6 +198,7 @@ export const contractsInfo = Object.freeze({ }, IDENTITY_TREE_STORE: { name: "IdentityTreeStore", + version: "1.1.0", unifiedAddress: "0x7dF78ED37d0B39Ffb6d4D527Bb1865Bf85B60f81", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.IdentityTreeStore")), verificationOpts: { @@ -208,6 +214,7 @@ export const contractsInfo = Object.freeze({ }, VC_PAYMENT: { name: "VCPayment", + version: "1.0.0", unifiedAddress: "", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.VCPayment")), verificationOpts: { @@ -217,6 +224,7 @@ export const contractsInfo = Object.freeze({ }, MC_PAYMENT: { name: "MCPayment", + version: "1.0.0", unifiedAddress: "", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.MCPayment")), verificationOpts: { diff --git a/helpers/helperUtils.ts b/helpers/helperUtils.ts index 4b25fe8f..7402db38 100644 --- a/helpers/helperUtils.ts +++ b/helpers/helperUtils.ts @@ -71,6 +71,18 @@ export async function isContract( return true; } +export async function checkContractVersion( + contractName: string, + contractAddress: string, + contractVersion: string, + signer?: any, +): Promise<{ upgraded: boolean; currentVersion: string }> { + const contract = await ethers.getContractAt(contractName, contractAddress, signer); + const version = await contract.VERSION(); + + return { upgraded: contractVersion === version, currentVersion: version }; +} + export async function verifyContract( contractAddress: any, opts: { diff --git a/scripts/maintenance/multi-chain/checkUnifiedContracts.ts b/scripts/maintenance/multi-chain/checkUnifiedContracts.ts index 7100db14..76a0b0d8 100644 --- a/scripts/maintenance/multi-chain/checkUnifiedContracts.ts +++ b/scripts/maintenance/multi-chain/checkUnifiedContracts.ts @@ -1,7 +1,15 @@ -import { getProviders, isContract, Logger } from "../../../helpers/helperUtils"; -import { contractsInfo } from "../../../helpers/constants"; +import { + checkContractVersion, + getProviders, + getStateContractAddress, + isContract, + Logger, +} from "../../../helpers/helperUtils"; +import { contractsInfo, DEFAULT_MNEMONIC } from "../../../helpers/constants"; import { ethers } from "hardhat"; +const mnemonicWallet = ethers.Wallet.fromPhrase(DEFAULT_MNEMONIC); + async function main() { const providers = getProviders(); @@ -9,17 +17,42 @@ async function main() { const jsonRpcProvider = new ethers.JsonRpcProvider(provider.rpcUrl); const contractsNotDeployed: string[] = []; + const contractsNotUpgraded: string[] = []; for (const property in contractsInfo) { if (contractsInfo[property].unifiedAddress !== "") { if (await isContract(contractsInfo[property].unifiedAddress, jsonRpcProvider)) { + if (contractsInfo[property].version) { + const signer = new ethers.Wallet(mnemonicWallet.privateKey, jsonRpcProvider); + + let contractAddress = contractsInfo[property].unifiedAddress; + + if (property === "STATE") { + contractAddress = getStateContractAddress( + Number((await jsonRpcProvider.getNetwork()).chainId), + ); + } + const { upgraded, currentVersion } = await checkContractVersion( + contractsInfo[property].name, + contractAddress, + contractsInfo[property].version, + signer, + ); + if (!upgraded) { + contractsNotUpgraded.push( + `${contractsInfo[property].name} (${currentVersion} -> ${contractsInfo[property].version})`, + ); + } + } } else { - contractsNotDeployed.push(property); + contractsNotDeployed.push(contractsInfo[property].name); } } } - if (contractsNotDeployed.length > 0) { + if (contractsNotDeployed.length > 0 || contractsNotUpgraded.length > 0) { + const contractsNotDeployedString = `${contractsNotDeployed.length} contracts are not deployed: ${contractsNotDeployed.join(", ")} `; + const contractsNotUpgradedString = `${contractsNotUpgraded.length} contracts are not upgraded: ${contractsNotUpgraded.join(", ")}`; Logger.error( - `${provider.network}: ${contractsNotDeployed.length} contracts are not deployed: ${contractsNotDeployed.map((property) => contractsInfo[property].name).join(", ")}`, + `${provider.network}: ${contractsNotDeployed.length > 0 ? contractsNotDeployedString : ""}${contractsNotUpgraded.length > 0 ? contractsNotUpgradedString : ""}`, ); } else { Logger.success(`${provider.network}: All contracts are deployed`); diff --git a/scripts/upgrade/state/state-upgrade.ts b/scripts/upgrade/state/state-upgrade.ts index 9099d176..c72821af 100644 --- a/scripts/upgrade/state/state-upgrade.ts +++ b/scripts/upgrade/state/state-upgrade.ts @@ -3,6 +3,7 @@ import hre, { ethers } from "hardhat"; import { expect } from "chai"; // abi of contract that will be upgraded import * as stateArtifact from "../../../artifacts/contracts/state/State.sol/State.json"; import { + checkContractVersion, getConfig, getStateContractAddress, removeLocalhostNetworkIgnitionFiles, @@ -50,6 +51,21 @@ async function main() { true, ); + const { upgraded, currentVersion } = await checkContractVersion( + contractsInfo.STATE.name, + stateContractAddress, + contractsInfo.STATE.version, + ); + + if (upgraded) { + console.log(`Contract is already upgraded to version ${contractsInfo.STATE.version}`); + return; + } else { + console.log( + `Contract is not upgraded and will upgrade version ${currentVersion} to ${contractsInfo.STATE.version}`, + ); + } + console.log("Proxy Admin Owner Address: ", await proxyAdminOwnerSigner.getAddress()); console.log("State Owner Address: ", await stateOwnerSigner.getAddress()); if (removePreviousIgnitionFiles) { diff --git a/scripts/upgrade/validators/validators-upgrade.ts b/scripts/upgrade/validators/validators-upgrade.ts index 56663ada..9d98667f 100644 --- a/scripts/upgrade/validators/validators-upgrade.ts +++ b/scripts/upgrade/validators/validators-upgrade.ts @@ -1,6 +1,7 @@ import { DeployHelper } from "../../../helpers/DeployHelper"; import hre, { ethers } from "hardhat"; import { + checkContractVersion, getConfig, removeLocalhostNetworkIgnitionFiles, verifyContract, @@ -48,23 +49,41 @@ async function main() { validatorContractName: contractsInfo.VALIDATOR_MTP.name, validatorType: VALIDATOR_TYPES.MTP_V2, validatorVerification: contractsInfo.VALIDATOR_MTP.verificationOpts, + version: contractsInfo.VALIDATOR_MTP.version, }, { validatorContractAddress: contractsInfo.VALIDATOR_SIG.unifiedAddress, validatorContractName: contractsInfo.VALIDATOR_SIG.name, validatorType: VALIDATOR_TYPES.SIG_V2, validatorVerification: contractsInfo.VALIDATOR_SIG.verificationOpts, + version: contractsInfo.VALIDATOR_SIG.version, }, { validatorContractAddress: contractsInfo.VALIDATOR_V3.unifiedAddress, validatorContractName: contractsInfo.VALIDATOR_V3.name, validatorType: VALIDATOR_TYPES.V3, validatorVerification: contractsInfo.VALIDATOR_V3.verificationOpts, + version: contractsInfo.VALIDATOR_V3.version, }, ]; const validatorsInfo: any = []; for (const v of validators) { + const { upgraded, currentVersion } = await checkContractVersion( + v.validatorContractName, + v.validatorContractAddress, + v.version, + ); + + if (upgraded) { + console.log(`Contract is already upgraded to version ${v.version}`); + continue; + } else { + console.log( + `Contract is not upgraded and will upgrade version ${currentVersion} to ${v.version}`, + ); + } + const { validator } = await deployHelper.upgradeValidator( v.validatorContractAddress as string, v.validatorContractName, diff --git a/scripts/upgrade/verifiers/universal-verifier-upgrade.ts b/scripts/upgrade/verifiers/universal-verifier-upgrade.ts index e2bd73ee..5f1811e2 100644 --- a/scripts/upgrade/verifiers/universal-verifier-upgrade.ts +++ b/scripts/upgrade/verifiers/universal-verifier-upgrade.ts @@ -11,6 +11,7 @@ import { submitZKPResponses_KYCAgeCredential, } from "./helpers/testVerifier"; import { + checkContractVersion, getConfig, getStateContractAddress, removeLocalhostNetworkIgnitionFiles, @@ -53,6 +54,23 @@ async function main() { console.log(`Starting Universal Verifier Contract Upgrade for ${universalVerifierAddress}`); + const { upgraded, currentVersion } = await checkContractVersion( + contractsInfo.UNIVERSAL_VERIFIER.name, + contractsInfo.UNIVERSAL_VERIFIER.unifiedAddress, + contractsInfo.UNIVERSAL_VERIFIER.version, + ); + + if (upgraded) { + console.log( + `Contract is already upgraded to version ${contractsInfo.UNIVERSAL_VERIFIER.version}`, + ); + return; + } else { + console.log( + `Contract is not upgraded and will upgrade version ${currentVersion} to ${contractsInfo.UNIVERSAL_VERIFIER.version}`, + ); + } + if (!ethers.isAddress(config.ledgerAccount)) { throw new Error("LEDGER_ACCOUNT is not set"); } diff --git a/test/state/state.test.ts b/test/state/state.test.ts index c0585d32..86da84c5 100644 --- a/test/state/state.test.ts +++ b/test/state/state.test.ts @@ -115,7 +115,10 @@ describe("State transition negative cases", () => { beforeEach(async () => { const deployHelper = await DeployHelper.initialize(); - const contracts = await deployHelper.deployStateWithLibraries(["0x0281", "0x0000"], g16VerifierStubName); + const contracts = await deployHelper.deployStateWithLibraries( + ["0x0281", "0x0000"], + g16VerifierStubName, + ); state = contracts.state; }); @@ -400,14 +403,41 @@ describe("Set Verifier", () => { }); }); -describe("Check timestamp expirations", () => { - it("Should return zero from the State if requested for a non-existent data", async function () { +describe("Check replacedAt timestamp expirations", () => { + it("Should throw for non-existent state and GIST roots", async function () { const deployHelper = await DeployHelper.initialize(); - const { state } = await deployHelper.deployStateWithLibraries(["0x0102"]); + // default type should be 0x0112 + const { state } = await deployHelper.deployStateWithLibraries([]); + + expect(await state.getGistRootReplacedAt("0x0112", 0)).to.be.equal(0); - await expect(state.getGistRootReplacedAt("0x0102", 10)).to.be.rejectedWith( - "Gist root entry not found", + await expect(state.getGistRootReplacedAt("0x0112", 10)).to.be.rejectedWith( + "GIST root entry not found", ); - await expect(state.getStateReplacedAt(10, 20)).to.be.rejectedWith("State entry not found"); + + await expect(state.getGistRootReplacedAt("0x0212", 10)).to.be.rejectedWith( + "Cross-chain GIST root not found", + ); + + expect( + await state.getStateReplacedAt( + BigInt("0xD9C10A0BFB514F30B64E115D7EEB3D547C240C104E03D4548375669FE1201"), + BigInt("0x10A0BFB514F30B64E115D7EEB3D547C240C104E03D4548375669FE5E5717281A"), + ), + ).to.be.equal(0); + + await expect( + state.getStateReplacedAt( + BigInt("0xD9C10A0BFB514F30B64E115D7EEB3D547C240C104E03D4548375669FE1201"), + 0, + ), + ).to.be.rejectedWith("State entry not found"); + + await expect( + state.getStateReplacedAt( + BigInt("0xD9C10A0BFB514F30B64E115D7EEB3D547C240C104E03D4548375669FE1202"), + 0, + ), + ).to.be.rejectedWith("Cross-chain state not found"); }); });