diff --git a/crates/cli/src/ciphernode/lifecycle.rs b/crates/cli/src/ciphernode/lifecycle.rs index 3df3e65ae0..dbfda59305 100644 --- a/crates/cli/src/ciphernode/lifecycle.rs +++ b/crates/cli/src/ciphernode/lifecycle.rs @@ -8,7 +8,7 @@ use alloy::primitives::U256; use anyhow::{bail, Result}; use super::context::ChainContext; -use super::utils::{format_amount, parse_amount, parse_u256_list}; +use super::utils::{format_amount, parse_amount}; pub(crate) async fn register(ctx: &ChainContext) -> Result<()> { let receipt = ctx @@ -26,11 +26,10 @@ pub(crate) async fn register(ctx: &ChainContext) -> Result<()> { Ok(()) } -pub(crate) async fn deregister(ctx: &ChainContext, siblings: Vec) -> Result<()> { - let proof = parse_u256_list(&siblings)?; +pub(crate) async fn deregister(ctx: &ChainContext) -> Result<()> { let receipt = ctx .bonding() - .deregisterOperator(proof) + .deregisterOperator() .send() .await? .get_receipt() diff --git a/crates/cli/src/ciphernode/mod.rs b/crates/cli/src/ciphernode/mod.rs index dadceae7a9..1f5b2667d1 100644 --- a/crates/cli/src/ciphernode/mod.rs +++ b/crates/cli/src/ciphernode/mod.rs @@ -68,10 +68,8 @@ pub enum CiphernodeCommands { #[command(flatten)] chain: ChainArgs, }, - /// Request deregistration by providing the IMT proof siblings + /// Request deregistration from the bonding registry Deregister { - #[arg(long = "proof", value_delimiter = ',', value_name = "NODE")] - sibling_nodes: Vec, #[command(flatten)] chain: ChainArgs, }, @@ -145,12 +143,9 @@ pub async fn execute(command: CiphernodeCommands, config: &AppConfig) -> Result< let ctx = ChainContext::new(config, chain.selection()).await?; lifecycle::register(&ctx).await? } - CiphernodeCommands::Deregister { - chain, - sibling_nodes, - } => { + CiphernodeCommands::Deregister { chain } => { let ctx = ChainContext::new(config, chain.selection()).await?; - lifecycle::deregister(&ctx, sibling_nodes).await? + lifecycle::deregister(&ctx).await? } CiphernodeCommands::Activate { chain } => { let ctx = ChainContext::new(config, chain.selection()).await?; diff --git a/crates/cli/src/ciphernode/utils.rs b/crates/cli/src/ciphernode/utils.rs index 7666ee99e8..3fb77b8fea 100644 --- a/crates/cli/src/ciphernode/utils.rs +++ b/crates/cli/src/ciphernode/utils.rs @@ -68,26 +68,6 @@ pub(crate) fn parse_amount(value: &str, decimals: u8) -> Result { Ok(result) } -pub(crate) fn parse_u256_list(values: &[String]) -> Result> { - values - .iter() - .filter(|s| !s.trim().is_empty()) - .map(|value| parse_u256(value)) - .collect() -} - -fn parse_u256(value: &str) -> Result { - let trimmed = value.trim(); - if let Some(hex) = trimmed - .strip_prefix("0x") - .or_else(|| trimmed.strip_prefix("0X")) - { - U256::from_str_radix(hex, 16).context("Invalid hex value") - } else { - U256::from_str(trimmed).context("Invalid decimal value") - } -} - pub(crate) async fn ensure_allowance( ctx: &ChainContext, token: Address, diff --git a/docs/pages/ciphernode-operators/exits-and-slashing.mdx b/docs/pages/ciphernode-operators/exits-and-slashing.mdx index abedefcc4b..28adf20b93 100644 --- a/docs/pages/ciphernode-operators/exits-and-slashing.mdx +++ b/docs/pages/ciphernode-operators/exits-and-slashing.mdx @@ -94,15 +94,12 @@ To safely exit the ciphernode registry: ### Step 1: Deregister -Request deregistration with your IMT proof siblings: +Request deregistration: ```bash -enclave ciphernode deregister --proof 0x123...,0x456...,0x789... +enclave ciphernode deregister ``` -> Save your sibling proof from the `CiphernodeAdded` event when you first register. You'll need it -> to deregister. - ### Step 2: Wait for Exit Delay Assets enter a queue with a delay period (7 days on Sepolia): diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index e02d4d3f3f..ec8df1ccb1 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -311,13 +311,7 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "uint256[]", - "name": "siblingNodes", - "type": "uint256[]" - } - ], + "inputs": [], "name": "deregisterOperator", "outputs": [], "stateMutability": "nonpayable", @@ -946,5 +940,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-8c7470711e670529b6d5512dcaf5896399b39260" + "buildInfoId": "solc-0_8_28-16cefaab22a5b0c22d849bfd93e340ea85b6ef79" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index a71ddb492b..1daf6d2fcb 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -637,11 +637,6 @@ "internalType": "address", "name": "node", "type": "address" - }, - { - "internalType": "uint256[]", - "name": "siblingNodes", - "type": "uint256[]" } ], "name": "removeCiphernode", @@ -787,5 +782,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-8c7470711e670529b6d5512dcaf5896399b39260" + "buildInfoId": "solc-0_8_28-16cefaab22a5b0c22d849bfd93e340ea85b6ef79" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index 4b6b181b37..530a8092f0 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -1263,5 +1263,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-8c7470711e670529b6d5512dcaf5896399b39260" + "buildInfoId": "solc-0_8_28-16cefaab22a5b0c22d849bfd93e340ea85b6ef79" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index b477746915..c64048d8df 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -934,5 +934,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-e31106e21ee0b1a0de522e91c17cb752d4a7fb4e" + "buildInfoId": "solc-0_8_28-16cefaab22a5b0c22d849bfd93e340ea85b6ef79" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol index 83a43d8389..2fbd6551a6 100644 --- a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol @@ -275,10 +275,8 @@ interface IBondingRegistry { /** * @notice Deregister as an operator and remove from IMT - * @param siblingNodes Sibling node proofs for IMT removal - * @dev Requires operator to provide sibling nodes for immediate IMT removal */ - function deregisterOperator(uint256[] calldata siblingNodes) external; + function deregisterOperator() external; /** * @notice Increase operator's ticket balance by depositing tokens diff --git a/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol index f6f002d828..21b0335707 100644 --- a/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -183,11 +183,7 @@ interface ICiphernodeRegistry { /// @notice Remove a ciphernode from the registry /// @param node Address of the ciphernode to remove - /// @param siblingNodes Array of sibling node indices for tree operations - function removeCiphernode( - address node, - uint256[] calldata siblingNodes - ) external; + function removeCiphernode(address node) external; /// @notice Initiates the committee selection process for a specified E3. /// @dev This function MUST revert when not called by the Enclave contract. diff --git a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol index fbc45c66a7..31363d03bf 100644 --- a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol @@ -312,9 +312,7 @@ contract BondingRegistry is IBondingRegistry, OwnableUpgradeable { } /// @inheritdoc IBondingRegistry - function deregisterOperator( - uint256[] calldata siblingNodes - ) external noExitInProgress(msg.sender) { + function deregisterOperator() external noExitInProgress(msg.sender) { Operator storage op = operators[msg.sender]; require(op.registered, NotRegistered()); @@ -353,7 +351,7 @@ contract BondingRegistry is IBondingRegistry, OwnableUpgradeable { } // CiphernodeRegistry already emits an event when a ciphernode is removed - registry.removeCiphernode(msg.sender, siblingNodes); + registry.removeCiphernode(msg.sender); emit CiphernodeDeregistrationRequested(msg.sender, op.exitUnlocksAt); _updateOperatorStatus(msg.sender); diff --git a/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index d5a110400d..365b97ed0e 100644 --- a/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -13,9 +13,9 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { - InternalLeanIMT, - LeanIMTData -} from "@zk-kit/lean-imt.sol/InternalLeanIMT.sol"; + InternalLazyIMT, + LazyIMTData +} from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; /** * @title CiphernodeRegistryOwnable @@ -23,7 +23,7 @@ import { * @dev Manages ciphernode registration, committee selection, and integrates with bonding registry */ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { - using InternalLeanIMT for LeanIMTData; + using InternalLazyIMT for LazyIMTData; //////////////////////////////////////////////////////////// // // @@ -55,8 +55,17 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { /// their tickets to be a part of the committee. uint256 public sortitionSubmissionWindow; + /// @notice Depth of the LazyIMT tree + uint8 public constant TREE_DEPTH = 20; + /// @notice Incremental Merkle Tree (IMT) containing all registered ciphernodes - LeanIMTData public ciphernodes; + LazyIMTData public ciphernodes; + + /// @notice Tracks whether a ciphernode is enabled in the registry + mapping(address => bool) public ciphernodeEnabled; + + /// @notice Tracks the tree leaf index for each ciphernode + mapping(address => uint40) public ciphernodeTreeIndex; /// @notice Maps E3 ID to the IMT root at the time of committee request mapping(uint256 e3Id => uint256 root) public roots; @@ -212,6 +221,7 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { require(_owner != address(0), ZeroAddress()); __Ownable_init(msg.sender); + ciphernodes._init(TREE_DEPTH); setSortitionSubmissionWindow(_submissionWindow); if (_owner != owner()) transferOwnership(_owner); } @@ -302,29 +312,33 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { return; } - uint160 ciphernode = uint160(node); - ciphernodes._insert(ciphernode); + uint40 index = ciphernodes.numberOfLeaves; + ciphernodes._insert(uint160(node)); + ciphernodeEnabled[node] = true; + ciphernodeTreeIndex[node] = index; numCiphernodes++; emit CiphernodeAdded( node, - ciphernodes._indexOf(ciphernode), + index, numCiphernodes, - ciphernodes.size + ciphernodes.numberOfLeaves ); } /// @inheritdoc ICiphernodeRegistry - function removeCiphernode( - address node, - uint256[] calldata siblingNodes - ) external onlyOwnerOrBondingVault { + function removeCiphernode(address node) external onlyOwnerOrBondingVault { require(isEnabled(node), CiphernodeNotEnabled(node)); - uint160 ciphernode = uint160(node); - uint256 index = ciphernodes._indexOf(ciphernode); - ciphernodes._remove(ciphernode, siblingNodes); + uint40 index = ciphernodeTreeIndex[node]; + ciphernodes._update(0, index); + ciphernodeEnabled[node] = false; numCiphernodes--; - emit CiphernodeRemoved(node, index, numCiphernodes, ciphernodes.size); + emit CiphernodeRemoved( + node, + index, + numCiphernodes, + ciphernodes.numberOfLeaves + ); } //////////////////////////////////////////////////////////// @@ -497,13 +511,13 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { /// @inheritdoc ICiphernodeRegistry function isEnabled(address node) public view returns (bool) { - return ciphernodes._has(uint160(node)); + return ciphernodeEnabled[node]; } /// @notice Returns the current root of the ciphernode IMT /// @return Current IMT root function root() public view returns (uint256) { - return (ciphernodes._root()); + return ciphernodes._root(TREE_DEPTH); } /// @notice Returns the IMT root at the time a committee was requested @@ -525,7 +539,7 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { /// @notice Returns the current size of the ciphernode IMT /// @return Size of the IMT function treeSize() public view returns (uint256) { - return ciphernodes.size; + return ciphernodes.numberOfLeaves; } /// @notice Returns the address of the bonding registry diff --git a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol index 6f1d3a4d10..8ddece9568 100644 --- a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol +++ b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol @@ -56,7 +56,7 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { function addCiphernode(address) external pure {} // solhint-disable-next-line no-empty-blocks - function removeCiphernode(address, uint256[] calldata) external pure {} + function removeCiphernode(address) external pure {} function publishCommittee( uint256, @@ -183,7 +183,7 @@ contract MockCiphernodeRegistryEmptyKey is ICiphernodeRegistry { function addCiphernode(address) external pure {} // solhint-disable-next-line no-empty-blocks - function removeCiphernode(address, uint256[] calldata) external pure {} + function removeCiphernode(address) external pure {} function publishCommittee( uint256, diff --git a/packages/enclave-contracts/hardhat.config.ts b/packages/enclave-contracts/hardhat.config.ts index 73e047e74a..b0826c6d9a 100644 --- a/packages/enclave-contracts/hardhat.config.ts +++ b/packages/enclave-contracts/hardhat.config.ts @@ -17,7 +17,6 @@ import { ciphernodeAdminAdd, ciphernodeMintTokens, ciphernodeRemove, - ciphernodeSiblings, updateSubmissionWindow, } from "./tasks/ciphernode"; import { @@ -92,7 +91,6 @@ const config: HardhatUserConfig = { ciphernodeAdminAdd, ciphernodeMintTokens, ciphernodeRemove, - ciphernodeSiblings, requestCommittee, publishPlaintext, publishCiphertext, diff --git a/packages/enclave-contracts/package.json b/packages/enclave-contracts/package.json index 8a1beefd7e..e9a82359a3 100644 --- a/packages/enclave-contracts/package.json +++ b/packages/enclave-contracts/package.json @@ -169,7 +169,6 @@ "ciphernode:admin-add": "hardhat ciphernode:admin-add", "ciphernode:mint-tokens": "hardhat ciphernode:mint-tokens", "ciphernode:remove": "hardhat ciphernode:remove", - "ciphernode:siblings": "hardhat ciphernode:siblings", "committee:new": "hardhat committee:new", "lint": "solhint --disc --max-warnings 10 \"contracts/**/*.sol\"", "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", @@ -188,8 +187,7 @@ }, "dependencies": { "@openzeppelin/contracts-upgradeable": "^5.0.2", - "@zk-kit/lean-imt.sol": "2.0.1", - "@zk-kit/lean-imt": "2.2.4", + "@zk-kit/lazy-imt.sol": "2.0.0-beta.12", "js-yaml": "^4.1.1", "poseidon-solidity": "0.0.5" }, diff --git a/packages/enclave-contracts/tasks/ciphernode.ts b/packages/enclave-contracts/tasks/ciphernode.ts index 8007725b2c..afd8f2096e 100644 --- a/packages/enclave-contracts/tasks/ciphernode.ts +++ b/packages/enclave-contracts/tasks/ciphernode.ts @@ -3,11 +3,9 @@ // This file is provided WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { LeanIMT } from "@zk-kit/lean-imt"; import { ZeroAddress } from "ethers"; import { task } from "hardhat/config"; import { ArgumentType } from "hardhat/types/arguments"; -import { poseidon2 } from "poseidon-lite"; export const ciphernodeAdd = task( "ciphernode:add", @@ -153,13 +151,8 @@ export const ciphernodeRemove = task( "ciphernode:remove", "Deregister a ciphernode from the bonding registry", ) - .addOption({ - name: "siblings", - description: "comma separated siblings from tree proof", - defaultValue: "", - }) .setAction(async () => ({ - default: async ({ siblings }, hre) => { + default: async (_, hre) => { const connection = await hre.network.connect(); const { ethers } = connection; @@ -173,14 +166,11 @@ export const ciphernodeRemove = task( const bondingRegistryConnected = bondingRegistry.connect(signer); - const siblingsArray = siblings.split(",").map((s: string) => BigInt(s)); - try { console.log( "Deregistering operator (will also remove from CiphernodeRegistry)...", ); - const tx = - await bondingRegistryConnected.deregisterOperator(siblingsArray); + const tx = await bondingRegistryConnected.deregisterOperator(); await tx.wait(); console.log(`Ciphernode ${signer.address} deregistered`); @@ -500,40 +490,6 @@ export const ciphernodeAdminAdd = task( })) .build(); -export const ciphernodeSiblings = task( - "ciphernode:siblings", - "Get the sibling of a ciphernode in the registry", -) - .addOption({ - name: "ciphernodeAddress", - description: "address of ciphernode to get siblings for", - defaultValue: ZeroAddress, - }) - .addOption({ - name: "ciphernodeAddresses", - description: - "comma separated addresses of ciphernodes in the order they were added to the registry", - defaultValue: ZeroAddress, - }) - .setAction(async () => ({ - default: async ({ ciphernodeAddress, ciphernodeAddresses }, _) => { - const hash = (a: bigint, b: bigint) => poseidon2([a, b]); - const tree = new LeanIMT(hash); - - const addresses = ciphernodeAddresses.split(","); - - for (const address of addresses) { - tree.insert(BigInt(address)); - } - - const index = tree.indexOf(BigInt(ciphernodeAddress)); - const { siblings } = tree.generateProof(index); - - console.log(`Siblings for ${ciphernodeAddress}: ${siblings}`); - }, - })) - .build(); - export const updateSubmissionWindow = task( "ciphernode:window", "Update the submission window for the ciphernode registry", diff --git a/packages/enclave-contracts/test/Enclave.spec.ts b/packages/enclave-contracts/test/Enclave.spec.ts index ec5ef122cf..70b6a5d8ae 100644 --- a/packages/enclave-contracts/test/Enclave.spec.ts +++ b/packages/enclave-contracts/test/Enclave.spec.ts @@ -3,11 +3,9 @@ // This file is provided WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { LeanIMT } from "@zk-kit/lean-imt"; import { expect } from "chai"; import type { Signer } from "ethers"; import { network } from "hardhat"; -import { poseidon2 } from "poseidon-lite"; import BondingRegistryModule from "../ignition/modules/bondingRegistry"; import CiphernodeRegistryModule from "../ignition/modules/ciphernodeRegistry"; @@ -69,9 +67,6 @@ describe("Enclave", function () { const data = "0xda7a"; const proof = "0x1337"; - // Hash function used to compute the tree nodes. - const hash = (a: bigint, b: bigint) => poseidon2([a, b]); - const setupAndPublishCommittee = async ( registry: any, e3Id: number, @@ -287,7 +282,6 @@ describe("Enclave", function () { // ── Operators ───────────────────────────────────────────────────────────── await licenseToken.setTransferRestriction(false); - const tree = new LeanIMT(hash); for (const operator of [operator1, operator2]) { await setupOperatorForSortition( @@ -298,7 +292,6 @@ describe("Enclave", function () { ticketToken, ciphernodeRegistryContract, ); - tree.insert(BigInt(await operator.getAddress())); } await mine(1); @@ -337,7 +330,6 @@ describe("Enclave", function () { ticketToken, usdcToken, slashingManager, - tree, request, mocks: { decryptionVerifier, e3Program, mockComputeProvider }, }; diff --git a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts index 103aa14996..cd36270061 100644 --- a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts @@ -3,10 +3,8 @@ // This file is provided WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { LeanIMT } from "@zk-kit/lean-imt"; import { expect } from "chai"; import { network } from "hardhat"; -import { poseidon2 } from "poseidon-lite"; import BondingRegistryModule from "../../ignition/modules/bondingRegistry"; import EnclaveTicketTokenModule from "../../ignition/modules/enclaveTicketToken"; @@ -28,8 +26,6 @@ const AddressOne = "0x0000000000000000000000000000000000000001"; const { ethers, networkHelpers, ignition } = await network.connect(); const { loadFixture, time } = networkHelpers; -const hash = (a: bigint, b: bigint) => poseidon2([a, b]); - const REASON_DEPOSIT = ethers.encodeBytes32String("DEPOSIT"); const REASON_WITHDRAW = ethers.encodeBytes32String("WITHDRAW"); const REASON_BOND = ethers.encodeBytes32String("BOND"); @@ -176,7 +172,6 @@ describe("BondingRegistry", function () { operator1Address, operator2Address, treasuryAddress, - tree: new LeanIMT(hash), }; } @@ -250,7 +245,7 @@ describe("BondingRegistry", function () { await bondingRegistry.connect(operator1).registerOperator(); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); await licenseToken .connect(operator1) @@ -405,7 +400,7 @@ describe("BondingRegistry", function () { await bondingRegistry.connect(operator1).bondLicense(bondAmount); await bondingRegistry.connect(operator1).registerOperator(); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); await time.increase(SEVEN_DAYS_IN_SECONDS + 1); @@ -434,7 +429,7 @@ describe("BondingRegistry", function () { await bondingRegistry.connect(operator1).registerOperator(); const latestTime = await time.latest(); - await expect(bondingRegistry.connect(operator1).deregisterOperator([])) + await expect(bondingRegistry.connect(operator1).deregisterOperator()) .to.emit(bondingRegistry, "CiphernodeDeregistrationRequested") .withArgs( await operator1.getAddress(), @@ -452,7 +447,7 @@ describe("BondingRegistry", function () { const { bondingRegistry, operator1 } = await loadFixture(setup); await expect( - bondingRegistry.connect(operator1).deregisterOperator([]), + bondingRegistry.connect(operator1).deregisterOperator(), ).to.be.revertedWithCustomError(bondingRegistry, "NotRegistered"); }); @@ -478,7 +473,7 @@ describe("BondingRegistry", function () { .approve(await ticketToken.getAddress(), ticketAmount); await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); const [ticketPending, licensePending] = await bondingRegistry.pendingExits(await operator1.getAddress()); @@ -731,7 +726,7 @@ describe("BondingRegistry", function () { .approve(await ticketToken.getAddress(), ticketAmount); await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); await time.increase(SEVEN_DAYS_IN_SECONDS + 1); @@ -765,7 +760,7 @@ describe("BondingRegistry", function () { await bondingRegistry.connect(operator1).bondLicense(bondAmount); await bondingRegistry.connect(operator1).registerOperator(); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); await expect( bondingRegistry.connect(operator1).claimExits(0, bondAmount), @@ -794,7 +789,7 @@ describe("BondingRegistry", function () { .approve(await ticketToken.getAddress(), ticketAmount); await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); await time.increase(SEVEN_DAYS_IN_SECONDS + 1); @@ -1101,7 +1096,7 @@ describe("BondingRegistry", function () { expect(await bondingRegistry.isActive(await operator1.getAddress())).to.be .true; - await bondingRegistry.connect(operator1).deregisterOperator([]); + await bondingRegistry.connect(operator1).deregisterOperator(); expect(await bondingRegistry.isRegistered(await operator1.getAddress())) .to.be.false; expect( diff --git a/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index a6b4c49581..fa1508a7a0 100644 --- a/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -3,11 +3,9 @@ // This file is provided WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { LeanIMT } from "@zk-kit/lean-imt"; import { expect } from "chai"; import type { Signer } from "ethers"; import { network } from "hardhat"; -import { poseidon2 } from "poseidon-lite"; import BondingRegistryModule from "../../ignition/modules/bondingRegistry"; import CiphernodeRegistryModule from "../../ignition/modules/ciphernodeRegistry"; @@ -35,9 +33,6 @@ const data = "0xda7a"; const dataHash = ethers.id(data); const SORTITION_SUBMISSION_WINDOW = 3; -// Hash function used to compute the tree nodes. -const hash = (a: bigint, b: bigint) => poseidon2([a, b]); - describe("CiphernodeRegistryOwnable", function () { async function finalizeCommitteeAfterWindow( registry: any, @@ -236,7 +231,6 @@ describe("CiphernodeRegistryOwnable", function () { // ── Operators ────────────────────────────────────────────────────────────── await licenseToken.setTransferRestriction(false); - const tree = new LeanIMT(hash); for (const operator of [operator1, operator2]) { await setupOperatorForSortition( @@ -247,7 +241,6 @@ describe("CiphernodeRegistryOwnable", function () { ticketToken, registry, ); - tree.insert(BigInt(await operator.getAddress())); } await networkHelpers.mine(1); @@ -263,7 +256,6 @@ describe("CiphernodeRegistryOwnable", function () { licenseToken, ticketToken, usdcToken, - tree, mockE3Program, mockDecryptionVerifier, request: { @@ -578,43 +570,34 @@ describe("CiphernodeRegistryOwnable", function () { it("reverts if the caller is not the owner", async function () { const { registry, notTheOwner } = await loadFixture(setup); await expect( - registry.connect(notTheOwner).removeCiphernode(AddressOne, []), + registry.connect(notTheOwner).removeCiphernode(AddressOne), ).to.be.revertedWithCustomError(registry, "NotOwnerOrBondingRegistry"); }); it("removes the ciphernode from the registry", async function () { - const { registry, operator1, tree } = await loadFixture(setup); + const { registry, operator1 } = await loadFixture(setup); const operator1Address = await operator1.getAddress(); - const localTree = new LeanIMT(hash); - for (let i = 0; i < tree.size; i++) { - localTree.insert(tree.leaves[i]); - } - const index = localTree.indexOf(BigInt(operator1Address)); - const proof = localTree.generateProof(index); - localTree.update(index, BigInt(0)); + const rootBefore = await registry.root(); expect(await registry.isEnabled(operator1Address)).to.be.true; - expect(await registry.removeCiphernode(operator1Address, proof.siblings)); + await registry.removeCiphernode(operator1Address); expect(await registry.isEnabled(operator1Address)).to.be.false; - expect(await registry.root()).to.equal(localTree.root); + expect(await registry.root()).to.not.equal(rootBefore); }); it("decrements numCiphernodes", async function () { - const { registry, operator1, tree } = await loadFixture(setup); + const { registry, operator1 } = await loadFixture(setup); const operator1Address = await operator1.getAddress(); const numCiphernodes = await registry.numCiphernodes(); - const index = tree.indexOf(BigInt(operator1Address)); - const proof = tree.generateProof(index); - expect(await registry.removeCiphernode(operator1Address, proof.siblings)); + await registry.removeCiphernode(operator1Address); expect(await registry.numCiphernodes()).to.equal( numCiphernodes - BigInt(1), ); }); it("emits a CiphernodeRemoved event", async function () { - const { registry, operator1, tree } = await loadFixture(setup); + const { registry, operator1 } = await loadFixture(setup); const operator1Address = await operator1.getAddress(); const numCiphernodes = await registry.numCiphernodes(); const size = await registry.treeSize(); - const index = tree.indexOf(BigInt(operator1Address)); - const proof = tree.generateProof(index); - await expect(registry.removeCiphernode(operator1Address, proof.siblings)) + const index = await registry.ciphernodeTreeIndex(operator1Address); + await expect(registry.removeCiphernode(operator1Address)) .to.emit(registry, "CiphernodeRemoved") .withArgs(operator1Address, index, numCiphernodes - BigInt(1), size); }); @@ -715,9 +698,9 @@ describe("CiphernodeRegistryOwnable", function () { }); describe("root()", function () { - it("returns the root of the ciphernode registry merkle tree", async function () { - const { registry, tree } = await loadFixture(setup); - expect(await registry.root()).to.equal(tree.root); + it("returns a non-zero root when ciphernodes are registered", async function () { + const { registry } = await loadFixture(setup); + expect(await registry.root()).to.not.equal(0); }); }); @@ -725,27 +708,28 @@ describe("CiphernodeRegistryOwnable", function () { it("returns the root of the ciphernode registry merkle tree at the given e3Id", async function () { const { registry, - tree, enclave, usdcToken, mockE3Program, mockDecryptionVerifier, } = await loadFixture(setup); const e3Id = 0; + const rootBeforeRequest = await registry.root(); await makeRequest( enclave, usdcToken, mockE3Program, mockDecryptionVerifier, ); - expect(await registry.rootAt(e3Id)).to.equal(tree.root); + expect(await registry.rootAt(e3Id)).to.equal(rootBeforeRequest); }); }); describe("treeSize()", function () { it("returns the size of the ciphernode registry merkle tree", async function () { - const { registry, tree } = await loadFixture(setup); - expect(await registry.treeSize()).to.equal(tree.size); + const { registry } = await loadFixture(setup); + // Two operators registered in setup + expect(await registry.treeSize()).to.equal(2); }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75bf516c4d..0d7669afc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -439,12 +439,9 @@ importers: '@openzeppelin/contracts-upgradeable': specifier: ^5.0.2 version: 5.4.0(@openzeppelin/contracts@5.3.0) - '@zk-kit/lean-imt': - specifier: 2.2.4 - version: 2.2.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@zk-kit/lean-imt.sol': - specifier: 2.0.1 - version: 2.0.1 + '@zk-kit/lazy-imt.sol': + specifier: 2.0.0-beta.12 + version: 2.0.0-beta.12 js-yaml: specifier: ^4.1.1 version: 4.1.1