From 79ba4e8f6374d9694d785e5dc0750749ea35c4ea Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 18:57:24 +0500 Subject: [PATCH 01/52] fix(contracts): release drained exit queue capacity [M-01] --- agent/flow-trace/00_INDEX.md | 1 + .../contracts/lib/ExitQueueLib.sol | 16 ++++++++-------- .../test/Registry/BondingRegistry.spec.ts | 6 ++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index ae3aec44d..00aaf4169 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -204,3 +204,4 @@ _Found during source-code cross-referencing of these trace documents._ | 15 | **Local/network event redelivery collision** | Resolved | EventStore now treats the same HLC timestamp plus stable event ID and equal payload as an idempotent duplicate even when transport context differs (`Local` versus `Net`). It retains the first stored context and still fails closed when different payloads claim the same timestamp, preserving collision detection without panicking on sync/resync redelivery. | | 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | | 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | +| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` now caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity, while a lazy sentinel initializes the counter safely for queues created before the upgrade. | diff --git a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol index 516d4b545..9abbe9653 100644 --- a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol +++ b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol @@ -46,12 +46,14 @@ library ExitQueueLib { * @param queueHeadIndexTicket Maps operator addresses to the head index for tickets * @param queueHeadIndexLicense Maps operator addresses to the head index for licenses * @param pendingTotals Maps operator addresses to their total pending amounts + * @param liveTrancheCount Maps operators to the number of non-empty tranches. */ struct ExitQueueState { mapping(address operator => ExitTranche[] operatorQueues) operatorQueues; mapping(address operator => uint256 queueHeadIndexTicket) queueHeadIndexTicket; mapping(address operator => uint256 queueHeadIndexLicense) queueHeadIndexLicense; mapping(address operator => PendingAmounts operatorPendings) pendingTotals; + mapping(address operator => uint256 count) liveTrancheCount; } /** @@ -171,15 +173,8 @@ library ExitQueueLib { } if (!merged) { - // Enforce a hard cap on the number of LIVE tranches an operator - // can hold simultaneously. "Live" = tranches at or after the - // earliest per-asset head (the lower of the two head indices). - // See `MAX_ACTIVE_TRANCHES`. - uint256 headT = state.queueHeadIndexTicket[operator]; - uint256 headL = state.queueHeadIndexLicense[operator]; - uint256 earliestHead = headT < headL ? headT : headL; require( - len - earliestHead < MAX_ACTIVE_TRANCHES, + state.liveTrancheCount[operator] < MAX_ACTIVE_TRANCHES, TooManyTranches() ); @@ -187,6 +182,7 @@ library ExitQueueLib { t.unlockTimestamp = unlockTimestamp; t.ticketAmount = ticketAmount; t.licenseAmount = licenseAmount; + state.liveTrancheCount[operator]++; } _updatePendingTotals( @@ -504,6 +500,10 @@ library ExitQueueLib { tranche.licenseAmount -= amountToTake; } + if (tranche.ticketAmount == 0 && tranche.licenseAmount == 0) { + state.liveTrancheCount[operator]--; + } + remainingWanted -= amountToTake; takenAmount += amountToTake; diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index e7f7a0392..e6ec4505b 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -1254,6 +1254,12 @@ describe("BondingRegistry", function () { await expect( bondingRegistry.connect(operator1).removeTicketBalance(step), ).to.be.revertedWithCustomError(bondingRegistry, "TooManyTranches"); + + // Draining the 64 ticket-only tranches must release all 64 slots even + // though the independent license head never advanced through them. + await time.increase(SEVEN_DAYS_IN_SECONDS + 1); + await bondingRegistry.connect(operator1).claimExits(step * 64n, 0); + await bondingRegistry.connect(operator1).removeTicketBalance(step); }); /** From 593fcfb6c6c484666b004400e45b92ec4c5e2cf1 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:00:20 +0500 Subject: [PATCH 02/52] fix(contracts): separate refund claim roles [M-02] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 6 ++- .../contracts/E3RefundManager.sol | 32 +++++++++--- .../contracts/interfaces/IE3RefundManager.sol | 13 +++-- .../test/E3Lifecycle/E3Integration.spec.ts | 49 +++++++++++++++++++ 5 files changed, 86 insertions(+), 15 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 00aaf4169..62a85de21 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -205,3 +205,4 @@ _Found during source-code cross-referencing of these trace documents._ | 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | | 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | | 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` now caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity, while a lazy sentinel initializes the counter safely for queues created before the upgrade. | +| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` now tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, while the aggregate `hasClaimed` compatibility view reports either role. | diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 809087071..bbc11cec9 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -176,7 +176,7 @@ REQUESTER claims: │ ├─ require(distribution calculated) ├─ require(msg.sender == requester from Interfold) -├─ require(!already claimed) +├─ require(!requester refund already claimed) ├─ requesterAmount includes BOTH: │ • Base refund (from work-value BPS allocation) │ • Slashed funds (requester filled first, up to originalPayment) @@ -188,7 +188,9 @@ HONEST NODE claims: │ ├─ require(distribution calculated) ├─ require(msg.sender is in honestNodes[e3Id]) -├─ require(!already claimed by this node) +├─ require(!honest-node reward already claimed by this node) +│ → This ledger is independent from the requester-refund claim ledger, so a +│ requester who is also an honest node can receive both entitlements ├─ honestNodeAmount includes BOTH: │ • Base compensation (from work-value BPS allocation) │ • Slashed funds surplus (after requester is made whole) diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index cf9ef486b..17564e681 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -42,9 +42,9 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { /// @notice Maps E3 ID to refund distribution mapping(uint256 e3Id => RefundDistribution distribution) internal _distributions; - /// @notice Tracks claims per E3 per address + /// @notice Tracks requester-refund claims per E3 per address mapping(uint256 e3Id => mapping(address claimer => bool hasClaimed)) - internal _claimed; + internal _requesterClaimed; /// @notice Tracks number of claims made per E3 mapping(uint256 e3Id => uint256 count) internal _claimCount; /// @notice Tracks number of honest node claims made per E3 @@ -65,6 +65,9 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { /// @dev Per-treasury so historical treasuries can drain even after rotation. mapping(address treasury => mapping(IERC20 token => uint256 amount)) internal _pendingTreasury; + /// @notice Tracks honest-node reward claims independently from requester claims + mapping(uint256 e3Id => mapping(address claimer => bool hasClaimed)) + internal _honestNodeClaimed; //////////////////////////////////////////////////////////// // // // Modifiers // @@ -320,12 +323,14 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { address requester = interfold.getRequester(e3Id); if (msg.sender != requester) revert NotRequester(e3Id, msg.sender); - if (_claimed[e3Id][msg.sender]) revert AlreadyClaimed(e3Id, msg.sender); + if (_requesterClaimed[e3Id][msg.sender]) { + revert AlreadyClaimed(e3Id, msg.sender); + } amount = dist.requesterAmount; if (amount == 0) revert NoRefundAvailable(e3Id); - _claimed[e3Id][msg.sender] = true; + _requesterClaimed[e3Id][msg.sender] = true; _claimCount[e3Id]++; // Use the per-E3 fee token (not the global one, which may have been rotated) @@ -347,7 +352,10 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { "feeToken not initialized" ); - require(!_claimed[e3Id][msg.sender], AlreadyClaimed(e3Id, msg.sender)); + require( + !_honestNodeClaimed[e3Id][msg.sender], + AlreadyClaimed(e3Id, msg.sender) + ); // Check if caller is honest node address[] memory nodes = _honestNodes[e3Id]; @@ -382,7 +390,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { } _totalHonestNodePaid[e3Id] += amount; - _claimed[e3Id][msg.sender] = true; + _honestNodeClaimed[e3Id][msg.sender] = true; _claimCount[e3Id]++; // Direct transfer to the honest node (refund path; bypasses BondingRegistry @@ -555,11 +563,19 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { } /// @inheritdoc IE3RefundManager - function hasClaimed( + function hasRequesterClaimed( + uint256 e3Id, + address claimant + ) external view returns (bool) { + return _requesterClaimed[e3Id][claimant]; + } + + /// @inheritdoc IE3RefundManager + function hasHonestNodeClaimed( uint256 e3Id, address claimant ) external view returns (bool) { - return _claimed[e3Id][claimant]; + return _honestNodeClaimed[e3Id][claimant]; } /// @inheritdoc IE3RefundManager diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index b20b56178..18a8a43ee 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -183,11 +183,14 @@ interface IE3RefundManager { uint256 e3Id ) external view returns (RefundDistribution memory distribution); - /// @notice Check if address has claimed refund - /// @param e3Id The E3 ID - /// @param claimant The address to check - /// @return claimed Whether the address has claimed - function hasClaimed( + /// @notice Check whether an address claimed the requester-refund role + function hasRequesterClaimed( + uint256 e3Id, + address claimant + ) external view returns (bool claimed); + + /// @notice Check whether an address claimed the honest-node reward role + function hasHonestNodeClaimed( uint256 e3Id, address claimant ) external view returns (bool claimed); diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 874e08bb8..22c44fa28 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -801,6 +801,55 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { }); describe("Full Failure Flow - DKG Timeout", function () { + it("AUD-M02: a requester who is also an honest node can claim both roles", async function () { + const { + interfold, + e3RefundManager, + registry, + makeRequest, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + await makeRequest(operator1); + + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(SORTITION_SUBMISSION_WINDOW + 1); + await registry.finalizeCommittee(0); + await time.increase(defaultTimeoutConfig.dkgWindow + 1); + await interfold.markE3Failed(0); + await interfold.processE3Failure(0); + + await e3RefundManager.connect(operator1).claimRequesterRefund(0); + expect( + await e3RefundManager.hasRequesterClaimed( + 0, + await operator1.getAddress(), + ), + ).to.equal(true); + expect( + await e3RefundManager.hasHonestNodeClaimed( + 0, + await operator1.getAddress(), + ), + ).to.equal(false); + + await e3RefundManager.connect(operator1).claimHonestNodeReward(0); + expect( + await e3RefundManager.hasHonestNodeClaimed( + 0, + await operator1.getAddress(), + ), + ).to.equal(true); + }); + it("complete flow: request -> committee formed -> DKG timeout -> fail -> process -> claim", async function () { const { interfold, From cf7e9d0d057b20a82fc391d83cb76b4f2b285c1b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:01:18 +0500 Subject: [PATCH 03/52] fix(contracts): authorize sale deployment operator [L-01] --- agent/flow-trace/00_INDEX.md | 1 + .../token/sale/InterfoldTokenSaleDeployer.sol | 16 +++++++++++--- .../Token/InterfoldTokenSaleDeployer.spec.ts | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 62a85de21..8cd686ba9 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -206,3 +206,4 @@ _Found during source-code cross-referencing of these trace documents._ | 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | | 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` now caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity, while a lazy sentinel initializes the counter safely for queues created before the upgrade. | | 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` now tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, while the aggregate `hasClaimed` compatibility view reports either role. | +| 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | diff --git a/packages/interfold-contracts/contracts/token/sale/InterfoldTokenSaleDeployer.sol b/packages/interfold-contracts/contracts/token/sale/InterfoldTokenSaleDeployer.sol index 07ca3382c..43ceb0fb8 100644 --- a/packages/interfold-contracts/contracts/token/sale/InterfoldTokenSaleDeployer.sol +++ b/packages/interfold-contracts/contracts/token/sale/InterfoldTokenSaleDeployer.sol @@ -22,8 +22,8 @@ interface IFoldToken { /** * @title InterfoldTokenSaleDeployer - * @notice Operator-callable sale deployment factory. The Safe passed as - * `protocolAdmin` becomes the FOLD owner; the caller only pays gas. + * @notice Designated-operator sale deployment factory. The Safe passed as + * `protocolAdmin` becomes the FOLD owner. * @dev The CCA auction address is discovered by the script from LBPStrategy * logs after deployment. The Safe then accepts FOLD ownership and sets * FOLD's claim source once. @@ -77,11 +77,16 @@ contract InterfoldTokenSaleDeployer { error SaleAmountTooLarge(); error FoldOwnershipNotRetained(address owner); error ArithmeticOverflow(); + error UnauthorizedOperator(address caller); /// @notice The Safe that becomes FOLD owner/admin. // solhint-disable-next-line immutable-vars-naming address public immutable protocolAdmin; + /// @notice The deployment operator fixed when this factory is created. + // solhint-disable-next-line immutable-vars-naming + address public immutable deploymentOperator; + /// @notice Replay guard: each config can be deployed exactly once. mapping(bytes32 configHash => bool used) public usedConfigHashes; @@ -107,18 +112,23 @@ contract InterfoldTokenSaleDeployer { constructor(address protocolAdmin_) { if (protocolAdmin_ == address(0)) revert ZeroAddress(); protocolAdmin = protocolAdmin_; + deploymentOperator = msg.sender; } /** * @notice Deploys FOLD and starts a Uniswap LiquidityLauncher/LBPStrategy * distribution in one transaction. - * @dev Callable by the operator wallet. FOLD ownership is transferred to + * @dev Callable only by the operator wallet that deployed this factory. + * FOLD ownership is transferred to * the Safe at the end, but remains pending until the Safe accepts it. */ function deploySaleWithLiquidityLauncher( LbpSaleConfig calldata config, bytes calldata foldInitCode ) external returns (address fold) { + if (msg.sender != deploymentOperator) { + revert UnauthorizedOperator(msg.sender); + } bytes32 configHash = _checkLbpConfig(config, foldInitCode); fold = _create(foldInitCode); diff --git a/packages/interfold-contracts/test/Token/InterfoldTokenSaleDeployer.spec.ts b/packages/interfold-contracts/test/Token/InterfoldTokenSaleDeployer.spec.ts index 7cc6c2265..396157559 100644 --- a/packages/interfold-contracts/test/Token/InterfoldTokenSaleDeployer.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldTokenSaleDeployer.spec.ts @@ -293,6 +293,28 @@ describe("InterfoldTokenSaleDeployer", function () { it("captures the Safe as protocolAdmin (no hardcoded address)", async function () { const ctx = await setup(); expect(await ctx.saleDeployer.protocolAdmin()).to.equal(ctx.safeAddress); + expect(await ctx.saleDeployer.deploymentOperator()).to.equal( + await ctx.operator.getAddress(), + ); + }); + + it("rejects a copied sale deployment from a non-operator", async function () { + const ctx = await setup(); + const plan = await buildLbpSaleConfig(ctx); + + await expect( + ctx.saleDeployer + .connect(ctx.stranger) + .deploySaleWithLiquidityLauncher(plan.lbpSaleConfig, plan.foldInitCode), + ) + .to.be.revertedWithCustomError(ctx.saleDeployer, "UnauthorizedOperator") + .withArgs(ctx.stranger.address); + + expect( + await ctx.saleDeployer.usedConfigHashes( + await ctx.saleDeployer.hashLbpConfig(plan.lbpSaleConfig), + ), + ).to.equal(false); }); it("deploys FOLD + CCA through LiquidityLauncher/LBPStrategy without a predicted auction", async function () { From a9a337ef488d2375889b46c79210e00b84db3959 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:03:01 +0500 Subject: [PATCH 04/52] fix(contracts): validate BFV verifier trust anchors [L-02] --- agent/flow-trace/00_INDEX.md | 1 + agent/flow-trace/04_DKG_AND_COMPUTATION.md | 4 ++ .../verifiers/bfv/BfvDecryptionVerifier.sol | 10 +++++ .../contracts/verifiers/bfv/BfvPkVerifier.sol | 10 +++++ .../test/BfvDecryptionVerifier.spec.ts | 43 +++++++++++++++++++ .../test/BfvPkVerifier.spec.ts | 43 +++++++++++++++++++ 6 files changed, 111 insertions(+) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 8cd686ba9..7c8aaa429 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -207,3 +207,4 @@ _Found during source-code cross-referencing of these trace documents._ | 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` now caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity, while a lazy sentinel initializes the counter safely for queues created before the upgrade. | | 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` now tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, while the aggregate `hasClaimed` compatibility view reports either role. | | 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | +| 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index e2f9d32ea..f59bd7644 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -684,6 +684,10 @@ ThresholdKeyshare receives AllThresholdSharesCollected > params `(e3Id, committeeRoot, sortedNodes)` are forwarded for interface compatibility and future > circuit-level binding. +> **Verifier deployment anchors:** `BfvPkVerifier` and `BfvDecryptionVerifier` constructors reject +> zero/EOA circuit-verifier addresses and zero recursive VK hashes. Production deployment tooling +> additionally compares the immutable VK hashes with the version-controlled circuit artifacts. + --- ## Phase 3: Encrypted Computation diff --git a/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol b/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol index 84b26f5cd..0d1de160e 100644 --- a/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol +++ b/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol @@ -39,6 +39,9 @@ import { CommitteeHashLib } from "../../lib/CommitteeHashLib.sol"; * interface for forward compatibility. */ contract BfvDecryptionVerifier is IDecryptionVerifier { + error InvalidCircuitVerifier(address verifier); + error InvalidVerificationKeyHash(); + /// @dev Message is always the last 100 public inputs (100 uint64 coeffs = 800 bytes plaintext). uint256 internal constant MESSAGE_COEFFS_COUNT = 100; @@ -80,6 +83,13 @@ contract BfvDecryptionVerifier is IDecryptionVerifier { uint256 _threshold ) { require(_threshold > 0, "BfvDecryptionVerifier: threshold=0"); + if (_circuitVerifier.code.length == 0) { + revert InvalidCircuitVerifier(_circuitVerifier); + } + if ( + _expectedC6FoldKeyHash == bytes32(0) || + _expectedC7KeyHash == bytes32(0) + ) revert InvalidVerificationKeyHash(); threshold = _threshold; expectedPublicInputsLen = 4 + diff --git a/packages/interfold-contracts/contracts/verifiers/bfv/BfvPkVerifier.sol b/packages/interfold-contracts/contracts/verifiers/bfv/BfvPkVerifier.sol index ffd6f049c..7e5d853f1 100644 --- a/packages/interfold-contracts/contracts/verifiers/bfv/BfvPkVerifier.sol +++ b/packages/interfold-contracts/contracts/verifiers/bfv/BfvPkVerifier.sol @@ -39,6 +39,9 @@ import { CommitteeHashLib } from "../../lib/CommitteeHashLib.sol"; * `sortedNodes` are preserved in the interface for forward compatibility. */ contract BfvPkVerifier is IPkVerifier { + error InvalidCircuitVerifier(address verifier); + error InvalidVerificationKeyHash(); + /// @dev `dkg_aggregator` return field count. uint256 internal constant DKG_RETURN_TAIL_LEN = 2; @@ -78,6 +81,13 @@ contract BfvPkVerifier is IPkVerifier { uint256 _h ) { require(_h > 0, "BfvPkVerifier: h=0"); + if (_circuitVerifier.code.length == 0) { + revert InvalidCircuitVerifier(_circuitVerifier); + } + if ( + _expectedNodesFoldKeyHash == bytes32(0) || + _expectedC5KeyHash == bytes32(0) + ) revert InvalidVerificationKeyHash(); h = _h; committeeHashHiIdx = 2 + _h; committeeHashLoIdx = 3 + _h; diff --git a/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts b/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts index 7a71a27b3..f7d10297d 100644 --- a/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts +++ b/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts @@ -133,6 +133,49 @@ describe("BfvDecryptionVerifier", function () { }; describe("reverts", function () { + it("rejects zero, EOA, and zero-hash trust anchors at deployment", async function () { + const factory = await ethers.getContractFactory("BfvDecryptionVerifier"); + + await expect( + factory.deploy( + ethers.ZeroAddress, + EXPECTED_C6_FOLD_KEY_HASH, + EXPECTED_C7_KEY_HASH, + THRESHOLD, + ), + ) + .to.be.revertedWithCustomError(factory, "InvalidCircuitVerifier") + .withArgs(ethers.ZeroAddress); + await expect( + factory.deploy( + testSigner.address, + EXPECTED_C6_FOLD_KEY_HASH, + EXPECTED_C7_KEY_HASH, + THRESHOLD, + ), + ) + .to.be.revertedWithCustomError(factory, "InvalidCircuitVerifier") + .withArgs(testSigner.address); + + const { mockCircuit } = await loadFixture(deployWithMockCircuit); + await expect( + factory.deploy( + await mockCircuit.getAddress(), + ethers.ZeroHash, + EXPECTED_C7_KEY_HASH, + THRESHOLD, + ), + ).to.be.revertedWithCustomError(factory, "InvalidVerificationKeyHash"); + await expect( + factory.deploy( + await mockCircuit.getAddress(), + EXPECTED_C6_FOLD_KEY_HASH, + ethers.ZeroHash, + THRESHOLD, + ), + ).to.be.revertedWithCustomError(factory, "InvalidVerificationKeyHash"); + }); + it("reverts on invalid proof encoding", async function () { const { bfvDecryptionVerifier } = await loadFixture( deployWithMockCircuit, diff --git a/packages/interfold-contracts/test/BfvPkVerifier.spec.ts b/packages/interfold-contracts/test/BfvPkVerifier.spec.ts index d6066f807..2aa0e795b 100644 --- a/packages/interfold-contracts/test/BfvPkVerifier.spec.ts +++ b/packages/interfold-contracts/test/BfvPkVerifier.spec.ts @@ -84,6 +84,49 @@ describe("BfvPkVerifier", function () { }); describe("reverts", function () { + it("rejects zero, EOA, and zero-hash trust anchors at deployment", async function () { + const factory = await ethers.getContractFactory("BfvPkVerifier"); + + await expect( + factory.deploy( + ethers.ZeroAddress, + EXPECTED_NODES_FOLD_KEY_HASH, + EXPECTED_C5_KEY_HASH, + H, + ), + ) + .to.be.revertedWithCustomError(factory, "InvalidCircuitVerifier") + .withArgs(ethers.ZeroAddress); + await expect( + factory.deploy( + testSigner.address, + EXPECTED_NODES_FOLD_KEY_HASH, + EXPECTED_C5_KEY_HASH, + H, + ), + ) + .to.be.revertedWithCustomError(factory, "InvalidCircuitVerifier") + .withArgs(testSigner.address); + + const { mockCircuit } = await loadFixture(deployWithMockCircuit); + await expect( + factory.deploy( + await mockCircuit.getAddress(), + ethers.ZeroHash, + EXPECTED_C5_KEY_HASH, + H, + ), + ).to.be.revertedWithCustomError(factory, "InvalidVerificationKeyHash"); + await expect( + factory.deploy( + await mockCircuit.getAddress(), + EXPECTED_NODES_FOLD_KEY_HASH, + ethers.ZeroHash, + H, + ), + ).to.be.revertedWithCustomError(factory, "InvalidVerificationKeyHash"); + }); + it("reverts on invalid proof encoding", async function () { const { bfvPkVerifier } = await loadFixture(deployWithMockCircuit); const { e3Id, root, nodes } = ctx(); From 6983f8f001f709a32f525f0807091417da20e0e1 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:03:57 +0500 Subject: [PATCH 05/52] fix(ci): enforce core contract size budget [L-03] --- .github/workflows/ci.yml | 3 ++ agent/flow-trace/00_INDEX.md | 1 + packages/interfold-contracts/package.json | 1 + .../scripts/checkContractSize.ts | 33 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 packages/interfold-contracts/scripts/checkContractSize.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ed82a363..54cdda943 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -376,6 +376,9 @@ jobs: echo "## Lint results" >> $GITHUB_STEP_SUMMARY echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + - name: 'Enforce production contract size budget' + run: 'pnpm --filter @interfold/contracts size:check' + - name: 'Test the contracts and generate the coverage report' run: 'pnpm coverage' diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 7c8aaa429..c8fb042a9 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -208,3 +208,4 @@ _Found during source-code cross-referencing of these trace documents._ | 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` now tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, while the aggregate `hasClaimed` compatibility view reports either role. | | 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | | 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | +| 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | diff --git a/packages/interfold-contracts/package.json b/packages/interfold-contracts/package.json index 597119cc9..0beb37e06 100644 --- a/packages/interfold-contracts/package.json +++ b/packages/interfold-contracts/package.json @@ -198,6 +198,7 @@ "sale:validate": "tsx scripts/sale.ts --action validate", "sale:bid-claim": "tsx scripts/sale.ts --action bid-claim", "sale:accept-ownership": "tsx scripts/sale.ts --action accept-ownership", + "size:check": "hardhat run scripts/checkContractSize.ts", "cca:schedule": "tsx scripts/ccaSchedule.ts", "protocol": "tsx scripts/protocol.ts", "protocol:deploy": "tsx scripts/protocol.ts --action deploy", diff --git a/packages/interfold-contracts/scripts/checkContractSize.ts b/packages/interfold-contracts/scripts/checkContractSize.ts new file mode 100644 index 000000000..6e9754464 --- /dev/null +++ b/packages/interfold-contracts/scripts/checkContractSize.ts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-3.0-only +import hre from "hardhat"; + +const EIP170_LIMIT_BYTES = 24_576; +const REQUIRED_HEADROOM_BYTES = 256; +const MAX_RUNTIME_BYTES = EIP170_LIMIT_BYTES - REQUIRED_HEADROOM_BYTES; + +const RELEASE_CONTRACTS = [ + "Interfold", + "DkgAggregatorVerifier", + "DecryptionAggregatorVerifier", +] as const; + +let failed = false; +for (const contractName of RELEASE_CONTRACTS) { + const artifact = await hre.artifacts.readArtifact(contractName); + const runtimeBytes = (artifact.deployedBytecode.length - 2) / 2; + const headroom = EIP170_LIMIT_BYTES - runtimeBytes; + + console.log( + `${contractName}: ${runtimeBytes} runtime bytes (${headroom} bytes EIP-170 headroom)`, + ); + + if (runtimeBytes > MAX_RUNTIME_BYTES) { + failed = true; + console.error( + `${contractName} exceeds the ${MAX_RUNTIME_BYTES}-byte release budget ` + + `(EIP-170 minus ${REQUIRED_HEADROOM_BYTES} bytes).`, + ); + } +} + +if (failed) process.exitCode = 1; From df5c5802bef2049721344b779a084edca8040379 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:08:30 +0500 Subject: [PATCH 06/52] fix(contracts): gate bonding asset rotation [M-08] --- agent/flow-trace/00_INDEX.md | 1 + agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 7 ++ .../IBondingRegistry.json | 29 +++++- .../contracts/interfaces/IBondingRegistry.sol | 17 +++- .../contracts/registry/BondingRegistry.sol | 43 ++++++++- .../test/Registry/BondingRegistry.spec.ts | 93 ++++++++++++------- .../test/fixtures/system.ts | 2 +- 7 files changed, 148 insertions(+), 44 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index c8fb042a9..4c3bd2d78 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -209,3 +209,4 @@ _Found during source-code cross-referencing of these trace documents._ | 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | | 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | +| 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index 7d231c9b1..637476de4 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -92,6 +92,13 @@ Before a node can register, it must stake two types of collateral: └───────────────────────────────────────────────────────────┘ ``` +Bonding-asset rotation is liability-gated. A replacement ticket wrapper cannot +be configured while the old wrapper has issued tickets or a payable balance, +and the FOLD license token cannot change while the bonding registry holds any +of the old token. Replacement assets must be deployed contracts; the only zero +exception is the one-time license-token placeholder used to resolve the circular +FOLD/BondingRegistry deployment. + --- ## Step 1: Bond License (`interfold ciphernode license bond`) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 31715c871..dce92ba70 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -49,6 +49,17 @@ "name": "InvalidAmount", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "InvalidBondingAsset", + "type": "error" + }, { "inputs": [], "name": "InvalidConfiguration", @@ -84,6 +95,22 @@ "name": "OperatorUnderSlash", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "OutstandingAssetLiabilities", + "type": "error" + }, { "inputs": [], "name": "RenounceOwnershipDisabled", @@ -1081,5 +1108,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-6d77a1fac0033f87a36ca50619587087010b9b66" + "buildInfoId": "solc-0_8_28-15fb9b9f505f75cabf5ea42fcda4c0eb24a8d7e8" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 7f1b6c47e..3a169347f 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -31,6 +31,13 @@ interface IBondingRegistry { error ExitInProgress(); error ExitNotReady(); error InvalidAmount(); + + /// @notice A bonding asset must be a deployed contract (except the one-time + /// zero license-token placeholder used during circular deployment). + error InvalidBondingAsset(address asset); + + /// @notice A bonding asset cannot rotate while balances remain denominated in it. + error OutstandingAssetLiabilities(address asset, uint256 amount); error InvalidConfiguration(); error NoPendingDeregistration(); error OnlyRewardDistributor(); @@ -171,8 +178,8 @@ interface IBondingRegistry { * in the registry as an unaccounted-for surplus. Operators and * monitoring infrastructure should treat any emission of this * event as evidence that the configured `licenseToken` is not a - * well-behaved ERC-20 and should be replaced via - * `setLicenseToken`. + * well-behaved ERC-20. Rotation is permitted only after every + * balance denominated in the old token has been drained. * @param recipient The address that received the (short) transfer * @param expectedAmount The amount the registry intended to send * @param actualAmount The actual delta in registry-held balance @@ -455,14 +462,16 @@ interface IBondingRegistry { /** * @notice Set ticket price * @param newTicketPrice New price per ticket - * @dev Only callable by contract owner + * @dev Only callable by contract owner; rotation requires zero outstanding + * ticket supply and zero payable balance in the old wrapper. */ function setTicketPrice(uint256 newTicketPrice) external; /** * @notice Set license bond price required * @param newLicenseRequiredBond New license bond price - * @dev Only callable by contract owner + * @dev Only callable by contract owner; rotation requires a zero registry + * balance in the old license token. */ function setLicenseRequiredBond(uint256 newLicenseRequiredBond) external; diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index be413234c..ec88b850f 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -725,14 +725,50 @@ contract BondingRegistry is function setTicketToken( InterfoldTicketToken newTicketToken ) public onlyOwner { + address next = address(newTicketToken); + if (next.code.length == 0) revert InvalidBondingAsset(next); + + InterfoldTicketToken current = ticketToken; + if (address(current) != address(0) && current != newTicketToken) { + uint256 liabilities = current.totalSupply() + + current.payableBalance(); + if (liabilities != 0) { + revert OutstandingAssetLiabilities( + address(current), + liabilities + ); + } + } ticketToken = newTicketToken; - emit TicketTokenSet(address(newTicketToken)); + emit TicketTokenSet(next); } /// @inheritdoc IBondingRegistry function setLicenseToken(IERC20 newLicenseToken) public onlyOwner { + address next = address(newLicenseToken); + IERC20 current = licenseToken; + + // BondingRegistry is deployed before FOLD because FOLD stores the + // registry address immutably. Permit only that initial zero placeholder. + if (next == address(0)) { + if (address(current) != address(0)) { + revert InvalidBondingAsset(next); + } + } else if (next.code.length == 0) { + revert InvalidBondingAsset(next); + } + + if (address(current) != address(0) && current != newLicenseToken) { + uint256 liabilities = current.balanceOf(address(this)); + if (liabilities != 0) { + revert OutstandingAssetLiabilities( + address(current), + liabilities + ); + } + } licenseToken = newLicenseToken; - emit LicenseTokenSet(address(newLicenseToken)); + emit LicenseTokenSet(next); } /// @inheritdoc IBondingRegistry @@ -873,7 +909,8 @@ contract BondingRegistry is /// fees that burn or reroute). Internal accounting is already decremented at /// the call site, so a shortfall emits {LicenseTransferShortfall} rather than /// reverting (a revert would brick claims if the token starts taking fees); - /// the owner can swap the token via {setLicenseToken}. + /// governance must pause new bonding and drain every liability before + /// rotating the token via {setLicenseToken}. function _safeTransferLicenseWithDeltaCheck( address recipient, uint256 expectedAmount diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index e6ec4505b..7918c4dfa 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -1262,53 +1262,76 @@ describe("BondingRegistry", function () { await bondingRegistry.connect(operator1).removeTicketBalance(step); }); - /** - * M-13 reproduction guard. - * - * `claimExits` / `withdrawSlashedFunds` previously called - * `licenseToken.safeTransfer` without measuring the registry's - * own balance delta. A fee-on-transfer / rebasing token configured - * via `setLicenseToken` would silently underpay the recipient while - * the registry's internal accounting was still decremented by the - * requested amount. The fix measures the delta and emits - * `LicenseTransferShortfall(recipient, expected, actual)`. - */ - it("M-13: emits LicenseTransferShortfall when license token charges a transfer fee", async function () { - const { bondingRegistry, licenseToken, operator1, operator1Address } = + it("AUD-M08: blocks license-token rotation until old liabilities are drained", async function () { + const { bondingRegistry, licenseToken, operator1 } = await loadFixture(setup); - // Bond + queue a license exit with the well-behaved token. const bondAmount = LICENSE_REQUIRED_BOND; await licenseToken .connect(operator1) .approve(await bondingRegistry.getAddress(), bondAmount); await bondingRegistry.connect(operator1).bondLicense(bondAmount); - await bondingRegistry.connect(operator1).registerOperator(); - await bondingRegistry.connect(operator1).unbondLicense(bondAmount); - await time.increase(SEVEN_DAYS_IN_SECONDS + 1); - // Swap the license token for a 1% fee-on-transfer token. const FoTFactory = await ethers.getContractFactory( "MockFeeOnTransferToken", ); const fot = await FoTFactory.deploy(100n); // 100 bps = 1% - // Seed the registry with enough FoT tokens to honor the (gross) claim - // amount: tests need to verify the delta-detection emits, not that the - // registry magically conjures tokens. We mint `bondAmount` directly to - // the registry's address. - await fot.mint(await bondingRegistry.getAddress(), bondAmount); - await bondingRegistry.setLicenseToken(await fot.getAddress()); - - // Claim — the safeTransfer will short by 1%. - const expectedFee = bondAmount / 100n; - const expectedActual = bondAmount - expectedFee; - - await expect(bondingRegistry.connect(operator1).claimExits(0, bondAmount)) - .to.emit(bondingRegistry, "LicenseTransferShortfall") - .withArgs(operator1Address, bondAmount, expectedActual); - - // Operator received the net (post-fee) amount. - expect(await fot.balanceOf(operator1Address)).to.equal(expectedActual); + await expect(bondingRegistry.setLicenseToken(ethers.ZeroAddress)) + .to.be.revertedWithCustomError(bondingRegistry, "InvalidBondingAsset") + .withArgs(ethers.ZeroAddress); + await expect(bondingRegistry.setLicenseToken(operator1.address)) + .to.be.revertedWithCustomError(bondingRegistry, "InvalidBondingAsset") + .withArgs(operator1.address); + await expect(bondingRegistry.setLicenseToken(await fot.getAddress())) + .to.be.revertedWithCustomError( + bondingRegistry, + "OutstandingAssetLiabilities", + ) + .withArgs(await licenseToken.getAddress(), bondAmount); + }); + + it("AUD-M08: blocks ticket-token rotation until supply and payouts are drained", async function () { + const { + bondingRegistry, + licenseToken, + ticketToken, + usdcToken, + operator1, + owner, + } = await loadFixture(setup); + + await licenseToken + .connect(operator1) + .approve(await bondingRegistry.getAddress(), LICENSE_REQUIRED_BOND); + await bondingRegistry + .connect(operator1) + .bondLicense(LICENSE_REQUIRED_BOND); + await bondingRegistry.connect(operator1).registerOperator(); + + const ticketAmount = ethers.parseUnits("10", 6); + await usdcToken + .connect(operator1) + .approve(await ticketToken.getAddress(), ticketAmount); + await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); + + const replacement = await ( + await ethers.getContractFactory("InterfoldTicketToken") + ).deploy( + await usdcToken.getAddress(), + await bondingRegistry.getAddress(), + owner.address, + ); + await replacement.waitForDeployment(); + + await expect(bondingRegistry.setTicketToken(ethers.ZeroAddress)) + .to.be.revertedWithCustomError(bondingRegistry, "InvalidBondingAsset") + .withArgs(ethers.ZeroAddress); + await expect(bondingRegistry.setTicketToken(await replacement.getAddress())) + .to.be.revertedWithCustomError( + bondingRegistry, + "OutstandingAssetLiabilities", + ) + .withArgs(await ticketToken.getAddress(), ticketAmount); }); }); }); diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index 8b36e31b6..e27a7f307 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -341,7 +341,7 @@ export async function deployInterfoldSystem( BondingRegistry: { owner: ownerAddress, ticketToken: await ticketToken.getAddress(), - licenseToken: ADDRESS_ONE, // placeholder — fixed below + licenseToken: ethers.ZeroAddress, // one-time placeholder — fixed below registry: effectiveRegistryAddress, slashedFundsTreasury: slashedFundsTreasuryAddress, ticketPrice: TICKET_PRICE, From 5a87e9d8ea1cfd22fc68079a66840f404a299fbe Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:12:32 +0500 Subject: [PATCH 07/52] fix(contracts): lock operator eligibility policy [M-03] --- agent/flow-trace/00_INDEX.md | 1 + agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 7 ++++ .../IBondingRegistry.json | 20 +++++++++- .../ICiphernodeRegistry.json | 15 ++++++- .../contracts/interfaces/IBondingRegistry.sol | 6 +++ .../interfaces/ICiphernodeRegistry.sol | 3 ++ .../contracts/registry/BondingRegistry.sol | 14 +++++++ .../registry/CiphernodeRegistryOwnable.sol | 2 +- .../contracts/test/MockCiphernodeRegistry.sol | 16 ++++++-- .../test/Registry/BondingRegistry.spec.ts | 39 +++++++++++++++++++ 10 files changed, 116 insertions(+), 7 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 4c3bd2d78..11112f112 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -210,3 +210,4 @@ _Found during source-code cross-referencing of these trace documents._ | 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | +| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy. Upgraded deployments also consult the live registry count, and the minimum ticket requirement can never be zero. | diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index 637476de4..6cd3366c2 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -173,6 +173,13 @@ _updateOperatorStatus(operator): emit OperatorActivationChanged(operator, true) ``` +The eligibility policy (`ticketPrice`, `licenseRequiredBond`, +`licenseActiveBps`, and `minTicketBalance`) is deployment-time configuration. +The first successful operator registration permanently locks all four values; +`minTicketBalance` must be nonzero. Consequently cached `active` state, +`numActiveOperators`, and the ticket units used for later sortition cannot be +invalidated by a governance update. + --- ## Step 2: Buy Tickets (`interfold ciphernode tickets buy`) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index dce92ba70..b05a605ce 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -18,6 +18,11 @@ "name": "CiphernodeBanned", "type": "error" }, + { + "inputs": [], + "name": "EligibilityConfigurationLocked", + "type": "error" + }, { "inputs": [ { @@ -489,6 +494,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "eligibilityConfigurationLocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "exitDelay", @@ -1108,5 +1126,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-15fb9b9f505f75cabf5ea42fcda4c0eb24a8d7e8" + "buildInfoId": "solc-0_8_28-e13a73dbee9a6b66f3d5ad62b37b50945035d8cf" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 810d6fa89..bf9049a1d 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1109,6 +1109,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "numCiphernodes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1332,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-6d77a1fac0033f87a36ca50619587087010b9b66" + "buildInfoId": "solc-0_8_28-e13a73dbee9a6b66f3d5ad62b37b50945035d8cf" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 3a169347f..45b33c5c2 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -38,6 +38,9 @@ interface IBondingRegistry { /// @notice A bonding asset cannot rotate while balances remain denominated in it. error OutstandingAssetLiabilities(address asset, uint256 amount); + + /// @notice Eligibility policy becomes immutable when the first operator registers. + error EligibilityConfigurationLocked(); error InvalidConfiguration(); error NoPendingDeregistration(); error OnlyRewardDistributor(); @@ -268,6 +271,9 @@ interface IBondingRegistry { */ function numActiveOperators() external view returns (uint256); + /// @notice Whether eligibility-affecting configuration is permanently locked. + function eligibilityConfigurationLocked() external view returns (bool); + /** * @notice Check if operator has deregistration in progress * @param operator Address of the operator diff --git a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol index a505c4189..f14af98ee 100644 --- a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -15,6 +15,9 @@ import { IBondingRegistry } from "./IBondingRegistry.sol"; * and coordinates committee selection for E3 computations */ interface ICiphernodeRegistry { + /// @notice Current number of registered ciphernodes. + function numCiphernodes() external view returns (uint256); + /// @notice Tracks a committee member's lifecycle state for a given E3. enum MemberStatus { None, diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index ec88b850f..5e870eef3 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -150,6 +150,9 @@ contract BondingRegistry is /// @dev Internal state for managing exit queue of tickets and licenses ExitQueueLib.ExitQueueState private _exits; + /// @notice One-way lock for every parameter that affects operator eligibility. + bool public eligibilityConfigurationLocked; + // ====================== // Modifiers // ====================== @@ -339,6 +342,7 @@ contract BondingRegistry is ); operators[msg.sender].registered = true; + eligibilityConfigurationLocked = true; // CiphernodeRegistry already emits an event when a ciphernode is added registry.addCiphernode(msg.sender); @@ -651,6 +655,7 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setTicketPrice(uint256 newTicketPrice) public onlyOwner { + _requireEligibilityConfigurationMutable(); require(newTicketPrice != 0, InvalidConfiguration()); uint256 oldValue = ticketPrice; @@ -663,6 +668,7 @@ contract BondingRegistry is function setLicenseRequiredBond( uint256 newLicenseRequiredBond ) public onlyOwner { + _requireEligibilityConfigurationMutable(); require(newLicenseRequiredBond != 0, InvalidConfiguration()); uint256 oldValue = licenseRequiredBond; @@ -677,6 +683,7 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setLicenseActiveBps(uint256 newBps) public onlyOwner { + _requireEligibilityConfigurationMutable(); require(newBps > 0 && newBps <= BPS_BASE, InvalidConfiguration()); uint256 oldValue = licenseActiveBps; @@ -687,6 +694,8 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setMinTicketBalance(uint256 newMinTicketBalance) public onlyOwner { + _requireEligibilityConfigurationMutable(); + require(newMinTicketBalance != 0, InvalidConfiguration()); uint256 oldValue = minTicketBalance; minTicketBalance = newMinTicketBalance; @@ -904,6 +913,11 @@ contract BondingRegistry is return (licenseRequiredBond * licenseActiveBps) / BPS_BASE; } + function _requireEligibilityConfigurationMutable() internal view { + if (eligibilityConfigurationLocked) + revert EligibilityConfigurationLocked(); + } + /// @dev `safeTransfer` of the license token, measuring the RECIPIENT-side delta /// to detect fee-on-transfer / rebasing behavior (sender-side delta misses /// fees that burn or reroute). Internal accounting is already decremented at diff --git a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index bf5faa96c..74b1b4732 100644 --- a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -80,7 +80,7 @@ contract CiphernodeRegistryOwnable is IBondingRegistry public bondingRegistry; /// @notice Current number of registered ciphernodes - uint256 public numCiphernodes; + uint256 public override numCiphernodes; /// @notice Submission Window for an E3 Sortition. /// @dev The submission window is the time period during which the ciphernodes can submit diff --git a/packages/interfold-contracts/contracts/test/MockCiphernodeRegistry.sol b/packages/interfold-contracts/contracts/test/MockCiphernodeRegistry.sol index d1bbf2017..d7e6b5e4f 100644 --- a/packages/interfold-contracts/contracts/test/MockCiphernodeRegistry.sol +++ b/packages/interfold-contracts/contracts/test/MockCiphernodeRegistry.sol @@ -10,6 +10,8 @@ import { IInterfold } from "../interfaces/IInterfold.sol"; import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; contract MockCiphernodeRegistry is ICiphernodeRegistry { + uint256 public override numCiphernodes; + /// @notice Configurable committee members per E3 for testing mapping(uint256 e3Id => address[] nodes) private _committeeNodes; @@ -85,11 +87,13 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { return false; } - // solhint-disable-next-line no-empty-blocks - function addCiphernode(address) external pure {} + function addCiphernode(address) external { + numCiphernodes++; + } - // solhint-disable-next-line no-empty-blocks - function removeCiphernode(address) external pure {} + function removeCiphernode(address) external { + numCiphernodes--; + } function publishCommittee( uint256, @@ -253,6 +257,10 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { } contract MockCiphernodeRegistryEmptyKey is ICiphernodeRegistry { + function numCiphernodes() external pure returns (uint256) { + return 0; + } + function requestCommittee( uint256, uint256, diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index 7918c4dfa..c35b53a17 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -915,6 +915,45 @@ describe("BondingRegistry", function () { }); }); + it("AUD-M03: locks every eligibility parameter at first registration", async function () { + const { bondingRegistry, licenseToken, operator1 } = + await loadFixture(setup); + + await licenseToken + .connect(operator1) + .approve(await bondingRegistry.getAddress(), LICENSE_REQUIRED_BOND); + await bondingRegistry + .connect(operator1) + .bondLicense(LICENSE_REQUIRED_BOND); + await bondingRegistry.connect(operator1).registerOperator(); + + expect(await bondingRegistry.eligibilityConfigurationLocked()).to.equal( + true, + ); + for (const call of [ + () => bondingRegistry.setTicketPrice(TICKET_PRICE + 1n), + () => + bondingRegistry.setLicenseRequiredBond(LICENSE_REQUIRED_BOND + 1n), + () => bondingRegistry.setLicenseActiveBps(9000n), + () => bondingRegistry.setMinTicketBalance(MIN_TICKET_BALANCE + 1), + ]) { + await expect(call()).to.be.revertedWithCustomError( + bondingRegistry, + "EligibilityConfigurationLocked", + ); + } + }); + + it("AUD-M03: rejects a zero minimum ticket requirement", async function () { + const { bondingRegistry } = await loadFixture(setup); + await expect( + bondingRegistry.setMinTicketBalance(0), + ).to.be.revertedWithCustomError( + bondingRegistry, + "InvalidConfiguration", + ); + }); + describe("withdrawSlashedFunds()", function () { it("allows owner to withdraw slashed funds", async function () { const { bondingRegistry, treasury } = await loadFixture(setup); From e36ad806fd74e448bfb20298c01afd480497916e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:24:47 +0500 Subject: [PATCH 08/52] fix(contracts): bind requests to fee consent [H-02] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 9 +++- crates/evm-helpers/src/contracts.rs | 11 ++++ .../interfaces/IInterfold.sol/IInterfold.json | 54 ++++++++++++++++++- .../contracts/Interfold.sol | 8 ++- .../contracts/interfaces/IInterfold.sol | 10 ++++ .../contracts/lib/InterfoldPricing.sol | 12 ++++- .../interfold-contracts/tasks/interfold.ts | 7 ++- .../test/E3Lifecycle/E3Integration.spec.ts | 18 +++++-- .../test/E3Lifecycle/Sortition.spec.ts | 7 ++- .../test/Interfold.spec.ts | 28 ++++++++++ .../test/Pricing/DustRotation.spec.ts | 9 +++- .../test/Pricing/Pricing.spec.ts | 26 ++++++--- .../Pricing/PullPaymentsAndAllowlist.spec.ts | 26 ++++++--- .../CiphernodeRegistryOwnable.spec.ts | 4 +- .../test/Slashing/CommitteeExpulsion.spec.ts | 6 ++- .../test/fixtures/helpers.ts | 23 ++++++-- .../test/fixtures/system.ts | 2 + .../src/contracts/contract-client.ts | 6 +++ packages/interfold-sdk/src/contracts/types.ts | 4 ++ packages/interfold-sdk/src/interfold-sdk.ts | 2 + 21 files changed, 238 insertions(+), 35 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 11112f112..25310ec23 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -211,3 +211,4 @@ _Found during source-code cross-referencing of these trace documents._ | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy. Upgraded deployments also consult the live registry count, and the minimum ticket requirement can never be zero. | +| 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index b30c5ea65..6b04b6f02 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -30,7 +30,9 @@ Requester calls: Interfold.request({ e3Program:
, // computation program contract e3ProgramParams: , // ABI-encoded program parameters computeProviderParams: , - customParams: + customParams: , + maxFee: , // maximum live quote the requester accepts + requestDeadline: // last timestamp at which the request may execute }) │ ├─ VALIDATION: @@ -39,12 +41,15 @@ Requester calls: Interfold.request({ │ ├─ inputWindow[0] >= block.timestamp (start in future) │ ├─ inputWindow[1] >= inputWindow[0] (end after start) │ ├─ total duration < maxDuration -│ └─ e3Programs[e3Program] == true (program whitelisted) +│ ├─ e3Programs[e3Program] == true (program whitelisted) +│ ├─ block.timestamp <= requestDeadline +│ └─ live fee quote <= maxFee │ ├─ FEE CALCULATION: │ ├─ fee = getE3Quote() │ │ → InterfoldPricing quote from committee threshold, time windows, │ │ proof counts, availability, decryption/publication costs, and margin +│ ├─ InterfoldPricing enforces the requester's maxFee and deadline │ ├─ feeToken.transferFrom(requester, address(this), fee) │ └─ e3Payments[e3Id] = fee (stored per-E3) │ _e3FeeTokens[e3Id] = feeToken (survives global token rotation) diff --git a/crates/evm-helpers/src/contracts.rs b/crates/evm-helpers/src/contracts.rs index 451583897..61f98df6d 100644 --- a/crates/evm-helpers/src/contracts.rs +++ b/crates/evm-helpers/src/contracts.rs @@ -74,6 +74,8 @@ sol! { bytes computeProviderParams; bytes customParams; bool proofAggregationEnabled; + uint256 maxFee; + uint256 requestDeadline; } #[derive(Debug, PartialEq)] @@ -387,6 +389,8 @@ where computeProviderParams: compute_provider_params, customParams: Bytes::new(), proofAggregationEnabled: proof_aggregation_enabled, + maxFee: U256::MAX, + requestDeadline: input_window[0], }; let contract = Interfold::new(self.contract_address, &self.provider); @@ -461,8 +465,15 @@ impl InterfoldWrite for InterfoldContract { computeProviderParams: compute_provider_params.clone(), customParams: custom_params.clone(), proofAggregationEnabled: proof_aggregation_enabled, + maxFee: U256::MAX, + requestDeadline: input_window[0], }; + let max_fee = contract.getE3Quote(e3_request.clone()).call().await?; + let e3_request = E3RequestParams { + maxFee: max_fee, + ..e3_request + }; let builder = contract.request(e3_request).nonce(nonce); let receipt = builder.send().await?.get_receipt().await?; e3_utils::require_successful_receipt("request E3", &receipt)?; diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 1c0c00c4c..82e2702db 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -177,6 +177,22 @@ "name": "FailureConditionNotMet", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "quotedFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFee", + "type": "uint256" + } + ], + "name": "FeeExceedsMax", + "type": "error" + }, { "inputs": [ { @@ -424,6 +440,22 @@ "name": "ProofRequired", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestDeadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + } + ], + "name": "RequestExpired", + "type": "error" + }, { "inputs": [ { @@ -1531,6 +1563,16 @@ "internalType": "bool", "name": "proofAggregationEnabled", "type": "bool" + }, + { + "internalType": "uint256", + "name": "maxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestDeadline", + "type": "uint256" } ], "internalType": "struct IInterfold.E3RequestParams", @@ -2002,6 +2044,16 @@ "internalType": "bool", "name": "proofAggregationEnabled", "type": "bool" + }, + { + "internalType": "uint256", + "name": "maxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestDeadline", + "type": "uint256" } ], "internalType": "struct IInterfold.E3RequestParams", @@ -2390,5 +2442,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-6d77a1fac0033f87a36ca50619587087010b9b66" + "buildInfoId": "solc-0_8_28-21aba0cf843b2f04de4551ed3d28e7d82f2fe707" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index b0961a56d..eebb42e0e 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -51,10 +51,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { /// operator margin cannot be configured to make requests unaffordable. uint16 public constant MAX_MARGIN_BPS = 5_000; - /// @notice Thrown when the quoted fee exceeds the requester-supplied bound. - /// Declared in {IInterfold} so {InterfoldPricing} can revert with the - /// same selector when validating a quote via DELEGATECALL. - //////////////////////////////////////////////////////////// // // // Storage Variables // @@ -290,7 +286,9 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { _timeoutConfig.computeWindow, _timeoutConfig.decryptionWindow, maxDuration, - e3Fee + e3Fee, + requestParams.maxFee, + requestParams.requestDeadline ); e3Id = nexte3Id; diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index 3fd2214d6..e344e459b 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -473,6 +473,12 @@ interface IInterfold { /// @param token The disallowed token. error FeeTokenNotAllowed(IERC20 token); + /// @notice The live quote exceeds the maximum fee authorized by the requester. + error FeeExceedsMax(uint256 quotedFee, uint256 maxFee); + + /// @notice The bounded request was mined after its requester-authorized deadline. + error RequestExpired(uint256 requestDeadline, uint256 currentTimestamp); + /// @notice Caller has no balance to claim for the given E3 / treasury / token. error NothingToClaim(); @@ -502,6 +508,10 @@ interface IInterfold { /// C5 and C7 proofs are always generated and verified on-chain /// regardless of this flag. bool proofAggregationEnabled; + /// @notice Maximum fee-token amount authorized for this request. + uint256 maxFee; + /// @notice Last timestamp at which this request may execute. + uint256 requestDeadline; } //////////////////////////////////////////////////////////// diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 74e50c05a..f502d610b 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -206,7 +206,7 @@ library InterfoldPricing { revert IInterfold.BelowMinThreshold(threshold[0], minThreshold); } - /// @notice Mirrors the input-window / duration gates at the top + /// @notice Mirrors the request-consent, input-window, and duration gates /// of {Interfold.request}. Reverts with the same selectors so off- /// chain `revertedWithCustomError(interfold, ...)` lookups keep /// working. @@ -216,14 +216,22 @@ library InterfoldPricing { /// @param decryptionWindow `_timeoutConfig.decryptionWindow`. /// @param maxDuration The Interfold-wide upper bound. /// @param quotedFee Fee returned by {InterfoldPricing.quote}. + /// @param maxFee Maximum fee authorized by the requester. + /// @param requestDeadline Last timestamp at which this quote is valid. function validateRequest( uint256[2] calldata inputWindow, uint256 nowTs, uint256 computeWindow, uint256 decryptionWindow, uint256 maxDuration, - uint256 quotedFee // solhint-disable-line no-unused-vars + uint256 quotedFee, + uint256 maxFee, + uint256 requestDeadline ) external pure { + if (nowTs > requestDeadline) + revert IInterfold.RequestExpired(requestDeadline, nowTs); + if (quotedFee > maxFee) + revert IInterfold.FeeExceedsMax(quotedFee, maxFee); if (inputWindow[0] < nowTs) revert IInterfold.InvalidInputDeadlineStart(inputWindow[0]); if (inputWindow[1] < inputWindow[0]) diff --git a/packages/interfold-contracts/tasks/interfold.ts b/packages/interfold-contracts/tasks/interfold.ts index c203381db..767baa806 100644 --- a/packages/interfold-contracts/tasks/interfold.ts +++ b/packages/interfold-contracts/tasks/interfold.ts @@ -239,6 +239,8 @@ export const requestCommittee = task( computeProviderParams, customParams, proofAggregationEnabled, + maxFee: ethers.MaxUint256, + requestDeadline: inputWindowStart, }; console.log("Request parameters:", requestParams); @@ -265,7 +267,10 @@ export const requestCommittee = task( await approveTx.wait(); console.log("USDC approved"); - const tx = await interfoldContract.request(requestParams); + const tx = await interfoldContract.request({ + ...requestParams, + maxFee: fee, + }); console.log("Requesting committee... ", tx.hash); await tx.wait(); diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 22c44fa28..e8a96e022 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -132,11 +132,15 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: startTime + 100, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(signer).approve(interfoldAddress, fee); - await interfold.connect(signer).request(requestParams); + await interfold + .connect(signer) + .request({ ...requestParams, maxFee: fee }); return { e3Id: 0 }; }; @@ -1109,10 +1113,14 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: startTime, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold.connect(requester).request(requestParams); + await interfold + .connect(requester) + .request({ ...requestParams, maxFee: fee }); return n; }; @@ -1192,10 +1200,14 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: startTime, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold.connect(requester).request(requestParams); + await interfold + .connect(requester) + .request({ ...requestParams, maxFee: fee }); } // Fail both diff --git a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts index bd4775f3b..ada174d86 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts @@ -115,8 +115,13 @@ async function deployStack() { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 10, } as any; - return interfold.connect(requester).request(req); + return interfold.connect(requester).request({ + ...req, + maxFee: await interfold.getE3Quote(req), + }); }; return { diff --git a/packages/interfold-contracts/test/Interfold.spec.ts b/packages/interfold-contracts/test/Interfold.spec.ts index 300ea6679..255eb9338 100644 --- a/packages/interfold-contracts/test/Interfold.spec.ts +++ b/packages/interfold-contracts/test/Interfold.spec.ts @@ -384,9 +384,35 @@ describe("Interfold", function () { computeProviderParams: request.computeProviderParams, customParams: request.customParams, proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: request.inputWindow[0], }), ).to.be.revertedWithCustomError(usdcToken, "ERC20InsufficientAllowance"); }); + it("AUD-H02: reverts when the live quote exceeds maxFee", async function () { + const { interfold, request, usdcToken } = await loadFixture(setup); + const fee = await interfold.getE3Quote(request); + await usdcToken.approve(await interfold.getAddress(), fee); + + await expect(interfold.request({ ...request, maxFee: fee - 1n })) + .to.be.revertedWithCustomError(interfold, "FeeExceedsMax") + .withArgs(fee, fee - 1n); + }); + it("AUD-H02: reverts after the requester-authorized deadline", async function () { + const { interfold, request } = await loadFixture(setup); + const deadline = BigInt(request.inputWindow[0].toString()); + await time.increaseTo(deadline + 1n); + + await expect( + interfold.request.staticCall({ + ...request, + maxFee: ethers.MaxUint256, + requestDeadline: deadline, + }), + ) + .to.be.revertedWithCustomError(interfold, "RequestExpired") + .withArgs(deadline, deadline + 1n); + }); it("reverts if committee size is not configured", async function () { const { interfold, request } = await loadFixture(setup); const configuredSizes = COMMITTEE_THRESHOLDS_DEFAULT.map( @@ -402,6 +428,8 @@ describe("Interfold", function () { computeProviderParams: request.computeProviderParams, customParams: request.customParams, proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: request.inputWindow[0], }; // `CommitteeSizeNotConfigured(3)` reverts correctly on-chain; ethers cannot // decode the custom error when the enum arg is not a named variant (0..2). diff --git a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts index 1e8a172be..24f393fc3 100644 --- a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts +++ b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts @@ -106,6 +106,8 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now0 + 10, } as any; }; @@ -125,9 +127,14 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 10, }; await feeToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request(req); + await interfold.request({ + ...req, + maxFee: await interfold.getE3Quote(req), + }); // topNodes are sorted by ascending address; operator3 < operator1 < operator2 const nodes = [ await operator3.getAddress(), diff --git a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts index 8de24cb50..089f7d0a2 100644 --- a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts +++ b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts @@ -359,11 +359,16 @@ describe("E3 Pricing", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 100, }; // Make request with large approval to avoid fee mismatch await usdcToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request(freshRequest); + await interfold.request({ + ...freshRequest, + maxFee: await interfold.getE3Quote(freshRequest), + }); const e3Id = 0; const fee = await interfold.e3Payments(e3Id); @@ -446,11 +451,16 @@ describe("E3 Pricing", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 100, }; // Make request with large approval await usdcToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request(freshRequest); + await interfold.request({ + ...freshRequest, + maxFee: await interfold.getE3Quote(freshRequest), + }); const e3Id = 0; const fee = await interfold.e3Payments(e3Id); @@ -540,7 +550,7 @@ describe("E3 Pricing", function () { const balanceBefore = await usdcToken.balanceOf(ownerAddr); await usdcToken.approve(await interfold.getAddress(), fee); - await interfold.request(request); + await interfold.request({ ...request, maxFee: fee }); const balanceAfter = await usdcToken.balanceOf(ownerAddr); expect(balanceBefore - balanceAfter).to.equal(fee); @@ -552,10 +562,12 @@ describe("E3 Pricing", function () { // Approve only 1 unit await usdcToken.approve(await interfold.getAddress(), 1); - await expect(interfold.request(request)).to.be.revertedWithCustomError( - usdcToken, - "ERC20InsufficientAllowance", - ); + await expect( + interfold.request({ + ...request, + maxFee: await interfold.getE3Quote(request), + }), + ).to.be.revertedWithCustomError(usdcToken, "ERC20InsufficientAllowance"); }); }); diff --git a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts index 93f70d2cb..e92a2beb6 100644 --- a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts +++ b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts @@ -95,6 +95,8 @@ describe("Interfold — pull payments + fee-token allow-list", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 10, }; return { @@ -127,7 +129,10 @@ describe("Interfold — pull payments + fee-token allow-list", function () { request, } = ctx; await feeToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request(request); + await interfold.request({ + ...request, + maxFee: await interfold.getE3Quote(request), + }); const e3Id = 0; const nodes = [ await operator1.getAddress(), @@ -179,8 +184,12 @@ describe("Interfold — pull payments + fee-token allow-list", function () { const req2 = { ...request, inputWindow: [now + 10, now + inputWindowDuration] as [number, number], + requestDeadline: now + 10, }; - await interfold.request(req2); + await interfold.request({ + ...req2, + maxFee: await interfold.getE3Quote(req2), + }); const e3Id2 = 1; await setupAndPublishCommittee( ctx.ciphernodeRegistryContract, @@ -270,15 +279,18 @@ describe("Interfold — pull payments + fee-token allow-list", function () { const fresh = { ...request, inputWindow: [now + 10, now + inputWindowDuration] as [number, number], + requestDeadline: now + 10, }; - await expect(interfold.request(fresh)).to.be.revertedWithCustomError( - interfold, - "FeeTokenNotAllowed", - ); + await expect( + interfold.request({ ...fresh, maxFee: ethers.MaxUint256 }), + ).to.be.revertedWithCustomError(interfold, "FeeTokenNotAllowed"); // Re-allow restores request(). await interfold.setFeeTokenAllowed(await feeToken.getAddress(), true); - await interfold.request(fresh); // should not revert + await interfold.request({ + ...fresh, + maxFee: await interfold.getE3Quote(fresh), + }); // should not revert }); }); }); diff --git a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index 54f15f5ae..b72055c55 100644 --- a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -78,6 +78,8 @@ describe("CiphernodeRegistryOwnable", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: currentTime + 100, }; const fee = await interfold.getE3Quote(requestParams); @@ -85,7 +87,7 @@ describe("CiphernodeRegistryOwnable", function () { const interfoldContract = signer ? interfold.connect(signer) : interfold; await tokenContract.approve(await interfold.getAddress(), fee); - return interfoldContract.request(requestParams); + return interfoldContract.request({ ...requestParams, maxFee: fee }); } describe("constructor / initialize()", function () { diff --git a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts index 9ee2d1e8d..d501948da 100644 --- a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -145,11 +145,15 @@ describe("Committee Expulsion & Fault Tolerance", function () { ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: startTime + 100, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold.connect(requester).request(requestParams); + await interfold + .connect(requester) + .request({ ...requestParams, maxFee: fee }); } async function finalizeCommitteeWithOperators( diff --git a/packages/interfold-contracts/test/fixtures/helpers.ts b/packages/interfold-contracts/test/fixtures/helpers.ts index f9eec7897..b8f2248ea 100644 --- a/packages/interfold-contracts/test/fixtures/helpers.ts +++ b/packages/interfold-contracts/test/fixtures/helpers.ts @@ -71,15 +71,30 @@ export const setupAndPublishCommittee = async ( export const makeRequest = async ( interfold: Interfold, usdcToken: MockUSDC, - requestParams: IInterfold.E3RequestParamsStruct, + requestParams: Omit< + IInterfold.E3RequestParamsStruct, + "maxFee" | "requestDeadline" + > & + Partial< + Pick + >, signer?: Signer, ): Promise => { - const fee = await interfold.getE3Quote(requestParams); + const normalizedParams: IInterfold.E3RequestParamsStruct = { + ...requestParams, + maxFee: requestParams.maxFee ?? ethers.MaxUint256, + requestDeadline: + requestParams.requestDeadline ?? requestParams.inputWindow[0], + }; + const fee = await interfold.getE3Quote(normalizedParams); const tokenContract = signer ? usdcToken.connect(signer) : usdcToken; const interfoldContract = signer ? interfold.connect(signer) : interfold; await tokenContract.approve(await interfold.getAddress(), fee); - return interfoldContract.request(requestParams); + return interfoldContract.request({ + ...normalizedParams, + maxFee: fee, + }); }; /** Options for {@link buildRequestParams}. */ @@ -132,5 +147,7 @@ export const buildRequestParams = async ( ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: opts.proofAggregationEnabled ?? false, + maxFee: ethers.MaxUint256, + requestDeadline: now + startOffset, }; }; diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index e27a7f307..2a7a217dc 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -547,6 +547,8 @@ export async function deployInterfoldSystem( ["0x1234567890123456789012345678901234567890"], ), proofAggregationEnabled: false, + maxFee: ethers.MaxUint256, + requestDeadline: now + 10, }; return { diff --git a/packages/interfold-sdk/src/contracts/contract-client.ts b/packages/interfold-sdk/src/contracts/contract-client.ts index 48432c5e9..a23f6cc52 100644 --- a/packages/interfold-sdk/src/contracts/contract-client.ts +++ b/packages/interfold-sdk/src/contracts/contract-client.ts @@ -145,6 +145,8 @@ export class ContractClient { } const committeeSize = validateCommitteeSize(params.committeeSize) + const maxFee = params.maxFee ?? (await this.getE3Quote(params)) + const requestDeadline = params.requestDeadline ?? params.inputWindow[0] const { request } = await this.publicClient.simulateContract({ address: this.contracts.interfold, @@ -159,6 +161,8 @@ export class ContractClient { computeProviderParams: params.computeProviderParams, customParams: params.customParams || '0x', proofAggregationEnabled: params.proofAggregationEnabled ?? true, + maxFee, + requestDeadline, }, ], account, @@ -234,6 +238,8 @@ export class ContractClient { computeProviderParams: requestParams.computeProviderParams, customParams: requestParams.customParams || '0x', proofAggregationEnabled: requestParams.proofAggregationEnabled ?? true, + maxFee: requestParams.maxFee ?? 0n, + requestDeadline: requestParams.requestDeadline ?? requestParams.inputWindow[0], }, ], }) diff --git a/packages/interfold-sdk/src/contracts/types.ts b/packages/interfold-sdk/src/contracts/types.ts index 7897bb365..05838b739 100644 --- a/packages/interfold-sdk/src/contracts/types.ts +++ b/packages/interfold-sdk/src/contracts/types.ts @@ -63,6 +63,10 @@ export interface E3RequestParams extends RequestParams { /** When true, ciphernodes generate wrapper/fold proofs for DKG proof aggregation. * When false, proof aggregation is skipped for faster computation. Defaults to true. */ proofAggregationEnabled?: boolean + /** Maximum fee-token amount authorized for this request. Defaults to a fresh quote. */ + maxFee?: bigint + /** Last timestamp at which the request may execute. Defaults to inputWindow[0]. */ + requestDeadline?: bigint } export enum E3Stage { diff --git a/packages/interfold-sdk/src/interfold-sdk.ts b/packages/interfold-sdk/src/interfold-sdk.ts index 39c2a62d8..5a9d3fe7f 100644 --- a/packages/interfold-sdk/src/interfold-sdk.ts +++ b/packages/interfold-sdk/src/interfold-sdk.ts @@ -141,6 +141,8 @@ export class InterfoldSDK { computeProviderParams: `0x${string}` customParams?: `0x${string}` proofAggregationEnabled?: boolean + maxFee?: bigint + requestDeadline?: bigint gasLimit?: bigint }): Promise { return this.contractClient.requestE3(params) From 73826ad850943309838ade9621f19f8b87c9b69b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:30:47 +0500 Subject: [PATCH 09/52] fix(contracts): freeze collateral for pending slashes [H-03] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 39 ++++-- .../ISlashingManager.json | 39 +++++- .../contracts/interfaces/IBondingRegistry.sol | 4 +- .../contracts/interfaces/ISlashingManager.sol | 18 ++- .../contracts/registry/BondingRegistry.sol | 41 ++++-- .../contracts/slashing/SlashingManager.sol | 69 ++++++--- .../test/Registry/BondingRegistry.spec.ts | 11 +- .../test/Slashing/SlashingLanes.spec.ts | 132 ++++++++++++++++-- 9 files changed, 281 insertions(+), 73 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 25310ec23..36cc23925 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -212,3 +212,4 @@ _Found during source-code cross-referencing of these trace documents._ | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy. Upgraded deployments also consult the live registry count, and the minimum ticket requirement can never be zero. | | 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | +| 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index bbc11cec9..441bd4e2c 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -549,16 +549,17 @@ Anyone calls: SlashingManager.proposeSlash(e3Id, operator, proof) │ ticketAmount: policy.ticketPenalty, │ licenseAmount: policy.licensePenalty, │ proofVerified: true, // Lane A marker -│ executableAt: block.timestamp, // immediate +│ executableAt: block.timestamp + policy.appealWindow, │ banNode: policy.banNode, │ affectsCommittee: policy.affectsCommittee, │ failureReason: policy.failureReason │ } │ → Policy values snapshotted at proposal time │ → Prevents execution drift if policy changes later +│ → Increment unresolved financial proposal count for operator │ -└─ 8. IMMEDIATELY execute: - _executeSlash(proposalId) +└─ 8. If appealWindow == 0, immediately execute; otherwise leave + the proposal deferred and appealable until executableAt │ │ (see "Slash Execution" below) ``` @@ -592,6 +593,7 @@ SLASHER_ROLE calls: SlashingManager.proposeSlashEvidence( │ failureReason: policy.failureReason │ } │ → NOT executed immediately +│ → Increment the same unresolved financial proposal count │ └─ 4. Emit SlashProposed(proposalId, e3Id, operator, reason) @@ -602,8 +604,6 @@ Operator (accused) calls: SlashingManager.fileAppeal(proposalId, evidence) ├─ require(msg.sender == proposal.operator) ├─ require(block.timestamp < proposal.executableAt) │ → Must appeal before window closes -├─ require(!proposal.proofVerified) -│ → Cannot appeal proof-based slashes ├─ require(!proposal.appealed) │ → Only one appeal per proposal ├─ proposal.appealed = true @@ -617,20 +617,26 @@ GOVERNANCE_ROLE resolves: SlashingManager.resolveAppeal( ├─ require(proposal.appealed && !proposal.resolved) ├─ proposal.resolved = true ├─ proposal.appealUpheld = upheld +├─ If upheld: decrement unresolved proposal count └─ Emit AppealResolved(proposalId, upheld, resolution) +If governance does not resolve a filed appeal by +`executableAt + APPEAL_RESOLUTION_GRACE`, anyone may call `expireAppeal`. +Expiry conclusively upholds the appeal and releases the collateral gate. + ─── AFTER APPEAL WINDOW ────────────────────────────────────── Anyone calls: SlashingManager.executeSlash(proposalId) │ -├─ require(!proposal.executed && !proposal.proofVerified) +├─ require(!proposal.executed) ├─ require(block.timestamp >= proposal.executableAt) ├─ If appealed: │ require(proposal.resolved) │ require(!proposal.appealUpheld) │ → If appeal was upheld, slash is cancelled │ -└─ _executeSlash(proposalId, policy) +├─ Decrement unresolved proposal count (reverts atomically on failure) +└─ _executeSlash(proposalId, lane) ``` ### Slash Execution (Both Lanes) @@ -1140,14 +1146,17 @@ Applied audit findings: **C-05, H-05, H-06, H-07, H-09, H-10, H-24, M-14, M-15, is emitted. The operator can call `fileAppeal` during that window; otherwise anyone may call `executeSlash` once it elapses. -### Lane B open-proposal gate (H-05) - -- `SlashingManager` tracks `_openLaneBCount[operator]`: `proposeSlashEvidence` increments, - `executeSlash` decrements before `_executeSlash`, and `resolveAppeal(upheld)` unwinds the counter. -- `hasOpenLaneBProposal(operator)` is exposed as a public view. -- `BondingRegistry.deregisterOperator()` reverts `OperatorUnderSlash()` while this gate is true, - preventing escape during an active Lane B proceeding. Lane A is intentionally not gated because it - is atomic (or short-windowed via H-06) and self-clears. +### Unified open-proposal collateral gate (H-05, AUD H-03) + +- `SlashingManager` tracks `_openProposalCount[operator]` for both Lane A and Lane B. Proposal + creation increments it; successful execution, an upheld appeal, or terminal appeal expiry + decrements it. `hasOpenSlashProposal` exposes the unified semantics. +- `BondingRegistry` reverts `OperatorUnderSlash()` on `removeTicketBalance`, `unbondLicense`, + `deregisterOperator`, and `claimExits` while the gate is raised. Both active collateral and assets + already queued for exit therefore remain slashable. +- A filed appeal cannot freeze collateral indefinitely: after the policy appeal window plus the + seven-day governance resolution grace, `expireAppeal` permissionlessly upholds it and clears its + gate. ### Pull-payment slashed funds (H-07, H-09) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index b9fa2e50e..c60a1e6c7 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -23,6 +23,11 @@ "name": "AppealPending", "type": "error" }, + { + "inputs": [], + "name": "AppealResolutionWindowActive", + "type": "error" + }, { "inputs": [], "name": "AppealUpheld", @@ -707,6 +712,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "expireAppeal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -916,6 +934,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "hasOpenSlashProposal", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1233,5 +1270,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-6d77a1fac0033f87a36ca50619587087010b9b66" + "buildInfoId": "solc-0_8_28-b6d88351d9521822c693319be886d5618e91683c" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 45b33c5c2..2c36a93fd 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -45,8 +45,8 @@ interface IBondingRegistry { error NoPendingDeregistration(); error OnlyRewardDistributor(); error ArrayLengthMismatch(); - /// @notice Thrown when an operator attempts to deregister while at least one Lane B - /// slash proposal against them is still pending execution. + /// @notice Thrown when an operator attempts to withdraw collateral while any + /// financial slash proposal against them remains unresolved. error OperatorUnderSlash(); /// @notice Thrown when {setExitDelay} input is outside the permitted range. diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index f96aacdda..aa2adf14e 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -156,6 +156,9 @@ interface ISlashingManager { /// @notice Thrown when attempting to execute a slash before the appeal window has closed error AppealWindowActive(); + /// @notice Thrown when an unresolved appeal has not reached its terminal expiry. + error AppealResolutionWindowActive(); + /// @notice Thrown when attempting to file a second appeal for the same proposal error AlreadyAppealed(); @@ -204,7 +207,7 @@ interface ISlashingManager { /// @notice Thrown when the attestation `deadline` has passed at the time of submission error SignatureExpired(); - /// @notice Thrown when an operator action is gated by an unresolved Lane B slash proposal + /// @notice Thrown when an operator action is gated by any unresolved slash proposal error OperatorUnderSlash(); /// @notice Thrown when a ban operation requires a distinct governance confirmer @@ -406,11 +409,11 @@ interface ISlashingManager { function isBanned(address node) external view returns (bool isBanned); /** - * @notice Returns true if the operator has at least one unresolved Lane B slash proposal - * @dev Used by BondingRegistry to block `deregisterOperator` while a slash is pending. + * @notice Returns true if the operator has at least one unresolved financial slash proposal. + * @dev Used by BondingRegistry to block collateral withdrawals and exit claims. * @param operator Operator address to check */ - function hasOpenLaneBProposal( + function hasOpenSlashProposal( address operator ) external view returns (bool); @@ -598,6 +601,13 @@ interface ISlashingManager { string calldata resolution ) external; + /** + * @notice Conclusively upholds an appeal if governance misses its resolution grace period. + * @dev Permissionless terminal path that releases the operator's collateral gate. + * @param proposalId ID of the unresolved appealed proposal + */ + function expireAppeal(uint256 proposalId) external; + // ====================== // Ban Management // ====================== diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index 5e870eef3..ef4400348 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -179,6 +179,19 @@ contract BondingRegistry is _; } + /// @dev Keeps active and already-queued collateral available while any + /// financial slash proposal against the operator is unresolved. + modifier noOpenSlashProposal(address operator) { + address sm = slashingManager; + if ( + sm != address(0) && + ISlashingManager(sm).hasOpenSlashProposal(operator) + ) { + revert OperatorUnderSlash(); + } + _; + } + //////////////////////////////////////////////////////////// // // // Initialization // @@ -351,21 +364,14 @@ contract BondingRegistry is } /// @inheritdoc IBondingRegistry - function deregisterOperator() external noExitInProgress(msg.sender) { + function deregisterOperator() + external + noExitInProgress(msg.sender) + noOpenSlashProposal(msg.sender) + { Operator storage op = operators[msg.sender]; require(op.registered, NotRegistered()); - // block deregistration while an unresolved Lane B slash proposal exists. - // An operator could otherwise drain ticket / license collateral during the appeal - // window and leave the slasher with nothing to slash. - address sm = slashingManager; - if (sm != address(0)) { - require( - !ISlashingManager(sm).hasOpenLaneBProposal(msg.sender), - OperatorUnderSlash() - ); - } - op.registered = false; op.exitRequested = true; op.exitUnlocksAt = uint64(block.timestamp) + exitDelay; @@ -429,7 +435,7 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function removeTicketBalance( uint256 amount - ) external noExitInProgress(msg.sender) { + ) external noExitInProgress(msg.sender) noOpenSlashProposal(msg.sender) { require(amount != 0, ZeroAmount()); require(operators[msg.sender].registered, NotRegistered()); require( @@ -460,7 +466,12 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function unbondLicense( uint256 amount - ) external nonReentrant noExitInProgress(msg.sender) { + ) + external + nonReentrant + noExitInProgress(msg.sender) + noOpenSlashProposal(msg.sender) + { require(amount != 0, ZeroAmount()); require( operators[msg.sender].licenseBond >= amount, @@ -488,7 +499,7 @@ contract BondingRegistry is function claimExits( uint256 maxTicketAmount, uint256 maxLicenseAmount - ) external nonReentrant { + ) external nonReentrant noOpenSlashProposal(msg.sender) { (uint256 ticketClaim, uint256 licenseClaim) = _exits.claimAssets( msg.sender, maxTicketAmount, diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index 4db5a7e57..00c40128f 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -45,6 +45,10 @@ contract SlashingManager is /// period during which governance can delay slash execution. uint64 public constant MAX_APPEAL_WINDOW = 30 days; + /// @notice Maximum time governance has after the appeal window closes to + /// resolve a filed appeal. Expiry is fail-safe in the operator's favour. + uint64 public constant APPEAL_RESOLUTION_GRACE = 7 days; + /// @notice Emitted when {bondingRegistry} is updated. event BondingRegistryUpdated( address indexed previous, @@ -100,12 +104,10 @@ contract SlashingManager is /// Lane B key is keccak256(abi.encode(e3Id, operator, keccak256(evidence))) — exact evidence bytes. mapping(bytes32 evidenceKey => bool consumed) public evidenceConsumed; - /// @notice Number of unexecuted Lane B proposals per operator. - /// @dev Incremented in `proposeSlashEvidence`, decremented in `executeSlash` and on - /// `resolveAppeal(upheld=true)`. `BondingRegistry.deregisterOperator` blocks while - /// this is > 0 so an operator cannot evade an inbound Lane B slash by exiting - /// during the appeal window. - mapping(address operator => uint256 openCount) internal _openLaneBCount; + /// @notice Number of unresolved financial proposals per operator, across both lanes. + /// @dev Incremented for every proposal and decremented only at successful execution or terminal + /// appeal resolution, so collateral cannot leave during a deferred slash. + mapping(address operator => uint256 openCount) internal _openProposalCount; /// @notice Pending two-step manual ban proposals. /// @dev `unbanNode` is single-step because it is strictly less dangerous than ban. @@ -236,10 +238,10 @@ contract SlashingManager is } /// @inheritdoc ISlashingManager - function hasOpenLaneBProposal( + function hasOpenSlashProposal( address operator ) external view returns (bool) { - return _openLaneBCount[operator] > 0; + return _openProposalCount[operator] > 0; } /// @inheritdoc ISlashingManager @@ -418,6 +420,8 @@ contract SlashingManager is p.affectsCommittee = policy.affectsCommittee; p.failureReason = policy.failureReason; + _openProposalCount[operator] += 1; + emit SlashProposed( proposalId, e3Id, @@ -433,6 +437,7 @@ contract SlashingManager is // Legacy atomic path: when no challenge window is configured, execute now. // Otherwise defer to `executeSlash` after `executableAt`. if (policy.appealWindow == 0) { + _openProposalCount[operator] -= 1; _executeSlash(proposalId, Lane.LaneA); } } @@ -482,9 +487,9 @@ contract SlashingManager is proposalId = totalProposals; totalProposals = proposalId + 1; - // Track unresolved Lane B proposals per operator so BondingRegistry blocks - // `deregisterOperator` until they execute, expire, or are upheld on appeal. - _openLaneBCount[operator] += 1; + // Track the unresolved proposal so collateral remains slashable until + // successful execution or terminal appeal resolution. + _openProposalCount[operator] += 1; uint256 executableAt = block.timestamp + policy.appealWindow; SlashProposal storage p = _proposals[proposalId]; @@ -531,12 +536,10 @@ contract SlashingManager is } Lane lane = p.proofVerified ? Lane.LaneA : Lane.LaneB; - if (lane == Lane.LaneB) { - // Decrement BEFORE `_executeSlash` so a reentrant deregister triggered by - // slash side-effects (e.g. `expelCommitteeMember`) is not gated on this - // proposal. Other open Lane B proposals keep the gate raised. - _openLaneBCount[p.operator] -= 1; - } + // Decrement BEFORE `_executeSlash` so a reentrant operator action triggered + // by slash side-effects is not gated on this proposal. Other open proposals + // keep the gate raised. A downstream revert restores the count atomically. + _openProposalCount[p.operator] -= 1; _executeSlash(proposalId, lane); } @@ -781,10 +784,9 @@ contract SlashingManager is p.resolved = true; p.appealUpheld = appealUpheld; - // an upheld appeal terminates the proposal — it can never `executeSlash`, - // so the open Lane B gate must drop here. Lane A proposals are not counted. - if (appealUpheld && !p.proofVerified) { - _openLaneBCount[p.operator] -= 1; + // An upheld appeal terminates the proposal, so its collateral gate ends. + if (appealUpheld) { + _openProposalCount[p.operator] -= 1; } emit AppealResolved( @@ -796,6 +798,31 @@ contract SlashingManager is ); } + /// @inheritdoc ISlashingManager + function expireAppeal(uint256 proposalId) external { + require(proposalId < totalProposals, InvalidProposal()); + SlashProposal storage p = _proposals[proposalId]; + require(p.appealed, InvalidProposal()); + require(!p.resolved, AlreadyResolved()); + require(!p.executed, AlreadyExecuted()); + require( + block.timestamp >= p.executableAt + APPEAL_RESOLUTION_GRACE, + AppealResolutionWindowActive() + ); + + p.resolved = true; + p.appealUpheld = true; + _openProposalCount[p.operator] -= 1; + + emit AppealResolved( + proposalId, + p.operator, + true, + msg.sender, + "governance resolution window expired" + ); + } + // ====================== // Ban Management // ====================== diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index c35b53a17..1f12bcaf2 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -267,12 +267,12 @@ describe("BondingRegistry", function () { const unbondAmount = ethers.parseEther("300"); const slashAmount = ethers.parseEther("800"); - await bondingRegistry.setSlashingManager(await notTheOwner.getAddress()); await licenseToken .connect(operator1) .approve(await bondingRegistry.getAddress(), bondAmount); await bondingRegistry.connect(operator1).bondLicense(bondAmount); await bondingRegistry.connect(operator1).unbondLicense(unbondAmount); + await bondingRegistry.setSlashingManager(await notTheOwner.getAddress()); await expect( bondingRegistry @@ -948,10 +948,7 @@ describe("BondingRegistry", function () { const { bondingRegistry } = await loadFixture(setup); await expect( bondingRegistry.setMinTicketBalance(0), - ).to.be.revertedWithCustomError( - bondingRegistry, - "InvalidConfiguration", - ); + ).to.be.revertedWithCustomError(bondingRegistry, "InvalidConfiguration"); }); describe("withdrawSlashedFunds()", function () { @@ -1365,7 +1362,9 @@ describe("BondingRegistry", function () { await expect(bondingRegistry.setTicketToken(ethers.ZeroAddress)) .to.be.revertedWithCustomError(bondingRegistry, "InvalidBondingAsset") .withArgs(ethers.ZeroAddress); - await expect(bondingRegistry.setTicketToken(await replacement.getAddress())) + await expect( + bondingRegistry.setTicketToken(await replacement.getAddress()), + ) .to.be.revertedWithCustomError( bondingRegistry, "OutstandingAssetLiabilities", diff --git a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts index 650dd7d60..73110e7a5 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts @@ -9,8 +9,8 @@ // `CommitteeExpulsion.spec.ts`: // // * SLASHER_ROLE admin is GOVERNANCE_ROLE (not DEFAULT_ADMIN_ROLE). -// * `BondingRegistry.deregisterOperator` blocked while a Lane B slash -// proposal is open; unblocks after execution or upheld appeal. +// * Bonding collateral withdrawals and exit claims are blocked while any +// financial slash proposal is open; they unblock after terminal resolution. // * Lane A `proposeSlash` with a non-zero `appealWindow` defers // execution (no auto-execute) and respects the challenge window. // * EIP-712 attestation signatures are bound to the SlashingManager's @@ -45,6 +45,7 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; const APPEAL_WINDOW = 7 * 24 * 60 * 60; + const APPEAL_RESOLUTION_GRACE = 7 * 24 * 60 * 60; // Constructor uses 2 days as the AccessControlDefaultAdminRules delay. const DEFAULT_ADMIN_DELAY = 2 * 24 * 60 * 60; @@ -186,9 +187,9 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function }); // -------------------------------------------------------------------------- - // deregisterOperator blocked while Lane B proposal open + // collateral exit blocked while financial proposal is open // -------------------------------------------------------------------------- - describe("deregisterOperator gated by open Lane B proposals", function () { + describe("operator exits gated by open slash proposals", function () { async function registerOperatorForExit( ctx: Awaited>, ) { @@ -202,12 +203,12 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function await bondingRegistry.connect(operator).registerOperator(); } - it("hasOpenLaneBProposal flips true after proposeSlashEvidence and false after executeSlash", async function () { + it("hasOpenSlashProposal flips true after proposeSlashEvidence and false after executeSlash", async function () { const ctx = await loadFixture(setup); const { slashingManager, slasher, operatorAddress } = ctx; await setupLaneBPolicy(slashingManager); - expect(await slashingManager.hasOpenLaneBProposal(operatorAddress)).to.be + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be .false; await slashingManager @@ -219,13 +220,13 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function ethers.toUtf8Bytes("ev"), ); - expect(await slashingManager.hasOpenLaneBProposal(operatorAddress)).to.be + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be .true; await time.increase(APPEAL_WINDOW + 1); await slashingManager.executeSlash(0); - expect(await slashingManager.hasOpenLaneBProposal(operatorAddress)).to.be + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be .false; }); @@ -312,7 +313,7 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function // Owner has GOVERNANCE_ROLE and can resolve appeals. await slashingManager.connect(owner).resolveAppeal(0, true, "upheld"); - expect(await slashingManager.hasOpenLaneBProposal(operatorAddress)).to.be + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be .false; await bondingRegistry.connect(operator).deregisterOperator(); expect(await bondingRegistry.isRegistered(operatorAddress)).to.be.false; @@ -340,6 +341,21 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function await mockCiphernodeRegistry.setThreshold(0, 2); } + async function registerOperatorWithQueuedExit( + ctx: Awaited>, + ) { + const { bondingRegistry, interfoldToken, operator } = ctx; + const licenseAmount = ethers.parseEther("1000"); + const queuedAmount = ethers.parseEther("100"); + await interfoldToken + .connect(operator) + .approve(await bondingRegistry.getAddress(), licenseAmount); + await bondingRegistry.connect(operator).bondLicense(licenseAmount); + await bondingRegistry.connect(operator).registerOperator(); + await bondingRegistry.connect(operator).unbondLicense(queuedAmount); + return queuedAmount; + } + it("proposeSlash with appealWindow>0 does NOT auto-execute and remains executable later", async function () { const ctx = await loadFixture(setup); const { slashingManager, proposer, operatorAddress, voter1, voter2 } = @@ -363,6 +379,8 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function const p = await slashingManager.getSlashProposal(0); expect(p.executed).to.be.false; expect(p.executableAt).to.be.gt(p.proposedAt); + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be + .true; // Cannot execute before window elapses await expect( @@ -374,6 +392,55 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function slashingManager, "SlashExecuted", ); + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be + .false; + }); + + it("AUD-H03: a deferred Lane A proposal freezes active and queued collateral", async function () { + const ctx = await loadFixture(setup); + const { + slashingManager, + bondingRegistry, + proposer, + operator, + operatorAddress, + voter1, + voter2, + } = ctx; + const queuedAmount = await registerOperatorWithQueuedExit(ctx); + await setupLaneAPolicy(slashingManager, APPEAL_WINDOW); + await setupCommittee(ctx); + + const proof = await signAndEncodeAttestation( + [voter1, voter2], + 0, + operatorAddress, + await slashingManager.getAddress(), + ); + await slashingManager + .connect(proposer) + .proposeSlash(0, operatorAddress, proof); + + await expect( + bondingRegistry.connect(operator).deregisterOperator(), + ).to.be.revertedWithCustomError(bondingRegistry, "OperatorUnderSlash"); + await expect( + bondingRegistry.connect(operator).removeTicketBalance(1), + ).to.be.revertedWithCustomError(bondingRegistry, "OperatorUnderSlash"); + await expect( + bondingRegistry.connect(operator).unbondLicense(1), + ).to.be.revertedWithCustomError(bondingRegistry, "OperatorUnderSlash"); + + await time.increase(APPEAL_WINDOW + 1); + await expect( + bondingRegistry.connect(operator).claimExits(0, queuedAmount), + ).to.be.revertedWithCustomError(bondingRegistry, "OperatorUnderSlash"); + + await slashingManager.executeSlash(0); + await bondingRegistry.connect(operator).claimExits(0, queuedAmount); + expect((await bondingRegistry.pendingExits(operatorAddress))[1]).to.equal( + 0n, + ); }); it("operator can fileAppeal on a Lane A deferred proposal", async function () { @@ -403,6 +470,53 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function slashingManager.connect(operator).fileAppeal(0, "not me"), ).to.emit(slashingManager, "AppealFiled"); }); + + it("expires an unresolved appeal after the governance grace period", async function () { + const ctx = await loadFixture(setup); + const { + slashingManager, + proposer, + operator, + operatorAddress, + voter1, + voter2, + } = ctx; + await setupLaneAPolicy(slashingManager, APPEAL_WINDOW); + await setupCommittee(ctx); + + const proof = await signAndEncodeAttestation( + [voter1, voter2], + 0, + operatorAddress, + await slashingManager.getAddress(), + ); + await slashingManager + .connect(proposer) + .proposeSlash(0, operatorAddress, proof); + await slashingManager.connect(operator).fileAppeal(0, "not me"); + + await expect( + slashingManager.expireAppeal(0), + ).to.be.revertedWithCustomError( + slashingManager, + "AppealResolutionWindowActive", + ); + + const proposal = await slashingManager.getSlashProposal(0); + await time.increaseTo( + proposal.executableAt + BigInt(APPEAL_RESOLUTION_GRACE), + ); + await expect(slashingManager.expireAppeal(0)).to.emit( + slashingManager, + "AppealResolved", + ); + + expect(await slashingManager.hasOpenSlashProposal(operatorAddress)).to.be + .false; + await expect( + slashingManager.executeSlash(0), + ).to.be.revertedWithCustomError(slashingManager, "AppealUpheld"); + }); }); // -------------------------------------------------------------------------- From 1f5f57a97709125c4ad2886c08ed1551b1778c98 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:44:52 +0500 Subject: [PATCH 10/52] fix(contracts): snapshot settlement policy per E3 [M-07] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 2 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 10 +- .../contracts/E3RefundManager.sol | 112 +++++++++++++++--- .../contracts/Interfold.sol | 1 + .../contracts/interfaces/IE3RefundManager.sol | 23 ++++ .../test/E3Lifecycle/E3Integration.spec.ts | 85 +++++++++++++ 7 files changed, 214 insertions(+), 20 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 36cc23925..eaa14bbac 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -213,3 +213,4 @@ _Found during source-code cross-referencing of these trace documents._ | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy. Upgraded deployments also consult the live registry count, and the minimum ticket requirement can never be zero. | | 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | +| 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 6b04b6f02..73a308f5c 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -56,6 +56,8 @@ Requester calls: Interfold.request({ │ ├─ E3 CREATION: │ ├─ e3Id = nexte3Id++ +│ ├─ e3RefundManager.snapshotE3Policy(e3Id) +│ │ → freezes refund/slash allocation, treasury, and policy version │ ├─ seed = uint256(keccak256(block.prevrandao, e3Id)) │ │ → On chains without `prevrandao`, the value is still deterministic │ │ per-block; downstream sortition relies on the per-E3 snapshot of diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 441bd4e2c..58907ffeb 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -848,7 +848,9 @@ distributeSlashedFundsOnSuccess(e3Id, activeNodes, paymentToken): ├─ _pendingSlashedFunds[e3Id] = 0 ├─ _slashedSuccessToken[e3Id] = paymentToken // snapshot for later claims │ -├─ Split using WorkValueAllocation.successSlashedNodeBps (default 5000): +├─ Load the immutable E3PolicySnapshot captured by Interfold.request +│ (allocation, treasury, policy version) +├─ Split using snapshot.allocation.successSlashedNodeBps (default 5000): │ toNodes = escrowed * successSlashedNodeBps / 10000 │ toTreasury = escrowed - toNodes │ @@ -859,8 +861,8 @@ distributeSlashedFundsOnSuccess(e3Id, activeNodes, paymentToken): │ Emit SlashedFundsCredited(e3Id, node, paymentToken, perNode) │ ├─ Credit treasury for protocol share: -│ _pendingTreasury[treasury][paymentToken] += toTreasury -│ Emit TreasurySlashedCredited(treasury, paymentToken, toTreasury) +│ _pendingTreasury[snapshot.treasury][paymentToken] += toTreasury +│ Emit TreasurySlashedCredited(snapshot.treasury, paymentToken, toTreasury) │ └─ Emit SlashedFundsDistributedOnSuccess(e3Id, toNodes, toTreasury) @@ -877,6 +879,8 @@ Design rationale: a slashed peer) and the protocol treasury. Both shares use a per-recipient pull ledger so a single failing recipient (e.g. blacklisted ERC-20 address) cannot brick the success-path or strand other claimants' funds. + Governance changes to the live allocation or treasury increment policyVersion + and apply only to later E3 requests; existing snapshots never migrate implicitly. ``` ### Slashed Funds Ordering: Escrow → Terminal State Resolution diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index 17564e681..1c6b200eb 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -24,6 +24,9 @@ import { IBondingRegistry } from "./interfaces/IBondingRegistry.sol"; * @dev Implements fault-attribution based refund system * */ +// Per-E3 token, claim, routing, and immutable policy ledgers are intentionally +// isolated rather than packed into an opaque monolithic mapping. +// solhint-disable-next-line max-states-count contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { using SafeERC20 for IERC20; //////////////////////////////////////////////////////////// @@ -68,6 +71,11 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { /// @notice Tracks honest-node reward claims independently from requester claims mapping(uint256 e3Id => mapping(address claimer => bool hasClaimed)) internal _honestNodeClaimed; + /// @notice Per-E3 settlement economics frozen at request time. + mapping(uint256 e3Id => E3PolicySnapshot snapshot) + internal _e3PolicySnapshots; + /// @notice Monotonic version for allocation or treasury changes. + uint64 public policyVersion; //////////////////////////////////////////////////////////// // // // Modifiers // @@ -115,6 +123,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { protocolBps: 500, successSlashedNodeBps: 5000 }); + policyVersion = 1; if (_owner != owner()) _transferOwnership(_owner); } @@ -155,11 +164,14 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(originalPayment > 0, "No payment"); require(address(paymentToken) != address(0), "Invalid fee token"); - // Calculate work value based on stage + E3PolicySnapshot memory policy = _policyFor(e3Id); + + // Calculate work value based on stage and the request-time policy. IInterfold.E3Stage failedAt = _getFailedAtStage(e3Id); - (uint16 workCompletedBps, uint16 workRemainingBps) = calculateWorkValue( - failedAt - ); + ( + uint16 workCompletedBps, + uint16 workRemainingBps + ) = _calculateWorkValue(failedAt, policy.allocation); // Calculate base distribution uint256 honestNodeAmount = (originalPayment * workCompletedBps) / @@ -201,9 +213,9 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { // Credit protocol fee via pull-payment so a malicious/reverting/blacklisted // treasury cannot brick failed-E3 processing. if (protocolAmount > 0) { - _pendingTreasury[treasury][paymentToken] += protocolAmount; + _pendingTreasury[policy.treasury][paymentToken] += protocolAmount; emit TreasurySlashedCredited( - treasury, + policy.treasury, paymentToken, protocolAmount ); @@ -280,8 +292,13 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function calculateWorkValue( IInterfold.E3Stage stage ) public view returns (uint16 workCompletedBps, uint16 workRemainingBps) { - WorkValueAllocation memory alloc = _workAllocation; + return _calculateWorkValue(stage, _workAllocation); + } + function _calculateWorkValue( + IInterfold.E3Stage stage, + WorkValueAllocation memory alloc + ) internal pure returns (uint16 workCompletedBps, uint16 workRemainingBps) { if ( stage == IInterfold.E3Stage.Requested || stage == IInterfold.E3Stage.None @@ -384,8 +401,13 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { ? dist.honestNodeAmount - paidIncludingThis : 0; if (dust > 0) { - _pendingTreasury[treasury][dist.feeToken] += dust; - emit TreasurySlashedCredited(treasury, dist.feeToken, dust); + address policyTreasury = _treasuryFor(e3Id); + _pendingTreasury[policyTreasury][dist.feeToken] += dust; + emit TreasurySlashedCredited( + policyTreasury, + dist.feeToken, + dust + ); } } _totalHonestNodePaid[e3Id] += amount; @@ -465,14 +487,19 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(address(paymentToken) != address(0), "Invalid fee token"); _slashedSuccessToken[e3Id] = paymentToken; - uint256 toNodes = (escrowed * _workAllocation.successSlashedNodeBps) / + E3PolicySnapshot memory policy = _policyFor(e3Id); + uint256 toNodes = (escrowed * policy.allocation.successSlashedNodeBps) / BPS_BASE; uint256 toProtocol = escrowed - toNodes; // Credit treasury share — pull only. if (toProtocol > 0) { - _pendingTreasury[treasury][paymentToken] += toProtocol; - emit TreasurySlashedCredited(treasury, paymentToken, toProtocol); + _pendingTreasury[policy.treasury][paymentToken] += toProtocol; + emit TreasurySlashedCredited( + policy.treasury, + paymentToken, + toProtocol + ); } if (toNodes > 0 && honestNodes.length > 0) { @@ -498,8 +525,12 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { } } else if (toNodes > 0) { // No honest nodes — funnel the node share to treasury for governance triage. - _pendingTreasury[treasury][paymentToken] += toNodes; - emit TreasurySlashedCredited(treasury, paymentToken, toNodes); + _pendingTreasury[policy.treasury][paymentToken] += toNodes; + emit TreasurySlashedCredited( + policy.treasury, + paymentToken, + toNodes + ); } emit SlashedFundsDistributedOnSuccess(e3Id, toNodes, toProtocol); @@ -529,8 +560,13 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { if (dist.honestNodeCount == 0 && toHonestNodes > 0) { IERC20 token = dist.feeToken; if (address(token) != address(0)) { - _pendingTreasury[treasury][token] += toHonestNodes; - emit TreasurySlashedCredited(treasury, token, toHonestNodes); + address policyTreasury = _treasuryFor(e3Id); + _pendingTreasury[policyTreasury][token] += toHonestNodes; + emit TreasurySlashedCredited( + policyTreasury, + token, + toHonestNodes + ); } toHonestNodes = 0; } @@ -587,6 +623,46 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { return _workAllocation; } + /// @inheritdoc IE3RefundManager + function getE3PolicySnapshot( + uint256 e3Id + ) external view returns (E3PolicySnapshot memory snapshot) { + return _e3PolicySnapshots[e3Id]; + } + + /// @inheritdoc IE3RefundManager + function snapshotE3Policy(uint256 e3Id) external onlyInterfold { + E3PolicySnapshot storage snapshot = _e3PolicySnapshots[e3Id]; + require(!snapshot.initialized, "Policy already snapshotted"); + if (policyVersion == 0) policyVersion = 1; + snapshot.allocation = _workAllocation; + snapshot.treasury = treasury; + snapshot.version = policyVersion; + snapshot.initialized = true; + emit E3PolicySnapshotted( + e3Id, + policyVersion, + treasury, + _workAllocation + ); + } + + function _policyFor( + uint256 e3Id + ) internal view returns (E3PolicySnapshot memory policy) { + policy = _e3PolicySnapshots[e3Id]; + if (!policy.initialized) { + policy.allocation = _workAllocation; + policy.treasury = treasury; + policy.version = policyVersion; + } + } + + function _treasuryFor(uint256 e3Id) internal view returns (address) { + E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; + return policy.treasury; + } + //////////////////////////////////////////////////////////// // // // Admin Functions // @@ -610,6 +686,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(allocation.successSlashedNodeBps <= BPS_BASE, "Invalid BPS"); _workAllocation = allocation; + policyVersion = policyVersion == 0 ? 1 : policyVersion + 1; emit WorkAllocationUpdated(allocation); } @@ -629,6 +706,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(_treasury != address(0), "Invalid treasury"); address oldValue = treasury; treasury = _treasury; + policyVersion = policyVersion == 0 ? 1 : policyVersion + 1; emit TreasuryUpdated(oldValue, _treasury); } @@ -673,7 +751,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { _pendingSlashedFunds[e3Id] = 0; require(address(paymentToken) != address(0), "Invalid fee token"); - paymentToken.safeTransfer(treasury, amount); + paymentToken.safeTransfer(_treasuryFor(e3Id), amount); emit OrphanedSlashedFundsWithdrawn(e3Id, amount); } diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index eebb42e0e..35ee260bb 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -293,6 +293,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { e3Id = nexte3Id; nexte3Id++; + e3RefundManager.snapshotE3Policy(e3Id); // Seed uses block.prevrandao combined with e3Id as additional entropy. // While prevrandao is not cryptographically unpredictable (validator-controlled), // the combination with the unique, incrementing e3Id mitigates manipulation. diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index 18a8a43ee..51a6bc689 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -26,6 +26,13 @@ interface IE3RefundManager { uint16 protocolBps; uint16 successSlashedNodeBps; } + /// @notice Immutable settlement policy selected when an E3 is requested. + struct E3PolicySnapshot { + WorkValueAllocation allocation; + address treasury; + uint64 version; + bool initialized; + } /// @notice Refund distribution for a failed E3 struct RefundDistribution { uint256 requesterAmount; // Amount for requester @@ -102,6 +109,13 @@ interface IE3RefundManager { ); /// @notice Emitted when work allocation is updated event WorkAllocationUpdated(WorkValueAllocation allocation); + /// @notice Emitted when an E3 freezes its settlement policy. + event E3PolicySnapshotted( + uint256 indexed e3Id, + uint64 indexed version, + address indexed treasury, + WorkValueAllocation allocation + ); /// @notice Emitted when orphaned slashed funds are withdrawn to treasury event OrphanedSlashedFundsWithdrawn(uint256 indexed e3Id, uint256 amount); /// @notice Emitted when the Interfold address is set @@ -147,6 +161,10 @@ interface IE3RefundManager { IERC20 paymentToken ) external; + /// @notice Freeze the current allocation and treasury for an E3. + /// @dev Only Interfold may call this, exactly once, during request creation. + function snapshotE3Policy(uint256 e3Id) external; + /// @notice Requester claims their refund /// @param e3Id The failed E3 ID /// @return amount The amount claimed @@ -216,6 +234,11 @@ interface IE3RefundManager { view returns (WorkValueAllocation memory allocation); + /// @notice Return the settlement policy frozen for an E3. + function getE3PolicySnapshot( + uint256 e3Id + ) external view returns (E3PolicySnapshot memory snapshot); + //////////////////////////////////////////////////////////// // // // Success-Path Slashed-Funds Pull Payments // diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index e8a96e022..7e4294cb2 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -220,6 +220,77 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const storedRequester = await interfold.getRequester(0); expect(storedRequester).to.equal(await requester.getAddress()); }); + + it("AUD-M07: snapshots failure allocation and treasury at request time", async function () { + const { + interfold, + e3RefundManager, + registry, + usdcToken, + makeRequest, + owner, + treasury, + computeProvider, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + await makeRequest(); + + const originalTreasury = await treasury.getAddress(); + const rotatedTreasury = await computeProvider.getAddress(); + const snapshot = await e3RefundManager.getE3PolicySnapshot(0); + expect(snapshot.initialized).to.equal(true); + expect(snapshot.version).to.equal(1); + expect(snapshot.treasury).to.equal(originalTreasury); + expect(snapshot.allocation.committeeFormationBps).to.equal(1000); + + await e3RefundManager.connect(owner).setWorkAllocation({ + committeeFormationBps: 2000, + dkgBps: 2000, + decryptionBps: 5500, + protocolBps: 500, + successSlashedNodeBps: 1000, + }); + await e3RefundManager.connect(owner).setTreasury(rotatedTreasury); + + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(SORTITION_SUBMISSION_WINDOW + 1); + await registry.finalizeCommittee(0); + const deadlines = await interfold.getDeadlines(0); + await time.increaseTo(deadlines.dkgDeadline + 1n); + await interfold.markE3Failed(0); + await interfold.processE3Failure(0); + + const distribution = await e3RefundManager.getRefundDistribution(0); + expect(distribution.honestNodeAmount).to.equal( + (distribution.originalPayment * 1000n) / 10000n, + ); + expect( + await e3RefundManager.pendingTreasuryClaim( + originalTreasury, + await usdcToken.getAddress(), + ), + ).to.equal(distribution.protocolAmount); + expect( + await e3RefundManager.pendingTreasuryClaim( + rotatedTreasury, + await usdcToken.getAddress(), + ), + ).to.equal(0); + + const unchanged = await e3RefundManager.getE3PolicySnapshot(0); + expect(unchanged.version).to.equal(1); + expect(unchanged.treasury).to.equal(originalTreasury); + expect(unchanged.allocation.committeeFormationBps).to.equal(1000); + }); }); describe("Committee Formed Integration", function () { @@ -1256,6 +1327,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { operator2, operator3, treasury, + computeProvider, + owner, setupOperator, } = await loadFixture(setup); @@ -1265,6 +1338,18 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { // 1. Request E3, form committee, publish key await makeRequest(undefined, 0); + // Governance changes after request must not alter this E3's success + // allocation or redirect its treasury share. + await e3RefundManager.connect(owner).setWorkAllocation({ + committeeFormationBps: 1000, + dkgBps: 3000, + decryptionBps: 5500, + protocolBps: 500, + successSlashedNodeBps: 1000, + }); + await e3RefundManager + .connect(owner) + .setTreasury(await computeProvider.getAddress()); await registry.connect(operator1).submitTicket(0, 1); await registry.connect(operator2).submitTicket(0, 1); await registry.connect(operator3).submitTicket(0, 1); From 19fd9e7529f7c85398bbbd6dedc86ba6f35c95bd Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 20:23:32 +0500 Subject: [PATCH 11/52] fix(contracts): snapshot lifecycle dependencies per E3 [M-04] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 26 ++- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 15 ++ .../ISlashingManager.json | 47 ++++ .../contracts/E3RefundManager.sol | 156 ++++++++----- .../contracts/Interfold.sol | 208 ++++++++---------- .../contracts/interfaces/IE3RefundManager.sol | 2 + .../contracts/interfaces/ISlashingManager.sol | 17 ++ .../contracts/lib/InterfoldPricing.sol | 90 ++++++++ .../registry/CiphernodeRegistryOwnable.sol | 77 +++++-- .../contracts/slashing/SlashingManager.sol | 126 ++++++++--- .../test/E3Lifecycle/E3Integration.spec.ts | 155 ++++++++++++- .../test/Interfold.spec.ts | 2 +- .../test/Pricing/Pricing.spec.ts | 2 +- .../test/Slashing/SlashingLanes.spec.ts | 6 + .../test/Slashing/SlashingManager.spec.ts | 9 + .../test/fixtures/system.ts | 2 +- 17 files changed, 704 insertions(+), 237 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index eaa14bbac..832bf0a48 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -214,3 +214,4 @@ _Found during source-code cross-referencing of these trace documents._ | 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | +| 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 73a308f5c..79add82ba 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -56,8 +56,12 @@ Requester calls: Interfold.request({ │ ├─ E3 CREATION: │ ├─ e3Id = nexte3Id++ -│ ├─ e3RefundManager.snapshotE3Policy(e3Id) -│ │ → freezes refund/slash allocation, treasury, and policy version +│ ├─ Snapshot Interfold dependencies for this E3: +│ │ registry, refund manager, and slashing manager +│ │ → later global rotations apply only to new requests +│ ├─ snapshottedRefundManager.snapshotE3Policy(e3Id) +│ │ → freezes refund/slash allocation, treasury, policy version, +│ │ and the request-time Interfold authorized to settle this E3 │ ├─ seed = uint256(keccak256(block.prevrandao, e3Id)) │ │ → On chains without `prevrandao`, the value is still deterministic │ │ per-block; downstream sortition relies on the per-E3 snapshot of @@ -89,10 +93,14 @@ Requester calls: Interfold.request({ │ │ │ │ │ │ │ │ │ requestCommittee(e3Id, seed, threshold) { │ │ │ │ │ 1. require(!committees[e3Id].initialized) │ -│ │ │ │ 2. require(threshold[1] <= │ +│ │ │ │ 2. Snapshot request-time Interfold, bonding, │ +│ │ │ │ and slashing manager for this committee │ +│ │ │ │ → ask SlashingManager to snapshot its │ +│ │ │ │ bonding, registry, Interfold, refund routes │ +│ │ │ │ 3. require(threshold[1] <= │ │ │ │ │ bondingRegistry.numActiveOperators()) │ │ │ │ │ → Enough active nodes must exist │ -│ │ │ │ 3. committees[e3Id] = Committee { │ +│ │ │ │ 4. committees[e3Id] = Committee { │ │ │ │ │ initialized: true, │ │ │ │ │ seed: seed, │ │ │ │ │ requestBlock: block.timestamp, // H-26 │ @@ -100,10 +108,10 @@ Requester calls: Interfold.request({ │ │ │ │ block.timestamp + sortitionWindow, │ │ │ │ │ threshold: threshold │ │ │ │ │ } │ -│ │ │ │ 4. roots[e3Id] = ciphernodes._root() │ +│ │ │ │ 5. roots[e3Id] = ciphernodes._root() │ │ │ │ │ → SNAPSHOT the IMT root at this moment │ │ │ │ │ → Only nodes in tree at request time eligible │ -│ │ │ │ 5. Emit CommitteeRequested(e3Id, seed, threshold,│ +│ │ │ │ 6. Emit CommitteeRequested(e3Id, seed, threshold,│ │ │ │ │ requestBlock, committeeDeadline) │ │ │ │ │ } │ │ │ │ └─────────────────────────────────────────────────────┘ @@ -403,6 +411,12 @@ If any deadline is missed → anyone can call markE3Failed() 6. **IMT root snapshot**: The Merkle tree root is captured at request time. Nodes that join/leave after the request don't affect this E3's committee. +7. **Dependency graph snapshot**: Each E3 drains through its request-time registry, bonding, + slashing, refund, and Interfold relationships. Admin rotation changes defaults for later E3s but + cannot redirect or brick committee callbacks, proof checks, failure settlement, rewards, or + slashed-fund routing for an in-flight E3. A request atomically records the complete graph before + committee formation begins. + --- ## Cluster 7 audit additions (post-fix semantics) diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 58907ffeb..2deef6513 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -883,6 +883,21 @@ Design rationale: and apply only to later E3 requests; existing snapshots never migrate implicitly. ``` +### In-flight dependency rotation (AUD M-04) + +Every slash and settlement route resolves the dependency graph frozen when the E3 was requested: + +- `Interfold` uses the per-E3 registry, refund manager, and slashing manager for callbacks, + committee reads, verification, rewards, failure settlement, and slash escrow. +- `CiphernodeRegistryOwnable` uses the per-E3 Interfold, bonding registry, and slashing manager for + ticket eligibility, committee callbacks, and expulsion authorization. +- `SlashingManager` uses the per-E3 bonding registry, ciphernode registry, Interfold, and refund + manager for attestations, penalties, expulsion, failure callbacks, and fund routing. +- `E3RefundManager` accepts lifecycle calls from the Interfold recorded in the E3 policy snapshot. + +Admin setters update the live defaults for future requests only. Each E3 must have a complete +request-time snapshot; lifecycle calls fail closed if that invariant is not satisfied. + ### Slashed Funds Ordering: Escrow → Terminal State Resolution ``` diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index c60a1e6c7..a2ccbcce4 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -743,6 +743,40 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + } + ], + "name": "getE3Dependencies", + "outputs": [ + { + "internalType": "address", + "name": "bonding", + "type": "address" + }, + { + "internalType": "address", + "name": "registry", + "type": "address" + }, + { + "internalType": "address", + "name": "interfoldContract", + "type": "address" + }, + { + "internalType": "address", + "name": "refundManager", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1209,6 +1243,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + } + ], + "name": "snapshotE3Dependencies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "totalProposals", diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index 1c6b200eb..f1aa4a8ec 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -87,6 +87,15 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { _; } + /// @dev Authorizes the Interfold contract frozen for an existing E3. + modifier onlyE3Interfold(uint256 e3Id) { + E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; + if (!policy.initialized || msg.sender != policy.interfold) { + revert Unauthorized(); + } + _; + } + //////////////////////////////////////////////////////////// // // // Initialization // @@ -159,28 +168,18 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { uint256 originalPayment, address[] calldata honestNodes, IERC20 paymentToken - ) external onlyInterfold { + ) external onlyE3Interfold(e3Id) { require(!_distributions[e3Id].calculated, "Already calculated"); require(originalPayment > 0, "No payment"); require(address(paymentToken) != address(0), "Invalid fee token"); - E3PolicySnapshot memory policy = _policyFor(e3Id); - // Calculate work value based on stage and the request-time policy. IInterfold.E3Stage failedAt = _getFailedAtStage(e3Id); ( - uint16 workCompletedBps, - uint16 workRemainingBps - ) = _calculateWorkValue(failedAt, policy.allocation); - - // Calculate base distribution - uint256 honestNodeAmount = (originalPayment * workCompletedBps) / - BPS_BASE; - uint256 requesterAmount = (originalPayment * workRemainingBps) / - BPS_BASE; - uint256 protocolAmount = originalPayment - - honestNodeAmount - - requesterAmount; + uint256 honestNodeAmount, + uint256 requesterAmount, + uint256 protocolAmount + ) = _baseDistribution(e3Id, originalPayment, failedAt); // No honest nodes: fold work share into the requester refund (mirrors // {Interfold._distributeRewards} success-path). Avoids per-failure dust @@ -213,9 +212,10 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { // Credit protocol fee via pull-payment so a malicious/reverting/blacklisted // treasury cannot brick failed-E3 processing. if (protocolAmount > 0) { - _pendingTreasury[policy.treasury][paymentToken] += protocolAmount; + address policyTreasury = _treasuryFor(e3Id); + _pendingTreasury[policyTreasury][paymentToken] += protocolAmount; emit TreasurySlashedCredited( - policy.treasury, + policyTreasury, paymentToken, protocolAmount ); @@ -247,6 +247,28 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { ); } + function _baseDistribution( + uint256 e3Id, + uint256 originalPayment, + IInterfold.E3Stage failedAt + ) + internal + view + returns ( + uint256 honestNodeAmount, + uint256 requesterAmount, + uint256 protocolAmount + ) + { + ( + uint16 workCompletedBps, + uint16 workRemainingBps + ) = _calculateWorkValue(failedAt, _allocationFor(e3Id)); + honestNodeAmount = (originalPayment * workCompletedBps) / BPS_BASE; + requesterAmount = (originalPayment * workRemainingBps) / BPS_BASE; + protocolAmount = originalPayment - honestNodeAmount - requesterAmount; + } + /// @notice Get the stage at which E3 failed (for work calculation) function _getFailedAtStage( uint256 e3Id @@ -427,7 +449,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function escrowSlashedFunds( uint256 e3Id, uint256 amount - ) external onlyInterfold { + ) external onlyE3Interfold(e3Id) { require(amount > 0, "Zero amount"); RefundDistribution storage dist = _distributions[e3Id]; @@ -479,7 +501,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { uint256 e3Id, address[] calldata honestNodes, IERC20 paymentToken - ) external onlyInterfold { + ) external onlyE3Interfold(e3Id) { uint256 escrowed = _pendingSlashedFunds[e3Id]; if (escrowed == 0) return; _pendingSlashedFunds[e3Id] = 0; @@ -487,55 +509,58 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(address(paymentToken) != address(0), "Invalid fee token"); _slashedSuccessToken[e3Id] = paymentToken; - E3PolicySnapshot memory policy = _policyFor(e3Id); - uint256 toNodes = (escrowed * policy.allocation.successSlashedNodeBps) / + address policyTreasury = _treasuryFor(e3Id); + uint256 toNodes = (escrowed * _successSlashedNodeBpsFor(e3Id)) / BPS_BASE; uint256 toProtocol = escrowed - toNodes; // Credit treasury share — pull only. if (toProtocol > 0) { - _pendingTreasury[policy.treasury][paymentToken] += toProtocol; + _pendingTreasury[policyTreasury][paymentToken] += toProtocol; emit TreasurySlashedCredited( - policy.treasury, + policyTreasury, paymentToken, toProtocol ); } if (toNodes > 0 && honestNodes.length > 0) { - uint256 perNode = toNodes / honestNodes.length; - uint256 distributed = 0; - for (uint256 i = 0; i < honestNodes.length; i++) { - uint256 nodeAmount = perNode; - if (i == honestNodes.length - 1) { - nodeAmount = toNodes - distributed; - } - if (nodeAmount > 0) { - // credit per-node so one blacklisted/reverting recipient - // does not brick payouts for the rest of the committee. - _pendingSlashedSuccess[e3Id][honestNodes[i]] += nodeAmount; - emit SlashedFundsCredited( - e3Id, - honestNodes[i], - paymentToken, - nodeAmount - ); - } - distributed += nodeAmount; - } + _creditSuccessSlashed(e3Id, honestNodes, paymentToken, toNodes); } else if (toNodes > 0) { // No honest nodes — funnel the node share to treasury for governance triage. - _pendingTreasury[policy.treasury][paymentToken] += toNodes; - emit TreasurySlashedCredited( - policy.treasury, - paymentToken, - toNodes - ); + _pendingTreasury[policyTreasury][paymentToken] += toNodes; + emit TreasurySlashedCredited(policyTreasury, paymentToken, toNodes); } emit SlashedFundsDistributedOnSuccess(e3Id, toNodes, toProtocol); } + function _creditSuccessSlashed( + uint256 e3Id, + address[] calldata honestNodes, + IERC20 paymentToken, + uint256 toNodes + ) internal { + uint256 perNode = toNodes / honestNodes.length; + uint256 distributed = 0; + for (uint256 i = 0; i < honestNodes.length; i++) { + uint256 nodeAmount = perNode; + if (i == honestNodes.length - 1) { + nodeAmount = toNodes - distributed; + } + if (nodeAmount > 0) { + _pendingSlashedSuccess[e3Id][honestNodes[i]] += nodeAmount; + emit SlashedFundsCredited( + e3Id, + honestNodes[i], + paymentToken, + nodeAmount + ); + } + distributed += nodeAmount; + } + } + /// @notice Apply slashed funds to an E3's refund distribution /// @dev This function is ONLY called on the failure path.Priority: make requester whole first, /// then distribute remainder to honest nodes. @@ -637,30 +662,43 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { if (policyVersion == 0) policyVersion = 1; snapshot.allocation = _workAllocation; snapshot.treasury = treasury; + snapshot.interfold = msg.sender; snapshot.version = policyVersion; snapshot.initialized = true; emit E3PolicySnapshotted( e3Id, policyVersion, treasury, + msg.sender, _workAllocation ); } - function _policyFor( + function _treasuryFor(uint256 e3Id) internal view returns (address) { + E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; + return policy.treasury; + } + + function _interfoldFor(uint256 e3Id) internal view returns (IInterfold) { + E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; + return IInterfold(policy.interfold); + } + + function _allocationFor( uint256 e3Id - ) internal view returns (E3PolicySnapshot memory policy) { - policy = _e3PolicySnapshots[e3Id]; - if (!policy.initialized) { - policy.allocation = _workAllocation; - policy.treasury = treasury; - policy.version = policyVersion; - } + ) internal view returns (WorkValueAllocation memory allocation) { + E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; + return policy.initialized ? policy.allocation : _workAllocation; } - function _treasuryFor(uint256 e3Id) internal view returns (address) { + function _successSlashedNodeBpsFor( + uint256 e3Id + ) internal view returns (uint16) { E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; - return policy.treasury; + return + policy.initialized + ? policy.allocation.successSlashedNodeBps + : _workAllocation.successSlashedNodeBps; } //////////////////////////////////////////////////////////// diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 35ee260bb..2879ad9f0 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -159,6 +159,12 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { mapping(address treasury => mapping(IERC20 token => uint256 amount)) internal _pendingTreasury; + struct E3Dependencies { + ICiphernodeRegistry registry; + IE3RefundManager refundManager; + ISlashingManager slashManager; + } + /// @notice Grace window (seconds) after a stage deadline during which only /// the original requester, owner, or an active committee member /// can call {markE3Failed}. After the grace window, anyone @@ -167,6 +173,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { /// restriction is undesired. uint256 public markFailedGracePeriod; + /// @notice Lifecycle contracts frozen when each E3 is requested. + mapping(uint256 e3Id => E3Dependencies dependencies) + internal _e3Dependencies; + /// @notice Emitted when the {markFailedGracePeriod} value is updated. event MarkFailedGracePeriodSet(uint256 gracePeriod); @@ -176,31 +186,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { // // //////////////////////////////////////////////////////////// - /// @notice Restricts function to CiphernodeRegistry contract only - modifier onlyCiphernodeRegistry() { - require( - msg.sender == address(ciphernodeRegistry), - OnlyCiphernodeRegistry() - ); - _; - } - - /// @notice Restricts function to CiphernodeRegistry or SlashingManager - modifier onlyCiphernodeRegistryOrSlashingManager() { - require( - msg.sender == address(ciphernodeRegistry) || - msg.sender == address(slashingManager), - OnlyCiphernodeRegistryOrSlashingManager() - ); - _; - } - - /// @notice Restricts function to SlashingManager contract only - modifier onlySlashingManager() { - require(msg.sender == address(slashingManager), OnlySlashingManager()); - _; - } - //////////////////////////////////////////////////////////// // // // Initialization // @@ -293,7 +278,11 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { e3Id = nexte3Id; nexte3Id++; - e3RefundManager.snapshotE3Policy(e3Id); + E3Dependencies storage dependencies = _e3Dependencies[e3Id]; + dependencies.registry = ciphernodeRegistry; + dependencies.refundManager = e3RefundManager; + dependencies.slashManager = slashingManager; + dependencies.refundManager.snapshotE3Policy(e3Id); // Seed uses block.prevrandao combined with e3Id as additional entropy. // While prevrandao is not cryptographically unpredictable (validator-controlled), // the combination with the unique, incrementing e3Id mitigates manipulation. @@ -363,7 +352,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { feeToken.safeTransferFrom(msg.sender, address(this), e3Fee); require( - ciphernodeRegistry.requestCommittee(e3Id, seed, threshold), + dependencies.registry.requestCommittee(e3Id, seed, threshold), CommitteeSelectionFailed() ); @@ -441,21 +430,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { if (e3.proofAggregationEnabled) { require(proof.length > 0, ProofRequired()); - // Reaching `CiphertextReady` implies the committee was published, so - // `getCommitteeHash` is guaranteed non-zero here; the registry still - // reverts with `CommitteeNotPublished` if that invariant ever breaks. - bytes32 committeeHash = ciphernodeRegistry.getCommitteeHash(e3Id); - // Wrapper reverts on any failure with a typed error (no `bool false`). - e3.decryptionVerifier.verify( - e3Id, - ciphernodeRegistry.rootAt(e3Id), - ciphernodeRegistry.getCommitteeNodes(e3Id), - e3.ciphertextOutput, - e3.committeePublicKey, - keccak256(plaintextOutput), - committeeHash, - proof - ); + _verifyPlaintext(e3Id, keccak256(plaintextOutput), proof); success = true; } else { success = true; @@ -467,6 +442,23 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { emit E3StageChanged(e3Id, E3Stage.CiphertextReady, E3Stage.Complete); } + function _verifyPlaintext( + uint256 e3Id, + bytes32 plaintextHash, + bytes calldata proof + ) internal view { + E3 storage e3 = e3s[e3Id]; + InterfoldPricing.verifyPlaintext( + address(e3.decryptionVerifier), + address(_registryFor(e3Id)), + e3Id, + e3.ciphertextOutput, + e3.committeePublicKey, + plaintextHash, + proof + ); + } + //////////////////////////////////////////////////////////// // // // Internal Functions // @@ -479,8 +471,9 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { /// transfer — single recipient, no other party harmed. /// @param e3Id The ID of the E3 for which to distribute rewards. function _distributeRewards(uint256 e3Id) internal { - (address[] memory activeNodes, ) = ciphernodeRegistry + (address[] memory activeNodes, ) = _registryFor(e3Id) .getActiveCommitteeNodes(e3Id); + IE3RefundManager refundManager = _refundManagerFor(e3Id); uint256 activeLength = activeNodes.length; uint256 totalAmount = e3Payments[e3Id]; @@ -490,7 +483,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { IERC20 paymentToken = _e3FeeTokens[e3Id]; if (totalAmount == 0) { - e3RefundManager.distributeSlashedFundsOnSuccess( + refundManager.distributeSlashedFundsOnSuccess( e3Id, activeNodes, paymentToken @@ -506,7 +499,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { if (requester != address(0)) { paymentToken.safeTransfer(requester, totalAmount); } - e3RefundManager.distributeSlashedFundsOnSuccess( + refundManager.distributeSlashedFundsOnSuccess( e3Id, activeNodes, paymentToken @@ -548,7 +541,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { emit RewardsDistributed(e3Id, activeNodes, amounts); - e3RefundManager.distributeSlashedFundsOnSuccess( + refundManager.distributeSlashedFundsOnSuccess( e3Id, activeNodes, paymentToken @@ -575,30 +568,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { /// @dev Uses active committee view from the registry (which excludes expelled/slashed members). /// @param e3Id The ID of the E3. /// @return honestNodes An array of addresses of honest committee nodes. - function _getHonestNodes( - uint256 e3Id - ) private view returns (address[] memory) { - FailureReason reason = _e3FailureReasons[e3Id]; - - // Early failures have no committee - if ( - reason == FailureReason.CommitteeFormationTimeout || - reason == FailureReason.InsufficientCommitteeMembers - ) { - return new address[](0); - } - - // Use active committee nodes (already filtered by expulsion) - try ciphernodeRegistry.getActiveCommitteeNodes(e3Id) returns ( - address[] memory nodes, - uint256[] memory - ) { - return nodes; - } catch { - return new address[](0); // Committee not published (DKG failed) - } - } - //////////////////////////////////////////////////////////// // // // Set Functions // @@ -649,11 +618,9 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { ); feeToken = _feeToken; // Auto allow-list the active fee token so `request()` keeps working - // after a rotation. Owner can still explicitly toggle later. - if (!_feeTokenAllowed[_feeToken]) { - _feeTokenAllowed[_feeToken] = true; - emit FeeTokenAllowed(_feeToken, true); - } + // after a rotation. FeeTokenSet provides the rotation receipt; explicit + // allow-list changes emit FeeTokenAllowed via setFeeTokenAllowed. + _feeTokenAllowed[_feeToken] = true; emit FeeTokenSet(address(_feeToken)); } @@ -745,11 +712,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { require(encodedParams.length > 0, "Empty params"); bytes memory previous = paramSetRegistry[paramSet]; paramSetRegistry[paramSet] = encodedParams; - if (previous.length == 0) { - emit ParamSetRegistered(paramSet, encodedParams); - } else { - emit ParamSetUpdated(paramSet, previous, encodedParams); - } + InterfoldPricing.emitParamSetChange(paramSet, previous, encodedParams); } /// @notice Sets the E3 Refund Manager contract address @@ -757,10 +720,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { function setE3RefundManager( IE3RefundManager _e3RefundManager ) public onlyOwner { - require( - address(_e3RefundManager) != address(0), - "Invalid E3RefundManager address" - ); + require(address(_e3RefundManager) != address(0)); e3RefundManager = _e3RefundManager; emit E3RefundManagerSet(address(_e3RefundManager)); } @@ -770,10 +730,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { function setSlashingManager( ISlashingManager _slashingManager ) external onlyOwner { - require( - address(_slashingManager) != address(0), - "Invalid SlashingManager address" - ); + require(address(_slashingManager) != address(0)); slashingManager = _slashingManager; emit SlashingManagerSet(address(_slashingManager)); } @@ -790,34 +747,37 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { require(payment > 0, NoPaymentToRefund(e3Id)); e3Payments[e3Id] = 0; // Prevent double processing - address[] memory honestNodes = _getHonestNodes(e3Id); + address[] memory honestNodes = InterfoldPricing.honestNodes( + address(_registryFor(e3Id)), + e3Id, + uint8(_e3FailureReasons[e3Id]) + ); IERC20 paymentToken = _e3FeeTokens[e3Id]; - paymentToken.safeTransfer(address(e3RefundManager), payment); - e3RefundManager.calculateRefund( - e3Id, - payment, - honestNodes, - paymentToken - ); + IE3RefundManager refundManager = _refundManagerFor(e3Id); + paymentToken.safeTransfer(address(refundManager), payment); + refundManager.calculateRefund(e3Id, payment, honestNodes, paymentToken); emit E3FailureProcessed(e3Id, payment, honestNodes.length); } /// @inheritdoc IInterfold - function escrowSlashedFunds( - uint256 e3Id, - uint256 amount - ) external onlySlashingManager { - e3RefundManager.escrowSlashedFunds(e3Id, amount); + function escrowSlashedFunds(uint256 e3Id, uint256 amount) external { + InterfoldPricing.validateSlashCaller( + msg.sender, + address(_slashingManagerFor(e3Id)) + ); + _refundManagerFor(e3Id).escrowSlashedFunds(e3Id, amount); emit SlashedFundsEscrowed(e3Id, amount); } /// @inheritdoc IInterfold - function onCommitteeFinalized( - uint256 e3Id - ) external onlyCiphernodeRegistry { + function onCommitteeFinalized(uint256 e3Id) external { + InterfoldPricing.validateRegistryCaller( + msg.sender, + address(_registryFor(e3Id)) + ); // Update E3 lifecycle stage - committee finalized, DKG starting E3Stage current = _e3Stages[e3Id]; if (current != E3Stage.Requested) { @@ -840,7 +800,11 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { function onCommitteePublished( uint256 e3Id, bytes32 committeePublicKey - ) external onlyCiphernodeRegistry { + ) external { + InterfoldPricing.validateRegistryCaller( + msg.sender, + address(_registryFor(e3Id)) + ); E3 storage e3 = e3s[e3Id]; E3Stage current = _e3Stages[e3Id]; if (current != E3Stage.CommitteeFinalized) { @@ -859,10 +823,12 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { } /// @inheritdoc IInterfold - function onE3Failed( - uint256 e3Id, - uint8 reason - ) external onlyCiphernodeRegistryOrSlashingManager { + function onE3Failed(uint256 e3Id, uint8 reason) external { + InterfoldPricing.validateRegistryOrSlashCaller( + msg.sender, + address(_registryFor(e3Id)), + address(_slashingManagerFor(e3Id)) + ); require( reason > 0 && reason <= uint8(FailureReason._MAX_FAILURE_REASON), "Invalid failure reason" @@ -905,7 +871,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { block.timestamp < graceEnds && msg.sender != _e3Requesters[e3Id] && msg.sender != owner() && - !ciphernodeRegistry.isCommitteeMember(e3Id, msg.sender) + !_registryFor(e3Id).isCommitteeMember(e3Id, msg.sender) ) { revert MarkE3FailedInGracePeriod(e3Id, graceEnds); } @@ -967,7 +933,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { ) private view returns (uint256 deadline, FailureReason reason) { if (stage == E3Stage.Requested) return ( - ciphernodeRegistry.getCommitteeDeadline(e3Id), + _registryFor(e3Id).getCommitteeDeadline(e3Id), FailureReason.CommitteeFormationTimeout ); E3Deadlines memory d = _e3Deadlines[e3Id]; @@ -1194,6 +1160,24 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { return _pendingTreasury[treasury][token]; } + function _registryFor( + uint256 e3Id + ) private view returns (ICiphernodeRegistry) { + return _e3Dependencies[e3Id].registry; + } + + function _refundManagerFor( + uint256 e3Id + ) private view returns (IE3RefundManager) { + return _e3Dependencies[e3Id].refundManager; + } + + function _slashingManagerFor( + uint256 e3Id + ) private view returns (ISlashingManager) { + return _e3Dependencies[e3Id].slashManager; + } + //////////////////////////////////////////////////////////// // // // ERC-165 Interface Detection // @@ -1217,5 +1201,5 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { /// array's length accordingly to preserve storage layout compatibility /// across upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[50] private __gap; + uint256[49] private __gap; } diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index 51a6bc689..a34902b62 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -30,6 +30,7 @@ interface IE3RefundManager { struct E3PolicySnapshot { WorkValueAllocation allocation; address treasury; + address interfold; uint64 version; bool initialized; } @@ -114,6 +115,7 @@ interface IE3RefundManager { uint256 indexed e3Id, uint64 indexed version, address indexed treasury, + address interfold, WorkValueAllocation allocation ); /// @notice Emitted when orphaned slashed funds are withdrawn to treasury diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index aa2adf14e..6a95fddc5 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -417,6 +417,23 @@ interface ISlashingManager { address operator ) external view returns (bool); + /// @notice Freeze all contracts used to validate and execute slashes for an E3. + /// @dev Called exactly once by the configured Interfold during E3 creation. + function snapshotE3Dependencies(uint256 e3Id) external; + + /// @notice Return the contracts frozen for an E3's slashing lifecycle. + function getE3Dependencies( + uint256 e3Id + ) + external + view + returns ( + address bonding, + address registry, + address interfoldContract, + address refundManager + ); + /** * @notice Returns the bonding registry contract used for executing slashes * @return registry The IBondingRegistry contract instance diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index f502d610b..5fe3bbbcd 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -6,6 +6,8 @@ pragma solidity >=0.8.27; import { IInterfold } from "../interfaces/IInterfold.sol"; +import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; +import { IDecryptionVerifier } from "../interfaces/IDecryptionVerifier.sol"; /** * @title InterfoldPricing @@ -27,6 +29,94 @@ library InterfoldPricing { uint16 internal constant MAX_MARGIN_BPS = 5_000; uint32 internal constant MAX_COMMITTEE_SIZE = 256; + event ParamSetRegistered(uint8 paramSet, bytes encodedParams); + event ParamSetUpdated( + uint8 paramSet, + bytes previousParams, + bytes newParams + ); + + function emitParamSetChange( + uint8 paramSet, + bytes calldata previous, + bytes calldata current + ) external { + if (previous.length == 0) { + emit ParamSetRegistered(paramSet, current); + } else { + emit ParamSetUpdated(paramSet, previous, current); + } + } + + function validateRegistryCaller( + address caller, + address registry + ) external pure { + require(caller == registry, IInterfold.OnlyCiphernodeRegistry()); + } + + function validateSlashCaller( + address caller, + address slashManager + ) external pure { + require(caller == slashManager, IInterfold.OnlySlashingManager()); + } + + function validateRegistryOrSlashCaller( + address caller, + address registry, + address slashManager + ) external pure { + require( + caller == registry || caller == slashManager, + IInterfold.OnlyCiphernodeRegistryOrSlashingManager() + ); + } + + function verifyPlaintext( + address verifierAddress, + address registryAddress, + uint256 e3Id, + bytes32 ciphertextHash, + bytes32 committeePublicKey, + bytes32 plaintextHash, + bytes calldata proof + ) external view { + IDecryptionVerifier(verifierAddress).verify( + e3Id, + ICiphernodeRegistry(registryAddress).rootAt(e3Id), + ICiphernodeRegistry(registryAddress).getCommitteeNodes(e3Id), + ciphertextHash, + committeePublicKey, + plaintextHash, + ICiphernodeRegistry(registryAddress).getCommitteeHash(e3Id), + proof + ); + } + + function honestNodes( + address registryAddress, + uint256 e3Id, + uint8 reason + ) external view returns (address[] memory) { + ICiphernodeRegistry registry = ICiphernodeRegistry(registryAddress); + if ( + reason == + uint8(IInterfold.FailureReason.CommitteeFormationTimeout) || + reason == + uint8(IInterfold.FailureReason.InsufficientCommitteeMembers) + ) return new address[](0); + + try registry.getActiveCommitteeNodes(e3Id) returns ( + address[] memory nodes, + uint256[] memory + ) { + return nodes; + } catch { + return new address[](0); + } + } + /// @notice Writes the default {IInterfold.PricingConfig} directly to /// the linked {Interfold} storage starting at slot 24. Called /// via DELEGATECALL from {Interfold.initialize}, so SSTORE diff --git a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index 74b1b4732..c4a246fe9 100644 --- a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -168,6 +168,16 @@ contract CiphernodeRegistryOwnable is mapping(uint256 e3Id => bytes32[] skAggCommits) internal dkgSkAggCommits; mapping(uint256 e3Id => bytes32[] esmAggCommits) internal dkgEsmAggCommits; + struct CommitteeDependencies { + IInterfold interfoldContract; + IBondingRegistry bonding; + ISlashingManager slashManager; + } + + /// @notice External contracts frozen for each committee lifecycle. + mapping(uint256 e3Id => CommitteeDependencies dependencies) + internal _committeeDependencies; + //////////////////////////////////////////////////////////// // // // Modifiers // @@ -195,12 +205,6 @@ contract CiphernodeRegistryOwnable is _; } - /// @dev Restricts function access to only the slashing manager - modifier onlySlashingManager() { - require(msg.sender == address(slashingManager), NotSlashingManager()); - _; - } - //////////////////////////////////////////////////////////// // // // Initialization // @@ -258,6 +262,14 @@ contract CiphernodeRegistryOwnable is CommitteeAlreadyRequested() ); + CommitteeDependencies storage dependencies = _committeeDependencies[ + e3Id + ]; + dependencies.interfoldContract = IInterfold(msg.sender); + dependencies.bonding = bondingRegistry; + dependencies.slashManager = slashingManager; + dependencies.slashManager.snapshotE3Dependencies(e3Id); + uint256 activeCount = bondingRegistry.numActiveOperators(); require( threshold[1] <= activeCount, @@ -307,7 +319,7 @@ contract CiphernodeRegistryOwnable is c.publicKey = pkCommitment; publicKeyHashes[e3Id] = pkCommitment; - E3 memory e3 = interfold.getE3(e3Id); + E3 memory e3 = _interfoldFor(e3Id).getE3(e3Id); if (e3.proofAggregationEnabled) { // Bind to the on-chain committee (c.topNodes), not caller-supplied // nodes, so a wrong `nodes` input cannot pre-commit the prover to @@ -324,7 +336,7 @@ contract CiphernodeRegistryOwnable is ); } - interfold.onCommitteePublished(e3Id, pkCommitment); + _interfoldFor(e3Id).onCommitteePublished(e3Id, pkCommitment); emit CommitteePublished( e3Id, @@ -545,7 +557,10 @@ contract CiphernodeRegistryOwnable is CommitteeDeadlineReached() ); require(!c.submitted[msg.sender], NodeAlreadySubmitted()); - require(isCiphernodeEligible(msg.sender), NodeNotEligible()); + require( + isEnabled(msg.sender) && _bondingFor(e3Id).isActive(msg.sender), + NodeNotEligible() + ); // Validate node eligibility and ticket number _validateNodeEligibility(msg.sender, ticketNumber, e3Id); @@ -596,7 +611,7 @@ contract CiphernodeRegistryOwnable is c.topNodes.length, c.threshold[1] ); - interfold.onE3Failed( + _interfoldFor(e3Id).onE3Failed( e3Id, uint8(IInterfold.FailureReason.InsufficientCommitteeMembers) ); @@ -614,7 +629,7 @@ contract CiphernodeRegistryOwnable is scores[i] = c.scoreOf[c.topNodes[i]]; } - interfold.onCommitteeFinalized(e3Id); + _interfoldFor(e3Id).onCommitteeFinalized(e3Id); emit SortitionCommitteeFinalized(e3Id, c.topNodes, scores); return true; } @@ -867,11 +882,11 @@ contract CiphernodeRegistryOwnable is uint256 e3Id, address node, bytes32 reason - ) - external - onlySlashingManager - returns (uint256 activeCount, uint32 thresholdM) - { + ) external returns (uint256 activeCount, uint32 thresholdM) { + require( + msg.sender == address(_slashingManagerFor(e3Id)), + NotSlashingManager() + ); Committee storage c = committees[e3Id]; require( c.stage == ICiphernodeRegistry.CommitteeStage.Finalized, @@ -1018,10 +1033,8 @@ contract CiphernodeRegistryOwnable is uint256 e3Id ) internal view { require(ticketNumber > 0, InvalidTicketNumber()); - require( - address(bondingRegistry) != address(0), - BondingRegistryNotSet() - ); + IBondingRegistry e3Bonding = _bondingFor(e3Id); + require(address(e3Bonding) != address(0), BondingRegistryNotSet()); Committee storage c = committees[e3Id]; @@ -1032,11 +1045,11 @@ contract CiphernodeRegistryOwnable is // and selection weight below derive purely from the historical // ticket balance at `c.requestBlock - 1`, so churn between request // time and the ticket submission window cannot inflate weights. - uint256 ticketBalance = bondingRegistry.getTicketBalanceAtBlock( + uint256 ticketBalance = e3Bonding.getTicketBalanceAtBlock( node, c.requestBlock - 1 ); - uint256 ticketPrice = bondingRegistry.ticketPrice(); + uint256 ticketPrice = e3Bonding.ticketPrice(); require(ticketPrice > 0, InvalidTicketNumber()); uint256 availableTickets = ticketBalance / ticketPrice; @@ -1045,6 +1058,24 @@ contract CiphernodeRegistryOwnable is require(ticketNumber <= availableTickets, InvalidTicketNumber()); } + function _bondingFor( + uint256 e3Id + ) internal view returns (IBondingRegistry e3Bonding) { + return _committeeDependencies[e3Id].bonding; + } + + function _interfoldFor( + uint256 e3Id + ) internal view returns (IInterfold e3Interfold) { + return _committeeDependencies[e3Id].interfoldContract; + } + + function _slashingManagerFor( + uint256 e3Id + ) internal view returns (ISlashingManager e3SlashingManager) { + return _committeeDependencies[e3Id].slashManager; + } + /// @notice Sort `topNodes` by ascending address before committee finalization. /// @dev Canonical address-ascending order so `CommitteeHashLib.hash(topNodes)` /// matches what off-chain aggregators independently compute over the same @@ -1129,5 +1160,5 @@ contract CiphernodeRegistryOwnable is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[50] private __gap; + uint256[49] private __gap; } diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index 00c40128f..c062c2981 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -86,6 +86,18 @@ contract SlashingManager is /// @notice Reference to the E3 Refund Manager for routing slashed funds IE3RefundManager public e3RefundManager; + struct E3Dependencies { + IBondingRegistry bonding; + ICiphernodeRegistry registry; + IInterfold interfoldContract; + IE3RefundManager refundManager; + bool initialized; + } + + /// @notice Contracts frozen for each E3's complete slashing lifecycle. + mapping(uint256 e3Id => E3Dependencies dependencies) + internal _e3Dependencies; + /// @notice Mapping from slash reason hash to its configured policy mapping(bytes32 reason => SlashPolicy policy) public slashPolicies; @@ -249,6 +261,44 @@ contract SlashingManager is return _domainSeparatorV4(); } + /// @inheritdoc ISlashingManager + function getE3Dependencies( + uint256 e3Id + ) + external + view + returns ( + address bonding, + address registry, + address interfoldContract, + address refundManager + ) + { + E3Dependencies memory dependencies = _dependenciesFor(e3Id); + return ( + address(dependencies.bonding), + address(dependencies.registry), + address(dependencies.interfoldContract), + address(dependencies.refundManager) + ); + } + + /// @inheritdoc ISlashingManager + function snapshotE3Dependencies(uint256 e3Id) external { + require( + msg.sender == address(interfold) || + msg.sender == address(ciphernodeRegistry), + Unauthorized() + ); + E3Dependencies storage dependencies = _e3Dependencies[e3Id]; + require(!dependencies.initialized, InvalidProposal()); + dependencies.bonding = bondingRegistry; + dependencies.registry = ciphernodeRegistry; + dependencies.interfoldContract = interfold; + dependencies.refundManager = e3RefundManager; + dependencies.initialized = true; + } + // ====================== // Admin Functions // ====================== @@ -386,7 +436,7 @@ contract SlashingManager is require(policy.requiresProof, InvalidPolicy()); require( - ciphernodeRegistry.isCommitteeMember(e3Id, operator), + _dependenciesFor(e3Id).registry.isCommitteeMember(e3Id, operator), OperatorNotInCommittee() ); @@ -449,9 +499,8 @@ contract SlashingManager is uint256 e3Id, uint256 partyId ) internal view returns (address operator) { - (uint256[] memory partyIds, , ) = ciphernodeRegistry.getDkgAnchors( - e3Id - ); + ICiphernodeRegistry registry = _dependenciesFor(e3Id).registry; + (uint256[] memory partyIds, , ) = registry.getDkgAnchors(e3Id); bool found = false; for (uint256 i = 0; i < partyIds.length; i++) { if (partyIds[i] == partyId) { @@ -460,7 +509,7 @@ contract SlashingManager is } } require(found, PartyIdNotInDkgAnchors()); - return ciphernodeRegistry.canonicalCommitteeNodeAt(e3Id, partyId); + return registry.canonicalCommitteeNodeAt(e3Id, partyId); } /// @inheritdoc ISlashingManager @@ -584,8 +633,7 @@ contract SlashingManager is // Get committee threshold — need at least M agreeing votes { - (, uint32 thresholdM, , ) = ciphernodeRegistry - .getCommitteeViability(e3Id); + uint32 thresholdM = _committeeThresholdM(e3Id); require(thresholdM > 0, InvalidProposal()); require(numVotes >= thresholdM, InsufficientAttestations()); } @@ -614,7 +662,7 @@ contract SlashingManager is // Verify voter is an active committee member for this E3 require( - ciphernodeRegistry.isCommitteeMemberActive(e3Id, voter), + _isCommitteeMemberActive(e3Id, voter), VoterNotInCommittee() ); @@ -641,6 +689,25 @@ contract SlashingManager is } } + function _committeeThresholdM( + uint256 e3Id + ) internal view returns (uint32 thresholdM) { + (, thresholdM, , ) = _dependenciesFor(e3Id) + .registry + .getCommitteeViability(e3Id); + } + + function _isCommitteeMemberActive( + uint256 e3Id, + address voter + ) internal view returns (bool) { + return + _dependenciesFor(e3Id).registry.isCommitteeMemberActive( + e3Id, + voter + ); + } + /// @dev Executes a slash: applies financial penalties, optional ban, and committee expulsion. /// Lane B: if the operator deregistered or exited during the appeal window, penalties /// gracefully become 0 (BondingRegistry uses min(requested, available)). Accepted tradeoff. @@ -653,12 +720,13 @@ contract SlashingManager is /// the primary defence; this ordering provides defence-in-depth. function _executeSlash(uint256 proposalId, Lane lane) internal { SlashProposal storage p = _proposals[proposalId]; + E3Dependencies memory dependencies = _dependenciesFor(p.e3Id); uint256 actualTicketSlashed = 0; // Execute financial penalties if (p.ticketAmount > 0) { - actualTicketSlashed = bondingRegistry.slashTicketBalance( + actualTicketSlashed = dependencies.bonding.slashTicketBalance( p.operator, p.ticketAmount, p.reason @@ -666,7 +734,7 @@ contract SlashingManager is } if (p.licenseAmount > 0) { - bondingRegistry.slashLicenseBond( + dependencies.bonding.slashLicenseBond( p.operator, p.licenseAmount, p.reason @@ -689,14 +757,20 @@ contract SlashingManager is // Committee expulsion for E3-scoped slashes (uses snapshotted behavioral flags) // expelCommitteeMember returns (activeCount, thresholdM) — one call instead of three if (p.affectsCommittee) { - (uint256 activeCount, uint32 thresholdM) = ciphernodeRegistry + (uint256 activeCount, uint32 thresholdM) = dependencies + .registry .expelCommitteeMember(p.e3Id, p.operator, p.reason); // If active count drops below M, fail the E3 if (activeCount < thresholdM && p.failureReason > 0) { // NOTE: catch block must not be empty (solc optimizer bug, see below) // solhint-disable-next-line no-empty-blocks - try interfold.onE3Failed(p.e3Id, p.failureReason) { + try + dependencies.interfoldContract.onE3Failed( + p.e3Id, + p.failureReason + ) + { // Side effects occur in the external call } catch { // E3 already failed or other error — slash still proceeds @@ -705,17 +779,11 @@ contract SlashingManager is } } - // Escrow slashed ticket funds for deferred distribution. - // Self-call for try/catch atomicity — on failure, funds stay in BondingRegistry. + // Escrow slashed ticket funds for deferred distribution. The self-call + // keeps payout and accounting atomic. A routing failure reverts the + // whole slash so the proposal remains retryable. if (actualTicketSlashed > 0) { - // NOTE: catch must not be empty — solc >=0.8.28 optimizer bug. - // solhint-disable no-empty-blocks - try - this.escrowSlashedFundsToRefund(p.e3Id, actualTicketSlashed) - {} catch { - // solhint-enable no-empty-blocks - emit RoutingFailed(p.e3Id, actualTicketSlashed); - } + this.escrowSlashedFundsToRefund(p.e3Id, actualTicketSlashed); } emit SlashExecuted( @@ -735,13 +803,21 @@ contract SlashingManager is /// External with self-only access for try/catch atomicity. function escrowSlashedFundsToRefund(uint256 e3Id, uint256 amount) external { require(msg.sender == address(this), Unauthorized()); - address refundManager = address(e3RefundManager); + E3Dependencies memory dependencies = _dependenciesFor(e3Id); + address refundManager = address(dependencies.refundManager); require(refundManager != address(0), ZeroAddress()); - bondingRegistry.redirectSlashedTicketFunds(refundManager, amount); - interfold.escrowSlashedFunds(e3Id, amount); + dependencies.bonding.redirectSlashedTicketFunds(refundManager, amount); + dependencies.interfoldContract.escrowSlashedFunds(e3Id, amount); emit SlashedFundsEscrowedToRefund(e3Id, amount); } + function _dependenciesFor( + uint256 e3Id + ) internal view returns (E3Dependencies memory dependencies) { + dependencies = _e3Dependencies[e3Id]; + require(dependencies.initialized, InvalidProposal()); + } + // ====================== // Appeal Functions // ====================== diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 7e4294cb2..51d6efbfc 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -291,6 +291,124 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect(unchanged.treasury).to.equal(originalTreasury); expect(unchanged.allocation.committeeFormationBps).to.equal(1000); }); + + it("AUD-M04: drains an in-flight E3 through request-time dependencies", async function () { + const { + interfold, + e3RefundManager, + bondingRegistry, + registry, + slashingManager, + usdcToken, + makeRequest, + owner, + requester, + treasury, + computeProvider, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + const rotationTestWindow = 100; + await registry + .connect(owner) + .setSortitionSubmissionWindow(rotationTestWindow); + await makeRequest(); + + const interfoldAddress = await interfold.getAddress(); + const refundManagerAddress = await e3RefundManager.getAddress(); + const registryAddress = await registry.getAddress(); + const bondingAddress = await bondingRegistry.getAddress(); + + const policy = await e3RefundManager.getE3PolicySnapshot(0); + expect(policy.interfold).to.equal(interfoldAddress); + const dependencies = await slashingManager.getE3Dependencies(0); + expect(dependencies.bonding).to.equal(bondingAddress); + expect(dependencies.registry).to.equal(registryAddress); + expect(dependencies.interfoldContract).to.equal(interfoldAddress); + expect(dependencies.refundManager).to.equal(refundManagerAddress); + + const rotatedRegistry = await requester.getAddress(); + const rotatedBonding = await computeProvider.getAddress(); + const rotatedRefundManager = await treasury.getAddress(); + const rotatedSlashingManager = await owner.getAddress(); + + // Rotate every global dependency after the E3 has been requested. EOAs are + // deliberate canaries: any accidental read through a live global pointer + // will fail instead of silently succeeding through another deployment. + await interfold.connect(owner).setCiphernodeRegistry(rotatedRegistry); + await interfold.connect(owner).setBondingRegistry(rotatedBonding); + await interfold.connect(owner).setE3RefundManager(rotatedRefundManager); + await interfold.connect(owner).setSlashingManager(rotatedSlashingManager); + await registry.connect(owner).setInterfold(rotatedRegistry); + await registry.connect(owner).setBondingRegistry(rotatedBonding); + await registry.connect(owner).setSlashingManager(rotatedSlashingManager); + await e3RefundManager.connect(owner).setInterfold(rotatedRegistry); + await slashingManager.connect(owner).setBondingRegistry(rotatedBonding); + await slashingManager + .connect(owner) + .setCiphernodeRegistry(rotatedRegistry); + await slashingManager.connect(owner).setInterfold(rotatedRegistry); + await slashingManager + .connect(owner) + .setE3RefundManager(rotatedRefundManager); + + // Committee selection still reads eligibility and ticket checkpoints from + // the original bonding registry, then calls back into the original Interfold. + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(rotationTestWindow + 1); + await registry.finalizeCommittee(0); + + const publicKey = "0x1234567890abcdef1234567890abcdef"; + await registry.publishCommittee( + 0, + publicKey, + ethers.keccak256(publicKey), + "0x", + "0x", + ); + + // Slashing also stays bound to the original registry, bonding, Interfold, + // and refund manager even though all four global pointers were rotated. + const proof = await signAndEncodeAttestation( + [operator2, operator3], + 0, + await operator1.getAddress(), + await slashingManager.getAddress(), + ); + const refundBalanceBefore = + await usdcToken.balanceOf(refundManagerAddress); + await slashingManager.proposeSlash( + 0, + await operator1.getAddress(), + proof, + ); + expect(await usdcToken.balanceOf(refundManagerAddress)).to.be.gt( + refundBalanceBefore, + ); + + const e3 = await interfold.getE3(0); + await time.increaseTo(Number(e3.inputWindow[1])); + await interfold.publishCiphertextOutput( + 0, + "0x" + "ab".repeat(100), + "0x1337", + ); + await interfold.publishPlaintextOutput( + 0, + "0x" + "cd".repeat(100), + "0x1337", + ); + + expect(await interfold.getE3Stage(0)).to.equal(5); // Complete + }); }); describe("Committee Formed Integration", function () { @@ -783,7 +901,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { interfold, e3RefundManager, makeRequest, - owner, operator1, operator2, operator3, @@ -808,11 +925,22 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const requesterGap = distributionBefore.originalPayment - distributionBefore.requesterAmount; - // Escrow slashed funds via the interfold proxy (swap interfold address for test) + // Call from the Interfold frozen in the E3 policy snapshot. Rotating the + // manager's live pointer must not grant settlement authority for old E3s. const originalInterfold = await e3RefundManager.interfold(); - await e3RefundManager.setInterfold(await owner.getAddress()); - await e3RefundManager.connect(owner).escrowSlashedFunds(0, slashedAmount); - await e3RefundManager.setInterfold(originalInterfold); + await ethers.provider.send("hardhat_impersonateAccount", [ + originalInterfold, + ]); + await ethers.provider.send("hardhat_setBalance", [ + originalInterfold, + "0x1000000000000000000", + ]); + await e3RefundManager + .connect(await ethers.getSigner(originalInterfold)) + .escrowSlashedFunds(0, slashedAmount); + await ethers.provider.send("hardhat_stopImpersonatingAccount", [ + originalInterfold, + ]); const distributionAfter = await e3RefundManager.getRefundDistribution(0); @@ -835,7 +963,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { interfold, e3RefundManager, makeRequest, - owner, operator1, operator2, operator3, @@ -856,9 +983,19 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { // Escrow slashed funds BEFORE processE3Failure — should be queued const originalInterfold = await e3RefundManager.interfold(); - await e3RefundManager.setInterfold(await owner.getAddress()); - await e3RefundManager.connect(owner).escrowSlashedFunds(0, slashedAmount); - await e3RefundManager.setInterfold(originalInterfold); + await ethers.provider.send("hardhat_impersonateAccount", [ + originalInterfold, + ]); + await ethers.provider.send("hardhat_setBalance", [ + originalInterfold, + "0x1000000000000000000", + ]); + await e3RefundManager + .connect(await ethers.getSigner(originalInterfold)) + .escrowSlashedFunds(0, slashedAmount); + await ethers.provider.send("hardhat_stopImpersonatingAccount", [ + originalInterfold, + ]); // Distribution should not exist yet const distBefore = await e3RefundManager.getRefundDistribution(0); diff --git a/packages/interfold-contracts/test/Interfold.spec.ts b/packages/interfold-contracts/test/Interfold.spec.ts index 255eb9338..5d7ba9eec 100644 --- a/packages/interfold-contracts/test/Interfold.spec.ts +++ b/packages/interfold-contracts/test/Interfold.spec.ts @@ -44,7 +44,7 @@ describe("Interfold", function () { const inputWindowDuration = 300; const setup = async () => { - const sys = await deployInterfoldSystem({ wireSlashingManager: false }); + const sys = await deployInterfoldSystem({ wireSlashingManager: true }); const dkgFoldAttestationVerifier = await ethers.deployContract( "DkgFoldAttestationVerifier", ); diff --git a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts index 089f7d0a2..94361aa3b 100644 --- a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts +++ b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts @@ -64,7 +64,7 @@ describe("E3 Pricing", function () { const treasurySigner = signers[5]; const sys = await deployInterfoldSystem({ treasury: treasurySigner, - wireSlashingManager: false, + wireSlashingManager: true, }); await mine(1); return { diff --git a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts index 73110e7a5..89dfaee9f 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts @@ -123,6 +123,12 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function ); await slashingManager.setInterfold(addressOne); await slashingManager.setE3RefundManager(addressOne); + await networkHelpers.setBalance(addressOne, ethers.parseEther("1")); + await networkHelpers.impersonateAccount(addressOne); + await slashingManager + .connect(await ethers.getSigner(addressOne)) + .snapshotE3Dependencies(0); + await networkHelpers.stopImpersonatingAccount(addressOne); return { owner, diff --git a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts index 145e861a0..2b7cf03bd 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts @@ -148,6 +148,15 @@ describe("SlashingManager", function () { await slashingManager.setCiphernodeRegistry(mockCiphernodeRegistryAddress); await slashingManager.setInterfold(addressOne); await slashingManager.setE3RefundManager(addressOne); + await networkHelpers.setBalance(addressOne, ethers.parseEther("1")); + await networkHelpers.impersonateAccount(addressOne); + await slashingManager + .connect(await ethers.getSigner(addressOne)) + .snapshotE3Dependencies(0); + await slashingManager + .connect(await ethers.getSigner(addressOne)) + .snapshotE3Dependencies(1); + await networkHelpers.stopImpersonatingAccount(addressOne); return { owner, diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index 2a7a217dc..ee32eb61b 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -107,7 +107,7 @@ export interface DeployInterfoldSystemOptions { * - `registry.setSlashingManager` * - `slashingManager.{setCiphernodeRegistry,setInterfold,setE3RefundManager}` * - * Pass `false` for legacy fixtures that only wire the + * Pass `false` for isolated fixtures that only wire the * `bondingRegistry <-> slashingManager` link (always wired). */ wireSlashingManager?: boolean; From e1c6d3d953fa5e3242b85ea7e0c676baa5aa3b46 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 20:33:59 +0500 Subject: [PATCH 12/52] fix(contracts): make slash routing retryable [M-05] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 44 ++++-- .../IBondingRegistry.json | 62 ++++++++ .../ISlashingManager.json | 146 ++++++++++++++++++ .../contracts/interfaces/IBondingRegistry.sol | 21 +++ .../contracts/interfaces/ISlashingManager.sol | 45 ++++++ .../contracts/registry/BondingRegistry.sol | 39 ++++- .../contracts/slashing/SlashingManager.sol | 87 ++++++++++- .../test/E3Lifecycle/E3Integration.spec.ts | 96 ++++++++++++ 9 files changed, 520 insertions(+), 21 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 832bf0a48..14f1b4008 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -215,3 +215,4 @@ _Found during source-code cross-referencing of these trace documents._ | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | +| 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 2deef6513..330f289ca 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -734,18 +734,30 @@ _executeSlash(proposalId): │ │ Always escrows — regardless of E3 stage. │ │ Destination decided later at terminal state. │ │ -│ │ Self-call for atomicity: -│ │ try this.escrowSlashedFundsToRefund(e3Id, actualTicketSlashed) +│ ├─ Reserve and record BEFORE attempting the route: +│ │ bondingRegistry.reserveSlashedTicketFunds(amount) +│ │ pendingSlashRoutes[proposalId] = { +│ │ e3Id, token: ticketToken.underlying(), amount, pending: true +│ │ } +│ │ → Generic redirect and treasury withdrawal cannot spend reserve +│ │ → Emit SlashRoutePending +│ │ +│ │ Bounded self-call for initial atomic attempt: +│ │ try this.routePendingSlashFunds(proposalId) │ │ │ -│ │ │ ┌─── escrowSlashedFundsToRefund() ───────────────────┐ +│ │ │ ┌─── routePendingSlashFunds() ───────────────────────┐ │ │ │ │ require(msg.sender == address(this)) │ │ │ │ │ → Self-call only (for try/catch atomicity) │ +│ │ │ │ require(route.pending) │ +│ │ │ │ route.pending = false before interactions │ +│ │ │ │ → Callback cannot consume the reserve twice │ +│ │ │ │ → Any later revert restores pending=true │ │ │ │ │ │ │ │ │ │ Step A: Move USDC from BondingRegistry │ -│ │ │ │ bondingRegistry.redirectSlashedTicketFunds( │ +│ │ │ │ bondingRegistry.redirectReservedSlashedTicketFunds( │ │ │ │ e3RefundManager, amount │ │ │ │ │ ) │ -│ │ │ │ │ │ +│ │ │ │ ├─ reservedSlashedTicketBalance -= amount │ │ │ │ │ ├─ slashedTicketBalance -= amount │ │ │ │ │ └─ ticketToken.payout(e3RefundManager, amount) │ │ │ │ │ → Transfers UNDERLYING USDC (not ticket │ @@ -767,19 +779,19 @@ _executeSlash(proposalId): │ │ │ │ (see priority logic below — failure path) │ │ │ │ │ │ │ │ │ │ If EITHER step reverts → both revert together │ -│ │ │ │ → Funds stay in BondingRegistry for treasury │ +│ │ │ │ → Route remains pending and funds stay reserved │ │ │ │ │ → Slash itself still proceeds │ +│ │ │ │ On success emit SlashRouteCompleted │ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ └─ catch: emit RoutingFailed(e3Id, actualTicketSlashed) -│ → Slash is NOT rolled back, only fund escrowing fails +│ → Slash is NOT rolled back; anyone may retry the route │ -├─ 6. proposal.executed = true -│ → Set AFTER the two bondingRegistry.slash* calls (and AFTER ban -│ update), so an OOG / revert during slashing leaves `executed` -│ false and the proposal can be retried (audit H-21b, defence in -│ depth). Reentrancy is already blocked by `_executeSlash` itself -│ being reachable only through nonReentrant entry points. +├─ 6. PERMISSIONLESS ROUTE RETRY (only after an initial failure): +│ anyone calls retrySlashRoute(proposalId) +│ ├─ pending == false → return false (idempotent no-op) +│ └─ pending == true → self-call routePendingSlashFunds +│ → transfer + accounting succeed atomically, or all state reverts │ └─ 7. Emit SlashExecuted(proposalId, e3Id, operator, reason, ticketSlashed, licenseSlashed, banned) @@ -1036,13 +1048,15 @@ state: ``` STEP 1: ESCROWING (always, at slash time) - Triggered by: _executeSlash → escrowSlashedFundsToRefund + Triggered by: _executeSlash → reserve + routePendingSlashFunds When: Any slash with actualTicketSlashed > 0, regardless of E3 stage - Flow: BondingRegistry.redirectSlashedTicketFunds(refundManager, amount) + Flow: BondingRegistry.redirectReservedSlashedTicketFunds(refundManager, amount) → ticketToken.payout(refundManager, amount) → USDC moves to E3RefundManager → _pendingSlashedFunds[e3Id] += amount (if not yet calculated) Effect: slashedTicketBalance goes UP (during slash) then DOWN (during redirect) + Failure: route stays pending and the same amount remains reserved against + generic redirects/treasury withdrawal until permissionless retry succeeds STEP 2a: E3 FAILS → Requester-first distribution Triggered by: processE3Failure → calculateRefund drains pending queue diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index b05a605ce..c48b9cda2 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -121,6 +121,11 @@ "name": "RenounceOwnershipDisabled", "type": "error" }, + { + "inputs": [], + "name": "ReservedSlashedFunds", + "type": "error" + }, { "inputs": [], "name": "Unauthorized", @@ -771,6 +776,24 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redirectReservedSlashedTicketFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -809,6 +832,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "reserveSlashedTicketFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reservedSlashedTicketBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1069,6 +1118,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "ticketToken", + "outputs": [ + { + "internalType": "contract InterfoldTicketToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index a2ccbcce4..e553bd89a 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -78,6 +78,11 @@ "name": "InsufficientAttestations", "type": "error" }, + { + "inputs": [], + "name": "InsufficientRoutingGas", + "type": "error" + }, { "inputs": [], "name": "InvalidPolicy", @@ -579,6 +584,68 @@ "name": "SlashProposed", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SlashRouteCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SlashRoutePending", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -777,6 +844,47 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "getPendingSlashRoute", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "pending", + "type": "bool" + } + ], + "internalType": "struct ISlashingManager.PendingSlashRoute", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1152,6 +1260,44 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "retrySlashRoute", + "outputs": [ + { + "internalType": "bool", + "name": "routed", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "routePendingSlashFunds", + "outputs": [ + { + "internalType": "bool", + "name": "routed", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 2c36a93fd..972505bef 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -49,6 +49,10 @@ interface IBondingRegistry { /// financial slash proposal against them remains unresolved. error OperatorUnderSlash(); + /// @notice Treasury withdrawal or generic routing attempted to consume funds + /// reserved for a pending E3 slash route. + error ReservedSlashedFunds(); + /// @notice Thrown when {setExitDelay} input is outside the permitted range. error ExitDelayOutOfBounds(uint64 exitDelay); @@ -343,6 +347,12 @@ interface IBondingRegistry { */ function slashedTicketBalance() external view returns (uint256); + /// @notice Get slashed ticket funds reserved for retryable E3 routing. + function reservedSlashedTicketBalance() external view returns (uint256); + + /// @notice Get the ticket wrapper whose underlying asset backs ticket slashes. + function ticketToken() external view returns (InterfoldTicketToken); + /** * @notice Get total slashed license bond * @return Amount of license bond slashed and available for treasury withdrawal @@ -445,6 +455,17 @@ interface IBondingRegistry { */ function redirectSlashedTicketFunds(address to, uint256 amount) external; + /// @notice Reserve slashed ticket funds so treasury cannot withdraw them. + /// @dev Only callable by the configured slashing manager. + function reserveSlashedTicketFunds(uint256 amount) external; + + /// @notice Route and consume previously reserved slashed ticket funds. + /// @dev Only callable by the configured slashing manager. + function redirectReservedSlashedTicketFunds( + address to, + uint256 amount + ) external; + // ====================== // Reward Distribution Functions // ====================== diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index 6a95fddc5..14a806c57 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -104,6 +104,14 @@ interface ISlashingManager { uint8 failureReason; } + /// @notice Durable routing record for slashed ticket funds awaiting escrow. + struct PendingSlashRoute { + uint256 e3Id; + address token; + uint256 amount; + bool pending; + } + // ====================== // Errors // ====================== @@ -216,6 +224,10 @@ interface ISlashingManager { /// @notice Thrown when no pending ban proposal exists for the target node error NoPendingBan(); + /// @notice The slash transaction did not leave enough gas for its bounded + /// initial routing attempt. + error InsufficientRoutingGas(); + // ====================== // Events // ====================== @@ -348,6 +360,22 @@ interface ISlashingManager { */ event RoutingFailed(uint256 indexed e3Id, uint256 amount); + /// @notice Emitted before a slash route is attempted and durably reserved. + event SlashRoutePending( + uint256 indexed proposalId, + uint256 indexed e3Id, + address indexed token, + uint256 amount + ); + + /// @notice Emitted after a pending slash route reaches E3 escrow. + event SlashRouteCompleted( + uint256 indexed proposalId, + uint256 indexed e3Id, + address indexed token, + uint256 amount + ); + /** * @notice Emitted when the bonding registry is set * @param bondingRegistry Address of the bonding registry @@ -434,6 +462,23 @@ interface ISlashingManager { address refundManager ); + /// @notice Return a slash route that remains pending after an initial failure. + function getPendingSlashRoute( + uint256 proposalId + ) external view returns (PendingSlashRoute memory); + + /// @notice Permissionlessly retry a pending slash route. + /// @return routed True when this call completed the route; false when the + /// proposal had already been routed or never created a ticket route. + function retrySlashRoute(uint256 proposalId) external returns (bool routed); + + /// @notice Atomically consume a proposal's reserved funds and account them + /// in the snapshotted E3 refund manager. + /// @dev Self-call only; exposed for try/catch transaction atomicity. + function routePendingSlashFunds( + uint256 proposalId + ) external returns (bool routed); + /** * @notice Returns the bonding registry contract used for executing slashes * @return registry The IBondingRegistry contract instance diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index ef4400348..5b4585126 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -153,6 +153,9 @@ contract BondingRegistry is /// @notice One-way lock for every parameter that affects operator eligibility. bool public eligibilityConfigurationLocked; + /// @notice Slashed tickets committed to retryable E3 refund routes. + uint256 public reservedSlashedTicketBalance; + // ====================== // Modifiers // ====================== @@ -630,12 +633,41 @@ contract BondingRegistry is ) external onlySlashingManager { require(to != address(0), ZeroAddress()); require(amount > 0, ZeroAmount()); - require(amount <= slashedTicketBalance, InsufficientBalance()); + require( + amount <= slashedTicketBalance - reservedSlashedTicketBalance, + ReservedSlashedFunds() + ); slashedTicketBalance -= amount; ticketToken.payout(to, amount); } + /// @inheritdoc IBondingRegistry + function reserveSlashedTicketFunds( + uint256 amount + ) external onlySlashingManager { + require(amount > 0, ZeroAmount()); + require( + amount <= slashedTicketBalance - reservedSlashedTicketBalance, + InsufficientBalance() + ); + reservedSlashedTicketBalance += amount; + } + + /// @inheritdoc IBondingRegistry + function redirectReservedSlashedTicketFunds( + address to, + uint256 amount + ) external onlySlashingManager { + require(to != address(0), ZeroAddress()); + require(amount > 0, ZeroAmount()); + require(amount <= reservedSlashedTicketBalance, InsufficientBalance()); + + reservedSlashedTicketBalance -= amount; + slashedTicketBalance -= amount; + ticketToken.payout(to, amount); + } + // ====================== // Reward Distribution Functions // ====================== @@ -848,7 +880,10 @@ contract BondingRegistry is uint256 ticketAmount, uint256 licenseAmount ) public onlyOwner { - require(ticketAmount <= slashedTicketBalance, InsufficientBalance()); + require( + ticketAmount <= slashedTicketBalance - reservedSlashedTicketBalance, + ReservedSlashedFunds() + ); require(licenseAmount <= slashedLicenseBond, InsufficientBalance()); if (ticketAmount > 0) { diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index c062c2981..f1e86d48d 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -98,6 +98,13 @@ contract SlashingManager is mapping(uint256 e3Id => E3Dependencies dependencies) internal _e3Dependencies; + /// @notice Slash routes retained until their reserved ticket funds reach E3 escrow. + mapping(uint256 proposalId => PendingSlashRoute route) + internal _pendingSlashRoutes; + + uint256 internal constant INITIAL_ROUTE_GAS = 400_000; + uint256 internal constant MIN_INITIAL_ROUTE_GAS = 450_000; + /// @notice Mapping from slash reason hash to its configured policy mapping(bytes32 reason => SlashPolicy policy) public slashPolicies; @@ -283,6 +290,21 @@ contract SlashingManager is ); } + /// @inheritdoc ISlashingManager + function getPendingSlashRoute( + uint256 proposalId + ) external view returns (PendingSlashRoute memory) { + return _pendingSlashRoutes[proposalId]; + } + + /// @inheritdoc ISlashingManager + function retrySlashRoute( + uint256 proposalId + ) external returns (bool routed) { + if (!_pendingSlashRoutes[proposalId].pending) return false; + return this.routePendingSlashFunds(proposalId); + } + /// @inheritdoc ISlashingManager function snapshotE3Dependencies(uint256 e3Id) external { require( @@ -779,11 +801,37 @@ contract SlashingManager is } } - // Escrow slashed ticket funds for deferred distribution. The self-call - // keeps payout and accounting atomic. A routing failure reverts the - // whole slash so the proposal remains retryable. + // Reserve and attempt escrow. Failure leaves both a durable proposal + // route and a matching BondingRegistry reservation for permissionless retry. if (actualTicketSlashed > 0) { - this.escrowSlashedFundsToRefund(p.e3Id, actualTicketSlashed); + dependencies.bonding.reserveSlashedTicketFunds(actualTicketSlashed); + PendingSlashRoute storage route = _pendingSlashRoutes[proposalId]; + route.e3Id = p.e3Id; + route.token = address( + dependencies.bonding.ticketToken().underlying() + ); + route.amount = actualTicketSlashed; + route.pending = true; + emit SlashRoutePending( + proposalId, + p.e3Id, + route.token, + actualTicketSlashed + ); + + require( + gasleft() >= MIN_INITIAL_ROUTE_GAS, + InsufficientRoutingGas() + ); + try + this.routePendingSlashFunds{ gas: INITIAL_ROUTE_GAS }( + proposalId + ) + returns (bool routed) { + require(routed, InvalidProposal()); + } catch { + emit RoutingFailed(p.e3Id, actualTicketSlashed); + } } emit SlashExecuted( @@ -811,6 +859,37 @@ contract SlashingManager is emit SlashedFundsEscrowedToRefund(e3Id, amount); } + /// @inheritdoc ISlashingManager + function routePendingSlashFunds( + uint256 proposalId + ) external returns (bool routed) { + require(msg.sender == address(this), Unauthorized()); + PendingSlashRoute storage route = _pendingSlashRoutes[proposalId]; + require(route.pending, InvalidProposal()); + + E3Dependencies memory dependencies = _dependenciesFor(route.e3Id); + // Clear before interacting so a callback cannot route the same reserve + // twice. Any downstream failure reverts this write together with the + // transfer and accounting, leaving the route pending for another retry. + route.pending = false; + dependencies.bonding.redirectReservedSlashedTicketFunds( + address(dependencies.refundManager), + route.amount + ); + dependencies.interfoldContract.escrowSlashedFunds( + route.e3Id, + route.amount + ); + emit SlashedFundsEscrowedToRefund(route.e3Id, route.amount); + emit SlashRouteCompleted( + proposalId, + route.e3Id, + route.token, + route.amount + ); + return true; + } + function _dependenciesFor( uint256 e3Id ) internal view returns (E3Dependencies memory dependencies) { diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 51d6efbfc..6d3531853 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -7,6 +7,7 @@ import { expect } from "chai"; import type { Signer } from "ethers"; import InterfoldModule from "../../ignition/modules/interfold"; +import type { MockBlacklistUSDC } from "../../types"; import { Interfold__factory as InterfoldFactory } from "../../types"; import { deployInterfoldSystem, @@ -70,6 +71,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { slashedFundsTreasury: treasury, timeoutConfig: defaultTimeoutConfig, treasury, + useBlacklistFeeToken: true, wireSlashingManager: true, }); @@ -821,6 +823,100 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); }); + it("AUD-M05: reserves a failed slash route and retries it permissionlessly", async function () { + const { + interfold, + e3RefundManager, + registry, + slashingManager, + bondingRegistry, + usdcToken, + makeRequest, + owner, + requester, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + + await makeRequest(requester, 0); + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(SORTITION_SUBMISSION_WINDOW + 1); + await registry.finalizeCommittee(0); + + const publicKey = "0x1234567890abcdef1234567890abcdef"; + await registry.publishCommittee( + 0, + publicKey, + ethers.keccak256(publicKey), + "0x", + "0x", + ); + + const e3 = await interfold.getE3(0); + const computeDeadline = + Number(e3.inputWindow[1]) + defaultTimeoutConfig.computeWindow; + await time.increaseTo(computeDeadline + 1); + await interfold.markE3Failed(0); + await interfold.processE3Failure(0); + + const blacklistToken = usdcToken as unknown as MockBlacklistUSDC; + const refundManagerAddress = await e3RefundManager.getAddress(); + await blacklistToken.blacklist(refundManagerAddress); + + const proof = await signAndEncodeAttestation( + [operator2, operator3], + 0, + await operator1.getAddress(), + await slashingManager.getAddress(), + ); + await expect( + slashingManager.proposeSlash(0, await operator1.getAddress(), proof), + ).to.emit(slashingManager, "SlashRoutePending"); + + const pending = await slashingManager.getPendingSlashRoute(0); + expect(pending.pending).to.equal(true); + expect(pending.e3Id).to.equal(0); + expect(pending.token).to.equal(await usdcToken.getAddress()); + expect(pending.amount).to.be.gt(0); + expect(await bondingRegistry.reservedSlashedTicketBalance()).to.equal( + pending.amount, + ); + expect(await bondingRegistry.slashedTicketBalance()).to.equal( + pending.amount, + ); + + await expect( + bondingRegistry.connect(owner).withdrawSlashedFunds(pending.amount, 0), + ).to.be.revertedWithCustomError(bondingRegistry, "ReservedSlashedFunds"); + + await blacklistToken.unblacklist(refundManagerAddress); + const refundBalanceBefore = + await usdcToken.balanceOf(refundManagerAddress); + await expect(slashingManager.connect(requester).retrySlashRoute(0)) + .to.emit(slashingManager, "SlashRouteCompleted") + .withArgs(0, 0, await usdcToken.getAddress(), pending.amount); + + expect( + (await usdcToken.balanceOf(refundManagerAddress)) - refundBalanceBefore, + ).to.equal(pending.amount); + expect((await slashingManager.getPendingSlashRoute(0)).pending).to.equal( + false, + ); + expect(await bondingRegistry.reservedSlashedTicketBalance()).to.equal(0); + expect(await bondingRegistry.slashedTicketBalance()).to.equal(0); + expect( + await slashingManager.connect(requester).retrySlashRoute.staticCall(0), + ).to.equal(false); + }); + it("E2E: honest nodes can claim their share after slashed funds are escrowed", async function () { const { interfold, From d858ae36b1057d641ab4bb6ac2fd7afd453089c9 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 20:50:43 +0500 Subject: [PATCH 13/52] fix(contracts): preserve slash token denomination [H-01] --- agent/flow-trace/00_INDEX.md | 1 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 223 ++++----- .../interfaces/IInterfold.sol/IInterfold.json | 11 + .../ISlashingManager.json | 6 + .../contracts/E3RefundManager.sol | 448 ++++++++---------- .../contracts/Interfold.sol | 9 +- .../contracts/interfaces/IE3RefundManager.sol | 71 +-- .../contracts/interfaces/IInterfold.sol | 13 +- .../contracts/interfaces/ISlashingManager.sol | 6 +- .../contracts/slashing/SlashingManager.sol | 19 +- .../test/E3Lifecycle/E3Integration.spec.ts | 229 +++++++-- 11 files changed, 599 insertions(+), 437 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 14f1b4008..a84ebf6db 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -216,3 +216,4 @@ _Found during source-code cross-referencing of these trace documents._ | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | +| 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 330f289ca..e17d9bed1 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -138,21 +138,18 @@ Anyone calls: Interfold.processE3Failure(e3Id) │ │ │ H-08: if honestNodes.length == 0 and │ │ │ │ honestNodeAmount > 0, fold honestNodeAmount back │ │ │ │ into requesterAmount before storing — the work- │ -│ │ │ completed share would otherwise be stranded forever │ -│ │ │ (claimHonestNodeReward requires honestNodeCount>0, │ -│ │ │ withdrawOrphanedSlashedFunds only drains │ -│ │ │ _pendingSlashedFunds). │ +│ │ │ completed share would otherwise have no eligible │ +│ │ │ honest-node claimant. │ │ │ │ │ -│ │ │ 5. Drain pending slashed funds queue: │ -│ │ │ pending = _pendingSlashedFunds[e3Id] │ -│ │ │ if pending > 0: │ -│ │ │ _applySlashedFunds(e3Id, pending) │ -│ │ │ (see "Slashed Funds Routing" section below) │ -│ │ │ → Handles slashes that arrived BEFORE │ -│ │ │ processE3Failure was called │ +│ │ │ 5. Preserve slashed assets as separate claims: │ +│ │ │ → New escrows are keyed by (e3Id, actual token) │ +│ │ │ → A pending entry matching paymentToken may settle │ +│ │ │ now; other tokens remain permissionlessly │ +│ │ │ settleable without being relabeled │ +│ │ │ → Legacy untyped pending state is migrated only │ +│ │ │ to the E3's historically recorded fee token │ │ │ │ │ -│ │ │ M-09: snapshot perNodeAmount AFTER the pending │ -│ │ │ drain so it reflects the final post-escrow pool: │ +│ │ │ M-09: snapshot the base fee-token per-node payout: │ │ │ │ if honestNodeCount > 0: │ │ │ │ dist.perNodeAmount = │ │ │ │ honestNodeAmount / honestNodeCount │ @@ -179,7 +176,6 @@ REQUESTER claims: ├─ require(!requester refund already claimed) ├─ requesterAmount includes BOTH: │ • Base refund (from work-value BPS allocation) -│ • Slashed funds (requester filled first, up to originalPayment) ├─ Transfer requesterAmount in the per-E3 fee token └─ Emit RefundClaimed(e3Id, requester, amount) @@ -191,19 +187,23 @@ HONEST NODE claims: ├─ require(!honest-node reward already claimed by this node) │ → This ledger is independent from the requester-refund claim ledger, so a │ requester who is also an honest node can receive both entitlements -├─ honestNodeAmount includes BOTH: -│ • Base compensation (from work-value BPS allocation) -│ • Slashed funds surplus (after requester is made whole) +├─ honestNodeAmount is base fee-token compensation from the work-value allocation ├─ perNodeAmount = honestNodeAmount / honestNodeCount -│ • SNAPSHOTTED at calculateRefund (M-09); also re-snapshotted -│ inside _applySlashedFunds while _claimCount == 0 so pre-first- -│ claim escrows are reflected. Post-first-claim escrows route to -│ _pendingSlashedFunds and never mutate the snapshot. +│ • SNAPSHOTTED at calculateRefund (M-09) and never changed by +│ slashed assets, even when the slash token equals the fee token. ├─ Last claimer routes the residual dust to _pendingTreasury via │ TreasurySlashedCredited (pull); the last node never gets a │ silently-inflated payout, and no per-claim dust is stranded. ├─ Transfer directly to node (not via BondingRegistry) └─ Emit RefundClaimed(e3Id, node, amount) + +SLASH RECIPIENT claims a token-specific entitlement: + E3RefundManager.claimSlashedFunds(e3Id, actualToken) +│ +├─ Read _pendingSlashedClaims[e3Id][actualToken][caller] +├─ Clear the claim and reduce actualToken's protected liability +├─ Transfer that exact token; base refunds never consume the protected reserve +└─ Emit SlashedFundsClaimed(e3Id, caller, actualToken, amount) ``` ### Refund Example (Base Only) @@ -227,29 +227,27 @@ Scenario: E3 fails at KeyPublished stage (compute timeout) ### Refund Example (With Slashed Funds) ``` -Same scenario as above, then 2 nodes are slashed for 300,000 each: +Same scenario as above, then 2 nodes are slashed for 300,000 units of +TICKET-USD each (which may be a different token/decimal scheme from USDC): Before slash: requesterAmount = 550,000 honestNodeAmount = 400,000 originalPayment = 1,000,000 - Slash #1: 300,000 escrowed to refund pool - Requester gap = 1,000,000 - 550,000 = 450,000 - toRequester = min(300,000, 450,000) = 300,000 - toHonestNodes = 300,000 - 300,000 = 0 - → requesterAmount = 850,000, honestNodeAmount = 400,000 - - Slash #2: 300,000 escrowed to refund pool - Requester gap = 1,000,000 - 850,000 = 150,000 - toRequester = min(300,000, 150,000) = 150,000 - toHonestNodes = 300,000 - 150,000 = 150,000 - → requesterAmount = 1,000,000, honestNodeAmount = 550,000 + Each slash is recorded as (e3Id, TICKET-USD, 300,000). + Failure compensation excludes the 5% protocol weight: + requester weight = 55 / (55 + 40) + honest-node weight = 40 / (55 + 40) Final: - Requester claims: 1,000,000 (fully made whole) - Each honest node gets: 550,000 / 3 = 183,333 - Treasury received: 50,000 (at processE3Failure time) + Base USDC claims remain: requester 550,000; nodes 400,000 total + Separate TICKET-USD claims: + requester: 347,368 + honest nodes: 252,632 total (dust assigned deterministically) + Treasury base USDC credit: 50,000 + + No TICKET-USD amount is compared to or relabeled as USDC. ``` --- @@ -766,17 +764,18 @@ _executeSlash(proposalId): │ │ │ │ burnTickets() during slashTicketBalance │ │ │ │ │ │ │ │ │ │ Step B: Update escrow accounting │ -│ │ │ │ interfold.escrowSlashedFunds(e3Id, amount) │ -│ │ │ │ → e3RefundManager.escrowSlashedFunds(e3Id, amt) │ +│ │ │ │ interfold.escrowSlashedFunds(e3Id, token, amount)│ +│ │ │ │ → e3RefundManager.escrowSlashedFunds( │ +│ │ │ │ e3Id, token, amount) │ │ │ │ │ │ │ │ │ │ │ ├─ If refund distribution NOT yet calculated: │ -│ │ │ │ │ _pendingSlashedFunds[e3Id] += amount │ -│ │ │ │ │ → Queued until terminal state is reached │ +│ │ │ │ │ _pendingSlashedByToken[e3Id][token] += amt │ +│ │ │ │ │ tokenLiability[token] += amount │ +│ │ │ │ │ → Require balance >= protected liability │ │ │ │ │ │ │ │ │ │ │ └─ If refund distribution IS calculated: │ -│ │ │ │ require(no claims started yet) │ -│ │ │ │ _applySlashedFunds(e3Id, amount) │ -│ │ │ │ (see priority logic below — failure path) │ +│ │ │ │ settle token-specific pull claims now │ +│ │ │ │ → Never mutate fee-token refund buckets │ │ │ │ │ │ │ │ │ │ If EITHER step reverts → both revert together │ │ │ │ │ → Route remains pending and funds stay reserved │ @@ -803,48 +802,32 @@ _executeSlash(proposalId): > short-pays the treasury. Booking has already been zeroed before the transfer; the event exists for > indexer-side reconciliation (audit M-13). -### Slashed Funds Priority Logic (Failure Path): \_applySlashedFunds() +### Token-Aware Slashed Funds Settlement (Failure Path) ``` -_applySlashedFunds(e3Id, amount): +settleSlashedFunds(e3Id, actualToken): │ -├─ Priority: MAKE REQUESTER WHOLE FIRST +├─ Read and clear _pendingSlashedByToken[e3Id][actualToken] │ -├─ requesterGap = originalPayment - dist.requesterAmount -│ → How much more the requester needs to reach their original payment +├─ If there are no honest nodes: credit the whole amount to requester │ -├─ toRequester = min(amount, requesterGap) -│ → Fill requester up to originalPayment, no more +├─ Otherwise split with dimensionless failure-stage work weights: +│ toRequester = amount * workRemainingBps / +│ (workRemainingBps + workCompletedBps) +│ toHonestNodes = amount - toRequester │ -├─ toHonestNodes = amount - toRequester -│ → Surplus (after requester is whole) goes to honest nodes +├─ Credit _pendingSlashedClaims[e3Id][actualToken][recipient] +│ → Base fee-token RefundDistribution fields never change +│ → Decimal/unit differences cannot corrupt the base refund │ -├─ H-08: if dist.honestNodeCount == 0 and toHonestNodes > 0, -│ route toHonestNodes to the treasury pull-credit pool -│ (_pendingTreasury[treasury][feeToken]) and emit -│ TreasurySlashedCredited. The requester cap (originalPayment) -│ is preserved; the honest-node bucket would otherwise be -│ unclaimable since `claimHonestNodeReward` reverts when -│ honestNodeCount == 0. -│ -├─ dist.requesterAmount += toRequester -├─ dist.honestNodeAmount += toHonestNodes -├─ dist.totalSlashed += amount -│ -├─ M-09: if honestNodeCount > 0, re-snapshot -│ dist.perNodeAmount = honestNodeAmount / honestNodeCount. -│ escrowSlashedFunds gates this path on _claimCount == 0, so the -│ snapshot only moves before any claim has landed; later escrows -│ land in _pendingSlashedFunds and surface via -│ withdrawOrphanedSlashedFunds. -│ -└─ Emit SlashedFundsApplied(e3Id, toRequester, toHonestNodes) +└─ Emit SlashedFundsApplied(e3Id, actualToken, + toRequester, toHonestNodes) Design rationale: - The requester PAID for the computation and got nothing. They should - be made whole before honest nodes receive any slash-based bonus. - Honest nodes already receive compensation via the base BPS allocation - for work they completed. + The protocol cannot compare or cap amounts across unrelated token units + without a trusted conversion price. A dimensionless stage-weight split + preserves the intended requester/honest-node priorities while every + recipient claims the exact asset that was slashed. ``` ### Slashed Funds Distribution (Success Path): distributeSlashedFundsOnSuccess() @@ -854,11 +837,10 @@ distributeSlashedFundsOnSuccess(e3Id, activeNodes, paymentToken): │ ├─ Called by Interfold._distributeRewards() when E3 completes successfully │ -├─ escrowed = _pendingSlashedFunds[e3Id] -│ if escrowed == 0: return (nothing to distribute) -│ -├─ _pendingSlashedFunds[e3Id] = 0 -├─ _slashedSuccessToken[e3Id] = paymentToken // snapshot for later claims +├─ Store the activeNodes set and mark success settlement ready +├─ Legacy untyped escrow may migrate only to the recorded paymentToken +├─ Every explicitly recorded token settles independently via +│ settleSlashedFunds(e3Id, actualToken) │ ├─ Load the immutable E3PolicySnapshot captured by Interfold.request │ (allocation, treasury, policy version) @@ -869,18 +851,18 @@ distributeSlashedFundsOnSuccess(e3Id, activeNodes, paymentToken): ├─ Credit (pull-payment, H-01/M-02) — funds are NOT pushed here: │ for node in activeNodes: │ perNode = toNodes / activeNodes.length (dust → last node) -│ _pendingSlashedSuccess[e3Id][node] += perNode -│ Emit SlashedFundsCredited(e3Id, node, paymentToken, perNode) +│ _pendingSlashedClaims[e3Id][actualToken][node] += perNode +│ Emit SlashedFundsCredited(e3Id, node, actualToken, perNode) │ ├─ Credit treasury for protocol share: -│ _pendingTreasury[snapshot.treasury][paymentToken] += toTreasury -│ Emit TreasurySlashedCredited(snapshot.treasury, paymentToken, toTreasury) +│ _pendingTreasury[snapshot.treasury][actualToken] += toTreasury +│ Emit TreasurySlashedCredited(snapshot.treasury, actualToken, toTreasury) │ -└─ Emit SlashedFundsDistributedOnSuccess(e3Id, toNodes, toTreasury) +└─ Emit SlashedFundsDistributedOnSuccess(e3Id, actualToken, + toNodes, toTreasury) Claim flow (separate transactions, pull-only): - honest node → e3RefundManager.claimSlashedFundsOnSuccess(e3Id) - / claimSlashedFundsOnSuccessBatch(e3Ids[]) + honest node → e3RefundManager.claimSlashedFunds(e3Id, actualToken) → Emits SlashedFundsClaimed(e3Id, node, token, amt) protocol treasury → e3RefundManager.treasuryClaim(token) → Emits TreasurySlashedClaimed(treasury, token, amt) @@ -913,34 +895,33 @@ request-time snapshot; lifecycle calls fail closed if that invariant is not sati ### Slashed Funds Ordering: Escrow → Terminal State Resolution ``` -Slashing always escrows funds in _pendingSlashedFunds[e3Id], -regardless of the current E3 stage. The destination is decided -only when the E3 reaches a terminal state (Complete or Failed). +Slashing always escrows funds in +_pendingSlashedByToken[e3Id][ticketUnderlying], regardless of the +current E3 stage. Settlement never substitutes the E3 fee token. ── FAILURE PATH ────────────────────────────────────────────── Case 1: Slash happens BEFORE processE3Failure → escrowSlashedFunds sees !dist.calculated - → Funds queued in _pendingSlashedFunds[e3Id] + → Funds queued under their actual token and liability protected → When processE3Failure → calculateRefund runs: - drains pending queue via _applySlashedFunds - (requester filled first, surplus to honest nodes) + matching fee-token escrows may settle immediately; every other + recorded token is permissionlessly settled with settleSlashedFunds Case 2: Slash happens AFTER processE3Failure → escrowSlashedFunds sees dist.calculated - → _applySlashedFunds runs immediately - → require(no claims started) — reverts if too late + → token-specific requester/node pull credits are created immediately + → Base refund claims may already have started; ledgers remain independent Case 3: Multiple slashes on same E3 (failure) → Each slash independently escrows funds - → Priority logic runs per-slash: requester filled first each time - → totalSlashed accumulates across all slashes + → Every token retains an independent pending/claim/liability ledger ── SUCCESS PATH ────────────────────────────────────────────── Case 4: E3 completes successfully with escrowed slashed funds → _distributeRewards calls distributeSlashedFundsOnSuccess - → _pendingSlashedFunds[e3Id] split between nodes and treasury + → Stores active nodes and enables per-token permissionless settlement → Nodes receive successSlashedNodeBps portion (default 50%) → Treasury receives the remainder ``` @@ -1052,34 +1033,32 @@ STEP 1: ESCROWING (always, at slash time) When: Any slash with actualTicketSlashed > 0, regardless of E3 stage Flow: BondingRegistry.redirectReservedSlashedTicketFunds(refundManager, amount) → ticketToken.payout(refundManager, amount) - → USDC moves to E3RefundManager - → _pendingSlashedFunds[e3Id] += amount (if not yet calculated) + → actual ticket underlying moves to E3RefundManager + → _pendingSlashedByToken[e3Id][actualToken] += amount + → tokenLiability[actualToken] protects the balance Effect: slashedTicketBalance goes UP (during slash) then DOWN (during redirect) Failure: route stays pending and the same amount remains reserved against generic redirects/treasury withdrawal until permissionless retry succeeds -STEP 2a: E3 FAILS → Requester-first distribution - Triggered by: processE3Failure → calculateRefund drains pending queue - OR: escrowSlashedFunds after distribution is calculated - Flow: _applySlashedFunds(e3Id, amount) - → Requester filled up to originalPayment first - → Surplus to honest nodes - Claims: requester and honest nodes claim via claimRequesterRefund / claimHonestNodeReward +STEP 2a: E3 FAILS → Token-specific compensation + Triggered by: terminal escrow or permissionless settleSlashedFunds + Flow: settleSlashedFunds(e3Id, actualToken) + → Requester/honest-node weights come from failure-stage work BPS + → Credits stay in actualToken; fee-token refund buckets are unchanged + Claims: claimSlashedFunds(e3Id, actualToken) STEP 2b: E3 SUCCEEDS → Nodes + Treasury split Triggered by: _distributeRewards → distributeSlashedFundsOnSuccess - Flow: _pendingSlashedFunds[e3Id] split by successSlashedNodeBps + Flow: each actualToken amount split by successSlashedNodeBps → Nodes receive their share evenly (with dust to last) → Treasury receives the remainder - Effect: _pendingSlashedFunds[e3Id] set to 0 + Effect: only _pendingSlashedByToken[e3Id][actualToken] is cleared -FALLBACK: TREASURY WITHDRAWAL - Triggered by: Owner calls BondingRegistry.withdrawSlashedFunds() - When: Escrowing failed (catch block) or leftover balance - Flow: BondingRegistry sends to slashedFundsTreasury - → ticketToken.payout(treasury, ticketAmount) - → licenseToken.safeTransfer(treasury, licenseAmount) - Effect: slashedTicketBalance decremented +FALLBACK: RETRY, NOT OWNER RELABELING + Failed routes remain reserved in BondingRegistry and retryable by anyone. + E3RefundManager has no owner function that accepts an arbitrary token to + relabel an untyped amount. Its transfer helper preserves each token's + protected slash liability after base refunds and treasury claims. License bond slashes always go to treasury (no escrow routing for FOLD). ``` @@ -1191,12 +1170,12 @@ Applied audit findings: **C-05, H-05, H-06, H-07, H-09, H-10, H-24, M-14, M-15, seven-day governance resolution grace, `expireAppeal` permissionlessly upholds it and clears its gate. -### Pull-payment slashed funds (H-07, H-09) +### Pull-payment slashed funds (H-01, H-07, H-09) -- Slashed funds are routed through the same pull-payment pull-pool as E3 rewards (Cluster 3 / H-08 - path). Recipients claim via `claimReward(e3Id)`; failed-transfer attackers cannot grief the whole - distribution. Late credits (e.g. `_applySlashedFunds` racing a prior reward claim) are accumulated - rather than lost. +- Slashed funds use their own `(e3Id, token, recipient)` pull-payment ledger rather than the normal + reward or refund bucket. Recipients call `claimSlashedFunds(e3Id, token)`; failed-transfer + recipients cannot grief other claims, different token decimals never mix, and late credits remain + independently claimable. ### Two-step ban (M-14, M-15) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 82e2702db..0f465df2e 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -1152,6 +1152,12 @@ "name": "e3Id", "type": "uint256" }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, { "indexed": false, "internalType": "uint256", @@ -1350,6 +1356,11 @@ "name": "e3Id", "type": "uint256" }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, { "internalType": "uint256", "name": "amount", diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index e553bd89a..8298a2588 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -655,6 +655,12 @@ "name": "e3Id", "type": "uint256" }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, { "indexed": false, "internalType": "uint256", diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index f1aa4a8ec..f001723cb 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -56,14 +56,6 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { mapping(uint256 e3Id => uint256 amount) internal _totalHonestNodePaid; /// @notice Maps E3 ID to honest node addresses mapping(uint256 e3Id => address[] nodes) internal _honestNodes; - /// @notice Pending slashed funds awaiting E3 terminal state - mapping(uint256 e3Id => uint256 amount) internal _pendingSlashedFunds; - - /// @notice Pull-payment ledger for success-path slashed-fund credits (e3Id => node => amount) - mapping(uint256 e3Id => mapping(address account => uint256 amount)) - internal _pendingSlashedSuccess; - /// @notice Snapshotted payment token for success-path slashed-fund credits - mapping(uint256 e3Id => IERC20 token) internal _slashedSuccessToken; /// @notice Treasury pull-payment ledger for protocol slashed-fund share and dust. /// @dev Per-treasury so historical treasuries can drain even after rotation. mapping(address treasury => mapping(IERC20 token => uint256 amount)) @@ -76,6 +68,19 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { internal _e3PolicySnapshots; /// @notice Monotonic version for allocation or treasury changes. uint64 public policyVersion; + /// @notice Unsettled slash escrow keyed by its actual underlying asset. + mapping(uint256 e3Id => mapping(IERC20 token => uint256 amount)) + internal _pendingSlashedByToken; + /// @notice Pull-payment slash entitlements, preserving token denomination. + mapping(uint256 e3Id => mapping(IERC20 token => mapping(address account => uint256 amount))) + internal _pendingSlashedClaims; + /// @notice Slashed portion of the shared treasury pull ledger. + mapping(address account => mapping(IERC20 token => uint256 amount)) + internal _pendingTrackedTreasury; + /// @notice Protected slashed-fund liabilities for each token. + mapping(IERC20 token => uint256 amount) internal _tokenLiability; + /// @notice Whether completion recorded the honest-node set for slash settlement. + mapping(uint256 e3Id => bool ready) internal _successSettlementReady; //////////////////////////////////////////////////////////// // // // Modifiers // @@ -182,9 +187,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { ) = _baseDistribution(e3Id, originalPayment, failedAt); // No honest nodes: fold work share into the requester refund (mirrors - // {Interfold._distributeRewards} success-path). Avoids per-failure dust - // stranded outside `_pendingSlashedFunds` (which `withdrawOrphanedSlashedFunds` - // cannot reach). + // {Interfold._distributeRewards} success-path). if (honestNodes.length == 0 && honestNodeAmount > 0) { requesterAmount += honestNodeAmount; honestNodeAmount = 0; @@ -221,16 +224,8 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { ); } - // Apply any slashed funds that arrived before the distribution was calculated - uint256 pending = _pendingSlashedFunds[e3Id]; - if (pending > 0) { - _pendingSlashedFunds[e3Id] = 0; - _applySlashedFunds(e3Id, pending); - } - - // Snapshot per-honest-node payout AFTER folding in pre-distribution slashed - // funds. `claimHonestNodeReward` reads this directly so the per-node payout - // is immutable for the distribution's lifetime. + // Snapshot the base fee-token payout. Slashed assets are deliberately + // separate token-specific pull claims and never mutate these amounts. RefundDistribution storage finalDist = _distributions[e3Id]; if (honestNodes.length > 0) { finalDist.perNodeAmount = @@ -238,6 +233,10 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { honestNodes.length; } + if (_pendingSlashedByToken[e3Id][paymentToken] > 0) { + _settleSlashedFunds(e3Id, paymentToken); + } + emit RefundDistributionCalculated( e3Id, finalDist.requesterAmount, @@ -273,7 +272,9 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function _getFailedAtStage( uint256 e3Id ) internal view returns (IInterfold.E3Stage) { - IInterfold.FailureReason reason = interfold.getFailureReason(e3Id); + IInterfold.FailureReason reason = _interfoldFor(e3Id).getFailureReason( + e3Id + ); // Map failure reason to stage if ( @@ -353,13 +354,13 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { RefundDistribution storage dist = _distributions[e3Id]; if (!dist.calculated) revert RefundNotCalculated(e3Id); - // Guard against pre-upgrade records where feeToken was not yet stored + // A calculated distribution must always retain its settlement token. require( address(dist.feeToken) != address(0), "feeToken not initialized" ); - address requester = interfold.getRequester(e3Id); + address requester = _interfoldFor(e3Id).getRequester(e3Id); if (msg.sender != requester) revert NotRequester(e3Id, msg.sender); if (_requesterClaimed[e3Id][msg.sender]) { @@ -373,7 +374,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { _claimCount[e3Id]++; // Use the per-E3 fee token (not the global one, which may have been rotated) - dist.feeToken.safeTransfer(msg.sender, amount); + _transferPreservingSlashedLiability(dist.feeToken, msg.sender, amount); emit RefundClaimed(e3Id, msg.sender, amount, "REQUESTER"); } @@ -385,7 +386,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { RefundDistribution storage dist = _distributions[e3Id]; require(dist.calculated, RefundNotCalculated(e3Id)); - // Guard against pre-upgrade records where feeToken was not yet stored + // A calculated distribution must always retain its settlement token. require( address(dist.feeToken) != address(0), "feeToken not initialized" @@ -406,8 +407,8 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(dist.honestNodeCount > 0, NoRefundAvailable(e3Id)); // Read the snapshot taken at `calculateRefund` time — immutable for the - // distribution's lifetime; post-claim slashed funds are routed to - // `_pendingSlashedFunds` and never mutate `dist.honestNodeAmount`. + // distribution's lifetime; post-claim slashed funds remain in the + // token-specific slash ledger and never mutate `dist.honestNodeAmount`. uint256 perNodeAmount = dist.perNodeAmount; require(perNodeAmount > 0, NoRefundAvailable(e3Id)); @@ -440,7 +441,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { // Direct transfer to the honest node (refund path; bypasses BondingRegistry // distributor authorization and operator-registered checks). IERC20 token = dist.feeToken; - token.safeTransfer(msg.sender, amount); + _transferPreservingSlashedLiability(token, msg.sender, amount); emit RefundClaimed(e3Id, msg.sender, amount, "HONEST_NODE"); } @@ -448,52 +449,30 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { /// @inheritdoc IE3RefundManager function escrowSlashedFunds( uint256 e3Id, + IERC20 token, uint256 amount ) external onlyE3Interfold(e3Id) { require(amount > 0, "Zero amount"); + require(address(token) != address(0), "Invalid slash token"); - RefundDistribution storage dist = _distributions[e3Id]; - if (dist.calculated) { - if (_claimCount[e3Id] == 0) { - _applySlashedFunds(e3Id, amount); - } else if (dist.honestNodeCount > 0) { - // Distribution calculated and a claim has landed, but honest nodes existed. - // Credit the latecomer slash to the honest committee (pull via - // `claimSlashedFundsOnSuccess`) instead of orphaning to the treasury. - // No double-pay risk: requester + unclaimed honest portions were already - // settled by the initial `_applySlashedFunds`. - IERC20 token = dist.feeToken; - if (address(_slashedSuccessToken[e3Id]) == address(0)) { - _slashedSuccessToken[e3Id] = token; - } - address[] storage nodes = _honestNodes[e3Id]; - uint256 n = nodes.length; - uint256 perNode = amount / n; - uint256 distributed = 0; - for (uint256 i = 0; i < n; i++) { - uint256 nodeAmount = perNode; - if (i == n - 1) { - nodeAmount = amount - distributed; - } - if (nodeAmount > 0) { - _pendingSlashedSuccess[e3Id][nodes[i]] += nodeAmount; - emit SlashedFundsCredited( - e3Id, - nodes[i], - token, - nodeAmount - ); - } - distributed += nodeAmount; - } - } else { - _pendingSlashedFunds[e3Id] += amount; - } - } else { - _pendingSlashedFunds[e3Id] += amount; + _pendingSlashedByToken[e3Id][token] += amount; + _increaseTokenLiability(token, amount); + + if (_distributions[e3Id].calculated || _successSettlementReady[e3Id]) { + _settleSlashedFunds(e3Id, token); } - emit SlashedFundsEscrowed(e3Id, amount); + emit SlashedFundsEscrowed(e3Id, token, amount); + } + + /// @inheritdoc IE3RefundManager + function settleSlashedFunds(uint256 e3Id, IERC20 token) external { + require(_pendingSlashedByToken[e3Id][token] > 0, NothingToClaim()); + require( + _distributions[e3Id].calculated || _successSettlementReady[e3Id], + "E3 not settled" + ); + _settleSlashedFunds(e3Id, token); } /// @inheritdoc IE3RefundManager @@ -502,113 +481,113 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { address[] calldata honestNodes, IERC20 paymentToken ) external onlyE3Interfold(e3Id) { - uint256 escrowed = _pendingSlashedFunds[e3Id]; - if (escrowed == 0) return; - _pendingSlashedFunds[e3Id] = 0; - require(address(paymentToken) != address(0), "Invalid fee token"); - _slashedSuccessToken[e3Id] = paymentToken; - - address policyTreasury = _treasuryFor(e3Id); - uint256 toNodes = (escrowed * _successSlashedNodeBpsFor(e3Id)) / - BPS_BASE; - uint256 toProtocol = escrowed - toNodes; - - // Credit treasury share — pull only. - if (toProtocol > 0) { - _pendingTreasury[policyTreasury][paymentToken] += toProtocol; - emit TreasurySlashedCredited( - policyTreasury, - paymentToken, - toProtocol - ); + require(!_successSettlementReady[e3Id], "Already initialized"); + for (uint256 i = 0; i < honestNodes.length; i++) { + _honestNodes[e3Id].push(honestNodes[i]); } + _successSettlementReady[e3Id] = true; - if (toNodes > 0 && honestNodes.length > 0) { - _creditSuccessSlashed(e3Id, honestNodes, paymentToken, toNodes); - } else if (toNodes > 0) { - // No honest nodes — funnel the node share to treasury for governance triage. - _pendingTreasury[policyTreasury][paymentToken] += toNodes; - emit TreasurySlashedCredited(policyTreasury, paymentToken, toNodes); + if (_pendingSlashedByToken[e3Id][paymentToken] > 0) { + _settleSlashedFunds(e3Id, paymentToken); } + } + + function _settleSlashedFunds(uint256 e3Id, IERC20 token) internal { + uint256 amount = _pendingSlashedByToken[e3Id][token]; + _pendingSlashedByToken[e3Id][token] = 0; - emit SlashedFundsDistributedOnSuccess(e3Id, toNodes, toProtocol); + if (_distributions[e3Id].calculated) { + _settleFailedE3Slash(e3Id, token, amount); + } else { + _settleSuccessfulE3Slash(e3Id, token, amount); + } } - function _creditSuccessSlashed( + function _settleFailedE3Slash( uint256 e3Id, - address[] calldata honestNodes, - IERC20 paymentToken, - uint256 toNodes + IERC20 token, + uint256 amount ) internal { - uint256 perNode = toNodes / honestNodes.length; - uint256 distributed = 0; - for (uint256 i = 0; i < honestNodes.length; i++) { - uint256 nodeAmount = perNode; - if (i == honestNodes.length - 1) { - nodeAmount = toNodes - distributed; - } - if (nodeAmount > 0) { - _pendingSlashedSuccess[e3Id][honestNodes[i]] += nodeAmount; - emit SlashedFundsCredited( - e3Id, - honestNodes[i], - paymentToken, - nodeAmount - ); - } - distributed += nodeAmount; + address[] storage nodes = _honestNodes[e3Id]; + uint256 toRequester; + uint256 toHonestNodes; + if (nodes.length == 0) { + toRequester = amount; + } else { + (uint16 completedBps, uint16 remainingBps) = _calculateWorkValue( + _getFailedAtStage(e3Id), + _allocationFor(e3Id) + ); + uint256 compensationBps = uint256(completedBps) + remainingBps; + toRequester = compensationBps == 0 + ? amount + : (amount * remainingBps) / compensationBps; + toHonestNodes = amount - toRequester; } - } - /// @notice Apply slashed funds to an E3's refund distribution - /// @dev This function is ONLY called on the failure path.Priority: make requester whole first, - /// then distribute remainder to honest nodes. - /// The requester is filled up to their original E3 payment before honest nodes receive - /// any portion, ensuring the party who paid for the computation is compensated first. - /// @param e3Id The E3 ID - /// @param amount The slashed amount to apply - function _applySlashedFunds(uint256 e3Id, uint256 amount) internal { - RefundDistribution storage dist = _distributions[e3Id]; + address requester = _interfoldFor(e3Id).getRequester(e3Id); + _creditSlashedClaim(e3Id, token, requester, toRequester); + _creditNodeSlashedClaims(e3Id, token, nodes, toHonestNodes); + emit SlashedFundsApplied(e3Id, token, toRequester, toHonestNodes); + } - // Priority: make requester whole first - // requesterGap = how much more the requester needs to reach their original payment - uint256 requesterGap = dist.originalPayment > dist.requesterAmount - ? dist.originalPayment - dist.requesterAmount - : 0; - uint256 toRequester = amount >= requesterGap ? requesterGap : amount; - uint256 toHonestNodes = amount - toRequester; - - // No honest nodes: residual would be unclaimable (`claimHonestNodeReward` - // reverts and `withdrawOrphanedSlashedFunds` only drains `_pendingSlashedFunds`). - // Route to treasury pull-pool; requester cap (originalPayment) is preserved. - if (dist.honestNodeCount == 0 && toHonestNodes > 0) { - IERC20 token = dist.feeToken; - if (address(token) != address(0)) { - address policyTreasury = _treasuryFor(e3Id); - _pendingTreasury[policyTreasury][token] += toHonestNodes; - emit TreasurySlashedCredited( - policyTreasury, - token, - toHonestNodes - ); - } - toHonestNodes = 0; + function _settleSuccessfulE3Slash( + uint256 e3Id, + IERC20 token, + uint256 amount + ) internal { + address[] storage nodes = _honestNodes[e3Id]; + uint256 toNodes = (amount * _successSlashedNodeBpsFor(e3Id)) / BPS_BASE; + uint256 toProtocol = amount - toNodes; + if (nodes.length == 0) { + toProtocol += toNodes; + toNodes = 0; } - dist.requesterAmount += toRequester; - dist.honestNodeAmount += toHonestNodes; - dist.totalSlashed += amount; + _creditNodeSlashedClaims(e3Id, token, nodes, toNodes); + _creditTrackedTreasury(_treasuryFor(e3Id), token, toProtocol); + emit SlashedFundsDistributedOnSuccess(e3Id, token, toNodes, toProtocol); + } - // Re-snapshot perNodeAmount for the pre-first-claim path (gated by - // `_claimCount==0` in `escrowSlashedFunds`). Post-first-claim funds bypass - // this code via `_pendingSlashedFunds`, so the snapshot stays immutable - // across the claim window. - if (dist.honestNodeCount > 0) { - dist.perNodeAmount = dist.honestNodeAmount / dist.honestNodeCount; + function _creditNodeSlashedClaims( + uint256 e3Id, + IERC20 token, + address[] storage nodes, + uint256 amount + ) internal { + if (amount == 0) return; + uint256 perNode = amount / nodes.length; + uint256 distributed; + for (uint256 i = 0; i < nodes.length; i++) { + uint256 nodeAmount = i == nodes.length - 1 + ? amount - distributed + : perNode; + _creditSlashedClaim(e3Id, token, nodes[i], nodeAmount); + distributed += nodeAmount; } + } - emit SlashedFundsApplied(e3Id, toRequester, toHonestNodes); + function _creditSlashedClaim( + uint256 e3Id, + IERC20 token, + address account, + uint256 amount + ) internal { + if (amount == 0) return; + _pendingSlashedClaims[e3Id][token][account] += amount; + emit SlashedFundsCredited(e3Id, account, token, amount); + } + + function _creditTrackedTreasury( + address account, + IERC20 token, + uint256 amount + ) internal { + if (amount == 0) return; + _pendingTreasury[account][token] += amount; + _pendingTrackedTreasury[account][token] += amount; + emit TreasurySlashedCredited(account, token, amount); } //////////////////////////////////////////////////////////// @@ -623,6 +602,28 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { return _distributions[e3Id]; } + /// @inheritdoc IE3RefundManager + function pendingSlashedFunds( + uint256 e3Id, + IERC20 token + ) external view returns (uint256) { + return _pendingSlashedByToken[e3Id][token]; + } + + /// @inheritdoc IE3RefundManager + function pendingSlashedClaim( + uint256 e3Id, + IERC20 token, + address account + ) external view returns (uint256) { + return _pendingSlashedClaims[e3Id][token][account]; + } + + /// @inheritdoc IE3RefundManager + function tokenLiability(IERC20 token) external view returns (uint256) { + return _tokenLiability[token]; + } + /// @inheritdoc IE3RefundManager function hasRequesterClaimed( uint256 e3Id, @@ -688,17 +689,14 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { uint256 e3Id ) internal view returns (WorkValueAllocation memory allocation) { E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; - return policy.initialized ? policy.allocation : _workAllocation; + return policy.allocation; } function _successSlashedNodeBpsFor( uint256 e3Id ) internal view returns (uint16) { E3PolicySnapshot storage policy = _e3PolicySnapshots[e3Id]; - return - policy.initialized - ? policy.allocation.successSlashedNodeBps - : _workAllocation.successSlashedNodeBps; + return policy.allocation.successSlashedNodeBps; } //////////////////////////////////////////////////////////// @@ -748,52 +746,6 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { emit TreasuryUpdated(oldValue, _treasury); } - /// @notice Recover orphaned slashed funds for an E3 that has already completed - /// or whose failure was already fully processed. - /// @dev When a slash executes after an E3 has completed (or after failure claims - /// have started), funds accumulate in `_pendingSlashedFunds` with no drain - /// path. This function allows the owner to redirect them to the treasury. - /// Only callable when the E3 is in a terminal state (Complete or Failed) - /// and the funds cannot be distributed through normal channels. - /// @param e3Id The E3 ID - /// @param paymentToken The token the slashed funds are denominated in - function withdrawOrphanedSlashedFunds( - uint256 e3Id, - IERC20 paymentToken - ) external onlyOwner { - uint256 amount = _pendingSlashedFunds[e3Id]; - require(amount > 0, "No orphaned funds"); - - // Only allow withdrawal when E3 is in a terminal state - IInterfold.E3Stage stage = interfold.getE3Stage(e3Id); - require( - stage == IInterfold.E3Stage.Complete || - stage == IInterfold.E3Stage.Failed, - "E3 not in terminal state" - ); - - // If E3 is Failed and distribution hasn't been calculated yet, - // funds should flow through the normal processE3Failure path - if (stage == IInterfold.E3Stage.Failed) { - RefundDistribution storage dist = _distributions[e3Id]; - require(dist.calculated, "Use processE3Failure first"); - // refuse to redirect to treasury when honest nodes existed — the - // latecomer crediting in `escrowSlashedFunds` should have routed funds - // to them. Reaching this branch with honest nodes implies an invariant - // violation that governance must triage off-chain. - require( - dist.honestNodeCount == 0, - "Honest nodes present; use slash crediting" - ); - } - - _pendingSlashedFunds[e3Id] = 0; - require(address(paymentToken) != address(0), "Invalid fee token"); - paymentToken.safeTransfer(_treasuryFor(e3Id), amount); - - emit OrphanedSlashedFundsWithdrawn(e3Id, amount); - } - //////////////////////////////////////////////////////////// // // // Pull-Payment Claim Functions // @@ -801,43 +753,16 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { //////////////////////////////////////////////////////////// /// @inheritdoc IE3RefundManager - function claimSlashedFundsOnSuccess( - uint256 e3Id + function claimSlashedFunds( + uint256 e3Id, + IERC20 token ) external returns (uint256 amount) { - amount = _claimSlashedFundsOnSuccess(e3Id, msg.sender); + amount = _pendingSlashedClaims[e3Id][token][msg.sender]; require(amount > 0, NothingToClaim()); - } - - /// @inheritdoc IE3RefundManager - function claimSlashedFundsOnSuccessBatch( - uint256[] calldata e3Ids - ) external { - uint256 len = e3Ids.length; - uint256 totalClaimed; - for (uint256 i = 0; i < len; i++) { - totalClaimed += _claimSlashedFundsOnSuccess(e3Ids[i], msg.sender); - } - require(totalClaimed > 0, NothingToClaim()); - } - - function _claimSlashedFundsOnSuccess( - uint256 e3Id, - address account - ) internal returns (uint256 amount) { - amount = _pendingSlashedSuccess[e3Id][account]; - if (amount == 0) return 0; - _pendingSlashedSuccess[e3Id][account] = 0; - IERC20 token = _slashedSuccessToken[e3Id]; - token.safeTransfer(account, amount); - emit SlashedFundsClaimed(e3Id, account, token, amount); - } - - /// @inheritdoc IE3RefundManager - function pendingSlashedFundsOnSuccess( - uint256 e3Id, - address account - ) external view returns (uint256) { - return _pendingSlashedSuccess[e3Id][account]; + _pendingSlashedClaims[e3Id][token][msg.sender] = 0; + _decreaseTokenLiability(token, amount); + _transferPreservingSlashedLiability(token, msg.sender, amount); + emit SlashedFundsClaimed(e3Id, msg.sender, token, amount); } /// @inheritdoc IE3RefundManager @@ -845,7 +770,12 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { amount = _pendingTreasury[msg.sender][token]; require(amount > 0, NothingToClaim()); _pendingTreasury[msg.sender][token] = 0; - token.safeTransfer(msg.sender, amount); + uint256 tracked = _pendingTrackedTreasury[msg.sender][token]; + if (tracked > 0) { + _pendingTrackedTreasury[msg.sender][token] = 0; + _decreaseTokenLiability(token, tracked); + } + _transferPreservingSlashedLiability(token, msg.sender, amount); emit TreasurySlashedClaimed(msg.sender, token, amount); } @@ -857,6 +787,32 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { return _pendingTreasury[treasuryAddr][token]; } + function _increaseTokenLiability(IERC20 token, uint256 amount) internal { + _tokenLiability[token] += amount; + _assertTokenSolvent(token); + } + + function _decreaseTokenLiability(IERC20 token, uint256 amount) internal { + _tokenLiability[token] -= amount; + } + + function _transferPreservingSlashedLiability( + IERC20 token, + address recipient, + uint256 amount + ) internal { + token.safeTransfer(recipient, amount); + _assertTokenSolvent(token); + } + + function _assertTokenSolvent(IERC20 token) internal view { + uint256 liability = _tokenLiability[token]; + uint256 balance = token.balanceOf(address(this)); + if (balance < liability) { + revert InsolventToken(token, liability, balance); + } + } + //////////////////////////////////////////////////////////// // // // ERC-165 Interface Detection // diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 2879ad9f0..87184152a 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -763,13 +763,16 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { } /// @inheritdoc IInterfold - function escrowSlashedFunds(uint256 e3Id, uint256 amount) external { + function escrowSlashedFunds( + uint256 e3Id, + IERC20 token, + uint256 amount + ) external { InterfoldPricing.validateSlashCaller( msg.sender, address(_slashingManagerFor(e3Id)) ); - _refundManagerFor(e3Id).escrowSlashedFunds(e3Id, amount); - emit SlashedFundsEscrowed(e3Id, amount); + _refundManagerFor(e3Id).escrowSlashedFunds(e3Id, token, amount); } /// @inheritdoc IInterfold diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index a34902b62..80b05f6a4 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -39,7 +39,7 @@ interface IE3RefundManager { uint256 requesterAmount; // Amount for requester uint256 honestNodeAmount; // Total amount for honest nodes uint256 protocolAmount; // Amount for protocol treasury - uint256 totalSlashed; // Slashed funds added + uint256 totalSlashed; // Legacy same-token aggregate; new token-aware escrows do not mutate it uint256 honestNodeCount; // Number of honest nodes bool calculated; // Whether distribution is calculated IERC20 feeToken; // The fee token used for this E3's payment (stored per-E3 to survive token rotations) @@ -67,10 +67,15 @@ interface IE3RefundManager { bytes32 claimType ); /// @notice Emitted when slashed funds are escrowed for an E3 - event SlashedFundsEscrowed(uint256 indexed e3Id, uint256 amount); + event SlashedFundsEscrowed( + uint256 indexed e3Id, + IERC20 indexed token, + uint256 amount + ); /// @notice Emitted when slashed funds are applied to a failed E3's refund distribution event SlashedFundsApplied( uint256 indexed e3Id, + IERC20 indexed token, uint256 toRequester, uint256 toHonestNodes ); @@ -79,6 +84,7 @@ interface IE3RefundManager { /// `SlashedFundsCredited` / `TreasurySlashedCredited` for per-recipient detail. event SlashedFundsDistributedOnSuccess( uint256 indexed e3Id, + IERC20 indexed token, uint256 toNodes, uint256 toProtocol ); @@ -118,8 +124,6 @@ interface IE3RefundManager { address interfold, WorkValueAllocation allocation ); - /// @notice Emitted when orphaned slashed funds are withdrawn to treasury - event OrphanedSlashedFundsWithdrawn(uint256 indexed e3Id, uint256 amount); /// @notice Emitted when the Interfold address is set event InterfoldSet(address indexed interfold); /// @notice Emitted when the treasury address is set @@ -145,6 +149,8 @@ interface IE3RefundManager { error Unauthorized(); /// @notice Caller has no pending balance to claim error NothingToClaim(); + /// @notice Recorded liabilities exceed the manager's balance of a token. + error InsolventToken(IERC20 token, uint256 liability, uint256 balance); //////////////////////////////////////////////////////////// // // @@ -183,8 +189,38 @@ interface IE3RefundManager { /// @notice Escrow slashed funds — destination decided at terminal state /// @param e3Id The E3 ID + /// @param token The actual ticket-underlying token transferred into escrow /// @param amount The slashed amount - function escrowSlashedFunds(uint256 e3Id, uint256 amount) external; + function escrowSlashedFunds( + uint256 e3Id, + IERC20 token, + uint256 amount + ) external; + + /// @notice Settle one recorded token after the E3 reaches a terminal state. + function settleSlashedFunds(uint256 e3Id, IERC20 token) external; + + /// @notice Pull a token-specific slashed-fund entitlement. + function claimSlashedFunds( + uint256 e3Id, + IERC20 token + ) external returns (uint256 amount); + + /// @notice Pending, unsettled slash escrow for an E3 and token. + function pendingSlashedFunds( + uint256 e3Id, + IERC20 token + ) external view returns (uint256 amount); + + /// @notice Token-specific slash entitlement for an account. + function pendingSlashedClaim( + uint256 e3Id, + IERC20 token, + address account + ) external view returns (uint256 amount); + + /// @notice Total protected liabilities recorded for a token. + function tokenLiability(IERC20 token) external view returns (uint256); /// @notice Distribute escrowed slashed funds on success /// @param e3Id The E3 ID @@ -241,31 +277,6 @@ interface IE3RefundManager { uint256 e3Id ) external view returns (E3PolicySnapshot memory snapshot); - //////////////////////////////////////////////////////////// - // // - // Success-Path Slashed-Funds Pull Payments // - // // - //////////////////////////////////////////////////////////// - - /// @notice Honest node pulls credited success-path slashed funds. - /// @param e3Id The successful E3 ID. - /// @return amount Amount transferred. - function claimSlashedFundsOnSuccess( - uint256 e3Id - ) external returns (uint256 amount); - - /// @notice Batch pull credited success-path slashed funds across multiple E3s. - /// @dev Each e3Id may use a different reward token (recorded at request time); - /// events carry the per-E3 token address. A mixed-token sum return would be - /// meaningless, so the function is intentionally void. - function claimSlashedFundsOnSuccessBatch(uint256[] calldata e3Ids) external; - - /// @notice Get pending success-path slashed-funds credit for (e3Id, account). - function pendingSlashedFundsOnSuccess( - uint256 e3Id, - address account - ) external view returns (uint256); - /// @notice Treasury pulls accrued credits (protocol slashed-fund share + dust). /// @dev Caller must be the treasury that was credited. function treasuryClaim(IERC20 token) external returns (uint256 amount); diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index e344e459b..a0d21a4f1 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -258,7 +258,11 @@ interface IInterfold { /// @notice Emitted when slashed funds are escrowed for an E3 /// @param e3Id The E3 ID. /// @param amount The amount of slashed funds escrowed. - event SlashedFundsEscrowed(uint256 indexed e3Id, uint256 amount); + event SlashedFundsEscrowed( + uint256 indexed e3Id, + IERC20 indexed token, + uint256 amount + ); /// @notice Emitted when a failed E3 is processed for refunds. /// @param e3Id The ID of the failed E3. @@ -693,8 +697,13 @@ interface IInterfold { /// @notice Escrow slashed funds for deferred distribution /// @dev Called by SlashingManager. Proxies to E3RefundManager. /// @param e3Id The E3 ID. + /// @param token Actual ticket-underlying token transferred into escrow. /// @param amount Amount of slashed funds to escrow. - function escrowSlashedFunds(uint256 e3Id, uint256 amount) external; + function escrowSlashedFunds( + uint256 e3Id, + IERC20 token, + uint256 amount + ) external; //////////////////////////////////////////////////////////// // // diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index 14a806c57..10c7e9dee 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -351,7 +351,11 @@ interface ISlashingManager { * @param e3Id ID of the E3 computation * @param amount Amount of slashed funds escrowed (underlying stablecoin) */ - event SlashedFundsEscrowedToRefund(uint256 indexed e3Id, uint256 amount); + event SlashedFundsEscrowedToRefund( + uint256 indexed e3Id, + address indexed token, + uint256 amount + ); /** * @notice Emitted when routing slashed funds fails (funds remain in BondingRegistry) diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index f1e86d48d..0092b3d8a 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -11,6 +11,7 @@ import { } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISlashingManager } from "../interfaces/ISlashingManager.sol"; import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; @@ -854,9 +855,16 @@ contract SlashingManager is E3Dependencies memory dependencies = _dependenciesFor(e3Id); address refundManager = address(dependencies.refundManager); require(refundManager != address(0), ZeroAddress()); + address token = address( + dependencies.bonding.ticketToken().underlying() + ); dependencies.bonding.redirectSlashedTicketFunds(refundManager, amount); - dependencies.interfoldContract.escrowSlashedFunds(e3Id, amount); - emit SlashedFundsEscrowedToRefund(e3Id, amount); + dependencies.interfoldContract.escrowSlashedFunds( + e3Id, + IERC20(token), + amount + ); + emit SlashedFundsEscrowedToRefund(e3Id, token, amount); } /// @inheritdoc ISlashingManager @@ -878,9 +886,14 @@ contract SlashingManager is ); dependencies.interfoldContract.escrowSlashedFunds( route.e3Id, + IERC20(route.token), + route.amount + ); + emit SlashedFundsEscrowedToRefund( + route.e3Id, + route.token, route.amount ); - emit SlashedFundsEscrowedToRefund(route.e3Id, route.amount); emit SlashRouteCompleted( proposalId, route.e3Id, diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 6d3531853..bebe18cb7 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -8,7 +8,10 @@ import type { Signer } from "ethers"; import InterfoldModule from "../../ignition/modules/interfold"; import type { MockBlacklistUSDC } from "../../types"; -import { Interfold__factory as InterfoldFactory } from "../../types"; +import { + Interfold__factory as InterfoldFactory, + MockUSDC__factory as MockUSDCFactory, +} from "../../types"; import { deployInterfoldSystem, ethers, @@ -117,6 +120,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const makeRequest = async ( signer: Signer = requester, committeeSize: number = 0, + requestToken = usdcToken, ): Promise<{ e3Id: number }> => { const startTime = (await time.latest()) + 100; @@ -139,7 +143,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { }; const fee = await interfold.getE3Quote(requestParams); - await usdcToken.connect(signer).approve(interfoldAddress, fee); + await requestToken.connect(signer).approve(interfoldAddress, fee); await interfold .connect(signer) .request({ ...requestParams, maxFee: fee }); @@ -803,23 +807,39 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { slashedBalanceBefore, // slash added then redirect removed the same amount ); - // 6. Verify distribution was updated with requester-first priority + // 6. Base refunds stay denominated in the E3 fee token; the slash is a + // separate claim in its actual underlying token. const distributionAfter = await e3RefundManager.getRefundDistribution(0); - expect(distributionAfter.totalSlashed).to.equal(actualSlashedAmount); - expect(distributionAfter.requesterAmount).to.be.gte( + expect(distributionAfter.requesterAmount).to.equal( distributionBefore.requesterAmount, ); + expect(distributionAfter.honestNodeAmount).to.equal( + distributionBefore.honestNodeAmount, + ); + const usdcAddress = await usdcToken.getAddress(); + const requesterSlashClaim = await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await requester.getAddress(), + ); + expect(requesterSlashClaim).to.be.gt(0); - // 7. Verify requester can actually claim and receives the correct USDC + // 7. The requester pulls the base refund and slash entitlement separately. const requesterBalanceBefore = await usdcToken.balanceOf( await requester.getAddress(), ); await e3RefundManager.connect(requester).claimRequesterRefund(0); + expect( + await usdcToken.balanceOf(await e3RefundManager.getAddress()), + ).to.be.gte(await e3RefundManager.tokenLiability(usdcAddress)); + await e3RefundManager + .connect(requester) + .claimSlashedFunds(0, usdcAddress); const requesterBalanceAfter = await usdcToken.balanceOf( await requester.getAddress(), ); expect(requesterBalanceAfter - requesterBalanceBefore).to.equal( - distributionAfter.requesterAmount, + distributionAfter.requesterAmount + requesterSlashClaim, ); }); @@ -917,6 +937,133 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ).to.equal(false); }); + it("AUD-H01: preserves a slash token distinct from the E3 fee token", async function () { + const { + interfold, + e3RefundManager, + registry, + slashingManager, + usdcToken: ticketUnderlying, + makeRequest, + owner, + requester, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + + const feeToken = await new MockUSDCFactory(owner).deploy(0); + await feeToken.waitForDeployment(); + await feeToken.mint( + await requester.getAddress(), + ethers.parseUnits("10000", 6), + ); + await interfold.connect(owner).setFeeToken(await feeToken.getAddress()); + + await makeRequest(requester, 0, feeToken); + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(SORTITION_SUBMISSION_WINDOW + 1); + await registry.finalizeCommittee(0); + const publicKey = "0x1234567890abcdef1234567890abcdef"; + await registry.publishCommittee( + 0, + publicKey, + ethers.keccak256(publicKey), + "0x", + "0x", + ); + + const proof = await signAndEncodeAttestation( + [operator2, operator3], + 0, + await operator1.getAddress(), + await slashingManager.getAddress(), + ); + await slashingManager.proposeSlash( + 0, + await operator1.getAddress(), + proof, + ); + + const underlyingAddress = await ticketUnderlying.getAddress(); + const feeTokenAddress = await feeToken.getAddress(); + const actualSlash = ethers.parseUnits("50", 6); + expect( + await e3RefundManager.pendingSlashedFunds(0, underlyingAddress), + ).to.equal(actualSlash); + + const e3 = await interfold.getE3(0); + await time.increaseTo( + Number(e3.inputWindow[1]) + defaultTimeoutConfig.computeWindow + 1, + ); + await interfold.markE3Failed(0); + await interfold.processE3Failure(0); + + // The fee-token settlement cannot consume or relabel the distinct + // ticket underlying. Anyone can settle that recorded token explicitly. + expect( + await e3RefundManager.pendingSlashedFunds(0, underlyingAddress), + ).to.equal(actualSlash); + await e3RefundManager + .connect(operator3) + .settleSlashedFunds(0, underlyingAddress); + + const distribution = await e3RefundManager.getRefundDistribution(0); + expect(distribution.feeToken).to.equal(feeTokenAddress); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + feeTokenAddress, + await requester.getAddress(), + ), + ).to.equal(0); + + const recipients = [requester, operator1, operator2, operator3]; + let totalSlashCredits = 0n; + for (const recipient of recipients) { + totalSlashCredits += await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await recipient.getAddress(), + ); + } + expect(totalSlashCredits).to.equal(actualSlash); + expect(await e3RefundManager.tokenLiability(underlyingAddress)).to.equal( + totalSlashCredits, + ); + + const requesterAddress = await requester.getAddress(); + const feeBefore = await feeToken.balanceOf(requesterAddress); + const underlyingBefore = + await ticketUnderlying.balanceOf(requesterAddress); + const slashClaim = await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + requesterAddress, + ); + await e3RefundManager.connect(requester).claimRequesterRefund(0); + await e3RefundManager + .connect(requester) + .claimSlashedFunds(0, underlyingAddress); + + expect((await feeToken.balanceOf(requesterAddress)) - feeBefore).to.equal( + distribution.requesterAmount, + ); + expect( + (await ticketUnderlying.balanceOf(requesterAddress)) - underlyingBefore, + ).to.equal(slashClaim); + expect(await e3RefundManager.tokenLiability(underlyingAddress)).to.equal( + totalSlashCredits - slashClaim, + ); + }); + it("E2E: honest nodes can claim their share after slashed funds are escrowed", async function () { const { interfold, @@ -955,7 +1102,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await interfold.markE3Failed(0); await interfold.processE3Failure(0); - // 3. Record distribution BEFORE slash to verify it actually changes + // 3. Record the base distribution before slash. const distributionBefore = await e3RefundManager.getRefundDistribution(0); const honestNodeAmountBefore = distributionBefore.honestNodeAmount; @@ -974,29 +1121,41 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distribution = await e3RefundManager.getRefundDistribution(0); expect(distribution.honestNodeCount).to.be.gt(0); - // Verify that honestNodeAmount INCREASED due to slashed funds escrow - expect(distribution.honestNodeAmount).to.be.gt(honestNodeAmountBefore); - expect(distribution.totalSlashed).to.be.gt(0); + expect(distribution.honestNodeAmount).to.equal(honestNodeAmountBefore); + const usdcAddress = await usdcToken.getAddress(); + const op2SlashClaim = await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator2.getAddress(), + ); + expect(op2SlashClaim).to.be.gt(0); // 5. operator2 (honest node) claims their share const op2BalanceBefore = await usdcToken.balanceOf( await operator2.getAddress(), ); await e3RefundManager.connect(operator2).claimHonestNodeReward(0); + await e3RefundManager + .connect(operator2) + .claimSlashedFunds(0, usdcAddress); const op2BalanceAfter = await usdcToken.balanceOf( await operator2.getAddress(), ); const perNodeAmount = distribution.honestNodeAmount / BigInt(distribution.honestNodeCount); - expect(op2BalanceAfter - op2BalanceBefore).to.equal(perNodeAmount); + expect(op2BalanceAfter - op2BalanceBefore).to.equal( + perNodeAmount + op2SlashClaim, + ); }); it("requester-first priority: requester gets filled before honest nodes", async function () { const { interfold, e3RefundManager, + usdcToken, makeRequest, + requester, operator1, operator2, operator3, @@ -1017,10 +1176,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distributionBefore = await e3RefundManager.getRefundDistribution(0); const slashedAmount = ethers.parseUnits("100", 6); - // requesterGap = originalPayment - requesterAmount (how much more needed to be whole) - const requesterGap = - distributionBefore.originalPayment - distributionBefore.requesterAmount; - // Call from the Interfold frozen in the E3 policy snapshot. Rotating the // manager's live pointer must not grant settlement authority for old E3s. const originalInterfold = await e3RefundManager.interfold(); @@ -1033,32 +1188,38 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ]); await e3RefundManager .connect(await ethers.getSigner(originalInterfold)) - .escrowSlashedFunds(0, slashedAmount); + .escrowSlashedFunds(0, await usdcToken.getAddress(), slashedAmount); await ethers.provider.send("hardhat_stopImpersonatingAccount", [ originalInterfold, ]); const distributionAfter = await e3RefundManager.getRefundDistribution(0); - // H-08: with no honest nodes (failure at committee formation), the\n // node share is routed to the treasury pull-pool instead of being\n // stranded in `dist.honestNodeAmount`. Requester is still capped at\n // `originalPayment` via the requesterGap. - const expectedToRequester = - slashedAmount >= requesterGap ? requesterGap : slashedAmount; - const expectedToHonestNodes = 0n; - expect(distributionAfter.requesterAmount).to.equal( - distributionBefore.requesterAmount + expectedToRequester, + distributionBefore.requesterAmount, ); expect(distributionAfter.honestNodeAmount).to.equal( - distributionBefore.honestNodeAmount + expectedToHonestNodes, + distributionBefore.honestNodeAmount, ); - expect(distributionAfter.totalSlashed).to.equal(slashedAmount); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + await usdcToken.getAddress(), + await requester.getAddress(), + ), + ).to.equal(slashedAmount); + expect( + await e3RefundManager.tokenLiability(await usdcToken.getAddress()), + ).to.equal(slashedAmount); }); it("queues slashed funds arriving before processE3Failure and applies on calculate", async function () { const { interfold, e3RefundManager, + usdcToken, makeRequest, + requester, operator1, operator2, operator3, @@ -1088,7 +1249,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ]); await e3RefundManager .connect(await ethers.getSigner(originalInterfold)) - .escrowSlashedFunds(0, slashedAmount); + .escrowSlashedFunds(0, await usdcToken.getAddress(), slashedAmount); await ethers.provider.send("hardhat_stopImpersonatingAccount", [ originalInterfold, ]); @@ -1102,9 +1263,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distAfter = await e3RefundManager.getRefundDistribution(0); expect(distAfter.calculated).to.be.true; - expect(distAfter.totalSlashed).to.equal(slashedAmount); - - // `totalSlashed` was already asserted above; the per-bucket split is\n // exercised by the dedicated requester-priority test — the residual\n // is routed to the treasury pull-pool, not to a single dist bucket. + const usdcAddress = await usdcToken.getAddress(); + expect( + await e3RefundManager.pendingSlashedFunds(0, usdcAddress), + ).to.equal(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await requester.getAddress(), + ), + ).to.equal(slashedAmount); }); }); @@ -1703,7 +1872,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { let slashedClaimedTotal = 0n; for (const op of ops) { const before = await usdcToken.balanceOf(await op.getAddress()); - await e3RefundManager.connect(op).claimSlashedFundsOnSuccess(0); + await e3RefundManager.connect(op).claimSlashedFunds(0, usdcAddress); const after = await usdcToken.balanceOf(await op.getAddress()); slashedClaimedTotal += after - before; } From 05c33fb79a5ff511670c5178f3a10ba801b1ae0d Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 21:03:46 +0500 Subject: [PATCH 14/52] chore(contracts): preserve proof-disabled development mode [C-02] --- agent/flow-trace/00_INDEX.md | 1 + agent/flow-trace/04_DKG_AND_COMPUTATION.md | 15 +++++++++++---- crates/evm/src/ciphernode_registry/effects.rs | 2 +- crates/evm/src/interfold_writing/effects.rs | 2 +- .../contracts/interfaces/IInterfold.sol | 2 -- .../registry/CiphernodeRegistryOwnable.sol | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index a84ebf6db..8767f422e 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -217,3 +217,4 @@ _Found during source-code cross-referencing of these trace documents._ | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | +| 32 | **Proof-disabled publication bypass (AUD C-02)** | Predeploy blocker | `proofAggregationEnabled=false` intentionally retains the faster unverified development path. The flag and both bypasses must be removed before any production deployment; C-02 is not closed by the current code. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index f59bd7644..a7d64fb35 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -637,6 +637,8 @@ ThresholdKeyshare receives AllThresholdSharesCollected ├─ Requires EffectsEnabled ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm committee public key is still unset + ├─ Encodes the DkgAggregator proof when aggregation is enabled, + │ otherwise passes empty proof bytes in development mode └─ Calls contract.publishCommittee(e3_id, publicKey, pkCommitment, proof) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ @@ -647,6 +649,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected │ │ 3. committeeHash = keccak256(abi.encodePacked(c.topNodes)) │ │ │ c.committeeHash = committeeHash │ │ │ 4. When proofAggregationEnabled: │ + │ │ require(proof.length > 0) │ │ │ e3.pkVerifier.verify( │ │ │ e3Id, committeeRoot, c.topNodes, │ │ │ pkCommitment, committeeHash, proof │ @@ -658,6 +661,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected │ │ [2+H] & [3+H]) vs committeeHash │ │ │ • last PI == pkCommitment │ │ │ • M-35: revert on failure (no `bool false`) │ + │ │ and verify/store per-node fold attestations │ │ │ 5. c.publicKey = pkCommitment │ │ │ publicKeyHashes[e3Id] = pkCommitment │ │ │ 6. interfold.onCommitteePublished(e3Id, pkCommitment) │ @@ -888,6 +892,8 @@ InterfoldSolReader decodes CiphertextOutputPublished event ├─ Requires EffectsEnabled ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm plaintextOutput is still empty + ├─ Encodes the final decryption proof when aggregation is enabled, + │ otherwise passes empty proof bytes in development mode └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) │ │ ┌─── ON-CHAIN (Interfold.sol) ─────────────────────────┐ @@ -895,8 +901,9 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ publishPlaintextOutput(e3Id, output, proof) { │ │ │ 1. require(stage == CiphertextReady) │ │ │ 2. require(now <= decryptionDeadline) │ - │ │ 3. e3.plaintextOutput = output │ - │ │ 4. decryptionVerifier.verify( │ + │ │ 3. When proofAggregationEnabled: │ + │ │ require(proof.length > 0), then call │ + │ │ decryptionVerifier.verify( │ │ │ e3Id, committeeRoot, │ │ │ committeeNodes, ciphertextOutput, │ │ │ committeePublicKey, │ @@ -904,8 +911,8 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ ) │ │ │ → M-34: c6Fold / C7 VK hashes are immutable. │ │ │ → M-35: revert path only (no `bool false`). │ - │ │ 5. stage = Complete │ - │ │ 6. _distributeRewards(e3Id) │ + │ │ 4. stage = Complete │ + │ │ 5. _distributeRewards(e3Id) │ │ │ │ │ │ │ │ ┌─ Reward Distribution (pull, H-01/M-02) ┐ │ │ │ │ │ 1. Get active committee nodes: │ │ diff --git a/crates/evm/src/ciphernode_registry/effects.rs b/crates/evm/src/ciphernode_registry/effects.rs index 71b8e1589..0f8c1bfad 100644 --- a/crates/evm/src/ciphernode_registry/effects.rs +++ b/crates/evm/src/ciphernode_registry/effects.rs @@ -150,7 +150,7 @@ pub async fn publish_committee_to_registry encode_zk_proof(p)?, None => Bytes::new(), diff --git a/crates/evm/src/interfold_writing/effects.rs b/crates/evm/src/interfold_writing/effects.rs index f4a8d798b..006e4a4bf 100644 --- a/crates/evm/src/interfold_writing/effects.rs +++ b/crates/evm/src/interfold_writing/effects.rs @@ -15,7 +15,7 @@ pub(in crate::actors::interfold_sol_writer) async fn publish_plaintext_output< ) -> Result { let e3_id: U256 = e3_id.try_into()?; - // `None` => proof aggregation disabled; contract accepts empty bytes in that case. + // `None` means proof aggregation is disabled for this predeployment E3. let proof: Bytes = match decryption_aggregator_proof { Some(p) => encode_zk_proof(p)?, None => Bytes::new(), diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index a0d21a4f1..30c4eca0a 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -509,8 +509,6 @@ interface IInterfold { /// @notice When true, ciphernodes generate and fold wrapper proofs /// for DKG proof aggregation (public verifiability). When /// false, wrapper/fold proofs are skipped to reduce latency. - /// C5 and C7 proofs are always generated and verified on-chain - /// regardless of this flag. bool proofAggregationEnabled; /// @notice Maximum fee-token amount authorized for this request. uint256 maxFee; diff --git a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index c4a246fe9..9d58dfc69 100644 --- a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -1160,5 +1160,5 @@ contract CiphernodeRegistryOwnable is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[49] private __gap; + uint256[50] private __gap; } From 590c425eeb1cfc4f61dcf57ee8c8d25804e6beb1 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 21:13:08 +0500 Subject: [PATCH 15/52] fix(indexer): bind published BFV keys to C5 [C-01] --- agent/CRATES_ARCHITECTURE.md | 2 + agent/flow-trace/00_INDEX.md | 3 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 7 ++ crates/bfv-client/src/client.rs | 95 +++++++++++++++++++ crates/bfv-client/src/lib.rs | 1 + crates/indexer/Cargo.toml | 4 +- crates/indexer/src/indexer.rs | 18 +++- examples/CRISP/Cargo.lock | 2 + .../interfaces/ICiphernodeRegistry.sol | 5 +- 9 files changed, 132 insertions(+), 5 deletions(-) diff --git a/agent/CRATES_ARCHITECTURE.md b/agent/CRATES_ARCHITECTURE.md index c7b75fca1..9d80535df 100644 --- a/agent/CRATES_ARCHITECTURE.md +++ b/agent/CRATES_ARCHITECTURE.md @@ -101,7 +101,9 @@ flowchart TD SDK --> Indexer SDK --> EvmHelpers SDK --> FheParams + Indexer --> BfvClient Indexer --> EvmHelpers + Indexer --> FheParams EvmHelpers --> Utils Wasm --> BfvClient Wasm --> FheParams diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 8767f422e..e95a95225 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -179,7 +179,7 @@ _Found during source-code cross-referencing of these trace documents._ | 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IInterfold.sol:82 | 03_E3_REQUEST | | 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | | 7 | `publishCommittee()` is now permissionless. The effective access control is DKG proof verification plus the single-publish guard `publicKeyHashes[e3Id] == 0`; the old `onlyOwner` note is obsolete. | CiphernodeRegistryOwnable.sol | 04_DKG | -| 8 | `CommitteePublished` event emits `(e3Id, nodes, publicKey, pkCommitment, proof)` — full PK bytes, pkCommitment, and proof bytes (DkgAggregator when proof aggregation is enabled), not just pkHash. | CiphernodeRegistryOwnable.sol | 04_DKG | +| 8 | `CommitteePublished` emits `(e3Id, nodes, publicKey, pkCommitment, proof)`. The full PK bytes are an untrusted transport hint that the indexer decodes and checks against the on-chain commitment before storage; that commitment is C5-proven when proof aggregation is enabled. | CiphernodeRegistryOwnable.sol | 04_DKG | | 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | | 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | @@ -218,3 +218,4 @@ _Found during source-code cross-referencing of these trace documents._ | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Predeploy blocker | `proofAggregationEnabled=false` intentionally retains the faster unverified development path. The flag and both bypasses must be removed before any production deployment; C-02 is not closed by the current code. | +| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index a7d64fb35..6bda56ff3 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -680,6 +680,13 @@ ThresholdKeyshare receives AllThresholdSharesCollected │ └─────────────────────────────────────────────────────┘ ``` +The serialized `publicKey` event field is a transport hint, not on-chain authority. Before +`e3-indexer` stores it in `E3.committee_public_key`, it decodes the BFV key, recomputes the circuit's +public-key commitment using the request's parameter set, and requires equality with the event's +on-chain `pkCommitment`. Malformed bytes or bytes for a different key fail closed and never reach +encryption clients. The commitment is C5-proven when proof aggregation is enabled; the explicitly +unsafe development mode trusts it from the aggregator. + > **C-08 (BfvPkVerifier domain binding) — implemented** The wrapper exposes a > `verify(e3Id, committeeRoot, sortedNodes, pkCommitment, committeeHash, proof)` signature. > `committeeHash` (computed on-chain as `keccak256(abi.encodePacked(c.topNodes))`) is split into diff --git a/crates/bfv-client/src/client.rs b/crates/bfv-client/src/client.rs index 91cc11e5a..167b28011 100644 --- a/crates/bfv-client/src/client.rs +++ b/crates/bfv-client/src/client.rs @@ -185,6 +185,38 @@ pub fn compute_pk_commitment( Ok(commitment) } +/// Validate client-consumed BFV public-key bytes against the on-chain +/// commitment (C5-proven when proof aggregation is enabled). +/// +/// Validation is semantic rather than byte-for-byte: `fhe.rs` normalizes an +/// internal variable-time flag while decoding threshold-aggregated keys, so a +/// decode/re-encode cycle is not a stable serialization check. The decoded key +/// is safe to consume only when its circuit commitment matches the expected +/// on-chain commitment. +pub fn validate_pk_commitment( + public_key: &[u8], + expected_commitment: [u8; 32], + degree: usize, + plaintext_modulus: u64, + moduli: Vec, +) -> Result<()> { + use e3_zk_helpers::circuits::threshold::user_data_encryption::utils::compute_public_key_commitment; + + let params = build_client_params(degree, plaintext_modulus, &moduli, None)?; + let decoded = PublicKey::from_bytes(public_key, ¶ms) + .map_err(|e| anyhow!("Error deserializing public key: {e}"))?; + + let actual_commitment = compute_public_key_commitment(¶ms, &decoded) + .map_err(|e| anyhow!("Error computing public key commitment: {e}"))?; + if actual_commitment != expected_commitment { + return Err(anyhow!( + "Public key commitment mismatch: event bytes are not the key proven by C5" + )); + } + + Ok(()) +} + pub fn compute_ct_commitment( ct: Vec, degree: usize, @@ -238,6 +270,69 @@ mod tests { assert!(error.to_string().contains("Invalid BFV parameters")); } + #[test] + fn validates_threshold_key_matching_the_proven_commitment() { + use fhe::mbfv::{Aggregate as _, CommonRandomPoly, PublicKeyShare}; + + let param_set: BfvParamSet = DEFAULT_BFV_PRESET.into(); + let params = build_bfv_params_from_set_arc(param_set); + let mut rng = rng(); + let crp = CommonRandomPoly::new(¶ms, &mut rng).unwrap(); + let shares = (0..2) + .map(|_| { + let secret_key = SecretKey::random(¶ms, &mut rng); + PublicKeyShare::new(&secret_key, crp.clone(), &mut rng).unwrap() + }) + .collect::>(); + let expected_key = PublicKey::from_shares(shares).unwrap(); + let substituted_key = PublicKey::new(&SecretKey::random(¶ms, &mut rng), &mut rng); + let expected_bytes = expected_key.to_bytes(); + let normalized_bytes = PublicKey::from_bytes(&expected_bytes, ¶ms) + .unwrap() + .to_bytes(); + assert_ne!( + normalized_bytes, expected_bytes, + "threshold aggregation must exercise fhe.rs variable-time normalization" + ); + let expected_commitment = compute_pk_commitment( + expected_bytes.clone(), + param_set.degree, + param_set.plaintext_modulus, + param_set.moduli.to_vec(), + ) + .unwrap(); + + validate_pk_commitment( + &expected_bytes, + expected_commitment, + param_set.degree, + param_set.plaintext_modulus, + param_set.moduli.to_vec(), + ) + .unwrap(); + + let error = validate_pk_commitment( + &substituted_key.to_bytes(), + expected_commitment, + param_set.degree, + param_set.plaintext_modulus, + param_set.moduli.to_vec(), + ) + .unwrap_err(); + assert!(error.to_string().contains("commitment mismatch")); + + let mut equivalent_encoding = expected_bytes; + equivalent_encoding.extend_from_slice(&[0x78, 0x01]); + validate_pk_commitment( + &equivalent_encoding, + expected_commitment, + param_set.degree, + param_set.plaintext_modulus, + param_set.moduli.to_vec(), + ) + .unwrap(); + } + #[test] fn verifiable_parameter_builder_uses_the_canonical_error_variance() { for preset in BfvPreset::PAIR_PRESETS { diff --git a/crates/bfv-client/src/lib.rs b/crates/bfv-client/src/lib.rs index 0a216f4f1..5f1891f49 100644 --- a/crates/bfv-client/src/lib.rs +++ b/crates/bfv-client/src/lib.rs @@ -13,6 +13,7 @@ use thiserror::Error as ThisError; pub use client::VerifiableEncryptionResult; pub use client::{ bfv_encrypt, bfv_verifiable_encrypt, compute_ct_commitment, compute_pk_commitment, + validate_pk_commitment, }; #[derive(ThisError, Debug)] diff --git a/crates/indexer/Cargo.toml b/crates/indexer/Cargo.toml index c6c1f5a87..9567a8653 100644 --- a/crates/indexer/Cargo.toml +++ b/crates/indexer/Cargo.toml @@ -10,7 +10,9 @@ repository = "https://github.com/theinterfold/interfold/crates/indexer" alloy.workspace = true async-trait.workspace = true bincode.workspace = true +e3-bfv-client.workspace = true e3-evm-helpers.workspace = true +e3-fhe-params = { workspace = true, features = ["abi-encoding"] } eyre.workspace = true serde.workspace = true thiserror.workspace = true @@ -18,8 +20,6 @@ tokio.workspace = true tracing.workspace = true [dev-dependencies] -e3-bfv-client.workspace = true -e3-fhe-params.workspace = true fhe.workspace = true fhe-traits.workspace = true rand.workspace = true diff --git a/crates/indexer/src/indexer.rs b/crates/indexer/src/indexer.rs index 85eb4f570..e56088901 100644 --- a/crates/indexer/src/indexer.rs +++ b/crates/indexer/src/indexer.rs @@ -9,10 +9,11 @@ use crate::callback_queue::CallbackQueue; use crate::E3Repository; use alloy::consensus::BlockHeader; use alloy::hex; -use alloy::primitives::Uint; +use alloy::primitives::{keccak256, Uint}; use alloy::providers::Provider; use alloy::sol_types::SolEvent; use async_trait::async_trait; +use e3_bfv_client::validate_pk_commitment; use e3_evm_helpers::{ block_listener::BlockListener, contracts::{ @@ -22,6 +23,7 @@ use e3_evm_helpers::{ event_listener::EventListener, events::{CiphertextOutputPublished, CommitteePublished, PlaintextOutputPublished}, }; +use e3_fhe_params::decode_bfv_params; use eyre::eyre; use eyre::Result; use serde::{de::DeserializeOwned, Serialize}; @@ -352,6 +354,20 @@ impl InterfoldIndexer { let e3 = contract.get_e3(e.e3Id).await?; let e3_params = contract.get_param_set_registry(e3.paramSet).await?; + if e3.encryptionSchemeId == keccak256("fhe.rs:BFV") { + let decoded_params = decode_bfv_params(&e3_params) + .map_err(|error| eyre!("invalid BFV parameters for E3 {e3_id}: {error}"))?; + validate_pk_commitment( + &e.publicKey, + e.pkCommitment.0, + decoded_params.degree(), + decoded_params.plaintext(), + decoded_params.moduli().to_vec(), + ) + .map_err(|error| { + eyre!("rejecting unbound CommitteePublished public key for E3 {e3_id}: {error}") + })?; + } let seed = e3.seed.to_be_bytes(); let request_block = u64_try_from(e3.requestBlock)?; let input_window = [ diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 6fff6991d..64dd74392 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2494,7 +2494,9 @@ dependencies = [ "alloy", "async-trait", "bincode", + "e3-bfv-client", "e3-evm-helpers", + "e3-fhe-params", "eyre", "serde", "thiserror 1.0.69", diff --git a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol index f14af98ee..2cf2c00b1 100644 --- a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -114,7 +114,10 @@ interface ICiphernodeRegistry { /// @notice This event MUST be emitted when a committee is selected for an E3. /// @param e3Id ID of the E3 for which the committee was selected. - /// @param publicKey Public key of the committee. + /// @param publicKey Serialized public-key transport hint. Consumers MUST + /// deserialize it and recompute `pkCommitment` before using it for + /// encryption. The commitment is C5-proven when proof aggregation is + /// enabled and trusted by the development aggregator otherwise. /// @param pkCommitment Hash-based aggregated PK commitment for the committee. /// @param proof DkgAggregator (EVM) proof bytes verified prior to publication, /// or empty bytes when proof aggregation is disabled for the E3. From f1d46f8366414cf1d7131eed838bbd59dfdd4a2a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 21:24:52 +0500 Subject: [PATCH 16/52] fix(contracts): prevent decryption proof replay [C-03] --- agent/flow-trace/00_INDEX.md | 1 + agent/flow-trace/04_DKG_AND_COMPUTATION.md | 8 +++- .../interfaces/IInterfold.sol/IInterfold.json | 13 +++++- .../contracts/Interfold.sol | 12 +++++- .../contracts/interfaces/IInterfold.sol | 3 ++ .../contracts/lib/InterfoldPricing.sol | 41 ++++++++++++++++++- .../test/Pricing/DustRotation.spec.ts | 3 +- .../Pricing/PullPaymentsAndAllowlist.spec.ts | 6 ++- 8 files changed, 79 insertions(+), 8 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index e95a95225..deb26215c 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -219,3 +219,4 @@ _Found during source-code cross-referencing of these trace documents._ | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Predeploy blocker | `proofAggregationEnabled=false` intentionally retains the faster unverified development path. The flag and both bypasses must be removed before any production deployment; C-02 is not closed by the current code. | | 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | +| 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | `Interfold` canonicalizes each `(rawProof, publicInputs)` payload into a single-use nullifier before verification. A successfully consumed proof cannot complete another E3, and equivalent ABI encodings share the same nullifier; failed verification rolls all state back atomically. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 6bda56ff3..9e7b0cf95 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -909,8 +909,12 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ 1. require(stage == CiphertextReady) │ │ │ 2. require(now <= decryptionDeadline) │ │ │ 3. When proofAggregationEnabled: │ - │ │ require(proof.length > 0), then call │ - │ │ decryptionVerifier.verify( │ + │ │ require(proof.length > 0), then canonically │ + │ │ decode (rawProof, publicInputs), │ + │ │ derive a proof nullifier, require it unused, │ + │ │ and consume it (equivalent ABI encodings map │ + │ │ to the same nullifier) │ + │ │ and call decryptionVerifier.verify( │ │ │ e3Id, committeeRoot, │ │ │ committeeNodes, ciphertextOutput, │ │ │ committeePublicKey, │ diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 0f465df2e..a846b2992 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -111,6 +111,17 @@ "name": "CommitteeSizeTooSmall", "type": "error" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proofNullifier", + "type": "bytes32" + } + ], + "name": "DecryptionProofAlreadyConsumed", + "type": "error" + }, { "inputs": [ { @@ -2453,5 +2464,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-21aba0cf843b2f04de4551ed3d28e7d82f2fe707" + "buildInfoId": "solc-0_8_28-f1477aba8252a554659a14e8a3473dc480e60550" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 87184152a..74a154d20 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -430,6 +430,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { if (e3.proofAggregationEnabled) { require(proof.length > 0, ProofRequired()); + + // Canonicalize the verifier ABI payload before deriving its nullifier. + // `abi.decode` accepts some trailing-byte variants; hashing the decoded + // tuple makes every equivalent encoding consume the same one-shot key. _verifyPlaintext(e3Id, keccak256(plaintextOutput), proof); success = true; } else { @@ -446,7 +450,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { uint256 e3Id, bytes32 plaintextHash, bytes calldata proof - ) internal view { + ) internal { E3 storage e3 = e3s[e3Id]; InterfoldPricing.verifyPlaintext( address(e3.decryptionVerifier), @@ -1199,10 +1203,14 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { interfaceId == 0x01ffc9a7; // IERC165.supportsInterface selector } + /// @dev Canonical decryption-proof nullifiers consumed by successful E3s. + mapping(bytes32 proofNullifier => bool consumed) + private _consumedDecryptionProofs; + /// @dev Reserved storage slots for future upgrades. Adding new state /// variables in derived versions of this contract must reduce this /// array's length accordingly to preserve storage layout compatibility /// across upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[49] private __gap; + uint256[50] private __gap; } diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index 30c4eca0a..865baf1e9 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -483,6 +483,9 @@ interface IInterfold { /// @notice The bounded request was mined after its requester-authorized deadline. error RequestExpired(uint256 requestDeadline, uint256 currentTimestamp); + /// @notice This canonical decryption proof payload already completed another E3. + error DecryptionProofAlreadyConsumed(bytes32 proofNullifier); + /// @notice Caller has no balance to claim for the given E3 / treasury / token. error NothingToClaim(); diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 5fe3bbbcd..a59333967 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -28,6 +28,9 @@ library InterfoldPricing { uint16 internal constant MAX_PROTOCOL_SHARE_BPS = 5_000; uint16 internal constant MAX_MARGIN_BPS = 5_000; uint32 internal constant MAX_COMMITTEE_SIZE = 256; + /// @dev Interfold `_consumedDecryptionProofs` mapping slot. Pinned by the + /// storage-layout validator alongside this external library. + uint256 internal constant CONSUMED_DECRYPTION_PROOFS_SLOT = 38; event ParamSetRegistered(uint8 paramSet, bytes encodedParams); event ParamSetUpdated( @@ -81,7 +84,8 @@ library InterfoldPricing { bytes32 committeePublicKey, bytes32 plaintextHash, bytes calldata proof - ) external view { + ) external { + _consumeDecryptionProof(proof); IDecryptionVerifier(verifierAddress).verify( e3Id, ICiphernodeRegistry(registryAddress).rootAt(e3Id), @@ -94,6 +98,41 @@ library InterfoldPricing { ); } + /// @notice Canonical single-use identifier for a decryption proof payload. + /// @dev Decoding and re-encoding maps ABI payloads with equivalent trailing + /// bytes to one nullifier, preventing representation-based replay. + function _consumeDecryptionProof(bytes calldata proof) private { + // Preserve compatibility with compact proof formats used by other + // encryption schemes; BFV's ABI tuple is always at least two words. + bytes32 proofNullifier; + if (proof.length < 64) { + proofNullifier = keccak256(proof); + } else { + (bytes memory rawProof, bytes32[] memory publicInputs) = abi.decode( + proof, + (bytes, bytes32[]) + ); + proofNullifier = keccak256(abi.encode(rawProof, publicInputs)); + } + + bytes32 storageKey = keccak256( + abi.encode(proofNullifier, CONSUMED_DECRYPTION_PROOFS_SLOT) + ); + bool consumed; + // solhint-disable-next-line no-inline-assembly + assembly { + consumed := sload(storageKey) + } + require( + !consumed, + IInterfold.DecryptionProofAlreadyConsumed(proofNullifier) + ); + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(storageKey, 1) + } + } + function honestNodes( address registryAddress, uint256 e3Id, diff --git a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts index 24f393fc3..b43202376 100644 --- a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts +++ b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts @@ -149,7 +149,8 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ); await time.increase(inputWindowDuration + 200); await interfold.publishCiphertextOutput(e3Id, data, proof); - await interfold.publishPlaintextOutput(e3Id, data, proof); + const e3Proof = ethers.concat([proof, ethers.toBeHex(e3Id, 32)]); + await interfold.publishPlaintextOutput(e3Id, data, e3Proof); return nodes; }; diff --git a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts index e92a2beb6..ac150a812 100644 --- a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts +++ b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts @@ -199,7 +199,11 @@ describe("Interfold — pull payments + fee-token allow-list", function () { ); await time.increase(inputWindowDuration + 200); await interfold.publishCiphertextOutput(e3Id2, data, proof); - await interfold.publishPlaintextOutput(e3Id2, data, proof); + await interfold.publishPlaintextOutput( + e3Id2, + data, + ethers.concat([proof, ethers.toBeHex(e3Id2, 32)]), + ); const op1Addr = await operator1.getAddress(); const expected = From b124f06ed8960285539b62a264217bccd7cc83ce Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 15 Jul 2026 19:39:55 +0500 Subject: [PATCH 17/52] fix(ci): enforce reviewed storage baselines [H-04] --- .github/workflows/ci.yml | 3 + agent/flow-trace/00_INDEX.md | 7 +- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 21 +- .../InterfoldTicketToken.json | 2 +- .../storage-layouts/BondingRegistry.json | 407 +++++++++ .../CiphernodeRegistryOwnable.json | 518 +++++++++++ .../storage-layouts/E3RefundManager.json | 493 ++++++++++ .../audits/storage-layouts/Interfold.json | 857 ++++++++++++++++++ .../contracts/lib/InterfoldPricing.sol | 4 +- .../test/InterfoldPricingStorageHarness.sol | 51 ++ packages/interfold-contracts/package.json | 9 +- .../scripts/snapshotStorageLayouts.ts | 70 ++ .../scripts/storageLayouts.ts | 413 +++++++++ .../scripts/validateUpgrade.ts | 230 ++--- .../test/Pricing/StorageAnchors.spec.ts | 39 + 18 files changed, 2925 insertions(+), 205 deletions(-) create mode 100644 packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json create mode 100644 packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json create mode 100644 packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json create mode 100644 packages/interfold-contracts/audits/storage-layouts/Interfold.json create mode 100644 packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol create mode 100644 packages/interfold-contracts/scripts/snapshotStorageLayouts.ts create mode 100644 packages/interfold-contracts/scripts/storageLayouts.ts create mode 100644 packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54cdda943..266d2f68e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -379,6 +379,9 @@ jobs: - name: 'Enforce production contract size budget' run: 'pnpm --filter @interfold/contracts size:check' + - name: 'Validate upgrade storage layouts' + run: 'pnpm --filter @interfold/contracts validate:upgrade' + - name: 'Test the contracts and generate the coverage report' run: 'pnpm coverage' diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index deb26215c..23c7f9b6f 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -204,15 +204,16 @@ _Found during source-code cross-referencing of these trace documents._ | 15 | **Local/network event redelivery collision** | Resolved | EventStore now treats the same HLC timestamp plus stable event ID and equal payload as an idempotent duplicate even when transport context differs (`Local` versus `Net`). It retains the first stored context and still fails closed when different payloads claim the same timestamp, preserving collision detection without panicking on sync/resync redelivery. | | 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | | 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | -| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` now caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity, while a lazy sentinel initializes the counter safely for queues created before the upgrade. | -| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` now tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, while the aggregate `hasClaimed` compatibility view reports either role. | +| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity. | +| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, and callers can query either role explicitly. | | 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | | 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | -| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy. Upgraded deployments also consult the live registry count, and the minimum ticket requirement can never be zero. | +| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy, and the minimum ticket requirement can never be zero. | | 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | +| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index c48b9cda2..1f8725237 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1188,5 +1188,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-e13a73dbee9a6b66f3d5ad62b37b50945035d8cf" + "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index bf9049a1d..4c0159d3e 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-e13a73dbee9a6b66f3d5ad62b37b50945035d8cf" + "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index a846b2992..b84d3e475 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2464,5 +2464,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-f1477aba8252a554659a14e8a3473dc480e60550" + "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 8298a2588..4c75763b6 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1063,25 +1063,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "hasOpenLaneBProposal", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -1469,5 +1450,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-b6d88351d9521822c693319be886d5618e91683c" + "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index c83934fff..43141aa1d 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1384,5 +1384,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-4eebd49e068b5e860a0155b6fc1dddcc50e5763e" + "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" } \ No newline at end of file diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json new file mode 100644 index 000000000..a9df7b0be --- /dev/null +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -0,0 +1,407 @@ +{ + "_format": "interfold-storage-layout-v1", + "contract": "BondingRegistry", + "source": "contracts/registry/BondingRegistry.sol", + "baseline": { + "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "compiler": "0.8.28+commit.7893614a", + "evmVersion": "paris", + "optimizerRuns": 1, + "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceSha256": "63dce01cce37e34a48bee91ebcf5d01f62cc85472418e4ac8e08cbce415e6097" + }, + "storage": [ + { + "astId": 25215, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "ticketToken", + "offset": 0, + "slot": "0", + "type": "t_contract(InterfoldTicketToken)35413" + }, + { + "astId": 25219, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseToken", + "offset": 0, + "slot": "1", + "type": "t_contract(IERC20)3710" + }, + { + "astId": 25223, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "registry", + "offset": 0, + "slot": "2", + "type": "t_contract(ICiphernodeRegistry)20485" + }, + { + "astId": 25226, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "slashingManager", + "offset": 0, + "slot": "3", + "type": "t_address" + }, + { + "astId": 25231, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "authorizedDistributors", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 25234, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "authorizedDistributorCount", + "offset": 0, + "slot": "5", + "type": "t_uint256" + }, + { + "astId": 25253, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "slashedFundsTreasury", + "offset": 0, + "slot": "6", + "type": "t_address" + }, + { + "astId": 25256, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "ticketPrice", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 25259, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseRequiredBond", + "offset": 0, + "slot": "8", + "type": "t_uint256" + }, + { + "astId": 25262, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "minTicketBalance", + "offset": 0, + "slot": "9", + "type": "t_uint256" + }, + { + "astId": 25265, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "exitDelay", + "offset": 0, + "slot": "10", + "type": "t_uint64" + }, + { + "astId": 25268, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseActiveBps", + "offset": 0, + "slot": "11", + "type": "t_uint256" + }, + { + "astId": 25271, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "numActiveOperators", + "offset": 0, + "slot": "12", + "type": "t_uint256" + }, + { + "astId": 25289, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "operators", + "offset": 0, + "slot": "13", + "type": "t_mapping(t_address,t_struct(Operator)25283_storage)" + }, + { + "astId": 25292, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "slashedTicketBalance", + "offset": 0, + "slot": "14", + "type": "t_uint256" + }, + { + "astId": 25295, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "slashedLicenseBond", + "offset": 0, + "slot": "15", + "type": "t_uint256" + }, + { + "astId": 25299, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "_exits", + "offset": 0, + "slot": "16", + "type": "t_struct(ExitQueueState)22998_storage" + }, + { + "astId": 25302, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "eligibilityConfigurationLocked", + "offset": 0, + "slot": "21", + "type": "t_bool" + }, + { + "astId": 25305, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "reservedSlashedTicketBalance", + "offset": 0, + "slot": "22", + "type": "t_uint256" + }, + { + "astId": 27530, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "__gap", + "offset": 0, + "slot": "23", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(ExitTranche)22967_storage)dyn_storage": { + "base": "t_struct(ExitTranche)22967_storage", + "encoding": "dynamic_array", + "label": "struct ExitQueueLib.ExitTranche[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(ICiphernodeRegistry)20485": { + "encoding": "inplace", + "label": "contract ICiphernodeRegistry", + "numberOfBytes": "20" + }, + "t_contract(IERC20)3710": { + "encoding": "inplace", + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(InterfoldTicketToken)35413": { + "encoding": "inplace", + "label": "contract InterfoldTicketToken", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_array(t_struct(ExitTranche)22967_storage)dyn_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", + "numberOfBytes": "32", + "value": "t_array(t_struct(ExitTranche)22967_storage)dyn_storage" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_struct(Operator)25283_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct BondingRegistry.Operator)", + "numberOfBytes": "32", + "value": "t_struct(Operator)25283_storage" + }, + "t_mapping(t_address,t_struct(PendingAmounts)22973_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", + "numberOfBytes": "32", + "value": "t_struct(PendingAmounts)22973_storage" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_struct(ExitQueueState)22998_storage": { + "encoding": "inplace", + "label": "struct ExitQueueLib.ExitQueueState", + "members": [ + { + "astId": 22980, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "operatorQueues", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)22967_storage)dyn_storage)" + }, + { + "astId": 22984, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "queueHeadIndexTicket", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 22988, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "queueHeadIndexLicense", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 22993, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "pendingTotals", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_struct(PendingAmounts)22973_storage)" + }, + { + "astId": 22997, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "liveTrancheCount", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_address,t_uint256)" + } + ], + "numberOfBytes": "160" + }, + "t_struct(ExitTranche)22967_storage": { + "encoding": "inplace", + "label": "struct ExitQueueLib.ExitTranche", + "members": [ + { + "astId": 22962, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "unlockTimestamp", + "offset": 0, + "slot": "0", + "type": "t_uint64" + }, + { + "astId": 22964, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "ticketAmount", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 22966, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseAmount", + "offset": 0, + "slot": "2", + "type": "t_uint256" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Operator)25283_storage": { + "encoding": "inplace", + "label": "struct BondingRegistry.Operator", + "members": [ + { + "astId": 25274, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseBond", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 25276, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "exitUnlocksAt", + "offset": 0, + "slot": "1", + "type": "t_uint64" + }, + { + "astId": 25278, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "registered", + "offset": 8, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 25280, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "exitRequested", + "offset": 9, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 25282, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "active", + "offset": 10, + "slot": "1", + "type": "t_bool" + } + ], + "numberOfBytes": "64" + }, + "t_struct(PendingAmounts)22973_storage": { + "encoding": "inplace", + "label": "struct ExitQueueLib.PendingAmounts", + "members": [ + { + "astId": 22970, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "ticketAmount", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 22972, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "licenseAmount", + "offset": 0, + "slot": "1", + "type": "t_uint256" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } +} diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json new file mode 100644 index 000000000..47d6bac9c --- /dev/null +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -0,0 +1,518 @@ +{ + "_format": "interfold-storage-layout-v1", + "contract": "CiphernodeRegistryOwnable", + "source": "contracts/registry/CiphernodeRegistryOwnable.sol", + "baseline": { + "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "compiler": "0.8.28+commit.7893614a", + "evmVersion": "paris", + "optimizerRuns": 1, + "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceSha256": "28aa169c03b248e1373c6cf57df74fcc2a6fe2695e70a3d215df6d5e80ee51a8" + }, + "storage": [ + { + "astId": 27598, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "interfold", + "offset": 0, + "slot": "0", + "type": "t_contract(IInterfold)22027" + }, + { + "astId": 27602, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "bondingRegistry", + "offset": 0, + "slot": "1", + "type": "t_contract(IBondingRegistry)19836" + }, + { + "astId": 27606, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "numCiphernodes", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 27609, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "sortitionSubmissionWindow", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 27629, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "ciphernodes", + "offset": 0, + "slot": "4", + "type": "t_struct(LazyIMTData)12803_storage" + }, + { + "astId": 27634, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "ciphernodeEnabled", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 27639, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "ciphernodeTreeIndex", + "offset": 0, + "slot": "7", + "type": "t_mapping(t_address,t_uint40)" + }, + { + "astId": 27644, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "roots", + "offset": 0, + "slot": "8", + "type": "t_mapping(t_uint256,t_uint256)" + }, + { + "astId": 27649, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "publicKeyHashes", + "offset": 0, + "slot": "9", + "type": "t_mapping(t_uint256,t_bytes32)" + }, + { + "astId": 27655, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "committees", + "offset": 0, + "slot": "10", + "type": "t_mapping(t_uint256,t_struct(Committee)19897_storage)" + }, + { + "astId": 27659, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "slashingManager", + "offset": 0, + "slot": "11", + "type": "t_contract(ISlashingManager)22674" + }, + { + "astId": 27663, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "dkgFoldAttestationVerifier", + "offset": 0, + "slot": "12", + "type": "t_contract(IDkgFoldAttestationVerifier)20554" + }, + { + "astId": 27670, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "pendingDkgFoldAttestationVerifier", + "offset": 0, + "slot": "13", + "type": "t_address" + }, + { + "astId": 27672, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "pendingDkgFoldAttestationVerifierAt", + "offset": 0, + "slot": "14", + "type": "t_uint256" + }, + { + "astId": 27675, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "accusationVoteValidity", + "offset": 0, + "slot": "15", + "type": "t_uint256" + }, + { + "astId": 27686, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "pendingAccusationVoteValidity", + "offset": 0, + "slot": "16", + "type": "t_uint256" + }, + { + "astId": 27688, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "pendingAccusationVoteValidityAt", + "offset": 0, + "slot": "17", + "type": "t_uint256" + }, + { + "astId": 27694, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "dkgPartyIds", + "offset": 0, + "slot": "18", + "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" + }, + { + "astId": 27699, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "dkgSkAggCommits", + "offset": 0, + "slot": "19", + "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" + }, + { + "astId": 27704, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "dkgEsmAggCommits", + "offset": 0, + "slot": "20", + "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" + }, + { + "astId": 27720, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "_committeeDependencies", + "offset": 0, + "slot": "21", + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)27714_storage)" + }, + { + "astId": 30248, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "__gap", + "offset": 0, + "slot": "22", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_bytes32)dyn_storage": { + "base": "t_bytes32", + "encoding": "dynamic_array", + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_array(t_uint256)dyn_storage": { + "base": "t_uint256", + "encoding": "dynamic_array", + "label": "uint256[]", + "numberOfBytes": "32" + }, + "t_array(t_uint32)2_storage": { + "base": "t_uint32", + "encoding": "inplace", + "label": "uint32[2]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IBondingRegistry)19836": { + "encoding": "inplace", + "label": "contract IBondingRegistry", + "numberOfBytes": "20" + }, + "t_contract(IDkgFoldAttestationVerifier)20554": { + "encoding": "inplace", + "label": "contract IDkgFoldAttestationVerifier", + "numberOfBytes": "20" + }, + "t_contract(IInterfold)22027": { + "encoding": "inplace", + "label": "contract IInterfold", + "numberOfBytes": "20" + }, + "t_contract(ISlashingManager)22674": { + "encoding": "inplace", + "label": "contract ISlashingManager", + "numberOfBytes": "20" + }, + "t_enum(CommitteeStage)19860": { + "encoding": "inplace", + "label": "enum ICiphernodeRegistry.CommitteeStage", + "numberOfBytes": "1" + }, + "t_enum(MemberStatus)19854": { + "encoding": "inplace", + "label": "enum ICiphernodeRegistry.MemberStatus", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_enum(MemberStatus)19854)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", + "numberOfBytes": "32", + "value": "t_enum(MemberStatus)19854" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_address,t_uint40)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint40)", + "numberOfBytes": "32", + "value": "t_uint40" + }, + "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => bytes32[])", + "numberOfBytes": "32", + "value": "t_array(t_bytes32)dyn_storage" + }, + "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => uint256[])", + "numberOfBytes": "32", + "value": "t_array(t_uint256)dyn_storage" + }, + "t_mapping(t_uint256,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_uint256,t_struct(Committee)19897_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", + "numberOfBytes": "32", + "value": "t_struct(Committee)19897_storage" + }, + "t_mapping(t_uint256,t_struct(CommitteeDependencies)27714_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", + "numberOfBytes": "32", + "value": "t_struct(CommitteeDependencies)27714_storage" + }, + "t_mapping(t_uint256,t_uint256)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_struct(Committee)19897_storage": { + "encoding": "inplace", + "label": "struct ICiphernodeRegistry.Committee", + "members": [ + { + "astId": 19864, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "stage", + "offset": 0, + "slot": "0", + "type": "t_enum(CommitteeStage)19860" + }, + { + "astId": 19866, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "seed", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 19868, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "requestBlock", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 19870, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "committeeDeadline", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 19872, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "publicKey", + "offset": 0, + "slot": "4", + "type": "t_bytes32" + }, + { + "astId": 19876, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "threshold", + "offset": 0, + "slot": "5", + "type": "t_array(t_uint32)2_storage" + }, + { + "astId": 19879, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "topNodes", + "offset": 0, + "slot": "6", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 19881, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "committeeHash", + "offset": 0, + "slot": "7", + "type": "t_bytes32" + }, + { + "astId": 19885, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "submitted", + "offset": 0, + "slot": "8", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 19889, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "scoreOf", + "offset": 0, + "slot": "9", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 19894, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "memberStatus", + "offset": 0, + "slot": "10", + "type": "t_mapping(t_address,t_enum(MemberStatus)19854)" + }, + { + "astId": 19896, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "activeCount", + "offset": 0, + "slot": "11", + "type": "t_uint256" + } + ], + "numberOfBytes": "384" + }, + "t_struct(CommitteeDependencies)27714_storage": { + "encoding": "inplace", + "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", + "members": [ + { + "astId": 27707, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "interfoldContract", + "offset": 0, + "slot": "0", + "type": "t_contract(IInterfold)22027" + }, + { + "astId": 27710, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "bonding", + "offset": 0, + "slot": "1", + "type": "t_contract(IBondingRegistry)19836" + }, + { + "astId": 27713, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "slashManager", + "offset": 0, + "slot": "2", + "type": "t_contract(ISlashingManager)22674" + } + ], + "numberOfBytes": "96" + }, + "t_struct(LazyIMTData)12803_storage": { + "encoding": "inplace", + "label": "struct LazyIMTData", + "members": [ + { + "astId": 12796, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "maxIndex", + "offset": 0, + "slot": "0", + "type": "t_uint40" + }, + { + "astId": 12798, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "numberOfLeaves", + "offset": 5, + "slot": "0", + "type": "t_uint40" + }, + { + "astId": 12802, + "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", + "label": "elements", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint256,t_uint256)" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint40": { + "encoding": "inplace", + "label": "uint40", + "numberOfBytes": "5" + } + } +} diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json new file mode 100644 index 000000000..4b23eb5b0 --- /dev/null +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -0,0 +1,493 @@ +{ + "_format": "interfold-storage-layout-v1", + "contract": "E3RefundManager", + "source": "contracts/E3RefundManager.sol", + "baseline": { + "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "compiler": "0.8.28+commit.7893614a", + "evmVersion": "paris", + "optimizerRuns": 1, + "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceSha256": "a6ba37835c555d69dc3592a8b47089a2a34e5dfbbe87dfa1b95f1b318875a21b" + }, + "storage": [ + { + "astId": 13997, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "interfold", + "offset": 0, + "slot": "0", + "type": "t_contract(IInterfold)22027" + }, + { + "astId": 14001, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "bondingRegistry", + "offset": 0, + "slot": "1", + "type": "t_contract(IBondingRegistry)19836" + }, + { + "astId": 14004, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "treasury", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 14008, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_workAllocation", + "offset": 0, + "slot": "3", + "type": "t_struct(WorkValueAllocation)20661_storage" + }, + { + "astId": 14014, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_distributions", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)20695_storage)" + }, + { + "astId": 14021, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_requesterClaimed", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" + }, + { + "astId": 14026, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_claimCount", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_uint256,t_uint256)" + }, + { + "astId": 14031, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_honestNodeClaimCount", + "offset": 0, + "slot": "7", + "type": "t_mapping(t_uint256,t_uint256)" + }, + { + "astId": 14036, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_totalHonestNodePaid", + "offset": 0, + "slot": "8", + "type": "t_mapping(t_uint256,t_uint256)" + }, + { + "astId": 14042, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_honestNodes", + "offset": 0, + "slot": "9", + "type": "t_mapping(t_uint256,t_array(t_address)dyn_storage)" + }, + { + "astId": 14050, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_pendingTreasury", + "offset": 0, + "slot": "10", + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + }, + { + "astId": 14057, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_honestNodeClaimed", + "offset": 0, + "slot": "11", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" + }, + { + "astId": 14063, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_e3PolicySnapshots", + "offset": 0, + "slot": "12", + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)20674_storage)" + }, + { + "astId": 14066, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "policyVersion", + "offset": 0, + "slot": "13", + "type": "t_uint64" + }, + { + "astId": 14074, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_pendingSlashedByToken", + "offset": 0, + "slot": "14", + "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_uint256))" + }, + { + "astId": 14084, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_pendingSlashedClaims", + "offset": 0, + "slot": "15", + "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256)))" + }, + { + "astId": 14092, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_pendingTrackedTreasury", + "offset": 0, + "slot": "16", + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + }, + { + "astId": 14098, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_tokenLiability", + "offset": 0, + "slot": "17", + "type": "t_mapping(t_contract(IERC20)3710,t_uint256)" + }, + { + "astId": 14103, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "_successSettlementReady", + "offset": 0, + "slot": "18", + "type": "t_mapping(t_uint256,t_bool)" + }, + { + "astId": 16362, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "__gap", + "offset": 0, + "slot": "19", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBondingRegistry)19836": { + "encoding": "inplace", + "label": "contract IBondingRegistry", + "numberOfBytes": "20" + }, + "t_contract(IERC20)3710": { + "encoding": "inplace", + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IInterfold)22027": { + "encoding": "inplace", + "label": "contract IInterfold", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(contract IERC20 => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_contract(IERC20)3710", + "label": "mapping(contract IERC20 => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_contract(IERC20)3710,t_uint256)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3710", + "label": "mapping(contract IERC20 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint256,t_array(t_address)dyn_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => address[])", + "numberOfBytes": "32", + "value": "t_array(t_address)dyn_storage" + }, + "t_mapping(t_uint256,t_bool)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_bool))": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => mapping(address => bool))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_bool)" + }, + "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256)))": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => mapping(contract IERC20 => mapping(address => uint256)))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256))" + }, + "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => mapping(contract IERC20 => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + }, + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)20674_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", + "numberOfBytes": "32", + "value": "t_struct(E3PolicySnapshot)20674_storage" + }, + "t_mapping(t_uint256,t_struct(RefundDistribution)20695_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", + "numberOfBytes": "32", + "value": "t_struct(RefundDistribution)20695_storage" + }, + "t_mapping(t_uint256,t_uint256)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_struct(E3PolicySnapshot)20674_storage": { + "encoding": "inplace", + "label": "struct IE3RefundManager.E3PolicySnapshot", + "members": [ + { + "astId": 20665, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "allocation", + "offset": 0, + "slot": "0", + "type": "t_struct(WorkValueAllocation)20661_storage" + }, + { + "astId": 20667, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "treasury", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 20669, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "interfold", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 20671, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "version", + "offset": 20, + "slot": "2", + "type": "t_uint64" + }, + { + "astId": 20673, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "initialized", + "offset": 28, + "slot": "2", + "type": "t_bool" + } + ], + "numberOfBytes": "96" + }, + "t_struct(RefundDistribution)20695_storage": { + "encoding": "inplace", + "label": "struct IE3RefundManager.RefundDistribution", + "members": [ + { + "astId": 20677, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "requesterAmount", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 20679, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "honestNodeAmount", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 20681, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "protocolAmount", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 20683, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "totalSlashed", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 20685, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "honestNodeCount", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 20687, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "calculated", + "offset": 0, + "slot": "5", + "type": "t_bool" + }, + { + "astId": 20690, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "feeToken", + "offset": 1, + "slot": "5", + "type": "t_contract(IERC20)3710" + }, + { + "astId": 20692, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "originalPayment", + "offset": 0, + "slot": "6", + "type": "t_uint256" + }, + { + "astId": 20694, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "perNodeAmount", + "offset": 0, + "slot": "7", + "type": "t_uint256" + } + ], + "numberOfBytes": "256" + }, + "t_struct(WorkValueAllocation)20661_storage": { + "encoding": "inplace", + "label": "struct IE3RefundManager.WorkValueAllocation", + "members": [ + { + "astId": 20652, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "committeeFormationBps", + "offset": 0, + "slot": "0", + "type": "t_uint16" + }, + { + "astId": 20654, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "dkgBps", + "offset": 2, + "slot": "0", + "type": "t_uint16" + }, + { + "astId": 20656, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "decryptionBps", + "offset": 4, + "slot": "0", + "type": "t_uint16" + }, + { + "astId": 20658, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "protocolBps", + "offset": 6, + "slot": "0", + "type": "t_uint16" + }, + { + "astId": 20660, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "successSlashedNodeBps", + "offset": 8, + "slot": "0", + "type": "t_uint16" + } + ], + "numberOfBytes": "32" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } +} diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json new file mode 100644 index 000000000..12d0805f4 --- /dev/null +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -0,0 +1,857 @@ +{ + "_format": "interfold-storage-layout-v1", + "contract": "Interfold", + "source": "contracts/Interfold.sol", + "baseline": { + "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "compiler": "0.8.28+commit.7893614a", + "evmVersion": "paris", + "optimizerRuns": 1, + "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceSha256": "a805ec4ace920e3efe826b5ed8ca2b667a2764213f3f3bcd56bba6d0a78b86a4" + }, + "storage": [ + { + "astId": 16425, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "ciphernodeRegistry", + "offset": 0, + "slot": "0", + "type": "t_contract(ICiphernodeRegistry)20485" + }, + { + "astId": 16429, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "bondingRegistry", + "offset": 0, + "slot": "1", + "type": "t_contract(IBondingRegistry)19836" + }, + { + "astId": 16433, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "e3RefundManager", + "offset": 0, + "slot": "2", + "type": "t_contract(IE3RefundManager)21075" + }, + { + "astId": 16437, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "slashingManager", + "offset": 0, + "slot": "3", + "type": "t_contract(ISlashingManager)22674" + }, + { + "astId": 16441, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "feeToken", + "offset": 0, + "slot": "4", + "type": "t_contract(IERC20)3710" + }, + { + "astId": 16444, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "maxDuration", + "offset": 0, + "slot": "5", + "type": "t_uint256" + }, + { + "astId": 16447, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "nexte3Id", + "offset": 0, + "slot": "6", + "type": "t_uint256" + }, + { + "astId": 16453, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "e3Programs", + "offset": 0, + "slot": "7", + "type": "t_mapping(t_contract(IE3Program)20642,t_bool)" + }, + { + "astId": 16459, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "e3s", + "offset": 0, + "slot": "8", + "type": "t_mapping(t_uint256,t_struct(E3)20602_storage)" + }, + { + "astId": 16465, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptionVerifiers", + "offset": 0, + "slot": "9", + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)20527)" + }, + { + "astId": 16471, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "pkVerifiers", + "offset": 0, + "slot": "10", + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)22065)" + }, + { + "astId": 16476, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "paramSetRegistry", + "offset": 0, + "slot": "11", + "type": "t_mapping(t_uint8,t_bytes_storage)" + }, + { + "astId": 16481, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "e3Payments", + "offset": 0, + "slot": "12", + "type": "t_mapping(t_uint256,t_uint256)" + }, + { + "astId": 16487, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3Stages", + "offset": 0, + "slot": "13", + "type": "t_mapping(t_uint256,t_enum(E3Stage)21104)" + }, + { + "astId": 16493, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3Deadlines", + "offset": 0, + "slot": "14", + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)21136_storage)" + }, + { + "astId": 16499, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3FailureReasons", + "offset": 0, + "slot": "15", + "type": "t_mapping(t_uint256,t_enum(FailureReason)21120)" + }, + { + "astId": 16504, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3Requesters", + "offset": 0, + "slot": "16", + "type": "t_mapping(t_uint256,t_address)" + }, + { + "astId": 16510, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3FeeTokens", + "offset": 0, + "slot": "17", + "type": "t_mapping(t_uint256,t_contract(IERC20)3710)" + }, + { + "astId": 16518, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "committeeThresholds", + "offset": 0, + "slot": "18", + "type": "t_mapping(t_enum(CommitteeSize)21095,t_array(t_uint32)2_storage)" + }, + { + "astId": 16523, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3ProtocolShareBps", + "offset": 0, + "slot": "19", + "type": "t_mapping(t_uint256,t_uint16)" + }, + { + "astId": 16528, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3ProtocolTreasury", + "offset": 0, + "slot": "20", + "type": "t_mapping(t_uint256,t_address)" + }, + { + "astId": 16532, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_timeoutConfig", + "offset": 0, + "slot": "21", + "type": "t_struct(E3TimeoutConfig)21128_storage" + }, + { + "astId": 16536, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_pricingConfig", + "offset": 0, + "slot": "24", + "type": "t_struct(PricingConfig)21168_storage" + }, + { + "astId": 16546, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_feeTokenAllowed", + "offset": 0, + "slot": "33", + "type": "t_mapping(t_contract(IERC20)3710,t_bool)" + }, + { + "astId": 16553, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_pendingRewards", + "offset": 0, + "slot": "34", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))" + }, + { + "astId": 16561, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_pendingTreasury", + "offset": 0, + "slot": "35", + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + }, + { + "astId": 16574, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "markFailedGracePeriod", + "offset": 0, + "slot": "36", + "type": "t_uint256" + }, + { + "astId": 16580, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_e3Dependencies", + "offset": 0, + "slot": "37", + "type": "t_mapping(t_uint256,t_struct(E3Dependencies)16571_storage)" + }, + { + "astId": 19308, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "_consumedDecryptionProofs", + "offset": 0, + "slot": "38", + "type": "t_mapping(t_bytes32,t_bool)" + }, + { + "astId": 19313, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "__gap", + "offset": 0, + "slot": "39", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)2_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[2]", + "numberOfBytes": "64" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_array(t_uint32)2_storage": { + "base": "t_uint32", + "encoding": "inplace", + "label": "uint32[2]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_contract(IBondingRegistry)19836": { + "encoding": "inplace", + "label": "contract IBondingRegistry", + "numberOfBytes": "20" + }, + "t_contract(ICiphernodeRegistry)20485": { + "encoding": "inplace", + "label": "contract ICiphernodeRegistry", + "numberOfBytes": "20" + }, + "t_contract(IDecryptionVerifier)20527": { + "encoding": "inplace", + "label": "contract IDecryptionVerifier", + "numberOfBytes": "20" + }, + "t_contract(IE3Program)20642": { + "encoding": "inplace", + "label": "contract IE3Program", + "numberOfBytes": "20" + }, + "t_contract(IE3RefundManager)21075": { + "encoding": "inplace", + "label": "contract IE3RefundManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)3710": { + "encoding": "inplace", + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IPkVerifier)22065": { + "encoding": "inplace", + "label": "contract IPkVerifier", + "numberOfBytes": "20" + }, + "t_contract(ISlashingManager)22674": { + "encoding": "inplace", + "label": "contract ISlashingManager", + "numberOfBytes": "20" + }, + "t_enum(CommitteeSize)21095": { + "encoding": "inplace", + "label": "enum IInterfold.CommitteeSize", + "numberOfBytes": "1" + }, + "t_enum(E3Stage)21104": { + "encoding": "inplace", + "label": "enum IInterfold.E3Stage", + "numberOfBytes": "1" + }, + "t_enum(FailureReason)21120": { + "encoding": "inplace", + "label": "enum IInterfold.FailureReason", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(contract IERC20 => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_bytes32,t_bool)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)20527)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => contract IDecryptionVerifier)", + "numberOfBytes": "32", + "value": "t_contract(IDecryptionVerifier)20527" + }, + "t_mapping(t_bytes32,t_contract(IPkVerifier)22065)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => contract IPkVerifier)", + "numberOfBytes": "32", + "value": "t_contract(IPkVerifier)22065" + }, + "t_mapping(t_contract(IE3Program)20642,t_bool)": { + "encoding": "mapping", + "key": "t_contract(IE3Program)20642", + "label": "mapping(contract IE3Program => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_contract(IERC20)3710,t_bool)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3710", + "label": "mapping(contract IERC20 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_contract(IERC20)3710,t_uint256)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3710", + "label": "mapping(contract IERC20 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_enum(CommitteeSize)21095,t_array(t_uint32)2_storage)": { + "encoding": "mapping", + "key": "t_enum(CommitteeSize)21095", + "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", + "numberOfBytes": "32", + "value": "t_array(t_uint32)2_storage" + }, + "t_mapping(t_uint256,t_address)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => address)", + "numberOfBytes": "32", + "value": "t_address" + }, + "t_mapping(t_uint256,t_contract(IERC20)3710)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => contract IERC20)", + "numberOfBytes": "32", + "value": "t_contract(IERC20)3710" + }, + "t_mapping(t_uint256,t_enum(E3Stage)21104)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => enum IInterfold.E3Stage)", + "numberOfBytes": "32", + "value": "t_enum(E3Stage)21104" + }, + "t_mapping(t_uint256,t_enum(FailureReason)21120)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => enum IInterfold.FailureReason)", + "numberOfBytes": "32", + "value": "t_enum(FailureReason)21120" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_uint256,t_struct(E3)20602_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct E3)", + "numberOfBytes": "32", + "value": "t_struct(E3)20602_storage" + }, + "t_mapping(t_uint256,t_struct(E3Deadlines)21136_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", + "numberOfBytes": "32", + "value": "t_struct(E3Deadlines)21136_storage" + }, + "t_mapping(t_uint256,t_struct(E3Dependencies)16571_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct Interfold.E3Dependencies)", + "numberOfBytes": "32", + "value": "t_struct(E3Dependencies)16571_storage" + }, + "t_mapping(t_uint256,t_uint16)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => uint16)", + "numberOfBytes": "32", + "value": "t_uint16" + }, + "t_mapping(t_uint256,t_uint256)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint8,t_bytes_storage)": { + "encoding": "mapping", + "key": "t_uint8", + "label": "mapping(uint8 => bytes)", + "numberOfBytes": "32", + "value": "t_bytes_storage" + }, + "t_struct(E3)20602_storage": { + "encoding": "inplace", + "label": "struct E3", + "members": [ + { + "astId": 20567, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "seed", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 20570, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "committeeSize", + "offset": 0, + "slot": "1", + "type": "t_enum(CommitteeSize)21095" + }, + { + "astId": 20572, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "requestBlock", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 20576, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "inputWindow", + "offset": 0, + "slot": "3", + "type": "t_array(t_uint256)2_storage" + }, + { + "astId": 20578, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "encryptionSchemeId", + "offset": 0, + "slot": "5", + "type": "t_bytes32" + }, + { + "astId": 20581, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "e3Program", + "offset": 0, + "slot": "6", + "type": "t_contract(IE3Program)20642" + }, + { + "astId": 20583, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "paramSet", + "offset": 20, + "slot": "6", + "type": "t_uint8" + }, + { + "astId": 20585, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "customParams", + "offset": 0, + "slot": "7", + "type": "t_bytes_storage" + }, + { + "astId": 20588, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptionVerifier", + "offset": 0, + "slot": "8", + "type": "t_contract(IDecryptionVerifier)20527" + }, + { + "astId": 20591, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "pkVerifier", + "offset": 0, + "slot": "9", + "type": "t_contract(IPkVerifier)22065" + }, + { + "astId": 20593, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "committeePublicKey", + "offset": 0, + "slot": "10", + "type": "t_bytes32" + }, + { + "astId": 20595, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "ciphertextOutput", + "offset": 0, + "slot": "11", + "type": "t_bytes32" + }, + { + "astId": 20597, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "plaintextOutput", + "offset": 0, + "slot": "12", + "type": "t_bytes_storage" + }, + { + "astId": 20599, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "requester", + "offset": 0, + "slot": "13", + "type": "t_address" + }, + { + "astId": 20601, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "proofAggregationEnabled", + "offset": 20, + "slot": "13", + "type": "t_bool" + } + ], + "numberOfBytes": "448" + }, + "t_struct(E3Deadlines)21136_storage": { + "encoding": "inplace", + "label": "struct IInterfold.E3Deadlines", + "members": [ + { + "astId": 21131, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "dkgDeadline", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 21133, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "computeDeadline", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 21135, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptionDeadline", + "offset": 0, + "slot": "2", + "type": "t_uint256" + } + ], + "numberOfBytes": "96" + }, + "t_struct(E3Dependencies)16571_storage": { + "encoding": "inplace", + "label": "struct Interfold.E3Dependencies", + "members": [ + { + "astId": 16564, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "registry", + "offset": 0, + "slot": "0", + "type": "t_contract(ICiphernodeRegistry)20485" + }, + { + "astId": 16567, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "refundManager", + "offset": 0, + "slot": "1", + "type": "t_contract(IE3RefundManager)21075" + }, + { + "astId": 16570, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "slashManager", + "offset": 0, + "slot": "2", + "type": "t_contract(ISlashingManager)22674" + } + ], + "numberOfBytes": "96" + }, + "t_struct(E3TimeoutConfig)21128_storage": { + "encoding": "inplace", + "label": "struct IInterfold.E3TimeoutConfig", + "members": [ + { + "astId": 21123, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "dkgWindow", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 21125, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "computeWindow", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 21127, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptionWindow", + "offset": 0, + "slot": "2", + "type": "t_uint256" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PricingConfig)21168_storage": { + "encoding": "inplace", + "label": "struct IInterfold.PricingConfig", + "members": [ + { + "astId": 21139, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "keyGenFixedPerNode", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 21141, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "keyGenPerEncryptionProof", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 21143, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "coordinationPerPair", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 21145, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "availabilityPerNodePerSec", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 21147, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptionPerNode", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 21149, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "publicationBase", + "offset": 0, + "slot": "5", + "type": "t_uint256" + }, + { + "astId": 21151, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "verificationPerProof", + "offset": 0, + "slot": "6", + "type": "t_uint256" + }, + { + "astId": 21153, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "protocolTreasury", + "offset": 0, + "slot": "7", + "type": "t_address" + }, + { + "astId": 21155, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "marginBps", + "offset": 20, + "slot": "7", + "type": "t_uint16" + }, + { + "astId": 21157, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "protocolShareBps", + "offset": 22, + "slot": "7", + "type": "t_uint16" + }, + { + "astId": 21159, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "dkgUtilizationBps", + "offset": 24, + "slot": "7", + "type": "t_uint16" + }, + { + "astId": 21161, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "computeUtilizationBps", + "offset": 26, + "slot": "7", + "type": "t_uint16" + }, + { + "astId": 21163, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "decryptUtilizationBps", + "offset": 28, + "slot": "7", + "type": "t_uint16" + }, + { + "astId": 21165, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "minCommitteeSize", + "offset": 0, + "slot": "8", + "type": "t_uint32" + }, + { + "astId": 21167, + "contract": "project/contracts/Interfold.sol:Interfold", + "label": "minThreshold", + "offset": 4, + "slot": "8", + "type": "t_uint32" + } + ], + "numberOfBytes": "288" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "encoding": "inplace", + "label": "uint8", + "numberOfBytes": "1" + } + } +} diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index a59333967..8de83bf8f 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -176,7 +176,7 @@ library InterfoldPricing { /// computeUtilizationBps(2) | decryptUtilizationBps(2) } /// 32: packed { minCommitteeSize(4) | minThreshold(4) } /// The contract storage layout snapshot in - /// `audits/storage-layouts/Interfold-v1.json` MUST keep these + /// `audits/storage-layouts/Interfold.json` MUST keep these /// slots stable; any storage reordering requires updating the /// constants below. function applyDefaultPricingConfig() external { @@ -201,7 +201,7 @@ library InterfoldPricing { sstore(29, 1000000) // publicationBase = 1.00 USDC sstore(30, 5000) // verificationPerProof = 0.005 USDC sstore(31, slot31) - // slot 32 (minCommitteeSize | minThreshold) stays zero. + sstore(32, 0) // minCommitteeSize | minThreshold } } diff --git a/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol b/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol new file mode 100644 index 000000000..d7bb1a2a6 --- /dev/null +++ b/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.28; + +import { IInterfold } from "../interfaces/IInterfold.sol"; +import { InterfoldPricing } from "../lib/InterfoldPricing.sol"; + +/// @dev Pins sentinels immediately before and after the pricing struct so the +/// assembly-backed initializer can be tested against its documented slots. +contract InterfoldPricingStorageHarness { + uint256[23] private _prefix; + bytes32 public leftSentinel; + IInterfold.PricingConfig private _pricingConfig; + bytes32 public rightSentinel; + + constructor(bytes32 left, bytes32 right) { + leftSentinel = left; + rightSentinel = right; + } + + function dirtyPricing() external { + _pricingConfig = IInterfold.PricingConfig({ + keyGenFixedPerNode: type(uint256).max, + keyGenPerEncryptionProof: type(uint256).max, + coordinationPerPair: type(uint256).max, + availabilityPerNodePerSec: type(uint256).max, + decryptionPerNode: type(uint256).max, + publicationBase: type(uint256).max, + verificationPerProof: type(uint256).max, + protocolTreasury: address(type(uint160).max), + marginBps: type(uint16).max, + protocolShareBps: type(uint16).max, + dkgUtilizationBps: type(uint16).max, + computeUtilizationBps: type(uint16).max, + decryptUtilizationBps: type(uint16).max, + minCommitteeSize: type(uint32).max, + minThreshold: type(uint32).max + }); + } + + function applyDefaultPricingConfig() external { + InterfoldPricing.applyDefaultPricingConfig(); + } + + function getPricingConfig() + external + view + returns (IInterfold.PricingConfig memory) + { + return _pricingConfig; + } +} diff --git a/packages/interfold-contracts/package.json b/packages/interfold-contracts/package.json index 0beb37e06..e53a4da2c 100644 --- a/packages/interfold-contracts/package.json +++ b/packages/interfold-contracts/package.json @@ -162,11 +162,12 @@ "configure:slashing-policies": "hardhat run scripts/runConfigureSlashingPolicies.ts --network localhost", "deploy:verifiers": "hardhat run scripts/deployVerifiers.ts", "deploy:faucet": "hardhat run scripts/deployFaucet.ts --network sepolia", - "upgrade:interfold": "hardhat run scripts/upgrade/interfold.ts", - "upgrade:bondingRegistry": "hardhat run scripts/upgrade/bondingRegistry.ts", + "upgrade:interfold": "pnpm validate:upgrade && hardhat run scripts/upgrade/interfold.ts", + "upgrade:bondingRegistry": "pnpm validate:upgrade && hardhat run scripts/upgrade/bondingRegistry.ts", "validate:upgrade": "hardhat run scripts/validateUpgrade.ts", - "upgrade:ciphernodeRegistryOwnable": "hardhat run scripts/upgrade/ciphernodeRegistryOwnable.ts", - "upgrade:e3RefundManager": "hardhat run scripts/upgrade/e3RefundManager.ts", + "snapshot:storage-layouts": "tsx scripts/snapshotStorageLayouts.ts", + "upgrade:ciphernodeRegistryOwnable": "pnpm validate:upgrade && hardhat run scripts/upgrade/ciphernodeRegistryOwnable.ts", + "upgrade:e3RefundManager": "pnpm validate:upgrade && hardhat run scripts/upgrade/e3RefundManager.ts", "e3:activate": "hardhat e3:activate", "e3:enable": "hardhat interfold:enableE3", "ciphernode:add": "hardhat ciphernode:add", diff --git a/packages/interfold-contracts/scripts/snapshotStorageLayouts.ts b/packages/interfold-contracts/scripts/snapshotStorageLayouts.ts new file mode 100644 index 000000000..480a19f27 --- /dev/null +++ b/packages/interfold-contracts/scripts/snapshotStorageLayouts.ts @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// Explicit maintainer-only baseline generator. The caller must identify the +// reviewed production build-info and the exact source commit it represents. +import { execFileSync } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; + +import { + PACKAGE_DIR, + SNAPSHOT_DIR, + type StorageSnapshot, + UPGRADEABLE_CONTRACTS, + loadLayoutFromBuildInfo, + sha256, +} from "./storageLayouts"; + +function argument(name: string): string { + const index = process.argv.indexOf(name); + const value = index === -1 ? undefined : process.argv[index + 1]; + if (!value || value.startsWith("--")) { + throw new Error(`Missing required ${name} argument.`); + } + return value; +} + +async function main(): Promise { + const outputPath = path.resolve(argument("--build-info")); + const sourceCommit = argument("--source-commit"); + if (!/^[0-9a-f]{40}$/i.test(sourceCommit)) { + throw new Error("--source-commit must be a full 40-character Git SHA."); + } + + fs.mkdirSync(SNAPSHOT_DIR, { recursive: true }); + for (const { source, contract } of UPGRADEABLE_CONTRACTS) { + const located = loadLayoutFromBuildInfo(outputPath, source, contract); + const committedSource = execFileSync( + "git", + ["show", `${sourceCommit}:packages/interfold-contracts/${source}`], + { cwd: PACKAGE_DIR, encoding: "utf8" }, + ); + if (sha256(committedSource) !== sha256(located.sourceContent)) { + throw new Error( + `${source}:${contract} in build-info does not match ${sourceCommit}.`, + ); + } + const snapshot: StorageSnapshot = { + _format: "interfold-storage-layout-v1", + contract, + source, + baseline: { + buildInfoId: located.buildInfoId, + compiler: located.compiler, + evmVersion: located.evmVersion, + optimizerRuns: located.optimizerRuns, + sourceCommit, + sourceSha256: sha256(located.sourceContent), + }, + storage: located.layout.storage, + types: located.layout.types, + }; + const snapshotPath = path.join(SNAPSHOT_DIR, `${contract}.json`); + fs.writeFileSync(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`); + console.log(` * wrote ${snapshotPath}`); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/packages/interfold-contracts/scripts/storageLayouts.ts b/packages/interfold-contracts/scripts/storageLayouts.ts new file mode 100644 index 000000000..0d5de9ffb --- /dev/null +++ b/packages/interfold-contracts/scripts/storageLayouts.ts @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: LGPL-3.0-only +import { createHash } from "crypto"; +import * as fs from "fs"; +import * as path from "path"; +import { fileURLToPath } from "url"; + +export interface StorageVar { + astId: number; + contract: string; + label: string; + offset: number; + slot: string; + type: string; +} + +export interface StorageType { + base?: string; + encoding: string; + key?: string; + label: string; + members?: StorageVar[]; + numberOfBytes: string; + value?: string; +} + +export interface StorageLayout { + storage: StorageVar[]; + types: Record; +} + +export interface StorageSnapshot extends StorageLayout { + _format: "interfold-storage-layout-v1"; + baseline: { + buildInfoId: string; + compiler: string; + evmVersion: string; + optimizerRuns: number; + sourceCommit: string; + sourceSha256: string; + }; + contract: string; + source: string; +} + +export const UPGRADEABLE_CONTRACTS = [ + { source: "contracts/Interfold.sol", contract: "Interfold" }, + { + source: "contracts/registry/CiphernodeRegistryOwnable.sol", + contract: "CiphernodeRegistryOwnable", + }, + { + source: "contracts/registry/BondingRegistry.sol", + contract: "BondingRegistry", + }, + { + source: "contracts/E3RefundManager.sol", + contract: "E3RefundManager", + }, +] as const; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +export const PACKAGE_DIR = path.resolve(SCRIPT_DIR, ".."); +export const SNAPSHOT_DIR = path.join(PACKAGE_DIR, "audits/storage-layouts"); +export const BUILD_INFO_DIR = path.join(PACKAGE_DIR, "artifacts/build-info"); + +interface BuildInfoInput { + id: string; + input: { + settings: { + evmVersion?: string; + optimizer?: { runs?: number }; + }; + sources: Record; + }; + solcLongVersion?: string; + solcVersion: string; +} + +interface BuildInfoOutput { + output: { + contracts: Record< + string, + Record + >; + }; +} + +export interface LocatedLayout { + buildInfoId: string; + compiler: string; + evmVersion: string; + layout: StorageLayout; + optimizerRuns: number; + sourceContent: string; + sourceKey: string; +} + +function pairedInputPath(outputPath: string): string { + if (!outputPath.endsWith(".output.json")) { + throw new Error(`Expected a *.output.json build-info path: ${outputPath}`); + } + return outputPath.replace(/\.output\.json$/, ".json"); +} + +function sourceKeys(source: string): string[] { + return [source, `project/${source}`]; +} + +export function loadLayoutFromBuildInfo( + outputPath: string, + source: string, + contract: string, +): LocatedLayout { + const inputPath = pairedInputPath(outputPath); + if (!fs.existsSync(inputPath)) { + throw new Error(`Paired build-info input is missing: ${inputPath}`); + } + + const input = JSON.parse( + fs.readFileSync(inputPath, "utf8"), + ) as BuildInfoInput; + const output = JSON.parse( + fs.readFileSync(outputPath, "utf8"), + ) as BuildInfoOutput; + + for (const sourceKey of sourceKeys(source)) { + const layout = + output.output.contracts?.[sourceKey]?.[contract]?.storageLayout; + const sourceContent = input.input.sources?.[sourceKey]?.content; + if (layout && sourceContent !== undefined) { + return { + buildInfoId: input.id, + compiler: input.solcLongVersion ?? input.solcVersion, + evmVersion: input.input.settings.evmVersion ?? "unknown", + layout, + optimizerRuns: input.input.settings.optimizer?.runs ?? 0, + sourceContent, + sourceKey, + }; + } + } + + throw new Error(`${source}:${contract} is not present in ${outputPath}.`); +} + +export function findCurrentLayout( + source: string, + contract: string, +): LocatedLayout { + if (!fs.existsSync(BUILD_INFO_DIR)) { + throw new Error(`No build-info directory. Run Hardhat compile first.`); + } + + const currentSource = fs.readFileSync(path.join(PACKAGE_DIR, source), "utf8"); + const outputs = fs + .readdirSync(BUILD_INFO_DIR) + .filter((name) => name.endsWith(".output.json")) + .map((name) => path.join(BUILD_INFO_DIR, name)) + .sort( + (left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs, + ); + + for (const outputPath of outputs) { + try { + const located = loadLayoutFromBuildInfo(outputPath, source, contract); + if (located.sourceContent === currentSource) return located; + } catch { + // Incremental Hardhat build-info files contain only their compilation job. + } + } + + throw new Error( + `No build-info storage layout matches the current ${source}:${contract}. ` + + `Run \`pnpm compile:contracts --force\` and retry.`, + ); +} + +export function sha256(content: string): string { + return createHash("sha256").update(content).digest("hex"); +} + +function compareType( + contract: string, + pathLabel: string, + previous: StorageLayout, + previousTypeId: string, + current: StorageLayout, + currentTypeId: string, + errors: string[], + visited: Set, +): void { + const visitKey = `${previousTypeId}:${currentTypeId}`; + if (visited.has(visitKey)) return; + visited.add(visitKey); + + const before = previous.types[previousTypeId]; + const after = current.types[currentTypeId]; + if (!before || !after) { + errors.push( + `${contract}: ${pathLabel} has unresolved storage type metadata.`, + ); + return; + } + if (before.encoding !== after.encoding || before.label !== after.label) { + errors.push( + `${contract}: ${pathLabel} type changed from ${before.label}/${before.encoding} ` + + `to ${after.label}/${after.encoding}.`, + ); + return; + } + + for (const key of ["base", "key", "value"] as const) { + const beforeChild = before[key]; + const afterChild = after[key]; + if ((beforeChild === undefined) !== (afterChild === undefined)) { + errors.push(`${contract}: ${pathLabel} ${key} type changed.`); + } else if (beforeChild && afterChild) { + compareType( + contract, + `${pathLabel}.${key}`, + previous, + beforeChild, + current, + afterChild, + errors, + visited, + ); + } + } + + if (before.members) { + if (!after.members) { + errors.push( + `${contract}: ${pathLabel} no longer exposes struct members.`, + ); + return; + } + for (const oldMember of before.members) { + const newMember = after.members.find( + (candidate) => candidate.label === oldMember.label, + ); + if (!newMember) { + errors.push( + `${contract}: ${pathLabel}.${oldMember.label} was removed or renamed.`, + ); + continue; + } + if ( + oldMember.slot !== newMember.slot || + oldMember.offset !== newMember.offset + ) { + errors.push( + `${contract}: ${pathLabel}.${oldMember.label} moved from ` + + `${oldMember.slot}+${oldMember.offset} to ` + + `${newMember.slot}+${newMember.offset}.`, + ); + } + compareType( + contract, + `${pathLabel}.${oldMember.label}`, + previous, + oldMember.type, + current, + newMember.type, + errors, + visited, + ); + } + } else if (before.numberOfBytes !== after.numberOfBytes) { + errors.push( + `${contract}: ${pathLabel} size changed from ${before.numberOfBytes} ` + + `to ${after.numberOfBytes} bytes.`, + ); + } +} + +function gapEnd(layout: StorageLayout, gap: StorageVar): bigint { + const bytes = BigInt(layout.types[gap.type].numberOfBytes); + return BigInt(gap.slot) + bytes / 32n; +} + +export function diffLayouts( + contract: string, + previous: StorageLayout, + current: StorageLayout, +): string[] { + const errors: string[] = []; + const previousGap = previous.storage.find((entry) => entry.label === "__gap"); + const currentGap = current.storage.find((entry) => entry.label === "__gap"); + + for (const oldEntry of previous.storage) { + if (oldEntry.label === "__gap") continue; + const newEntry = current.storage.find( + (candidate) => candidate.label === oldEntry.label, + ); + if (!newEntry) { + errors.push( + `${contract}: state variable \`${oldEntry.label}\` was removed or renamed.`, + ); + continue; + } + if ( + oldEntry.slot !== newEntry.slot || + oldEntry.offset !== newEntry.offset + ) { + errors.push( + `${contract}: \`${oldEntry.label}\` moved from ${oldEntry.slot}+${oldEntry.offset} ` + + `to ${newEntry.slot}+${newEntry.offset}.`, + ); + } + compareType( + contract, + oldEntry.label, + previous, + oldEntry.type, + current, + newEntry.type, + errors, + new Set(), + ); + } + + if (previousGap) { + if (!currentGap) { + errors.push(`${contract}: reserved __gap was removed.`); + return errors; + } + if (gapEnd(previous, previousGap) !== gapEnd(current, currentGap)) { + errors.push( + `${contract}: reserved __gap must shrink from the front without changing its end slot.`, + ); + } + const oldGapStart = BigInt(previousGap.slot); + const newGapStart = BigInt(currentGap.slot); + if (newGapStart < oldGapStart) { + errors.push(`${contract}: reserved __gap moved backward.`); + } + const previousLabels = new Set( + previous.storage.map((entry) => entry.label), + ); + for (const entry of current.storage) { + if (previousLabels.has(entry.label) || entry.label === "__gap") continue; + const slot = BigInt(entry.slot); + if (slot < oldGapStart || slot >= newGapStart) { + errors.push( + `${contract}: new variable \`${entry.label}\` at slot ${entry.slot} ` + + `does not consume the front of the reserved gap.`, + ); + } + } + } + + return errors; +} + +export function assertInterfoldPricingSlots(layout: StorageLayout): string[] { + const errors: string[] = []; + const pricing = layout.storage.find( + (entry) => entry.label === "_pricingConfig", + ); + if (!pricing || pricing.slot !== "24" || pricing.offset !== 0) { + return ["Interfold: _pricingConfig must begin at slot 24+0."]; + } + + const expected = [ + ["keyGenFixedPerNode", "0", 0], + ["keyGenPerEncryptionProof", "1", 0], + ["coordinationPerPair", "2", 0], + ["availabilityPerNodePerSec", "3", 0], + ["decryptionPerNode", "4", 0], + ["publicationBase", "5", 0], + ["verificationPerProof", "6", 0], + ["protocolTreasury", "7", 0], + ["marginBps", "7", 20], + ["protocolShareBps", "7", 22], + ["dkgUtilizationBps", "7", 24], + ["computeUtilizationBps", "7", 26], + ["decryptUtilizationBps", "7", 28], + ["minCommitteeSize", "8", 0], + ["minThreshold", "8", 4], + ] as const; + const members = layout.types[pricing.type]?.members ?? []; + for (const [label, slot, offset] of expected) { + const member = members.find((candidate) => candidate.label === label); + if (!member || member.slot !== slot || member.offset !== offset) { + errors.push( + `Interfold: PricingConfig.${label} must remain at absolute slot ` + + `${24n + BigInt(slot)}+${offset}.`, + ); + } + } + const next = layout.storage.find( + (entry) => entry.label === "_feeTokenAllowed", + ); + if (!next || next.slot !== "33" || next.offset !== 0) { + errors.push( + "Interfold: _feeTokenAllowed must remain adjacent at slot 33+0.", + ); + } + const consumedProofs = layout.storage.find( + (entry) => entry.label === "_consumedDecryptionProofs", + ); + if ( + !consumedProofs || + consumedProofs.slot !== "38" || + consumedProofs.offset !== 0 + ) { + errors.push( + "Interfold: _consumedDecryptionProofs must remain at slot 38+0 for InterfoldPricing.", + ); + } + return errors; +} diff --git a/packages/interfold-contracts/scripts/validateUpgrade.ts b/packages/interfold-contracts/scripts/validateUpgrade.ts index 6fd419368..44b10ef3a 100644 --- a/packages/interfold-contracts/scripts/validateUpgrade.ts +++ b/packages/interfold-contracts/scripts/validateUpgrade.ts @@ -1,197 +1,83 @@ // SPDX-License-Identifier: LGPL-3.0-only -// -// H-22: storage-layout snapshot + diff for upgradeable contracts. -// -// This script does NOT depend on `@openzeppelin/hardhat-upgrades` (the OZ -// upgrades plugin is currently not compatible with Hardhat 3 — once it ships -// Hardhat 3 support, prefer it). Instead it reads the `storageLayout` solc -// output produced by `hardhat.config.ts` (see the `outputSelection` block) -// from the latest build-info file, then for each upgradeable contract: -// -// * If `audits/storage-layouts/.json` exists, diff the live -// layout against the committed snapshot and FAIL on any of: -// - a slot whose `type` or `label` changed, -// - a slot whose `offset` or `slot` index moved, -// - a state variable that was removed. -// Appending new variables at the END is allowed (this is what `__gap` -// reservations are for). -// * If the snapshot is missing, write it (first run / new contract). -// -// Run with: `pnpm hardhat compile && pnpm exec ts-node scripts/validateUpgrade.ts` -// -// Marked as a best-effort guard; CI should call this on every PR that -// touches an upgradeable contract. +// Read-only storage-layout compatibility gate for upgradeable contracts. +// Missing baselines are fatal; maintainers create them explicitly with +// `pnpm snapshot:storage-layouts` from reviewed production build-info. import * as fs from "fs"; import * as path from "path"; -import { fileURLToPath } from "url"; -interface StorageVar { - astId: number; - contract: string; - label: string; - offset: number; - slot: string; - type: string; -} - -interface StorageLayout { - storage: StorageVar[]; - types: Record | null; -} - -const UPGRADEABLE_CONTRACTS: { source: string; contract: string }[] = [ - { source: "contracts/Interfold.sol", contract: "Interfold" }, - { - source: "contracts/registry/CiphernodeRegistryOwnable.sol", - contract: "CiphernodeRegistryOwnable", - }, - { - source: "contracts/registry/BondingRegistry.sol", - contract: "BondingRegistry", - }, - { - source: "contracts/E3RefundManager.sol", - contract: "E3RefundManager", - }, -]; - -const SNAPSHOT_DIR = path.resolve( - path.dirname(fileURLToPath(import.meta.url)), - "../audits/storage-layouts", -); -const BUILD_INFO_DIR = path.resolve( - path.dirname(fileURLToPath(import.meta.url)), - "../artifacts/build-info", -); - -function latestBuildInfo(): string { - if (!fs.existsSync(BUILD_INFO_DIR)) { - throw new Error( - `No build-info dir at ${BUILD_INFO_DIR}. Run \`pnpm hardhat compile\` first.`, - ); - } - const outputs = fs - .readdirSync(BUILD_INFO_DIR) - .filter((f) => f.endsWith(".output.json")) - .map((f) => ({ - f, - mtime: fs.statSync(path.join(BUILD_INFO_DIR, f)).mtimeMs, - })) - .sort((a, b) => b.mtime - a.mtime); - if (outputs.length === 0) { - throw new Error(`No *.output.json under ${BUILD_INFO_DIR}.`); - } - return path.join(BUILD_INFO_DIR, outputs[0].f); -} +import { + SNAPSHOT_DIR, + type StorageSnapshot, + UPGRADEABLE_CONTRACTS, + assertInterfoldPricingSlots, + diffLayouts, + findCurrentLayout, +} from "./storageLayouts"; -function loadLayout( - buildInfoPath: string, - source: string, - contract: string, -): StorageLayout { - const buildInfo = JSON.parse(fs.readFileSync(buildInfoPath, "utf8")) as { - output: { - contracts: Record< - string, - Record - >; - }; - }; - const node = buildInfo.output.contracts?.[source]?.[contract]; - if (!node) { - throw new Error(`Contract ${source}:${contract} not in build-info.`); - } - if (!node.storageLayout) { - throw new Error( - `No storageLayout for ${contract}. Ensure hardhat.config.ts outputSelection includes "storageLayout".`, - ); - } - return node.storageLayout; -} +async function main(): Promise { + let totalErrors = 0; -function diffLayouts( - contract: string, - prev: StorageLayout, - curr: StorageLayout, -): string[] { - const errors: string[] = []; - const prevByLabel = new Map(prev.storage.map((s) => [s.label, s])); - for (const p of prev.storage) { - const c = curr.storage.find((x) => x.label === p.label); - if (!c) { - // Removal is only safe if the variable was the LAST one (could be - // converted into a __gap entry). We still flag it for review. - errors.push( - `${contract}: state variable \`${p.label}\` (slot ${p.slot}) was removed.`, + for (const { source, contract } of UPGRADEABLE_CONTRACTS) { + const snapshotPath = path.join(SNAPSHOT_DIR, `${contract}.json`); + if (!fs.existsSync(snapshotPath)) { + console.error( + ` ✗ ${contract}: required baseline is missing at ${snapshotPath}.`, ); + totalErrors += 1; continue; } - if (c.slot !== p.slot || c.offset !== p.offset) { - errors.push( - `${contract}: \`${p.label}\` moved from slot ${p.slot}+${p.offset} to ${c.slot}+${c.offset}.`, - ); + + const snapshot = JSON.parse( + fs.readFileSync(snapshotPath, "utf8"), + ) as StorageSnapshot; + if ( + snapshot._format !== "interfold-storage-layout-v1" || + snapshot.contract !== contract || + snapshot.source !== source + ) { + console.error(` ✗ ${contract}: baseline metadata is invalid.`); + totalErrors += 1; + continue; } - if (c.type !== p.type) { + + const candidate = findCurrentLayout(source, contract); + const errors = diffLayouts(contract, snapshot, candidate.layout); + if ( + candidate.compiler !== snapshot.baseline.compiler || + candidate.evmVersion !== snapshot.baseline.evmVersion || + candidate.optimizerRuns !== snapshot.baseline.optimizerRuns + ) { errors.push( - `${contract}: \`${p.label}\` type changed from ${p.type} to ${c.type}.`, + `${contract}: candidate compiler settings differ from the production baseline.`, ); } - } - // Appended new variables are OK (consume __gap or appended). - for (const c of curr.storage) { - if (!prevByLabel.has(c.label)) { - // informational only - console.log( - ` + ${contract}: new state variable \`${c.label}\` at slot ${c.slot}+${c.offset} (${c.type}).`, - ); + if (contract === "Interfold") { + errors.push(...assertInterfoldPricingSlots(candidate.layout)); } - } - return errors; -} - -async function main(): Promise { - if (!fs.existsSync(SNAPSHOT_DIR)) { - fs.mkdirSync(SNAPSHOT_DIR, { recursive: true }); - } - const buildInfoPath = latestBuildInfo(); - console.log(`Using build-info: ${path.basename(buildInfoPath)}`); - let totalErrors = 0; - for (const { source, contract } of UPGRADEABLE_CONTRACTS) { - const snapshotPath = path.join(SNAPSHOT_DIR, `${contract}.json`); - const layout = loadLayout(buildInfoPath, source, contract); - if (!fs.existsSync(snapshotPath)) { - fs.writeFileSync(snapshotPath, JSON.stringify(layout, null, 2) + "\n"); - console.log(` * ${contract}: snapshot CREATED at ${snapshotPath}.`); - continue; - } - const prev = JSON.parse( - fs.readFileSync(snapshotPath, "utf8"), - ) as StorageLayout; - const errs = diffLayouts(contract, prev, layout); - if (errs.length === 0) { - console.log(` ✓ ${contract}: storage layout compatible.`); + if (errors.length === 0) { + console.log( + ` ✓ ${contract}: compatible with ${snapshot.baseline.sourceCommit} ` + + `(${snapshot.baseline.buildInfoId}).`, + ); } else { - totalErrors += errs.length; - for (const e of errs) console.error(` ✗ ${e}`); + totalErrors += errors.length; + for (const error of errors) console.error(` ✗ ${error}`); } } if (totalErrors > 0) { - console.error( - `\nvalidateUpgrade FAILED with ${totalErrors} storage incompatibilit${ - totalErrors === 1 ? "y" : "ies" - }.`, - ); - console.error( - `If the change is intentional, update the snapshot under audits/storage-layouts/ and re-run.`, + throw new Error( + `validateUpgrade failed with ${totalErrors} storage-layout error${ + totalErrors === 1 ? "" : "s" + }. Baselines are never created or modified by this command.`, ); - process.exit(1); } - console.log("\nvalidateUpgrade OK."); + + console.log("validateUpgrade OK (read-only)."); } -main().catch((err) => { - console.error(err); - process.exit(1); +main().catch((error) => { + console.error(error); + process.exitCode = 1; }); diff --git a/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts b/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts new file mode 100644 index 000000000..aff1e5809 --- /dev/null +++ b/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +import { expect } from "chai"; + +import { ethers } from "../fixtures"; + +describe("InterfoldPricing storage anchors", function () { + it("AUD-H04: writes every PricingConfig field without touching adjacent slots", async function () { + const pricing = await ethers.deployContract("InterfoldPricing"); + const harnessFactory = await ethers.getContractFactory( + "InterfoldPricingStorageHarness", + { libraries: { InterfoldPricing: await pricing.getAddress() } }, + ); + const left = ethers.keccak256(ethers.toUtf8Bytes("left-slot-23")); + const right = ethers.keccak256(ethers.toUtf8Bytes("right-slot-33")); + const harness = await harnessFactory.deploy(left, right); + + await harness.dirtyPricing(); + await harness.applyDefaultPricingConfig(); + + const config = await harness.getPricingConfig(); + expect(config.keyGenFixedPerNode).to.equal(100000n); + expect(config.keyGenPerEncryptionProof).to.equal(50000n); + expect(config.coordinationPerPair).to.equal(10000n); + expect(config.availabilityPerNodePerSec).to.equal(50n); + expect(config.decryptionPerNode).to.equal(300000n); + expect(config.publicationBase).to.equal(1000000n); + expect(config.verificationPerProof).to.equal(5000n); + expect(config.protocolTreasury).to.equal(ethers.ZeroAddress); + expect(config.marginBps).to.equal(1000); + expect(config.protocolShareBps).to.equal(0); + expect(config.dkgUtilizationBps).to.equal(2500); + expect(config.computeUtilizationBps).to.equal(5000); + expect(config.decryptUtilizationBps).to.equal(2500); + expect(config.minCommitteeSize).to.equal(0); + expect(config.minThreshold).to.equal(0); + expect(await harness.leftSentinel()).to.equal(left); + expect(await harness.rightSentinel()).to.equal(right); + }); +}); From d724131ca4cf8d3a1d4a832eee9bfebd293b5c0a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 16 Jul 2026 16:56:36 +0500 Subject: [PATCH 18/52] fix(protocol): move proof aggregation skip to ciphernode [C-02] --- .github/workflows/ci.yml | 2 +- agent/flow-trace/00_INDEX.md | 52 +++++------ agent/flow-trace/04_DKG_AND_COMPUTATION.md | 26 +++--- circuits/benchmarks/README.md | 19 ++-- .../scripts/extract_crisp_verify_gas.sh | 6 +- circuits/benchmarks/scripts/run_benchmarks.sh | 2 +- crates/aggregator/src/ext.rs | 12 ++- .../effects/prove_plaintext.rs | 7 +- .../plaintext_aggregation/tests/completion.rs | 7 +- .../effects/aggregate_dkg_proofs.rs | 12 +-- .../effects/publish_result.rs | 23 +++-- .../src/ciphernode_builder.rs | 18 +++- crates/config/src/app_config.rs | 35 ++++++++ crates/entrypoint/src/start/start.rs | 15 +++- .../src/interfold_event/e3_requested.rs | 5 -- .../interfold_event/plaintext_aggregated.rs | 8 +- .../threshold_share_pending.rs | 3 - crates/evm-helpers/src/contracts.rs | 8 -- crates/evm-helpers/src/events.rs | 1 - crates/evm/src/ciphernode_registry/effects.rs | 21 ++--- crates/evm/src/contracts.rs | 1 - crates/evm/src/interfold/events.rs | 2 - crates/evm/src/interfold_writing/effects.rs | 9 +- crates/evm/src/interfold_writing/workflow.rs | 21 +++-- crates/keyshare/src/ext.rs | 1 - .../effects/generate_threshold_share.rs | 7 -- .../keyshare/src/threshold_keyshare/state.rs | 3 - .../src/threshold_keyshare/state_tests.rs | 2 - crates/request/src/meta.rs | 3 - .../src/commitment_consistency_checker_ext.rs | 1 - .../src/ciphernode_selection/actor.rs | 1 - crates/tests/tests/integration.rs | 52 +++++++---- crates/zk-prover/src/actor_system.rs | 11 ++- .../src/node_proof_aggregation/actor.rs | 16 +++- .../src/node_proof_aggregation/effects.rs | 6 +- crates/zk-prover/src/proof_request/actor.rs | 12 ++- .../src/proof_request/actor_tests.rs | 4 +- .../effects/decryption_key_proofs.rs | 2 +- .../src/proof_request/effects/dkg_proofs.rs | 5 +- .../effects/encryption_key_result.rs | 3 +- .../src/proof_request/effects/signing.rs | 2 +- crates/zk-prover/src/proof_request/state.rs | 2 - .../src/proof_request/workflow_tests.rs | 1 - .../src/proof_verification/actor_tests.rs | 1 - docs/pages/cryptography.mdx | 4 +- docs/pages/internals/dkg.mdx | 20 +++-- examples/CRISP/Readme.md | 14 +-- examples/CRISP/crisp.dev.env.example | 8 +- .../CRISP/docs/PROOF_AGGREGATION_AND_ZK.md | 60 +++++++------ examples/CRISP/interfold.config.yaml | 2 + .../CRISP/packages/crisp-contracts/README.md | 3 +- .../contracts/Mocks/MockInterfold.sol | 6 +- .../crisp-contracts/deploy/syncCrispEnv.ts | 32 +------ examples/CRISP/scripts/crisp_deploy.sh | 2 +- examples/CRISP/scripts/lib/dev_config.sh | 40 ++++----- examples/CRISP/server/.env.example | 1 - examples/CRISP/server/src/cli/commands.rs | 4 - examples/CRISP/server/src/config.rs | 2 - .../CRISP/server/src/server/routes/rounds.rs | 3 - .../interfaces/IInterfold.sol/IInterfold.json | 27 +----- .../contracts/Interfold.sol | 22 +++-- .../interfaces/ICiphernodeRegistry.sol | 8 +- .../contracts/interfaces/IE3.sol | 1 - .../contracts/interfaces/IInterfold.sol | 4 - .../registry/CiphernodeRegistryOwnable.sol | 28 +++--- .../test/MockDkgFoldAttestationVerifier.sol | 32 +++++++ .../scripts/deployInterfold.ts | 15 ++++ .../interfold-contracts/tasks/interfold.ts | 8 -- .../test/E3Lifecycle/E3Integration.spec.ts | 88 +++++++++++++++---- .../test/E3Lifecycle/Sortition.spec.ts | 1 - .../test/Interfold.spec.ts | 49 ++++++----- .../test/Pricing/DustRotation.spec.ts | 11 ++- .../test/Pricing/Pricing.spec.ts | 2 - .../Pricing/PullPaymentsAndAllowlist.spec.ts | 10 ++- .../CiphernodeRegistryOwnable.spec.ts | 73 +++++++++++++-- ...iphernodeRegistryVerifierLifecycle.spec.ts | 4 +- .../test/Slashing/CommitteeExpulsion.spec.ts | 6 +- .../test/fixtures/helpers.ts | 23 ++++- .../test/fixtures/system.ts | 19 +++- .../src/contracts/contract-client.ts | 2 - packages/interfold-sdk/src/contracts/types.ts | 3 - packages/interfold-sdk/src/interfold-sdk.ts | 1 - .../src/pages/steps/RequestComputation.tsx | 1 - templates/default/interfold.config.yaml | 2 + templates/default/tests/integration.spec.ts | 1 - tests/integration/base.sh | 11 ++- tests/integration/fns.sh | 2 +- tests/integration/interfold.config.yaml | 2 + tests/integration/lib/prebuild.sh | 2 +- tests/integration/persist.sh | 5 +- tests/integration/test.sh | 25 ++++-- 91 files changed, 674 insertions(+), 465 deletions(-) create mode 100644 packages/interfold-contracts/contracts/test/MockDkgFoldAttestationVerifier.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 266d2f68e..0d0d6108c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,7 +213,7 @@ jobs: - name: Run Integration Tests env: - BENCHMARK_PROOF_AGGREGATION: 'false' + CIPHERNODE_SKIP_PROOF_AGGREGATION: 'true' run: 'cargo test --test integration -- --nocapture' zk_prover_integration: diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 23c7f9b6f..18cdd50b7 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -170,18 +170,18 @@ _Found during source-code cross-referencing of these trace documents._ ### Critical Doc Inaccuracies (now fixed) -| # | Description | Where | Fix Applied | -| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | --------------- | -| 1 | `addTicketBalance` does NOT multiply by `ticketPrice` — raw stablecoin units are passed directly to `ticketToken.depositFrom()`. `ticketPrice` is only used in the activation check. | BondingRegistry.sol:371 | 02_TOKENS | -| 2 | `removeTicketBalance` does NOT multiply by `ticketPrice` — raw amount passed to `ticketToken.burnTickets()`. | BondingRegistry.sol:395 | 02_TOKENS | -| 3 | `gracePeriod` is NOT added to deadline checks in `_checkFailureCondition()`. All timeout checks compare `block.timestamp` directly against the raw deadline. `gracePeriod` is only validated in `_setTimeoutConfig` but never referenced in failure detection. | Interfold.sol:860-887 | 05_FAILURE | -| 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION | -| 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IInterfold.sol:82 | 03_E3_REQUEST | -| 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | -| 7 | `publishCommittee()` is now permissionless. The effective access control is DKG proof verification plus the single-publish guard `publicKeyHashes[e3Id] == 0`; the old `onlyOwner` note is obsolete. | CiphernodeRegistryOwnable.sol | 04_DKG | +| # | Description | Where | Fix Applied | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | --------------- | +| 1 | `addTicketBalance` does NOT multiply by `ticketPrice` — raw stablecoin units are passed directly to `ticketToken.depositFrom()`. `ticketPrice` is only used in the activation check. | BondingRegistry.sol:371 | 02_TOKENS | +| 2 | `removeTicketBalance` does NOT multiply by `ticketPrice` — raw amount passed to `ticketToken.burnTickets()`. | BondingRegistry.sol:395 | 02_TOKENS | +| 3 | `gracePeriod` is NOT added to deadline checks in `_checkFailureCondition()`. All timeout checks compare `block.timestamp` directly against the raw deadline. `gracePeriod` is only validated in `_setTimeoutConfig` but never referenced in failure detection. | Interfold.sol:860-887 | 05_FAILURE | +| 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION | +| 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IInterfold.sol:82 | 03_E3_REQUEST | +| 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | +| 7 | `publishCommittee()` is now permissionless. The effective access control is DKG proof verification plus the single-publish guard `publicKeyHashes[e3Id] == 0`; the old `onlyOwner` note is obsolete. | CiphernodeRegistryOwnable.sol | 04_DKG | | 8 | `CommitteePublished` emits `(e3Id, nodes, publicKey, pkCommitment, proof)`. The full PK bytes are an untrusted transport hint that the indexer decodes and checks against the on-chain commitment before storage; that commitment is C5-proven when proof aggregation is enabled. | CiphernodeRegistryOwnable.sol | 04_DKG | -| 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | -| 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | +| 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | +| 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | ### Protocol Design Concerns @@ -204,20 +204,20 @@ _Found during source-code cross-referencing of these trace documents._ | 15 | **Local/network event redelivery collision** | Resolved | EventStore now treats the same HLC timestamp plus stable event ID and equal payload as an idempotent duplicate even when transport context differs (`Local` versus `Net`). It retains the first stored context and still fails closed when different payloads claim the same timestamp, preserving collision detection without panicking on sync/resync redelivery. | | 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | | 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | -| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity. | -| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, and callers can query either role explicitly. | -| 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | -| 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | -| 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | -| 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | -| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy, and the minimum ticket requirement can never be zero. | -| 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | -| 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | -| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | -| 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | -| 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | -| 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | +| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity. | +| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, and callers can query either role explicitly. | +| 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | +| 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | +| 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | +| 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | +| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy, and the minimum ticket requirement can never be zero. | +| 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | +| 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | +| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | +| 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | +| 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | +| 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | -| 32 | **Proof-disabled publication bypass (AUD C-02)** | Predeploy blocker | `proofAggregationEnabled=false` intentionally retains the faster unverified development path. The flag and both bypasses must be removed before any production deployment; C-02 is not closed by the current code. | -| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | +| 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | +| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | | 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | `Interfold` canonicalizes each `(rawProof, publicInputs)` payload into a single-use nullifier before verification. A successfully consumed proof cannot complete another E3, and equivalent ABI encodings share the same nullifier; failed verification rolls all state back atomically. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 9e7b0cf95..6decdb73f 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -637,8 +637,9 @@ ThresholdKeyshare receives AllThresholdSharesCollected ├─ Requires EffectsEnabled ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm committee public key is still unset - ├─ Encodes the DkgAggregator proof when aggregation is enabled, - │ otherwise passes empty proof bytes in development mode + ├─ Encodes the DkgAggregator proof in production + ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C5 proof as a + │ mock-verifier placeholder; this does not bypass contract verification └─ Calls contract.publishCommittee(e3_id, publicKey, pkCommitment, proof) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ @@ -648,8 +649,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected │ │ 2. require(c.publicKey == 0) — publish once │ │ │ 3. committeeHash = keccak256(abi.encodePacked(c.topNodes)) │ │ │ c.committeeHash = committeeHash │ - │ │ 4. When proofAggregationEnabled: │ - │ │ require(proof.length > 0) │ + │ │ 4. require(proof.length > 0) │ │ │ e3.pkVerifier.verify( │ │ │ e3Id, committeeRoot, c.topNodes, │ │ │ pkCommitment, committeeHash, proof │ @@ -681,11 +681,11 @@ ThresholdKeyshare receives AllThresholdSharesCollected ``` The serialized `publicKey` event field is a transport hint, not on-chain authority. Before -`e3-indexer` stores it in `E3.committee_public_key`, it decodes the BFV key, recomputes the circuit's -public-key commitment using the request's parameter set, and requires equality with the event's -on-chain `pkCommitment`. Malformed bytes or bytes for a different key fail closed and never reach -encryption clients. The commitment is C5-proven when proof aggregation is enabled; the explicitly -unsafe development mode trusts it from the aggregator. +`e3-indexer` stores it in `E3.committee_public_key`, it decodes the BFV key, recomputes the +circuit's public-key commitment using the request's parameter set, and requires equality with the +event's on-chain `pkCommitment`. Malformed bytes or bytes for a different key fail closed and never +reach encryption clients. The commitment is C5-proven when proof aggregation is enabled; the +explicitly unsafe development mode trusts it from the aggregator. > **C-08 (BfvPkVerifier domain binding) — implemented** The wrapper exposes a > `verify(e3Id, committeeRoot, sortedNodes, pkCommitment, committeeHash, proof)` signature. @@ -899,8 +899,9 @@ InterfoldSolReader decodes CiphertextOutputPublished event ├─ Requires EffectsEnabled ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm plaintextOutput is still empty - ├─ Encodes the final decryption proof when aggregation is enabled, - │ otherwise passes empty proof bytes in development mode + ├─ Encodes the final DecryptionAggregator proof in production + ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C7 proof as a + │ mock-verifier placeholder; this does not bypass contract verification └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) │ │ ┌─── ON-CHAIN (Interfold.sol) ─────────────────────────┐ @@ -908,8 +909,7 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ publishPlaintextOutput(e3Id, output, proof) { │ │ │ 1. require(stage == CiphertextReady) │ │ │ 2. require(now <= decryptionDeadline) │ - │ │ 3. When proofAggregationEnabled: │ - │ │ require(proof.length > 0), then canonically │ + │ │ 3. require(proof.length > 0), then canonically │ │ │ decode (rawProof, publicInputs), │ │ │ derive a proof nullifier, require it unused, │ │ │ and consume it (equivalent ABI encodings map │ diff --git a/circuits/benchmarks/README.md b/circuits/benchmarks/README.md index dd89b271d..17cdf3894 100644 --- a/circuits/benchmarks/README.md +++ b/circuits/benchmarks/README.md @@ -70,15 +70,16 @@ switch; manual edits to those files will trip the check. ### Proof aggregation and folding (integration) -The gas / integration stage always runs `cargo test -p e3-tests test_trbfv_actor` with proof -aggregation enabled (`E3Requested.proof_aggregation_enabled = true`): per-node `ZkNodeDkgFold`, fold -attestations (EIP-712 against `DkgFoldAttestationVerifier`), and exported folded `dkg_aggregator` / -`decryption_aggregator` proofs for Π_DKG / Π_dec on-chain gas. - -| Flag / env | Effect | -| ------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| `BENCHMARK_MULTITHREAD_JOBS=N` / `--multithread-jobs N` | Rayon concurrent ZK jobs (default `1`) | -| `BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER=0x…` | EIP-712 verifying contract for fold attestations (default: localhost deploy address) | +The gas / integration stage always runs `cargo test -p e3-tests test_trbfv_actor` with the +ciphernode test/CI skip flag off: per-node `ZkNodeDkgFold`, fold attestations (EIP-712 against +`DkgFoldAttestationVerifier`), and exported folded `dkg_aggregator` / `decryption_aggregator` proofs +for Π_DKG / Π_dec on-chain gas. + +| Flag / env | Effect | +| ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `CIPHERNODE_SKIP_PROOF_AGGREGATION=true` | Test/CI-only: skip recursive aggregation; benchmark scripts explicitly set it to `false` | +| `BENCHMARK_MULTITHREAD_JOBS=N` / `--multithread-jobs N` | Rayon concurrent ZK jobs (default `1`) | +| `BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER=0x…` | EIP-712 verifying contract for fold attestations (default: localhost deploy address) | **Output directories** (under `circuits/benchmarks/`): diff --git a/circuits/benchmarks/scripts/extract_crisp_verify_gas.sh b/circuits/benchmarks/scripts/extract_crisp_verify_gas.sh index 0b19109d5..ec0b753bb 100755 --- a/circuits/benchmarks/scripts/extract_crisp_verify_gas.sh +++ b/circuits/benchmarks/scripts/extract_crisp_verify_gas.sh @@ -5,7 +5,7 @@ # [--committee minimum|micro|small] [--verbose] [--skip-build] [--force-build] # # Integration test env (also set by run_benchmarks.sh): -# BENCHMARK_PROOF_AGGREGATION=true — always enabled in the benchmark harness +# CIPHERNODE_SKIP_PROOF_AGGREGATION=false — aggregation stays enabled in the benchmark harness # BENCHMARK_MULTITHREAD_JOBS=N — Rayon concurrent ZK jobs (default: 1) # BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER — EIP-712 verifying contract for fold attestations @@ -165,12 +165,12 @@ echo " [gas] Running CRISP verifier test for Pi_user gas..." CRISP_TEST_EXIT_CODE=${PIPESTATUS[0]} echo " [gas] CRISP test completed (exit=${CRISP_TEST_EXIT_CODE})." require_preset_artifacts -BENCHMARK_PROOF_AGGREGATION=true +CIPHERNODE_SKIP_PROOF_AGGREGATION=false echo " [gas] Running integration test (test_trbfv_actor); proof_aggregation=true, multithread_jobs=${BENCHMARK_MULTITHREAD_JOBS:-1}, profile=release..." ( cd "$REPO_ROOT" && \ BENCHMARK_MODE="$MODE" \ - BENCHMARK_PROOF_AGGREGATION=true \ + CIPHERNODE_SKIP_PROOF_AGGREGATION=false \ BENCHMARK_FOLDED_OUTPUT="$TMP_JSON_FOLDED" \ BENCHMARK_SUMMARY_OUTPUT="$TMP_JSON_SUMMARY" \ cargo test --release -p e3-tests test_trbfv_actor -- --nocapture diff --git a/circuits/benchmarks/scripts/run_benchmarks.sh b/circuits/benchmarks/scripts/run_benchmarks.sh index 566ba2993..59f4e5306 100755 --- a/circuits/benchmarks/scripts/run_benchmarks.sh +++ b/circuits/benchmarks/scripts/run_benchmarks.sh @@ -22,7 +22,7 @@ CIRCUIT_FILTER="" VERBOSE=false PRESET_ARTIFACTS_READY=false MULTITHREAD_JOBS="${BENCHMARK_MULTITHREAD_JOBS:-}" -export BENCHMARK_PROOF_AGGREGATION=true +export CIPHERNODE_SKIP_PROOF_AGGREGATION=false # Parse arguments while [[ $# -gt 0 ]]; do diff --git a/crates/aggregator/src/ext.rs b/crates/aggregator/src/ext.rs index 309bb73b4..3149ea5a7 100644 --- a/crates/aggregator/src/ext.rs +++ b/crates/aggregator/src/ext.rs @@ -202,13 +202,19 @@ fn create_publickey_aggregator( pub struct ThresholdPlaintextAggregatorExtension { bus: BusHandle, sortition: Addr, + proof_aggregation_enabled: bool, } impl ThresholdPlaintextAggregatorExtension { - pub fn create(bus: &BusHandle, sortition: &Addr) -> Box { + pub fn create( + bus: &BusHandle, + sortition: &Addr, + proof_aggregation_enabled: bool, + ) -> Box { Box::new(Self { bus: bus.clone(), sortition: sortition.clone(), + proof_aggregation_enabled, }) } @@ -287,7 +293,7 @@ impl ThresholdPlaintextAggregatorExtension { return false; } }, - proof_aggregation_enabled: meta.proof_aggregation_enabled, + proof_aggregation_enabled: self.proof_aggregation_enabled, committee_addresses, honest_committee_addresses, }, @@ -626,7 +632,7 @@ impl E3Extension for ThresholdPlaintextAggregatorExtension { meta.threshold_n ) })?, - proof_aggregation_enabled: meta.proof_aggregation_enabled, + proof_aggregation_enabled: self.proof_aggregation_enabled, committee_addresses, honest_committee_addresses, }, diff --git a/crates/aggregator/src/plaintext_aggregation/effects/prove_plaintext.rs b/crates/aggregator/src/plaintext_aggregation/effects/prove_plaintext.rs index 3b796f072..4a5658c7f 100644 --- a/crates/aggregator/src/plaintext_aggregation/effects/prove_plaintext.rs +++ b/crates/aggregator/src/plaintext_aggregation/effects/prove_plaintext.rs @@ -88,7 +88,10 @@ impl ThresholdPlaintextAggregator { } if !self.proof_aggregation_enabled { if self.pending.decryption_aggregator_proofs.is_none() { - self.pending.decryption_aggregator_proofs = Some(Vec::new()); + // Reuse the already-generated C7 proofs as non-empty test placeholders. Mock + // decryption verifiers accept them; production verifiers reject them because + // they are not DecryptionAggregator proofs. + self.pending.decryption_aggregator_proofs = self.pending.c7_proofs_pending.clone(); } return Ok(()); } @@ -117,7 +120,7 @@ impl ThresholdPlaintextAggregator { return Ok(()); } if !self.proof_aggregation_enabled { - self.pending.decryption_aggregator_proofs = Some(Vec::new()); + self.pending.decryption_aggregator_proofs = self.pending.c7_proofs_pending.clone(); return Ok(()); } let Some(honest_c6) = self.pending.honest_c6_proofs_for_agg.as_ref() else { diff --git a/crates/aggregator/src/plaintext_aggregation/tests/completion.rs b/crates/aggregator/src/plaintext_aggregation/tests/completion.rs index 8b0bb3475..4e58ce95a 100644 --- a/crates/aggregator/src/plaintext_aggregation/tests/completion.rs +++ b/crates/aggregator/src/plaintext_aggregation/tests/completion.rs @@ -43,10 +43,11 @@ async fn missing_c6_inner_proofs_emit_e3_failed() -> Result<()> { } #[actix::test] -async fn proof_aggregation_disabled_marks_decryption_aggregator_ready() -> Result<()> { +async fn test_skip_reuses_c7_as_mock_verifier_placeholder() -> Result<()> { let (mut aggregator, _history, _e3_id) = build_plaintext_aggregator(generating_c7_state(), false).await?; - aggregator.pending.c7_proofs_pending = Some(vec![dummy_proof(CircuitName::PkAggregation)]); + let c7_proof = dummy_proof(CircuitName::PkAggregation); + aggregator.pending.c7_proofs_pending = Some(vec![c7_proof.clone()]); let ec = test_ctx(E3Failed { e3_id: aggregator.e3_id.clone(), failed_at_stage: E3Stage::None, @@ -58,7 +59,7 @@ async fn proof_aggregation_disabled_marks_decryption_aggregator_ready() -> Resul .pending .decryption_aggregator_proofs .as_ref() - .is_some_and(|p| p.is_empty())); + .is_some_and(|proofs| proofs == &[c7_proof])); assert!(aggregator .pending .decryption_aggregation_correlation diff --git a/crates/aggregator/src/public_key_aggregation/effects/aggregate_dkg_proofs.rs b/crates/aggregator/src/public_key_aggregation/effects/aggregate_dkg_proofs.rs index 7980ee512..aa676e7de 100644 --- a/crates/aggregator/src/public_key_aggregation/effects/aggregate_dkg_proofs.rs +++ b/crates/aggregator/src/public_key_aggregation/effects/aggregate_dkg_proofs.rs @@ -44,9 +44,10 @@ impl PublicKeyAggregator { return Ok(()); } - // `proof_aggregation_enabled` is an E3-level flag shared by all nodes, so honest-party - // proofs should be uniformly Some (aggregation on) or uniformly None (aggregation off). - // A mixed bag would silently truncate the dispatched request below; reject it explicitly. + // Proof aggregation is a node-level test/CI setting and must be configured consistently + // across a test swarm. Honest-party proofs should therefore be uniformly Some + // (aggregation on) or uniformly None (aggregation skipped). A mixed bag would silently + // truncate the dispatched request below; reject it explicitly. let some_count = honest_party_ids .iter() .filter(|id| { @@ -140,10 +141,11 @@ impl PublicKeyAggregator { ); if node_fold_proofs.is_empty() { - // Proof aggregation disabled. Do NOT call `try_publish_complete` here — it + // Proof aggregation was skipped by the node's test/CI setting. Do NOT call + // `try_publish_complete` here — it // is the most common entry into this method, so re-entering it would create // unbounded mutual recursion (stack overflow in deployed nodes). - info!("PublicKeyAggregator: proof aggregation disabled — skipping DkgAggregation"); + info!("PublicKeyAggregator: test/CI skip flag active — skipping DkgAggregation"); return Ok(()); } diff --git a/crates/aggregator/src/public_key_aggregation/effects/publish_result.rs b/crates/aggregator/src/public_key_aggregation/effects/publish_result.rs index c4f2ac589..d0fa9411b 100644 --- a/crates/aggregator/src/public_key_aggregation/effects/publish_result.rs +++ b/crates/aggregator/src/public_key_aggregation/effects/publish_result.rs @@ -5,7 +5,8 @@ use super::super::*; impl PublicKeyAggregator { - /// Publish `PublicKeyAggregated` when C5 (non-ZK recursive) and, if applicable, the EVM DkgAggregator proof are ready (or aggregation skipped). + /// Publish `PublicKeyAggregated` when C5 and the final DkgAggregator proof are ready, or when + /// a test/CI node deliberately skips recursive aggregation. pub(in crate::actors::publickey_aggregator) fn try_publish_complete(&mut self) -> Result<()> { if let Some(ec) = self.state.get().and_then(|s| { if let PublicKeyAggregatorState::GeneratingC5Proof { last_ec, .. } = &s { @@ -68,17 +69,23 @@ impl PublicKeyAggregator { .clone() .ok_or_else(|| anyhow::anyhow!("No EventContext for publish"))?; + let pk_commitment = extract_pk_commitment(c5_proof)?; + // Test/CI nodes reuse the already-generated C5 proof as a non-empty placeholder. Mock + // verifiers accept it; production DKG verifiers reject it because it is not a + // DkgAggregator proof. This keeps the testing escape hatch entirely in the ciphernode. + let published_dkg_proof = dkg_aggregated_proof + .clone() + .or_else(|| all_proofs_are_none.then(|| c5_proof.clone())); + info!( "Publishing PublicKeyAggregated (dkg_evm_proof={})", if dkg_aggregated_proof.is_some() { - "present" + "aggregated" } else { - "skipped" + "test-placeholder" } ); - let pk_commitment = extract_pk_commitment(c5_proof)?; - // Full committee (N entries) — used by on-chain `committee_hash` binding. let mut full_committee_party_ids: Vec = party_nodes.keys().copied().collect(); full_committee_party_ids.sort(); @@ -99,7 +106,9 @@ impl PublicKeyAggregator { )?; Some(ArcBytes::from_bytes(&bundle)) } - None => None, + // The mock fold-attestation verifier used by test deployments only requires a + // non-empty payload. Production deployments never take this path. + None => Some(ArcBytes::from_bytes(&[1])), }; let event = PublicKeyAggregated { @@ -109,7 +118,7 @@ impl PublicKeyAggregator { committee_addresses: committee_addresses.clone(), honest_committee_addresses: honest_committee_addresses.clone(), pk_commitment, - dkg_aggregator_proof: dkg_aggregated_proof.clone(), + dkg_aggregator_proof: published_dkg_proof, dkg_attestation_bundle, }; self.bus.publish(event, ec.clone())?; diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index 5e965017a..b237b8d11 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -80,6 +80,7 @@ pub struct CiphernodeBuilder { multithread_cache: Option>, multithread_concurrent_jobs: Option, multithread_report: Option>, + proof_aggregation_enabled: bool, pubkey_agg: bool, rng: SharedRng, sortition_backend: SortitionBackend, @@ -148,6 +149,7 @@ impl CiphernodeBuilder { multithread_cache: None, multithread_concurrent_jobs: None, multithread_report: None, + proof_aggregation_enabled: true, pubkey_agg: false, rng, sortition_backend: SortitionBackend::score(), @@ -325,6 +327,16 @@ impl CiphernodeBuilder { self } + /// Skip recursive proof aggregation for tests and CI. + /// + /// This only changes local ciphernode work. Contracts still verify the final DKG and + /// decryption proof payloads, so this setting is only useful with test deployments whose + /// configured mock verifiers accept the non-empty C5/C7 placeholder payloads. + pub fn with_proof_aggregation_disabled_for_testing(mut self) -> Self { + self.proof_aggregation_enabled = false; + self + } + /// Connect rayon work to the given threadpool pub fn with_shared_taskpool(mut self, pool: &TaskPool) -> Self { self.task_pool = Some(pool.clone()); @@ -811,6 +823,7 @@ impl CiphernodeBuilder { _signer, dkg_fold_verifier_by_chain.clone(), zk_recovery.clone(), + self.proof_aggregation_enabled, ); } @@ -836,6 +849,7 @@ impl CiphernodeBuilder { signer, dkg_fold_verifier_by_chain.clone(), zk_recovery, + self.proof_aggregation_enabled, ); } } @@ -845,7 +859,9 @@ impl CiphernodeBuilder { info!("Setting up ThresholdPlaintextAggregatorExtension"); let _ = self.ensure_multithread(bus); e3_builder = e3_builder.with(ThresholdPlaintextAggregatorExtension::create( - bus, sortition, + bus, + sortition, + self.proof_aggregation_enabled, )); } diff --git a/crates/config/src/app_config.rs b/crates/config/src/app_config.rs index 821be4cd3..73ffe252c 100644 --- a/crates/config/src/app_config.rs +++ b/crates/config/src/app_config.rs @@ -74,6 +74,9 @@ pub struct NodeDefinition { /// Maximum estimated bytes retained by the network startup buffer. #[serde(default = "default_max_buffered_net_bytes")] pub max_buffered_net_bytes: usize, + /// Test/CI-only escape hatch that skips recursive DKG and decryption proof aggregation. + /// On-chain verification remains mandatory, so this requires mock verifiers. + pub skip_proof_aggregation: bool, } fn default_multithread_reserve_threads() -> usize { @@ -118,6 +121,7 @@ impl Default for NodeDefinition { max_buffered_evm_events: default_max_buffered_evm_events(), max_buffered_net_events: default_max_buffered_net_events(), max_buffered_net_bytes: default_max_buffered_net_bytes(), + skip_proof_aggregation: false, } } } @@ -418,6 +422,11 @@ impl AppConfig { pub fn max_buffered_net_bytes(&self) -> usize { self.node_def().max_buffered_net_bytes } + + /// Whether this node skips recursive proof aggregation for test/CI runs. + pub fn skip_proof_aggregation(&self) -> bool { + self.node_def().skip_proof_aggregation + } } #[derive(Debug, Default, Deserialize, Serialize)] @@ -811,6 +820,32 @@ node: Ok(()) } + #[test] + fn test_skip_proof_aggregation_defaults_off_and_can_be_enabled() -> Result<()> { + let configured: UnscopedAppConfig = serde_yaml::from_str( + r#" +node: + skip_proof_aggregation: true +"#, + )?; + let configured = configured.into_scoped_with_defaults( + "_default", + &PathBuf::from("/default/data"), + &PathBuf::from("/default/config"), + &PathBuf::from("/my/cwd"), + )?; + assert!(configured.skip_proof_aggregation()); + + let default = UnscopedAppConfig::default().into_scoped_with_defaults( + "_default", + &PathBuf::from("/default/data"), + &PathBuf::from("/default/config"), + &PathBuf::from("/my/cwd"), + )?; + assert!(!default.skip_proof_aggregation()); + Ok(()) + } + #[test] fn test_startup_timeout_config_and_default() -> Result<()> { let configured: UnscopedAppConfig = serde_yaml::from_str( diff --git a/crates/entrypoint/src/start/start.rs b/crates/entrypoint/src/start/start.rs index 00bc486af..f6c81c2a2 100644 --- a/crates/entrypoint/src/start/start.rs +++ b/crates/entrypoint/src/start/start.rs @@ -14,7 +14,7 @@ use rand_chacha::ChaCha20Rng; use std::future::Future; use std::sync::{Arc, Mutex}; use std::time::Duration; -use tracing::{info, instrument}; +use tracing::{info, instrument, warn}; async fn await_startup(future: F, timeout: Duration) -> Result where @@ -48,7 +48,7 @@ pub async fn execute(config: &AppConfig) -> Result { "Ciphernode startup deadline configured" ); - let build = CiphernodeBuilder::new(rng.clone(), cipher.clone()) + let mut builder = CiphernodeBuilder::new(rng.clone(), cipher.clone()) .with_name(&config.name()) .with_logging() .with_persistence(&config.log_file(), &config.db_file()) @@ -70,8 +70,15 @@ pub async fn execute(config: &AppConfig) -> Result { .with_threshold_plaintext_aggregation() .with_net(config.peers(), config.quic_port()) .with_shared_store() - .with_shared_eventstore() - .build(); + .with_shared_eventstore(); + if config.skip_proof_aggregation() { + warn!( + "Skipping recursive proof aggregation for this test/CI node; \ + on-chain final proof verification remains mandatory" + ); + builder = builder.with_proof_aggregation_disabled_for_testing(); + } + let build = builder.build(); let node = await_startup(build, startup_timeout).await?; Ok(node) diff --git a/crates/events/src/interfold_event/e3_requested.rs b/crates/events/src/interfold_event/e3_requested.rs index a0f8c6f07..069cdf025 100644 --- a/crates/events/src/interfold_event/e3_requested.rs +++ b/crates/events/src/interfold_event/e3_requested.rs @@ -31,10 +31,6 @@ pub struct E3Requested { /// ABI-encoded BFV parameters (derived from `params_preset`). /// Kept for downstream code that needs the raw bytes (e.g. `TrBFVConfig`). pub params: ArcBytes, - /// When true, ciphernodes generate wrapper/fold proofs for DKG proof - /// aggregation (public verifiability). When false, wrapper/fold proofs - /// are skipped to reduce latency. C5 and C7 proofs are always generated. - pub proof_aggregation_enabled: bool, } impl Default for E3Requested { @@ -47,7 +43,6 @@ impl Default for E3Requested { seed: Seed([0u8; 32]), threshold_m: 0, threshold_n: 0, - proof_aggregation_enabled: false, } } } diff --git a/crates/events/src/interfold_event/plaintext_aggregated.rs b/crates/events/src/interfold_event/plaintext_aggregated.rs index 7023b203f..370047061 100644 --- a/crates/events/src/interfold_event/plaintext_aggregated.rs +++ b/crates/events/src/interfold_event/plaintext_aggregated.rs @@ -17,10 +17,10 @@ use std::fmt::{self, Display}; pub struct PlaintextAggregated { pub e3_id: E3id, pub decrypted_output: Vec, - /// Final DecryptionAggregator (EVM) proof(s): one per ciphertext index. Empty when - /// proof aggregation is disabled. On-chain publication currently only supports a - /// single-output plaintext, in which case the first proof is forwarded to - /// `publishPlaintextOutput`. + /// Final DecryptionAggregator (EVM) proof(s): one per ciphertext index. Test/CI nodes that + /// skip recursive aggregation carry the existing C7 proofs here as mock-verifier + /// placeholders. On-chain publication currently only supports a single-output plaintext, in + /// which case the first proof is forwarded to `publishPlaintextOutput`. pub decryption_aggregator_proofs: Vec, } diff --git a/crates/events/src/interfold_event/threshold_share_pending.rs b/crates/events/src/interfold_event/threshold_share_pending.rs index 459a6a388..a5b024719 100644 --- a/crates/events/src/interfold_event/threshold_share_pending.rs +++ b/crates/events/src/interfold_event/threshold_share_pending.rs @@ -33,9 +33,6 @@ pub struct ThresholdSharePending { /// Required because collected_encryption_keys may be filtered for expulsions, /// making positional indices diverge from actual party IDs. pub recipient_party_ids: Vec, - /// When true, wrapper/fold proofs are generated for DKG proof aggregation. - /// When false, proof aggregation is skipped (only raw C1-C4 proofs generated). - pub proof_aggregation_enabled: bool, } impl Display for ThresholdSharePending { diff --git a/crates/evm-helpers/src/contracts.rs b/crates/evm-helpers/src/contracts.rs index 61f98df6d..71485a17a 100644 --- a/crates/evm-helpers/src/contracts.rs +++ b/crates/evm-helpers/src/contracts.rs @@ -62,7 +62,6 @@ sol! { bytes32 ciphertextOutput; bytes plaintextOutput; address requester; - bool proofAggregationEnabled; } #[derive(Debug)] @@ -73,7 +72,6 @@ sol! { uint8 paramSet; bytes computeProviderParams; bytes customParams; - bool proofAggregationEnabled; uint256 maxFee; uint256 requestDeadline; } @@ -163,7 +161,6 @@ pub trait InterfoldRead { e3_program: Address, param_set: u8, compute_provider_params: Bytes, - proof_aggregation_enabled: bool, ) -> Result; async fn get_e3_stage(&self, e3_id: U256) -> Result; @@ -193,7 +190,6 @@ pub trait InterfoldWrite { param_set: u8, compute_provider_params: Bytes, custom_params: Bytes, - proof_aggregation_enabled: bool, ) -> Result<(TransactionReceipt, U256)>; /// Enable an E3 program @@ -379,7 +375,6 @@ where e3_program: Address, param_set: u8, compute_provider_params: Bytes, - proof_aggregation_enabled: bool, ) -> Result { let e3_request = E3RequestParams { committeeSize: committee_size, @@ -388,7 +383,6 @@ where paramSet: param_set, computeProviderParams: compute_provider_params, customParams: Bytes::new(), - proofAggregationEnabled: proof_aggregation_enabled, maxFee: U256::MAX, requestDeadline: input_window[0], }; @@ -446,7 +440,6 @@ impl InterfoldWrite for InterfoldContract { param_set: u8, compute_provider_params: Bytes, custom_params: Bytes, - proof_aggregation_enabled: bool, ) -> Result<(TransactionReceipt, U256)> { let _guard = NONCE_LOCK.lock().await; let wallet_addr = self @@ -464,7 +457,6 @@ impl InterfoldWrite for InterfoldContract { paramSet: param_set, computeProviderParams: compute_provider_params.clone(), customParams: custom_params.clone(), - proofAggregationEnabled: proof_aggregation_enabled, maxFee: U256::MAX, requestDeadline: input_window[0], }; diff --git a/crates/evm-helpers/src/events.rs b/crates/evm-helpers/src/events.rs index a67cd40dc..21330e3fe 100644 --- a/crates/evm-helpers/src/events.rs +++ b/crates/evm-helpers/src/events.rs @@ -50,7 +50,6 @@ sol! { bytes32 ciphertextOutput; bytes plaintextOutput; address requester; - bool proofAggregationEnabled; } #[derive(Debug)] diff --git a/crates/evm/src/ciphernode_registry/effects.rs b/crates/evm/src/ciphernode_registry/effects.rs index 0f8c1bfad..730e3d64b 100644 --- a/crates/evm/src/ciphernode_registry/effects.rs +++ b/crates/evm/src/ciphernode_registry/effects.rs @@ -149,16 +149,17 @@ pub async fn publish_committee_to_registry encode_zk_proof(p)?, - None => Bytes::new(), - }; - let attestation_bundle: Bytes = match dkg_attestation_bundle { - Some(b) => Bytes::copy_from_slice(b), - None => Bytes::new(), - }; + // Skip mode creates non-empty mock-only placeholders before this boundary. An absent payload + // is therefore always an internal error, while production verifiers still reject placeholders. + let proof = encode_zk_proof( + dkg_aggregator_proof + .ok_or_else(|| anyhow::anyhow!("mandatory DKG aggregator proof payload missing"))?, + )?; + let attestation_bundle = Bytes::copy_from_slice( + dkg_attestation_bundle + .filter(|bundle| !bundle.is_empty()) + .ok_or_else(|| anyhow::anyhow!("mandatory DKG attestation bundle missing"))?, + ); // RPC may not have synced finalization yet send_tx_with_retry("publishCommittee", &["CommitteeNotFinalized"], || { diff --git a/crates/evm/src/contracts.rs b/crates/evm/src/contracts.rs index 5830c3b8e..f9b8395da 100644 --- a/crates/evm/src/contracts.rs +++ b/crates/evm/src/contracts.rs @@ -36,7 +36,6 @@ sol! { bytes32 ciphertextOutput; bytes plaintextOutput; address requester; - bool proofAggregationEnabled; } // ── Write functions ───────────────────────────────────────────────── diff --git a/crates/evm/src/interfold/events.rs b/crates/evm/src/interfold/events.rs index 0a068171b..4f90e1ce8 100644 --- a/crates/evm/src/interfold/events.rs +++ b/crates/evm/src/interfold/events.rs @@ -93,7 +93,6 @@ impl E3RequestedWithChainId { seed: self.0.e3.seed.into(), error_size, e3_id: E3id::new(self.0.e3Id.to_string(), self.1), - proof_aggregation_enabled: self.0.e3.proofAggregationEnabled, }) } } @@ -571,7 +570,6 @@ mod tests { ciphertextOutput: B256::ZERO, plaintextOutput: Bytes::new(), requester: Address::ZERO, - proofAggregationEnabled: false, }, e3Program: Address::ZERO, }; diff --git a/crates/evm/src/interfold_writing/effects.rs b/crates/evm/src/interfold_writing/effects.rs index 006e4a4bf..246fbec20 100644 --- a/crates/evm/src/interfold_writing/effects.rs +++ b/crates/evm/src/interfold_writing/effects.rs @@ -15,11 +15,10 @@ pub(in crate::actors::interfold_sol_writer) async fn publish_plaintext_output< ) -> Result { let e3_id: U256 = e3_id.try_into()?; - // `None` means proof aggregation is disabled for this predeployment E3. - let proof: Bytes = match decryption_aggregator_proof { - Some(p) => encode_zk_proof(p)?, - None => Bytes::new(), - }; + // Skip mode creates a non-empty mock-only C7 placeholder before this boundary. + let proof = encode_zk_proof(decryption_aggregator_proof.ok_or_else(|| { + anyhow::anyhow!("mandatory decryption aggregator proof payload missing") + })?)?; send_tx_with_retry( "publishPlaintextOutput", diff --git a/crates/evm/src/interfold_writing/workflow.rs b/crates/evm/src/interfold_writing/workflow.rs index abd454ea0..a93da059f 100644 --- a/crates/evm/src/interfold_writing/workflow.rs +++ b/crates/evm/src/interfold_writing/workflow.rs @@ -18,8 +18,8 @@ use e3_events::CircuitName; /// Validate a decrypted result before it is written on-chain. /// -/// Returns `Ok(())` when exactly one decrypted output is present and (when -/// proof aggregation is enabled) the proof count matches the output count. +/// Returns `Ok(())` when exactly one decrypted output is present and the non-empty proof list +/// matches the output count. /// Returns a human-readable error message otherwise. pub(crate) fn validate_plaintext_output( e3_id: &E3id, @@ -38,11 +38,13 @@ pub(crate) fn validate_plaintext_output( decrypted_output.len() )); } - // `decryption_aggregator_proofs` is empty when proof aggregation is disabled. - // When enabled, its length must match `decrypted_output`. - if !decryption_aggregator_proofs.is_empty() - && decrypted_output.len() != decryption_aggregator_proofs.len() - { + if decryption_aggregator_proofs.is_empty() { + return Err(format!( + "E3 {} has no decryption aggregator proof payload", + e3_id + )); + } + if decrypted_output.len() != decryption_aggregator_proofs.len() { return Err(format!( "E3 {} decrypted_output len ({}) != decryption_aggregator_proofs len ({})", e3_id, @@ -86,8 +88,9 @@ mod tests { } #[test] - fn accepts_single_output_no_proofs() { - assert!(validate_plaintext_output(&e3(), &bytes(1), &[]).is_ok()); + fn rejects_single_output_without_proof() { + let err = validate_plaintext_output(&e3(), &bytes(1), &[]).unwrap_err(); + assert!(err.contains("no decryption aggregator proof")); } #[test] diff --git a/crates/keyshare/src/ext.rs b/crates/keyshare/src/ext.rs index 82711d5f5..2c5dcecd4 100644 --- a/crates/keyshare/src/ext.rs +++ b/crates/keyshare/src/ext.rs @@ -66,7 +66,6 @@ impl E3Extension for ThresholdKeyshareExtension { meta.threshold_n as u64, meta.params.clone(), self.address.clone(), - meta.proof_aggregation_enabled, ))); // New container with None diff --git a/crates/keyshare/src/threshold_keyshare/effects/generate_threshold_share.rs b/crates/keyshare/src/threshold_keyshare/effects/generate_threshold_share.rs index 3ccbf11dd..afdee9f16 100644 --- a/crates/keyshare/src/threshold_keyshare/effects/generate_threshold_share.rs +++ b/crates/keyshare/src/threshold_keyshare/effects/generate_threshold_share.rs @@ -140,12 +140,6 @@ impl ThresholdKeyshare { // Cache own plaintext share rows for the AggregatingDecryptionKey transition. self.pending.own_dkg_shares = Some((plan.own_sk_share_raw, plan.own_esi_shares_raw)); - let proof_aggregation_enabled = self - .state - .try_get() - .map(|s| s.proof_aggregation_enabled) - .unwrap_or(true); - info!("Publishing ThresholdSharePending for E3 {}", e3_id); // Publish ThresholdSharePending - ProofRequestActor will generate proof, sign, and publish ThresholdShareCreated @@ -159,7 +153,6 @@ impl ThresholdKeyshare { sk_share_encryption_requests: plan.sk_share_encryption_requests, e_sm_share_encryption_requests: plan.e_sm_share_encryption_requests, recipient_party_ids: plan.recipient_party_ids, - proof_aggregation_enabled, }, ec, )?; diff --git a/crates/keyshare/src/threshold_keyshare/state.rs b/crates/keyshare/src/threshold_keyshare/state.rs index 4d3ea4aa0..c6457eedf 100644 --- a/crates/keyshare/src/threshold_keyshare/state.rs +++ b/crates/keyshare/src/threshold_keyshare/state.rs @@ -195,7 +195,6 @@ pub struct ThresholdKeyshareState { /// Downstream proof circuits index parties by position in this sorted set. pub honest_parties: Option>, pub dkg_started_at_unix_secs: Option, - pub proof_aggregation_enabled: bool, /// Set once `KeyshareCreated` has actually been published from an authorized /// path (after C4 honest-set verification, the no-C4-proofs path, or the /// sole-honest fast path). `ReadyForDecryption` is entered *before* that @@ -214,7 +213,6 @@ impl ThresholdKeyshareState { threshold_n: u64, params: ArcBytes, address: String, - proof_aggregation_enabled: bool, ) -> Self { Self { e3_id, @@ -228,7 +226,6 @@ impl ThresholdKeyshareState { expelled_parties: HashSet::new(), honest_parties: None, dkg_started_at_unix_secs: Some(now_unix_secs()), - proof_aggregation_enabled, keyshare_published: false, } } diff --git a/crates/keyshare/src/threshold_keyshare/state_tests.rs b/crates/keyshare/src/threshold_keyshare/state_tests.rs index da18bc72b..1f4b53e21 100644 --- a/crates/keyshare/src/threshold_keyshare/state_tests.rs +++ b/crates/keyshare/src/threshold_keyshare/state_tests.rs @@ -20,7 +20,6 @@ fn base_state(state: KeyshareState) -> ThresholdKeyshareState { 3, arc(b"params"), "0xabc".to_string(), - true, ) } @@ -32,7 +31,6 @@ fn new_initialises_defaults_and_records_dkg_start() { assert!(s.expelled_parties.is_empty()); assert!(s.honest_parties.is_none()); assert!(s.dkg_started_at_unix_secs.is_some()); - assert!(s.proof_aggregation_enabled); assert_eq!(s.get_threshold_m(), 1); assert_eq!(s.get_threshold_n(), 3); assert_eq!(s.get_party_id(), 0); diff --git a/crates/request/src/meta.rs b/crates/request/src/meta.rs index 707a2586c..bc802f5ca 100644 --- a/crates/request/src/meta.rs +++ b/crates/request/src/meta.rs @@ -22,7 +22,6 @@ pub struct E3Meta { pub params_preset: BfvPreset, pub params: ArcBytes, pub error_size: ArcBytes, - pub proof_aggregation_enabled: bool, } pub struct E3MetaExtension; @@ -47,7 +46,6 @@ impl E3Extension for E3MetaExtension { params_preset, params, error_size, - proof_aggregation_enabled, .. } = data.clone(); @@ -59,7 +57,6 @@ impl E3Extension for E3MetaExtension { params_preset, params, error_size, - proof_aggregation_enabled, }; ctx.repositories().meta(&e3_id).write(&meta); ctx.set_dependency(META_KEY, meta); diff --git a/crates/slashing/src/commitment_consistency_checker_ext.rs b/crates/slashing/src/commitment_consistency_checker_ext.rs index a1cec584b..3b1a4e036 100644 --- a/crates/slashing/src/commitment_consistency_checker_ext.rs +++ b/crates/slashing/src/commitment_consistency_checker_ext.rs @@ -140,7 +140,6 @@ mod tests { params_preset: BfvPreset::InsecureThreshold512, params: ArcBytes::from_bytes(&[]), error_size: ArcBytes::from_bytes(&[]), - proof_aggregation_enabled: false, } } diff --git a/crates/sortition/src/ciphernode_selection/actor.rs b/crates/sortition/src/ciphernode_selection/actor.rs index 818625525..3ca2bb74a 100644 --- a/crates/sortition/src/ciphernode_selection/actor.rs +++ b/crates/sortition/src/ciphernode_selection/actor.rs @@ -37,7 +37,6 @@ fn e3_meta_from(req: &E3Requested) -> E3Meta { params_preset: req.params_preset, params: req.params.clone(), error_size: req.error_size.clone(), - proof_aggregation_enabled: req.proof_aggregation_enabled, } } diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 216b1a6b3..d19c2c14a 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -130,15 +130,17 @@ fn benchmark_participant_node_count(threshold_m: usize, threshold_n: usize) -> u /// Whether `test_trbfv_actor` runs the full recursive fold + aggregator path (default: on). /// -/// Benchmark harness always enables proof aggregation (`run_benchmarks.sh` exports `true`). +/// CI may set the ciphernode test flag to skip recursive aggregation; benchmark scripts explicitly +/// leave it enabled. fn benchmark_proof_aggregation_enabled() -> bool { - !matches!( - std::env::var("BENCHMARK_PROOF_AGGREGATION") - .unwrap_or_else(|_| "true".into()) - .to_ascii_lowercase() - .as_str(), - "0" | "false" | "no" | "off" - ) + let value = std::env::var("CIPHERNODE_SKIP_PROOF_AGGREGATION") + .unwrap_or_else(|_| "false".into()) + .to_ascii_lowercase(); + match value.as_str() { + "1" | "true" | "yes" | "on" => false, + "0" | "false" | "no" | "off" => true, + _ => panic!("CIPHERNODE_SKIP_PROOF_AGGREGATION must be a boolean, got {value:?}"), + } } /// Rayon multithread pool concurrency for benchmark runs (`BENCHMARK_MULTITHREAD_JOBS`, default 1). @@ -1424,6 +1426,7 @@ async fn test_trbfv_actor() -> Result<()> { // Setup ZK backend for proof generation/verification let (zk_backend, _zk_temp) = setup_test_zk_backend(benchmark_params.preset_subdir).await?; + let proof_aggregation_enabled = benchmark_proof_aggregation_enabled(); let nodes = CiphernodeSystemBuilder::new() // All nodes run the same binary under the aggregator-committee model. @@ -1434,7 +1437,7 @@ async fn test_trbfv_actor() -> Result<()> { let addr = rand_eth_addr(&node_rng); println!("Building collector {}!", addr); { - let b = CiphernodeBuilder::new(node_rng, cipher.clone()) + let mut b = CiphernodeBuilder::new(node_rng, cipher.clone()) .with_history_collector() .with_shared_taskpool(&task_pool) .with_multithread_concurrent_jobs(concurrent_jobs) @@ -1448,6 +1451,9 @@ async fn test_trbfv_actor() -> Result<()> { .with_forked_bus(bus.event_bus()) .with_chains(std::slice::from_ref(&bench_chain_config)) .with_logging(); + if !proof_aggregation_enabled { + b = b.with_proof_aggregation_disabled_for_testing(); + } b.build().await } }) @@ -1458,7 +1464,7 @@ async fn test_trbfv_actor() -> Result<()> { let addr = rand_eth_addr(&node_rng); println!("Building normal {}", &addr); { - let b = CiphernodeBuilder::new(node_rng, cipher.clone()) + let mut b = CiphernodeBuilder::new(node_rng, cipher.clone()) .with_history_collector() .with_shared_taskpool(&task_pool) .with_multithread_concurrent_jobs(concurrent_jobs) @@ -1472,6 +1478,9 @@ async fn test_trbfv_actor() -> Result<()> { .with_forked_bus(bus.event_bus()) .with_chains(std::slice::from_ref(&bench_chain_config)) .with_logging(); + if !proof_aggregation_enabled { + b = b.with_proof_aggregation_disabled_for_testing(); + } b.build().await } }, @@ -1530,7 +1539,6 @@ async fn test_trbfv_actor() -> Result<()> { // Trigger actor DKG let e3_id = E3id::new("0", 1); - let proof_aggregation_enabled = benchmark_proof_aggregation_enabled(); println!( "Benchmark trbfv: proof_aggregation={proof_aggregation_enabled}, preset={}, pool_threads={pool_threads}, max_concurrent_jobs={concurrent_jobs}", benchmark_params.preset_subdir @@ -1544,7 +1552,6 @@ async fn test_trbfv_actor() -> Result<()> { error_size, params_preset: benchmark_params.bfv_preset, params, - proof_aggregation_enabled, }; bus.publish_without_context(e3_requested)?; @@ -1783,6 +1790,17 @@ async fn test_trbfv_actor() -> Result<()> { let pubkey_bytes = pubkey_event.pubkey.clone(); let dkg_aggregator_proof = pubkey_event.dkg_aggregator_proof.clone(); + assert!( + dkg_aggregator_proof.is_some(), + "PublicKeyAggregated must always carry a final DKG proof payload" + ); + assert!( + pubkey_event + .dkg_attestation_bundle + .as_ref() + .is_some_and(|bundle| !bundle.is_empty()), + "PublicKeyAggregated must always carry a non-empty DKG attestation payload" + ); let pubkey = PublicKey::from_bytes(&pubkey_bytes, ¶ms_raw)?; @@ -2051,12 +2069,10 @@ async fn test_trbfv_actor() -> Result<()> { ) })?; - if proof_aggregation_enabled { - assert!( - !decryption_aggregator_proofs.is_empty(), - "DecryptionAggregator proofs should be present in PlaintextAggregated when proof_aggregation_enabled" - ); - } + assert!( + !decryption_aggregator_proofs.is_empty(), + "PlaintextAggregated must always carry a final decryption proof payload" + ); if let Ok(path) = std::env::var("BENCHMARK_FOLDED_OUTPUT") { if let (Some(dkg_proof), Some(dec_proof)) = ( diff --git a/crates/zk-prover/src/actor_system.rs b/crates/zk-prover/src/actor_system.rs index 03761cf68..7a6340d2d 100644 --- a/crates/zk-prover/src/actor_system.rs +++ b/crates/zk-prover/src/actor_system.rs @@ -51,6 +51,7 @@ pub fn setup_zk_actors( signer: PrivateKeySigner, dkg_fold_attestation_verifiers_by_chain: HashMap>, recovery: ZkActorRecovery, + proof_aggregation_enabled: bool, ) -> ZkActors { let ZkActorRecovery { finalized_committees, @@ -59,12 +60,16 @@ pub fn setup_zk_actors( let zk_actor = ZkActor::new(backend).start(); let verifier = zk_actor.clone().recipient(); - let proof_request = ProofRequestActor::setup(bus, signer.clone()); + let proof_request = ProofRequestActor::setup(bus, signer.clone(), proof_aggregation_enabled); let proof_verification = ProofVerificationActor::setup(bus, verifier, finalized_committees.clone(), e3_metadata); let share_verification = ShareVerificationActor::setup(bus, finalized_committees); - let node_proof_aggregator = - NodeProofAggregator::setup(bus, signer, dkg_fold_attestation_verifiers_by_chain); + let node_proof_aggregator = NodeProofAggregator::setup( + bus, + signer, + dkg_fold_attestation_verifiers_by_chain, + proof_aggregation_enabled, + ); ZkActors { zk_actor, diff --git a/crates/zk-prover/src/node_proof_aggregation/actor.rs b/crates/zk-prover/src/node_proof_aggregation/actor.rs index f8d857e48..9ef3e75e6 100644 --- a/crates/zk-prover/src/node_proof_aggregation/actor.rs +++ b/crates/zk-prover/src/node_proof_aggregation/actor.rs @@ -29,6 +29,7 @@ use crate::node_fold_public::extract_node_fold_agg_commits; pub struct NodeProofAggregator { bus: BusHandle, signer: PrivateKeySigner, + proof_aggregation_enabled: bool, /// Per-chain `DkgFoldAttestationVerifier` address (EIP-712 `verifyingContract`). /// Looked up by `e3_id.chain_id()` when signing fold attestations. dkg_fold_attestation_verifiers_by_chain: HashMap>, @@ -42,10 +43,12 @@ impl NodeProofAggregator { bus: &BusHandle, signer: PrivateKeySigner, dkg_fold_attestation_verifiers_by_chain: HashMap>, + proof_aggregation_enabled: bool, ) -> Self { Self { bus: bus.clone(), signer, + proof_aggregation_enabled, dkg_fold_attestation_verifiers_by_chain, states: HashMap::new(), fold_correlation: HashMap::new(), @@ -57,8 +60,15 @@ impl NodeProofAggregator { bus: &BusHandle, signer: PrivateKeySigner, dkg_fold_attestation_verifiers_by_chain: HashMap>, + proof_aggregation_enabled: bool, ) -> Addr { - let addr = Self::new(bus, signer, dkg_fold_attestation_verifiers_by_chain).start(); + let addr = Self::new( + bus, + signer, + dkg_fold_attestation_verifiers_by_chain, + proof_aggregation_enabled, + ) + .start(); bus.subscribe(EventType::ThresholdSharePending, addr.clone().into()); bus.subscribe(EventType::DKGInnerProofReady, addr.clone().into()); bus.subscribe(EventType::ComputeResponse, addr.clone().into()); @@ -112,7 +122,7 @@ mod tests { #[actix::test] async fn node_dkg_fold_compute_error_emits_e3_failed() -> Result<()> { let (bus, _rng, _seed, _params, _crp, _errors, history) = get_common_setup(None)?; - let mut aggregator = NodeProofAggregator::new(&bus, test_signer(), HashMap::new()); + let mut aggregator = NodeProofAggregator::new(&bus, test_signer(), HashMap::new(), true); let e3_id = E3id::new("42", 1); let correlation_id = CorrelationId::new(); @@ -197,7 +207,7 @@ mod tests { #[actix::test] async fn early_inner_proof_is_prebuffered_until_collection_starts() -> Result<()> { let (bus, _rng, _seed, _params, _crp, _errors, history) = get_common_setup(None)?; - let mut aggregator = NodeProofAggregator::new(&bus, test_signer(), HashMap::new()); + let mut aggregator = NodeProofAggregator::new(&bus, test_signer(), HashMap::new(), true); let e3_id = E3id::new("43", 1); let early_proof = dummy_proof(10); diff --git a/crates/zk-prover/src/node_proof_aggregation/effects.rs b/crates/zk-prover/src/node_proof_aggregation/effects.rs index 60a991cff..21028edb3 100644 --- a/crates/zk-prover/src/node_proof_aggregation/effects.rs +++ b/crates/zk-prover/src/node_proof_aggregation/effects.rs @@ -12,10 +12,10 @@ impl NodeProofAggregator { let (msg, ec) = msg.into_components(); let e3_id = msg.e3_id.clone(); - if !msg.proof_aggregation_enabled { + if !self.proof_aggregation_enabled { self.pending_inner_proofs.remove(&e3_id); info!( - "NodeProofAggregator: proof aggregation disabled for E3 {} — skipping", + "NodeProofAggregator: test/CI skip flag active for E3 {}", e3_id ); if let Err(err) = self.bus.publish( @@ -28,7 +28,7 @@ impl NodeProofAggregator { ec, ) { error!( - "NodeProofAggregator: failed to publish DKGRecursiveAggregationComplete (skipped) for E3 {}: {err}", + "NodeProofAggregator: failed to publish skipped DKGRecursiveAggregationComplete for E3 {}: {err}", e3_id ); } diff --git a/crates/zk-prover/src/proof_request/actor.rs b/crates/zk-prover/src/proof_request/actor.rs index 6a72f6946..05224cdc9 100644 --- a/crates/zk-prover/src/proof_request/actor.rs +++ b/crates/zk-prover/src/proof_request/actor.rs @@ -39,6 +39,7 @@ use crate::workflow::proof_request::{ pub struct ProofRequestActor { bus: BusHandle, signer: PrivateKeySigner, + proof_aggregation_enabled: bool, pending: HashMap, threshold_correlation: HashMap, pending_threshold: HashMap, @@ -63,10 +64,11 @@ pub struct ProofRequestActor { } impl ProofRequestActor { - pub fn new(bus: &BusHandle, signer: PrivateKeySigner) -> Self { + pub fn new(bus: &BusHandle, signer: PrivateKeySigner, proof_aggregation_enabled: bool) -> Self { Self { bus: bus.clone(), signer, + proof_aggregation_enabled, pending: HashMap::new(), pending_threshold: HashMap::new(), threshold_correlation: HashMap::new(), @@ -82,8 +84,12 @@ impl ProofRequestActor { } } - pub fn setup(bus: &BusHandle, signer: PrivateKeySigner) -> Addr { - let addr = Self::new(bus, signer).start(); + pub fn setup( + bus: &BusHandle, + signer: PrivateKeySigner, + proof_aggregation_enabled: bool, + ) -> Addr { + let addr = Self::new(bus, signer, proof_aggregation_enabled).start(); bus.subscribe(EventType::EncryptionKeyPending, addr.clone().into()); bus.subscribe(EventType::ComputeResponse, addr.clone().into()); bus.subscribe(EventType::ComputeRequestError, addr.clone().into()); diff --git a/crates/zk-prover/src/proof_request/actor_tests.rs b/crates/zk-prover/src/proof_request/actor_tests.rs index c9865f889..12015fd3c 100644 --- a/crates/zk-prover/src/proof_request/actor_tests.rs +++ b/crates/zk-prover/src/proof_request/actor_tests.rs @@ -27,7 +27,7 @@ async fn next_event(history: &Addr>) -> Result< #[actix::test] async fn c0_compute_error_emits_e3_failed() -> Result<()> { let (bus, _rng, _seed, _params, _crp, _errors, history) = get_common_setup(None)?; - let mut actor = ProofRequestActor::new(&bus, PrivateKeySigner::random()); + let mut actor = ProofRequestActor::new(&bus, PrivateKeySigner::random(), true); let e3_id = E3id::new("44", 1); let correlation_id = CorrelationId::new(); @@ -75,7 +75,7 @@ async fn c0_compute_error_emits_e3_failed() -> Result<()> { #[actix::test] async fn decryption_failure_helper_emits_e3_failed() -> Result<()> { let (bus, _rng, _seed, _params, _crp, _errors, history) = get_common_setup(None)?; - let actor = ProofRequestActor::new(&bus, PrivateKeySigner::random()); + let actor = ProofRequestActor::new(&bus, PrivateKeySigner::random(), true); let e3_id = E3id::new("45", 1); actor.fail_decryption_round( diff --git a/crates/zk-prover/src/proof_request/effects/decryption_key_proofs.rs b/crates/zk-prover/src/proof_request/effects/decryption_key_proofs.rs index 1674c3c16..6a203e62b 100644 --- a/crates/zk-prover/src/proof_request/effects/decryption_key_proofs.rs +++ b/crates/zk-prover/src/proof_request/effects/decryption_key_proofs.rs @@ -94,7 +94,7 @@ impl ProofRequestActor { } if let Some(meta) = self.node_agg_meta.get(&e3_id) { - if meta.proof_aggregation_enabled { + if self.proof_aggregation_enabled { if let Err(err) = self.bus.publish( DKGInnerProofReady { e3_id: e3_id.clone(), diff --git a/crates/zk-prover/src/proof_request/effects/dkg_proofs.rs b/crates/zk-prover/src/proof_request/effects/dkg_proofs.rs index 9b33d4030..9352ee10d 100644 --- a/crates/zk-prover/src/proof_request/effects/dkg_proofs.rs +++ b/crates/zk-prover/src/proof_request/effects/dkg_proofs.rs @@ -57,12 +57,11 @@ impl ProofRequestActor { party_id: msg.full_share.party_id, total_expected, pending_c0: None, - proof_aggregation_enabled: msg.proof_aggregation_enabled, }, ); // If C0 proof arrived before meta, emit DKGInnerProofReady now - if let Some(c0_proof) = pending_c0 { - if msg.proof_aggregation_enabled { + if self.proof_aggregation_enabled { + if let Some(c0_proof) = pending_c0 { if let Err(err) = self.bus.publish( DKGInnerProofReady { e3_id: e3_id.clone(), diff --git a/crates/zk-prover/src/proof_request/effects/encryption_key_result.rs b/crates/zk-prover/src/proof_request/effects/encryption_key_result.rs index a821e54a0..6762865b3 100644 --- a/crates/zk-prover/src/proof_request/effects/encryption_key_result.rs +++ b/crates/zk-prover/src/proof_request/effects/encryption_key_result.rs @@ -89,7 +89,7 @@ impl ProofRequestActor { // Emit DKGInnerProofReady for C0, or buffer if meta not yet available if let Some(meta) = self.node_agg_meta.get(&e3_id) { - if meta.proof_aggregation_enabled { + if self.proof_aggregation_enabled { if let Err(err) = self.bus.publish( DKGInnerProofReady { e3_id: e3_id.clone(), @@ -110,7 +110,6 @@ impl ProofRequestActor { party_id: 0, total_expected: 0, pending_c0: Some(proof), - proof_aggregation_enabled: true, // will be overwritten by ThresholdSharePending }, ); } diff --git a/crates/zk-prover/src/proof_request/effects/signing.rs b/crates/zk-prover/src/proof_request/effects/signing.rs index 8f2704677..c7815da67 100644 --- a/crates/zk-prover/src/proof_request/effects/signing.rs +++ b/crates/zk-prover/src/proof_request/effects/signing.rs @@ -34,7 +34,7 @@ impl ProofRequestActor { ); if let Some(meta) = self.node_agg_meta.get(&e3_id) { - if meta.proof_aggregation_enabled { + if self.proof_aggregation_enabled { if let Err(err) = self.bus.publish( DKGInnerProofReady { e3_id: e3_id.clone(), diff --git a/crates/zk-prover/src/proof_request/state.rs b/crates/zk-prover/src/proof_request/state.rs index 54c9253bf..924d6705d 100644 --- a/crates/zk-prover/src/proof_request/state.rs +++ b/crates/zk-prover/src/proof_request/state.rs @@ -35,8 +35,6 @@ pub(crate) struct NodeAggregationMeta { pub(crate) total_expected: usize, /// Buffered C0 proof, if it arrived before meta was stored. pub(crate) pending_c0: Option, - /// When false, skip emitting DKGInnerProofReady (no recursive DKG aggregation). - pub(crate) proof_aggregation_enabled: bool, } impl NodeAggregationMeta { diff --git a/crates/zk-prover/src/proof_request/workflow_tests.rs b/crates/zk-prover/src/proof_request/workflow_tests.rs index b9960f899..0185994cc 100644 --- a/crates/zk-prover/src/proof_request/workflow_tests.rs +++ b/crates/zk-prover/src/proof_request/workflow_tests.rs @@ -191,7 +191,6 @@ fn node_agg_meta_seq_helpers() { party_id: 0, total_expected: NodeAggregationMeta::total_expected_for(2, 1), pending_c0: None, - proof_aggregation_enabled: true, }; // c4_base_seq sits just after C0..C3 = total_expected - 2. assert_eq!(meta.c4_base_seq(), 4 + 2 + 1); diff --git a/crates/zk-prover/src/proof_verification/actor_tests.rs b/crates/zk-prover/src/proof_verification/actor_tests.rs index e57acd8c6..9c63036c1 100644 --- a/crates/zk-prover/src/proof_verification/actor_tests.rs +++ b/crates/zk-prover/src/proof_verification/actor_tests.rs @@ -134,7 +134,6 @@ async fn restored_context_dispatches_c0_without_replayed_lifecycle_events() { params_preset: preset, params: ArcBytes::default(), error_size: ArcBytes::default(), - proof_aggregation_enabled: false, }, )]); let (observed_tx, mut observed_rx) = mpsc::unbounded_channel(); diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx index 135dd20d8..893bc99f2 100644 --- a/docs/pages/cryptography.mdx +++ b/docs/pages/cryptography.mdx @@ -208,8 +208,8 @@ expected commitments match outputs) is only checked off-chain, end-to-end correctness still depends on trusting the committee network rather than on verifiable mathematics. -The aggregation design addresses both problems. When an E3 is requested with -`proofAggregationEnabled = true`, only **two recursive proofs** are posted on-chain per E3 session: +The aggregation design addresses both problems. Only **two recursive proofs** are posted on-chain +per E3 session: 1. **DKG recursive proof**: the aggregator recursively verifies every node's C0–C4 fold chain together with **C5**, which aggregates the public key. Verified on-chain by diff --git a/docs/pages/internals/dkg.mdx b/docs/pages/internals/dkg.mdx index dba119332..6c9a1c5aa 100644 --- a/docs/pages/internals/dkg.mdx +++ b/docs/pages/internals/dkg.mdx @@ -330,10 +330,14 @@ aggregator. ## Proof Aggregation and On-Chain Anchoring -E3 programs can opt into **proof aggregation** by setting `proofAggregationEnabled = true` at -request time. In that mode the network does not post per-node, per-circuit proofs on-chain. Instead -the aggregator submits two recursive proofs — one for DKG, one for decryption — together with -per-node attestations that bind each surfaced commitment to a specific registered operator. +**Proof aggregation is mandatory at the contract boundary.** The network does not post per-node, +per-circuit proofs on-chain. Instead the aggregator submits two recursive proofs — one for DKG, one +for decryption — together with per-node attestations that bind each surfaced commitment to a +specific registered operator. + +Ciphernodes expose a test/CI-only `skip_proof_aggregation` node setting. It skips the recursive +workers and reuses the already-generated C5/C7 proofs as non-empty placeholders for mock verifiers. +Contracts still execute their verifier calls, so this flag cannot weaken a production deployment. ### Canonical party_id assignment @@ -494,10 +498,10 @@ Production deploys on a non-canonical preset must regenerate locally for that de The repo enforces sync with two modes of `pnpm generate:verifiers`: - **`--check`** — used by `tests/integration/lib/prebuild.sh` (when - `PROOF_AGGREGATION_ENABLED=true`) and by the benchmark gas-extraction scripts. Regenerates each - verifier in memory, runs the same `prettier-plugin-solidity` formatting the committed files use, - and diffs against the committed `.sol`. Any drift fails the run with a clear fix recipe instead of - silently rewriting committed contracts mid-test. + `CIPHERNODE_SKIP_PROOF_AGGREGATION=false`) and by the benchmark gas-extraction scripts. + Regenerates each verifier in memory, runs the same `prettier-plugin-solidity` formatting the + committed files use, and diffs against the committed `.sol`. Any drift fails the run with a clear + fix recipe instead of silently rewriting committed contracts mid-test. - **`--write`** (the default for manual invocation) — overwrites the committed files. Run this when you intentionally bump the canonical-preset circuits or the Noir/bb toolchain, then commit the diff. diff --git a/examples/CRISP/Readme.md b/examples/CRISP/Readme.md index ec0622da8..85bf88dd6 100644 --- a/examples/CRISP/Readme.md +++ b/examples/CRISP/Readme.md @@ -51,7 +51,7 @@ The simplest way to run CRISP is: ```bash # Optional: choose local profile (copied to crisp.dev.env on first setup) cp crisp.dev.env.example crisp.dev.env -# Edit CRISP_PROOF_AGGREGATION_ENABLED and CRISP_BFV_PRESET (see docs/PROOF_AGGREGATION_AND_ZK.md) +# Edit CRISP_SKIP_PROOF_AGGREGATION and CRISP_BFV_PRESET (see docs/PROOF_AGGREGATION_AND_ZK.md) # Install dependencies and build everything (applies crisp.dev.env → server/.env) pnpm dev:setup @@ -184,13 +184,13 @@ After `pnpm dev:up`, contract addresses are written automatically to `interfold. Edit **`crisp.dev.env`** (created from `crisp.dev.env.example` on first `pnpm dev:setup`): -| Variable | Default | Effect | -| --------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------- | -| `CRISP_BFV_PRESET` | `insecure-512` | DKG circuit build preset when aggregation is on | -| `CRISP_PROOF_AGGREGATION_ENABLED` | `false` | Synced to `server/.env`; controls DKG circuit build, deploy (`ENABLE_ZK_VERIFICATION`), and runtime aggregation | +| Variable | Default | Effect | +| ------------------------------ | -------------- | --------------------------------------------------------------------- | +| `CRISP_BFV_PRESET` | `insecure-512` | DKG circuit build preset for full aggregation | +| `CRISP_SKIP_PROOF_AGGREGATION` | `true` | Ciphernode-only local-dev skip; also selects mock verifier deployment | -`pnpm dev:setup` applies this profile (build DKG circuits when needed, sync `server/.env`). -`pnpm dev:up` deploys contracts using the same flags. +`pnpm dev:setup` applies this profile and builds recursive circuits only when needed. `pnpm dev:up` +deploys contracts using the same flags. See **[docs/PROOF_AGGREGATION_AND_ZK.md](./docs/PROOF_AGGREGATION_AND_ZK.md)** for modes, address sync, and troubleshooting (`VkHashMismatch`, etc.). diff --git a/examples/CRISP/crisp.dev.env.example b/examples/CRISP/crisp.dev.env.example index 7ffe9a963..9c89740a6 100644 --- a/examples/CRISP/crisp.dev.env.example +++ b/examples/CRISP/crisp.dev.env.example @@ -9,9 +9,9 @@ # secure-8192 -> param_set 1 (production-grade; slower proving) CRISP_BFV_PRESET=insecure-512 -# When true: +# When false: # - dev:setup runs `pnpm build:circuits --preset ` # - dev:up deploy sets ENABLE_ZK_VERIFICATION=true (BfvPkVerifier, fold attestations) -# - server/.env gets E3_PROOF_AGGREGATION_ENABLED=true -# When false (default): mock verifiers, no DKG circuit build, faster DKG. -CRISP_PROOF_AGGREGATION_ENABLED=false +# - ciphernodes run recursive proof aggregation +# When true (default): mock verifiers, no recursive aggregation build, faster DKG. +CRISP_SKIP_PROOF_AGGREGATION=true diff --git a/examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md b/examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md index 571f59beb..730ffbe4c 100644 --- a/examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md +++ b/examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md @@ -4,26 +4,28 @@ **Source of truth for local dev:** `examples/CRISP/crisp.dev.env` (from `crisp.dev.env.example`). -| Variable | Default | Effect | -| --------------------------------- | -------------- | ----------------------------------------------------- | -| `CRISP_BFV_PRESET` | `insecure-512` | `pnpm build:circuits --preset` when aggregation is on | -| `CRISP_PROOF_AGGREGATION_ENABLED` | `false` | Drives setup, deploy, and `server/.env` | +| Variable | Default | Effect | +| ------------------------------ | -------------- | ---------------------------------------------------- | +| `CRISP_BFV_PRESET` | `insecure-512` | `pnpm build:circuits --preset` for full aggregation | +| `CRISP_SKIP_PROOF_AGGREGATION` | `true` | Enables the ciphernode-only local-dev/test skip flag | -`pnpm dev:setup` copies the example if missing, syncs `E3_PROOF_AGGREGATION_ENABLED` into -`server/.env`, and builds DKG circuits only when aggregation is `true`. `pnpm dev:up` → -`crisp_deploy.sh` sets `ENABLE_ZK_VERIFICATION` from the same file. +`pnpm dev:setup` copies the example if missing and builds recursive circuits only when the skip flag +is `false`. `pnpm dev:up` → `crisp_deploy.sh` deploys real verifiers for full aggregation and mock +verifiers for skipped local-dev runs. After changing `crisp.dev.env`, re-run `pnpm dev:setup` and a fresh `pnpm dev:up` (wipe `.interfold/data` when switching modes). Lower-level switches (kept in sync by the scripts): -| Switch | Where | Effect | -| ------------------------------ | ---------------------------------------------------- | ------------------------------- | -| `E3_PROOF_AGGREGATION_ENABLED` | `server/.env` (managed by setup) | Passed to `Interfold.requestE3` | -| `ENABLE_ZK_VERIFICATION` | Set at deploy from `CRISP_PROOF_AGGREGATION_ENABLED` | Real vs mock BFV verifiers | +| Switch | Where | Effect | +| --------------------------------------- | ---------------------- | --------------------------------------- | +| `E3_NODES__CN*__SKIP_PROOF_AGGREGATION` | Ciphernode environment | Skips recursive aggregation workers | +| `ENABLE_ZK_VERIFICATION` | Deploy environment | Selects real vs mock on-chain verifiers | -Misalignment causes `publishCommittee` to revert with **`VkHashMismatch()`** (`0x0c260259`). +The contracts always require and verify final proof payloads. Skip mode works only because the local +deployment uses mock verifiers; enabling the skip flag against production verifiers causes +publication to fail. --- @@ -37,14 +39,14 @@ verifier checks. ```bash # crisp.dev.env CRISP_BFV_PRESET=insecure-512 -CRISP_PROOF_AGGREGATION_ENABLED=false +CRISP_SKIP_PROOF_AGGREGATION=true ``` ### Steps ```bash # From examples/CRISP -pnpm dev:setup # once — skips DKG circuit build, syncs server/.env +pnpm dev:setup # once — skips recursive aggregation circuit build pnpm dev:up ``` @@ -59,8 +61,8 @@ pnpm cli init ### What you should see - Ciphernodes skip long `NodeDkgFold` / `zk_dkg_aggregation` runs -- `publishCommittee` succeeds without a DKG Honk proof (empty `proof` bytes are allowed when - aggregation is disabled on the E3) +- `publishCommittee` still verifies a non-empty ciphernode placeholder through the configured mock + PK and fold-attestation verifiers - `POST /rounds/current` returns 200 once the indexer has recorded the round --- @@ -75,7 +77,7 @@ Honk proof, and `BfvPkVerifier` checks at `publishCommittee`. ```bash # crisp.dev.env CRISP_BFV_PRESET=insecure-512 -CRISP_PROOF_AGGREGATION_ENABLED=true +CRISP_SKIP_PROOF_AGGREGATION=false ``` CRISP `requestE3` still uses on-chain `param_set = 0` (`InsecureThreshold512`) unless you change the @@ -86,7 +88,7 @@ server — keep `CRISP_BFV_PRESET=insecure-512` for the default Minimum committe ```bash cd examples/CRISP # Edit crisp.dev.env (or crisp.dev.env.example → crisp.dev.env) as above -pnpm dev:setup # builds DKG circuits + syncs server/.env +pnpm dev:setup # builds DKG circuits rm -rf .interfold/data # required when switching from Mode A pnpm dev:up # deploy with ENABLE_ZK_VERIFICATION=true pnpm cli init @@ -112,11 +114,11 @@ proving). ## Invalid combinations -| Deploy | `E3_PROOF_AGGREGATION_ENABLED` | Result | -| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Mock (`ENABLE_ZK_VERIFICATION` unset) | `true` | Ciphernodes generate real aggregation proofs, but `Interfold.pkVerifier` is `MockPkVerifier`. Attestation path may still run if a previous ZK deploy left `DkgFoldAttestationVerifier` on the registry from stale `deployed_contracts.json` on a **fresh** Anvil — always use `clean:deployments` + fresh chain. Prefer wiping `.interfold/data` and setting aggregation `false` for mock deploy. | -| ZK (`ENABLE_ZK_VERIFICATION=true`) | `false` | Valid but skips on-chain DKG proof verification; committee publication uses empty proof bytes. | -| ZK, circuits recompiled **after** deploy | `true` | **`VkHashMismatch()`** at `publishCommittee` — redeploy `BfvPkVerifier` (full ZK deploy) after `pnpm compile:circuits`. | +| Deploy | Ciphernode skip flag | Result | +| ---------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- | +| Mock verifiers | `false` | Valid, but unnecessarily performs the expensive recursive workflow. | +| Production verifiers | `true` | Final C5/C7 placeholders are rejected; no contract-side bypass exists. | +| ZK, circuits recompiled **after** deploy | `false` | **`VkHashMismatch()`**; redeploy the production verifier wrappers after rebuilding circuits. | --- @@ -126,7 +128,7 @@ proving). - `interfold.config.yaml` (ciphernode contract watches) - `server/.env` (`INTERFOLD_ADDRESS`, `E3_PROGRAM_ADDRESS`, `CRISP_VOTING_TOKEN`, registry, fee - token, mock refs, `E3_PROOF_AGGREGATION_ENABLED` from `crisp.dev.env`) + token, and mock references) - `client/.env` (`VITE_CRISP_TOKEN`) No manual copy from `deployed_contracts.json` is required. Stale addresses only happen if you skip @@ -144,7 +146,7 @@ deploy). **Fix:** -1. Set `CRISP_PROOF_AGGREGATION_ENABLED=true` in `crisp.dev.env` (and matching `CRISP_BFV_PRESET`) +1. Set `CRISP_SKIP_PROOF_AGGREGATION=false` in `crisp.dev.env` (and matching `CRISP_BFV_PRESET`) 2. `pnpm dev:setup` then `rm -rf .interfold/data && pnpm dev:up` 3. `pnpm cli init` @@ -170,10 +172,10 @@ RPC is running. Harmless for CRISP-on-Anvil. ## Reference: what the scripts do -| Step | Mode A (`CRISP_PROOF_AGGREGATION_ENABLED=false`) | Mode B (`=true`) | -| ---------------- | ----------------------------------------------------------------- | --------------------------------------------------------- | -| `pnpm dev:setup` | Skips `build:circuits`; sets aggregation `false` in `server/.env` | `pnpm build:circuits --preset …`; sets aggregation `true` | -| `pnpm dev:up` | Mock BFV verifiers | `ENABLE_ZK_VERIFICATION=true` + prints env vars | +| Step | Mode A (`CRISP_SKIP_PROOF_AGGREGATION=true`) | Mode B (`=false`) | +| ---------------- | -------------------------------------------- | --------------------------------------------- | +| `pnpm dev:setup` | Skips recursive circuit builds | `pnpm build:circuits --preset …` | +| `pnpm dev:up` | Mock PK/decryption/fold verifiers | Production verifiers + full recursive proving | See also: `packages/interfold-contracts/scripts/deployInterfold.ts`, `packages/interfold-contracts/contracts/verifiers/bfv/BfvPkVerifier.sol`, and diff --git a/examples/CRISP/interfold.config.yaml b/examples/CRISP/interfold.config.yaml index 160028964..951fd8c46 100644 --- a/examples/CRISP/interfold.config.yaml +++ b/examples/CRISP/interfold.config.yaml @@ -45,6 +45,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 + # # Test/CI only. Requires mock on-chain proof verifiers. + # skip_proof_aggregation: false nodes: cn1: address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' diff --git a/examples/CRISP/packages/crisp-contracts/README.md b/examples/CRISP/packages/crisp-contracts/README.md index 10cb09549..5a67cb52b 100644 --- a/examples/CRISP/packages/crisp-contracts/README.md +++ b/examples/CRISP/packages/crisp-contracts/README.md @@ -25,8 +25,7 @@ pnpm test Local deploy is driven by **`../../crisp.dev.env`** (see **[../../docs/PROOF_AGGREGATION_AND_ZK.md](../../docs/PROOF_AGGREGATION_AND_ZK.md)**): -- `pnpm dev:setup` — applies profile, builds DKG circuits when - `CRISP_PROOF_AGGREGATION_ENABLED=true` +- `pnpm dev:setup` — applies profile, builds DKG circuits when `CRISP_SKIP_PROOF_AGGREGATION=false` - `pnpm dev:up` → `scripts/crisp_deploy.sh` — sets `ENABLE_ZK_VERIFICATION` from the same file ### CRISP-only deploy (Interfold already deployed) diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockInterfold.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockInterfold.sol index 12300af7f..c8ce8b962 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockInterfold.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockInterfold.sol @@ -34,8 +34,7 @@ contract MockInterfold { committeePublicKey: committeePublicKey, ciphertextOutput: bytes32(0), plaintextOutput: plaintextOutput, - requester: address(0), - proofAggregationEnabled: false + requester: address(0) }); IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2, 0, 0)); @@ -71,8 +70,7 @@ contract MockInterfold { committeePublicKey: committeePublicKey, ciphertextOutput: bytes32(0), plaintextOutput: plaintextOutput, - requester: address(0), - proofAggregationEnabled: false + requester: address(0) }); } } diff --git a/examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts b/examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts index 73e62e26e..ae676923a 100644 --- a/examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts +++ b/examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts @@ -16,25 +16,6 @@ const __dirname = path.dirname(__filename) /** examples/CRISP */ const CRISP_ROOT = path.join(__dirname, '..', '..', '..') -function parseSimpleEnvFile(filePath: string): Record { - if (!fs.existsSync(filePath)) { - return {} - } - const out: Record = {} - for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { - const trimmed = line.trim() - if (!trimmed || trimmed.startsWith('#')) { - continue - } - const eq = trimmed.indexOf('=') - if (eq === -1) { - continue - } - out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim() - } - return out -} - function ensureEnvFile(envPath: string, examplePath: string): void { if (!fs.existsSync(envPath)) { if (!fs.existsSync(examplePath)) { @@ -66,10 +47,7 @@ function deploymentAddress(contractName: string, chain: string): string | undefi return readDeploymentArgs(contractName, chain)?.address } -/** - * Writes localhost deployment addresses into server/.env and client/.env, and - * syncs E3_PROOF_AGGREGATION_ENABLED from crisp.dev.env. - */ +/** Writes localhost deployment addresses into server/.env and client/.env. */ export function syncCrispEnvFromDeployments(chain: string): void { const interfoldAddress = deploymentAddress('Interfold', chain) const feeTokenAddress = deploymentAddress('MockUSDC', chain) @@ -88,12 +66,6 @@ export function syncCrispEnvFromDeployments(chain: string): void { throw new Error(`Cannot sync CRISP .env files: missing deployments for ${missing.join(', ')} on chain "${chain}"`) } - const crispDev = parseSimpleEnvFile(path.join(CRISP_ROOT, 'crisp.dev.env')) - const proofAggregation = - crispDev.CRISP_PROOF_AGGREGATION_ENABLED ?? - parseSimpleEnvFile(path.join(CRISP_ROOT, 'crisp.dev.env.example')).CRISP_PROOF_AGGREGATION_ENABLED ?? - 'false' - const serverEnv = path.join(CRISP_ROOT, 'server', '.env') const clientEnv = path.join(CRISP_ROOT, 'client', '.env') @@ -106,7 +78,6 @@ export function syncCrispEnvFromDeployments(chain: string): void { E3_PROGRAM_ADDRESS: programAddress!, CIPHERNODE_REGISTRY_ADDRESS: registryAddress!, CRISP_VOTING_TOKEN: votingTokenAddress!, - E3_PROOF_AGGREGATION_ENABLED: proofAggregation, } const mockMappings: Array<[string, string]> = [ @@ -129,5 +100,4 @@ export function syncCrispEnvFromDeployments(chain: string): void { console.log(`Synced deployment addresses → ${path.relative(CRISP_ROOT, serverEnv)}`) console.log(`Synced VITE_CRISP_TOKEN → ${path.relative(CRISP_ROOT, clientEnv)}`) - console.log(` E3_PROOF_AGGREGATION_ENABLED=${proofAggregation} (from crisp.dev.env)`) } diff --git a/examples/CRISP/scripts/crisp_deploy.sh b/examples/CRISP/scripts/crisp_deploy.sh index 8c0846fa5..5b3c8ba5b 100755 --- a/examples/CRISP/scripts/crisp_deploy.sh +++ b/examples/CRISP/scripts/crisp_deploy.sh @@ -19,7 +19,7 @@ cd "${CRISP_ROOT}/packages/crisp-contracts" pnpm clean:deployments --network localhost -if [[ "$CRISP_PROOF_AGGREGATION_ENABLED" == "true" ]]; then +if [[ "$CRISP_SKIP_PROOF_AGGREGATION" == "false" ]]; then export ENABLE_ZK_VERIFICATION=true echo "Deploy: ENABLE_ZK_VERIFICATION=true (BfvPkVerifier + fold attestation)" else diff --git a/examples/CRISP/scripts/lib/dev_config.sh b/examples/CRISP/scripts/lib/dev_config.sh index 19a003b5a..6d2ada07a 100644 --- a/examples/CRISP/scripts/lib/dev_config.sh +++ b/examples/CRISP/scripts/lib/dev_config.sh @@ -21,7 +21,7 @@ load_crisp_dev_config() { set +a CRISP_BFV_PRESET="${CRISP_BFV_PRESET:-insecure-512}" - CRISP_PROOF_AGGREGATION_ENABLED="${CRISP_PROOF_AGGREGATION_ENABLED:-false}" + CRISP_SKIP_PROOF_AGGREGATION="${CRISP_SKIP_PROOF_AGGREGATION:-true}" case "$CRISP_BFV_PRESET" in insecure-512 | secure-8192) ;; @@ -31,33 +31,26 @@ load_crisp_dev_config() { ;; esac - case "$CRISP_PROOF_AGGREGATION_ENABLED" in + case "$CRISP_SKIP_PROOF_AGGREGATION" in true | false) ;; *) - echo "Invalid CRISP_PROOF_AGGREGATION_ENABLED='${CRISP_PROOF_AGGREGATION_ENABLED}' (use true or false)" >&2 + echo "Invalid CRISP_SKIP_PROOF_AGGREGATION='${CRISP_SKIP_PROOF_AGGREGATION}' (use true or false)" >&2 exit 1 ;; esac - if [[ "$CRISP_PROOF_AGGREGATION_ENABLED" == "true" ]]; then - export ENABLE_ZK_VERIFICATION=true - else + if [[ "$CRISP_SKIP_PROOF_AGGREGATION" == "true" ]]; then unset ENABLE_ZK_VERIFICATION - fi - - export CRISP_BFV_PRESET CRISP_PROOF_AGGREGATION_ENABLED CRISP_ROOT REPO_ROOT -} - -_set_env_kv() { - local file=$1 key=$2 value=$3 - if [[ -f "$file" ]] && grep -q "^${key}=" "$file"; then - local tmp - tmp="$(mktemp)" - sed "s|^${key}=.*|${key}=${value}|" "$file" >"$tmp" - mv "$tmp" "$file" else - echo "${key}=${value}" >>"$file" + export ENABLE_ZK_VERIFICATION=true fi + export E3_NODES__CN1__SKIP_PROOF_AGGREGATION="$CRISP_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN2__SKIP_PROOF_AGGREGATION="$CRISP_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN3__SKIP_PROOF_AGGREGATION="$CRISP_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN4__SKIP_PROOF_AGGREGATION="$CRISP_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN5__SKIP_PROOF_AGGREGATION="$CRISP_SKIP_PROOF_AGGREGATION" + + export CRISP_BFV_PRESET CRISP_SKIP_PROOF_AGGREGATION CRISP_ROOT REPO_ROOT } apply_crisp_dev_config_to_server_env() { @@ -65,10 +58,13 @@ apply_crisp_dev_config_to_server_env() { if [[ ! -f "$server_env" ]]; then cp "${CRISP_ROOT}/server/.env.example" "$server_env" fi - _set_env_kv "$server_env" "E3_PROOF_AGGREGATION_ENABLED" "$CRISP_PROOF_AGGREGATION_ENABLED" } build_interfold_circuits_at_setup() { + if [[ "$CRISP_SKIP_PROOF_AGGREGATION" == "true" ]]; then + echo "Skipping recursive proof-aggregation circuit build for the CRISP dev profile." + return 0 + fi local committee="${CRISP_COMMITTEE:-minimum}" echo "Building interfold circuits (preset=${CRISP_BFV_PRESET}, committee=${committee})..." ( @@ -101,9 +97,9 @@ print_crisp_dev_config_summary() { CRISP dev profile (${CRISP_ROOT}/crisp.dev.env): CRISP_BFV_PRESET=${CRISP_BFV_PRESET} - CRISP_PROOF_AGGREGATION_ENABLED=${CRISP_PROOF_AGGREGATION_ENABLED} + CRISP_SKIP_PROOF_AGGREGATION=${CRISP_SKIP_PROOF_AGGREGATION} ENABLE_ZK_VERIFICATION=${ENABLE_ZK_VERIFICATION:-false} (used at deploy via dev:up) - server/.env E3_PROOF_AGGREGATION_ENABLED synced by dev:setup + ciphernode skip flag=${CRISP_SKIP_PROOF_AGGREGATION} Contract addresses synced by dev:up (deploy → server/.env, client/.env, interfold.config.yaml) EOF diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index cb1b3fe2a..cf3da9a18 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -45,4 +45,3 @@ E3_COMPUTE_PROVIDER_BATCH_SIZE=4 # Must be a power of 2 # Whether to enable proof aggregation for E3 programs. # Managed by pnpm dev:setup from ../crisp.dev.env — edit that file, then re-run dev:setup. # See ../docs/PROOF_AGGREGATION_AND_ZK.md. -E3_PROOF_AGGREGATION_ENABLED=false diff --git a/examples/CRISP/server/src/cli/commands.rs b/examples/CRISP/server/src/cli/commands.rs index b789aa670..03c2b9d71 100644 --- a/examples/CRISP/server/src/cli/commands.rs +++ b/examples/CRISP/server/src/cli/commands.rs @@ -270,8 +270,6 @@ pub async fn initialize_crisp_round( U256::from(window_start + CONFIG.e3_duration), ]; - let proof_aggregation_enabled = CONFIG.e3_proof_aggregation_enabled; - let fee_amount = contract .get_e3_quote( committee_size, @@ -279,7 +277,6 @@ pub async fn initialize_crisp_round( e3_program, param_set, compute_provider_params_bytes.clone(), - proof_aggregation_enabled, ) .await?; info!("Fee required: {} tokens", fee_amount); @@ -333,7 +330,6 @@ pub async fn initialize_crisp_round( param_set, compute_provider_params_bytes, custom_params_bytes, - proof_aggregation_enabled, ) .await .map_err(format_request_e3_revert)?; diff --git a/examples/CRISP/server/src/config.rs b/examples/CRISP/server/src/config.rs index 56c421bfe..948fffffd 100644 --- a/examples/CRISP/server/src/config.rs +++ b/examples/CRISP/server/src/config.rs @@ -28,8 +28,6 @@ pub struct Config { pub chain_id: u64, pub cron_api_key: String, // E3 parameters - #[serde(default)] - pub e3_proof_aggregation_enabled: bool, pub e3_committee_size: u8, // 0=Minimum, 1=Micro, 2=Small pub e3_duration: u64, pub e3_compute_provider_name: String, diff --git a/examples/CRISP/server/src/server/routes/rounds.rs b/examples/CRISP/server/src/server/routes/rounds.rs index 0949dae3c..240b67e49 100644 --- a/examples/CRISP/server/src/server/routes/rounds.rs +++ b/examples/CRISP/server/src/server/routes/rounds.rs @@ -227,8 +227,6 @@ pub async fn initialize_crisp_round( batch_size: CONFIG.e3_compute_provider_batch_size, }; - let proof_aggregation_enabled = CONFIG.e3_proof_aggregation_enabled; - let compute_provider_params = Bytes::from(bincode::serialize(&compute_provider_params)?); let (receipt, e3_id) = contract .request_e3( @@ -238,7 +236,6 @@ pub async fn initialize_crisp_round( param_set, compute_provider_params, custom_params_bytes, - proof_aggregation_enabled, ) .await?; info!( diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index b84d3e475..2dfa41e60 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -740,11 +740,6 @@ "internalType": "address", "name": "requester", "type": "address" - }, - { - "internalType": "bool", - "name": "proofAggregationEnabled", - "type": "bool" } ], "indexed": false, @@ -1532,11 +1527,6 @@ "internalType": "address", "name": "requester", "type": "address" - }, - { - "internalType": "bool", - "name": "proofAggregationEnabled", - "type": "bool" } ], "internalType": "struct E3", @@ -1581,11 +1571,6 @@ "name": "customParams", "type": "bytes" }, - { - "internalType": "bool", - "name": "proofAggregationEnabled", - "type": "bool" - }, { "internalType": "uint256", "name": "maxFee", @@ -2062,11 +2047,6 @@ "name": "customParams", "type": "bytes" }, - { - "internalType": "bool", - "name": "proofAggregationEnabled", - "type": "bool" - }, { "internalType": "uint256", "name": "maxFee", @@ -2161,11 +2141,6 @@ "internalType": "address", "name": "requester", "type": "address" - }, - { - "internalType": "bool", - "name": "proofAggregationEnabled", - "type": "bool" } ], "internalType": "struct E3", @@ -2464,5 +2439,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" + "buildInfoId": "solc-0_8_28-873e74c1b5b14b995c4e623d1b2305d86bdf9b7a" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 74a154d20..0158a0b4f 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -315,7 +315,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { e3.e3Program = requestParams.e3Program; e3.paramSet = requestParams.paramSet; e3.customParams = requestParams.customParams; - e3.proofAggregationEnabled = requestParams.proofAggregationEnabled; e3.requester = msg.sender; bytes memory e3ProgramParams = paramSetRegistry[requestParams.paramSet]; @@ -407,7 +406,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { bytes calldata plaintextOutput, bytes calldata proof ) external returns (bool success) { - E3 memory e3 = getE3(e3Id); + require( + e3s[e3Id].e3Program != IE3Program(address(0)), + E3DoesNotExist(e3Id) + ); // Check we are in the right stage // no need to check if there's a ciphertext as we would not @@ -428,17 +430,13 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { e3s[e3Id].plaintextOutput = plaintextOutput; _e3Stages[e3Id] = E3Stage.Complete; - if (e3.proofAggregationEnabled) { - require(proof.length > 0, ProofRequired()); + require(proof.length > 0, ProofRequired()); - // Canonicalize the verifier ABI payload before deriving its nullifier. - // `abi.decode` accepts some trailing-byte variants; hashing the decoded - // tuple makes every equivalent encoding consume the same one-shot key. - _verifyPlaintext(e3Id, keccak256(plaintextOutput), proof); - success = true; - } else { - success = true; - } + // Canonicalize the verifier ABI payload before deriving its nullifier. + // `abi.decode` accepts some trailing-byte variants; hashing the decoded + // tuple makes every equivalent encoding consume the same one-shot key. + _verifyPlaintext(e3Id, keccak256(plaintextOutput), proof); + success = true; _distributeRewards(e3Id); diff --git a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol index 2cf2c00b1..b8e0a2350 100644 --- a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -401,17 +401,15 @@ interface ICiphernodeRegistry { /// @notice Publishes the public key resulting from the committee selection process. /// @dev Permissionless once the committee is finalized. - /// When `e3.proofAggregationEnabled` is true, the `proof` is verified against - /// `pkCommitment` via the E3's pk verifier. When disabled, `proof` may be empty - /// and `pkCommitment` is trusted from the (signed) aggregator. + /// The `proof` is verified against `pkCommitment` via the E3's pk verifier. /// @param e3Id ID of the E3 for which to select the committee. /// @param publicKey The public key generated by the given committee. /// @param pkCommitment Hash-based aggregated PK commitment for the committee. /// @param proof DkgAggregator (EVM) proof ABI-encoded `(bytes rawProof, bytes32[] publicInputs)`, - /// or empty bytes when proof aggregation is disabled. + /// as accepted by the configured public-key verifier. /// @param dkgAttestationBundle ABI-encoded /// `(DkgFoldAttestationLib.Attestation[] attestations, DkgFoldAttestationLib.PartySlotBinding[] bindings)`. - /// Required (non-empty) when proof aggregation is enabled; ignored otherwise. + /// Required and non-empty for every committee publication. function publishCommittee( uint256 e3Id, bytes calldata publicKey, diff --git a/packages/interfold-contracts/contracts/interfaces/IE3.sol b/packages/interfold-contracts/contracts/interfaces/IE3.sol index fcdd0d950..1f53b57c8 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3.sol @@ -44,5 +44,4 @@ struct E3 { bytes32 ciphertextOutput; bytes plaintextOutput; address requester; - bool proofAggregationEnabled; } diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index 865baf1e9..c4ea2b690 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -509,10 +509,6 @@ interface IInterfold { uint8 paramSet; bytes computeProviderParams; bytes customParams; - /// @notice When true, ciphernodes generate and fold wrapper proofs - /// for DKG proof aggregation (public verifiability). When - /// false, wrapper/fold proofs are skipped to reduce latency. - bool proofAggregationEnabled; /// @notice Maximum fee-token amount authorized for this request. uint256 maxFee; /// @notice Last timestamp at which this request may execute. diff --git a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index 9d58dfc69..6a1c35279 100644 --- a/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/interfold-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -320,21 +320,19 @@ contract CiphernodeRegistryOwnable is publicKeyHashes[e3Id] = pkCommitment; E3 memory e3 = _interfoldFor(e3Id).getE3(e3Id); - if (e3.proofAggregationEnabled) { - // Bind to the on-chain committee (c.topNodes), not caller-supplied - // nodes, so a wrong `nodes` input cannot pre-commit the prover to - // an attacker's set (C-08). - _verifyAndStoreDkgAnchors( - e3Id, - e3, - roots[e3Id], - c.topNodes, - pkCommitment, - committeeHash, - proof, - dkgAttestationBundle - ); - } + // Bind to the on-chain committee (c.topNodes), not caller-supplied + // nodes, so a wrong `nodes` input cannot pre-commit the prover to + // an attacker's set (C-08). + _verifyAndStoreDkgAnchors( + e3Id, + e3, + roots[e3Id], + c.topNodes, + pkCommitment, + committeeHash, + proof, + dkgAttestationBundle + ); _interfoldFor(e3Id).onCommitteePublished(e3Id, pkCommitment); diff --git a/packages/interfold-contracts/contracts/test/MockDkgFoldAttestationVerifier.sol b/packages/interfold-contracts/contracts/test/MockDkgFoldAttestationVerifier.sol new file mode 100644 index 000000000..ca0defbe0 --- /dev/null +++ b/packages/interfold-contracts/contracts/test/MockDkgFoldAttestationVerifier.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +pragma solidity 0.8.28; + +import { + IDkgFoldAttestationVerifier +} from "../interfaces/IDkgFoldAttestationVerifier.sol"; + +/// @notice Test-only verifier used when ciphernodes skip recursive proof aggregation. +/// @dev Production deployments must use DkgFoldAttestationVerifier. +contract MockDkgFoldAttestationVerifier is IDkgFoldAttestationVerifier { + function verify( + address, + uint256, + uint256, + bytes calldata, + bytes calldata + ) + external + pure + returns ( + uint256[] memory partyIds, + bytes32[] memory skAggCommits, + bytes32[] memory esmAggCommits + ) + { + return (new uint256[](0), new bytes32[](0), new bytes32[](0)); + } +} diff --git a/packages/interfold-contracts/scripts/deployInterfold.ts b/packages/interfold-contracts/scripts/deployInterfold.ts index 9309f7e6c..5aac39576 100644 --- a/packages/interfold-contracts/scripts/deployInterfold.ts +++ b/packages/interfold-contracts/scripts/deployInterfold.ts @@ -604,6 +604,21 @@ export const deployInterfold = async ( "Successfully set DkgFoldAttestationVerifier on CiphernodeRegistry", ); } + } else if (shouldDeployMocks) { + console.log("Deploying MockDkgFoldAttestationVerifier for test/CI..."); + const mockDkgFoldAttestationVerifier = await ethers.deployContract( + "MockDkgFoldAttestationVerifier", + ); + await mockDkgFoldAttestationVerifier.waitForDeployment(); + dkgFoldAttestationVerifierAddress = + await mockDkgFoldAttestationVerifier.getAddress(); + const tx = await ciphernodeRegistry.setInitialDkgFoldAttestationVerifier( + dkgFoldAttestationVerifierAddress, + ); + await tx.wait(); + console.log( + "Successfully set MockDkgFoldAttestationVerifier on CiphernodeRegistry", + ); } const verifierLines = diff --git a/packages/interfold-contracts/tasks/interfold.ts b/packages/interfold-contracts/tasks/interfold.ts index 767baa806..1388b3adf 100644 --- a/packages/interfold-contracts/tasks/interfold.ts +++ b/packages/interfold-contracts/tasks/interfold.ts @@ -131,12 +131,6 @@ export const requestCommittee = task( defaultValue: ZeroAddress, type: ArgumentType.STRING, }) - .addOption({ - name: "proofAggregationEnabled", - description: "whether to enable proof aggregation (default: false)", - defaultValue: false, - type: ArgumentType.BOOLEAN, - }) .setAction(async () => ({ default: async ( { @@ -147,7 +141,6 @@ export const requestCommittee = task( e3Params: _e3Params, computeParams, customParams, - proofAggregationEnabled, }, hre, ) => { @@ -238,7 +231,6 @@ export const requestCommittee = task( paramSet, computeProviderParams, customParams, - proofAggregationEnabled, maxFee: ethers.MaxUint256, requestDeadline: inputWindowStart, }; diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index bebe18cb7..3d30eb8cb 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -14,6 +14,7 @@ import { } from "../../types"; import { deployInterfoldSystem, + encodeMockDkgProof, ethers, ignition, networkHelpers, @@ -137,7 +138,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: startTime + 100, }; @@ -377,8 +377,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { 0, publicKey, ethers.keccak256(publicKey), - "0x", - "0x", + encodeMockDkgProof(ethers.keccak256(publicKey)), + "0x01", ); // Slashing also stays bound to the original registry, bonding, Interfold, @@ -455,7 +455,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); // Verify stage transitioned to KeyPublished (after publishCommittee which calls onKeyPublished) stage = await interfold.getE3Stage(0); @@ -496,7 +502,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const pkCommitment = ethers.keccak256(publicKey); await expect( - registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"), + registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ), ) .to.emit(interfold, "CommitteeFormed") .withArgs(0); @@ -755,7 +767,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); // 2. Wait past compute deadline → mark as failed const e3 = await interfold.getE3(0); @@ -876,8 +894,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { 0, publicKey, ethers.keccak256(publicKey), - "0x", - "0x", + encodeMockDkgProof(ethers.keccak256(publicKey)), + "0x01", ); const e3 = await interfold.getE3(0); @@ -976,8 +994,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { 0, publicKey, ethers.keccak256(publicKey), - "0x", - "0x", + encodeMockDkgProof(ethers.keccak256(publicKey)), + "0x01", ); const proof = await signAndEncodeAttestation( @@ -1092,7 +1110,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); // 2. Fail via compute timeout const e3 = await interfold.getE3(0); @@ -1426,7 +1450,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); stage = await interfold.getE3Stage(0); expect(stage).to.equal(3); // KeyPublished @@ -1500,7 +1530,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); stage = await interfold.getE3Stage(0); expect(stage).to.equal(3); // KeyPublished @@ -1585,7 +1621,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: startTime, }; @@ -1672,7 +1707,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: startTime, }; @@ -1760,7 +1794,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); expect(await interfold.getE3Stage(0)).to.equal(3); // KeyPublished @@ -1914,7 +1954,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); expect(await interfold.getE3Stage(0)).to.equal(3); // KeyPublished @@ -1968,7 +2014,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const publicKey = "0x1234567890abcdef1234567890abcdef"; const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(0, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); // Publish outputs const e3 = await interfold.getE3(0); diff --git a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts index ada174d86..81aba0483 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts @@ -114,7 +114,6 @@ async function deployStack() { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 10, } as any; diff --git a/packages/interfold-contracts/test/Interfold.spec.ts b/packages/interfold-contracts/test/Interfold.spec.ts index 5d7ba9eec..de7c45c4a 100644 --- a/packages/interfold-contracts/test/Interfold.spec.ts +++ b/packages/interfold-contracts/test/Interfold.spec.ts @@ -45,12 +45,6 @@ describe("Interfold", function () { const setup = async () => { const sys = await deployInterfoldSystem({ wireSlashingManager: true }); - const dkgFoldAttestationVerifier = await ethers.deployContract( - "DkgFoldAttestationVerifier", - ); - await sys.ciphernodeRegistry.setInitialDkgFoldAttestationVerifier( - await dkgFoldAttestationVerifier.getAddress(), - ); return { owner: sys.owner, notTheOwner: sys.notTheOwner, @@ -64,7 +58,6 @@ describe("Interfold", function () { ticketToken: sys.ticketToken, usdcToken: sys.usdcToken, slashingManager: sys.slashingManager, - dkgFoldAttestationVerifier, request: sys.request, mocks: { decryptionVerifier: sys.mocks.decryptionVerifier, @@ -221,7 +214,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }); const e3 = await interfold.getE3(0); @@ -383,7 +375,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: request.inputWindow[0], }), @@ -427,7 +418,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: request.inputWindow[0], }; @@ -451,7 +441,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }), ).to.be.revertedWithCustomError(interfold, "InvalidDuration"); }); @@ -466,7 +455,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }), ) .to.be.revertedWithCustomError(interfold, "E3ProgramNotAllowed") @@ -483,7 +471,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }), ) .to.be.revertedWithCustomError(interfold, "InvalidEncryptionScheme") @@ -499,7 +486,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }); const e3 = await interfold.getE3(0); @@ -530,7 +516,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }); const e3 = await interfold.getE3(0); @@ -568,7 +553,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }); await setupAndPublishCommittee(ciphernodeRegistryContract, e3Id, data, [ @@ -631,7 +615,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - proofAggregationEnabled: false, }); await setupAndPublishCommittee(ciphernodeRegistryContract, e3Id, data, [ @@ -792,6 +775,34 @@ describe("Interfold", function () { interfold.publishPlaintextOutput(e3Id, data, proof), ).to.be.revertedWithCustomError(interfold, "InvalidStage"); }); + it("AUD-C02: requires a final decryption proof", async function () { + const { + interfold, + request, + usdcToken, + ciphernodeRegistryContract, + operator1, + operator2, + operator3, + } = await loadFixture(setup); + const e3Id = 0; + + await makeRequest(interfold, usdcToken, { + ...request, + inputWindow: [(await time.latest()) + 20, (await time.latest()) + 100], + }); + await setupAndPublishCommittee(ciphernodeRegistryContract, e3Id, data, [ + operator1, + operator2, + operator3, + ]); + await mine(2, { interval: inputWindowDuration }); + await interfold.publishCiphertextOutput(e3Id, data, proof); + + await expect( + interfold.publishPlaintextOutput(e3Id, data, "0x"), + ).to.be.revertedWithCustomError(interfold, "ProofRequired"); + }); it("reverts if output is not valid", async function () { const { interfold, @@ -801,14 +812,12 @@ describe("Interfold", function () { operator1, operator2, operator3, - dkgFoldAttestationVerifier, } = await loadFixture(setup); const e3Id = 0; await makeRequest(interfold, usdcToken, { ...request, inputWindow: [(await time.latest()) + 20, (await time.latest()) + 100], - proofAggregationEnabled: true, }); const operators = [operator1, operator2, operator3]; @@ -816,7 +825,7 @@ describe("Interfold", function () { operators, e3Id, data, - await dkgFoldAttestationVerifier.getAddress(), + await ciphernodeRegistryContract.dkgFoldAttestationVerifier(), ); await setupAndPublishCommittee( ciphernodeRegistryContract, diff --git a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts index b43202376..6715f27c6 100644 --- a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts +++ b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts @@ -14,6 +14,7 @@ import { SORTITION_SUBMISSION_WINDOW, DATA as data, deployInterfoldSystem, + encodeMockDkgProof, ethers, networkHelpers, PROOF as proof, @@ -37,7 +38,13 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await registry.finalizeCommittee(e3Id); const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(e3Id, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + e3Id, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); }; const setup = async () => { @@ -105,7 +112,6 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now0 + 10, } as any; @@ -126,7 +132,6 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 10, }; diff --git a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts index 94361aa3b..f3ce47d7c 100644 --- a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts +++ b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts @@ -358,7 +358,6 @@ describe("E3 Pricing", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 100, }; @@ -450,7 +449,6 @@ describe("E3 Pricing", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 100, }; diff --git a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts index ac150a812..43d980905 100644 --- a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts +++ b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts @@ -9,6 +9,7 @@ import { SORTITION_SUBMISSION_WINDOW, DATA as data, deployInterfoldSystem, + encodeMockDkgProof, ethers, networkHelpers, PROOF as proof, @@ -32,7 +33,13 @@ describe("Interfold — pull payments + fee-token allow-list", function () { await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await registry.finalizeCommittee(e3Id); const pkCommitment = ethers.keccak256(publicKey); - await registry.publishCommittee(e3Id, publicKey, pkCommitment, "0x", "0x"); + await registry.publishCommittee( + e3Id, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); }; // Two fixtures: one using vanilla USDC (allow-list tests), @@ -94,7 +101,6 @@ describe("Interfold — pull payments + fee-token allow-list", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 10, }; diff --git a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index b72055c55..acf2e6283 100644 --- a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -11,6 +11,7 @@ import { ADDRESS_ONE as AddressOne, ADDRESS_TWO as AddressTwo, deployInterfoldSystem, + encodeMockDkgProof, ethers, networkHelpers, } from "../fixtures"; @@ -77,7 +78,6 @@ describe("CiphernodeRegistryOwnable", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: currentTime + 100, }; @@ -212,6 +212,41 @@ describe("CiphernodeRegistryOwnable", function () { }); describe("publishCommittee()", function () { + it("AUD-C02: requires a final DKG proof and attestation bundle", async function () { + const { + registry, + interfold, + usdcToken, + mockE3Program, + mockDecryptionVerifier, + operator1, + operator2, + operator3, + } = await loadFixture(setup); + await makeRequest( + interfold, + usdcToken, + mockE3Program, + mockDecryptionVerifier, + ); + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await finalizeCommitteeAfterWindow(registry, 0); + + await expect( + registry.publishCommittee(0, data, dataHash, "0x", "0x"), + ).to.be.revertedWithCustomError(registry, "DkgProofRequired"); + await expect( + registry.publishCommittee( + 0, + data, + dataHash, + encodeMockDkgProof(dataHash), + "0x", + ), + ).to.be.revertedWithCustomError(registry, "FoldAttestationsRequired"); + }); it("allows any caller to publish a finalized committee", async function () { const { registry, @@ -239,7 +274,13 @@ describe("CiphernodeRegistryOwnable", function () { await expect( registry .connect(notTheOwner) - .publishCommittee(0, data, dataHash, "0x", "0x"), + .publishCommittee( + 0, + data, + dataHash, + encodeMockDkgProof(dataHash), + "0x01", + ), ) .to.emit(registry, "CommitteePublished") .withArgs( @@ -251,7 +292,7 @@ describe("CiphernodeRegistryOwnable", function () { ], data, dataHash, - "0x", + encodeMockDkgProof(dataHash), ); }); it("stores the public key of the committee", async function () { @@ -277,7 +318,13 @@ describe("CiphernodeRegistryOwnable", function () { await registry.connect(operator3).submitTicket(0, 1); await finalizeCommitteeAfterWindow(registry, 0); - await registry.publishCommittee(0, data, dataHash, "0x", "0x"); + await registry.publishCommittee( + 0, + data, + dataHash, + encodeMockDkgProof(dataHash), + "0x01", + ); expect(await registry.committeePublicKey(0)).to.equal(dataHash); }); it("emits a CommitteePublished event", async function () { @@ -305,7 +352,13 @@ describe("CiphernodeRegistryOwnable", function () { await finalizeCommitteeAfterWindow(registry, 0); await expect( - await registry.publishCommittee(0, data, dataHash, "0x", "0x"), + await registry.publishCommittee( + 0, + data, + dataHash, + encodeMockDkgProof(dataHash), + "0x01", + ), ) .to.emit(registry, "CommitteePublished") .withArgs( @@ -317,7 +370,7 @@ describe("CiphernodeRegistryOwnable", function () { ], data, dataHash, - "0x", + encodeMockDkgProof(dataHash), ); }); }); @@ -477,7 +530,13 @@ describe("CiphernodeRegistryOwnable", function () { await registry.connect(operator3).submitTicket(e3Id, 1); await finalizeCommitteeAfterWindow(registry, e3Id); - await registry.publishCommittee(e3Id, data, dataHash, "0x", "0x"); + await registry.publishCommittee( + e3Id, + data, + dataHash, + encodeMockDkgProof(dataHash), + "0x01", + ); expect(await registry.committeePublicKey(e3Id)).to.equal(dataHash); }); it("reverts if the committee has not been published", async function () { diff --git a/packages/interfold-contracts/test/Registry/CiphernodeRegistryVerifierLifecycle.spec.ts b/packages/interfold-contracts/test/Registry/CiphernodeRegistryVerifierLifecycle.spec.ts index dd67634aa..1a77fdafc 100644 --- a/packages/interfold-contracts/test/Registry/CiphernodeRegistryVerifierLifecycle.spec.ts +++ b/packages/interfold-contracts/test/Registry/CiphernodeRegistryVerifierLifecycle.spec.ts @@ -11,7 +11,9 @@ const { loadFixture, time } = networkHelpers; describe("CiphernodeRegistryOwnable verifier lifecycle", function () { const setup = async () => { - const sys = await deployInterfoldSystem(); + const sys = await deployInterfoldSystem({ + wireMockDkgFoldAttestationVerifier: false, + }); const verifier1 = await ethers.deployContract("DkgFoldAttestationVerifier"); const verifier2 = await ethers.deployContract("DkgFoldAttestationVerifier"); const verifier3 = await ethers.deployContract("DkgFoldAttestationVerifier"); diff --git a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts index d501948da..da3dadbcc 100644 --- a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -25,6 +25,7 @@ import { ONE_DAY, SORTITION_SUBMISSION_WINDOW, deployInterfoldSystem, + encodeMockDkgProof, ethers, networkHelpers, signAndEncodeAttestation, @@ -144,7 +145,6 @@ describe("Committee Expulsion & Fault Tolerance", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: startTime + 100, }; @@ -172,8 +172,8 @@ describe("Committee Expulsion & Fault Tolerance", function () { e3Id, publicKey, pkCommitment, - "0x", - "0x", + encodeMockDkgProof(pkCommitment), + "0x01", ); } diff --git a/packages/interfold-contracts/test/fixtures/helpers.ts b/packages/interfold-contracts/test/fixtures/helpers.ts index b8f2248ea..88c16f3b0 100644 --- a/packages/interfold-contracts/test/fixtures/helpers.ts +++ b/packages/interfold-contracts/test/fixtures/helpers.ts @@ -13,6 +13,7 @@ import { COMMITTEE_SIZE_MINIMUM, SORTITION_SUBMISSION_WINDOW, } from "./constants"; +import { buildMockDkgAttestationFixtureData } from "./dkgAttestation"; const { time } = networkHelpers; const abiCoder = ethers.AbiCoder.defaultAbiCoder(); @@ -50,6 +51,25 @@ export const setupAndPublishCommittee = async ( await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await registry.finalizeCommittee(e3Id); const pkCommitment = ethers.keccak256(publicKey); + if (committeeProof === "0x" && dkgAttestationBundle === "0x") { + let verifierAddress = await registry.dkgFoldAttestationVerifier(); + if (verifierAddress === ethers.ZeroAddress) { + const verifier = await ethers.deployContract( + "DkgFoldAttestationVerifier", + ); + await verifier.waitForDeployment(); + verifierAddress = await verifier.getAddress(); + await registry.setInitialDkgFoldAttestationVerifier(verifierAddress); + } + const fixture = await buildMockDkgAttestationFixtureData( + operators, + e3Id, + pkCommitment, + verifierAddress, + ); + committeeProof = fixture.proof; + dkgAttestationBundle = fixture.bundle; + } await registry.publishCommittee( e3Id, publicKey, @@ -105,8 +125,6 @@ export interface BuildRequestParamsOptions { startOffset?: number; /** `inputWindow[1] - time.latest()`. Defaults to `300` (5 minutes). */ windowDuration?: number; - /** Defaults to `false`. */ - proofAggregationEnabled?: boolean; /** Override custom params bytes. Defaults to an ABI-encoded throwaway address. */ customParams?: string; /** Param-set id registered on the Interfold. Defaults to `0`. */ @@ -146,7 +164,6 @@ export const buildRequestParams = async ( ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: opts.proofAggregationEnabled ?? false, maxFee: ethers.MaxUint256, requestDeadline: now + startOffset, }; diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index ee32eb61b..7c4ca0e99 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -97,6 +97,12 @@ export interface DeployInterfoldSystemOptions { maxDuration?: number; /** Override the timeout config. Defaults to {@link DEFAULT_TIMEOUT_CONFIG}. */ timeoutConfig?: TimeoutConfig; + /** + * Install the permissive DKG fold-attestation verifier used by ordinary tests. + * Defaults to `true`. Set to `false` for verifier lifecycle tests that need an + * initially unset registry. + */ + wireMockDkgFoldAttestationVerifier?: boolean; /** Treasury for `E3RefundManager`. Defaults to `"owner"`. */ treasury?: "owner" | Signer; /** `slashedFundsTreasury` passed to `BondingRegistry`. Defaults to `"owner"`. */ @@ -499,6 +505,18 @@ export async function deployInterfoldSystem( ENCRYPTION_SCHEME_ID, await pkVerifier.getAddress(), ); + if ( + !mockCiphernodeRegistry && + (opts.wireMockDkgFoldAttestationVerifier ?? true) + ) { + const mockDkgFoldAttestationVerifier = await ethers.deployContract( + "MockDkgFoldAttestationVerifier", + ); + await mockDkgFoldAttestationVerifier.waitForDeployment(); + await ciphernodeRegistry.setInitialDkgFoldAttestationVerifier( + await mockDkgFoldAttestationVerifier.getAddress(), + ); + } // ── Committee thresholds ([M, N] per CommitteeSize) ───────────────────── for (const [size, [m, n]] of committeeThresholds) { @@ -546,7 +564,6 @@ export async function deployInterfoldSystem( ["address"], ["0x1234567890123456789012345678901234567890"], ), - proofAggregationEnabled: false, maxFee: ethers.MaxUint256, requestDeadline: now + 10, }; diff --git a/packages/interfold-sdk/src/contracts/contract-client.ts b/packages/interfold-sdk/src/contracts/contract-client.ts index a23f6cc52..bf3214c77 100644 --- a/packages/interfold-sdk/src/contracts/contract-client.ts +++ b/packages/interfold-sdk/src/contracts/contract-client.ts @@ -160,7 +160,6 @@ export class ContractClient { paramSet: params.paramSet, computeProviderParams: params.computeProviderParams, customParams: params.customParams || '0x', - proofAggregationEnabled: params.proofAggregationEnabled ?? true, maxFee, requestDeadline, }, @@ -237,7 +236,6 @@ export class ContractClient { paramSet: requestParams.paramSet, computeProviderParams: requestParams.computeProviderParams, customParams: requestParams.customParams || '0x', - proofAggregationEnabled: requestParams.proofAggregationEnabled ?? true, maxFee: requestParams.maxFee ?? 0n, requestDeadline: requestParams.requestDeadline ?? requestParams.inputWindow[0], }, diff --git a/packages/interfold-sdk/src/contracts/types.ts b/packages/interfold-sdk/src/contracts/types.ts index 05838b739..bfe215138 100644 --- a/packages/interfold-sdk/src/contracts/types.ts +++ b/packages/interfold-sdk/src/contracts/types.ts @@ -60,9 +60,6 @@ export interface E3RequestParams extends RequestParams { paramSet: number computeProviderParams: `0x${string}` customParams?: `0x${string}` - /** When true, ciphernodes generate wrapper/fold proofs for DKG proof aggregation. - * When false, proof aggregation is skipped for faster computation. Defaults to true. */ - proofAggregationEnabled?: boolean /** Maximum fee-token amount authorized for this request. Defaults to a fresh quote. */ maxFee?: bigint /** Last timestamp at which the request may execute. Defaults to inputWindow[0]. */ diff --git a/packages/interfold-sdk/src/interfold-sdk.ts b/packages/interfold-sdk/src/interfold-sdk.ts index 5a9d3fe7f..2bc190ac1 100644 --- a/packages/interfold-sdk/src/interfold-sdk.ts +++ b/packages/interfold-sdk/src/interfold-sdk.ts @@ -140,7 +140,6 @@ export class InterfoldSDK { paramSet: number computeProviderParams: `0x${string}` customParams?: `0x${string}` - proofAggregationEnabled?: boolean maxFee?: bigint requestDeadline?: bigint gasLimit?: bigint diff --git a/templates/default/client/src/pages/steps/RequestComputation.tsx b/templates/default/client/src/pages/steps/RequestComputation.tsx index 162ba3307..f6596f42e 100644 --- a/templates/default/client/src/pages/steps/RequestComputation.tsx +++ b/templates/default/client/src/pages/steps/RequestComputation.tsx @@ -109,7 +109,6 @@ const RequestComputation: React.FC = () => { e3Program: contracts.e3Program, paramSet: 0, // ParamSet.Insecure512 computeProviderParams, - proofAggregationEnabled: false, } const fee = await sdk.sdk.getE3Quote(requestParams) diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index 7dec47d07..acc55da65 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -43,6 +43,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 + # # Test/CI only. Requires mock on-chain proof verifiers. + # skip_proof_aggregation: false nodes: cn1: address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 3c67049f9..f8541808f 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -209,7 +209,6 @@ describe('Integration', () => { e3Program: contracts.e3Program, paramSet: 0, // ParamSet.InsecureThreshold512 computeProviderParams, - proofAggregationEnabled: false, } const quote = await sdk.getE3Quote(requestParams) console.log('E3 quote:', quote) diff --git a/tests/integration/base.sh b/tests/integration/base.sh index ee14ae48f..15b00c04f 100755 --- a/tests/integration/base.sh +++ b/tests/integration/base.sh @@ -19,7 +19,7 @@ done pnpm evm:clean -if [[ "$PROOF_AGGREGATION_ENABLED" == "true" ]]; then +if [[ "$FULL_PROOF_AGGREGATION" == "true" ]]; then heading "Deploy contracts (ZK verification + fold attestation verifier)" ENABLE_ZK_VERIFICATION=true pnpm evm:deploy else @@ -63,7 +63,7 @@ pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_4 --network localho heading "Add ciphernode $CIPHERNODE_ADDRESS_5" pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_5 --network localhost -heading "Request Committee (proof-aggregation-enabled=$PROOF_AGGREGATION_ENABLED)" +heading "Request Committee" ENCODED_PARAMS=0x$($SCRIPT_DIR/lib/pack_e3_params.sh \ --moduli 0xffffee001 \ @@ -82,12 +82,11 @@ pnpm committee:new \ --input-window-start "$INPUT_WINDOW_START" \ --input-window-end "$INPUT_WINDOW_END" \ --e3-params "$ENCODED_PARAMS" \ - --committee-size 0 \ - --proof-aggregation-enabled "$PROOF_AGGREGATION_ENABLED" + --committee-size 0 wait_for_committee_pubkey 0 "$SCRIPT_DIR/output/pubkey.bin" "$INTEGRATION_DKG_TIMEOUT" -if [[ "$PROOF_AGGREGATION_ENABLED" == "true" ]]; then +if [[ "$FULL_PROOF_AGGREGATION" == "true" ]]; then heading "Verify active aggregator (proof aggregation / DKG path)" ACTIVE_AGG_ADDRESS=$(wait_for_active_aggregator_address 0 "$INTEGRATION_DKG_TIMEOUT") echo "Active aggregator: $ACTIVE_AGG_ADDRESS" @@ -98,7 +97,7 @@ daemon_query_events cn1 "$SCRIPT_DIR/output/events.txt" check_last_line "$SCRIPT_DIR/output/events.txt" '{"Next":10}' -if [[ "$PROOF_AGGREGATION_ENABLED" == "true" ]]; then +if [[ "$FULL_PROOF_AGGREGATION" == "true" ]]; then heading "Wire MockE3Program → Interfold so publishInput triggers decryption" pnpm e3-program:setMockInterfold --network localhost diff --git a/tests/integration/fns.sh b/tests/integration/fns.sh index 11e15c368..32a0a6bd5 100644 --- a/tests/integration/fns.sh +++ b/tests/integration/fns.sh @@ -7,7 +7,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" PLAINTEXT="4,0" # Set by test.sh via export_integration_flags -PROOF_AGGREGATION_ENABLED="${PROOF_AGGREGATION_ENABLED:-false}" +FULL_PROOF_AGGREGATION="${FULL_PROOF_AGGREGATION:-false}" INTEGRATION_DKG_TIMEOUT="${INTEGRATION_DKG_TIMEOUT:-1300}" ID=$(date +%s) diff --git a/tests/integration/interfold.config.yaml b/tests/integration/interfold.config.yaml index 44d4f35ae..bc75c672d 100644 --- a/tests/integration/interfold.config.yaml +++ b/tests/integration/interfold.config.yaml @@ -45,6 +45,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 + # # Test/CI only. Requires mock on-chain proof verifiers. + # skip_proof_aggregation: false nodes: cn1: diff --git a/tests/integration/lib/prebuild.sh b/tests/integration/lib/prebuild.sh index 2116a0e8f..79caf5ba0 100755 --- a/tests/integration/lib/prebuild.sh +++ b/tests/integration/lib/prebuild.sh @@ -15,7 +15,7 @@ echo "" echo "FINISHED PREBUILDING BINARIES" echo "" -if [[ "${PROOF_AGGREGATION_ENABLED:-false}" == "true" ]]; then +if [[ "${FULL_PROOF_AGGREGATION:-false}" == "true" ]]; then echo "" echo "BUILDING ZK CIRCUITS + ON-CHAIN VERIFIERS (proof aggregation enabled)..." echo "" diff --git a/tests/integration/persist.sh b/tests/integration/persist.sh index 0350c521d..0c168876b 100755 --- a/tests/integration/persist.sh +++ b/tests/integration/persist.sh @@ -19,7 +19,7 @@ done pnpm evm:clean -if [[ "${PROOF_AGGREGATION_ENABLED:-false}" == "true" ]]; then +if [[ "${FULL_PROOF_AGGREGATION:-false}" == "true" ]]; then ENABLE_ZK_VERIFICATION=true pnpm evm:deploy else pnpm evm:deploy @@ -73,8 +73,7 @@ pnpm committee:new \ --input-window-start "$INPUT_WINDOW_START" \ --input-window-end "$INPUT_WINDOW_END" \ --e3-params "$ENCODED_PARAMS" \ - --committee-size 0 \ - --proof-aggregation-enabled "${PROOF_AGGREGATION_ENABLED:-false}" + --committee-size 0 wait_for_committee_pubkey 0 "$SCRIPT_DIR/output/pubkey.bin" "${INTEGRATION_DKG_TIMEOUT:-1300}" diff --git a/tests/integration/test.sh b/tests/integration/test.sh index f8bde2cc0..f199bd74b 100755 --- a/tests/integration/test.sh +++ b/tests/integration/test.sh @@ -4,15 +4,15 @@ set -eu # Exit immediately if a command exits with a non-zero status THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROOF_AGGREGATION_ENABLED="${PROOF_AGGREGATION_ENABLED:-false}" +CIPHERNODE_SKIP_PROOF_AGGREGATION="${CIPHERNODE_SKIP_PROOF_AGGREGATION:-true}" SKIP_PREBUILD=false parse_integration_args() { while [[ $# -gt 0 ]]; do case "$1" in - --proof-aggregation-enabled) + --skip-proof-aggregation) shift - PROOF_AGGREGATION_ENABLED="${1:-true}" + CIPHERNODE_SKIP_PROOF_AGGREGATION="${1:-true}" shift ;; --no-prebuild) @@ -21,7 +21,7 @@ parse_integration_args() { ;; *) echo "Unknown integration argument: $1" >&2 - echo "Usage: ./test.sh [base|persist|net|restart] [--proof-aggregation-enabled true|false] [--no-prebuild]" >&2 + echo "Usage: ./test.sh [base|persist|net|restart] [--skip-proof-aggregation true|false] [--no-prebuild]" >&2 exit 1 ;; esac @@ -29,18 +29,29 @@ parse_integration_args() { } export_integration_flags() { - export PROOF_AGGREGATION_ENABLED - if [[ "$PROOF_AGGREGATION_ENABLED" == "true" ]]; then + if [[ "$CIPHERNODE_SKIP_PROOF_AGGREGATION" == "true" ]]; then + FULL_PROOF_AGGREGATION=false + else + FULL_PROOF_AGGREGATION=true + fi + export FULL_PROOF_AGGREGATION + if [[ "$FULL_PROOF_AGGREGATION" == "true" ]]; then export ENABLE_ZK_VERIFICATION=true export INTEGRATION_DKG_TIMEOUT="${INTEGRATION_DKG_TIMEOUT:-3600}" else export ENABLE_ZK_VERIFICATION=false export INTEGRATION_DKG_TIMEOUT="${INTEGRATION_DKG_TIMEOUT:-1300}" fi + export CIPHERNODE_SKIP_PROOF_AGGREGATION + export E3_NODES__CN1__SKIP_PROOF_AGGREGATION="$CIPHERNODE_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN2__SKIP_PROOF_AGGREGATION="$CIPHERNODE_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN3__SKIP_PROOF_AGGREGATION="$CIPHERNODE_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN4__SKIP_PROOF_AGGREGATION="$CIPHERNODE_SKIP_PROOF_AGGREGATION" + export E3_NODES__CN5__SKIP_PROOF_AGGREGATION="$CIPHERNODE_SKIP_PROOF_AGGREGATION" } if [ $# -eq 0 ]; then - export PROOF_AGGREGATION_ENABLED=false + export CIPHERNODE_SKIP_PROOF_AGGREGATION=true export_integration_flags "$THIS_DIR/lib/prebuild.sh" "$THIS_DIR/persist.sh" From 52eeddf5770988c3a9aadb797e10d5ab64ab01fd Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 16 Jul 2026 16:58:33 +0500 Subject: [PATCH 19/52] chore(contracts): refresh predeployment storage baselines --- .../storage-layouts/BondingRegistry.json | 118 ++++---- .../CiphernodeRegistryOwnable.json | 140 ++++----- .../storage-layouts/E3RefundManager.json | 146 ++++----- .../audits/storage-layouts/Interfold.json | 286 +++++++++--------- 4 files changed, 341 insertions(+), 349 deletions(-) diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json index a9df7b0be..3313a030b 100644 --- a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -3,40 +3,40 @@ "contract": "BondingRegistry", "source": "contracts/registry/BondingRegistry.sol", "baseline": { - "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", "sourceSha256": "63dce01cce37e34a48bee91ebcf5d01f62cc85472418e4ac8e08cbce415e6097" }, "storage": [ { - "astId": 25215, + "astId": 26811, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketToken", "offset": 0, "slot": "0", - "type": "t_contract(InterfoldTicketToken)35413" + "type": "t_contract(InterfoldTicketToken)38747" }, { - "astId": 25219, + "astId": 26815, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseToken", "offset": 0, "slot": "1", - "type": "t_contract(IERC20)3710" + "type": "t_contract(IERC20)4293" }, { - "astId": 25223, + "astId": 26819, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registry", "offset": 0, "slot": "2", - "type": "t_contract(ICiphernodeRegistry)20485" + "type": "t_contract(ICiphernodeRegistry)21722" }, { - "astId": 25226, + "astId": 26822, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashingManager", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_address" }, { - "astId": 25231, + "astId": 26827, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributors", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 25234, + "astId": 26830, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributorCount", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 25253, + "astId": 26849, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedFundsTreasury", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_address" }, { - "astId": 25256, + "astId": 26852, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketPrice", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_uint256" }, { - "astId": 25259, + "astId": 26855, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseRequiredBond", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_uint256" }, { - "astId": 25262, + "astId": 26858, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "minTicketBalance", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_uint256" }, { - "astId": 25265, + "astId": 26861, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitDelay", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_uint64" }, { - "astId": 25268, + "astId": 26864, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseActiveBps", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_uint256" }, { - "astId": 25271, + "astId": 26867, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "numActiveOperators", "offset": 0, @@ -116,15 +116,15 @@ "type": "t_uint256" }, { - "astId": 25289, + "astId": 26885, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operators", "offset": 0, "slot": "13", - "type": "t_mapping(t_address,t_struct(Operator)25283_storage)" + "type": "t_mapping(t_address,t_struct(Operator)26879_storage)" }, { - "astId": 25292, + "astId": 26888, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedTicketBalance", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 25295, + "astId": 26891, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedLicenseBond", "offset": 0, @@ -140,15 +140,15 @@ "type": "t_uint256" }, { - "astId": 25299, + "astId": 26895, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_exits", "offset": 0, "slot": "16", - "type": "t_struct(ExitQueueState)22998_storage" + "type": "t_struct(ExitQueueState)24594_storage" }, { - "astId": 25302, + "astId": 26898, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityConfigurationLocked", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_bool" }, { - "astId": 25305, + "astId": 26901, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "reservedSlashedTicketBalance", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_uint256" }, { - "astId": 27530, + "astId": 29126, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "__gap", "offset": 0, @@ -178,8 +178,8 @@ "label": "address", "numberOfBytes": "20" }, - "t_array(t_struct(ExitTranche)22967_storage)dyn_storage": { - "base": "t_struct(ExitTranche)22967_storage", + "t_array(t_struct(ExitTranche)24563_storage)dyn_storage": { + "base": "t_struct(ExitTranche)24563_storage", "encoding": "dynamic_array", "label": "struct ExitQueueLib.ExitTranche[]", "numberOfBytes": "32" @@ -195,27 +195,27 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICiphernodeRegistry)20485": { + "t_contract(ICiphernodeRegistry)21722": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IERC20)3710": { + "t_contract(IERC20)4293": { "encoding": "inplace", "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(InterfoldTicketToken)35413": { + "t_contract(InterfoldTicketToken)38747": { "encoding": "inplace", "label": "contract InterfoldTicketToken", "numberOfBytes": "20" }, - "t_mapping(t_address,t_array(t_struct(ExitTranche)22967_storage)dyn_storage)": { + "t_mapping(t_address,t_array(t_struct(ExitTranche)24563_storage)dyn_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", "numberOfBytes": "32", - "value": "t_array(t_struct(ExitTranche)22967_storage)dyn_storage" + "value": "t_array(t_struct(ExitTranche)24563_storage)dyn_storage" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", @@ -224,19 +224,19 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_struct(Operator)25283_storage)": { + "t_mapping(t_address,t_struct(Operator)26879_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct BondingRegistry.Operator)", "numberOfBytes": "32", - "value": "t_struct(Operator)25283_storage" + "value": "t_struct(Operator)26879_storage" }, - "t_mapping(t_address,t_struct(PendingAmounts)22973_storage)": { + "t_mapping(t_address,t_struct(PendingAmounts)24569_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", "numberOfBytes": "32", - "value": "t_struct(PendingAmounts)22973_storage" + "value": "t_struct(PendingAmounts)24569_storage" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -245,20 +245,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(ExitQueueState)22998_storage": { + "t_struct(ExitQueueState)24594_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitQueueState", "members": [ { - "astId": 22980, + "astId": 24576, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operatorQueues", "offset": 0, "slot": "0", - "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)22967_storage)dyn_storage)" + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24563_storage)dyn_storage)" }, { - "astId": 22984, + "astId": 24580, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexTicket", "offset": 0, @@ -266,7 +266,7 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 22988, + "astId": 24584, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexLicense", "offset": 0, @@ -274,15 +274,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 22993, + "astId": 24589, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "pendingTotals", "offset": 0, "slot": "3", - "type": "t_mapping(t_address,t_struct(PendingAmounts)22973_storage)" + "type": "t_mapping(t_address,t_struct(PendingAmounts)24569_storage)" }, { - "astId": 22997, + "astId": 24593, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "liveTrancheCount", "offset": 0, @@ -292,12 +292,12 @@ ], "numberOfBytes": "160" }, - "t_struct(ExitTranche)22967_storage": { + "t_struct(ExitTranche)24563_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitTranche", "members": [ { - "astId": 22962, + "astId": 24558, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "unlockTimestamp", "offset": 0, @@ -305,7 +305,7 @@ "type": "t_uint64" }, { - "astId": 22964, + "astId": 24560, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -313,7 +313,7 @@ "type": "t_uint256" }, { - "astId": 22966, + "astId": 24562, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, @@ -323,12 +323,12 @@ ], "numberOfBytes": "96" }, - "t_struct(Operator)25283_storage": { + "t_struct(Operator)26879_storage": { "encoding": "inplace", "label": "struct BondingRegistry.Operator", "members": [ { - "astId": 25274, + "astId": 26870, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseBond", "offset": 0, @@ -336,7 +336,7 @@ "type": "t_uint256" }, { - "astId": 25276, + "astId": 26872, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitUnlocksAt", "offset": 0, @@ -344,7 +344,7 @@ "type": "t_uint64" }, { - "astId": 25278, + "astId": 26874, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registered", "offset": 8, @@ -352,7 +352,7 @@ "type": "t_bool" }, { - "astId": 25280, + "astId": 26876, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitRequested", "offset": 9, @@ -360,7 +360,7 @@ "type": "t_bool" }, { - "astId": 25282, + "astId": 26878, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "active", "offset": 10, @@ -370,12 +370,12 @@ ], "numberOfBytes": "64" }, - "t_struct(PendingAmounts)22973_storage": { + "t_struct(PendingAmounts)24569_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.PendingAmounts", "members": [ { - "astId": 22970, + "astId": 24566, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -383,7 +383,7 @@ "type": "t_uint256" }, { - "astId": 22972, + "astId": 24568, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json index 47d6bac9c..e6baa466b 100644 --- a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -3,32 +3,32 @@ "contract": "CiphernodeRegistryOwnable", "source": "contracts/registry/CiphernodeRegistryOwnable.sol", "baseline": { - "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", - "sourceSha256": "28aa169c03b248e1373c6cf57df74fcc2a6fe2695e70a3d215df6d5e80ee51a8" + "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", + "sourceSha256": "033ae30ce7a728081ca6cf34e050c970a5ed23fbab21ca81b66f4c8bd759af5e" }, "storage": [ { - "astId": 27598, + "astId": 29194, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)22027" + "type": "t_contract(IInterfold)23293" }, { - "astId": 27602, + "astId": 29198, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)19836" + "type": "t_contract(IBondingRegistry)21073" }, { - "astId": 27606, + "astId": 29202, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numCiphernodes", "offset": 0, @@ -36,7 +36,7 @@ "type": "t_uint256" }, { - "astId": 27609, + "astId": 29205, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "sortitionSubmissionWindow", "offset": 0, @@ -44,15 +44,15 @@ "type": "t_uint256" }, { - "astId": 27629, + "astId": 29225, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodes", "offset": 0, "slot": "4", - "type": "t_struct(LazyIMTData)12803_storage" + "type": "t_struct(LazyIMTData)14046_storage" }, { - "astId": 27634, + "astId": 29230, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeEnabled", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 27639, + "astId": 29235, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeTreeIndex", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_address,t_uint40)" }, { - "astId": 27644, + "astId": 29240, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "roots", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 27649, + "astId": 29245, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKeyHashes", "offset": 0, @@ -84,31 +84,31 @@ "type": "t_mapping(t_uint256,t_bytes32)" }, { - "astId": 27655, + "astId": 29251, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committees", "offset": 0, "slot": "10", - "type": "t_mapping(t_uint256,t_struct(Committee)19897_storage)" + "type": "t_mapping(t_uint256,t_struct(Committee)21134_storage)" }, { - "astId": 27659, + "astId": 29255, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashingManager", "offset": 0, "slot": "11", - "type": "t_contract(ISlashingManager)22674" + "type": "t_contract(ISlashingManager)23940" }, { - "astId": 27663, + "astId": 29259, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgFoldAttestationVerifier", "offset": 0, "slot": "12", - "type": "t_contract(IDkgFoldAttestationVerifier)20554" + "type": "t_contract(IDkgFoldAttestationVerifier)21825" }, { - "astId": 27670, + "astId": 29266, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifier", "offset": 0, @@ -116,7 +116,7 @@ "type": "t_address" }, { - "astId": 27672, + "astId": 29268, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifierAt", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint256" }, { - "astId": 27675, + "astId": 29271, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "accusationVoteValidity", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 27686, + "astId": 29282, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidity", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_uint256" }, { - "astId": 27688, + "astId": 29284, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidityAt", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_uint256" }, { - "astId": 27694, + "astId": 29290, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgPartyIds", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" }, { - "astId": 27699, + "astId": 29295, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgSkAggCommits", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 27704, + "astId": 29300, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgEsmAggCommits", "offset": 0, @@ -172,15 +172,15 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 27720, + "astId": 29316, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "_committeeDependencies", "offset": 0, "slot": "21", - "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)27714_storage)" + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29310_storage)" }, { - "astId": 30248, + "astId": 31840, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "__gap", "offset": 0, @@ -234,32 +234,32 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)19836": { + "t_contract(IBondingRegistry)21073": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IDkgFoldAttestationVerifier)20554": { + "t_contract(IDkgFoldAttestationVerifier)21825": { "encoding": "inplace", "label": "contract IDkgFoldAttestationVerifier", "numberOfBytes": "20" }, - "t_contract(IInterfold)22027": { + "t_contract(IInterfold)23293": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)22674": { + "t_contract(ISlashingManager)23940": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeStage)19860": { + "t_enum(CommitteeStage)21097": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.CommitteeStage", "numberOfBytes": "1" }, - "t_enum(MemberStatus)19854": { + "t_enum(MemberStatus)21091": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.MemberStatus", "numberOfBytes": "1" @@ -271,12 +271,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_enum(MemberStatus)19854)": { + "t_mapping(t_address,t_enum(MemberStatus)21091)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", "numberOfBytes": "32", - "value": "t_enum(MemberStatus)19854" + "value": "t_enum(MemberStatus)21091" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -313,19 +313,19 @@ "numberOfBytes": "32", "value": "t_bytes32" }, - "t_mapping(t_uint256,t_struct(Committee)19897_storage)": { + "t_mapping(t_uint256,t_struct(Committee)21134_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", "numberOfBytes": "32", - "value": "t_struct(Committee)19897_storage" + "value": "t_struct(Committee)21134_storage" }, - "t_mapping(t_uint256,t_struct(CommitteeDependencies)27714_storage)": { + "t_mapping(t_uint256,t_struct(CommitteeDependencies)29310_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", "numberOfBytes": "32", - "value": "t_struct(CommitteeDependencies)27714_storage" + "value": "t_struct(CommitteeDependencies)29310_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -334,20 +334,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(Committee)19897_storage": { + "t_struct(Committee)21134_storage": { "encoding": "inplace", "label": "struct ICiphernodeRegistry.Committee", "members": [ { - "astId": 19864, + "astId": 21101, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "stage", "offset": 0, "slot": "0", - "type": "t_enum(CommitteeStage)19860" + "type": "t_enum(CommitteeStage)21097" }, { - "astId": 19866, + "astId": 21103, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "seed", "offset": 0, @@ -355,7 +355,7 @@ "type": "t_uint256" }, { - "astId": 19868, + "astId": 21105, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "requestBlock", "offset": 0, @@ -363,7 +363,7 @@ "type": "t_uint256" }, { - "astId": 19870, + "astId": 21107, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeDeadline", "offset": 0, @@ -371,7 +371,7 @@ "type": "t_uint256" }, { - "astId": 19872, + "astId": 21109, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKey", "offset": 0, @@ -379,7 +379,7 @@ "type": "t_bytes32" }, { - "astId": 19876, + "astId": 21113, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "threshold", "offset": 0, @@ -387,7 +387,7 @@ "type": "t_array(t_uint32)2_storage" }, { - "astId": 19879, + "astId": 21116, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "topNodes", "offset": 0, @@ -395,7 +395,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 19881, + "astId": 21118, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeHash", "offset": 0, @@ -403,7 +403,7 @@ "type": "t_bytes32" }, { - "astId": 19885, + "astId": 21122, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "submitted", "offset": 0, @@ -411,7 +411,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 19889, + "astId": 21126, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "scoreOf", "offset": 0, @@ -419,15 +419,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 19894, + "astId": 21131, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "memberStatus", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_enum(MemberStatus)19854)" + "type": "t_mapping(t_address,t_enum(MemberStatus)21091)" }, { - "astId": 19896, + "astId": 21133, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "activeCount", "offset": 0, @@ -437,43 +437,43 @@ ], "numberOfBytes": "384" }, - "t_struct(CommitteeDependencies)27714_storage": { + "t_struct(CommitteeDependencies)29310_storage": { "encoding": "inplace", "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", "members": [ { - "astId": 27707, + "astId": 29303, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfoldContract", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)22027" + "type": "t_contract(IInterfold)23293" }, { - "astId": 27710, + "astId": 29306, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bonding", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)19836" + "type": "t_contract(IBondingRegistry)21073" }, { - "astId": 27713, + "astId": 29309, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)22674" + "type": "t_contract(ISlashingManager)23940" } ], "numberOfBytes": "96" }, - "t_struct(LazyIMTData)12803_storage": { + "t_struct(LazyIMTData)14046_storage": { "encoding": "inplace", "label": "struct LazyIMTData", "members": [ { - "astId": 12796, + "astId": 14039, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "maxIndex", "offset": 0, @@ -481,7 +481,7 @@ "type": "t_uint40" }, { - "astId": 12798, + "astId": 14041, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numberOfLeaves", "offset": 5, @@ -489,7 +489,7 @@ "type": "t_uint40" }, { - "astId": 12802, + "astId": 14045, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "elements", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json index 4b23eb5b0..cfa8c019b 100644 --- a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -3,32 +3,32 @@ "contract": "E3RefundManager", "source": "contracts/E3RefundManager.sol", "baseline": { - "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", + "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", "sourceSha256": "a6ba37835c555d69dc3592a8b47089a2a34e5dfbbe87dfa1b95f1b318875a21b" }, "storage": [ { - "astId": 13997, + "astId": 15240, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)22027" + "type": "t_contract(IInterfold)23293" }, { - "astId": 14001, + "astId": 15244, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)19836" + "type": "t_contract(IBondingRegistry)21073" }, { - "astId": 14004, + "astId": 15247, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -36,23 +36,23 @@ "type": "t_address" }, { - "astId": 14008, + "astId": 15251, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_workAllocation", "offset": 0, "slot": "3", - "type": "t_struct(WorkValueAllocation)20661_storage" + "type": "t_struct(WorkValueAllocation)21930_storage" }, { - "astId": 14014, + "astId": 15257, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_distributions", "offset": 0, "slot": "4", - "type": "t_mapping(t_uint256,t_struct(RefundDistribution)20695_storage)" + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21964_storage)" }, { - "astId": 14021, + "astId": 15264, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_requesterClaimed", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" }, { - "astId": 14026, + "astId": 15269, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_claimCount", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 14031, + "astId": 15274, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodeClaimCount", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 14036, + "astId": 15279, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_totalHonestNodePaid", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 14042, + "astId": 15285, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodes", "offset": 0, @@ -92,15 +92,15 @@ "type": "t_mapping(t_uint256,t_array(t_address)dyn_storage)" }, { - "astId": 14050, + "astId": 15293, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingTreasury", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 14057, + "astId": 15300, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodeClaimed", "offset": 0, @@ -108,15 +108,15 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" }, { - "astId": 14063, + "astId": 15306, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_e3PolicySnapshots", "offset": 0, "slot": "12", - "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)20674_storage)" + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21943_storage)" }, { - "astId": 14066, + "astId": 15309, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "policyVersion", "offset": 0, @@ -124,39 +124,39 @@ "type": "t_uint64" }, { - "astId": 14074, + "astId": 15317, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingSlashedByToken", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_uint256))" + "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 14084, + "astId": 15327, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingSlashedClaims", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256)))" + "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_mapping(t_address,t_uint256)))" }, { - "astId": 14092, + "astId": 15335, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingTrackedTreasury", "offset": 0, "slot": "16", - "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 14098, + "astId": 15341, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_tokenLiability", "offset": 0, "slot": "17", - "type": "t_mapping(t_contract(IERC20)3710,t_uint256)" + "type": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, { - "astId": 14103, + "astId": 15346, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_successSettlementReady", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_bool)" }, { - "astId": 16362, + "astId": 17605, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "__gap", "offset": 0, @@ -195,17 +195,17 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(IBondingRegistry)19836": { + "t_contract(IBondingRegistry)21073": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IERC20)3710": { + "t_contract(IERC20)4293": { "encoding": "inplace", "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IInterfold)22027": { + "t_contract(IInterfold)23293": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" @@ -217,12 +217,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => mapping(contract IERC20 => uint256))", "numberOfBytes": "32", - "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -231,16 +231,16 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256))": { + "t_mapping(t_contract(IERC20)4293,t_mapping(t_address,t_uint256))": { "encoding": "mapping", - "key": "t_contract(IERC20)3710", + "key": "t_contract(IERC20)4293", "label": "mapping(contract IERC20 => mapping(address => uint256))", "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_contract(IERC20)3710,t_uint256)": { + "t_mapping(t_contract(IERC20)4293,t_uint256)": { "encoding": "mapping", - "key": "t_contract(IERC20)3710", + "key": "t_contract(IERC20)4293", "label": "mapping(contract IERC20 => uint256)", "numberOfBytes": "32", "value": "t_uint256" @@ -266,33 +266,33 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_bool)" }, - "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256)))": { + "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_mapping(t_address,t_uint256)))": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => mapping(contract IERC20 => mapping(address => uint256)))", "numberOfBytes": "32", - "value": "t_mapping(t_contract(IERC20)3710,t_mapping(t_address,t_uint256))" + "value": "t_mapping(t_contract(IERC20)4293,t_mapping(t_address,t_uint256))" }, - "t_mapping(t_uint256,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_uint256))": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => mapping(contract IERC20 => uint256))", "numberOfBytes": "32", - "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3PolicySnapshot)20674_storage)": { + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21943_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", "numberOfBytes": "32", - "value": "t_struct(E3PolicySnapshot)20674_storage" + "value": "t_struct(E3PolicySnapshot)21943_storage" }, - "t_mapping(t_uint256,t_struct(RefundDistribution)20695_storage)": { + "t_mapping(t_uint256,t_struct(RefundDistribution)21964_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", "numberOfBytes": "32", - "value": "t_struct(RefundDistribution)20695_storage" + "value": "t_struct(RefundDistribution)21964_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -301,20 +301,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(E3PolicySnapshot)20674_storage": { + "t_struct(E3PolicySnapshot)21943_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.E3PolicySnapshot", "members": [ { - "astId": 20665, + "astId": 21934, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "allocation", "offset": 0, "slot": "0", - "type": "t_struct(WorkValueAllocation)20661_storage" + "type": "t_struct(WorkValueAllocation)21930_storage" }, { - "astId": 20667, + "astId": 21936, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -322,7 +322,7 @@ "type": "t_address" }, { - "astId": 20669, + "astId": 21938, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, @@ -330,7 +330,7 @@ "type": "t_address" }, { - "astId": 20671, + "astId": 21940, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "version", "offset": 20, @@ -338,7 +338,7 @@ "type": "t_uint64" }, { - "astId": 20673, + "astId": 21942, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "initialized", "offset": 28, @@ -348,12 +348,12 @@ ], "numberOfBytes": "96" }, - "t_struct(RefundDistribution)20695_storage": { + "t_struct(RefundDistribution)21964_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.RefundDistribution", "members": [ { - "astId": 20677, + "astId": 21946, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "requesterAmount", "offset": 0, @@ -361,7 +361,7 @@ "type": "t_uint256" }, { - "astId": 20679, + "astId": 21948, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeAmount", "offset": 0, @@ -369,7 +369,7 @@ "type": "t_uint256" }, { - "astId": 20681, + "astId": 21950, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolAmount", "offset": 0, @@ -377,7 +377,7 @@ "type": "t_uint256" }, { - "astId": 20683, + "astId": 21952, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "totalSlashed", "offset": 0, @@ -385,7 +385,7 @@ "type": "t_uint256" }, { - "astId": 20685, + "astId": 21954, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeCount", "offset": 0, @@ -393,7 +393,7 @@ "type": "t_uint256" }, { - "astId": 20687, + "astId": 21956, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "calculated", "offset": 0, @@ -401,15 +401,15 @@ "type": "t_bool" }, { - "astId": 20690, + "astId": 21959, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "feeToken", "offset": 1, "slot": "5", - "type": "t_contract(IERC20)3710" + "type": "t_contract(IERC20)4293" }, { - "astId": 20692, + "astId": 21961, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "originalPayment", "offset": 0, @@ -417,7 +417,7 @@ "type": "t_uint256" }, { - "astId": 20694, + "astId": 21963, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "perNodeAmount", "offset": 0, @@ -427,12 +427,12 @@ ], "numberOfBytes": "256" }, - "t_struct(WorkValueAllocation)20661_storage": { + "t_struct(WorkValueAllocation)21930_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.WorkValueAllocation", "members": [ { - "astId": 20652, + "astId": 21921, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "committeeFormationBps", "offset": 0, @@ -440,7 +440,7 @@ "type": "t_uint16" }, { - "astId": 20654, + "astId": 21923, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "dkgBps", "offset": 2, @@ -448,7 +448,7 @@ "type": "t_uint16" }, { - "astId": 20656, + "astId": 21925, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "decryptionBps", "offset": 4, @@ -456,7 +456,7 @@ "type": "t_uint16" }, { - "astId": 20658, + "astId": 21927, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolBps", "offset": 6, @@ -464,7 +464,7 @@ "type": "t_uint16" }, { - "astId": 20660, + "astId": 21929, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "successSlashedNodeBps", "offset": 8, diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json index 12d0805f4..06b0f6b95 100644 --- a/packages/interfold-contracts/audits/storage-layouts/Interfold.json +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -3,56 +3,56 @@ "contract": "Interfold", "source": "contracts/Interfold.sol", "baseline": { - "buildInfoId": "solc-0_8_28-7e1c3095a523de6d38d9acb51d82270ee976d136", + "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "367667d4585586c53416b43d8c49371f04ef2d79", - "sourceSha256": "a805ec4ace920e3efe826b5ed8ca2b667a2764213f3f3bcd56bba6d0a78b86a4" + "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", + "sourceSha256": "aaddcad10578e4520e1588c68fd25f7cf437fbbb2d8845e50bc11759f6954bab" }, "storage": [ { - "astId": 16425, + "astId": 17668, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphernodeRegistry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)20485" + "type": "t_contract(ICiphernodeRegistry)21722" }, { - "astId": 16429, + "astId": 17672, "contract": "project/contracts/Interfold.sol:Interfold", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)19836" + "type": "t_contract(IBondingRegistry)21073" }, { - "astId": 16433, + "astId": 17676, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3RefundManager", "offset": 0, "slot": "2", - "type": "t_contract(IE3RefundManager)21075" + "type": "t_contract(IE3RefundManager)22344" }, { - "astId": 16437, + "astId": 17680, "contract": "project/contracts/Interfold.sol:Interfold", "label": "slashingManager", "offset": 0, "slot": "3", - "type": "t_contract(ISlashingManager)22674" + "type": "t_contract(ISlashingManager)23940" }, { - "astId": 16441, + "astId": 17684, "contract": "project/contracts/Interfold.sol:Interfold", "label": "feeToken", "offset": 0, "slot": "4", - "type": "t_contract(IERC20)3710" + "type": "t_contract(IERC20)4293" }, { - "astId": 16444, + "astId": 17687, "contract": "project/contracts/Interfold.sol:Interfold", "label": "maxDuration", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 16447, + "astId": 17690, "contract": "project/contracts/Interfold.sol:Interfold", "label": "nexte3Id", "offset": 0, @@ -68,39 +68,39 @@ "type": "t_uint256" }, { - "astId": 16453, + "astId": 17696, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Programs", "offset": 0, "slot": "7", - "type": "t_mapping(t_contract(IE3Program)20642,t_bool)" + "type": "t_mapping(t_contract(IE3Program)21911,t_bool)" }, { - "astId": 16459, + "astId": 17702, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3s", "offset": 0, "slot": "8", - "type": "t_mapping(t_uint256,t_struct(E3)20602_storage)" + "type": "t_mapping(t_uint256,t_struct(E3)21871_storage)" }, { - "astId": 16465, + "astId": 17708, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifiers", "offset": 0, "slot": "9", - "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)20527)" + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21798)" }, { - "astId": 16471, + "astId": 17714, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifiers", "offset": 0, "slot": "10", - "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)22065)" + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23331)" }, { - "astId": 16476, + "astId": 17719, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSetRegistry", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_mapping(t_uint8,t_bytes_storage)" }, { - "astId": 16481, + "astId": 17724, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Payments", "offset": 0, @@ -116,31 +116,31 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 16487, + "astId": 17730, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Stages", "offset": 0, "slot": "13", - "type": "t_mapping(t_uint256,t_enum(E3Stage)21104)" + "type": "t_mapping(t_uint256,t_enum(E3Stage)22373)" }, { - "astId": 16493, + "astId": 17736, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Deadlines", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_struct(E3Deadlines)21136_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22405_storage)" }, { - "astId": 16499, + "astId": 17742, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3FailureReasons", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_enum(FailureReason)21120)" + "type": "t_mapping(t_uint256,t_enum(FailureReason)22389)" }, { - "astId": 16504, + "astId": 17747, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Requesters", "offset": 0, @@ -148,23 +148,23 @@ "type": "t_mapping(t_uint256,t_address)" }, { - "astId": 16510, + "astId": 17753, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3FeeTokens", "offset": 0, "slot": "17", - "type": "t_mapping(t_uint256,t_contract(IERC20)3710)" + "type": "t_mapping(t_uint256,t_contract(IERC20)4293)" }, { - "astId": 16518, + "astId": 17761, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeThresholds", "offset": 0, "slot": "18", - "type": "t_mapping(t_enum(CommitteeSize)21095,t_array(t_uint32)2_storage)" + "type": "t_mapping(t_enum(CommitteeSize)22364,t_array(t_uint32)2_storage)" }, { - "astId": 16523, + "astId": 17766, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3ProtocolShareBps", "offset": 0, @@ -172,7 +172,7 @@ "type": "t_mapping(t_uint256,t_uint16)" }, { - "astId": 16528, + "astId": 17771, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3ProtocolTreasury", "offset": 0, @@ -180,31 +180,31 @@ "type": "t_mapping(t_uint256,t_address)" }, { - "astId": 16532, + "astId": 17775, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_timeoutConfig", "offset": 0, "slot": "21", - "type": "t_struct(E3TimeoutConfig)21128_storage" + "type": "t_struct(E3TimeoutConfig)22397_storage" }, { - "astId": 16536, + "astId": 17779, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pricingConfig", "offset": 0, "slot": "24", - "type": "t_struct(PricingConfig)21168_storage" + "type": "t_struct(PricingConfig)22437_storage" }, { - "astId": 16546, + "astId": 17789, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_feeTokenAllowed", "offset": 0, "slot": "33", - "type": "t_mapping(t_contract(IERC20)3710,t_bool)" + "type": "t_mapping(t_contract(IERC20)4293,t_bool)" }, { - "astId": 16553, + "astId": 17796, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pendingRewards", "offset": 0, @@ -212,15 +212,15 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))" }, { - "astId": 16561, + "astId": 17804, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pendingTreasury", "offset": 0, "slot": "35", - "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))" + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 16574, + "astId": 17817, "contract": "project/contracts/Interfold.sol:Interfold", "label": "markFailedGracePeriod", "offset": 0, @@ -228,15 +228,15 @@ "type": "t_uint256" }, { - "astId": 16580, + "astId": 17823, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Dependencies", "offset": 0, "slot": "37", - "type": "t_mapping(t_uint256,t_struct(E3Dependencies)16571_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)" }, { - "astId": 19308, + "astId": 20545, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_consumedDecryptionProofs", "offset": 0, @@ -244,7 +244,7 @@ "type": "t_mapping(t_bytes32,t_bool)" }, { - "astId": 19313, + "astId": 20550, "contract": "project/contracts/Interfold.sol:Interfold", "label": "__gap", "offset": 0, @@ -291,67 +291,67 @@ "label": "bytes", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)19836": { + "t_contract(IBondingRegistry)21073": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(ICiphernodeRegistry)20485": { + "t_contract(ICiphernodeRegistry)21722": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IDecryptionVerifier)20527": { + "t_contract(IDecryptionVerifier)21798": { "encoding": "inplace", "label": "contract IDecryptionVerifier", "numberOfBytes": "20" }, - "t_contract(IE3Program)20642": { + "t_contract(IE3Program)21911": { "encoding": "inplace", "label": "contract IE3Program", "numberOfBytes": "20" }, - "t_contract(IE3RefundManager)21075": { + "t_contract(IE3RefundManager)22344": { "encoding": "inplace", "label": "contract IE3RefundManager", "numberOfBytes": "20" }, - "t_contract(IERC20)3710": { + "t_contract(IERC20)4293": { "encoding": "inplace", "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IPkVerifier)22065": { + "t_contract(IPkVerifier)23331": { "encoding": "inplace", "label": "contract IPkVerifier", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)22674": { + "t_contract(ISlashingManager)23940": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeSize)21095": { + "t_enum(CommitteeSize)22364": { "encoding": "inplace", "label": "enum IInterfold.CommitteeSize", "numberOfBytes": "1" }, - "t_enum(E3Stage)21104": { + "t_enum(E3Stage)22373": { "encoding": "inplace", "label": "enum IInterfold.E3Stage", "numberOfBytes": "1" }, - "t_enum(FailureReason)21120": { + "t_enum(FailureReason)22389": { "encoding": "inplace", "label": "enum IInterfold.FailureReason", "numberOfBytes": "1" }, - "t_mapping(t_address,t_mapping(t_contract(IERC20)3710,t_uint256))": { + "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => mapping(contract IERC20 => uint256))", "numberOfBytes": "32", - "value": "t_mapping(t_contract(IERC20)3710,t_uint256)" + "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -367,44 +367,44 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)20527)": { + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21798)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IDecryptionVerifier)", "numberOfBytes": "32", - "value": "t_contract(IDecryptionVerifier)20527" + "value": "t_contract(IDecryptionVerifier)21798" }, - "t_mapping(t_bytes32,t_contract(IPkVerifier)22065)": { + "t_mapping(t_bytes32,t_contract(IPkVerifier)23331)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IPkVerifier)", "numberOfBytes": "32", - "value": "t_contract(IPkVerifier)22065" + "value": "t_contract(IPkVerifier)23331" }, - "t_mapping(t_contract(IE3Program)20642,t_bool)": { + "t_mapping(t_contract(IE3Program)21911,t_bool)": { "encoding": "mapping", - "key": "t_contract(IE3Program)20642", + "key": "t_contract(IE3Program)21911", "label": "mapping(contract IE3Program => bool)", "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_contract(IERC20)3710,t_bool)": { + "t_mapping(t_contract(IERC20)4293,t_bool)": { "encoding": "mapping", - "key": "t_contract(IERC20)3710", + "key": "t_contract(IERC20)4293", "label": "mapping(contract IERC20 => bool)", "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_contract(IERC20)3710,t_uint256)": { + "t_mapping(t_contract(IERC20)4293,t_uint256)": { "encoding": "mapping", - "key": "t_contract(IERC20)3710", + "key": "t_contract(IERC20)4293", "label": "mapping(contract IERC20 => uint256)", "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_enum(CommitteeSize)21095,t_array(t_uint32)2_storage)": { + "t_mapping(t_enum(CommitteeSize)22364,t_array(t_uint32)2_storage)": { "encoding": "mapping", - "key": "t_enum(CommitteeSize)21095", + "key": "t_enum(CommitteeSize)22364", "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", "numberOfBytes": "32", "value": "t_array(t_uint32)2_storage" @@ -416,26 +416,26 @@ "numberOfBytes": "32", "value": "t_address" }, - "t_mapping(t_uint256,t_contract(IERC20)3710)": { + "t_mapping(t_uint256,t_contract(IERC20)4293)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => contract IERC20)", "numberOfBytes": "32", - "value": "t_contract(IERC20)3710" + "value": "t_contract(IERC20)4293" }, - "t_mapping(t_uint256,t_enum(E3Stage)21104)": { + "t_mapping(t_uint256,t_enum(E3Stage)22373)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.E3Stage)", "numberOfBytes": "32", - "value": "t_enum(E3Stage)21104" + "value": "t_enum(E3Stage)22373" }, - "t_mapping(t_uint256,t_enum(FailureReason)21120)": { + "t_mapping(t_uint256,t_enum(FailureReason)22389)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.FailureReason)", "numberOfBytes": "32", - "value": "t_enum(FailureReason)21120" + "value": "t_enum(FailureReason)22389" }, "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { "encoding": "mapping", @@ -444,26 +444,26 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3)20602_storage)": { + "t_mapping(t_uint256,t_struct(E3)21871_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct E3)", "numberOfBytes": "32", - "value": "t_struct(E3)20602_storage" + "value": "t_struct(E3)21871_storage" }, - "t_mapping(t_uint256,t_struct(E3Deadlines)21136_storage)": { + "t_mapping(t_uint256,t_struct(E3Deadlines)22405_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", "numberOfBytes": "32", - "value": "t_struct(E3Deadlines)21136_storage" + "value": "t_struct(E3Deadlines)22405_storage" }, - "t_mapping(t_uint256,t_struct(E3Dependencies)16571_storage)": { + "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct Interfold.E3Dependencies)", "numberOfBytes": "32", - "value": "t_struct(E3Dependencies)16571_storage" + "value": "t_struct(E3Dependencies)17814_storage" }, "t_mapping(t_uint256,t_uint16)": { "encoding": "mapping", @@ -486,12 +486,12 @@ "numberOfBytes": "32", "value": "t_bytes_storage" }, - "t_struct(E3)20602_storage": { + "t_struct(E3)21871_storage": { "encoding": "inplace", "label": "struct E3", "members": [ { - "astId": 20567, + "astId": 21838, "contract": "project/contracts/Interfold.sol:Interfold", "label": "seed", "offset": 0, @@ -499,15 +499,15 @@ "type": "t_uint256" }, { - "astId": 20570, + "astId": 21841, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeSize", "offset": 0, "slot": "1", - "type": "t_enum(CommitteeSize)21095" + "type": "t_enum(CommitteeSize)22364" }, { - "astId": 20572, + "astId": 21843, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requestBlock", "offset": 0, @@ -515,7 +515,7 @@ "type": "t_uint256" }, { - "astId": 20576, + "astId": 21847, "contract": "project/contracts/Interfold.sol:Interfold", "label": "inputWindow", "offset": 0, @@ -523,7 +523,7 @@ "type": "t_array(t_uint256)2_storage" }, { - "astId": 20578, + "astId": 21849, "contract": "project/contracts/Interfold.sol:Interfold", "label": "encryptionSchemeId", "offset": 0, @@ -531,15 +531,15 @@ "type": "t_bytes32" }, { - "astId": 20581, + "astId": 21852, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Program", "offset": 0, "slot": "6", - "type": "t_contract(IE3Program)20642" + "type": "t_contract(IE3Program)21911" }, { - "astId": 20583, + "astId": 21854, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSet", "offset": 20, @@ -547,7 +547,7 @@ "type": "t_uint8" }, { - "astId": 20585, + "astId": 21856, "contract": "project/contracts/Interfold.sol:Interfold", "label": "customParams", "offset": 0, @@ -555,23 +555,23 @@ "type": "t_bytes_storage" }, { - "astId": 20588, + "astId": 21859, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifier", "offset": 0, "slot": "8", - "type": "t_contract(IDecryptionVerifier)20527" + "type": "t_contract(IDecryptionVerifier)21798" }, { - "astId": 20591, + "astId": 21862, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifier", "offset": 0, "slot": "9", - "type": "t_contract(IPkVerifier)22065" + "type": "t_contract(IPkVerifier)23331" }, { - "astId": 20593, + "astId": 21864, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeePublicKey", "offset": 0, @@ -579,7 +579,7 @@ "type": "t_bytes32" }, { - "astId": 20595, + "astId": 21866, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphertextOutput", "offset": 0, @@ -587,7 +587,7 @@ "type": "t_bytes32" }, { - "astId": 20597, + "astId": 21868, "contract": "project/contracts/Interfold.sol:Interfold", "label": "plaintextOutput", "offset": 0, @@ -595,30 +595,22 @@ "type": "t_bytes_storage" }, { - "astId": 20599, + "astId": 21870, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requester", "offset": 0, "slot": "13", "type": "t_address" - }, - { - "astId": 20601, - "contract": "project/contracts/Interfold.sol:Interfold", - "label": "proofAggregationEnabled", - "offset": 20, - "slot": "13", - "type": "t_bool" } ], "numberOfBytes": "448" }, - "t_struct(E3Deadlines)21136_storage": { + "t_struct(E3Deadlines)22405_storage": { "encoding": "inplace", "label": "struct IInterfold.E3Deadlines", "members": [ { - "astId": 21131, + "astId": 22400, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgDeadline", "offset": 0, @@ -626,7 +618,7 @@ "type": "t_uint256" }, { - "astId": 21133, + "astId": 22402, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeDeadline", "offset": 0, @@ -634,7 +626,7 @@ "type": "t_uint256" }, { - "astId": 21135, + "astId": 22404, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionDeadline", "offset": 0, @@ -644,43 +636,43 @@ ], "numberOfBytes": "96" }, - "t_struct(E3Dependencies)16571_storage": { + "t_struct(E3Dependencies)17814_storage": { "encoding": "inplace", "label": "struct Interfold.E3Dependencies", "members": [ { - "astId": 16564, + "astId": 17807, "contract": "project/contracts/Interfold.sol:Interfold", "label": "registry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)20485" + "type": "t_contract(ICiphernodeRegistry)21722" }, { - "astId": 16567, + "astId": 17810, "contract": "project/contracts/Interfold.sol:Interfold", "label": "refundManager", "offset": 0, "slot": "1", - "type": "t_contract(IE3RefundManager)21075" + "type": "t_contract(IE3RefundManager)22344" }, { - "astId": 16570, + "astId": 17813, "contract": "project/contracts/Interfold.sol:Interfold", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)22674" + "type": "t_contract(ISlashingManager)23940" } ], "numberOfBytes": "96" }, - "t_struct(E3TimeoutConfig)21128_storage": { + "t_struct(E3TimeoutConfig)22397_storage": { "encoding": "inplace", "label": "struct IInterfold.E3TimeoutConfig", "members": [ { - "astId": 21123, + "astId": 22392, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgWindow", "offset": 0, @@ -688,7 +680,7 @@ "type": "t_uint256" }, { - "astId": 21125, + "astId": 22394, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeWindow", "offset": 0, @@ -696,7 +688,7 @@ "type": "t_uint256" }, { - "astId": 21127, + "astId": 22396, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionWindow", "offset": 0, @@ -706,12 +698,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PricingConfig)21168_storage": { + "t_struct(PricingConfig)22437_storage": { "encoding": "inplace", "label": "struct IInterfold.PricingConfig", "members": [ { - "astId": 21139, + "astId": 22408, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenFixedPerNode", "offset": 0, @@ -719,7 +711,7 @@ "type": "t_uint256" }, { - "astId": 21141, + "astId": 22410, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenPerEncryptionProof", "offset": 0, @@ -727,7 +719,7 @@ "type": "t_uint256" }, { - "astId": 21143, + "astId": 22412, "contract": "project/contracts/Interfold.sol:Interfold", "label": "coordinationPerPair", "offset": 0, @@ -735,7 +727,7 @@ "type": "t_uint256" }, { - "astId": 21145, + "astId": 22414, "contract": "project/contracts/Interfold.sol:Interfold", "label": "availabilityPerNodePerSec", "offset": 0, @@ -743,7 +735,7 @@ "type": "t_uint256" }, { - "astId": 21147, + "astId": 22416, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionPerNode", "offset": 0, @@ -751,7 +743,7 @@ "type": "t_uint256" }, { - "astId": 21149, + "astId": 22418, "contract": "project/contracts/Interfold.sol:Interfold", "label": "publicationBase", "offset": 0, @@ -759,7 +751,7 @@ "type": "t_uint256" }, { - "astId": 21151, + "astId": 22420, "contract": "project/contracts/Interfold.sol:Interfold", "label": "verificationPerProof", "offset": 0, @@ -767,7 +759,7 @@ "type": "t_uint256" }, { - "astId": 21153, + "astId": 22422, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolTreasury", "offset": 0, @@ -775,7 +767,7 @@ "type": "t_address" }, { - "astId": 21155, + "astId": 22424, "contract": "project/contracts/Interfold.sol:Interfold", "label": "marginBps", "offset": 20, @@ -783,7 +775,7 @@ "type": "t_uint16" }, { - "astId": 21157, + "astId": 22426, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolShareBps", "offset": 22, @@ -791,7 +783,7 @@ "type": "t_uint16" }, { - "astId": 21159, + "astId": 22428, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgUtilizationBps", "offset": 24, @@ -799,7 +791,7 @@ "type": "t_uint16" }, { - "astId": 21161, + "astId": 22430, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeUtilizationBps", "offset": 26, @@ -807,7 +799,7 @@ "type": "t_uint16" }, { - "astId": 21163, + "astId": 22432, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptUtilizationBps", "offset": 28, @@ -815,7 +807,7 @@ "type": "t_uint16" }, { - "astId": 21165, + "astId": 22434, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minCommitteeSize", "offset": 0, @@ -823,7 +815,7 @@ "type": "t_uint32" }, { - "astId": 21167, + "astId": 22436, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minThreshold", "offset": 4, From 112eb7557f9570e0ab0c7dd794a3bf9e34ed8e4b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 16 Jul 2026 20:52:34 +0500 Subject: [PATCH 20/52] fix(protocol): bind decryption proofs to E3 domains [C-03] --- Cargo.lock | 6 + agent/flow-trace/00_INDEX.md | 2 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 32 ++- .../integration_summary.json | 265 +++--------------- .../recursive_aggregation/c6_fold/src/main.nr | 23 +- .../c6_fold_kernel/src/main.nr | 18 +- .../decryption_aggregator/src/main.nr | 12 +- .../threshold/share_decryption/src/main.nr | 6 + .../src/ciphernode_builder.rs | 21 +- crates/committee-hash/Cargo.toml | 3 +- crates/committee-hash/src/lib.rs | 122 +++++++- crates/events/Cargo.toml | 1 + .../src/interfold_event/compute_request/zk.rs | 3 + crates/events/src/interfold_event/proof.rs | 16 +- crates/keyshare/Cargo.toml | 2 + crates/keyshare/src/ext.rs | 35 ++- .../keyshare/src/threshold_keyshare/actor.rs | 4 + .../effects/create_decryption_share.rs | 4 + .../effects/route_events.rs | 7 + .../keyshare/src/threshold_keyshare/state.rs | 5 + .../keyshare/src/threshold_keyshare/tests.rs | 2 + crates/multithread/Cargo.toml | 2 + crates/multithread/src/multithread.rs | 15 + .../zk-helpers/src/circuits/output_layout.rs | 22 +- .../threshold/share_decryption/circuit.rs | 3 + .../threshold/share_decryption/computation.rs | 8 + .../threshold/share_decryption/sample.rs | 2 + .../circuits/aggregation/c6_accumulator.rs | 15 +- .../src/circuits/aggregation/helpers.rs | 2 + .../src/circuits/aggregation/node_dkg_fold.rs | 13 +- .../tests/fold_accumulators_e2e_tests.rs | 9 +- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 13 +- .../ISlashingManager.json | 2 +- .../contracts/Interfold.sol | 11 +- .../interfaces/IDecryptionVerifier.sol | 27 +- .../contracts/interfaces/IInterfold.sol | 3 - .../contracts/lib/InterfoldPricing.sol | 61 +--- .../contracts/test/MockDecryptionVerifier.sol | 4 - .../verifiers/bfv/BfvDecryptionVerifier.sol | 49 ++-- .../bfv/honk/DecryptionAggregatorVerifier.sol | 98 +++---- .../scripts/storageLayouts.ts | 12 - packages/interfold-contracts/scripts/utils.ts | 7 +- .../test/BfvDecryptionVerifier.spec.ts | 151 ++++------ .../test/BfvVkBindingIntegration.spec.ts | 12 +- .../bfv_vk_binding/folded_artifacts.json | 8 +- 47 files changed, 585 insertions(+), 557 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45c390228..cec0d491d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,6 +3201,7 @@ version = "0.3.0" dependencies = [ "alloy", "hex", + "serde", ] [[package]] @@ -3387,6 +3388,7 @@ dependencies = [ "chrono", "derivative", "e3-ciphernode-builder", + "e3-committee-hash", "e3-crypto", "e3-data", "e3-events", @@ -3567,9 +3569,11 @@ name = "e3-keyshare" version = "0.3.0" dependencies = [ "actix", + "alloy", "anyhow", "async-trait", "bincode 1.3.3", + "e3-committee-hash", "e3-config", "e3-crypto", "e3-data", @@ -3610,9 +3614,11 @@ name = "e3-multithread" version = "0.3.0" dependencies = [ "actix", + "alloy", "anyhow", "bincode 1.3.3", "e3-bfv-client", + "e3-committee-hash", "e3-crypto", "e3-data", "e3-events", diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 18cdd50b7..a873fb31c 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -220,4 +220,4 @@ _Found during source-code cross-referencing of these trace documents._ | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | | 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | -| 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | `Interfold` canonicalizes each `(rawProof, publicInputs)` payload into a single-use nullifier before verification. A successfully consumed proof cannot complete another E3, and equivalent ABI encodings share the same nullifier; failed verification rolls all state back atomically. | +| 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 6decdb73f..62445748e 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -791,6 +791,11 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ → Circuit: ThresholdShareDecryption (C6) │ │ → Proves decryption share was correctly computed from │ │ sk_poly_sum, es_poly_sum, and ciphertext + │ │ → Publicly commits to the E3 decryption-domain limbs: + │ │ keccak256(abi.encode( + │ │ chainId, Interfold address, e3Id, committeeHash, + │ │ ciphertextOutputHash, committeePublicKey + │ │ )) │ │ → Fiat-Shamir transcript absorbs full `d` (all coefficients per CRT limb) │ ├─ ZkActor generates proof via bb binary │ ├─ Signs proof @@ -883,8 +888,11 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ ├─ Dispatches ComputeRequest::zk(ZkRequest::DecryptionAggregation { │ │ │ c6_total_slots, jobs, params_preset │ │ │ }) -│ │ ├─ Each job folds the selected C6 proofs for one ciphertext index and checks them -│ │ │ against the matching C7 proof inside `DecryptionAggregator` +│ │ ├─ Each job folds the selected C6 proofs for one ciphertext index, requires every +│ │ │ C6 leaf to carry the same E3 domain, and checks them against the matching C7 proof +│ │ │ inside `DecryptionAggregator` +│ │ ├─ `DecryptionAggregator` exposes that C6-authenticated domain as two public +│ │ │ 128-bit limbs in the final EVM proof │ │ ├─ Tracks the in-flight correlation id │ │ ├─ ComputeRequestError, missing C6 inner proofs, or C7/decryption-aggregator proof-count │ │ │ mismatches now emit @@ -909,17 +917,17 @@ InterfoldSolReader decodes CiphertextOutputPublished event │ │ publishPlaintextOutput(e3Id, output, proof) { │ │ │ 1. require(stage == CiphertextReady) │ │ │ 2. require(now <= decryptionDeadline) │ - │ │ 3. require(proof.length > 0), then canonically │ - │ │ decode (rawProof, publicInputs), │ - │ │ derive a proof nullifier, require it unused, │ - │ │ and consume it (equivalent ABI encodings map │ - │ │ to the same nullifier) │ - │ │ and call decryptionVerifier.verify( │ - │ │ e3Id, committeeRoot, │ - │ │ committeeNodes, ciphertextOutput, │ - │ │ committeePublicKey, │ - │ │ keccak256(output), proof │ + │ │ 3. require(proof.length > 0), recompute │ + │ │ decryptionDomain = keccak256(abi.encode( │ + │ │ chainId, address(this), e3Id, │ + │ │ committeeHash, ciphertextOutput, │ + │ │ committeePublicKey │ + │ │ )), then call decryptionVerifier.verify( │ + │ │ decryptionDomain, keccak256(output), │ + │ │ committeeHash, proof │ │ │ ) │ + │ │ → C-03: final proof domain must match the │ + │ │ domain already committed by every C6 leaf. │ │ │ → M-34: c6Fold / C7 VK hashes are immutable. │ │ │ → M-35: revert path only (no `bool false`). │ │ │ 4. stage = Complete │ diff --git a/circuits/benchmarks/results_insecure_minimum/integration_summary.json b/circuits/benchmarks/results_insecure_minimum/integration_summary.json index 83fd87c6b..e8b8d17ab 100644 --- a/circuits/benchmarks/results_insecure_minimum/integration_summary.json +++ b/circuits/benchmarks/results_insecure_minimum/integration_summary.json @@ -6,7 +6,7 @@ "bfv_preset": "InsecureThreshold512", "lambda": 2, "proof_aggregation_enabled": true, - "multithread_concurrent_jobs": 13, + "multithread_concurrent_jobs": 4, "committee_h": 2, "committee_n": 3, "committee_t": 1, @@ -17,234 +17,61 @@ "proof_aggregation_enabled": true, "dkg_fold_attestation_verifier": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", "multithread": { - "rayon_threads": 13, - "max_simultaneous_rayon_tasks": 13, - "cores_available": 14 + "rayon_threads": 4, + "max_simultaneous_rayon_tasks": 4, + "cores_available": 8 }, "operation_timings": [ - { - "name": "CalculateDecryptionKey", - "avg_seconds": 0.003846458, - "runs": 3, - "total_seconds": 0.011539375 - }, - { - "name": "CalculateDecryptionShare", - "avg_seconds": 0.021596236, - "runs": 3, - "total_seconds": 0.064788708 - }, - { - "name": "CalculateThresholdDecryption", - "avg_seconds": 0.021793167, - "runs": 1, - "total_seconds": 0.021793167 - }, - { - "name": "GenEsiSss", - "avg_seconds": 0.006900819, - "runs": 3, - "total_seconds": 0.020702458 - }, - { - "name": "GenPkShareAndSkSss", - "avg_seconds": 0.01082625, - "runs": 3, - "total_seconds": 0.03247875 - }, - { - "name": "NodeDkgFold/c2ab_fold", - "avg_seconds": 18.129162805, - "runs": 3, - "total_seconds": 54.387488416 - }, - { - "name": "NodeDkgFold/c3a_fold", - "avg_seconds": 71.186463291, - "runs": 3, - "total_seconds": 213.559389875 - }, - { - "name": "NodeDkgFold/c3ab_fold", - "avg_seconds": 7.876658916, - "runs": 3, - "total_seconds": 23.62997675 - }, - { - "name": "NodeDkgFold/c3b_fold", - "avg_seconds": 70.951759472, - "runs": 3, - "total_seconds": 212.855278417 - }, - { - "name": "NodeDkgFold/c4ab_fold", - "avg_seconds": 7.872269472, - "runs": 3, - "total_seconds": 23.616808416 - }, - { - "name": "NodeDkgFold/node_fold", - "avg_seconds": 18.285537124, - "runs": 3, - "total_seconds": 54.856611374 - }, - { - "name": "ZkDecryptedSharesAggregation", - "avg_seconds": 1.53384025, - "runs": 1, - "total_seconds": 1.53384025 - }, - { - "name": "ZkDecryptionAggregation", - "avg_seconds": 46.886299625, - "runs": 1, - "total_seconds": 46.886299625 - }, - { - "name": "ZkDkgAggregation", - "avg_seconds": 5.350731209, - "runs": 1, - "total_seconds": 5.350731209 - }, - { - "name": "ZkDkgShareDecryption", - "avg_seconds": 1.001284562, - "runs": 6, - "total_seconds": 6.007707375 - }, - { - "name": "ZkNodeDkgFold", - "avg_seconds": 105.400482805, - "runs": 3, - "total_seconds": 316.201448416 - }, - { - "name": "ZkNodesFoldStep", - "avg_seconds": 5.041216854, - "runs": 2, - "total_seconds": 10.082433708 - }, - { - "name": "ZkPkAggregation", - "avg_seconds": 0.408154917, - "runs": 1, - "total_seconds": 0.408154917 - }, - { - "name": "ZkPkBfv", - "avg_seconds": 0.217929541, - "runs": 3, - "total_seconds": 0.653788625 - }, - { - "name": "ZkPkGeneration", - "avg_seconds": 2.247310694, - "runs": 3, - "total_seconds": 6.741932083 - }, - { - "name": "ZkShareComputation", - "avg_seconds": 2.368657458, - "runs": 6, - "total_seconds": 14.211944749 - }, - { - "name": "ZkShareEncryption", - "avg_seconds": 4.003050597, - "runs": 24, - "total_seconds": 96.073214335 - }, - { - "name": "ZkThresholdShareDecryption", - "avg_seconds": 3.246631902, - "runs": 3, - "total_seconds": 9.739895708 - }, - { - "name": "ZkVerifyShareDecryptionProofs", - "avg_seconds": 0.082421444, - "runs": 3, - "total_seconds": 0.247264333 - }, - { - "name": "ZkVerifyShareProofs", - "avg_seconds": 0.253021858, - "runs": 5, - "total_seconds": 1.265109292 - } + {"name": "CalculateDecryptionKey", "avg_seconds": 0.002493002, "runs": 3, "total_seconds": 0.007479008}, + {"name": "CalculateDecryptionShare", "avg_seconds": 0.014546000, "runs": 3, "total_seconds": 0.043638000}, + {"name": "CalculateThresholdDecryption", "avg_seconds": 0.014658909, "runs": 1, "total_seconds": 0.014658909}, + {"name": "GenEsiSss", "avg_seconds": 0.003312954, "runs": 3, "total_seconds": 0.009938863}, + {"name": "GenPkShareAndSkSss", "avg_seconds": 0.006545443, "runs": 3, "total_seconds": 0.019636331}, + {"name": "NodeDkgFold/c2ab_fold", "avg_seconds": 15.542266178, "runs": 3, "total_seconds": 46.626798535}, + {"name": "NodeDkgFold/c3a_fold", "avg_seconds": 67.103157296, "runs": 3, "total_seconds": 201.309471890}, + {"name": "NodeDkgFold/c3ab_fold", "avg_seconds": 8.157004177, "runs": 3, "total_seconds": 24.471012532}, + {"name": "NodeDkgFold/c3b_fold", "avg_seconds": 66.292620289, "runs": 3, "total_seconds": 198.877860868}, + {"name": "NodeDkgFold/c4ab_fold", "avg_seconds": 8.877390382, "runs": 3, "total_seconds": 26.632171146}, + {"name": "NodeDkgFold/node_fold", "avg_seconds": 20.289683136, "runs": 3, "total_seconds": 60.869049409}, + {"name": "ZkDecryptedSharesAggregation", "avg_seconds": 1.746945975, "runs": 1, "total_seconds": 1.746945975}, + {"name": "ZkDecryptionAggregation", "avg_seconds": 77.416901137, "runs": 1, "total_seconds": 77.416901137}, + {"name": "ZkDkgAggregation", "avg_seconds": 8.409547509, "runs": 1, "total_seconds": 8.409547509}, + {"name": "ZkDkgShareDecryption", "avg_seconds": 0.927539390, "runs": 6, "total_seconds": 5.565236343}, + {"name": "ZkNodeDkgFold", "avg_seconds": 146.711601900, "runs": 3, "total_seconds": 440.134805700}, + {"name": "ZkNodesFoldStep", "avg_seconds": 8.132157674, "runs": 2, "total_seconds": 16.264315348}, + {"name": "ZkPkAggregation", "avg_seconds": 0.678950539, "runs": 1, "total_seconds": 0.678950539}, + {"name": "ZkPkBfv", "avg_seconds": 0.243462617, "runs": 3, "total_seconds": 0.730387852}, + {"name": "ZkPkGeneration", "avg_seconds": 1.639551785, "runs": 3, "total_seconds": 4.918655355}, + {"name": "ZkShareComputation", "avg_seconds": 1.182185354, "runs": 6, "total_seconds": 7.093112124}, + {"name": "ZkShareEncryption", "avg_seconds": 1.861872787, "runs": 24, "total_seconds": 44.684946896}, + {"name": "ZkThresholdShareDecryption", "avg_seconds": 4.559046281, "runs": 3, "total_seconds": 13.677138845}, + {"name": "ZkVerifyShareDecryptionProofs", "avg_seconds": 0.041032219, "runs": 3, "total_seconds": 0.123096659}, + {"name": "ZkVerifyShareProofs", "avg_seconds": 0.148539630, "runs": 5, "total_seconds": 0.742698153} ], - "operation_timings_total_seconds": 1098.460620331, + "operation_timings_total_seconds": 1181.068453926, "operation_timings_metric": "tracked_job_wall", "phase_timings": [ - { - "label": "Starting trbfv actor test", - "seconds": 0e-9, - "metric": "wall_clock" - }, - { - "label": "Setup completed", - "seconds": 0.9476265, - "metric": "wall_clock" - }, - { - "label": "Committee Setup Completed", - "seconds": 7.02067775, - "metric": "wall_clock" - }, - { - "label": "Committee Finalization Complete", - "seconds": 0.003086, - "metric": "wall_clock" - }, - { - "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", - "seconds": 120.643449, - "metric": "wall_clock" - }, - { - "label": "ThresholdShares -> PublicKeyAggregated", - "seconds": 132.69507875, - "metric": "wall_clock" - }, - { - "label": "E3Request -> PublicKeyAggregated", - "seconds": 133.201828291, - "metric": "wall_clock" - }, - { - "label": "Application CT Gen", - "seconds": 0.009273208, - "metric": "wall_clock" - }, - { - "label": "Running FHE Application", - "seconds": 0.000058375, - "metric": "wall_clock" - }, - { - "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", - "seconds": 48.434143, - "metric": "wall_clock" - }, - { - "label": "Ciphertext published -> PlaintextAggregated", - "seconds": 51.995006459, - "metric": "wall_clock" - }, - { - "label": "Entire Test", - "seconds": 193.176080792, - "metric": "wall_clock" - } + {"label": "Starting trbfv actor test", "seconds": 0.000000000, "metric": "wall_clock"}, + {"label": "Setup completed", "seconds": 0.800449549, "metric": "wall_clock"}, + {"label": "Committee Setup Completed", "seconds": 7.024013699, "metric": "wall_clock"}, + {"label": "Committee Finalization Complete", "seconds": 0.001819861, "metric": "wall_clock"}, + {"label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", "seconds": 134.166717000, "metric": "wall_clock"}, + {"label": "ThresholdShares -> PublicKeyAggregated", "seconds": 241.734708588, "metric": "wall_clock"}, + {"label": "E3Request -> PublicKeyAggregated", "seconds": 242.237859363, "metric": "wall_clock"}, + {"label": "Application CT Gen", "seconds": 0.011831894, "metric": "wall_clock"}, + {"label": "Running FHE Application", "seconds": 0.000089208, "metric": "wall_clock"}, + {"label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", "seconds": 77.109859000, "metric": "wall_clock"}, + {"label": "Ciphertext published -> PlaintextAggregated", "seconds": 83.937900581, "metric": "wall_clock"}, + {"label": "Entire Test", "seconds": 334.013529569, "metric": "wall_clock"} ], "folded_artifacts": { "dkg_aggregator": { - "proof_hex": "0x000000000000000000000000000000000000000000000001e75e6e6bf316ec7900000000000000000000000000000000000000000000000e5e66538093519ec700000000000000000000000000000000000000000000000ea1d99185bf57a67100000000000000000000000000000000000000000000000000027a81f6b520c100000000000000000000000000000000000000000000000300a975ba6f13dbee000000000000000000000000000000000000000000000004a477a61f80a9e6d100000000000000000000000000000000000000000000000fd219fd84fb4defd70000000000000000000000000000000000000000000000000002fef0aaa80f5700000000000000000000000000000000000000000000000587f4bf0b7f2c5edb00000000000000000000000000000000000000000000000e89f1f317a49cf14f00000000000000000000000000000000000000000000000176869939c2c359c0000000000000000000000000000000000000000000000000000129747f04f15f000000000000000000000000000000000000000000000002d178d9b11de53f8f0000000000000000000000000000000000000000000000009044587fc21f92c1000000000000000000000000000000000000000000000009fdf61347211a781000000000000000000000000000000000000000000000000000008cdfd42e77de1980c43e1a2bdb19cf0131ff4fad06d6f11d7fd3279bd66410f2388c088f70a31e30bb64c3895db6a3301e9be27e79c07a3027013b0e68eb9f15025acf14e1972ecf23db1d3f80e4db8d3a8ce5e61a085775421a4ef5a8e770f626dc0fce3db71d0f0ff62f59bef95993de4ff0fc655fba80d47d6dd49ac6a99222e6b74f833800c35d744198e39965e71b95e55f6b34b670494a4279fb653f46cdce9e7d9e910a06fdd4d860032388619541f32f14d021660e8fa5497b849a703d63a58e930a195f63db46d4043c35c1fc90f21d2251f27e90874b191252eea17c8946d69d060959a597a665f3f225822e7cd537ed3be57beb9ea1ca04fc1baf6135541631ab1500bf70062fecf66545666e5f092e35c6655aa6eaf8d8bf20727575f7fc6c2c26c51b65b1a2161c60550ff91579866608e5f84a22bd9d525db9c3c0910d5a9d257d8b9fb3278bf7e3983384d4569ef101962e973fdc50af677e61249f7659180a6357f6d55ff9c3d0c3ded3bbcb37b1c49d08a76bba3d8ade40e097310dc2d2162b769f4d49f4a18af470a46bbe04ee29c054b4d644f034bed1b330b7f8624916ab19c29d424d52039e2f724aece13e882b30e012c588d515212a73f8539ef1167250177895980aea7471392e3ba028ec0715fae1cf2539f351dcf30b5d48790b4c6789f819bad95e55d1789e239dbd28d7cdcc56ad0bc1359ba6868a569d5d0bee03d62aec658dfd96444f35a5254fb150e5273acc5c643f0c6232b71c27d7109e197faa332c23d8b7c3f590a4a9972c5dc7f072121d62db7e63389c59c03726436b338944ea9825f719c9763ef1c8633e4ba2ee18556f162d735ae3c56d9e09e8ac442f6d947881166eef26a7b6fa538cbd84f3eebe8dbb6853e7399ec4520677fd7801b63bd7fa27430251fa14ea07fc97371a20021362a971be8ff709462c2e2be304a8baf3c6f57cecfb7e2dcb804656a16b3533262110bfd6bb8983cb16f23b3025152a5a67b3ba9d5f073fde73e67ff05ee11a29924d74412cf1555709498cba2023bf4d40d0fdbc85670c51082e006b173761c4183d75815fb12cda2b696fad8923295ae21e8958d220435e0a1b7bed0da50cee047f64c599848aab17beebb6cc9e802e65596603823a70658891f4d5da2d768e5acba89a796f3b8b3034f1a2c01add7ccb7fed20cc7068b76c2738cdc9f4c0cb7b752dba7393173a28f16aafb2f8423dc1c661fe8bfb5339cc45482f1df073b995b7e97f2a6c2a5824adc1dc1cfcdfeb2a64daef86e37da39bc9c8197716f12be4903f3228fbaab02bd7959a5ef295739062e15917703d8fd32f6a2a7c7667561282eb80fdb3fb6b2b123ef2586fd658281d49dca1b320e2b10851631c9b11f42550bf7a0bf737c3005918962545897986fb73a7cfacd6960fdef24f2a08d0d9909425f1b401e0572f6c2d7f7f1b16621e22948e190ffada5611905e724e14031cf80a38fd2431500edd1c857e0d04bfe118ba0e6620899d7e77a2d87d2a74adcca704a1b2c0186526acfe031abc4da8b5acfa7d50b2c69790ebf6c47f70cc3f43c6984704592a4e28316ef07835a8b598db038d52417ec5f81bf7f1b3697f5f1bc45e95a34112300f0b77c2213b2a827c30939cfd9282d84dcaa386c25bee8d7da7ca98b02968b31e1c714facf0d484cf017398d694cc979513571261b7df8c6b2c32a28820004f27df14770753cc82ad82fc23820185c486444867a8080bb57075d9c3cf32e23d1d643fda915e0ad53332c302f5e2804bdc2581874b0104eedc116923256237f41adb04fe2fb7c053b4716f2bbfada9b87cc1b4c22af9b77248e0a26bb622c3521f0e4244c66a7af714d4f840273335d1aa8d2e3159cddfd166e3d567c9268b472dc2d46705f03557130da85c7d69b53de20961e3850b6e8fb8591beaa86d0fd10b765c64e3a18d3a3171538b4a236aa5a0598d8e0678617a8cb90c62c8f941010680b196b8fefd31a1948ffda6faa57093c0e5bdb15ab793644f278ceb0ff43f12090bdafbc4674113de9a445ce1aaa725587a8e3d5aaa1ba49ff4930ec7447e0dbc9bfbdceafd180cfa38214128df7f02b436865f4ab2e5dc449557d5386c301affca4aeac69ec4f8167d8db4ad57d0e4ea4b4e60ac2f62187edfef5efbc7b2009d2829cf84a14526960e56b022d48ede9b144f6440242904da9cacb7f8db180bbed0140d8a53ef6cebbf37bb856bf73bd8fa5317e59044f72aa111b65483550994ca20ca1b0b1236fc724c0863806e5f7382ad322d66b21a8e1756f01ad84504a8c28f257e89a6369320f29a2369a69ea93a8a5afc6781483cbf31c33091a400513cca8308b55905d0cbdb38a453c8db8833cdba8c6ae2ff057fcd60e43a561ddfc683a046f7584dc4e2861eb9fcce302bf211e9693589c6e77a11cccedefb2dd16cf71ac62163775ddb1c3832a7b7ec669461968961ceccb928d387ee35e10a040c3f1cb63e84e1d359e36fe76052c8c44d4c7448e50cdd148dccfd5868a009d8b8e6fb0ca9f7e41ff668cde4ebe82df366177918e950849d8107e9a338c822b015efb43ba64949f8bcb267a544cd2e2485d66cc1525ee4e54f0d3dd0b3221e77b6ca1ad4a95d905c06145fd1a9d2821708e1ae3000e868b4f4707f38af1404d399bb89c3f4d4c4a5730271048b5d96072f147d106ac16cc13f20367e67b8142a20c858c21ee4e5e081ea05813323e4b76020518dd83c4e3eb040aac1ba862a4870e2f07eb53071ef39daa2cd981381a6adc2fe595944b16b8f3f487a870f2dfb23a1114ebbd6a5de16cc1f0fb09af8d174afba1902c520a90117ecb45afc04f60d3d3105d013af7696668a02aa56e31ac9cc3670265bb8a0d2103cfc3d072cc2af914aa524cdbf165a4f3190afc28b5810774e6c6b77669f24c3b35fc3f4119e0006c49411d8ae9918d6c3ff4207bea8185e836c24ba66c4f6c6caea6d341386396dde428fe86014248ef438f5ab688a18dbb82b516e360a706f83e414f119573fe6cc28a0d6d52ec6c133bdfcecfd4e547d43816e6fa064b6d7e93f532825ab2a81e8db45f8efb64ae9972a7cca6a163df9f2033de5a2b79240304f782d2dd93fa04491da05424e25dc8c6cbefcbffae80b6c8e792c0e5f43edc39dd7df23f9d0f14541bafae611a59f5c8aa26e03380b723934162c19a459608d98b347299086598038e2a91c73b7e0782ce39331f6d8512fbe07a7fc7b08c9b98f35082cbe354c1fb739964ac30f61d4b1f71d08a8b6a196574934238a87028427dfec27fc4436566de283beb42dbdc55e87188162e72844fcba35e452d654f06d80840a0f19ab6ecb4bab013f2e50d3069b47664158b0c63fe98f22dda6ce21cc87ad039f328fbc56d4394b0c8d0c42d6acd596912931ed363a6054458ae54789198e2d3bbc4b536f974d0b8bd865e9909e2e5bd297d45b64da2dfd9ae5c9a3a687c208e2a4ff1134488d967ddbc64b3107a9b23e7b7ca83402be13743a67d66c9a5e18418af5a327d3d8ddcd5e15421465c4b889bfa0e31c1126a743aa2f515d3c8b2a2f5598ad35c7bde623ae2bd70be128018bab38332186f3c8ea5b0ed790046421e604996d3d77c0257ce4598a66ffa6c39c755cc330dd0f827298ff230a11502223b2d5017042fb406f1cd386e642521d9f761c3c6d99b16cd03b8253982d331303a9d35dd122d9f49cc010e33c00eb51cf199abdcaf56f8648edea566a574d2e69611d85210c46ad4702245353f3616f899c74847da4456bd3fc09b84d396e234d15ca8b10caf34411fa4d9b16c44c0ae469bdeef548b7ad3a26c772810cf225a4af0997e2f4c9145a0b0e91ab7a1cc519dd271a6c3b58f2bc45c24a790bcb0bebca54fbef6125eb192cb92b1fa3c6bcb0a61c73d1efbd57d138b402953e5100ae1c1e7d107647636bee7a9309e1b1eb66e1cc1faf8ab5e8f77ddc06121a3f0244f4f4304b06453fdc75fa41db3e1b15bc1b04256ae0896b00f3268f67ff511f363f7632cadbbbac7997eb2722ddfc9d59e21c8d842b5251c4d3d76aed8fc50042272b8de59329dff728a309ff3d13bccc8913f2a664e2ba6fd8bdc0d0cbf42d453ece719b816784dc9aebe3e3e9053b6c22d14aabbec84bd9fd670e1ccbe7055be7145f3995da1a2187f9f16cbcd1b3d757624bedbf93346352a131c1840f187b4964ab692aad4d6107b75b2d22cdf9efeb5536c24a0ce84b4a57d30b42262d404e0f3178cbc291d51a96ba7ee2f7e14025059d8ff407aac7d03758f4c4260114dda8a818c704dfd406e98d804128cab1c04f35bb681799ee4a30b656ec202fb62bccc933a1416ad7b85aad49eb3180167ca52842b89ba82ac83b8d6554950ac7cf180f14c8bf1fb3b4362be359524d47bfac31b78affb5dc5c6d3d3026ef181ea0ec299ebdf35bf046ad6e9c45b6f18a4010de317e26295723ba5743fd700d3ad9a07c060be8eaf1c903412063aece07c05331fd2dfa95ef1060d2f601fd08bf69a7b0a6f2a4527320ca4f4969abce4099492c007166abe37419d20826cc1f0a166adb3e36c8f5a8e072a4aff8ca9bec1181beaa2b87e9a0f70f1c18c2db10c751f37041d1362319dcfb04fc5793083903e75cb4b510b777e81fc4ae7e3d27d853dc7a2f500f08e29ec406bd29da17b3c2477fc65aa413c13ae19d66f9b120edca4a240d2c8848d9fe4e33f695538043af26e4f6f3f19fed0f13f01ec1f103eb9cd40eafecb1d2fa1baab9e43ae7d150426827416f64a93865dc1f97b0610eb497f1d8364fdf8cb9a23478d77d66aa4a120752fa843f0c7dd4b37ed3d3d8117864f2c913d928bee9f700024ce3e597abd468289f4ba5acf754817f751a831e80aa16566b4f413037a9b7fd9fdd21400a23091807b764e3010226586b9ab929c21f256d595a95444a4e09cc4e7a5ff59d047ea54260df1d4a7a603a3a18a108984ca89e76f6ffc7369eb3215e572e865e2c05ca1c108dd3e592371a7f7bb626a0dc75a6b3d61c907c9d3f35895b219ba5d50f573e739942737cacfcaef3f70fea9682b9104dae31342cfafc5832621000bc409403687fef64c904c7d4e9a3249c19a2b07c46eb1d0667837580a380a5bf8d4501792b51fdbd8e215e44e307251e8b962cc1c1cbe05e792db1e0d4870bceacd545709283af3a9b5c3e917c690e4cff620d02eda82401c1349026a6a9809c49f899d86499d1c7900523454b7623569eeca508db51e54938b1ff954bd5abfd4b2ab5c7f67e7a11adb60119a07f0bfdb09da284b1450ba5b230fff60c7f4be779eb8d3cde7c0580fb5205f4802e0b8d987dfa249b777f42cd3d213f5945759a31c79211426f0bae9a138c9b90892c8f6912ac582724062ee8b6192acb157553f51f6317fd6ebb50af0e51419e6f25c2fbeb3e2814a21016c8489f37fe016d82b563409565692bedab6449f7095c24e77e51ee36c1d4daf80c9ef363a2f9b3673a0b3da7ae46993667adfabb47d80d6d7a3d15e84d0c42cffbdf6a2e75dc2815f6d5e0a1f64aa7d3dd22a838b11f1f6293522395fc29b60109bbadcf3de1d3a461d75131580ed56675b7648064e12626c930ca131799f18d1a050b8f2bf5e84366b8ae90d4dc77f15e98a84a3a290b13dde5201fa4ba401b2959be9d60793da5cafc8c11cf3e891aea323b5bf15d0e24a049c82597ac0781bab9dd41edfb58268a690625911be4f01c7cb94d934c1b25176d969dfe9b34d6fed67f9ae47d57d833d9af528dbaaf91285ddc45eaf9122dbe16539cf24a0c3dacc44ec98f8d88ab7adcb74bfee1a543c88f4e2e62d022541213eaeef891e4debbe5ea26f90f3474e55099d4b7f5ee86eef7772989fe1d1c25d9420a2e0d332e755af7b835a05e47a493edbd291f92126cf8f1855f5c1a244c568838828c4d494aaf5b88e13acbd8c3d7c4be49b0b3fd8b7ab4c9fb630e8afa9e4bf719bf5632d8910b7fb79b4cbba77f5810c1e6fe7401b5b41b91c61f9a5940657a8bcbafcf34afc0e9a434569a4f1919307a325dd86c8c10e1aa2a133fd94a58aac7c3be622a6f994261eac893484f9aea9826feaf71c375f073520cc58a69a35ceb6dbb2c1c65df95947118cd406728a12eb3854ee662074247b417b5a11b5daf6791ed17986906542a20f94515f08f218a8ab502e1b11e0aac770081d293b47933c806076012139832a8e64423c19a1074e9e0de79531196f2ea0bfcc8bc150670f2be8274e8ae922f2bbfad37eebc9077f98aa4ee75a28f4d7905a64017a72c409e12e9cf6ada08ac13b2dba318d32bd083c672a6c614d0bf9822bf7306835cc4c6de854e0d688c9f9cbb2e1232afd03be44541fa60eaac5c8a1f63971da1ccfa9936179c5019c8ff81a58b59edf5999d04508ed13c89645c6613ab155d18e2619002fd0a72004b09d224cb4148f271591cb2a4426ed904fb2d2212ad06ad516d91c6bf5327f0eb369bce8c8b21e7565d02cea64b2d7f3c34c91a5a0c816b61884489c8b3276e5561350da01cb1a119a6731003f2b17e86c1b30bdc6dc3b26d9151f5431e081c634ea1a9b614d7ae882a25d795af76b912eb841c150e88557ca4b44af991b82f7de755d4194ffd7451cdce97a08e72519e2bda060743c8fdba876bba64d622d3a327b26d7b2afa91a01a50d1d01d185360b8ae0170c51968c9f474009653bd419140cad4617e717a1bfa931648040b94e327da062e27394520550f3306ef2e382620771b6f8fbeee6cd62843a3250439de314019c4b41cc44de9007f07a2e5080ab05ac265f887744c70327792f6049a1fcccb27da87d4ab427b9b4c85af0927600f93fdc236d2289da94542315d39b7bdb768060b03b1d14a9cdc60b1557c6fd8b6bac4542231bca105e635f3281e7c62cc2b1f48c7159380fd0d6216d9554aba836c201b849f2a605040e9121314be4b866d1a2fae8c523bbdff12f569e21fce88ffa46cb9ced93c840921992dea2efd19460757f5c2f64cd24c7715a82de62612724ae70c8ca5d3cc85b63cc9c7fc299ce7233846d0922dcc43f9add1a83bbef63484e773b158ee55a5d30f4578266b1dfa2373becaaff6cf6b7170b4c2085fc791eff1de8917f17c9431bf4cc8ee95f61713c630086899abd3d7f20e0e8268815fd7983d5f3537e1c3c468c2c230cd39f0139087845c94cb7b81f812f650f9380f4cb21477b3a38b803134e9a22c24f67d29dfa788f5121f5f0d9eb5525984578f241189170113154c11b2254cab4bc5341007ddf7059c3331775cfb74f91f6191831e9cb87307dd294081121ffada8aaa073cc372846273c03b391c56748ddf263f87d4a60fc4b995ae8cb2b20221305b1aa0cc91d7da4aab51b807dde0675cc29c484c3c02c3456307b7eeb667a831d523c695ef210c609ba4cba27c99da6e51dd302b8fdae82d6d9f6a9aa396a6c3e30795648859df738869d97752561a1d88841995749366464fdfcbd661586ceadb144bb1217b3c7c2f44ecbe837472b2664dc8332fef90f9510cc2487d88b5927c2433456419d301db11a097d3a25997a3d2eeefbef2a7a8db4f2c7245b8485e932c50919631c4ce6b5ce255fb3a5cc858c3e3544c506117a619f707bc19c3efba1d95b287af15f587081deebabcdfb4c4f908b3b8c8693691b096df4f6dc52a41305543367023d60fd114cc4bf0affd9b5d9ffa07ae31d8c7f25cc412930046350c203cf0ae151b4ca11593001a15458bbce8485d4482fce3d2cc3403c04c77aa2f20209eaf549ebd9cc1a981171ababf3ec2ba1ed8e65ca6551c7788d52d515a0b089e0b1524de80ab0fd091d1214e4d5bdba3565a7b0efcc62d8e0fa9f404dd21f4dec3ff782e6ab2b5ae3ee011ee9d11a0d1a78b1e210366fd5b3f8edc769c2db8fe731d318984acb0ac6f4623ee3da9efdc6f9ff6ff3ba48567db397df4ea289db1c6510d93632c53679ff55101b789440208421573fd0c444df98007f3492ae39f895998fbf498f649ed257cda5f907d6ed197e2fff53a5419db41ac81a50d483775cecb08ae4c0bf98c73fced128605c54cdc366204ea89b06d2334a7ac22c92bd95e7c976ce697aa0c4f2dd1c14f78a292df8a922e8fef5e70f3a71e6e12a3ecba8a005ff5b5f0907cde6addd618f87dc3af2282c7fd224c4b81489e5c21e04a303f14fd115821b8d2c9c0c87ddef37c2f8b75a38706ace5b753a14cd404d0070d8090b5b1a0c558531a7626b40b0793f8ba8222bf81c038a3873d56881446ccea637d62a1649fb1ef247d0a342eb4a80b80e50d9b3bde954cb36da862292fc562e6ea29b4e4540670f6091f8e208f3654dc8775fe7cb2d0396c9098912f3841a820514ab2888d1336989d54d731f343bc117e3e5ea9c44273500bdf4c1ade9f6df986799bdea8d7f971c6914e70d406ca623d827a38c06e8b3824e9792853e50587a4b6d3dd71d34debc7e16ca04ca6b341b156b7393f409ded9f6f2c15cdf3390434653ccc1001e4122760cedec3a26aef2447d1ea5ee1af1035e28e1c6bdfeb3b32736479f04b19a4a1145dc0c458661d5b7d1318bb173d15385635180271dc91dd237698a29c921bd0894c593b7baa8ae6a7acd3611b5d0f4412852d476bb32f265ad24625d3e0682a8287d30a28e80d927243ebd4d31f233fc390176f79482c591fb0dc511b571e2c5f3542d52f32262ce0dd0226d4a0289de5c40550e19c3a7ae98cb29bc08cf3b6839097693797d5ca83fe66d684f44f9e9044232f95842896713cf8d85fadad0cd8292644b1a544e5a1313668f27af4f17cd600b28b913f5decc071740c52d7743edc53cda78277d9aff4e5ee8155c0854b4f135d1f7ac80785ddf791093fd7f722f19b2adcfd954f7aa838f2fe64c22214ce19c24064dc86e1c55333c3c32439bc8e8a71e8124af4078a7e9e50762bd0f4331327baf15995e2559ba95f44067b3e51a2038379b66ca898763030856451c29b2ea529401bcaf41b1b223ef9984d1c50793dfbb0744b871ac7385b1bed63140508a63c8806d470c88868cd900442157edcab4575b1a4a4841dc59e05610a17881163a53193b4f46038ec16d3a2acbfb22a828e1a66624123169cb5a837c2b2011ad3085e87c50e9c2090861347696909b69c5e58b7815ddcd0643ba8878b1e8f036cee3022f1da9114688c17a2ebf421eaafd7361287c4c521181d54cba83b3817308c53918ee0943f330dbbc80cf4cc32b9ba6b6a4c062a8f43b4376f1d14b52559c7ad92155bd042d3c5eebfb3507f296f6d43c94c22498614015eff3695f70e0ff18ee349bdf38f952f695bf514c1df01bf656937c556ac772dd985c440922409ee81e639296fdc8c12260c32447264782affadcc5bf4a79dd22811caab34269b381426ce376540fc588cf3619c022e7eadc42b5922677c22c2ff713ca3cf107db447a08cf0613c5bc706a050c83c06471958fa6469a56d77e3f45e8d3c331a535823cee65ac7c8c4afdb19949f24181d7c7f21e1aa4799980622831538c8151cc5566f52a7b13833b80415eac513ec1c379d80deb19bd856e201a18dfebc2eee4f168918883e7340b94437016cfa000c73edfdcb415138dd4f2e7d1f1b5500564a9eac0cf699202924402233d3cf17fb0cc71c2d36a5756e6c9a66a4d0e82e54d53a42d0f6c6e68b4fc6201138af40afdafdb86dd19c3743f5c1bd756b3a0129e5d2912421adc8599fd2153517102b90b3e61e6057536869bc28db874754062177ae022f1f1b0d740d39449cd7180f6e9e5a1ae0f570ab236b4296d4306c1a3fd3f59866db6bb3853c890f8993b31fadd42ad1c44c9c054f18a41db4facf2a7a8a5a2fc146bd14040dcd9dee96424fcff3a3d942093bb298b8a23f9721ee12abfb3f889e0bf1fdb61379cf4b45319e071b6e2a6d6c37be28099addceef0a0b65e63b0f17706ad47fb2cc73a40de4dfe018a4e3fa93c7b873a2456c35610a1272f488091e34387fd1db5b9e5b3fa673dd169652af9eafd7070bd4e353ce961b0b0f58259760aba424f2216f090ca085bfcce32299bce70cf9b1c4167f08ec2842f0022eeb47eccd16484a32e08299e9f7f6688c3d21d74780c284d98af5d22077f2b2d8792de1960e21ed61e1af6df5948ffb2f92d82cd8d00efae054c6730f75d06e2287eba7416dcefe8bddc2fc68b222de614f5642a8a1787b4bf652411071c6e00b058da7aedceb2923578667f49be6451e2f68336c6a63a3f1c8dbbf2126794ee71fd02d449d1b0febcdfd42713447a8023cd91866df9eb83cb0c84b2ea1de622cb3cf144613d6f7a002d159653c9f96d02de3d21bc23192c9f78f911f2c41152c4066d401b95984d314f961f38b6743f440a3f04550d4d4e150b7110d1b91be8944aa03a26fa598db4a5a0f69e75b533a2207c46bc39c351c9ba7f223fda2324aea7101eafad809321f8415dbab2aff593cb49291b7e8945641d8f425cb352ec3a15786cdc3597cd032670839a4c551ab6574edb82c6e76dcba9333268c043a2982fa0d4317237502c90a1200ee11e53feb3637ec36f551f122e1e50083ae2b39be1cd219b0d78c6958e6b5cfd88996fd748a14e99a6d2fc2962bc114690032e6d81fa9d2ac6aed38b94d92af501ff32c9b3ab5aee0b0e76d84d5cd235088e4efd4d536a88ce0b512f98777f9b0198e44e865876669cd498d3a55ab0dd10534d18582c409a543c9c9f87a00a79e62382382765eba9bef2b1c5e91be0031fd9f3b1fb2d76b661d1d0f7b9a0cd08ad31881809c625913a6eb18fe23f50f119e9782aecc36fee9ac6f56a3b3fe0c5240297a01afc7cb0f0675af297a790755a4ccbcd4f1866e1f561b5f03e8a9448e57528caf7148b9725cac6831ca982740bc1864a832bf1c7df354412f5e31780a951386ff364ea1f01ce880fc974c176e1209efff827a6e99ff93a5626451cbf51c4bc89b2f14e5aa376ad61e9fc22f7fd42b0f4378cd0326b21031805568ac8fd7d64ccf4e872e7cdff021f48f7d162205911801d4a6ac2cd45882dc7e02c465034d976c358a781079831f87a889242de0921ca6e9ec53f785ec9b695b3cd0bb080a6109a2d5158e9d9573bf42ab2c6f3fb93baeb4cb45d84d16ff83ebcf879cacef6a2c47f6eddd47f08cb9649b0b1ab3c4334de9a157252c42ed94d64ce65ab4666fdd06e256489ccef93a9fe5041380b0aba4610c03a7cad4f8d205c8c7477cf65e79edf520e85f0752dc4f9427bc42b9359c1a8498f935dc47fbc843d4fff6eaa3d0eb3a361edfe1e31a10502f073561801dae02a117682fcbd044310886caf0f756bee1388d1c260d1e0dd320cc47a959354d31a31e6f8aa49c74a388f2e6515dfb55b712cfc9e54397d24918b47a2576194194a6efcdc203bb3e67524336a62e288fb251ba07d348954ef92fea075d7e50785d56376bcff1157cc85e2670076c58a9d2ce39cb1be88abbc00413d4ccd164c11e57b2f8c0e122cebd75ad03376dc7da3355b0ccb87dc125a31c3176bfdd507b2fe2eab4a020b0bb85a2e75a40b46f7e25b4b7df5dd0a92ccd01bb041eb5abfce247954c34fa5a4b5cbec62d0cd0860db0f1a6cbf9d5f106e202c3c76759bd8ed2d8b45dee6b1470428729ea03d0dac9a65e9da2048c7f99ce04c3329559fa0290eac1a9a42e1bae853424cfd3fada637d50067aa58d1d4d943015b03c2309ccaca5a209459c4bf2c301e4274b1e3045453a918d8a2e214db618dff718f0e8287927480d5a3ae118be697a50cff71b45211e65eacf0357d8d8084a5a7663313d67403bd7ecb9140c76fbaf2387e480f89302b47bcc31d1e6d7005b0b9e5c5d626e9a6619c4ce5587637b45c44a486f7c62e075adc727eb01cb2213bcb92edf2e81be9f2a49ad512fea35fd981727f3140cc114f24f28e9ce950f6e1d41030ca825b20c02bafa07da5283d31726260659ea0575a470e9f3862612594ae7da751efe342fbf919a5ac503522de63fd4cdb644d7560d435f237f7c22eabffdcda551aacaa33bfbfd7b264b0de538421e87c5b6112d4c10fa837a670872ab3fd883ca6be74ec4a41888ea76efccbda23a48f3876c20007bb5e8fd6608bcf1f22036ccf763f19291926e1ae21061c1381f42bb82af11bc3bb8f34f1d25c566966289b286af824e626be44fa609fb9f8e5c280b11ab25da9eae47547d1134ba4a20c37fa9f9c9119b3f0946765dc30ae229db3828469d37c4af2a3d43255889f4da75691e9009aed63ec762f684754cc16909e6f55c5269ff4ed7626917c8a264883124b54e4c661f42400179867e83256dd6c8b5c70a6eecf33f69fd2e3c04920a72f5f2249b63e1e08881e8273da02e502582e13dbe9d0b5d79b1b226a8698609fc8716a362b03401118b9ffd1a8d4151e25d3aab6864751a3f82db0affa91866f5748099a7a04ecf6c582401622c3aee7b56e9926ed231141c26f1034408199b216c95816741706ac3a241e10725fb30888123fbcd7609ae4cc233041e412d88d21b96c2c899f9d3464d3be3ada75bfa0a939522cb1222f6fd2d201250460ce0f1a7fb9074a0f6fa3b172e262e1724003855416d8485c2d780f07f1195cae1b14cd3e8b70a23cf4667c9913433b4f2ca4488685c0d83f58f24902023ce9091da8bd491e977134e6d24ba3e0d62afba9b9f0c9596ce5e207a75f02e05a488621b6b1b2f82eab998582f6da1b507d69d16de20539f5ea50ca1e89bf3043c3d2bb158ea3be35187e000b92298549448cd4d5b896af4c10ff7c292a9b6234888e4059dd68e03f9d2f6ba50284c55a63594c727efccb615a7dc0336d3641caec9c366f569ff0f2cecb46452264a800c5963aafcc862f4ef840e4839dc1c25c64913bb18e4943f1bca953e7bc4ca7238ffb61f87ccb9b4f411cca65172d82a9b37ff2faf9238ab9a8cd548f01a5c06a2f0c852b173ed8bb2f16e4e49e7491a0841bc464caab75ddb9afac16a3c4c9979a16067072e41ec38ac45384fc7db175ce8432e258234b709460386d667b25e97f32010e41cec2cab1c959c68d23b09d014d1421ec2b3fecd14fa25e9c288f5e508fa5606c89a8678bf87d7b7c938002486328b9b29b627d0f6ebf8a77cb2e8a8038986c07da3a8b718c3fb80602705be98a904bb2f54859cc16434cde0b7e8a6de36b5ec8a6ea22f10f227be5b060f1dcb994fad2f44350307e50d5481ce1a3cde7147806383acb56cf1cb59bd25244acc1d62e14a82954b17b519a1cb03b5d4103beb62feb96e13350c9c7fa5bb07bd3a2c8ea44417d8a935a9e6099406c7b36215c35285a0614326bf40fccab901a9a0fcb2319ffbade7f8d15ced7f3f51f979809c892ac9f6bfd4c9a2a1a5aa04a128248d3cb749415d418a4384da9bebaeca135d0f28ba169e4947a16ca1f52b82b51b5263c99d50552714a9a2fea1a5d0bd680d2fe441f8abe121960053680300e3f56615a750fc1d8e8bbdc1a74feb42de4f686920a55b33c9d37364c1261f9637c6e7ca508cd210914189000700c7299658e9f007bacaa37980f64bd6cb249cc42f44e8f86a0afca5ea0f982453b72208aa9fbb69df5b2d91e1cbcefebc21027e3be6e9bafc8efa9000169e7fdc38fe27e534e786e5b363fd2fb3c706ca24bb1c43e8e4d331239503d6c0820a75a673526b2b4c8e33d810f0cd432caeac2961cae418d33d3f5fc24a83452e897af28d3d2cb3e94d177fd1eccfd030ebc1186974863f6fbeed1d5b16ff8eb4fff18629f25349caeda0f588bee9bf6342c42f5b264f5b6c882fa61c8e9d80c4fd68019c6220c9c04a4fb50fda44c1294f0417e5c37beb768112252becc0cb87f4f7b3668e7acf882e119b83517186944b6402d290f7c84aca9901c8c2e39bf76cdc6e6dc58affb9a31cc2eda3b04ffb86700a2cffaa7330da0604b73af6be18bb8c028958709e814773086372e6f300e1a31df307488514ba7a83b1776aadac1d20c306b12776e8050edc6d21e3cd6e23910a90de67c3d41d621aa294394bfad19c0cade227c268ab93bd9c69fea9d554c4253a34614ea3eb7565f89a7b76c6294edc4359819409b436da27f11206bd097b26c2e4d4e0b551afde56514b47c71407c1b1faf7470958c3cd29ddc98d4d57782f6fafc9c9b673e30ba8387fc5d09b2d6af4fb50260aad9cd5efbc5232f9c79b30612b7f03f037f16a8a5b587aa1556ceacd8c1e3cb7a936a7dc132341ecb5d0067578f8637ba3fdb938deedfe74d89f87891021566578a7f2c238794d73d0571a77b344f2989ebe251e7e1ff9029bd686e1608af6d7c5b275671f67d2083830134ab23a331c5f5d8208748b39f75da694aa518406fcb20273227668c7e598121a8f82dfb5d84f876fba9a258d1a1817968395ee75da396f59abbd3bc8d3143a11a434282eeac18c25ed67af3b2733e69375ecc8b0e604e168f2f6bdf41a4c9f0ba4b7b622754e332144a32a7678575beeffc8cd293964509b5d9bc054a0792e2d967ff97e72a34fcde286456d9a94e248963c6fd50338647d8996d23f27298118e03f44699f28d7ff31f31c5cc651b47df9ec3b9b869ac0bc4f66bef4e5b14c0e186b0227bba2e618527749c1c041b9e4b5c39abb3a77e9328fcbc0cb025d4225f3bb62ce3504f467d07fc5ece3b1ba8a5ad7682a503bc7a6502f37f4f0fe13106355c8bda71d12797aaca34008c3a006fdd066b64da246671b4d0f37152bd4", - "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c92208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f72b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c22f5ffac9352fe35d950a4ce548798309f5d896dd11f304f93d83cdfe284b37a6" + "proof_hex": "0x0000000000000000000000000000000000000000000000038e146b049f5a2ebc0000000000000000000000000000000000000000000000018fbadc502ad14036000000000000000000000000000000000000000000000008a2cf332394158a790000000000000000000000000000000000000000000000000001f710378be6e900000000000000000000000000000000000000000000000c08a0a49dcc1784f700000000000000000000000000000000000000000000000479a1f04a4849464900000000000000000000000000000000000000000000000ecdd4f70261b68b8e0000000000000000000000000000000000000000000000000002bdc67e780e270000000000000000000000000000000000000000000000068c088e96890e815e0000000000000000000000000000000000000000000000019927ec72a8a0dea300000000000000000000000000000000000000000000000bc30ab5296a7e92200000000000000000000000000000000000000000000000000001b1768dcaf05300000000000000000000000000000000000000000000000514324a6e0f591c0a000000000000000000000000000000000000000000000002e31090bcd0a36cca00000000000000000000000000000000000000000000000eee316dba6c9c144300000000000000000000000000000000000000000000000000000fea047711602896aebdb264dac06725b3966345b9e4ef9019cf6b810f3dcc51860c5764920f2014eca6c2f04e7cf3b91e2628946b6a3718640954ab71d723bde7bf502a9d1a2f34db5ecb0806f11af806f3bb030c60338ec88b64ffe73e839f5c5870542b041441320f7b67e34ad210322d2f5fb516ac74a4f27d68106a4b6bb8862f403fea0fde735d225bf57a92f107fcaf3280cbae16c9c7029987ff0a7f4ee7a20274e10bff5a51024341ee9a5663df40a9283f6b04fc17bae3b7bb5a1581785ff55d700c1e06cfb9c565754142bcaa7b5b4cbdfe8d1a68400b427ad5a3859a91572c9a07739d0572ceabbe0d52bd4b99c0f620ca8a5321b1b804a8a139818f04a251b12a1ddf209a0d7b853ec134beb579539c5f1c5c12272c86207ade7f36723a0c1808564b49f500341dc967a931ef95cdf14fe2d6e213f59a4c08241a0a630c0a20094d9edbb54cc50d8085bd1ae3f24c32b350465caa4ee2c1f3f0e98d99ef1f280d566dbffac05a37803f1edd0b55a4915e64dd5cc8040dfa02d698b48ed45c2b0512682837918095625fc2f6808d921af9f7809b5769a1d571d5ae35c1ecedd22eb10c42bf1697e343247de4678b4739d8468c15ff49d0789daad865c2a4dfd520a710c69acd71d2e39b1b66bca361a77383686b095267d483e9a65a7106fbc129b9018e43e5304f0560ef448d298fd8c68d0bbfa7f415422f83b3a7c0c1149d100a6294b4a17081217254d550f784ac02a667e1d23540040f976e34a6d2059b0ee597f3b9db445f113f30e5498a99ff0bbc642ec6ec2902fcf42cfc0dc9c8ce290fc0e267a0be0f576ff0af58e4a68a01ccc44047c891eff4f54421844362872cc5f85a0b7a4a2c5683e6927c0129ebf5aabd8df471e02ae39565e11f3ce0831c28465d49309ddf5bce3974f68fefaa445b99e5aec1d308991ef090362bc9611951db034f99dd86a24366d10c24747aa78338a374f51595dec1de2f4e35604f0fda9c6dc2f4a1d965603db25df75368cfdcaf4a4e5c8defa96b67e839008bf02d308407c26a577d97b478ea360252b08fae8d535c3c54649a262d2c9725450e1db0494dbc8db5b571cb6e856c23cbd330154826c02ce5ba29b4bb2720ae713e0e751541f9b404bc211cacc9691d3c0917a149cb51e4ac5ddd944b7d1bc9668d2bc5615a7d472255e69005a613c4f949a3bab81819ae67ec962bbcc2635ab94e1dce72569ae638d012c6ca6cf21634eb535f80177106ee176eea6d1a4c3c3f032e3839c2b0f5c9165b77e0af36691c7451341d88be0a519f7a87b72170406ea908f56f676ef12fdbd5cdd673d77a9fe1106d29f5be7ab396ef83fc3cdbbf70152b69c3d9cf3c2da2f6e32224297dfd0d86e41bcbd3a04343c5c05547f1fa94412933a7e458060e19ce1faf9db3c9ca6e069a30c1c4ee3368ea06f3f29022bfdb118c7245fc64000f57ffdb78ef63bc87ee99108ec5ae0f3b79c18eb49a5a87e8174a8fdb7cacc0af74eb2668d45b1d999a92c3ac7234b0c504b22981c0f44fce2d45cd8817e21eea6a471a53ceb7aad4e2f20efe82889e70284075c63df31f1802f49619073b093b611c5d89f0b69bbe6510cd5c0098862491d94c229b9fefeb301a1359c653d0d5a264cc62a8ae6c08b988e588ea96f11355bdf497f26207530a887415ad40f0da37e84607ef267fef2a4392874e3e035db64ed8b4e804a5bf27ca07f83779e8ae2cf14654363fe4f88f25093ed819e433a9510367bea91d9f2db1e26383a4c8a0df489007f12ea00f91d6d00a39eef342cfce4049cb1005331a81e08d651c53177fee573a87fb0652bf0f8b6ae2eabdab0cd53a222a97bb1f2599e409604b872622a24d9f0eda3e56a7cc78ec9780f28a329e531e56b416540277caf2f5367e0c6ab8463bb850496fb67960433bd6fbfdc0f384b3734ce64902f8aaa512adc680bf8b4ee6e8805006c28ac3fe58625d3eff6c55ed1418b2d80af9a08acd5b9cb6bc4dc70c131282f2aaa7b77da31a88736f45781f136f23841e512e5a081aa544a6d5b4b7da8fcfb871abc7c2aeffb7d5c5f36c3a47ef542d0a2f0e77853444ef1bd396138b4692c68bcbc71b49273d6b85c43bd3d636d8171f9b8f9e1a6cce6b155ca1b8941e29f9384fb8675df1b925fe9d049934b57d2e0344065e99856c0f58560c7b61969d8f8a1b22185d7cee49e155dc41eeddaf392055b9fbff12a248c53aa009b31d8d898fa411dc0ae2f8832e8286f7a2502af118c76caca54feb76a5660e2e925f0a2fc3e76a09df485fad25d6454f2078345d247e5986d23f8130dde014cc70ce4d6600102584d37d2ae1ee3339cca8ebaacd1b0bc9c2cfeba8bf0d26baf663766f1a41511b6643f268b8a4b18e72dfc133af07299dd37a8e816a3301dfeb7186b727fcf0d937e36aedf38061ec699e43aa9622ba36764425a1f2d8e63cf07b25d5eea48a81658ceafff1eb99284fd40ba0ea0e8070b0f18b508f8dc8b73ad007a52a97b95997da9c88e51d725fb0322dfcb52b15678857422cd69a0a38d0b739c4640cb3a65bb1387a18b9c597802a7008b307335192168f583e82957952e50c1bb0872a59617318249d8f63aa6db33db7d90fa9601322e78210e75087f0f74d98f6de55c577d149bb5323f10274cc323a571e1a58d0bd353b5cea492d943f2b9ce4a7c1c597290edf32132364b69af83381298d05687cd395bfa6845e299ba59cfc67314a07cc4347892ef618d0f36a3fb62442db09f8bd01600b9d4797b1a1855cdd9c2fcbe56ddcf7aa3c9a6f7808d7d602c5ecf4a2882da2d6b21cb057b030bf46f1c58e6177a97ba2aed6b67bdbf42715a3daede130d745cf8524e663319b106661a34762cedd498bc1f8670ea78a042158e87a0661db99505ee3a6aae62d8ca251044674fde2521bbe3dd236f6c35a2d7f3bed582584f5791362da4c8b52a5c85a7d9d698e0d2ca59ea2fc4aea21c2256ee0cbe2b992e43e977d082c1f7979c49189644d6e4f56a293347de835599703e0ada4a22af6d5fde5b44821ad479e26d73e1af7994d26d04794cbeb8ec61c05d6afa779be840e98bd1c3ed40836c5741c99fc55edb8f7203823f6a50dd44e19c061d28ad101a955c8358078cd5490855af88af9eb62704daeea6382ddc35400d11c2b43f4373b045010f285bd78c8912d74b74f7d294126ac5e72155dcea405e9031605c04becdb06f8b84e244e6baad4146c7dab4572f9d1d4ba038d452018a742401211aea375ff3f2b56acfdb64639a433002aca37cf20cebb3be5c60715ef324c76ea04192181fa5121703321e84713478c02f63a9192f50067ae13421931e93801854687d8e60ddd847100095fcd9bb2ede66d370e647fe4f0d7fc3a2f363c468eef65a3793cd4bfb3e56d79ab68a3b09be67ab2bc3c2da6cf38eb631310185da9dfb841b5467e8183bc21132f88582589c25856117e167d80dd692c076cbda0ce6d6d2d5dcfd89311658456916c62de398c5638e43a843366d817cc0ddc7d7de125fc1c474608004580282688063f5aca6f06690dfc491a8ae9c5c02ecb8837abd0b8af52d74854a8f15b57dad2ce4346c3a57b9f16e8708730a6cb064b6fe9fb0089a65dd35b6aa970b411c78f7b2aa5b9400d87fcc1acacb3e369171c47f3230a9f6a371d420d11eafa30fd4658145178f4dcf5c28c34bb19e1a123b681a4887939e0a351aaf8fe25680434596806ca11ea526051c082247bb87a209e2e9c586721be984a8f864c9d3be80c8c7523c21113e1b18b3b706aa7d8c21d657c054d8769747e011d716dddfbefdc0cc7fa8d471e1c58ca31cf89c1d196218dc8b201131c3a34389ce3fee2ec9ed61ff3959c451e5a0f020c8033efcdfb0b5bcf51236b0b6541029662b8a3c50376f053a85124291f173411ced41748f009c22127832ab9f36659c9c87f116365b90af9e2392f5e92086b801e6fe6376a2ac461be885eea8608ddbfa4db732feb4e936819e6446f9697249b38e1f716292348b3287168eb24314e2425373dd6756868bf66717995f106cd9c871a5a68572cee61ede06b11f37d231a609bea864675dec910763e710754c461b778f906bd095df757103c44f4bdebdbc1409647a6892f67ceadc6bad48589824e199226432fafb8eda60a1202f598713bcfcb94681048b4980b98760455343316b5b35a9b295941a67f7b4ac4f1a24c8b3ed045247feb50d3670a5a918355ad64e5cf7e302775f423dd2acf738d94430a22803d157ac60ae399707c08adcec7d41e03d118052a31d8a16b8a3aef7c9830df6815906f0440531b33a10571f1ed27d84cb75510495df8efcb714197c94f8c9fd53b198efe93334064d84b5c6c22c822c643d02c3a053da66d5c4cc774b5fd04740d89c3839be5157aa9ad2d2169bdecfb020c002199232930c8eb7fd2e59c70ca689f838a282b03d2372e35f3b52fb332d8e7118f60a499489c36626769c99700537dd8f5e80422d92dd0683b5efc3f21690e288089688a1421854d4653247bb20bed1dccd20b56c9dc4daa52cab2a984fa03024b5ad9af64d50703a4de10752bf3a82f835938640c7ab80e0a0190b0a2261d2ab6a42d129566586bf0db21334d4336c2754b368cd5ce3bce6196007c51a2b52cb2a7f29f13b502784ba00d49726cdd8f2315488c2511acb7f64acb5e86a6fd2506d1d03a141d4247fee5336d6210577a55cb80c60607c3ddf33d9e3ca1c46f0d62d50ce69948edfbb234bbf9216a1aff75647b311f859620385c0acaa4193f2e5f309fabc40436ac440f39da8db73698e0a85edafc0836ff33e234321278352394536d28d050f56da00b11fbb40fd5f8a4566c72be8e5ecfacaa7d38c2cf3e0f9e2384d56265c064dc420fdbc6a73f8c0e6e04b644e9cbc61df97d88e9c529145184b011f9eb91fc5096842a3aaec5b6caee0899dbc249e19b2d0a176d35b82cb08a43f16b3b82da140b313618120d6906492df6fe2f189703dcb70023b64c28fe97dc5ee19d42a34cc5e7abadd24ffd49c250e34a4741c8956e6616ec6efe20ae8c503e52ce778350c20b65b6f09a5051a7e95694565ea4bf3cf1f7a93dba00e28eaf5dbcae67ebfdba29b7068b913ea43126ce9a9a39170ce0e05aff43bc00119d5e9e43798de0b003442bbf4b3e07af206186e1a96eb324bb77eb0f8c242b4d00117d41cb40900378b7235ea285c4f1dd4cc09843501eb4e301e1a7fe7606f17485221a20da729dca07ec410db76f9c69f7b723e209e79a605b5fbfdc431f6420d8788a71c1af68e3f2d8a68768e632346dd1cd22d6a4a2c6268c6224810247c6cc0c8cd333a78b736a02f0d029e760f7f68589298a4919b407290a97dd2083d9af9cb142c55efde8b4c69193ac087c976a0dee9ee7b5f2630707a1632209af5ed7b86d5f08d8abcde1dc1ff387ad7a31e9388ca089e2f5f8340d54aeb0232f5ad00ce41cec1f6647b1edd63ccd207531485b06b8874ac25d769e17b22c1c8f6d4b1b5251c23e315c2f0a6b18b5521fe3ffb7fc36c156e935e34a3d918c2ab04f3bbdcb1307886b001258e4ca0efe37f22a80fbc035788ea7704d8681a4277cf318460ff17c8f2790f78a22a05264e33715a085c7429e7181944af22fb329334c5f59fed35528ec577f296de352adb6d95822e85dd1493d81be726aaef9165c3501ed7bc205db385083ad76207c9f586daae7f8c78b4ce4eb3de59338c426c7fb9191c9f897feec6174af661fd70b303f6f50a23266aa7cee73e15d320a0cad09345c02e98c86f16b6b80b94c18edf982583b4f96e047c1fbcc45c4205618b64c24cd8c14c082dc62b338b3c048a4293481ee0bef71dd3cdadd40fbd4f419a02b9f28c205241b18241d5e117306e89dd23a884a3fc54cb8079a924bebbe11deebc2e717ffce26862b8b7804bdd09db9704b0623a397c429054666741d2302640a3ab0551496af654bf68fd032082432e7e8596aefae1ed73ec7546b6fe41f13b641fdaa7396a87890b29dd70425b2c60daf38e4fac39375c7da128620a42cb6b472caa894b66488e4056e20adee4b5051750d58f3c8f9e21e5f69dcb03f0eac9e69b653a0485052211d6495de5aa9b499a123aad61eb126d430172d07e2090d8abf78ae916581c51b2e3abfa8c68740a7dd7f47dc90d8ea573a6460059a273621d0211dc09cf4274902a2c2681eb4d62fbb27ffdc5483473734e87e78621e05869d22c3adc90ced561fa5d5b023d494c41c392a690b001dedbda3dd7fdd20f479ce52973dba498a97f2358bb08cbd67c571c90d6cdc69a1fee0ad59de442bfb9bcdbd21e349693af3522519fb8c32f21a588e1f426b1d95683e4446ef340e9b664df02bf2b8ff0ad4148f50d517ff24acb4b9b33a01c3e9e0d13ebc789c0b4686f8844ffc6f638b7a90dc67bbe0a6b00d0174f3135d48eedab7fddaaf4c165a4460fca15426dde3b0718d64c4697d296912edc702e1f5258f0d26939ff4220fb00ff5792b4efb016789b009d6812f1c81d5a4dd82dbee54d9864a15068121598b4701b74147dce1853d2ce745710ef36f85432d5bee18b5fca0662be8bd0a622e484d95516e6a425a4919a4fc52c8764d106b4bcb6242dea3a8b3cb8b58306313fc37588ec9f6aa94c22ab4151908b675806ad6c9a95df02143ddc913ef054bf6e35f383708a2d5259d0a2336ddbc2a4ef93772e289a8a1710c78898caf0dc63980253765e3ec76174601394171065781698d149637a81dbf295fc6fb80020d85ea2aadf1f6b2d34f425aaf14d4a7b2d32afcd5eafa3013f9f76d12d0e70ca208763a96a78410d65d925326ac2a85f000eb77f77517672807b5b279bbba1914c7955fbdd9ba738021e16940413339f9fc19702af01758a8964a3648eb5f2b38cb8f8170de80e05194bae8de9fc3fc98e7d99456bf7ed4e9280dcd188a2909e5b07d0f283c47d1f7f9f0703a70c3d068d48040b976324abea0d70af38dad20fd871e487c34122fb7fbc2e808ce4923f17beea6b0c78baf481c0e7a7782e6301d886ffd464f271910ff70eae27784767ee6cad1b7c706568b8448f2ae1db00007db90b02058d7c9a26e29392545cc731e629682b34cc76007a0e2c672503c02b62b0a2861aa8b1541eafe096469fd0fc7671db638eac0d7a3c861af4e041b06358dd08a1d1fabf41e2caf56c93619dcaa1c152f84f123b433b5de6f526d652dfdbb3cf6ca3b495bf4e119d3192744aaa057e59b25c4dbcbcb1551531c925a1fed585cbba1e3e4832dbe93ef798ea3104aed3c15ebb35567a1b0fba49c080f1b259db8e480c19b86f74d3f12242ad8ddf276551b5579f889a925699391618b2fc3fb82abb5dabc4b164696a158efa88188d644926aa14601c5e62ef22038b12fe800ae4bbd980802c5321da39fa532fb6546a3996cd3d7c89e3a22e390bba90555e59695339ceeaa217f88e74826783c3a1efa8de6714f2275cd9067e427bd21bf502ff800c2cdef2d00ff1baab89b91b68c2c0541964433c87a099d90ddbe0a8798761cd55fee0f4d5ff34e9a58ba87decf37f3cf420687bf0371171ca8252f6e8b2f4617c0fa96b9c65542f0cb40662789e0f2e00b7f05db17c528cfe216108f9162d4eefd554a028a1db73813040a371fad8c790996a7084a20eaf0e67507873b7fa254aa0638156ce5e8b739179fc965a19dffd739a07b3a1a6a71bfb42c1b02e90a38270855f423d40e83bfa56b31993377625174ab859dd3a66a2c880c1aa42a078a47519c83c3718af991f3475f0951012ec4643a53495feb2f534715a02251c6da883227283e90750f5c68b6d14a712291ad78b16e8663ba0ada3d25809bd3d1c6bf98b92f28bc97921c7b6cf346f57797a2f7d8355f8247f29fcf1e70b2e59b61d4a90e90204e3e843a475f1525a80f27f2c139aa440bee68db3b1b272f57c8719c8d01e533e12e6d01c477823c46343278281d910d26a6c4f3c32f8a20f59a9785a883d74bfd8f621578d57a4b149b01eb7cfb4b6918dcb7b68f139b1ba1b7a052b79be15a40085bf9b91ec9937f78e54d385f2a09a747a7138f2e909b486a842c9943fac57c89900e32d4c0eccd701f3d439fe00aa9bf910d2827178724f0614e8084a6c472f74c0289623b10ba0edf264e88f45dc9d80079cb17ba5e3ece6e6c3c76f57b6b4459f8536cbb511054face7d1636809a52d7904d0bad02e0ef3e5992417d9c6c356646d4ae6f8ef5ca2f8b45d00d22e7f3f43a321d402a81c0794c86c6cbcc0b8f0af9cdf907836ecc4dcc159ac76e7e9b2d4ab90476fefeceb4acff387ae5e1eb6abe96c97be936ff5523f2b2767e3e3b1f0d4128ba97d019c749862c304731f9462a2d22583235f00df1f812f7fe18ba6afd24260768907290435becbac3106b72c5289bda8f55ac47bc0b45a0337cef3f77f51faac7cc16a64e8d42db7fe0de579a842761f013e5c1ac835ec7a7a4d5a1c50e061863ba5f8315e1d829c7c1f0c507a69075f5c5d39c659a49f0e766abbf080711f8623621c44771dc91147349f6208f5578b294a9ebb3fc3ed64da4c19c98982ef5d818424ad862d1f5dbd75d9200c40ae912792a93633ca6fbf0bb199000da10ca86481b98e624f81d5c0a4acb3bc1abc06c5d113683ec1c40e345ec9ac66607bb8da713163665fd11d4c11bc6ea47bf2647ba10234b4f4e7a20c32f8ced0d125d027030726358be036baa138cbef9ad966d57399ed14cce73aa45921d5254099bd00d5f347f292bae143b759c9448fcbcca7ea6cbee944f8a00fc8aeceae222362079175d66ee0c74e798127385d4e7ab0103a050e05e16345d82f4e3f98b1ac557f7a3dc1a6d7fda9a7b5ac1ed91ed23fbaa48afe8376e6b3c52e0c40bd10574bcd3a407a0a700420cf3ff6bf4e9d2c92603b0e3ba2fe76a86601f5b58b32b1d5c029f31a366f0a64a899988644c135db3aabe1188c1937e0e67d43784cb259c6df89d2678b2eda7d8f7c59d3742b405fc7dcbe70eb947e80cdabfe853cd0ab21de8f0cd93b0e975f77dfa85612421e01fb87848c26875a6423b34de9a820d6ca2314c7d276f56fa815f374af9a3c15ade27bfd6c74a94d8df257ec68dc401969ab545a44962b4480d7787827e9220791ccfab4ec5ed310f331ac84d41d620b975acd3d6d7244e0c9bb86cc0a45d5b86e6a8e0a8387020337602fb9664fa1f42a5e9bace6f4a0adf7a680459ac831e9148dd067aec1e6247b703bf3fd541262169439bbbced69403ebe737c5b1bf602ec254585f3533875af458d75f4b6b04904c93b04484c354815a73fa7a2f84c69b33822bd91db96ba88c06b4e7083f11ad2f5ee09720c05014344b0a80575011bfc838f5691b8401628a0bf8f137e10ad62be00adddc2e69fdc00dac0bba8a8dd99116dd8c0475879304f478e4d0b419780617665f19a173d472938bde6fc5443a221a8bb6ae4fce38148cb97e9a3c2faca00d80752a8daf6e02a7af2b014f0ad3d05f16ad4cb3155fc320b777805b1e7a4de59fd26fec3113ec4ff03fe8cace4e8a5c352032020155f6df973172802d4120638812c9ee71880c4e97cb48d2e1cddc2777d093912a2b6ee71de7d54e12e877b827611ca16f81f1dc67a480230e997c5b8437c9b4a99997b227bc034216731efbe60e9f9b3fc4a0f877a973aed8e2a29baf5de5b7a63406987c15e0670464c90090c236014d4b77d3455b4d09048f5123409f32d104cb7f6d59f3a2550f13d808471f826a1338c92e579bd02cc4f12e97833c52547dc31c1c207fb3f7024ca9687a18b57a64626be3d6c34563e9bf612404c60dff70ca16804759818329d8f0fae167bb6649e51caa8831278645f950c16888e24ececc5378976da9c82c894e200a8ef1fd2dc188d41e70e336ea373cf1ef81751147d768bf50c2aad418eb384eb68613754260e9d2bea84c850924d171bbda4c856a78b74f4bf9e5242bebf28c2c3c53437b81528c77b5e14145392417ac24b77b634ea8709293dffc03b4d4d57e67ce15a550d6d3003bc839d1beadcddd0abc5157f06c3542ecf35a030ef12b79dd6dbd1f86bafdef9613f63ad08a03cf7978dbcbcb9ee67aff50230c810be573ac148f794c0650424b0e7992692d96f482481aa5638d16c88e31812f60dcb63c920e69f88d14ff0d18d076f8fd0faa93c4b8c2bbc6f41fa2c0857f2a656284d2218612e128640002e4af05c6be31686edf9974791630d4ae3197b8211e31a04979b215d23a9834258fe87c12d45d77944db3606cdfbabdd010a05c0d3e23bc252d9cd18a0cdf2d9cc94865bfd0a803a4ec27de80a01ffa5dee9b9e0ca42cb9c506e92f339d0f1504199b9e624a71e581ea1e531e48659f75e7ed992c62e94ad931b28ae7495f5fec6b73eb59707c8cdf58149dc4ac231f7b2b770911fe287c9f702d993205d349896c2a4e491b9a6c5f923c6195ef8dd2d064cce0249c1753b3c38b42b2e67c8e75d0eba600108c3c400fbbbc2da699a5fb635a0707212d96fea2841bdb3a184a7bee263731ef6f4184fc903ae392c4012977f4e32b41f65e453c9892c2f685f43636eecf49d451735342f95e842551c9f2817dd104b05865b8c0e3fca15f8c9f236b8ac32287d9b79f1a2b2a978641c76bf86e1a280d8af6b20b402423863ffad8270c35656613c8eaa25d2bddf64ed473508f442a0ddcaf8fb65ff255b991762c99df15a0f3e406b47e367a5290e9a93502cda808f5003de148bdafa621f1056d271f7d5b3d9ed3b3564f7ca2321bcf5e9f728e15e13d7b117abfc92f42a4ab68c260e37f27187224246a90a2040d0e133ceb3b194a4dfe23338883d6cedf803623c9e7e319320fa414208a7e9e083e334e7a102ffaf3053c5ec4dbc1afc1fd0a1e2ec41fb236993d167299e4ac66fb39a6ff7b2dddf7de35183aa28dbd3e7f55085df1e85823b7a2a1415afa97ce1fba342d0c1b76ce8c291da4eab121b9bf1ed337fc3eaa48c0519f81beb378a21cf30de0211a36c491e17075f9d390f74af0c916b4b72a52cca839a8ec77453f5069a11ee606a0a221273d418f367330a250f685df6535c0f011535a661f5e0329507faba7086cb2591cb49bb2d7f9f36cb318bcb71b3a0c36a764de9159da2b359b42bfa40c2be677dbac3cbf3257f23add29381424b2c501161f347275a0eeaabfe55c84297b2d313b2aefea9bea73663caf91539278db07b06b54ade556bc5f863552870016e61cddec04e209f65b23a294dec5f51ada9a71877524071a6ea37e323abf10477b2c9ed66ea8cdd54cea94061109bb65d0c735cc408c908a3f03d4082a2208a7fc84c0a72caf1ffc0e6ed1e76508a1b12281c8c2c9c689539c18df34d9110b40a9a11243c885df0264afe639e088f4eb0b58e37f66646193e462e31328bb219dab1e270ed096487efaaeffea549d1155f6880c13af3e2cb5b0911d2a464a01415ef211740de758492628c66e9dd91e7f4e34764d67f6752df3b1d916f8d2145e0e5f0f89f34315af664917caa889c19aea42b0d33dc42316513ad09688731a9d0847762e6e23049166ec03a3806de98ca8f294bb1d5a048d4edcf28e099f109305e145b3d4eeb96745bbccf43a9539ee56cf4e98272ab0748490a629e802252bbfe3be8fe6b8385583cc012128fe518bcaa30542f39f988b9fb67442b0fb0e7504643c613e4d19af19ac28c4c5836e764648abd2e35ffa5686349d7809c70ce07a4620bc5fd09ebb0ceeab9325bab28cccbbef4dd184df360140a1d4615c202b344fe389b9dc87b319c09e0dbb55a2e43baeb87f1436120c1252986972141ba8ecc47065d5ec5f8cb112daf5090184e5feb2a314be75e75d274315c55af2213ffd564c562bc0cca937ecbdc9f30ff98354721e0dbf5cde8c067ce35cd6851ee308e251e86c55bba859435f09869c38758ea576ece6900fa2b8be2f89abd908ff57ade95004643698340099abbfd7e03c84b0318c007f4f05278d99644dd92a8c2dcc238e60a4dbf7d3d55c9944a71904123e26ffa3fb020ee020881ec78e002a17629d2c4c3964192849fda0e6adb70e34bc84f1467ebb9e22b1c565fe9e01bd5f58733c2da2eba1144d50afec878d11547b4b6c50bafd213a7dfea0081c28601df6765d0e4ee2c62ea450f53ce44526df86f32d1defd826168a72a6b920213190556ae50722f3880e4af3b1edd283ca0350c6a283fdf00ad92bdaa18bf2157c350f6ee718a1a3cde639733fc2349d24c78270fdc21200b686dad3608802168371758ded5ccc436d431141f79d62b38cef19f5a47555da701ed8ae18231825b7c906ce453bb755d3bc1676eec754c0dab65f27c40e59a55398dc0fba641a0892b81895428beefb7ed284886e138b82014911fb433cd864aa73aa714613402189ecfbeeb18f9b16b55c507adb769f2c4c5f835c7648df6b5262fb07fd0a2817fe3da73410af4651d7a63b68e23928fd166e3f267fce54cf78292a56ab39e20fb21d359fa63fb3e1ec851eca417207415d1a31c3d555e7f37ecf615cfc28880368029a43fb583cc9ec4a17a734ae0a8049756ee5ff77ff74f8111ec484ae801983d6932fb35c81a7b46486eba0913f01076f46d206f3a489a23d140886dd571cbda8df5b840b54f0a7d916fba3a2a113386c7dbe0f549148d3e707d9a9d6b6072a9b6033b5be4c53b68aa9f7b477c40091461f2e2f02e16d4415d7a2ecd09024f69b28b9794c45f96282ae002d52b4a8220df57fefd31e37c19c2fd036b7ff2cb77fa1ccfc2c67146766aa5f35a98f4dcd212d7279c899818285fbb9b83e6c259c7a955c3493e431b5bbde4227647df6a83c86e3769f1f0d9c89e2c5a480e91738e2dc7e605d15367ea67ce4c71cc11f99bc44eb2182437c564e6773e46a9a0d5a77619a1a6331ecf0ff39d75636169e1c0b4e38fcfd6ed67c848b93022faf1d8e51615c0774abeea8a79ffc4d2caec987768a90d93237d016e7713eb8118316c1bc2b8b9ccc7aa4faf4a50be342596ad7b4c8edc8ed7fe5a7ff5f5939591900df5a5284d15388b073a0af365eb1c24592b64b1c82d038ed563e553092e6d7048e132898602a07c0f960b2570b85d5fa2e1b4decabecfccdf1112b8b861bd626aa202c37ebec7772441b5f603bc932a664de98260e9d49ccb0755d9c7d822428b6d3ed257e5ac8ea166103154e36c20aa5f22c7939d0c03ddc9792cde75d4826794267b7ce2691bcf03f39f3d22b0880db9d479b70844c8a8a77fa1c60907e14d06a5afc5131c18866c61c265c54a18724f68c19c8b82709b1bf888b5c73292ba7cc7276f19d4202234cf42211e8469e760aa814e2c294e8c2382e7e99294b127ee3cc14a3c6cfe5134b9a6bade9114d816f61113f52b41198c21ff59a6c6820d1486a96ba8b765442a01b6038a1fd73d5ffaa332b3e550ee414c45e231d0e2d1423d06f425b2ce5df3a31e84885399841165521a56b4eeb752ff05e0c40310e9ce5f10b9e04a82d4b4a8ded7f583b45545e1f9d1c968ebf17db91821b9c873011955b2e280d34e3438df5326ad21c9f1ed98d6381eafec7d79a0080d0acab0b34e2d1daaf718033987daa5b33ac16ca645236f57a240a68b56bb18876f6592b1ed337f1e257d929a91f65e86638d0acab0b6888283c291fe9ef9e954ea08510e39719e12227711908eab00635ef7924f1faa04716e4a34d4c8807bedb8997256608e44ac98289e2fcd9b02c0b0ab0f8052577ff841ff5e5b79488f0b7fd432ca2ca5e920866c51eeaccec44e4058cb5117f0616d9d116aa4fc7022499ee1b10b9f5fc832077a33189c4aadd1556c83f06b77f925691ccc5686fb376c6c49a17937868fb780b8e3f922a603dcff6f04c6dcecb61c049f57ab204b26d9ede8e28d4ac29a8bb31d996cb32fa7ecc34188e070a5aabdd6cf44e901df3fe0324cd0b4d45d3abfe6f26bf5518fedaded225a1035af7ce5718cfcc45f680f1cdd5462201443bbb8b3c7a170e5a6c149204fc0aaf929c596dc73e0892cc48491a58cf1be03855cc91be6d8bf06a7fa80feddce1fd960863852ecfb7cdc46a7b286f0c29a51bf98b25b04881d1a0212e05e6969f39fa4b7952e2413bcbf31fcd2734362db6cd58cc76bf3b72c19014acff4c67ef4cdc24684f1320d63c269a189be80521208b163f2041974ed84a52863c756b994eaf8742f40c82c0cffffc9181184626865e3d548de611cb99f1a9b17e2b56f32db2f9191c07a9a604cbe44c3ad0182de47bd199a0fb45ee54a0822cd8e96d3557e8038a3d6e349979edd20261bfb92ff749ce13423317b1f5c5ea4098f22e55539d2ddb4aa1ba6629460f46976276157df52e4c2296fae7f02811a91b132aa57ebbbfcd9fefd8d553ed1a0214e0a21bf873377ecb470024fb9ecfed2e815120d5d428ca9f0b05de050081fe146e7d02a448638f8bcfea0900ff7c9f3708147674b69b2c7ca201efdf77cbb1318a2f1bde19219ba1ed0a9d8e938573fc5f080b86b7b9806980f26e202cde0805f99f2632811d2237127a1461fb0a2e7f96966c3217f6cef17878964d4858dcb3d8d40e779558453eee9fc19844fce26ad920446fbb4196dbf31525e5453e2dbf01a61cc54ed7971d0096ba4dd210313a41b3b826ddcec371851e8c1f65505a210e3e2c3916f5eafc1c6d84ee035d17bec092aca97468727448cb5af027e190a40a00", + "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000cb80a0dfbeaeb79af355a70d8f19855c00000000000000000000000000000000915681ec51465d5a3404f61907428b712208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f714ae98e2b0527277d74d7898e5a1d76041a613ee74f2db890bd6db2fd3a76e9d30329528532c70174aa6a80f652ef9fb86217d722ac4e756dac8ec699873be87009de2f6efc0e2420bebb740d52be9795045be2c08d2f61c665fd8c2946f25a70037344482bad1ff4c46d1ed3887ee2f37d4bc2dd3a1c34c3c6a0b0d75d4fa63077e46b34c8c83b27e71c711f27ad90e03f985d92330211d3eb4c6c88abb18ae" }, "decryption_aggregator": { - "proof_hex": "0x0000000000000000000000000000000000000000000000046462eded1bbde4bf000000000000000000000000000000000000000000000009f2fdc2ae91c95310000000000000000000000000000000000000000000000000b2ea26392102492c00000000000000000000000000000000000000000000000000004afc85764e79000000000000000000000000000000000000000000000004c469c087756779e800000000000000000000000000000000000000000000000d3dc12dc70ceba4e500000000000000000000000000000000000000000000000d09dfd159e66bd2360000000000000000000000000000000000000000000000000001d0742afdbf00000000000000000000000000000000000000000000000009104c8f5d91fa2bf500000000000000000000000000000000000000000000000836b1660e0992fc3c0000000000000000000000000000000000000000000000031380efa2b7ff3ce80000000000000000000000000000000000000000000000000001d837d5ff0a5500000000000000000000000000000000000000000000000db4b2385e73fbe68b00000000000000000000000000000000000000000000000e4f7388c73406d61700000000000000000000000000000000000000000000000e8623e231e09b7f3a000000000000000000000000000000000000000000000000000132c5f9251f0e1f3f0033a84e44de526080bdfdef3d2c4e9be76761bf24b2d0e31ac487ab12b12f3b1b559ce6d9ce7c97b5f31aae0e6529acf2d329858733b8c0215b3e3314c51eb174b690f7d4f832df6574dd9e4bfdd41cc47539b0301c66a88984cc0347a312b53b6e93390fa5cff0fbb34c9a55b3bfa61ab7841f31779fec5644f07e5f9c17d0eb1194403db98035cef3a0d47e70ddedfbfb47bd33c44fc0f7f4921d50180f10ba9e083af9917dde07ffbe45a53e65983092dc0f35e1c84f92b7189595390810dddf63209c3f94b856eae91571feb451a665cd1a1035b990c887ecc43cba2433686433d56359e92f6bacf0501d6d7662ce00d33aebf14dbe64164c036caf16519af50842a2f1478375a011705e1b6e3cc8d1427b463a370002e3a70896ad27996d20e8ff12e70916e72471a5dbaef7f378d7232905aba8cecc91e8c077c72a272b8af0c6bc297b6b8563e218a17a1d510b271bf12dd8d3aa90e56d6519a32d29635466a7bd4dfdf51438ccceb51d486058596bd2e201d320c9b63fe239251273008b48d56b8a01c916ad52aace95cfaaae3232df4a22af016a830f00b2280dc4090249ebc6c4f4977b3d2f3e92603ca1e86d0c65fb91f757747e5b3c64f229c98b47e1adbdae651e8c93d1b5e22b26a27a4004b26a4cc6dece078c1e1f420e928f4810f467c371466a5ad780e27de4b7cc7d34c4bcf9bc4d1a8d69382b5c26a661451205b6074193728e248ef7585707509e09eae306e78710f1fb3288251b20d0edf70c5a978f32b92a869fe73975d955f5281b23392981a6f19a2f0f58271d29171ed9a120fb70186dd4f920f56f767149d663c103ad123bb86de7d5ab1678aaa63cc7e6039049df417f336220a6115e3e860bf85aa96a44d22587562527980f69c45463860ee9df44482e0f52c5a10107aa1bd1fa8a3ffa215868740307471504d1532b017118223f73cec88bdb2514519f0eb31f1c14aa42fccc0d541f5b3ea28bd0f5a00d90defbeeb8e25bdb73835f623fa6bfee1e5ea3032e649608c59db0e1d6d963d552f4b4633c5767b0e8bddd6f350c179ed032448ecb129c15f26bb9e1ca1f3529eef918c8ff9788b597b6befad98ef7b67aa0eb9134d5b4210870b8989b0adbbeabe3dfebe6ea30bd7d60659f4e39e266184b8243691b2e0552aae18e9cd627f7c5ec8693c5fa0d609eded0c8d047ec9ea6c224418278250112560148af7398eef1f9dd48561fa9c0441801dbdd69c8bd46e5c4af99ffe4109f7ed54f9cb7f83e617e6b2f93b922bce7e1c07151e676a7ece630dd747bcc142aeccbc157a93950eed8401606e1b16e66571b7f8ef663ccf4e93fb82995212bc9e169ea1c0594356b171e607e54fc02180a1059edf5acb8983064734fd9f8209e394bb72f81f207edcfaf9f2ed0b8c0689b85c47181abcfc8d36e08b0c5ae05832a231dcb4f6da7f1ff2ac341a08b210acdf2bf48f315d5920175f3d5cec908b4961c1e62cb178fd865c31734ac843d19fe58175f36c3bd84dd20e8069f6b0a624f48a0e47210130d1e6697959e2fc351c28cdc4228fe9fe37fd5abaa897e269391d032b12bcccdfe6c5b4157526f2325f20b13b8a81f8ee1492e0008cc770dc6aa7a1be35998418d9d54e3eeb7bc87d1ae47d20d47645c4973519428a79b15d9121c942e03cc0ccd93c2158ec1e33c2b59bb09173e7b7c9662f2baba474b01d7183e0570580d82094361ca63a614ebb95d875ff403af22cabd79cc4e8f400424df7b0904cb8d32decc3e907e6a0b511bec4ce2fe9331afdf13b26b4b010b0b3f755f1a444cc372cbbb860872fa0493f8c492a7f23867ff93dda7b637a2e90efee7b19c99a3476e8f399e1c68944d4bac5b988ae883dac4f406ec18abb7ac0c45dca453ba41f5b9c28dcca7905816fc9600d2298b31a000bda8fd06731f6f26b119a02a7fc2a4ae33307e9f2798f2502ba08f0cba86fa82624386a8a7cf1a1bcc4583eb73ca4ac0b5285cef38d0a4a6302218e5ff0184f9c9908eca82c01a26e711272a4d2dc6c57ad49857ecb1b4adab7807061cb4f5edfdde5930058ee71e8bc31fedd59323de5af8e5713c7ceb21d48c3f73611979e7e2712684589c4711f24d5a2b83af9233bea1cf2268234eac399129feda79cc48c5cd77735d9b000d2d0a40be4f80ee8003228f36bd9602fff33d890bec95c8c23e00e7cae127c41075162afa6c2b18a81f299e7eae0f590c18989adf321dfd19a846924be23a7918c08a3dd27cd71b39d3e3562973ca37074ec000f69aa3824710bc344bd5ea292ae856b14a9a14635879ccbf87f082eddf2dbd240b9fa4702f5fc5d88ee72b2a096808a9862cbf8ae30240058d22dd69272b346467520339fda95a07f6a8403325be8b2eb1744fef4514ad7158c52488019b3eafa4d8cf7df694c3bf47c3a59c1a1fc4a36060bc9af14ebf2eb79d07cfda2528c398b145317c2cc84de6137d44221226dbd233ab0c7c0263ddef6811487072f0e218b94a6c72fc7e39bc27141401b11d54d55aba111509422d31d6cb92dea31534ace379c0427c6c3a92d4f90f02251ec156fd36045aa90adf78403f49e8479b8ca9778326da1425427815fbd6274bf80015317e3ecdca8795d60e025b58a56dc77f5a4b9acb453d3e78deee5c14a5cd11d5d16c49fef05d37c81548bcc270ef9f21a79b449a30d540ee0b9a8307e991ea9b46f57eb69d7c1ebe87a8fdf4f8048b4f41d4808162f8e0f7cfb3e11a5c3cdec6eecb39b1351814f82418b9170740fff3f5681d48fce7310e527b6a03d5ae1282295d1a77b314268efd8cac07fc028290a1c8657e45227f4b6ee63a24de15adf0cd89fa38336b41277d5fbb3dd19e281b815b2c0d2e9c3e5511797e00e3f664895d68974f56c39c9889994ab59f8cab6b8a8ecb2d703646a2236aa91198ae08f77f02dfc879c800f7c84e398ed8a43f20f2aea496b004cc58fcf4780b4966b97a4be732ee4358cbdfa657c322cd78549ee3780e0350374ea4a3d8900f6418d683ec071a1273be730e362daf287de8c335727321143ba40ee0115c241f9ecad9f72e9925e3de109f1e566341752dd39b45d512d2388dacf23b281ce3108c6659ba45864d7bb6836042b9097f20f3f2a053046045dc613ef501a2eea81281834f5e8f9d7140f709b79d53fd264556d70139ed47d25666027aa69c58e42b34757fdfd0b9d122191696b4b4bf4eca578e24fd9b844b34d73c31ac3ddfa801ff012ca13ccd6b0f5e561e0e6430b4be2b0b2d374ea129e470491ed7c3b13b183e35a8ce4cd1853425fe864158971415c7a65f11dfe69d5074f950809a122a28063e5d27dab57ae4f0b3ae9fc361028adf438677dfb5dfb988f8b8b6ae57a3076e5ea1a0b2f974df0ffbfe0a060e3637c3127776fa81d9b01ea0d657dabda901d6807a490c87e46e4e394192ec98b42ed04f334b5a1c0f5057e4a4c557c01a1043c947cc968219522c1abe82b20d6a4db9c2b388a24b71cced988a4b883a481d25e3810be1b850a40508bc3fa632b2e5fcff0a7b1a49340915865bd52f501f1f78f68122727eb366d619ffc67d08d4d38cf0e91ea66920699c3a2dcd37656724c44f6274e12fa976dcaa3d6cb603e2b323f8dcacbcfda2d2371a78596fd7e42e52e2ba095e30b7021a480d05dad2f2838df1fbbaf38e0df2af304c8b1175b80cb447d4e3ba6d26637a624eec49a5617a8380020addb7879bc212fe1c600425147bf057f7596b5f2c938d61049a8cd1889ab5c40ab9386a13aa7022d00ad42d0e1b1338ff64fa766858440e350fa32f5fc613ccd8b46c95090843de78343271006a68bf23479c2223f60d11d64a98c2865fd34f92e687eb8c2b7a28deca830818a2208adc5a09a52dcfdc04f6e0e53bbc232b046d60528ffb794238b3e64b5417d6dfa423205c8a1ab17a90537a19ef9e5931c33a757771a62c7288865270fa1762f450463e4256850c78446525c13e0f55928414615620e1c41fc5f2a62bbe2ec7351b1d969bf8399ed677fcdd79a6689cb8c08682aa486ed19431e2fea30326c3b9cb74eb998fdd93bd366ca54253b2ac0e213d65275b7e15be6f55b87e5326ae1e2391bfc71e63cb96fe8badbb8eea59b1bfbbc89d9fa9c76214c968a9ef10f4697708d257e2caa80176fce9c9dd0bf7f08db787efff22e8c50a2b3d851c03bc1a26a9f96c6c7519fa1f9f69e89d21f3a846cfe8c7825b32cd3b1df30dba1d5dbd1e3883130ed44f9f1708164d7c037f7110fc42a2569005b099cd6c03561737abb36b4e6751c47f50a1749e53575fb2d60336dfb156eea527c39e6044fd0fa4c2d07af7ebb89646540fb42fce47fe70faaf75eeef6741a570a90a39e2212e25594c3141f72b1caabdbcdd33ae4e941fe07d0a9fd16698c6856b74619a810c0cfff04663e7bee110fefa623a4db43fad9bd5d72829e65d9a71427a0d5c2f0bc57df006430e7edbc07970858f1f006b42a6b8e510300fc604821abe5d9ed612879f61da683c655452ab2d76bf7345c63299b02b7ccaf83dcb03552c5048020354ed28001f40fb66a7fc55ff272aa8de1844409e56a6c2a3fcb89c94218f082674c00541fe4443178d227abef44644d4abfd5e9303fff2e68bf3e1d140235910cd28a2f0ac9cd54303522a68c80e618d937536c1d26897c8f85b4acf4aa1060c9d7cd154657431320a619ea0a603fd86a52edf39d34cbfd2327b95c4d02dac2df4af3fda5d0fb1411f7499e9228e69f7baba0aeed7b1e31f6d17aad11c2c4a1fda0b5b8b20f3f9963100e0dcd1ab2346b12f602bc30a2bb453e615a7c7df7a1e209bafa91194c0e3da621aea9637db24336c649d82aed522c6ebdc9e3079df264481411d0846d3f82043e90df085bb974d2ff8c5094b56b0721e622a3ece1e2c72afee9cce381b1ab0a042d7a2a0d32bfe34d76e43ae24cfa00802038ce3482e328423d2b01a87447801dd0fc1e2c33563bf35c6bf7ee27f8ea7808e26da630559777a16193d3cbaa060b686fdf5756e395ca3c942d5415ea321930d9bc4581da056043fe7c53176c8fcd66835b9ef69bd815564d1bc7fc71c1373bfc03d480b7aacb1b71a1d5cb97773bd8420ecedb4abeac3762884e73eb9701d5e192c1e2798ea30398aa7c6aa711720294d914419284a52a9794048ad73895e21c73f2e27ee0aeb6cbfc1ed89266847420a154aa825df70f24eb1b1f0b958c046771a911baa6453cba72219ac18b8e31e8b660828ed849b2cd1b642f2494e7430d440552ccde3ff0ea6860a58505c5d2c029cbb4f5fa598fe4ac011751790e0e606322c13d130dfeb16f00ef3a180f8e4886c9a3f367a36a4b2fa215a60511d77a4d1ab2e1d16ee35174f5333bacc3170eb79641811ab8d999ca78bd6aeb778c04bfe4b022405d81d43d828fa1363837759b1d688f9bc423f18067c1e63e7ddcae77b252d284dd9ebdebff77e6f0f9e799a7461a0f4899e54c7d34d148cb21cc3d29c76292c218095c7386baa2c5304e29fd4c70bd82e6062cf160ebb110230f444c2d71f3187f7f4932fda50d850dc56b3ef554e7745311c76955f8eb116e15d3f72d81a267864acfa25775724ef1f370daa11bc9983df1f6d897ed8f040899ecaad740b34dfb34c45c325bd21c38b8634103c639653053c1a72bf63e3a074b437a978169d1d7cf13831621c414bd3d03f307e7bf3c2bc654afdd857c43c0e1e47c4e726f6703cf414f9bef59c90a72eb596013447cc3cf7a8576cecd1674d8607f22e0cf5d470df13e216be6c2e9e8ea0ff0a179c00cac95375a14e02787cacb54a901fd68c2ae7eb0ee85c442aae3d8304039a8209c8090040c83dc3e287ce5187731d9b8e516f91b73a1475c4c4077544dbd325ee6ccfa3aabd1144352afdbd457c2f39054b92189663deb023ab48081a80b8e28841188838be3981e7e93840ac35199412daa396d472a6a2cd403a92a8a80995a1092e99f0dab79a2c024258bff1009161d190a6eff111d46fd2e4fb3528475d89ed0f4acbd2a83ed488cd0d14fb2864e012328fa9fa8234ef040bd7e64c94d8e5dd435f85564aed36b3c59bf50b0611681a82df36ab4b44b009b20fe02e8689ea64df791546cd3db0588d08074c241768008e3feedafd677164b7e19c9dfb2a1f5b4c91abe6b2716dda9027a8cc020340435acab05014bc0b5fba39be8a5dfe4751ec0cd65a54e3acaf559fc45310001c7116a956f02d2c919493c1972c2a8c37c5c7018371de046603f943861d069b44e45d5c59aa7b8e14620ef592b42e43dcd8022db7fd3d652bc292fdebed066f0db566282ce6fd05432d2bf063cc05c2372a442e4eb1ae95d943750660e129ab4a81c1e699cb974b471bb885af4c42f8be4be4a44f6c16cf33b22bfacc7122532e5f78e3da9ca7b8da40ad3651c7a3cde3c028a39f76ee378a5df8e7122d117b0ada3274420f4169996ff8ad7f0644294b964bd02e43b573e027e56c31f30281ded22c23f5318715d0f675c4c697a2c257f22112738f30ba3d9abfab6ffa1b183c5a511100513b515950e7995ccf0f082cb5b0d62b57269e2c7cf3e4c0eb0bffb7398d6bcbc8c3a742ce3bfb2975d365c391409d9590552733692a1a62b7138cea6bdb50758166ea8db309d0ad52dd680140c79d82fe429ceced64c6c214103a90d27fdc490bf99777ffd713275db4329c3cc42f5d631a29af86d0e449a5298a95d8f509c980f7b11abc90efb5b6ec019ba2abe05ff97971e3cc4494a8d11c320855d17be84acbdabcc29634e10451cc55f7b18e63385dee746c70927b271c9d654c07f30212ba3d0b34ab1c7d81484353e7303ba8739122ea0b4d054c5905b3b6fc0e3482c9d1f92261d1caf129228350cf179238a409ae49a4887645050da8ffcf50fa4ef02b22e5cd98fee5625348f8dd72c4806e6a6028da2514e05314cfd201047def07dbac92fc3f31197a0f94659a6264cd08c7b7b1b73f5cc19d074a8c82be071ccd4ad401a5907a3816735668a55c2c2254cc94d9fd42d2990419d4ce2b03bae535d55557127ecd66370f5a555fa5b3d3255b0e3e18040e7c9718c174a2ecd7916c33c950e946764b5a2490252a6c8b4e8a3f4e4200160e12472f73ca99a627089fc5affdb380da4506c458ac642d6a8f151f60b09261abddb2092edcbd085881db6d5b96013108dabdc80a3b38fa8d2e638be26c78465bbeaf2c06f7a360a2cc343d6d9dc2db8f6424302e9718b8884ec3d80e99407770f64a15777f2da7f4aab9408e3ae2f3414bbffd74a290cd14195ef7fff6b31d737bc22f2a1491ff76b75114b2105c8ced13258e504a7b36c5102321daa348581a551729aa4fd8e5c8a411c89f29eb17ccbbdb02141008bbdf46c7274a2c719595fb7a2f5854c0f6543b85ec7221d1e15b8e867c80848016f25e72617f27e537cd721c05b0e96a7801ee3fc43a534ff819962b1e8282ed7fe5437df8026f12931affc92a45a488eb039c2285d69ebb9cc721d0c0481ade8e27986cc2424bd63f09ea13281fa330a68c69d21ea8262d617a8784c5c3130f9fe2638a28d3cdda46fa9c3f110d509bb1dfefd5f72c7af0c2d6cf3036ea02de515f6876005bbac8d8c51d6300ac7fc8e451e423e4ba7c25eb016814dbbe5a9847c8d72a519dee86749635c52fc29948f9db22787989d3d2630dabd1b4a74528011a8a2df437b4fa220098850a3e4753fadb485e012a8cc00da2cd5919c51598f8ff98d82ff813d03bedaedf2fea1e5e0c489ac9b0400e0d949e92115373e6350c76f49c345b79f2ff56bcd001b956017661a2f6b6b167f5f12b4894a32157334c60f29d0521166d13d15de9302c9b0c0bb5c7880f6acbf2acb78d97a66d12e898b7ec0d8c7322bf62e51aec263770b749a733e1842af368cb129d636ca2c23890cd74c44f69a427e6dcd095119e080b300e82de5fc8b08396e97d027b8e781f0cbd63489c5d496949b3daee08a8d6b94c056c4b29bdbefdfb87f715725d48e2aa2c8ecb2e72e4a6329b861c11a1733f664320f80bb5e0624c847534380dd10bf3b9d7b0b3f3cbc00ed6d8d72ea19cc0831d47f5142d7699110a13d154190e29363598288d9e87582621ec8518aa844817a33cf7385a4867a624e6d35d19f08689cb4694d9eb4068a5a89cee14fe3cfd331d9edeba664f921f4e2af1b182eff2ffca37f65408d3d1b42eb324193f27aea86433f3a6aaa1f0ccc589243d364a6d284678652a5727709a417c212fda27231f6e20f52417f284a5641a6d80362cf3dd402b81e6dda971168329fc14aa355a2874208110adb7169be2c53ef263bdf59336ba78618087e36be1f3c9277257150ffe847f6d86895f1197dcaa651987c33d02f5ba5b482311f817d2752f96ec1b41838e338b2af7008a0d5949ff9f18dc7be79ef5739211659f266d621462e12c1be7bc4b4934fdb8699f993d2caf7b1c570cf25b802c98a25d69b994073b68b01ae79b6a69eb211a914afceb059e78d2a636d304a48f6b05bc32993f1eb3e3e642813c85af8b5f1de0888cb13c53fdc5d5d479c70ccbbe61744c1239002bf3e62cebcaeb5e7303b3e12a1f30fd0db795b804875570e7fed3230bd8052871b4aea05a90ad7b63f4113cef0be4dda60693a503b2378172a58c4624a2eb2d5737e779f841a33c28eb444b62d3d23520278eb740181fda57f544f3aee7e30252680255206ff7d0a4a519afb865069d8a05da1132d795895891fa94f8f29904f4324c26aceac3104c97d39759801a293e22e65a381aa86de3b50159ca9fca09cf8f2106c1ecba895d547e1dc71ed0d9e8db14b9db90888777e29ef793b337179e7d5a6852b64d326b7fff80c4434942d3585902b1c534450562c6ffd93e8c0d157d7f9386ead62dbe90615399b0c8fb87e5fca00721a2a3fb68d14d36819b09983dca3f3792b69abc99b3f3f5afd33d28cba0999b8968f72566f05de37d660436b2b1da3001d81f914ab0dc20eb913507e7e526d5f96f842dbc6d461b96df2c259d17a61cc499cc16d0ecb910225076caef6a3d6c2fa282b2bf34a1deb87129450563c1427f7e543ebbd7aa31c01a16f7f9ef377414182129f183ba1246f91048c125b304aec8c136a897807710b6a9a73cf827b2c53e733c2eb1df3fb2fb0210be8239b1631db74571a2e30ff1dc5e77a7be632890b5bed671bdaf6e2a960bdf75213571caa91dd52fab661a9e4fa96e23b6d814748fc4bd6a032ca1cbc71a6e2915cc8eb694d63a591eae3f5aec153656fb2311b4ba339a7e871ef872702461a3b251b88bd55cea092af0c1cf7ec9f96f35e46cd359223da833d8e518990775f13a8a47b5df373d0d46a9098a0b23c1dd6aa76b286f6708e7713811025a23a510820428c3768c4dfc8965fb9d34ed574ea7ca0d2eb426afe5f8fe0681ef02a7a0700344716e9466f589aaee49d2860b604052684091a9e0ea23dc12661a18dc0d314a36ae8c600e6a886d4343a2b42f3419ba19e793ede386baaa303a402f234bee6497b9e517ac24aaab0fb01afb0b1707423554a869d1bea6a5bf743f11f32baa913c5500c9df9db6f92f49d30b7adb7cda475c5cbcfbdab514f391b12c3e705687b7ca3381e1363090cbf57ff7a86d1fc0c9b110893d6537435e223b0966964c5e464aa76eb466b04c4d5c7037f19229aa437e7e490d1bf9b19eec700cee2eaf6b694fa0b6b912b86ac7bddd35f7270baf326abc5234e02798047f6100dd53510a41dc8fe6369350a1b458bf042e8204d4453a359a5d107ad266d8bd0a393b07ac21e86143568945dea47579f961da100dcd17c89e800299d1dac22413736f9bcfe75432de59c190fec427a659e0878ae078596ccfca7ce18070706a27da89553eba9079775003ae2cb47c214b82ea2eefefe40da9638e912cfe60822ae649c2057442471a471a9f952eff46a2babf1a83e19a4ddab3794cdac562db101a09b7a278b2dd6fb6e2a9d393579c92db556817f6dbdc7733599f140cb4382e6b319f574a62dbebd2af4cc151e0773ab2fec6239c69681263c76fd83cff19220271f850682cc592aaaa6eea28d6bc0cb73007fd4bd1bd995a099bd82c024f0a2f782d188ed96e6e3bf4087c26d3d237a2b12204a57053ad307c344ae34fa4213bd4ee4f056cb822fe91f44421f97b9553e372050526ddbec9a9810f0b77cd05e7dd08def408b601eea156f02988103cdc6af0fb020894d84e00680fc6cb690af351f694b00379f093c09cf78cb4261d49857bea4be6f8dd3f4a7b32f59a230e3189ede5dddf5124a8caf0482b511ba11705b8e97b0ae8daff89770da3fb7e20cb1d9be77cebb1509930887487b1599d90171d64bffc6e929ec317bf7e5aec2f7740dbe894189de87675d7e978196e17b528ac35b67d091c6b144dccebf6770a01a0077e8d2c021ad57fb50a34e3158e55e57f94290cb6db1438e73c26dd5f03367dc364867a8930faff385c5e3805b3f5d61b5fe90d04e51b1e14373054971a06811bbc967bd9eec33c892ae9c4ef7274d5e4351abbabd2bcb803a5f5c70f02860e11bdf399a1dfe38969bf66e7da76086fd1368b68e47963db7f9565b3130d69008e708fc5f8961db2d87f77fe3aa722795a6c711cdaa2d4da8f38eaef83196be74e4cf2a837a79259f60cff97b4901f7f48941807f07784fcbf4db57ba80c897759c9ca3474d1f1c03abf18050430fe347a17998ad5498c2afd48c8a98415b32a411758c283b4c28c1d5ca62fc3dc246c62fae1b7450b168c5854c3f6f81f25ec496179eca02a8934cceb7f9ab9fc81a0b5c0982e045b5742e3d21791562bf709a403a8ff7f4c3d3ed483bd94c60426e37242a53fb501d465f6771bdd1c01e4364a0bcd48a20d4fff5363f75c85d02ce552a1c66784f8c393df093885b02286068214029f61fe4946410855f18276aac139a43f0b328f281a40f607dc65006694ad288bedeb4d931e515e3d739eb1c0cc95d7c5284f3ae66f43286a98a92480eb742d842cf382e23e07211c6767bc86b65efcaf58199ad5bbcfaeb67b8116ab42ba4929e61ef40822b47b3b77e1411f2f8dbc72c1e930cd08e4eb38f37b0bea7c2020d7ee87ea54c1b3696fdbc146aa584a23042c69e6efe33f7556c1762b3e3ba8c773badbf5104256d6a599fd5b20145beefa188347e080716230875a1f23ad5fd8f118d14793db2a001b386410d01cae4501bde987126554aa1f943809b93a34284851e0092f0ddadd9d27066c2568c8ebf91bbd5e1554c3ebbc6eb509d2ea1544f9c6b1dce97cd8584418ced461121ffcdec9e3dd1fa14f95d2e4ea08cf6a13709220c27a0a49bb9334e11e182a621cde1bde5f384b1b0ad51d19833033a68bb8ad808d6c9c7b07ab154119abbcbb85ef10d40fb251969dc2108c90221aec3e7930b31580525bb5871112787777aea6ad16614536a5a667520debea18baad0cb9f041125b510f5f1fd48f41cd60268c6ec4149908e7f9c9a1c3fcf813a79c776a2eea726579809ef20c267d839f3a208120f8736779796e9dfa7bca253a82820d90f4f5f7fb4b94a870394dbb688fdcc0bb429e43546b8b88743f3f114951cfc8e34a21110a24cfb809985668ea681e55f068d8f260436dfcbaffc91c32edff5ee1777d2ff82c80ea797077225f691258424cf5f5d64b3c312d6f48257b1ca15daafc6d830d0d38e74669add7907e870f0f8e896837af7e0104b15d103b7ad3d25f8de54bcc7ba08a0b3bb9f30e238fe4e3321a523728c554b162110489739821241ca55e4ecbcc5f6f25bfb44763a32e19fe977940ce8800db666317bdcdf1f29834dc6b2f07c60f7076ea4f4c73881e89734d46a31a160380704e0e4d0b30a60fae2bd517e3c939cf57682a076d4f6f364126bb2975f940ee8fdd0976ffd75e34827735162044bff075b04735ee3468e51d80a7a09bed81b48942205981554c5a78f504f763531fdb93d2861ef929be2f873e59dcec85631807b70484a7f6ec144b234ea375bdd48188e15ada0eb56ad1b987d75ed356bb65d5e01bdf10e80176f7be41f9d31008b8274f1b0a8e37108cfd37fa80c5be57fdc6450207b4b4209e983e6aa97e26c81afb4d57059de74276ffde689f883d5ed67abd2ccd9fbc681e291fed67130e43f755601bae4c8849909e9030484cf3c71aea07227f95a36dffb88c8d0131284fba36b597c0607f0ab3b1bb6c215a19fd80cdc92c0ae72660453b0973ebfa890718c2939f1207e3ae5ae18bde7208392f708a9a093cbd866e4426034e015fa66d8b9379509021996d41841c44374f7e4ec77a8e0bcb2604e0783d0a7981f77addc0d9d00dcdba536950e38f1893b138696ca20828cc6f843d63ce7c068774807ac2f6a52bd2adc3649c130a35b854e2965dda9a2a39eab89126cc289a350e2bf0eda765a5bc5da8640047bf12423fd54224d0d6053505f8e689a403440ad11e799b653515ad5ebadac03c3c17a2e6a40ad8b829025a520eabe6104fefc0349b83a1292f1713d4e56131708398777532728fcd2b0b712b5802b896281422c1bc219dfd0d9df3be491a20443ed5119ae62c468d2a22adda3834369b2cefc68f1033b217baa21720c4e4813cd4b52388f3bce5eb3e21ea381f1ee6305814a07f4742b52311720f7aa836766eb57fc2504d89fa68be03b747454dcc12f3b4434342eab6d95eb64c2dccd735983c553b76a009c18e6e2e8e783a9067cda1f5109f7371fcaa2cc1658c50fc513effba99a62f84479ede1c2afee100f375462e12d611ca1e58b350f4713e43436f8d44ed129d36d741772485b57e2cde8766e19956bf15fe1a3f715f462ff70e3e66911ca8b0349ccac50d0c4d59a71edabc1844172fce54e10bcba6f74cbd36c2c6cb8cdf86b07fcf272bbeae03157930ff5bf215a8fbeae00610efde36e8803758c44b71611ad8b7530f77a243c70d35bcbdf3d390e0e1f358a53bb7b271748486ae81cecf1d3bd7b312e91ec7312eb62f7f4465e9f133c90b40b5aabadf897db276170982e3682959027d4f12eee5a6720a6863c259d5569d30604063277d5afa87a04ff1db7cbbd8048b5f6ba32e0972746c42fada98ecc8139f01726b225c661b7131c962a6af431c46da1b18603580e0eb36b1b3a78803784752e95178856a52cdf293be7d20ca28afd91216a390b4c5fa200d13796d66a09e67c0dcad2e5cc6ba969843f35de110a26385709f5eaa333c3dbc672805554507e4492dd88e4e1fb37d30f00b100b0b73ef0f2805b3dcee77ee3c518d4d2b5e10a6e74139d13ec437fb20caf21f6b14258b219d2d7c874ff865b46668c9a98a6f95f7513c4840eac1fcd8c0d5983f2fc417fa028662ef9622af0c8b9a8370044569515953bc3351e7879256a1682f1913c6f71242142890f49878f50a88254967744ee98fc8f39b077df24f896475138b353992d43a58ab679391c189def4abb585c95fc78f8560ed1578334a54ef06c4e242e99763949a6214d2f1e6b2e0084716b5acb458f0132451aca43dbe9002d447989ac471fc47f751e9ab748f01dba71e41478c7ba6cba186c59ab2b22e0ae2cd982005400350f2b1fd5804f85a943324dcd60e01aec98058ec872048fc2a3aa79499437917db1f087117dc5627fb97486c24adaf4cdea43ec70a5ecb1115482b3eccdfff6508c76655387abd8db57b01b170d5e1870faa1384ba4365e7054331d92e9f9f0b1ad97b7b530669a248a4c26ceaf174cf6479efbc7da6ea54099c32957b8e46d8efb77b50d25e3c705ec7679f33abae0b097fd615c45f9d461b03dc3ca49cfa79c813d003bc5fe4ec9cfa94a1acd523d23b88d35ebb5a6e9a2fea8939d0a2391f5ba2f337b17972354cbee48f9f4316611ca4747267fb899a2c3deec9cbe9907b63384af2f8949c18e82d0599a14c794393c329254a5497322b8f054ff0c0c835e7176bef90eebe63c9caf6838cf97942832984d19839ab9a15ff2ee3f4e15a05e49a92af53f986375f372836e8b0871f4c4ec28a690e5e631d2dd1341b55a31770bbeb958ca73627dcb539875b11846f8129714880e708ee260d85ed2385e58c520b25866ee5635882ab26c9019999d6fbf748f1e799852121d87673b8e00f15076cdc02771e293c1ca49972b74ab8790702fbc2d08d89be1c26e413b5ff7ff00f30fdc45ed794caecb4f8e943d341f7ec1f9cbe686e8408302d278a2da6ae5b6328b996f93480f99dcbf66ac56244f780859f9bc5e6d61413b5b5724c4eaff5aa4d5113e19f2072cc4486528b4f864c0b2fdd8dc12c07f70799dcb83bccdf08e82e1fb2354171bec979c58e2dc82f05fb9018859906ff3803fc845ea83bfb869fca929234cedf2c7ed172a4a857031bd37d4a65886916451a01d80e3d40970692f0ab31cc53c737a8ffd3408641dd18b12b59d00880a1a51bf1201219c156f4687c89b3bba7966515c263a6da337c70219b18cbd7e1a7ca07f77287b04f62adf3ba0a00f903a4de156b402aa92faec6737f1be36050a4810eb34741a55cfc86f22b7e734e338f600f09df00574221e9f861fe9b68b5f8501ec4c809a6c85ede1d9410633478be10ac32a14f2d77664f050a16f2b0a73b470a99616c27a96a7b747c365d9191b9a0a92f96eb10e8cfd399d35a186ab9ee7f1680f3102a604fc69d1b1e5058113ac9d917ec32e1cb339755b5536e4715758f", - "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c91806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f75128000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c2000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "proof_hex": "0x00000000000000000000000000000000000000000000000b848f7067da04597300000000000000000000000000000000000000000000000cfb8eacb33dc5026500000000000000000000000000000000000000000000000a140f61a0ae7da9830000000000000000000000000000000000000000000000000001b35adc769dec000000000000000000000000000000000000000000000005529748f6a3df43120000000000000000000000000000000000000000000000041e2b70da2e142a38000000000000000000000000000000000000000000000002489ca9f8182b061f000000000000000000000000000000000000000000000000000069af3676bfe700000000000000000000000000000000000000000000000bc2f7c98198f35216000000000000000000000000000000000000000000000001de57a498803e7aa800000000000000000000000000000000000000000000000e40fd6827d61103c1000000000000000000000000000000000000000000000000000084230bc9e5b600000000000000000000000000000000000000000000000c20745ecc0a9b617d0000000000000000000000000000000000000000000000098615242b257ddaf500000000000000000000000000000000000000000000000d98b13f67a2b6a5220000000000000000000000000000000000000000000000000000df689a73e63b1a953e2b375615edc3058e7d1ca5cc4bcc12d1207ee7045581b3b6d157383acb008447d2b42adf3a25787526050a2c464130614c700a9cf0b996d758e0ea3101207ba4219ef22ce8b381e24a97633135e5ae877a3905824a6b2117d2c52adb6121f18d2e4a72128ba85103c6084065a0a957c369df32907adc47c59f3457ae3524cfaf1eb600e22c8222d5c7f3d05b89dfb6a6252ed158a263b28cf1d704e00722d53425971fa3915cb5842157f175ef3b6c978bd686b6bb911469c0a0bb04e524323ec504346fc6d320aefb7c8a5aadf1728b7a7598f38ee2595296f4aa053a27c5f74b1cb2d97320928bf8e5004eaea31e7ec579c77a628c4d06c323adffc62948c40c873573a3a100f76bfd152510de33a30d61a10d2b6d1c93de13958e1c18776cc9836534e19b35a0f44676561fe123ab6651f2926973483bed88c85e5c1ed56dc8a8651f15acc31d6d6bcf2e97b303b160aff49148eaae0a509cf30cd9294fb69b8ae360bd1e988a8fd0b940a61d4811e0e8b18fb2e137aa4e76109a151c5d96eba7b16f7543505b2e217df4e672a962b340c011df2743b79be60e0b0c05a2155c315ad8038be74112c3c0a11a2ba8bb424fe02ff440851c431f3a0117298170f8e7b02639cab117e04b8477192bc4e2feb6dc7d675bd3a0e09017ee842e52ae8728ea51e4a18da1ff28088f42d38ff18e60317977213e9c0d60b20d2828872222cc0f34e17aa5c9267d627f4ac25bf6c7969cc08f69079cc5182cf58c183cfc115d5179470fd7395d888cfca34a1913a3ac918e490bb8231b59359ef909ab3830af8794f8ad241303954d3f8c794969cf24f7f171196fd1267895e1e42daea16482d70b755aeac34e0b1442d10765de80604fff4fb678bc7f0400f5db1065748acf1169718b09f0aa0f601350f0a894f8d32b81d408c6d02b1906426515448f4e0aae668ea694fbbc2a7280df188e758dcd850ad11c7fa74268dd35f910a27b4c1b260afcbcd7f6837d48fc77d4a6da0cb7b8500247d05df4e3154c3024c6488ba33e3fd241db9e47eadb5c7ecdd8246a0532a85ea5c12ad12077537a256a2e262e3ba7450fcb7709ad3b2b9857b7326ea74d55f5ceb973b149f8cf7e0a8ab8172a80b043633b24ae10967d6489570114fe7c5bcc2419711b7d4be07121e64aa42315eaadd92cad2c7dfaba18ac683b7ba27997eec1d15edee22fd752017f1e3ea133164724fb9730c4863fac6f3c79aac2204d17c154dfeaa11ad78e1c9c38a7affeb9d26b70487b79b739b2daa0d2b89b0a57de0138d35875d5fd23095e676dc7daa828b9c87b55c7a3518b138dda5947ad0dd1b14a160547a853850ac087cc19fc4ec1f18b50f448df96d3b691645c3f02ae58749b108889cc72921344c9da4fcc45375fc89d97646e8a7d8ac9fc1507cfcb38fe716ca264c886db2146a1ccd11f884504c374b7a78a3396c92c52412791af6611b89bec51c8ba232444938535ffa6ecb26f8768571cb60fcb4e413ce7ccf34fd796bee3fbe9753322b0fa40c4ece2d11c0a0fbe4caf01ee10217d9713e3da320a49d2e44fa046fa2d95725fde9404a03323ed83df779d69954a6159ad108a0cec3860d6790373240e33e2916f05c0e22bf7ef3e1e55026f54b16bac5918c42a031e3cd75368a3ea0911eacdce98241da2874f049bf64456301ae43029d846360fc0b7157f3ccf9c2a618b5f19970b3569e8e6c296cb17ded8d661167462409d035d33f8f410a1af1c9d7c94e3a65ea5bb07307f1472f9bf7d3a77d4d738bafefbbb37b11d76a8592e9c374b65d060a6e0432b1e507bc3121a282a9098f9b1c56d75ca6cca35104415bbbb52c70c65afa557e9a5bda404374ce6d4933c90b2bb75b3ad3579f19bfe2479c82140a0b2d0dc1c358799373f4b0054141fdc63e26b4d40735cb98b412e0a92db0db0d383f17d1a3c5b32c14c7e00ecce12114b2a14c522a007a951e5d02562699300507e2202c4ec98a17de017c2a98f9f696f74f012a63e0ddea2b4c40e88b9ea9eba8c6a614ee3d17688edd8bfd425e968efd6e41f18fa4212a02c471d1fe5f93f27bbc092530c4ac6f2ad3a6560773c63da42312ebe80209dbe7af5221fa153635f616561ac1701355d572dda9f76e1858ec211a1cddd0c571472cc1524e7a358c86623765427aff53132b5a43e43735b0724cf4f7888341d96c119180e1cfed86fd01c41ab9a7b9ed10c57679055a8590d57581a6d0249971f93e12e33f3c568d000fc1ed9f0f12a741a42ec1a40e365c1ef0da413aa7292fdb61c0e994f53d3698d0be3dafb87348b8de14bef041dc9e7928909858239b791c7981e35ba93a4550b707bde9024dae26188c586bd851e350ae151480f3b787fc3db0053c854ac64e45cfd0c7526783b1d9066c34520dad62fd5c2195a94f05dcd90289201e5ef356c1ef34c84a1f3908211af44c1a9e44f63c34bc1077872f831cf2d6227dc6d78093ae08420e2c0ee695b9b8876fcbe7a5e27f7167c33cdfb5d3315023c8573bd927cd69c7dd64f6725215180bc8ec72d43dbe87904c30e01fd9e1315d81e115af8bef91661176d0d43d12c5281e09189e4fa8da6ef31867c5e49229b5e9f1e762ad2820ad879257ae7d089e7c29665fe3350109b817b2ba1cff502fc48497b60a3fcf25520e674245d397e728388b0885fb2254724111ce886c3008b363f7fa3ee256c6d2b66b2ee8ee6706129c381a305f06e0bf6427cb59d072472eff3c40da57fa40bad15e9c5fe639305d72ea09049d2a4598b72aa20bbe11559f8a819c5bc7278828b036cbf3cdbad90b31710103fa95493a3e1d95f14311b48fe60856b93baa932b1264f8203a3c78d7c04f10295c5ee7acf6d430edc2028a04b182925feeca261c0d7721177163c92b273fdc65ea9e8dc8644588ea1090684ef3bb32cd09380e7e12c59b402bd772c4418952cc8a294ff4ff1e46364041d295cc8f55949a82ae31c2f69523f349d5a258bacccd74e56231e958f8fee981ff582ff63854b92917c9bba45cf614b18c5b4632989de011ed79138ab62362d26ef1fd5eebd2afa99b676fdf9ba3ca972fba10c25aa0cb6dab126aa1a390be51ea1f33fa64b2d28c185a7a108e7dab30c1b6294c066e945d9eb6fec0dfdb9691c8121fe3c63bf90063464f2d891bdd627ae213e03b80d381676e921583af3cd21a651eee9fed187b8e69df4575045e3917026bfd4bf22a9aa5c680ce3d5cc0e0637807164449acdb6f3b12ac057411106e158e043f2ae60fa35ef009d6037eb0a92bf7d3b84212cfbf3ec6bc236b7821f0b17152ef2f1dbd536790d8060aaf81de5ee880095e83cca416a8783a531b8b9affc26f14848e3ea28aa826521b91d20a4b663e9fd51696ad6dcfd0068afb00e91ff5a813da0d09e4e27ddd02d477c294acafe501df227625d3ec378c8ebce32a57811927421e2e173efe6c0222a7e111dce77d8ff4a253b43bb277027570fc74609252c0c480a91326a694214312e2f5cab400667c72202f583340647f2910a182c44d174422c75d287fbed54792a2d54d768ea9152152da70641eabc415a78251bf2cd430f248bd5e22089e8d574231c9823edd290fb508e77034dc2f163bdc0126715cc646beef40785e43a7a6c10fad4cc07ac70c73a86c05ab097eae1fa23091df78485e6c00c7e9ab187eb8e09177ebd746640f923894114b8a1c066370d18c21cd3f0779187e04600feb1150e3812fe80bee8cd54a1622028c75ca001bb037e569533f3d9c127b688309a0026ba070ab9931c737db933bff1d7feff03fde071d5d47462704098e3f37e18e509b1f0c3190850f84d9ce5bef54dd7fc2f3c3c578ee43c40ee92e6256f605ded019e527d4b2c3f5e4e29cc85841ff0e94697affc8a31cb61062bc94a0e39bb140656f880eae3933627a1239b9b85969b64e9bffd551162916a19cd0f363259ab1a90ab085bb36bace2486c4692d034964ac64b049d51febf49b19dcfa5f99c170b17bd9df0b4b930338d1ef2d5696406b529fbd7dc830fc8c14c6bd8c16f00f1253c1e478d7e88fd2978ba0bd327c11331425cf4b985fd58b14d879b9839e0620bf079427cc9eeb05626ce30880a78ce60ae4d6ac366b9102d69765aec3752b40bc86f3d0a057736d13e3c85e4713dca46a775894be6a86afc029e0f8a76cea40f2182cf1d36ca720da6d2b5b679660c5061add32b5fc152a820b782087ba66b0a07809344a0134d129ba7f4e19956cfd5c47b410b7302525494b9a3e397b51e3039ad1ef896965dec0bff9b358868014c10ca1fb842e3b901b0e510122498551f2e9d7794aad63d358a377b42b0c6d1d78415abe68f58df1e04dc5bf11769b1051f90bc0f3e131d24df2c55c2e7050e7d33d22ad39e5db73e9af8b1c00128081b8ce0ce2a9951c146ffeff3e1fcd55fa601217e3da2193448fb858c06be8d3c1f7e565a2e821cba698bc9a13112bdfe25250d3823deb0e7a8fb419d9e772a922eb38484d054315f4418bb9a05efd6b9bfc3e5ee052f6a7fde779216815dfc4f13478e9b3d87b0a82f217cd9b0e782f61a8697d5d6707d5e0e04ae039d32acb62cbb49c04501ac0643d2524d3709215390667fd7b63a2c0c85f63f7a392074fd094f6c07f5ef4742de0a6aba56f4b16d8ce2e6f7270e5906c9cac6bbd72e0440214dd6d3ec8f200878ca27d4d36ca018bd7d9092b0aa1b86e6ea37d637778c3808dd56099b383a37fe3b60526ab524475ac22dd37d44a10380cf05c26d795edf159e18b8e9e07abf492f5ad7031ab13106b34b66be1ecc8366bb6933be7233d02250b41b4fb8f028195bc6f56fb47efbaa729680c0a89e3bab9c2d109baaf5231fed54a986f8a867b839ba0b3394cab7b638f79fb5fd0003bbb4f17236d2178b244a32093464ef748333651ad7798fefe65c89e14efea7bf094f077db3dcaaa2153a977ffc12f1b060c3fc74f4cac4c8852b1b86683e693d11c4978c2879e757056e7665ae4ec522122f631b3e7be0e247c70e498be722e31864c30b9e131f00069729385ad3114bbf616f1ee7b0da8bcacb110bf9e27cb437bedf1e283c5261008c0347f73b653e6bf9ac59c203749cca809dd1c666d128eaa074865773063400f1a2f125d9711cbd61c211db9c22d974b11205d2af4aff9f8f5795a2aa02991bc661edf40dcd7704f835795a7eaaa5d2ffca5a355400fd8eb053cd2eb201b72cecc94ff7c922e1351c74bec7068a18db3043d9048ebd87ffccbadae1ec64fa2b05d1c7370963c3ce5976f94d3c3cf4337bc97e2862c5371d7de5fbf8b08cf00a469637d69d340a3acad48ee62a6da336abe55d5a82d1c88e1a4cc0d64019010879351b300c0d4f5c8ec7593bcc6a9afa75eb0b529fe079bd4e99238be11c171ced5a0a55a72439584a10a548ff26452e842426bd0572527c6bb2e48b5f9ecb117d9f4717426d96ddc8c4e5c2d3cf707a3a44a20e5d564401a54fcbb2bec9c81527aed8153ecda2dfe77976690a75f4ad60038a20a8287fba638619b1bd8acf09bb43d9e15613ca424fb0f4b15117d8bf4cc72ceeabc45b803d6a4657ac4acf0bfbd262fd8158150068a3e2e6e6b97148231ab66fedb12b089fbf9ac106e6d70aec7ed98cc0a00c75a21498193b1249a75d4a121c8de1da14e8f03d76530171172b24f9cf58ed5bb84c96a80bff5fb44d8276815de2b11f63280aaf0910c977015b57b509baedf1a0abb96552ded4ffec30c1aa214f478ffbad0332bf585026177219291f2a7a5d6a47eb312e333eebadb57634b2c6b7b42340883b9444a9cf0d756bc8ec5020031ab0255720813527bf9ea6fa107c276178962953db9abb091a6dfccc0ee73df060ed6671445cb8ab79645d36a4c81daf02949f2ccac2f2b50d8c680c18a8038e2e4fb9e4613caec509de7e672fea040c71ade0f84ddf82442060de941d416b6cda862fb8a064d0721b031c7579c9b917480ee6e05054b3b70b73cb22a12cbebf44319d890f99ae72567d4fea41d298a200676aed5c9c440d2c1da3167a37b4a6a2a8e3cab3ab003d0a9bd75c1f3565db7440a50840afa0d820ed8e1a347f5ed5f14306fa9d6580d65de70e3d9e6ff372855141bf3baab542143e686a3af2c0ad7eec32397fea1d8261d7ad9fd280a835cfa66015b1e27517225c5c595103ebf1dae43c83a1b20d5d48a99fdd13de8804b71a36e29bb9220d2eb9e83179fb7d6d036a24a977f063539add79293bc4b8a514db5b794b578e0719e14d05a12be872f6d6d6eac49bf338195d99a4a767b10a4ef31b8df5c8b2240e27dfa9b58ab84a85882f45235480b01b40a00c94a4f7030f0963ce2a4cd8b0263bbcac11527f42563962bd99da5db443d0b1e0033795ca9a8c7903f1f339f6255933c3493c3bf79745abbd70be31483d1cac03942d2168a4f1847170fc85d302fff1ab84401d0eab0359433eeaf7cd60090c9d81e6c8e5c07e205eb049b2f10c72d392a223087ab81ca2a4f0077e20706fab5f7f305bdde4d0a9153a4915de2000df2d1e2967f24ba50b979f116f205af7492a83adf7a55eaa247ee60a2c421e43b1b9e7b656041004affd6a20ea5bb0ef63b91c467780eb487c6070fab41811ce6b8ad866d10db953803612ed1bbf5988481c603f79276edf7e871459055802f3d0053b41b8792dfdd36153e6ed6258f93c1f8e0b8ac021b9b94f525408b202a84935cd7589deadc831c1c5605db0e200fad3e10317a51d3fec67451b252c292e7b1774e660407e5a15b34a8c9f4d61f0e733d8c85ca0963b9f22a8435baa12070dbd1df83e54b0b2a1acc4171d1caee9a39fd3c7923cc6871804cd6a45241522f4d78196ee7eaffc973e1267f5890b93c6d0653bcef6aa53b62ed9b61b841e8bab815b1ded0129cb1b38c56e9a754538a399e7800b170ca36571923260a12d6bb79a7fb8e6dc8e5b7f00ca46cab952bce4483ad5cd24ffbf0f717380323122c46ba422755404ae8510b406b2fd907d9aaeb3963a3c27ececec99a33edaa9101f93327e93488b4a619d33020e760bf00158724f273434655cbb6f8d1a975c132e4be33e35e0efc792875a03699d80227c2f4a82cc1c41779b595ec6b229ca1aeb4878e91d33f5c1f8e6f4fc6dc0272fc57bc3f5695d22c8d8c48d43c1bdc31d2da24ac5e9316f10b959e21550a2cfd755abaefd3f38123733ffcd44e99736179ac0bae52fe1f58c61775f68d32f624cd87d2feb6cb4dbdbf4373a9d66b80d12eb80cbbe95ba9303626558d899af8106ced7de5ee279a67156c0a3c331aec02e87e29957b9c0ac857cce8a96e3f1df0c20d5de2919263398f66b9d600a73211678262826d92f4bcb00a9d6eb2c2eb2a7ecc4938a4014ac4c51c552607b6dc10cd7a5bd4df68a63f60e99a4217feee85660182837c340fc514b1babb8999f22252b722718ed1b4cba7feeea9e62cf1f928561264ffccd96ba25a5b2d251034805640ec7a51cd89c8c15551954728e5c4a0fb51a2eb096f42476fa59c85cede303994fab2c2b79d77b62c0e603897a38e965fefa190cb8f5a56e41f3e5d982ec1d07f555fdc8fb2ad2f4ffe98295c150230780734aa03b45309488fd2833b8381d4757d04fa792cc07684c3779d6e7925b62e7a9c7e4a480bbcb4d6fedac1f18128d5eb739d4b69e8b21071a541c26179848912a0d4cc4ff0a781cb6b31f878f02974c4f50900ed33f113e0bb0f26468012cc533c2ca3992893bab98c90ffc870737d1e3dbd4fefb3f3cad23798bd53a8582110eaffd4d743e338d4d722f37ce1ebcc857a827e17f21b11559e1967b8d77375ab7496a26c4b0b074a869f4dba80ee390a805941102fcfd60ae53451ffdea972f2540da6e0158f526d3746ad4151bbfc38f1e51d61b972ba86463812c0757425438511fb52e822cfaf6de308b220969aa9f86e81ea726dd04aec3f9d5ac8a8f17b0f20f9d609076fc00b14bd3fe02345a027a720c6e69b4a63e5e36183dbfd0f8cc4e91bbe3c3068c619a1b1d3403aff3af1b56cc7b1739136e60067ae287c98d9e2ce149030a202ac73941a6ff0f94086685820732dd374210e88b9f4b63485d7aedd71fb847236dba844137ce0450b47164a5f527c08afd78e8f640c14d87643dd72e423cf6fbba79157dcc420566c93478dd666c41ffa31c2ecd7be1dbce9d9bdcf168d4ada263e0d0f430011c777c6b3b40b46080e53d4119b244eff42c6e69ed48279af0cf4770293edaf511a16c3963ec99669ac601b463cbf66e3fab9c15d3060e000fa88e3d05bc656a1ba70800b6c5741a4423df85e4054f05520df212d9281dc34f7fb9249ecf8c3c0f4d2c8f7acc0e83aee1d251a75f8b91adcb606c62d3ede2690a94ac2278b8bb19f63535ec4947928b83cb975772c1af82294fa24b19f01347a7152ac2c3ddc62079fcae6d7b3760bb8b4c789def8284a0c967e2a4952a1a004f5f0eacd2cc75056ccd1e5bb952dff6b0d686e0518547fa090b71c61135b66143c3902bb940381e174fb8cabadfc57cf2b221173a632bb2118164ee73e7551653239d0f26b0ab0251555276a22689f249497e1aa2fdb80c1ccb10f1c9251356172afcc8daff492210ecc2afbe7e22c86e233d33094753e2d99ebb44400299bfadba8f00742d9d1a3407043af6996354516cea8df2b32a4a812e495d44034010823b33d462803f0b1a6e0bbd39623cc963d33a0a091ca88a609129f5ca0aa07564b4ddf1cf3fda284e629a09f29850659d05d665d6239a15df892595a38396e55b60e15f582ba40f41fd33fc05b2dfdcd49d4d2c57699a0ed952941b8cad789dcf07e30b681da50407bed8c5b81c23c1b3b42c545d849a58c8a573234b62976474fcf2a8fcaeb807573f4628bc97825e7f54b1a3ad043bea287e4445e53f042c4c6a15464841ae11ff27679c65cd10dc23087e03e9e29be3c1f3a2e9cb0ae4fa6854e09b9d26f1063cbac8be17a250addbc6bd7abaae71d56e594050d5c1d7df6b7442966069fd243dd956498931f6cf7cb430a0cd69c77ff8e4607c1afe006c8d74c3f813f7b41f1087a949f426d578c92f78fae3702cc06f55f93ea35fe7b8d001c8f615ce1d1ffe8754bad06ea54d0a3112a3810bf1bc5b9ae3baa0d2ab3c099718bf98bf4820658f23fbdb337379fddaaff6303ce007a89157a5a9a8b2509cb6f2e0c72114244743184f8156393fe128396af8c48d5a74dc8946b61d66137aa6ee7d4930cf264a587fdbd7aa0c068804057b2215c264c3c77663dd4cb58f2bdc2de0b5ab112b104da9bac0d9db69a448412588863ea869d4f890eb8e4faab5827299a111b42ee9463654303cd9f4dcff5e58afe88724abcd3fe4cb3168aef9b6ceb7e9b1a6156679fd2085e6db996c6540ada72d5ea0cff41cdb9483648f9f2685347e7bc512a9d6e535978c5ed0ce38f4cd903a38b3b61ddc2d79e14080166c08fb5272b52644a0ee32396a63c02d3746844ceec71816272a612ce308778c70a75119f5b0140916fafdc97b1f9dcadb8fd0735e7c5e57c9da0c017770e01e20ea42b73f771522f9921eeb1daa7e293c702d0f61b4569b5b516fca3820d31f6dd4ceda2f9728c0cfef56256dfb1a8d61cd21a3f45f7eb98e3091e05328e13e50cdbc6843ce2d8f397f0050b564b3ebd0acbafc6510b3e86e22b2d2047f8ec5257c46db621209a2a078c9dec80cc74b327642819a2f99c7be4b2e1d2199e2f2beac3dc2e5022d6d91efdb7b76b09877ceed20947ac3cab1538083c437423814930ef96dea4c15090bc6cebea4517d208e2e824892c6e16377ee7929e61d65ad7237dd82598010f7abaf49e01ec9e6e08a82c4e853284210a8c4702df5bf12123f71a1f0f5160d6794f9c7a9279b930eea4d341380919281804a4e94872273e2e5cc539c76901209f99b730c355459134fe2c0f31cf6b4588b1d44d3ebac7719797d95bd035208afabe41e1975f751fc36aaa3407e07c6d57413529e35a7c1b38491422ef1291f6a30c0c6daf41e7058c2591f6b097d9b2edde98f1b20ce6fd5aece66bea7fa2915ce7d77023ae83c91694c5f0b737add3e40e4843a6c18f6d707c3d2c85df0299529419ac1919c5bc769faedd0f12b209ccb799f5dc915a985c0708c680aa11d7895f6e24a12d2bfd0a60807702e15814b1520e87f3409de46e5209973232d01db4272a97d90adb153e7cc27dd74f4185a29398200a656d1d9dfd683aaf39313946aea52c413519d6b3bc374cc173cff10809b8909baa02b1e5159edee8bb62eac59720ed6dcacfe763a0b7cfe322855ebf7bff78973198d6938e4ffd88c8b0d32ae740ef3e56931f09c0c29f96ab6c186380f5071cec86000f7a22df3a72d0b895de61a03eac7b5558fbe848e4f10ff02e3908077f41ad02d24fd4cd0bb6b26e7e931f92d07c7fb6398efe14d166d8636b74c50f36125536e8d4e54b8ef490f9613caac20bc9595db4415c3c2220cdde4da13c96c7fc5aec1fe4d3959b9f924f963f560c6b25f8fde46885119f57a6f7371258532c6993d273a15634e653f02b022ab922892ed90592aab7ecbc248ca426f6c781699f05b94fd9699d9193d1fac31588805eb9e9c2b0f6d07d672153267fa971b65d7919fc06fed449ec9f10cfa1765e6fe32511c5cd9964110fa2e8c31067d0aca0594a8e30930f7cad4df0852eeb8f4ee0118d291d2d6bc65e845de6259392a3a23f0ebb4b8044e9b033a0e02436c782b517fd3571f5e640fc633f9eaea7afb3e5e52d7fb7ae85002bd43280e9cb71ca103b038f5440f07ffb5c07022c63c71b7b13c5d02d95611745d5e163831b7d8405c891e99b0187be7e16ee91a4df4d51a1f1e7471a4df113b506b27d0bd739504efa54410a51805c7a0c3473591bc5e1fb407b88989972bc23efa2d26ee8732f82d458d22a48f6f87698fb230510278f71e6110d3c824c27b0ad00a8cfa8560bd7d8182893afe1d9fb2977cb50d6fa56f26d1517d2f71e0ccb42622faa7016742636fb83a916407f2d4efc89f9946cc1cd1e0d1cc3807759633cb0510e8ac665d0a45803f1c9568411eef994874bfcc7931df17395ff51241ff5c124400dfcdd47389cb656691a889fe095e97f339d36646df49e57b965d8b33e214b4f694f8f0925e03fd5f40981b9286d6e407f18f92ffc8c85f83ef8d07181312b7055d28e732a517a8521eaaaa90a3ca67de07dc28e701b85c5dbdacfd5a5008b34dfe53d6fc73f30cb18c00518041bfc57d9f64b64e8678cd4c1188676e530227175b1eef5ff44203e722278bb91f98fe8b911573c39b9f761e7f73dad7a92dbf76cec5be468770a1978606c24b0d3e522f5120b693eff54d38b1d10cd80f114153657e6e4376f300f59fecc5e7709e17b7e14fe4af20642b7ad7bfe91bb0300fd5d9db6bf4df35703fe05bff89f997c274d95ccf9bf07eb5ddb8c8b8fc9c2f48ece63e29a592ea41c2685c73cd1097a2134a4c484b2b53d6b4bb234739e22abdc8030545ac4df71a37aaefb9c7a704ce293764bbbd30c4baadbcf6c369eb0ec714758cb33b4e304652545922f487f96c6a267ffd7fc0d7c11a25d6e0429608c2f6df6ef67e6390eff059329d36ba9db3eac7bca7a8f2ca56840f6141305f2c116a3db192620c0a131bbaefac4da65c54529043350d609fcad09ade9a24402e8725b3338cfb990f458ae7a4c52ccaf53ce9ae282547a422a26827d1d3dce2274e9b51112988587a3ab714f0ef42a1d876dc39e3c112a79cae3896a33d9a13072177ee6d6fea94f01eb78f935f8c9de33b62221ba77b347b9229a334d417182e59c4a6cd9fa657cc2a8667b92f6bb818b472c5d7715b61bc5989755ee041240801037f656357e6c0fa53e6b42cee535390a6876123780b29ba90d7c558da9f12d9a37b69f6f5b3e924428e8b08fc14af078a468c81f4c3c6c1e06c277de2a620a0f69e36deb8c19cddcb73fa68c8301e1ebdf39a72ade861dc02e5ab7f8ab428cab45a4ff96e733274d6d87f1df3e42f219b2d613394a1755de67872ffbeed131a535a20e7c418741bd73ca355044fa144a7936e930279ed6e3ded182d8cc61d89de26952b776cf1d17ed04d61968d07370a8d3950445b4bd9642394f003ff14a6c73f993dc9ca33bfb497cfcb7f27a2f3f2452fead8ab31a0182e3d3b70632ed9d410f61054261e79d7a3ac90233c3b473a3576fff10fcfd468e95749cbdd2add7cff11960f67ed0ef73691934d54f99e0ee94202a349f03436c695b561a81e808338a7d39286e584ac61ee5d2d94c0c22bd24bec13e7e10ef3125e2ae98301659ef85444a286a35b4837e5a034f3f1e4acfa0ae181957571788a160dec670c479afbd10fc99caf3c58705fe8f1fa7cd599743838be1e46742adc2f33a8691dd72b5b0c3d6dc48a5d7fb4bfe2e7112ab91b325348739db3d804d56d207583091d5c455a120de60438a23ac79f773c4f3a7d3f6ba689cd73e78d8fcb11c34928e9f3e2950b59524e5c978460553925ee38e93f4a36ba9fc319a2cb9554530603f73f6d43197e47255602bdbfa9d0f42763a3d7c827fe7652119d280f8bfcb1291f0c1ef60919a98e8fd034a894a276ef544c3c726a4d7765486549002b0b6401c820c3ae13077142caa1a9cf4dfeb037b575379d09bbfdfad8e9a3a65b442a2eb998ee8036dc8ee44b19765f3ad89c68250136bdaebcd6f20a64a2a996a0ee2a2e15af6c67e71250d9fb7dc0c4f5396938174d1836f598c91312cc390edbac22082e3de6dc6c42bbfc782edb4735308a414b581425c46fa1899c43dffab7ad1470b789f3a376d8f05e382e4eef5ad2f730a4c6ca71b3dc181bfedc014a3d2b00dea01112567d0465b3e605cbca12e841543e9f39ecc126724ac802fbccaa8c0238a905a0a909ba0c4d302b30dc3b77073e601e426d5ecdcd9e991f8cb9a833291480283cb121f1bdc9f638f91033e3aa6b5c64dc4006f5aae7cfd5d5bbfac70ba40e9cb25808a0a689515f811e67e7d63cc307cc75654506f44405570f452418754d1712c04993196cab35c7ea1f2144ecc1543a2c4e1591f3b94ecd495fee1c1bb846112a5aae6cb6a19faf9fb27b33277f32daa6f82c824fbb3055507bc818f96b1ec9ef2de81da729dca128fdcda775a39fab77b88be963fb242a099ccc0ece6a91d0bf6d93df0e19f975ad7da6e9f10a546949ebd6b18180ed3825a77e1c6e15d81d01398ec10c8fd8724f52c696b34de7e26ac48375189ddd503e19ed293aa1cd6eae95bcf17f3add4b4dfa09c3433fd656e0c3dadf111b8f1143a7e22516d371c2a38e182f341290fbb5056ac9f1ea51058ee260825918bb89a393502df87d475d7fc3d36b0dc606bcf6c369c51a422d9b95752543704a8d9c2ac374271e2d61cd899fffba684b47845bda1683342d76d8ee853a801288cb933b889f1446f0ad542d89e6721fae1dcf14690775cf7b2bea19715f14e2ce869bfe48871f14cafb1ebc870c16ad964b0571772d77f785dc4d116ac3ec7dd3e6e33a316d292cf4826fc7534dd40a58496a31a6698165ed2d15d6b65f4cf65a083b233081182c75c5c7256093280baf3e6039b485c966a4adc93e968128151b789e493478216822e156940e5383997307d339c3cd0f6e6b797ed3c20efa02724897beb560224117481c8293bacccec8b5930f3debc7459fa6c8e71d9f3277b14c5fa01c4311b90c808a4fdf40dc95186163f4e28cc568c5e5acbd27b7618bd0e5c9bb7efe2219af0f6204b7497060c1d9601f9efc7f317f7c6f7081ab661059f573305d9e2fd2eb51c85ac7b45341c2cc132192bc506304df61661e833aa262d3fa75e21208e014fe42ce0846f8b16168a662a57a5b55d0d27af4237a165855fd27f122851019b4c04fb5d022c676fc313ffb296a03eef785638c893853cfc602561ad32b101fd6ad5ea0fea4d4d5a492017fcad531bc307455c1b7905de3408a9d8850ce25e8e3a1e6ed3c3306eb705fe3785cb93b49278b24fb6a96793798d2f0a057052df7591a5099f3fcbb2e0e7410709cc4bbbe1b05460812d481bb5f02b59603601e677bb528c3a003004de33da4925468554eac3cb3229f138b19a1278a83d7ab15e9be54c76e61c983702c43cffc2d2e9b91907bc8d7274aa2212996a91e1c880655f31c7e5235bd1859b5362ec0d4e1c591b9da7391a509c56d6f7a331e6e7e0e8bd781ec70faec75a6bfb9b6405e5ad2ffe2acf7ab915b986a456f16b61c3f0e1244ad90e3f92e0b892ee2608af2845ec819cb196bfb2cdbda39e30bc912d81b93a3d81947e6fddc6b4f3efb313fed09e5c0d9f6a27b1e97bb5a143474d3b51a0a41cc70485edfc31889587523d0da2943be33c2ef8f1298b0a6194efa03ac29ccc15bb12ba5b38d399585be59fb9ece3a9e74f5a92249c7da5ff28469859c0770a3a910361232dad8292a99fcd4f06d55ad29a3abb22aa9de47d58d9567572260eb019429970a087a1bc0f84db9d940b67051598e083885cf0a54dd7c7f41166e9923e928060b8c82bd127cdddeb969328bd242e5043220916323ddef3e8f09c3519aac68604cb8f6cc8a01aa514bbe678b3c2d57c6a5d69459f3c6adbcd0064007fa97d6dd95157d7281574bb25d82d45f1357627bd5426b55360c9e1c022d671d7c295d43f13f84d21247c8b1ae74a060e60e8aca8c6e9520e0b6defe58", + "public_inputs_hex": "0x15c9b404c344691839abf638a132bd083b09e98c2fadb268923013737d217ad41dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000cb80a0dfbeaeb79af355a70d8f19855c00000000000000000000000000000000915681ec51465d5a3404f61907428b7100000000000000000000000000000000666965e3fee8efc43146d3b5cc157df900000000000000000000000000000000b2039cf00b2cd6017d01a8e38e5d2d102b26ea14534f288a6affb3a0aa4953baf450f69fa48000890435a8ee45258a0a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000214ae98e2b0527277d74d7898e5a1d76041a613ee74f2db890bd6db2fd3a76e9d30329528532c70174aa6a80f652ef9fb86217d722ac4e756dac8ec699873be87009de2f6efc0e2420bebb740d52be9795045be2c08d2f61c665fd8c2946f25a70037344482bad1ff4c46d1ed3887ee2f37d4bc2dd3a1c34c3c6a0b0d75d4fa630000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } } diff --git a/circuits/bin/recursive_aggregation/c6_fold/src/main.nr b/circuits/bin/recursive_aggregation/c6_fold/src/main.nr index 932548cd7..e6ff0126d 100644 --- a/circuits/bin/recursive_aggregation/c6_fold/src/main.nr +++ b/circuits/bin/recursive_aggregation/c6_fold/src/main.nr @@ -14,11 +14,13 @@ use bb_proof_verification::{ }; use lib::configs::default::T; -/// One C6 public row: `expected_sk`, `expected_e_sm`, `ct_commitment`, `d_commitment` (see C6 leaf). -pub global C6_PUBLIC_LEN: u32 = 4; +/// One C6 public row: `expected_sk`, `expected_e_sm`, `ct_commitment`, +/// `domain_hi`, `domain_lo`, `d_commitment` (see C6 leaf). +pub global C6_PUBLIC_LEN: u32 = 6; -/// Same shape as `c3_fold`: 4 pub parameters + 4 columns of `T + 1` party slots each. -pub global C6_FOLD_PUBLIC_LEN: u32 = 4 + (4 * (T + 1)); +/// Four pub parameters + the common domain limbs + four columns of +/// `T + 1` party slots each. +pub global C6_FOLD_PUBLIC_LEN: u32 = 6 + (4 * (T + 1)); pub global C6_FOLD_PREFIX_LEN: u32 = C6_FOLD_PUBLIC_LEN - (4 * (T + 1)); @@ -33,7 +35,7 @@ fn main( acc_key_hash: pub Field, is_first_step: pub bool, slot_index: pub u32, -) -> pub ([Field; T + 1], [Field; T + 1], [Field; T + 1], [Field; T + 1]) { +) -> pub (Field, Field, [Field; T + 1], [Field; T + 1], [Field; T + 1], [Field; T + 1]) { assert(slot_index < T + 1); verify_honk_proof(inner_vk, inner_proof, c6_public_inputs, inner_key_hash); @@ -43,7 +45,14 @@ fn main( let sk_new = c6_public_inputs[0]; let esm_new = c6_public_inputs[1]; let ct_new = c6_public_inputs[2]; - let d_new = c6_public_inputs[3]; + let domain_hi = c6_public_inputs[3]; + let domain_lo = c6_public_inputs[4]; + let d_new = c6_public_inputs[5]; + + if !is_first_step { + assert(acc_public_inputs[4] == domain_hi); + assert(acc_public_inputs[5] == domain_lo); + } let mut out_sk: [Field; T + 1] = [0; T + 1]; let mut out_esm: [Field; T + 1] = [0; T + 1]; @@ -88,5 +97,5 @@ fn main( } } - (out_sk, out_esm, out_ct, out_d) + (domain_hi, domain_lo, out_sk, out_esm, out_ct, out_d) } diff --git a/circuits/bin/recursive_aggregation/c6_fold_kernel/src/main.nr b/circuits/bin/recursive_aggregation/c6_fold_kernel/src/main.nr index 6abb49fbe..593c82263 100644 --- a/circuits/bin/recursive_aggregation/c6_fold_kernel/src/main.nr +++ b/circuits/bin/recursive_aggregation/c6_fold_kernel/src/main.nr @@ -14,11 +14,13 @@ use bb_proof_verification::{ }; use lib::configs::default::T; -/// One C6 public row: `expected_sk`, `expected_e_sm`, `ct_commitment`, `d_commitment` (see C6 leaf). -pub global C6_PUBLIC_LEN: u32 = 4; +/// One C6 public row: `expected_sk`, `expected_e_sm`, `ct_commitment`, +/// `domain_hi`, `domain_lo`, `d_commitment` (see C6 leaf). +pub global C6_PUBLIC_LEN: u32 = 6; -/// Same shape as `c6_fold`: 4 pub parameters + 4 columns of `T + 1` party slots each. -pub global C6_FOLD_PUBLIC_LEN: u32 = 4 + (4 * (T + 1)); +/// Same shape as `c6_fold`: 4 pub parameters + the common domain limbs + +/// 4 columns of `T + 1` party slots each. +pub global C6_FOLD_PUBLIC_LEN: u32 = 6 + (4 * (T + 1)); fn main( inner_vk: UltraHonkVerificationKey, @@ -31,7 +33,7 @@ fn main( acc_key_hash: pub Field, is_first_step: pub bool, slot_index: pub u32, -) -> pub ([Field; T + 1], [Field; T + 1], [Field; T + 1], [Field; T + 1]) { +) -> pub (Field, Field, [Field; T + 1], [Field; T + 1], [Field; T + 1], [Field; T + 1]) { assert(slot_index < T + 1); verify_honk_proof(inner_vk, inner_proof, c6_public_inputs, inner_key_hash); @@ -44,7 +46,9 @@ fn main( let sk_new = c6_public_inputs[0]; let esm_new = c6_public_inputs[1]; let ct_new = c6_public_inputs[2]; - let d_new = c6_public_inputs[3]; + let domain_hi = c6_public_inputs[3]; + let domain_lo = c6_public_inputs[4]; + let d_new = c6_public_inputs[5]; let mut out_sk: [Field; T + 1] = [0; T + 1]; let mut out_esm: [Field; T + 1] = [0; T + 1]; @@ -66,5 +70,5 @@ fn main( } } - (out_sk, out_esm, out_ct, out_d) + (domain_hi, domain_lo, out_sk, out_esm, out_ct, out_d) } diff --git a/circuits/bin/recursive_aggregation/decryption_aggregator/src/main.nr b/circuits/bin/recursive_aggregation/decryption_aggregator/src/main.nr index 67f6a513b..fce82e7d1 100644 --- a/circuits/bin/recursive_aggregation/decryption_aggregator/src/main.nr +++ b/circuits/bin/recursive_aggregation/decryption_aggregator/src/main.nr @@ -21,8 +21,9 @@ use lib::configs::default::T; use lib::math::commitments::compute_vk_hash; use lib::math::committee_hash::committee_hash_limbs; -/// Must match `c6_fold::C6_FOLD_PUBLIC_LEN`: pub params + flattened `(out_sk, out_esm, out_ct, out_d)`. -pub global C6_FOLD_PUBLIC_LEN: u32 = 4 + (4 * (T + 1)); +/// Must match `c6_fold::C6_FOLD_PUBLIC_LEN`: pub params, common domain limbs, +/// and flattened `(out_sk, out_esm, out_ct, out_d)`. +pub global C6_FOLD_PUBLIC_LEN: u32 = 6 + (4 * (T + 1)); pub global C6_FOLD_PREFIX_LEN: u32 = C6_FOLD_PUBLIC_LEN - (4 * (T + 1)); @@ -53,6 +54,8 @@ fn main( committee_members: [Field; N_PARTIES], committee_hash_hi: pub Field, committee_hash_lo: pub Field, + domain_hi: pub Field, + domain_lo: pub Field, ) -> pub (Field, [Field; T + 1], [Field; T + 1], [Field; T + 1], [Field; MAX_MSG_NON_ZERO_COEFFS]) { let (expected_hi, expected_lo) = committee_hash_limbs(committee_members); assert(expected_hi == committee_hash_hi); @@ -61,6 +64,11 @@ fn main( verify_honk_proof_non_zk(c6_fold_vk, c6_fold_proof, c6_fold_public, c6_fold_key_hash); verify_honk_proof_non_zk(c7_vk, c7_proof, c7_public, c7_key_hash); + // The C6 fold only accepts secret-bearing leaf proofs carrying one common + // domain. Expose that domain in the final EVM proof for on-chain checking. + assert(c6_fold_public[4] == domain_hi); + assert(c6_fold_public[5] == domain_lo); + // Cross-circuit C6 to C7: each folded `d_commitment` column entry must match `c7_public[i]` // (leading `T + 1` words of `c7_public`). for i in 0..(T + 1) { diff --git a/circuits/bin/threshold/share_decryption/src/main.nr b/circuits/bin/threshold/share_decryption/src/main.nr index da2ad3cac..de7f03c9b 100644 --- a/circuits/bin/threshold/share_decryption/src/main.nr +++ b/circuits/bin/threshold/share_decryption/src/main.nr @@ -18,6 +18,8 @@ fn main( expected_sk_commitment: pub Field, expected_e_sm_commitment: pub Field, ct_commitment: pub Field, + domain_hi: pub Field, + domain_lo: pub Field, ct0: [Polynomial; L], ct1: [Polynomial; L], sk: [Polynomial; L], @@ -27,6 +29,10 @@ fn main( d: [Polynomial; L], d_native_trunc: [Polynomial; L], ) -> pub Field { + // The domain is intentionally not part of the BFV arithmetic. Making it a + // public input of this secret-bearing proof cryptographically authorizes + // this exact E3 context; recursive aggregation must preserve both limbs. + let _ = (domain_hi, domain_lo); let share_decryption: ShareDecryption = ShareDecryption::new( THRESHOLD_SHARE_DECRYPTION_CONFIGS, expected_sk_commitment, diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index b237b8d11..b88a566c8 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -812,9 +812,26 @@ impl CiphernodeBuilder { backend.ensure_installed().await?; let _signer = provider_cache.ensure_signer().await?; + let mut interfold_addresses = HashMap::new(); + for chain in self.chains.iter().filter(|c| c.enabled.unwrap_or(true)) { + let provider = provider_cache.ensure_read_provider(chain).await?; + let chain_id = provider.chain_id(); + validate_chain_id(chain, chain_id)?; + interfold_addresses.insert(chain_id, chain.contracts.interfold.address()?); + } + for chain in self.chains.iter().filter(|c| !c.enabled.unwrap_or(true)) { + if let Some(chain_id) = chain.chain_id { + interfold_addresses.insert(chain_id, chain.contracts.interfold.address()?); + } + } + info!("Setting up ThresholdKeyshareExtension"); - e3_builder = - e3_builder.with(ThresholdKeyshareExtension::create(bus, &self.cipher, addr)); + e3_builder = e3_builder.with(ThresholdKeyshareExtension::create( + bus, + &self.cipher, + addr, + interfold_addresses, + )); info!("Setting up ZK actors"); setup_zk_actors( diff --git a/crates/committee-hash/Cargo.toml b/crates/committee-hash/Cargo.toml index da2ab4be8..6a2497dc4 100644 --- a/crates/committee-hash/Cargo.toml +++ b/crates/committee-hash/Cargo.toml @@ -3,9 +3,10 @@ name = "e3-committee-hash" version.workspace = true edition.workspace = true license.workspace = true -description = "Canonical committee hash for DKG / decryption aggregator proofs" +description = "Canonical EVM domain hashes for DKG / decryption aggregator proofs" repository = "https://github.com/theinterfold/interfold/crates/committee-hash" [dependencies] alloy = { workspace = true } hex = { workspace = true } +serde = { workspace = true } diff --git a/crates/committee-hash/src/lib.rs b/crates/committee-hash/src/lib.rs index 798ecd244..03b207ba3 100644 --- a/crates/committee-hash/src/lib.rs +++ b/crates/committee-hash/src/lib.rs @@ -4,10 +4,16 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Canonical committee hash for DKG / decryption aggregator proofs. -//! Must match `CommitteeHashLib.sol` (`keccak256(abi.encodePacked(addresses))`). +//! Canonical EVM hashes for DKG / decryption aggregator proofs. +//! Committee hashing must match `CommitteeHashLib.sol` +//! (`keccak256(abi.encodePacked(addresses))`). Decryption-domain hashing must +//! match `InterfoldPricing.decryptionDomain`. -use alloy::primitives::{keccak256, Address, B256}; +use alloy::{ + primitives::{keccak256, Address, B256, U256}, + sol_types::SolValue, +}; +use serde::{Deserialize, Serialize}; /// Hi/lo limbs of `keccak256(abi.encodePacked(addresses))` for Noir public inputs. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -16,6 +22,22 @@ pub struct CommitteeHashLimbs { pub lo: B256, } +/// Stable, non-secret context needed to bind a C6 decryption-share proof to +/// the E3 that authorized it. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct DecryptionDomainContext { + pub interfold_address: Address, + pub committee_hash: B256, + pub committee_public_key: B256, +} + +/// Hi/lo 128-bit limbs of the decryption-domain hash for Noir public inputs. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DecryptionDomainLimbs { + pub hi: u128, + pub lo: u128, +} + /// `keccak256(abi.encodePacked(addresses))` for the ordered on-chain committee. pub fn hash_committee_addresses(addresses: &[Address]) -> B256 { let packed: Vec = addresses @@ -49,6 +71,56 @@ pub fn committee_hash_field_hex(addresses: &[Address]) -> (String, String) { (field_hex_from_b256(limbs.hi), field_hex_from_b256(limbs.lo)) } +/// Compute the E3 decryption domain: +/// +/// `keccak256(abi.encode(chainId, interfold, e3Id, committeeHash, +/// ciphertextOutputHash, committeePublicKey))`. +/// +/// The Interfold address prevents cross-deployment replay. The remaining +/// fields prevent replay across chains, E3s, committees, ciphertexts, or DKG +/// keys within one deployment. +pub fn hash_decryption_domain( + chain_id: u64, + e3_id: U256, + context: DecryptionDomainContext, + ciphertext_output_hash: B256, +) -> B256 { + keccak256( + ( + U256::from(chain_id), + context.interfold_address, + e3_id, + context.committee_hash, + ciphertext_output_hash, + context.committee_public_key, + ) + .abi_encode(), + ) +} + +/// Split a decryption-domain hash into two 128-bit Noir field elements. +pub fn split_decryption_domain(hash: B256) -> DecryptionDomainLimbs { + DecryptionDomainLimbs { + hi: u128::from_be_bytes(hash[..16].try_into().expect("16-byte high limb")), + lo: u128::from_be_bytes(hash[16..].try_into().expect("16-byte low limb")), + } +} + +/// Hash and split the E3 decryption domain in one step. +pub fn decryption_domain_limbs( + chain_id: u64, + e3_id: U256, + context: DecryptionDomainContext, + ciphertext_output_hash: B256, +) -> DecryptionDomainLimbs { + split_decryption_domain(hash_decryption_domain( + chain_id, + e3_id, + context, + ciphertext_output_hash, + )) +} + fn field_hex_from_b256(value: B256) -> String { format!("0x{}", hex::encode(value)) } @@ -89,4 +161,48 @@ mod tests { expected_lo[16..].copy_from_slice(&hash.0[16..]); assert_eq!(limbs.lo.0, expected_lo); } + + #[test] + fn decryption_domain_matches_solidity_abi_layout() { + let context = DecryptionDomainContext { + interfold_address: address!("0x1111111111111111111111111111111111111111"), + committee_hash: B256::repeat_byte(0x22), + committee_public_key: B256::repeat_byte(0x44), + }; + let ciphertext_hash = B256::repeat_byte(0x33); + let chain_id = 31_337u64; + let e3_id = U256::from(7); + + let mut encoded = [0u8; 32 * 6]; + U256::from(chain_id) + .to_be_bytes_vec() + .iter() + .enumerate() + .for_each(|(index, byte)| encoded[index] = *byte); + encoded[32 + 12..64].copy_from_slice(context.interfold_address.as_slice()); + e3_id + .to_be_bytes_vec() + .iter() + .enumerate() + .for_each(|(index, byte)| encoded[64 + index] = *byte); + encoded[96..128].copy_from_slice(context.committee_hash.as_slice()); + encoded[128..160].copy_from_slice(ciphertext_hash.as_slice()); + encoded[160..192].copy_from_slice(context.committee_public_key.as_slice()); + + let expected = keccak256(encoded); + assert_eq!( + hash_decryption_domain(chain_id, e3_id, context, ciphertext_hash), + expected + ); + + let limbs = split_decryption_domain(expected); + assert_eq!( + limbs.hi, + u128::from_be_bytes(expected[..16].try_into().unwrap()) + ); + assert_eq!( + limbs.lo, + u128::from_be_bytes(expected[16..].try_into().unwrap()) + ); + } } diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml index 9a8ba5177..9f4b30f51 100644 --- a/crates/events/Cargo.toml +++ b/crates/events/Cargo.toml @@ -27,6 +27,7 @@ thiserror = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } e3-crypto = { workspace = true } +e3-committee-hash = { workspace = true } e3-trbfv = { workspace = true } e3-utils = { workspace = true } e3-fhe-params = { workspace = true } diff --git a/crates/events/src/interfold_event/compute_request/zk.rs b/crates/events/src/interfold_event/compute_request/zk.rs index c82263147..cefac7d85 100644 --- a/crates/events/src/interfold_event/compute_request/zk.rs +++ b/crates/events/src/interfold_event/compute_request/zk.rs @@ -7,6 +7,7 @@ use crate::{Proof, SignedProofPayload}; use alloy::primitives::Address; use derivative::Derivative; +use e3_committee_hash::DecryptionDomainContext; use e3_crypto::SensitiveBytes; use e3_fhe_params::BfvPreset; use e3_utils::utility_types::ArcBytes; @@ -368,6 +369,8 @@ pub struct ThresholdShareDecryptionProofRequest { pub es_poly_sum: Vec, /// Computed decryption share polynomials, one per output index. pub d_share_bytes: Vec, + /// Stable E3 context cryptographically bound into every C6 proof. + pub decryption_domain: DecryptionDomainContext, /// BFV preset for parameter resolution. pub params_preset: BfvPreset, /// Committee size for per-committee circuit artifact resolution. diff --git a/crates/events/src/interfold_event/proof.rs b/crates/events/src/interfold_event/proof.rs index a53d41d3d..c3bfbc4bc 100644 --- a/crates/events/src/interfold_event/proof.rs +++ b/crates/events/src/interfold_event/proof.rs @@ -316,12 +316,14 @@ mod tests { #[test] fn extract_c6_d_commitment_after_pub_inputs() { - // C6: 3 public inputs + 1 output (`d_commitment` at tail). - let mut signals = vec![0u8; 128]; + // C6: 5 public inputs + 1 output (`d_commitment` at tail). + let mut signals = vec![0u8; 192]; signals[0..32].copy_from_slice(&[0x11; 32]); // expected_sk_commitment signals[32..64].copy_from_slice(&[0x22; 32]); // expected_e_sm_commitment signals[64..96].copy_from_slice(&[0x33; 32]); // ct_commitment - signals[96..128].copy_from_slice(&[0x77; 32]); // d_commitment + signals[96..128].copy_from_slice(&[0x44; 32]); // domain_hi + signals[128..160].copy_from_slice(&[0x55; 32]); // domain_lo + signals[160..192].copy_from_slice(&[0x77; 32]); // d_commitment let proof = make_proof(CircuitName::ThresholdShareDecryption, &signals); assert_eq!(&*proof.extract_output("d_commitment").unwrap(), &[0x77; 32]); @@ -329,11 +331,13 @@ mod tests { #[test] fn extract_c6_public_inputs() { - let mut signals = vec![0u8; 128]; + let mut signals = vec![0u8; 192]; signals[0..32].copy_from_slice(&[0x11; 32]); signals[32..64].copy_from_slice(&[0x22; 32]); signals[64..96].copy_from_slice(&[0x33; 32]); - signals[96..128].copy_from_slice(&[0x77; 32]); + signals[96..128].copy_from_slice(&[0x44; 32]); + signals[128..160].copy_from_slice(&[0x55; 32]); + signals[160..192].copy_from_slice(&[0x77; 32]); let proof = make_proof(CircuitName::ThresholdShareDecryption, &signals); assert_eq!( @@ -345,6 +349,8 @@ mod tests { &[0x22; 32] ); assert_eq!(&*proof.extract_input("ct_commitment").unwrap(), &[0x33; 32]); + assert_eq!(&*proof.extract_input("domain_hi").unwrap(), &[0x44; 32]); + assert_eq!(&*proof.extract_input("domain_lo").unwrap(), &[0x55; 32]); } #[test] diff --git a/crates/keyshare/Cargo.toml b/crates/keyshare/Cargo.toml index 00ddd8c6d..afc0bfa16 100644 --- a/crates/keyshare/Cargo.toml +++ b/crates/keyshare/Cargo.toml @@ -12,6 +12,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } e3-config = { workspace = true } +e3-committee-hash = { workspace = true } e3-data = { workspace = true } e3-crypto = { workspace = true } e3-events = { workspace = true } @@ -22,6 +23,7 @@ e3-request = { workspace = true } e3-trbfv = { workspace = true } e3-utils = { workspace = true } e3-zk-helpers = { workspace = true } +alloy = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } rand = { workspace = true } diff --git a/crates/keyshare/src/ext.rs b/crates/keyshare/src/ext.rs index 2c5dcecd4..ab03f95d1 100644 --- a/crates/keyshare/src/ext.rs +++ b/crates/keyshare/src/ext.rs @@ -9,6 +9,7 @@ use crate::{ ThresholdKeyshareState, }; use actix::Actor; +use alloy::primitives::Address; use anyhow::{anyhow, Result}; use async_trait::async_trait; use e3_crypto::Cipher; @@ -17,20 +18,27 @@ use e3_events::{prelude::*, BusHandle, EType, InterfoldEvent, InterfoldEventData use e3_request::{E3Context, E3ContextSnapshot, E3Extension, META_KEY}; use crate::KeyshareState; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; pub struct ThresholdKeyshareExtension { bus: BusHandle, cipher: Arc, address: String, + interfold_addresses: HashMap, } impl ThresholdKeyshareExtension { - pub fn create(bus: &BusHandle, cipher: &Arc, address: &str) -> Box { + pub fn create( + bus: &BusHandle, + cipher: &Arc, + address: &str, + interfold_addresses: HashMap, + ) -> Box { Box::new(Self { bus: bus.clone(), cipher: cipher.to_owned(), address: address.to_owned(), + interfold_addresses, }) } } @@ -51,6 +59,17 @@ impl E3Extension for ThresholdKeyshareExtension { } let e3_id = data.clone().e3_id; + let Some(interfold_address) = self.interfold_addresses.get(&e3_id.chain_id()).copied() + else { + self.bus.err( + EType::KeyGeneration, + anyhow!( + "Interfold address not configured for chain {}", + e3_id.chain_id() + ), + ); + return; + }; let party_id = data.clone().party_id; let Some(meta) = ctx.get_dependency(META_KEY) else { self.bus @@ -80,6 +99,7 @@ impl E3Extension for ThresholdKeyshareExtension { .params_preset .dkg_counterpart() .unwrap_or(meta.params_preset), + interfold_address, }) .start() .into(), @@ -112,6 +132,16 @@ impl E3Extension for ThresholdKeyshareExtension { .params_preset .dkg_counterpart() .unwrap_or(meta.params_preset); + let interfold_address = self + .interfold_addresses + .get(&snapshot.e3_id.chain_id()) + .copied() + .ok_or_else(|| { + anyhow!( + "Interfold address not configured for chain {}", + snapshot.e3_id.chain_id() + ) + })?; // Construct from snapshot let value = ThresholdKeyshare::new(ThresholdKeyshareParams { @@ -119,6 +149,7 @@ impl E3Extension for ThresholdKeyshareExtension { cipher: self.cipher.clone(), state, share_enc_preset, + interfold_address, }) .start() .into(); diff --git a/crates/keyshare/src/threshold_keyshare/actor.rs b/crates/keyshare/src/threshold_keyshare/actor.rs index 4d18c9066..f6c007d06 100644 --- a/crates/keyshare/src/threshold_keyshare/actor.rs +++ b/crates/keyshare/src/threshold_keyshare/actor.rs @@ -5,6 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use actix::prelude::*; +use alloy::primitives::Address; use anyhow::{anyhow, bail, Context, Result}; use e3_crypto::{Cipher, SensitiveBytes}; use e3_data::Persistable; @@ -111,6 +112,7 @@ pub struct ThresholdKeyshareParams { pub cipher: Arc, pub state: Persistable, pub share_enc_preset: BfvPreset, + pub interfold_address: Address, } /// Ephemeral bridge data for operations already represented by the persisted keyshare phase. @@ -143,6 +145,7 @@ pub struct ThresholdKeyshare { decryption_key_shared_collector: Option>, state: Persistable, share_enc_preset: BfvPreset, + interfold_address: Address, pending: PendingKeyshareWork, } @@ -156,6 +159,7 @@ impl ThresholdKeyshare { decryption_key_shared_collector: None, state: params.state, share_enc_preset: params.share_enc_preset, + interfold_address: params.interfold_address, pending: PendingKeyshareWork::default(), } } diff --git a/crates/keyshare/src/threshold_keyshare/effects/create_decryption_share.rs b/crates/keyshare/src/threshold_keyshare/effects/create_decryption_share.rs index 25544eba8..fd2051462 100644 --- a/crates/keyshare/src/threshold_keyshare/effects/create_decryption_share.rs +++ b/crates/keyshare/src/threshold_keyshare/effects/create_decryption_share.rs @@ -189,6 +189,9 @@ impl ThresholdKeyshare { .aggregated_pk .clone() .ok_or_else(|| anyhow!("Aggregated public key not available for C6 proof"))?; + let decryption_domain = state + .decryption_domain + .ok_or_else(|| anyhow!("E3 decryption domain not available for C6 proof"))?; let threshold_preset = self .share_enc_preset @@ -222,6 +225,7 @@ impl ThresholdKeyshare { sk_poly_sum: decrypting.sk_poly_sum, es_poly_sum: decrypting.es_poly_sum, d_share_bytes: d_share_poly.clone(), + decryption_domain, params_preset: threshold_preset, committee_size, }, diff --git a/crates/keyshare/src/threshold_keyshare/effects/route_events.rs b/crates/keyshare/src/threshold_keyshare/effects/route_events.rs index 34a7a383c..9cc7902fe 100644 --- a/crates/keyshare/src/threshold_keyshare/effects/route_events.rs +++ b/crates/keyshare/src/threshold_keyshare/effects/route_events.rs @@ -16,9 +16,16 @@ impl Handler for ThresholdKeyshare { self.notify_sync(ctx, TypedEvent::new(data, ec)) } InterfoldEventData::PublicKeyAggregated(data) => { + let committee_hash = + e3_committee_hash::hash_committee_addresses(&data.committee_addresses); let pk = ArcBytes::from_bytes(&data.pubkey); let _ = self.state.try_mutate(&ec, |mut s| { s.aggregated_pk = Some(pk); + s.decryption_domain = Some(e3_committee_hash::DecryptionDomainContext { + interfold_address: self.interfold_address, + committee_hash, + committee_public_key: data.pk_commitment.into(), + }); Ok(s) }); } diff --git a/crates/keyshare/src/threshold_keyshare/state.rs b/crates/keyshare/src/threshold_keyshare/state.rs index c6457eedf..3109d8ebf 100644 --- a/crates/keyshare/src/threshold_keyshare/state.rs +++ b/crates/keyshare/src/threshold_keyshare/state.rs @@ -12,6 +12,7 @@ //! synchronous data and transition logic, which makes it directly unit-testable. use anyhow::{anyhow, Result}; +use e3_committee_hash::DecryptionDomainContext; use e3_crypto::SensitiveBytes; use e3_events::{CiphernodeSelected, E3id, EncryptionKey, PartyId, SignedProofPayload}; use e3_trbfv::{ @@ -190,6 +191,9 @@ pub struct ThresholdKeyshareState { pub params: ArcBytes, /// Aggregated public key bytes, captured from PublicKeyAggregated event for C6 proof. pub aggregated_pk: Option, + /// Public E3 context captured with the aggregated key and bound into every + /// C6 proof so the final decryption proof cannot be replayed elsewhere. + pub decryption_domain: Option, pub expelled_parties: HashSet, /// Honest party IDs in deterministic ascending order (`BTreeSet` guarantees this). /// Downstream proof circuits index parties by position in this sorted set. @@ -223,6 +227,7 @@ impl ThresholdKeyshareState { threshold_n, params, aggregated_pk: None, + decryption_domain: None, expelled_parties: HashSet::new(), honest_parties: None, dkg_started_at_unix_secs: Some(now_unix_secs()), diff --git a/crates/keyshare/src/threshold_keyshare/tests.rs b/crates/keyshare/src/threshold_keyshare/tests.rs index 8cef1ff68..e66470191 100644 --- a/crates/keyshare/src/threshold_keyshare/tests.rs +++ b/crates/keyshare/src/threshold_keyshare/tests.rs @@ -7,6 +7,7 @@ use super::*; use crate::actors::decryption_key_shared_collector::DecryptionKeySharedCollectionFailed; use actix::{Actor, Addr, Handler}; +use alloy::primitives::Address; use anyhow::Result; use e3_crypto::Cipher; use e3_data::{AutoPersist, DataStore, InMemStore, Persistable, Repository}; @@ -64,6 +65,7 @@ async fn start_actor() -> Result<( cipher: Arc::new(Cipher::from_password("test-password").await?), state: test_state(), share_enc_preset: DEFAULT_BFV_PRESET, + interfold_address: Address::ZERO, }) .start(); diff --git a/crates/multithread/Cargo.toml b/crates/multithread/Cargo.toml index 822de62b6..38bcc4eb7 100644 --- a/crates/multithread/Cargo.toml +++ b/crates/multithread/Cargo.toml @@ -8,9 +8,11 @@ repository.workspace = true [dependencies] actix = { workspace = true } +alloy = { workspace = true } anyhow = { workspace = true } bincode = { workspace = true } e3-bfv-client = { workspace = true } +e3-committee-hash = { workspace = true } e3-data = { workspace = true } e3-fhe-params = { workspace = true } e3-trbfv = { workspace = true } diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 60f9b17bb..394520070 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -17,6 +17,7 @@ use crate::TaskPool; use crate::TaskTimeouts; use actix::prelude::*; use actix::{Actor, Handler}; +use alloy::primitives::keccak256; use anyhow::Result; use e3_crypto::Cipher; use e3_events::trap_fut; @@ -418,6 +419,12 @@ fn handle_threshold_share_decryption_proof( let bb_work_base = zk_bb_work_id(&request); let artifacts_dir = prover.resolve_artifacts_dir(req.params_preset, req.committee_size.as_str()); + let numeric_e3_id = request.e3_id.clone().try_into().map_err(|e| { + make_zk_error( + &request, + format!("invalid numeric E3 id for decryption domain: {e}"), + ) + })?; for i in 0..num_indices { // Deserialize ciphertext @@ -442,6 +449,12 @@ fn handle_threshold_share_decryption_proof( let d_share_poly = try_poly_pb_from_bytes(&req.d_share_bytes[i], &threshold_params) .map_err(|e| make_zk_error(&request, format!("d_share[{}] deserialize: {}", i, e)))?; let d_share = CrtPolynomial::from_fhe_polynomial(&d_share_poly); + let domain = e3_committee_hash::decryption_domain_limbs( + request.e3_id.chain_id(), + numeric_e3_id, + req.decryption_domain, + keccak256(&req.ciphertext_bytes[i][..]), + ); // Build circuit data let circuit_data = e3_zk_helpers::threshold::share_decryption::ShareDecryptionCircuitData { @@ -450,6 +463,8 @@ fn handle_threshold_share_decryption_proof( s: s.clone(), e, d_share, + domain_hi: domain.hi, + domain_lo: domain.lo, }; // Generate proof diff --git a/crates/zk-helpers/src/circuits/output_layout.rs b/crates/zk-helpers/src/circuits/output_layout.rs index 7131792cb..f04833f5f 100644 --- a/crates/zk-helpers/src/circuits/output_layout.rs +++ b/crates/zk-helpers/src/circuits/output_layout.rs @@ -115,6 +115,8 @@ pub const THRESHOLD_SHARE_DECRYPTION_INPUTS: &[OutputField] = &[ f("expected_sk_commitment"), f("expected_e_sm_commitment"), f("ct_commitment"), + f("domain_hi"), + f("domain_lo"), ]; /// C3 — Share encryption public return (`-> pub Field`). @@ -319,12 +321,14 @@ mod tests { let layout = CircuitOutputLayout::Fixed { fields: THRESHOLD_SHARE_DECRYPTION_OUTPUTS, }; - // C6: 3 public inputs + 1 output = 128 bytes - let mut signals = vec![0u8; 128]; + // C6: 5 public inputs + 1 output = 192 bytes + let mut signals = vec![0u8; 192]; signals[0..32].copy_from_slice(&[0x11; 32]); signals[32..64].copy_from_slice(&[0x22; 32]); signals[64..96].copy_from_slice(&[0x33; 32]); - signals[96..128].copy_from_slice(&[0x77; 32]); + signals[96..128].copy_from_slice(&[0x44; 32]); + signals[128..160].copy_from_slice(&[0x55; 32]); + signals[160..192].copy_from_slice(&[0x77; 32]); assert_eq!( layout.extract_field(&signals, "d_commitment").unwrap(), @@ -362,10 +366,12 @@ mod tests { let layout = CircuitInputLayout::Fixed { fields: THRESHOLD_SHARE_DECRYPTION_INPUTS, }; - let mut signals = vec![0u8; 96]; + let mut signals = vec![0u8; 160]; signals[0..32].copy_from_slice(&[0x11; 32]); signals[32..64].copy_from_slice(&[0x22; 32]); signals[64..96].copy_from_slice(&[0x33; 32]); + signals[96..128].copy_from_slice(&[0x44; 32]); + signals[128..160].copy_from_slice(&[0x55; 32]); assert_eq!( layout @@ -383,6 +389,14 @@ mod tests { layout.extract_field(&signals, "ct_commitment").unwrap(), &[0x33; 32] ); + assert_eq!( + layout.extract_field(&signals, "domain_hi").unwrap(), + &[0x44; 32] + ); + assert_eq!( + layout.extract_field(&signals, "domain_lo").unwrap(), + &[0x55; 32] + ); } #[test] diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs index 54a742956..6e6e97244 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs @@ -27,6 +27,9 @@ pub struct ShareDecryptionCircuitData { pub s: CrtPolynomial, pub e: CrtPolynomial, pub d_share: CrtPolynomial, + /// High and low 128-bit limbs of the E3 decryption domain. + pub domain_hi: u128, + pub domain_lo: u128, } impl Circuit for ShareDecryptionCircuit { diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index b6ed723cb..4313d5226 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -132,6 +132,8 @@ pub struct Inputs { pub expected_sk_commitment: BigInt, pub expected_e_sm_commitment: BigInt, pub ct_commitment: BigInt, + pub domain_hi: BigInt, + pub domain_lo: BigInt, } impl Computation for Configs { @@ -369,6 +371,8 @@ impl Computation for Inputs { expected_sk_commitment, expected_e_sm_commitment, ct_commitment, + domain_hi: BigInt::from(data.domain_hi), + domain_lo: BigInt::from(data.domain_lo), }) } @@ -384,6 +388,8 @@ impl Computation for Inputs { let expected_sk_commitment = self.expected_sk_commitment.to_string(); let expected_e_sm_commitment = self.expected_e_sm_commitment.to_string(); let ct_commitment = self.ct_commitment.to_string(); + let domain_hi = self.domain_hi.to_string(); + let domain_lo = self.domain_lo.to_string(); let json = serde_json::json!({ "ct0": ct0, @@ -397,6 +403,8 @@ impl Computation for Inputs { "expected_sk_commitment": expected_sk_commitment, "expected_e_sm_commitment": expected_e_sm_commitment, "ct_commitment": ct_commitment, + "domain_hi": domain_hi, + "domain_lo": domain_lo, }); Ok(json) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs index 016af8bf2..cea27069a 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs @@ -241,6 +241,8 @@ impl ShareDecryptionCircuitData { s: CrtPolynomial::from_fhe_polynomial(&sk_poly_sum), e: CrtPolynomial::from_fhe_polynomial(&es_poly_sum), d_share: CrtPolynomial::from_fhe_polynomial(&d_share_rns), + domain_hi: 1, + domain_lo: 2, }) } } diff --git a/crates/zk-prover/src/circuits/aggregation/c6_accumulator.rs b/crates/zk-prover/src/circuits/aggregation/c6_accumulator.rs index 91a343356..984a65da6 100644 --- a/crates/zk-prover/src/circuits/aggregation/c6_accumulator.rs +++ b/crates/zk-prover/src/circuits/aggregation/c6_accumulator.rs @@ -22,11 +22,12 @@ use serde::Serialize; /// `total_slots` = `T + 1` (one slot per party index in the C6 leaf layout). fn c6_fold_public_input_field_count(total_slots: usize) -> usize { - 4 + 4 * total_slots + 6 + 4 * total_slots } -/// Public-signal layout of `c6_fold`: 4-field prefix, then 4-field-wide per-slot tail. -const C6_FOLD_PREFIX_LEN: usize = 4; +/// Public-signal layout of `c6_fold`: four fold parameters, two common domain +/// limbs, then a four-field-wide per-slot tail. +const C6_FOLD_PREFIX_LEN: usize = 6; const C6_FOLD_SLOT_WIDTH: usize = 4; struct C6FoldVks { @@ -110,7 +111,7 @@ fn generate_c6_fold_kernel_genesis_proof( Ok(proof) } -fn threshold_share_decryption_inner_public_inputs(proof: &Proof) -> Result<[String; 4], ZkError> { +fn threshold_share_decryption_inner_public_inputs(proof: &Proof) -> Result<[String; 6], ZkError> { if proof.circuit != CircuitName::ThresholdShareDecryption { return Err(ZkError::InvalidInput(format!( "expected ThresholdShareDecryption inner proof, got {}", @@ -122,6 +123,8 @@ fn threshold_share_decryption_inner_public_inputs(proof: &Proof) -> Result<[Stri extract_single_field(proof, "input", field_keys::EXPECTED_SK_COMMITMENT, ctx)?, extract_single_field(proof, "input", field_keys::EXPECTED_E_SM_COMMITMENT, ctx)?, extract_single_field(proof, "input", field_keys::CT_COMMITMENT, ctx)?, + extract_single_field(proof, "input", field_keys::DOMAIN_HI, ctx)?, + extract_single_field(proof, "input", field_keys::DOMAIN_LO, ctx)?, extract_single_field(proof, "output", field_keys::D_COMMITMENT, ctx)?, ]) } @@ -130,7 +133,7 @@ fn threshold_share_decryption_inner_public_inputs(proof: &Proof) -> Result<[Stri struct C6FoldStepInput { inner_vk: Vec, inner_proof: Vec, - c6_public_inputs: [String; 4], + c6_public_inputs: [String; 6], acc_vk: Vec, acc_proof: Vec, acc_public_inputs: Vec, @@ -193,7 +196,7 @@ fn generate_c6_fold_step_with_vks( } else { let p = prior_fold.expect("prior_fold required when is_first_step is false"); let acc_pi = parse_c6_fold_public_field_strings(p)?; - let prior_slots = (acc_pi.len() - 4) / 4; + let prior_slots = (acc_pi.len() - C6_FOLD_PREFIX_LEN) / C6_FOLD_SLOT_WIDTH; if prior_slots == 0 { return Err(ZkError::InvalidInput( "c6_fold proof implies zero slots".into(), diff --git a/crates/zk-prover/src/circuits/aggregation/helpers.rs b/crates/zk-prover/src/circuits/aggregation/helpers.rs index 544ff0310..97cfb3c8a 100644 --- a/crates/zk-prover/src/circuits/aggregation/helpers.rs +++ b/crates/zk-prover/src/circuits/aggregation/helpers.rs @@ -26,6 +26,8 @@ pub mod field_keys { pub const EXPECTED_SK_COMMITMENT: &str = "expected_sk_commitment"; pub const EXPECTED_E_SM_COMMITMENT: &str = "expected_e_sm_commitment"; pub const CT_COMMITMENT: &str = "ct_commitment"; + pub const DOMAIN_HI: &str = "domain_hi"; + pub const DOMAIN_LO: &str = "domain_lo"; pub const D_COMMITMENT: &str = "d_commitment"; } diff --git a/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs b/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs index 9b321a676..a92ffb1f4 100644 --- a/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs +++ b/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs @@ -553,6 +553,8 @@ struct DecryptionAggregatorWitness { committee_members: Vec, committee_hash_hi: String, committee_hash_lo: String, + domain_hi: String, + domain_lo: String, } /// Prove [`CircuitName::DecryptionAggregator`] for each job (C6 fold + C7), with @@ -608,11 +610,18 @@ pub fn prove_decryption_aggregation_jobs( &format!("{e3_id}-c6fold-{i}"), artifacts_dir, )?; + let c6_fold_public = proof_public_field_strings(&c6_fold)?; + let domain_hi = c6_fold_public.get(4).cloned().ok_or_else(|| { + ZkError::InvalidInput("C6 fold proof is missing domain_hi at public input 4".into()) + })?; + let domain_lo = c6_fold_public.get(5).cloned().ok_or_else(|| { + ZkError::InvalidInput("C6 fold proof is missing domain_lo at public input 5".into()) + })?; let witness = DecryptionAggregatorWitness { c6_fold_vk: c6_fold_vk.verification_key.clone(), c6_fold_proof: proof_field_strings(&c6_fold)?, - c6_fold_public: proof_public_field_strings(&c6_fold)?, + c6_fold_public, c7_vk: c7_vk.verification_key.clone(), c7_proof: proof_field_strings(job.c7_proof)?, c7_public: proof_public_field_strings(job.c7_proof)?, @@ -621,6 +630,8 @@ pub fn prove_decryption_aggregation_jobs( committee_members: committee_members.clone(), committee_hash_hi: committee_hash_hi.clone(), committee_hash_lo: committee_hash_lo.clone(), + domain_hi, + domain_lo, }; let json = serde_json::to_value(&witness) diff --git a/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs b/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs index 32b177328..1ad667007 100644 --- a/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs +++ b/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs @@ -96,7 +96,8 @@ fn c3_fold_total_slots_from_compiled_json() -> usize { (len - 4) / 3 } -/// Reads slot count from the compiled `c6_fold` ABI (`acc_public_inputs` length is `4 + 4 * slots`). +/// Reads slot count from the compiled `c6_fold` ABI +/// (`acc_public_inputs` length is `6 + 4 * slots`: four fold params plus two domain limbs). fn c6_fold_total_slots_from_compiled_json() -> usize { let path = c6_fold_json_path(); let raw = std::fs::read_to_string(&path).unwrap_or_else(|e| { @@ -119,11 +120,11 @@ fn c6_fold_total_slots_from_compiled_json() -> usize { }) .expect("c6_fold.json: abi.parameters.acc_public_inputs.length") as usize; assert!( - len >= 4 && (len - 4).is_multiple_of(4), - "unexpected acc_public_inputs length {} (expected 4 + 4 * slots)", + len >= 6 && (len - 6).is_multiple_of(4), + "unexpected acc_public_inputs length {} (expected 6 + 4 * slots)", len ); - (len - 4) / 4 + (len - 6) / 4 } #[test] diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 1f8725237..e7747f68d 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1188,5 +1188,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" + "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 4c0159d3e..d53ac98b7 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" + "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 2dfa41e60..0488792a4 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -111,17 +111,6 @@ "name": "CommitteeSizeTooSmall", "type": "error" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "proofNullifier", - "type": "bytes32" - } - ], - "name": "DecryptionProofAlreadyConsumed", - "type": "error" - }, { "inputs": [ { @@ -2439,5 +2428,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-873e74c1b5b14b995c4e623d1b2305d86bdf9b7a" + "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 4c75763b6..b35935826 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1450,5 +1450,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" + "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 0158a0b4f..47ec7c8ca 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -432,9 +432,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { require(proof.length > 0, ProofRequired()); - // Canonicalize the verifier ABI payload before deriving its nullifier. - // `abi.decode` accepts some trailing-byte variants; hashing the decoded - // tuple makes every equivalent encoding consume the same one-shot key. _verifyPlaintext(e3Id, keccak256(plaintextOutput), proof); success = true; @@ -448,7 +445,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { uint256 e3Id, bytes32 plaintextHash, bytes calldata proof - ) internal { + ) internal view { E3 storage e3 = e3s[e3Id]; InterfoldPricing.verifyPlaintext( address(e3.decryptionVerifier), @@ -1201,14 +1198,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { interfaceId == 0x01ffc9a7; // IERC165.supportsInterface selector } - /// @dev Canonical decryption-proof nullifiers consumed by successful E3s. - mapping(bytes32 proofNullifier => bool consumed) - private _consumedDecryptionProofs; - /// @dev Reserved storage slots for future upgrades. Adding new state /// variables in derived versions of this contract must reduce this /// array's length accordingly to preserve storage layout compatibility /// across upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[50] private __gap; + uint256[49] private __gap; } diff --git a/packages/interfold-contracts/contracts/interfaces/IDecryptionVerifier.sol b/packages/interfold-contracts/contracts/interfaces/IDecryptionVerifier.sol index 06d13aa32..2588b5dc2 100644 --- a/packages/interfold-contracts/contracts/interfaces/IDecryptionVerifier.sol +++ b/packages/interfold-contracts/contracts/interfaces/IDecryptionVerifier.sol @@ -13,9 +13,9 @@ pragma solidity 0.8.28; * the final EVM proof and enforces: * - the immutable recursive sub-circuit VK hashes * - the plaintext slot matches the caller-supplied hash - * - a domain-binding slot binding the proof to - * (chainId, this, e3Id, committeeRoot, sortedNodes, ciphertextOutputHash, - * committeePublicKey, plaintextOutputHash) + * - a domain-binding slot supplied by Interfold and derived from + * (chainId, Interfold address, e3Id, committeeHash, + * ciphertextOutputHash, committeePublicKey) * and reverts on any mismatch. */ interface IDecryptionVerifier { @@ -37,27 +37,16 @@ interface IDecryptionVerifier { /// recomputed on-chain from the call context. error DomainBindingMismatch(); - /// @notice Verify a DecryptionAggregator EVM proof and bind it to the full - /// on-chain call context. - /// @param e3Id Identifier of the E3 the plaintext was decrypted for. - /// @param committeeRoot Ciphernode IMT root snapshotted at committee request time - /// (`CiphernodeRegistry.rootAt(e3Id)`). - /// @param sortedNodes The on-chain-selected committee (`c.topNodes`), bound into - /// the domain-binding hash. - /// @param ciphertextOutputHash The previously-published ciphertext hash - /// (`e3.ciphertextOutput`). - /// @param committeePublicKey The committee's aggregated PK commitment - /// (`e3.committeePublicKey`). + /// @notice Verify a DecryptionAggregator EVM proof and bind it to the E3 + /// domain recomputed by Interfold. + /// @param decryptionDomain `keccak256(abi.encode(chainId, interfold, e3Id, + /// committeeHash, ciphertextOutputHash, committeePublicKey))`. /// @param plaintextOutputHash `keccak256(plaintextOutput)` expected by the Interfold. /// @param committeeHash `keccak256(abi.encodePacked(topNodes))` for the on-chain committee. /// @param proof ABI-encoded `(bytes rawProof, bytes32[] publicInputs)`. /// @return success Always `true` on success; the wrapper reverts on any failure. function verify( - uint256 e3Id, - uint256 committeeRoot, - address[] calldata sortedNodes, - bytes32 ciphertextOutputHash, - bytes32 committeePublicKey, + bytes32 decryptionDomain, bytes32 plaintextOutputHash, bytes32 committeeHash, bytes calldata proof diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index c4ea2b690..e2bddf717 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -483,9 +483,6 @@ interface IInterfold { /// @notice The bounded request was mined after its requester-authorized deadline. error RequestExpired(uint256 requestDeadline, uint256 currentTimestamp); - /// @notice This canonical decryption proof payload already completed another E3. - error DecryptionProofAlreadyConsumed(bytes32 proofNullifier); - /// @notice Caller has no balance to claim for the given E3 / treasury / token. error NothingToClaim(); diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 8de83bf8f..5615e17c4 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -28,9 +28,6 @@ library InterfoldPricing { uint16 internal constant MAX_PROTOCOL_SHARE_BPS = 5_000; uint16 internal constant MAX_MARGIN_BPS = 5_000; uint32 internal constant MAX_COMMITTEE_SIZE = 256; - /// @dev Interfold `_consumedDecryptionProofs` mapping slot. Pinned by the - /// storage-layout validator alongside this external library. - uint256 internal constant CONSUMED_DECRYPTION_PROOFS_SLOT = 38; event ParamSetRegistered(uint8 paramSet, bytes encodedParams); event ParamSetUpdated( @@ -84,55 +81,27 @@ library InterfoldPricing { bytes32 committeePublicKey, bytes32 plaintextHash, bytes calldata proof - ) external { - _consumeDecryptionProof(proof); + ) external view { + bytes32 committeeHash = ICiphernodeRegistry(registryAddress) + .getCommitteeHash(e3Id); + bytes32 decryptionDomain = keccak256( + abi.encode( + block.chainid, + address(this), + e3Id, + committeeHash, + ciphertextHash, + committeePublicKey + ) + ); IDecryptionVerifier(verifierAddress).verify( - e3Id, - ICiphernodeRegistry(registryAddress).rootAt(e3Id), - ICiphernodeRegistry(registryAddress).getCommitteeNodes(e3Id), - ciphertextHash, - committeePublicKey, + decryptionDomain, plaintextHash, - ICiphernodeRegistry(registryAddress).getCommitteeHash(e3Id), + committeeHash, proof ); } - /// @notice Canonical single-use identifier for a decryption proof payload. - /// @dev Decoding and re-encoding maps ABI payloads with equivalent trailing - /// bytes to one nullifier, preventing representation-based replay. - function _consumeDecryptionProof(bytes calldata proof) private { - // Preserve compatibility with compact proof formats used by other - // encryption schemes; BFV's ABI tuple is always at least two words. - bytes32 proofNullifier; - if (proof.length < 64) { - proofNullifier = keccak256(proof); - } else { - (bytes memory rawProof, bytes32[] memory publicInputs) = abi.decode( - proof, - (bytes, bytes32[]) - ); - proofNullifier = keccak256(abi.encode(rawProof, publicInputs)); - } - - bytes32 storageKey = keccak256( - abi.encode(proofNullifier, CONSUMED_DECRYPTION_PROOFS_SLOT) - ); - bool consumed; - // solhint-disable-next-line no-inline-assembly - assembly { - consumed := sload(storageKey) - } - require( - !consumed, - IInterfold.DecryptionProofAlreadyConsumed(proofNullifier) - ); - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(storageKey, 1) - } - } - function honestNodes( address registryAddress, uint256 e3Id, diff --git a/packages/interfold-contracts/contracts/test/MockDecryptionVerifier.sol b/packages/interfold-contracts/contracts/test/MockDecryptionVerifier.sol index 0c70c941a..8b5b21314 100644 --- a/packages/interfold-contracts/contracts/test/MockDecryptionVerifier.sol +++ b/packages/interfold-contracts/contracts/test/MockDecryptionVerifier.sol @@ -14,10 +14,6 @@ contract MockDecryptionVerifier is IDecryptionVerifier { bytes4 private constant _FAIL_MAGIC = 0xdeadbeef; function verify( - uint256, - uint256, - address[] calldata, - bytes32, bytes32, bytes32, bytes32, diff --git a/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol b/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol index 0d1de160e..77c9305ba 100644 --- a/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol +++ b/packages/interfold-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol @@ -23,20 +23,19 @@ import { CommitteeHashLib } from "../../lib/CommitteeHashLib.sol"; * [1] = expectedC7KeyHash (VK anchor) * [2] = committee_hash_hi * [3] = committee_hash_lo - * [4 .. 4+1+(3*(T+1))) = circuit-internal (sk, esm, ct columns) + * [4] = decryption_domain_hi + * [5] = decryption_domain_lo + * [6 .. 6+1+(3*(T+1))) = circuit-internal (sk, esm, ct columns) * [last 100] = plaintext message coefficients (100 u64 LE) - * Total: expectedPublicInputsLen = 4 + 1 + 3*(T+1) + 100. + * Total: expectedPublicInputsLen = 6 + 1 + 3*(T+1) + 100. * * The two VK-hash slots are checked against contract immutables set at * construction; this anchors the recursive aggregation trust and * prevents a malicious aggregator from substituting a forged sub-VK. * - * NOTE -- domain binding relaxation: wrapper-level chainId/deployment/e3Id - * binding requires a dedicated circuit public input. The current circuits - * do not expose such a slot. Full cryptographic enforcement tracked as - * future work. The caller-supplied `e3Id`, `committeeRoot`, `sortedNodes`, - * `ciphertextOutputHash`, and `committeePublicKey` are preserved in the - * interface for forward compatibility. + * Each secret-bearing C6 proof commits to the same domain limbs. C6Fold + * preserves them, and DecryptionAggregator exposes them here, so an + * aggregator cannot re-label an existing proof for another E3. */ contract BfvDecryptionVerifier is IDecryptionVerifier { error InvalidCircuitVerifier(address verifier); @@ -57,10 +56,14 @@ contract BfvDecryptionVerifier is IDecryptionVerifier { /// @dev `publicInputs` index for `committee_hash_lo`. uint256 internal constant COMMITTEE_HASH_LO_IDX = 3; + /// @dev Public input indices for the E3 decryption-domain limbs. + uint256 internal constant DECRYPTION_DOMAIN_HI_IDX = 4; + uint256 internal constant DECRYPTION_DOMAIN_LO_IDX = 5; + /// @notice BFV threshold `T`; must match the compiled DecryptionAggregator circuit. uint256 public immutable threshold; - /// @dev `4 + DEC_RETURN_PREFIX_LEN + DEC_RETURN_COLUMN_COUNT*(T+1) + MESSAGE_COEFFS_COUNT`. + /// @dev `6 + DEC_RETURN_PREFIX_LEN + DEC_RETURN_COLUMN_COUNT*(T+1) + MESSAGE_COEFFS_COUNT`. uint256 internal immutable expectedPublicInputsLen; /// @notice Underlying Honk verifier for the DecryptionAggregator circuit. @@ -92,7 +95,7 @@ contract BfvDecryptionVerifier is IDecryptionVerifier { ) revert InvalidVerificationKeyHash(); threshold = _threshold; expectedPublicInputsLen = - 4 + + 6 + DEC_RETURN_PREFIX_LEN + (DEC_RETURN_COLUMN_COUNT * (_threshold + 1)) + MESSAGE_COEFFS_COUNT; @@ -104,11 +107,7 @@ contract BfvDecryptionVerifier is IDecryptionVerifier { /// @inheritdoc IDecryptionVerifier function verify( - uint256 e3Id, - uint256 committeeRoot, - address[] calldata sortedNodes, - bytes32 ciphertextOutputHash, - bytes32 committeePublicKey, + bytes32 decryptionDomain, bytes32 plaintextOutputHash, bytes32 committeeHash, bytes calldata proof @@ -143,20 +142,24 @@ contract BfvDecryptionVerifier is IDecryptionVerifier { ) { revert DomainBindingMismatch(); } + if ( + publicInputs[DECRYPTION_DOMAIN_HI_IDX] != + CommitteeHashLib.hi(decryptionDomain) + ) { + revert DomainBindingMismatch(); + } + if ( + publicInputs[DECRYPTION_DOMAIN_LO_IDX] != + CommitteeHashLib.lo(decryptionDomain) + ) { + revert DomainBindingMismatch(); + } // Plaintext hash check: 100-coefficient plaintext must hash to the claimed value. if (!_verifyPlaintextHash(publicInputs, plaintextOutputHash)) { revert PlaintextHashMismatch(); } - // Suppress unused-variable warnings for forward-compatibility params. - // These will be used for circuit-level domain binding in a future circuit update. - e3Id; - committeeRoot; - sortedNodes; - ciphertextOutputHash; - committeePublicKey; - // Bubble up as a revert instead of a silent `false`. if (!circuitVerifier.verify(rawProof, publicInputs)) { revert InvalidProof(); diff --git a/packages/interfold-contracts/contracts/verifiers/bfv/honk/DecryptionAggregatorVerifier.sol b/packages/interfold-contracts/contracts/verifiers/bfv/honk/DecryptionAggregatorVerifier.sol index 110829355..02b055088 100644 --- a/packages/interfold-contracts/contracts/verifiers/bfv/honk/DecryptionAggregatorVerifier.sol +++ b/packages/interfold-contracts/contracts/verifiers/bfv/honk/DecryptionAggregatorVerifier.sol @@ -7,8 +7,8 @@ pragma solidity >=0.8.21; uint256 constant N = 2097152; uint256 constant LOG_N = 21; -uint256 constant NUMBER_OF_PUBLIC_INPUTS = 127; -uint256 constant VK_HASH = 0x179aeedaf3c48066180561e127d73c1ffbabf175e47589b309ddec6b1cd679d3; +uint256 constant NUMBER_OF_PUBLIC_INPUTS = 129; +uint256 constant VK_HASH = 0x194ab481ffef18a45daf58632a2a4f33c0a18e1aa9f86c6c36a4110060062ccc; library HonkVerificationKey { function loadVerificationKey() internal @@ -18,149 +18,149 @@ library HonkVerificationKey { Honk.VerificationKey memory vk = Honk.VerificationKey({ circuitSize: uint256(2097152), logCircuitSize: uint256(21), - publicInputsSize: uint256(127), + publicInputsSize: uint256(129), ql: Honk.G1Point({ x: uint256( - 0x21b909972bfe373b93f74e8d1b23d4f9da2d359033f9bea3bfd0dff4b14583b7 + 0x010521afb34a08599b6050718fc9b8c87a4d2c07eb2dd5bb601b0a5f1c7e184a ), y: uint256( - 0x00cca3f61b1c83f29f138790d10246931d7ac90c67f0868928a8f87f115bae3f + 0x2d2da06c4f068f98957930d8e3e6620ec8c4ac6b2efd070e453ea994503eeacb ) }), qr: Honk.G1Point({ x: uint256( - 0x282acc4814ca5bff02f14c4a58b751ecc70c1c01494610385bff82d9072bf548 + 0x1152e6db8ad8a05dbb30c8168db63ca5d9f3949ab9bf441afe3ff7e242d7157f ), y: uint256( - 0x1d4bd882f21883ba61543964458224dbd4a53595d002e18c0db812390c45416c + 0x18ef95f1bcaf881852b1c3a393cf4d1d1f306cc9cb6837a3e35a251f903e982c ) }), qo: Honk.G1Point({ x: uint256( - 0x1dd7761ff2c72c1af984fb53d1b3b42460c3323214a2d7dde42c62973d34198e + 0x110388762f39683a2464c340dfb43812e108e539c39b112196cbf77e5a48bf8b ), y: uint256( - 0x13040272a4c0bedcfdec2b313f9efe3ee9a0bbd5ca58d9432dc8d832127e99b0 + 0x1477d531425197458cee05db775238ccbf8146c6e1cc5aabdcafe6dae4b790c1 ) }), q4: Honk.G1Point({ x: uint256( - 0x174f2955dfa54bf68658a287d4658af6994ecd888993452c4c14f1c60871ce56 + 0x1882a8a60fd8e59740e19b600c5459a4b642c7e9b089f8926b88b5a66cb3e904 ), y: uint256( - 0x0cda4ce133f1492a2f13a0e29c6df44b8e697f84e481f1b2dbf2d8d741be5e04 + 0x20c2cea5d12751d28f53d5be4854f392d68c8ca45c74c3fc4f5d1aa6ee01688a ) }), qm: Honk.G1Point({ x: uint256( - 0x22ac07bc4c7d102054ba8dc18954f43d66ed6c57ede3a78e5fe44e80ab26daff + 0x23adba2e6e3307f724078a2ecffb74fd97b3f4c728d09ef210dd4206bdff8a4b ), y: uint256( - 0x01a5cce0a2e3607ae4fc406e7379aed53d7cd2cdb0d3a14e759531cee30cb9ea + 0x05b21af164d64b64d280ed384c88a8c3934ed2667164579ed8708f4877d3830f ) }), qc: Honk.G1Point({ x: uint256( - 0x11fee8c098df12a40892852407a771a7d280dbfbab5eeb06b23896095ca7a290 + 0x0162dc5fec3fbc821088520d018a189bfba2d0694703a50e2ff68e1746d4ddfa ), y: uint256( - 0x17ec33cd33eacb4335ba2e0b3baffe2b0bd0f8371c7cf7213447d3ba6dd4ba6b + 0x0d3bf9f887cc9f913494d7b19fc9d2f584956209134f92cfcc492d251fcb495f ) }), qLookup: Honk.G1Point({ x: uint256( - 0x13143d24a192079453fc93ca72b6be61609f4d042621b3d3973bfa341bb8a424 + 0x0e01e2471d2cc58c36a2af88d7c457a0c643082e0ea549b50e64780288cddb33 ), y: uint256( - 0x2dee6429bc80fc94550da46393279fc6e08def2542d28bbc312a082f31e56409 + 0x1bdf27437b6f422c586cd3ccef4cb4d8bb8b1cf1db74d99a6f85215f93af99f3 ) }), qArith: Honk.G1Point({ x: uint256( - 0x2624d4d9d7eac2515cb4b322afc263ddc87c535791e2206eef64bc24024968e1 + 0x22a366917320f410156b25de971a1e9d15251295e2cc326cb5b8c7f1801a2ce1 ), y: uint256( - 0x105a6a9c813245babf469aebeafca60e878d41b05f79125dedf362bee561b5ec + 0x18b3f08d795debf73da2daa73f1d567f7d38e94e3aa40f637a365fb0e5a76821 ) }), qDeltaRange: Honk.G1Point({ x: uint256( - 0x1cec49a84cd964f7dccf24f37f746eb4660ffa446ba4e79d04582d86fc5fb2be + 0x194e200afec2b4572c3206296df1b4c1122b2634aebf9a1ec5369d39035482ba ), y: uint256( - 0x16acc276874333a56f75e2c79d9e723e9ac1bb18d1ab5bd579a3ab1702464ed0 + 0x06a89d0def7080d25a384a7a4af8930b5c0f8796439b8eeab6a1d1bc6f44f5fe ) }), qElliptic: Honk.G1Point({ x: uint256( - 0x006554df9837516dfb90ce208134e4b81d29ebf81032b08330501733f5f20d6a + 0x1cb1882e6203b03ff642b4b149440c663a805f7cab669675dd3956891fa7996c ), y: uint256( - 0x0ff31f52484554b3123ffc5c911d928e91ee373db03b305bd1350ae27aba4169 + 0x301b524d91f9ef744a9120228b2434d6fcf625ea55aa95e2f4521adbef1c9a11 ) }), qMemory: Honk.G1Point({ x: uint256( - 0x28fad415a8ba66a6c2d15321977f696a033b56580937a63c0be78be9ccdbf00a + 0x234f73f3382bba3e07b886671eacdbb5cd23d3edecadccd5ae41e644543e0bc3 ), y: uint256( - 0x229fa12d35300e25b3095908acfed5751d51e93cd6ecf4af6757ba5a4c540c18 + 0x041c69a96e8c443ef178f6dda788f59d4577eced661436c793d46ec99b6ff316 ) }), qNnf: Honk.G1Point({ x: uint256( - 0x27769c90ab027f74a7f86fbe3a1832e41518cd4975e8ba110311664df43f0ce3 + 0x0e47e62e6db09aa6cbdefbc3136d417c81bdc3993795fb6081760bbc1bf9ebd7 ), y: uint256( - 0x24a8f977133bcb034382e4cbcd3e335373ae5aac0e67824a2647554a52536b87 + 0x287df15d22e3ed8af0a13608fa903f9cf2ae3e93d47368189f36d1df673fec2e ) }), qPoseidon2External: Honk.G1Point({ x: uint256( - 0x2eb3443efed96b06718b28d1bfbbc35a407b6af60f720ba5a9d0ac78501f0ed2 + 0x1c85b9febc61f29919ebeffd0e83fd1af35c73c7612123d55047696f768920e1 ), y: uint256( - 0x17022aa4435561f83bdddcaa9174723a1e31c11d128a3455edc0b21bf22d334b + 0x2e180d1bcd14356e561423bf126837d55f33666c3525d15c4a38ec65c50e1348 ) }), qPoseidon2Internal: Honk.G1Point({ x: uint256( - 0x2ef9e66a814fe6821f53a2a2e1e93ac8630a347d7c9fee2afd2edcdc13bc0548 + 0x07b50c401476d1319de4be38bb5ac8712e4df5f889c4f332e8e0f1f0b2afb2e5 ), y: uint256( - 0x0ebdcee17969483e898170e905ff58418ad7e99173fa87c028966bd8c040c923 + 0x0895e445eb250bcb0753bb742ab19332740313526bb5734e0397fc89726323e4 ) }), s1: Honk.G1Point({ x: uint256( - 0x235a96328f656f5e8e3935de342e7ffb06d3400d2e11e03ff3f5e9729dec07de + 0x22c639fd089056e756d0dd5f49b1f4bc6dc774ec4b1ed8d7546479af37c7a3f4 ), y: uint256( - 0x23b38403e02d9a93b48b7e21c10d7360fd7a39299f6aae2174d47e2df318775f + 0x0aa616c5cb54e9d85733de81170d6384579ba7e72a375e69ff2722ca3af4cac2 ) }), s2: Honk.G1Point({ x: uint256( - 0x2c15698c01375d97f94676f1bbdb3ba5b157bacaadec11b12cf074cf212f6e12 + 0x07e8fe3a63ece67d929f1ae2e111188d86ef3485e5755cccf7069a09e8f5392c ), y: uint256( - 0x141ec5c8ba7190c9cb1fab4f19e817d9d8f9cb2c0cedd614d4f7820a2f7c4abb + 0x1283fc4212300f568be4d9bf1df7e10eba2c0a522a460afd8610a271df3c21d4 ) }), s3: Honk.G1Point({ x: uint256( - 0x22e7871e851cfc6514318d6f16d1c34305dd0e3c0dbe39df3527feda3b0d1eb0 + 0x18a7cd98bdfa528a77c6cd37da0efe90bd61368e8d26c4fed7e8669e461529e9 ), y: uint256( - 0x078c546da57b7d1340a5a5b11922ab15592a2c3d32553532c318f0a238768a01 + 0x2313045014b131da2894b943835443bab338f9da58eea9b00f1f508b351c4ba2 ) }), s4: Honk.G1Point({ x: uint256( - 0x17e47cca2b9876b87b90039176b89b889b2e6f6ab55bf5b6ade7026c1886a55b + 0x0d90d96b1aaf3b28a8a4a478450f36dc0543f181e6aad3c5bb1f6d49de24000c ), y: uint256( - 0x1277395e4b6af40bd3099eedef9f6f1f4a3f6e95a1c0540bc521df5df391fdce + 0x020f9e28c5643771079a8c2cee87806e122bca98e2db61c540ce6e5621da1435 ) }), t1: Honk.G1Point({ @@ -197,34 +197,34 @@ library HonkVerificationKey { }), id1: Honk.G1Point({ x: uint256( - 0x162e6ffc2acbbe037aa8301684ed9e2d850a2c83a3c1a3164453b5c2187c8c75 + 0x1d2c7ad198ae54fb6a7fc6fc40f17c013d4fdfaf7443766f69c9d2327c01d30f ), y: uint256( - 0x22f0647302fbfc4d83670140b7ec0cd606fd991bd3e7cebeee96ee3b6169e538 + 0x156a0ae17a38cc19f98d28ba414ae4e6b6a5bddd07e960a9064b779e6c3d0cf3 ) }), id2: Honk.G1Point({ x: uint256( - 0x1fc1d8dce21a638cd9695d5ed2d796b7b1423fe391391cbf0076dabcd5b1229c + 0x1e00feff267b2444858036a89fc5d53c6a3fcfb6094eb652d46edf6796874d11 ), y: uint256( - 0x2e4d338298032c5426ca47e6970b8ef0b055728771a8ad6b455f4d3001abd402 + 0x3045db4643b53c1783fc74d6c8bfa440fd28d010c3069f843375ad65fc3a4811 ) }), id3: Honk.G1Point({ x: uint256( - 0x011c7ccc37d9abaf9dd6ffb88f045f8f6adb02dde453b8645b7a5461356255f8 + 0x09fc12b4fa13d47337ccab21e46377db14ff27de07660f77ff9eecccd5e1f113 ), y: uint256( - 0x0186d6fa335ec0a6179c9edeb2cfca478103eb4989218cd11ddeb6a4762ff294 + 0x02cd05f2f8779395bb30f6efe75c0cfacd0c54daa0c93d6e6fddf72e9591503e ) }), id4: Honk.G1Point({ x: uint256( - 0x1be8e47ef6bff9941f3febe177d14f28448a16fe9dd81b1c9cfd05bd9136c02c + 0x1c4df7145e8eb4fe3b49165e87646fbfad9afef3f5980c08712fad3bb9fe8ff5 ), y: uint256( - 0x1d6ea8c9b1f0fd0d27694dee140ef177141fc8e1d240e5715834070a82a9d7e0 + 0x0dc75037ff2094d4a3fa0e7f689905ec8b6ef1cf087a47d36daceca7564c9912 ) }), lagrangeFirst: Honk.G1Point({ @@ -237,10 +237,10 @@ library HonkVerificationKey { }), lagrangeLast: Honk.G1Point({ x: uint256( - 0x201feccb28b5ddf7440c37e1a8d5676a8f9d7feb0e373436b3413fa9f775fd6b + 0x162e289cadfb8e38f36041f2d12ef29d09757e625ed43ee4211cfd4d54a3efea ), y: uint256( - 0x03f87d81d9e68bc20ce687e8a53620c9947d06fdc887f89e9fc6a023c8880e74 + 0x2d6edc8136af3ddcb87da500d93e149310d086b3af87031bb6e34ff231bfa101 ) }) }); diff --git a/packages/interfold-contracts/scripts/storageLayouts.ts b/packages/interfold-contracts/scripts/storageLayouts.ts index 0d5de9ffb..b6a4c412c 100644 --- a/packages/interfold-contracts/scripts/storageLayouts.ts +++ b/packages/interfold-contracts/scripts/storageLayouts.ts @@ -397,17 +397,5 @@ export function assertInterfoldPricingSlots(layout: StorageLayout): string[] { "Interfold: _feeTokenAllowed must remain adjacent at slot 33+0.", ); } - const consumedProofs = layout.storage.find( - (entry) => entry.label === "_consumedDecryptionProofs", - ); - if ( - !consumedProofs || - consumedProofs.slot !== "38" || - consumedProofs.offset !== 0 - ) { - errors.push( - "Interfold: _consumedDecryptionProofs must remain at slot 38+0 for InterfoldPricing.", - ); - } return errors; } diff --git a/packages/interfold-contracts/scripts/utils.ts b/packages/interfold-contracts/scripts/utils.ts index add04d1f0..8506c4070 100644 --- a/packages/interfold-contracts/scripts/utils.ts +++ b/packages/interfold-contracts/scripts/utils.ts @@ -110,7 +110,7 @@ export function bfvDkgCommitteeHashIndices(h: number): { /** `decryption_aggregator` EVM public-input count for BFV threshold `t`. */ export function bfvDecExpectedPublicInputsLen(threshold: number): number { - return 108 + 3 * threshold; + return 110 + 3 * threshold; } /** `publicInputs` indices for decryption-aggregator committee hash limbs. */ @@ -118,6 +118,11 @@ export function bfvDecCommitteeHashIndices(): { hi: number; lo: number } { return { hi: 2, lo: 3 }; } +/** `publicInputs` indices for decryption-aggregator E3 domain limbs. */ +export function bfvDecDomainIndices(): { hi: number; lo: number } { + return { hi: 4, lo: 5 }; +} + /** Recursive VK hashes for `BfvPkVerifier` sub-circuits (from `pnpm compile:circuits`). */ export function getBfvPkSubCircuitVkHashPaths() { const root = getRepoRoot(); diff --git a/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts b/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts index f7d10297d..6ce53e998 100644 --- a/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts +++ b/packages/interfold-contracts/test/BfvDecryptionVerifier.spec.ts @@ -25,6 +25,7 @@ const MESSAGE_COEFFS_COUNT = 100; const EXPECTED_C6_FOLD_KEY_HASH = ethers.id("c6_fold"); const EXPECTED_C7_KEY_HASH = ethers.id("c7"); +const DECRYPTION_DOMAIN = ethers.id("e3-decryption-domain"); /** Must match `BfvDecryptionVerifier.threshold` / default circuit `T`. */ const THRESHOLD = BFV_THRESHOLD_T; @@ -35,6 +36,8 @@ const EXPECTED_PUBLIC_INPUTS_LEN = bfvDecExpectedPublicInputsLen(THRESHOLD); /** Indices for committee hash limbs (fixed layout). */ const COMMITTEE_HASH_HI_IDX = 2; const COMMITTEE_HASH_LO_IDX = 3; +const DECRYPTION_DOMAIN_HI_IDX = 4; +const DECRYPTION_DOMAIN_LO_IDX = 5; function committeeHashHi(committeeHash: string): string { const v = BigInt(committeeHash); @@ -55,6 +58,7 @@ function buildPublicInputsWithMessage( EXPECTED_C7_KEY_HASH, ], committeeHash = ethers.ZeroHash, + decryptionDomain = DECRYPTION_DOMAIN, ): string[] { const arr: string[] = new Array(totalInputs); arr[0] = subCircuitHashes[0]; @@ -64,6 +68,8 @@ function buildPublicInputsWithMessage( } arr[COMMITTEE_HASH_HI_IDX] = committeeHashHi(committeeHash); arr[COMMITTEE_HASH_LO_IDX] = committeeHashLo(committeeHash); + arr[DECRYPTION_DOMAIN_HI_IDX] = committeeHashHi(decryptionDomain); + arr[DECRYPTION_DOMAIN_LO_IDX] = committeeHashLo(decryptionDomain); const offset = totalInputs - MESSAGE_COEFFS_COUNT; for (let i = 0; i < messageCoeffs.length && i < MESSAGE_COEFFS_COUNT; i++) { arr[offset + i] = "0x" + messageCoeffs[i].toString(16).padStart(64, "0"); @@ -122,14 +128,9 @@ describe("BfvDecryptionVerifier", function () { return { bfvDecryptionVerifier: dv, mockCircuit: mc }; }; - /** Contextual params forwarded to verify; not checked against circuit outputs (future domain binding). */ + /** Domain supplied by Interfold after hashing the full E3 context. */ const ctx = () => { - const e3Id = 7n; - const root = BigInt(ethers.id("test-root")); - const nodes = [testSigner.address]; - const ciphertextHash = ethers.id("ct-hash"); - const committeePk = ethers.id("committee-pk"); - return { e3Id, root, nodes, ciphertextHash, committeePk }; + return { decryptionDomain: DECRYPTION_DOMAIN }; }; describe("reverts", function () { @@ -180,16 +181,12 @@ describe("BfvDecryptionVerifier", function () { const { bfvDecryptionVerifier } = await loadFixture( deployWithMockCircuit, ); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const plaintextHash = ethers.keccak256("0x1234"); await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, "0xdeadbeef", @@ -202,7 +199,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage(messageCoeffs).slice( @@ -214,11 +211,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -234,7 +227,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage( @@ -246,11 +239,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -266,7 +255,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage( @@ -279,11 +268,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -296,7 +281,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage( @@ -309,11 +294,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -326,7 +307,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const committeeHash = ethers.id("real-committee"); const wrongCommitteeHash = ethers.id("wrong-committee"); @@ -344,11 +325,7 @@ describe("BfvDecryptionVerifier", function () { // pass wrong committeeHash to verify — hi/lo check should fail await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, wrongCommitteeHash, proof, @@ -359,12 +336,36 @@ describe("BfvDecryptionVerifier", function () { ); }); + it("rejects replaying a proof under a different E3 decryption domain (C-03)", async function () { + const { bfvDecryptionVerifier, mockCircuit } = await loadFixture( + deployWithMockCircuit, + ); + await mockCircuit.setReturnValue(true); + + const messageCoeffs = [1n, 2n, 3n]; + const publicInputs = buildPublicInputsWithMessage(messageCoeffs); + const plaintextHash = plaintextToHash(messageCoeffs); + const proof = encodeProof("0x01", publicInputs); + + await expect( + bfvDecryptionVerifier.verify.staticCall( + ethers.id("different-e3-domain"), + plaintextHash, + ethers.ZeroHash, + proof, + ), + ).to.be.revertedWithCustomError( + bfvDecryptionVerifier, + "DomainBindingMismatch", + ); + }); + it("reverts PlaintextHashMismatch when message coeffs don't hash to plaintextHash", async function () { const { bfvDecryptionVerifier, mockCircuit } = await loadFixture( deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const wrongHash = ethers.keccak256("0x0000"); @@ -373,11 +374,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, wrongHash, ethers.ZeroHash, proof, @@ -393,7 +390,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(false); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage(messageCoeffs); @@ -402,11 +399,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -418,7 +411,7 @@ describe("BfvDecryptionVerifier", function () { const { mockCircuit } = await loadFixture(deployWithMockCircuit); await mockCircuit.setReturnValue(true); const mockAddr = await mockCircuit.getAddress(); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const bfvDecryptionVerifier = await ( await ethers.getContractFactory("BfvDecryptionVerifier") @@ -437,11 +430,7 @@ describe("BfvDecryptionVerifier", function () { await expect( bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -456,7 +445,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n, 42n, 100n]; const publicInputs = buildPublicInputsWithMessage(messageCoeffs); @@ -464,11 +453,7 @@ describe("BfvDecryptionVerifier", function () { const proof = encodeProof("0x0102", publicInputs); const result = await bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -481,7 +466,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = [1n, 2n, 3n]; const publicInputs = buildPublicInputsWithMessage( @@ -492,11 +477,7 @@ describe("BfvDecryptionVerifier", function () { const proof = encodeProof("0x01", publicInputs); const result = await bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -509,7 +490,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const committeeHash = ethers.id("the-committee"); const messageCoeffs = [10n, 20n, 30n]; @@ -523,11 +504,7 @@ describe("BfvDecryptionVerifier", function () { const proof = encodeProof("0x01", publicInputs); const result = await bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, committeeHash, proof, @@ -540,7 +517,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs: bigint[] = []; const publicInputs = buildPublicInputsWithMessage(messageCoeffs); @@ -548,11 +525,7 @@ describe("BfvDecryptionVerifier", function () { const proof = encodeProof("0x01", publicInputs); const result = await bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, @@ -565,7 +538,7 @@ describe("BfvDecryptionVerifier", function () { deployWithMockCircuit, ); await mockCircuit.setReturnValue(true); - const { e3Id, root, nodes, ciphertextHash, committeePk } = ctx(); + const { decryptionDomain } = ctx(); const messageCoeffs = Array.from( { length: MESSAGE_COEFFS_COUNT }, @@ -576,11 +549,7 @@ describe("BfvDecryptionVerifier", function () { const proof = encodeProof("0x01", publicInputs); const result = await bfvDecryptionVerifier.verify.staticCall( - e3Id, - root, - nodes, - ciphertextHash, - committeePk, + decryptionDomain, plaintextHash, ethers.ZeroHash, proof, diff --git a/packages/interfold-contracts/test/BfvVkBindingIntegration.spec.ts b/packages/interfold-contracts/test/BfvVkBindingIntegration.spec.ts index 7d43ae1f4..98c49c855 100644 --- a/packages/interfold-contracts/test/BfvVkBindingIntegration.spec.ts +++ b/packages/interfold-contracts/test/BfvVkBindingIntegration.spec.ts @@ -15,6 +15,7 @@ import { assertBfvDecryptionVerifierSubCircuitVkHashes, assertBfvPkVerifierSubCircuitVkHashes, bfvDecCommitteeHashIndices, + bfvDecDomainIndices, bfvDecExpectedPublicInputsLen, bfvDkgCommitteeHashIndices, bfvPkExpectedPublicInputsLen, @@ -115,6 +116,7 @@ function hexToBytes32Array(hex: string): string[] { const DKG_COMMITTEE_HASH_IDX = bfvDkgCommitteeHashIndices(BFV_DKG_H); const DKG_EXPECTED_PUBLIC_INPUT_LEN = bfvPkExpectedPublicInputsLen(BFV_DKG_H); const DEC_COMMITTEE_HASH_IDX = bfvDecCommitteeHashIndices(); +const DEC_DOMAIN_IDX = bfvDecDomainIndices(); const DEC_EXPECTED_PUBLIC_INPUT_LEN = bfvDecExpectedPublicInputsLen(BFV_THRESHOLD_T); @@ -317,6 +319,10 @@ describe("BfvVkBindingIntegration", function () { decPublicInputs[DEC_COMMITTEE_HASH_IDX.hi], decPublicInputs[DEC_COMMITTEE_HASH_IDX.lo], ); + const decDomain = committeeHashFromLimbs( + decPublicInputs[DEC_DOMAIN_IDX.hi], + decPublicInputs[DEC_DOMAIN_IDX.lo], + ); if (isCoverageRun) { // Instrumented Honk verifiers can exceed any practical eth_call budget; @@ -357,11 +363,7 @@ describe("BfvVkBindingIntegration", function () { const plaintextHash = plaintextHashFromPublicInputs(decPublicInputs); expect( await bfvDec.verify.staticCall( - testE3Id, - testRoot, - [testSigner.address], - ethers.id("test-ciphertext"), - ethers.id("test-pubkey"), + decDomain, plaintextHash, decCommitteeHash, decEncoded, diff --git a/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json b/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json index eb1378844..5e42acfb2 100644 --- a/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json +++ b/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json @@ -1,10 +1,10 @@ { "dkg_aggregator": { - "proof_hex": "0x000000000000000000000000000000000000000000000001e75e6e6bf316ec7900000000000000000000000000000000000000000000000e5e66538093519ec700000000000000000000000000000000000000000000000ea1d99185bf57a67100000000000000000000000000000000000000000000000000027a81f6b520c100000000000000000000000000000000000000000000000300a975ba6f13dbee000000000000000000000000000000000000000000000004a477a61f80a9e6d100000000000000000000000000000000000000000000000fd219fd84fb4defd70000000000000000000000000000000000000000000000000002fef0aaa80f5700000000000000000000000000000000000000000000000587f4bf0b7f2c5edb00000000000000000000000000000000000000000000000e89f1f317a49cf14f00000000000000000000000000000000000000000000000176869939c2c359c0000000000000000000000000000000000000000000000000000129747f04f15f000000000000000000000000000000000000000000000002d178d9b11de53f8f0000000000000000000000000000000000000000000000009044587fc21f92c1000000000000000000000000000000000000000000000009fdf61347211a781000000000000000000000000000000000000000000000000000008cdfd42e77de1980c43e1a2bdb19cf0131ff4fad06d6f11d7fd3279bd66410f2388c088f70a31e30bb64c3895db6a3301e9be27e79c07a3027013b0e68eb9f15025acf14e1972ecf23db1d3f80e4db8d3a8ce5e61a085775421a4ef5a8e770f626dc0fce3db71d0f0ff62f59bef95993de4ff0fc655fba80d47d6dd49ac6a99222e6b74f833800c35d744198e39965e71b95e55f6b34b670494a4279fb653f46cdce9e7d9e910a06fdd4d860032388619541f32f14d021660e8fa5497b849a703d63a58e930a195f63db46d4043c35c1fc90f21d2251f27e90874b191252eea17c8946d69d060959a597a665f3f225822e7cd537ed3be57beb9ea1ca04fc1baf6135541631ab1500bf70062fecf66545666e5f092e35c6655aa6eaf8d8bf20727575f7fc6c2c26c51b65b1a2161c60550ff91579866608e5f84a22bd9d525db9c3c0910d5a9d257d8b9fb3278bf7e3983384d4569ef101962e973fdc50af677e61249f7659180a6357f6d55ff9c3d0c3ded3bbcb37b1c49d08a76bba3d8ade40e097310dc2d2162b769f4d49f4a18af470a46bbe04ee29c054b4d644f034bed1b330b7f8624916ab19c29d424d52039e2f724aece13e882b30e012c588d515212a73f8539ef1167250177895980aea7471392e3ba028ec0715fae1cf2539f351dcf30b5d48790b4c6789f819bad95e55d1789e239dbd28d7cdcc56ad0bc1359ba6868a569d5d0bee03d62aec658dfd96444f35a5254fb150e5273acc5c643f0c6232b71c27d7109e197faa332c23d8b7c3f590a4a9972c5dc7f072121d62db7e63389c59c03726436b338944ea9825f719c9763ef1c8633e4ba2ee18556f162d735ae3c56d9e09e8ac442f6d947881166eef26a7b6fa538cbd84f3eebe8dbb6853e7399ec4520677fd7801b63bd7fa27430251fa14ea07fc97371a20021362a971be8ff709462c2e2be304a8baf3c6f57cecfb7e2dcb804656a16b3533262110bfd6bb8983cb16f23b3025152a5a67b3ba9d5f073fde73e67ff05ee11a29924d74412cf1555709498cba2023bf4d40d0fdbc85670c51082e006b173761c4183d75815fb12cda2b696fad8923295ae21e8958d220435e0a1b7bed0da50cee047f64c599848aab17beebb6cc9e802e65596603823a70658891f4d5da2d768e5acba89a796f3b8b3034f1a2c01add7ccb7fed20cc7068b76c2738cdc9f4c0cb7b752dba7393173a28f16aafb2f8423dc1c661fe8bfb5339cc45482f1df073b995b7e97f2a6c2a5824adc1dc1cfcdfeb2a64daef86e37da39bc9c8197716f12be4903f3228fbaab02bd7959a5ef295739062e15917703d8fd32f6a2a7c7667561282eb80fdb3fb6b2b123ef2586fd658281d49dca1b320e2b10851631c9b11f42550bf7a0bf737c3005918962545897986fb73a7cfacd6960fdef24f2a08d0d9909425f1b401e0572f6c2d7f7f1b16621e22948e190ffada5611905e724e14031cf80a38fd2431500edd1c857e0d04bfe118ba0e6620899d7e77a2d87d2a74adcca704a1b2c0186526acfe031abc4da8b5acfa7d50b2c69790ebf6c47f70cc3f43c6984704592a4e28316ef07835a8b598db038d52417ec5f81bf7f1b3697f5f1bc45e95a34112300f0b77c2213b2a827c30939cfd9282d84dcaa386c25bee8d7da7ca98b02968b31e1c714facf0d484cf017398d694cc979513571261b7df8c6b2c32a28820004f27df14770753cc82ad82fc23820185c486444867a8080bb57075d9c3cf32e23d1d643fda915e0ad53332c302f5e2804bdc2581874b0104eedc116923256237f41adb04fe2fb7c053b4716f2bbfada9b87cc1b4c22af9b77248e0a26bb622c3521f0e4244c66a7af714d4f840273335d1aa8d2e3159cddfd166e3d567c9268b472dc2d46705f03557130da85c7d69b53de20961e3850b6e8fb8591beaa86d0fd10b765c64e3a18d3a3171538b4a236aa5a0598d8e0678617a8cb90c62c8f941010680b196b8fefd31a1948ffda6faa57093c0e5bdb15ab793644f278ceb0ff43f12090bdafbc4674113de9a445ce1aaa725587a8e3d5aaa1ba49ff4930ec7447e0dbc9bfbdceafd180cfa38214128df7f02b436865f4ab2e5dc449557d5386c301affca4aeac69ec4f8167d8db4ad57d0e4ea4b4e60ac2f62187edfef5efbc7b2009d2829cf84a14526960e56b022d48ede9b144f6440242904da9cacb7f8db180bbed0140d8a53ef6cebbf37bb856bf73bd8fa5317e59044f72aa111b65483550994ca20ca1b0b1236fc724c0863806e5f7382ad322d66b21a8e1756f01ad84504a8c28f257e89a6369320f29a2369a69ea93a8a5afc6781483cbf31c33091a400513cca8308b55905d0cbdb38a453c8db8833cdba8c6ae2ff057fcd60e43a561ddfc683a046f7584dc4e2861eb9fcce302bf211e9693589c6e77a11cccedefb2dd16cf71ac62163775ddb1c3832a7b7ec669461968961ceccb928d387ee35e10a040c3f1cb63e84e1d359e36fe76052c8c44d4c7448e50cdd148dccfd5868a009d8b8e6fb0ca9f7e41ff668cde4ebe82df366177918e950849d8107e9a338c822b015efb43ba64949f8bcb267a544cd2e2485d66cc1525ee4e54f0d3dd0b3221e77b6ca1ad4a95d905c06145fd1a9d2821708e1ae3000e868b4f4707f38af1404d399bb89c3f4d4c4a5730271048b5d96072f147d106ac16cc13f20367e67b8142a20c858c21ee4e5e081ea05813323e4b76020518dd83c4e3eb040aac1ba862a4870e2f07eb53071ef39daa2cd981381a6adc2fe595944b16b8f3f487a870f2dfb23a1114ebbd6a5de16cc1f0fb09af8d174afba1902c520a90117ecb45afc04f60d3d3105d013af7696668a02aa56e31ac9cc3670265bb8a0d2103cfc3d072cc2af914aa524cdbf165a4f3190afc28b5810774e6c6b77669f24c3b35fc3f4119e0006c49411d8ae9918d6c3ff4207bea8185e836c24ba66c4f6c6caea6d341386396dde428fe86014248ef438f5ab688a18dbb82b516e360a706f83e414f119573fe6cc28a0d6d52ec6c133bdfcecfd4e547d43816e6fa064b6d7e93f532825ab2a81e8db45f8efb64ae9972a7cca6a163df9f2033de5a2b79240304f782d2dd93fa04491da05424e25dc8c6cbefcbffae80b6c8e792c0e5f43edc39dd7df23f9d0f14541bafae611a59f5c8aa26e03380b723934162c19a459608d98b347299086598038e2a91c73b7e0782ce39331f6d8512fbe07a7fc7b08c9b98f35082cbe354c1fb739964ac30f61d4b1f71d08a8b6a196574934238a87028427dfec27fc4436566de283beb42dbdc55e87188162e72844fcba35e452d654f06d80840a0f19ab6ecb4bab013f2e50d3069b47664158b0c63fe98f22dda6ce21cc87ad039f328fbc56d4394b0c8d0c42d6acd596912931ed363a6054458ae54789198e2d3bbc4b536f974d0b8bd865e9909e2e5bd297d45b64da2dfd9ae5c9a3a687c208e2a4ff1134488d967ddbc64b3107a9b23e7b7ca83402be13743a67d66c9a5e18418af5a327d3d8ddcd5e15421465c4b889bfa0e31c1126a743aa2f515d3c8b2a2f5598ad35c7bde623ae2bd70be128018bab38332186f3c8ea5b0ed790046421e604996d3d77c0257ce4598a66ffa6c39c755cc330dd0f827298ff230a11502223b2d5017042fb406f1cd386e642521d9f761c3c6d99b16cd03b8253982d331303a9d35dd122d9f49cc010e33c00eb51cf199abdcaf56f8648edea566a574d2e69611d85210c46ad4702245353f3616f899c74847da4456bd3fc09b84d396e234d15ca8b10caf34411fa4d9b16c44c0ae469bdeef548b7ad3a26c772810cf225a4af0997e2f4c9145a0b0e91ab7a1cc519dd271a6c3b58f2bc45c24a790bcb0bebca54fbef6125eb192cb92b1fa3c6bcb0a61c73d1efbd57d138b402953e5100ae1c1e7d107647636bee7a9309e1b1eb66e1cc1faf8ab5e8f77ddc06121a3f0244f4f4304b06453fdc75fa41db3e1b15bc1b04256ae0896b00f3268f67ff511f363f7632cadbbbac7997eb2722ddfc9d59e21c8d842b5251c4d3d76aed8fc50042272b8de59329dff728a309ff3d13bccc8913f2a664e2ba6fd8bdc0d0cbf42d453ece719b816784dc9aebe3e3e9053b6c22d14aabbec84bd9fd670e1ccbe7055be7145f3995da1a2187f9f16cbcd1b3d757624bedbf93346352a131c1840f187b4964ab692aad4d6107b75b2d22cdf9efeb5536c24a0ce84b4a57d30b42262d404e0f3178cbc291d51a96ba7ee2f7e14025059d8ff407aac7d03758f4c4260114dda8a818c704dfd406e98d804128cab1c04f35bb681799ee4a30b656ec202fb62bccc933a1416ad7b85aad49eb3180167ca52842b89ba82ac83b8d6554950ac7cf180f14c8bf1fb3b4362be359524d47bfac31b78affb5dc5c6d3d3026ef181ea0ec299ebdf35bf046ad6e9c45b6f18a4010de317e26295723ba5743fd700d3ad9a07c060be8eaf1c903412063aece07c05331fd2dfa95ef1060d2f601fd08bf69a7b0a6f2a4527320ca4f4969abce4099492c007166abe37419d20826cc1f0a166adb3e36c8f5a8e072a4aff8ca9bec1181beaa2b87e9a0f70f1c18c2db10c751f37041d1362319dcfb04fc5793083903e75cb4b510b777e81fc4ae7e3d27d853dc7a2f500f08e29ec406bd29da17b3c2477fc65aa413c13ae19d66f9b120edca4a240d2c8848d9fe4e33f695538043af26e4f6f3f19fed0f13f01ec1f103eb9cd40eafecb1d2fa1baab9e43ae7d150426827416f64a93865dc1f97b0610eb497f1d8364fdf8cb9a23478d77d66aa4a120752fa843f0c7dd4b37ed3d3d8117864f2c913d928bee9f700024ce3e597abd468289f4ba5acf754817f751a831e80aa16566b4f413037a9b7fd9fdd21400a23091807b764e3010226586b9ab929c21f256d595a95444a4e09cc4e7a5ff59d047ea54260df1d4a7a603a3a18a108984ca89e76f6ffc7369eb3215e572e865e2c05ca1c108dd3e592371a7f7bb626a0dc75a6b3d61c907c9d3f35895b219ba5d50f573e739942737cacfcaef3f70fea9682b9104dae31342cfafc5832621000bc409403687fef64c904c7d4e9a3249c19a2b07c46eb1d0667837580a380a5bf8d4501792b51fdbd8e215e44e307251e8b962cc1c1cbe05e792db1e0d4870bceacd545709283af3a9b5c3e917c690e4cff620d02eda82401c1349026a6a9809c49f899d86499d1c7900523454b7623569eeca508db51e54938b1ff954bd5abfd4b2ab5c7f67e7a11adb60119a07f0bfdb09da284b1450ba5b230fff60c7f4be779eb8d3cde7c0580fb5205f4802e0b8d987dfa249b777f42cd3d213f5945759a31c79211426f0bae9a138c9b90892c8f6912ac582724062ee8b6192acb157553f51f6317fd6ebb50af0e51419e6f25c2fbeb3e2814a21016c8489f37fe016d82b563409565692bedab6449f7095c24e77e51ee36c1d4daf80c9ef363a2f9b3673a0b3da7ae46993667adfabb47d80d6d7a3d15e84d0c42cffbdf6a2e75dc2815f6d5e0a1f64aa7d3dd22a838b11f1f6293522395fc29b60109bbadcf3de1d3a461d75131580ed56675b7648064e12626c930ca131799f18d1a050b8f2bf5e84366b8ae90d4dc77f15e98a84a3a290b13dde5201fa4ba401b2959be9d60793da5cafc8c11cf3e891aea323b5bf15d0e24a049c82597ac0781bab9dd41edfb58268a690625911be4f01c7cb94d934c1b25176d969dfe9b34d6fed67f9ae47d57d833d9af528dbaaf91285ddc45eaf9122dbe16539cf24a0c3dacc44ec98f8d88ab7adcb74bfee1a543c88f4e2e62d022541213eaeef891e4debbe5ea26f90f3474e55099d4b7f5ee86eef7772989fe1d1c25d9420a2e0d332e755af7b835a05e47a493edbd291f92126cf8f1855f5c1a244c568838828c4d494aaf5b88e13acbd8c3d7c4be49b0b3fd8b7ab4c9fb630e8afa9e4bf719bf5632d8910b7fb79b4cbba77f5810c1e6fe7401b5b41b91c61f9a5940657a8bcbafcf34afc0e9a434569a4f1919307a325dd86c8c10e1aa2a133fd94a58aac7c3be622a6f994261eac893484f9aea9826feaf71c375f073520cc58a69a35ceb6dbb2c1c65df95947118cd406728a12eb3854ee662074247b417b5a11b5daf6791ed17986906542a20f94515f08f218a8ab502e1b11e0aac770081d293b47933c806076012139832a8e64423c19a1074e9e0de79531196f2ea0bfcc8bc150670f2be8274e8ae922f2bbfad37eebc9077f98aa4ee75a28f4d7905a64017a72c409e12e9cf6ada08ac13b2dba318d32bd083c672a6c614d0bf9822bf7306835cc4c6de854e0d688c9f9cbb2e1232afd03be44541fa60eaac5c8a1f63971da1ccfa9936179c5019c8ff81a58b59edf5999d04508ed13c89645c6613ab155d18e2619002fd0a72004b09d224cb4148f271591cb2a4426ed904fb2d2212ad06ad516d91c6bf5327f0eb369bce8c8b21e7565d02cea64b2d7f3c34c91a5a0c816b61884489c8b3276e5561350da01cb1a119a6731003f2b17e86c1b30bdc6dc3b26d9151f5431e081c634ea1a9b614d7ae882a25d795af76b912eb841c150e88557ca4b44af991b82f7de755d4194ffd7451cdce97a08e72519e2bda060743c8fdba876bba64d622d3a327b26d7b2afa91a01a50d1d01d185360b8ae0170c51968c9f474009653bd419140cad4617e717a1bfa931648040b94e327da062e27394520550f3306ef2e382620771b6f8fbeee6cd62843a3250439de314019c4b41cc44de9007f07a2e5080ab05ac265f887744c70327792f6049a1fcccb27da87d4ab427b9b4c85af0927600f93fdc236d2289da94542315d39b7bdb768060b03b1d14a9cdc60b1557c6fd8b6bac4542231bca105e635f3281e7c62cc2b1f48c7159380fd0d6216d9554aba836c201b849f2a605040e9121314be4b866d1a2fae8c523bbdff12f569e21fce88ffa46cb9ced93c840921992dea2efd19460757f5c2f64cd24c7715a82de62612724ae70c8ca5d3cc85b63cc9c7fc299ce7233846d0922dcc43f9add1a83bbef63484e773b158ee55a5d30f4578266b1dfa2373becaaff6cf6b7170b4c2085fc791eff1de8917f17c9431bf4cc8ee95f61713c630086899abd3d7f20e0e8268815fd7983d5f3537e1c3c468c2c230cd39f0139087845c94cb7b81f812f650f9380f4cb21477b3a38b803134e9a22c24f67d29dfa788f5121f5f0d9eb5525984578f241189170113154c11b2254cab4bc5341007ddf7059c3331775cfb74f91f6191831e9cb87307dd294081121ffada8aaa073cc372846273c03b391c56748ddf263f87d4a60fc4b995ae8cb2b20221305b1aa0cc91d7da4aab51b807dde0675cc29c484c3c02c3456307b7eeb667a831d523c695ef210c609ba4cba27c99da6e51dd302b8fdae82d6d9f6a9aa396a6c3e30795648859df738869d97752561a1d88841995749366464fdfcbd661586ceadb144bb1217b3c7c2f44ecbe837472b2664dc8332fef90f9510cc2487d88b5927c2433456419d301db11a097d3a25997a3d2eeefbef2a7a8db4f2c7245b8485e932c50919631c4ce6b5ce255fb3a5cc858c3e3544c506117a619f707bc19c3efba1d95b287af15f587081deebabcdfb4c4f908b3b8c8693691b096df4f6dc52a41305543367023d60fd114cc4bf0affd9b5d9ffa07ae31d8c7f25cc412930046350c203cf0ae151b4ca11593001a15458bbce8485d4482fce3d2cc3403c04c77aa2f20209eaf549ebd9cc1a981171ababf3ec2ba1ed8e65ca6551c7788d52d515a0b089e0b1524de80ab0fd091d1214e4d5bdba3565a7b0efcc62d8e0fa9f404dd21f4dec3ff782e6ab2b5ae3ee011ee9d11a0d1a78b1e210366fd5b3f8edc769c2db8fe731d318984acb0ac6f4623ee3da9efdc6f9ff6ff3ba48567db397df4ea289db1c6510d93632c53679ff55101b789440208421573fd0c444df98007f3492ae39f895998fbf498f649ed257cda5f907d6ed197e2fff53a5419db41ac81a50d483775cecb08ae4c0bf98c73fced128605c54cdc366204ea89b06d2334a7ac22c92bd95e7c976ce697aa0c4f2dd1c14f78a292df8a922e8fef5e70f3a71e6e12a3ecba8a005ff5b5f0907cde6addd618f87dc3af2282c7fd224c4b81489e5c21e04a303f14fd115821b8d2c9c0c87ddef37c2f8b75a38706ace5b753a14cd404d0070d8090b5b1a0c558531a7626b40b0793f8ba8222bf81c038a3873d56881446ccea637d62a1649fb1ef247d0a342eb4a80b80e50d9b3bde954cb36da862292fc562e6ea29b4e4540670f6091f8e208f3654dc8775fe7cb2d0396c9098912f3841a820514ab2888d1336989d54d731f343bc117e3e5ea9c44273500bdf4c1ade9f6df986799bdea8d7f971c6914e70d406ca623d827a38c06e8b3824e9792853e50587a4b6d3dd71d34debc7e16ca04ca6b341b156b7393f409ded9f6f2c15cdf3390434653ccc1001e4122760cedec3a26aef2447d1ea5ee1af1035e28e1c6bdfeb3b32736479f04b19a4a1145dc0c458661d5b7d1318bb173d15385635180271dc91dd237698a29c921bd0894c593b7baa8ae6a7acd3611b5d0f4412852d476bb32f265ad24625d3e0682a8287d30a28e80d927243ebd4d31f233fc390176f79482c591fb0dc511b571e2c5f3542d52f32262ce0dd0226d4a0289de5c40550e19c3a7ae98cb29bc08cf3b6839097693797d5ca83fe66d684f44f9e9044232f95842896713cf8d85fadad0cd8292644b1a544e5a1313668f27af4f17cd600b28b913f5decc071740c52d7743edc53cda78277d9aff4e5ee8155c0854b4f135d1f7ac80785ddf791093fd7f722f19b2adcfd954f7aa838f2fe64c22214ce19c24064dc86e1c55333c3c32439bc8e8a71e8124af4078a7e9e50762bd0f4331327baf15995e2559ba95f44067b3e51a2038379b66ca898763030856451c29b2ea529401bcaf41b1b223ef9984d1c50793dfbb0744b871ac7385b1bed63140508a63c8806d470c88868cd900442157edcab4575b1a4a4841dc59e05610a17881163a53193b4f46038ec16d3a2acbfb22a828e1a66624123169cb5a837c2b2011ad3085e87c50e9c2090861347696909b69c5e58b7815ddcd0643ba8878b1e8f036cee3022f1da9114688c17a2ebf421eaafd7361287c4c521181d54cba83b3817308c53918ee0943f330dbbc80cf4cc32b9ba6b6a4c062a8f43b4376f1d14b52559c7ad92155bd042d3c5eebfb3507f296f6d43c94c22498614015eff3695f70e0ff18ee349bdf38f952f695bf514c1df01bf656937c556ac772dd985c440922409ee81e639296fdc8c12260c32447264782affadcc5bf4a79dd22811caab34269b381426ce376540fc588cf3619c022e7eadc42b5922677c22c2ff713ca3cf107db447a08cf0613c5bc706a050c83c06471958fa6469a56d77e3f45e8d3c331a535823cee65ac7c8c4afdb19949f24181d7c7f21e1aa4799980622831538c8151cc5566f52a7b13833b80415eac513ec1c379d80deb19bd856e201a18dfebc2eee4f168918883e7340b94437016cfa000c73edfdcb415138dd4f2e7d1f1b5500564a9eac0cf699202924402233d3cf17fb0cc71c2d36a5756e6c9a66a4d0e82e54d53a42d0f6c6e68b4fc6201138af40afdafdb86dd19c3743f5c1bd756b3a0129e5d2912421adc8599fd2153517102b90b3e61e6057536869bc28db874754062177ae022f1f1b0d740d39449cd7180f6e9e5a1ae0f570ab236b4296d4306c1a3fd3f59866db6bb3853c890f8993b31fadd42ad1c44c9c054f18a41db4facf2a7a8a5a2fc146bd14040dcd9dee96424fcff3a3d942093bb298b8a23f9721ee12abfb3f889e0bf1fdb61379cf4b45319e071b6e2a6d6c37be28099addceef0a0b65e63b0f17706ad47fb2cc73a40de4dfe018a4e3fa93c7b873a2456c35610a1272f488091e34387fd1db5b9e5b3fa673dd169652af9eafd7070bd4e353ce961b0b0f58259760aba424f2216f090ca085bfcce32299bce70cf9b1c4167f08ec2842f0022eeb47eccd16484a32e08299e9f7f6688c3d21d74780c284d98af5d22077f2b2d8792de1960e21ed61e1af6df5948ffb2f92d82cd8d00efae054c6730f75d06e2287eba7416dcefe8bddc2fc68b222de614f5642a8a1787b4bf652411071c6e00b058da7aedceb2923578667f49be6451e2f68336c6a63a3f1c8dbbf2126794ee71fd02d449d1b0febcdfd42713447a8023cd91866df9eb83cb0c84b2ea1de622cb3cf144613d6f7a002d159653c9f96d02de3d21bc23192c9f78f911f2c41152c4066d401b95984d314f961f38b6743f440a3f04550d4d4e150b7110d1b91be8944aa03a26fa598db4a5a0f69e75b533a2207c46bc39c351c9ba7f223fda2324aea7101eafad809321f8415dbab2aff593cb49291b7e8945641d8f425cb352ec3a15786cdc3597cd032670839a4c551ab6574edb82c6e76dcba9333268c043a2982fa0d4317237502c90a1200ee11e53feb3637ec36f551f122e1e50083ae2b39be1cd219b0d78c6958e6b5cfd88996fd748a14e99a6d2fc2962bc114690032e6d81fa9d2ac6aed38b94d92af501ff32c9b3ab5aee0b0e76d84d5cd235088e4efd4d536a88ce0b512f98777f9b0198e44e865876669cd498d3a55ab0dd10534d18582c409a543c9c9f87a00a79e62382382765eba9bef2b1c5e91be0031fd9f3b1fb2d76b661d1d0f7b9a0cd08ad31881809c625913a6eb18fe23f50f119e9782aecc36fee9ac6f56a3b3fe0c5240297a01afc7cb0f0675af297a790755a4ccbcd4f1866e1f561b5f03e8a9448e57528caf7148b9725cac6831ca982740bc1864a832bf1c7df354412f5e31780a951386ff364ea1f01ce880fc974c176e1209efff827a6e99ff93a5626451cbf51c4bc89b2f14e5aa376ad61e9fc22f7fd42b0f4378cd0326b21031805568ac8fd7d64ccf4e872e7cdff021f48f7d162205911801d4a6ac2cd45882dc7e02c465034d976c358a781079831f87a889242de0921ca6e9ec53f785ec9b695b3cd0bb080a6109a2d5158e9d9573bf42ab2c6f3fb93baeb4cb45d84d16ff83ebcf879cacef6a2c47f6eddd47f08cb9649b0b1ab3c4334de9a157252c42ed94d64ce65ab4666fdd06e256489ccef93a9fe5041380b0aba4610c03a7cad4f8d205c8c7477cf65e79edf520e85f0752dc4f9427bc42b9359c1a8498f935dc47fbc843d4fff6eaa3d0eb3a361edfe1e31a10502f073561801dae02a117682fcbd044310886caf0f756bee1388d1c260d1e0dd320cc47a959354d31a31e6f8aa49c74a388f2e6515dfb55b712cfc9e54397d24918b47a2576194194a6efcdc203bb3e67524336a62e288fb251ba07d348954ef92fea075d7e50785d56376bcff1157cc85e2670076c58a9d2ce39cb1be88abbc00413d4ccd164c11e57b2f8c0e122cebd75ad03376dc7da3355b0ccb87dc125a31c3176bfdd507b2fe2eab4a020b0bb85a2e75a40b46f7e25b4b7df5dd0a92ccd01bb041eb5abfce247954c34fa5a4b5cbec62d0cd0860db0f1a6cbf9d5f106e202c3c76759bd8ed2d8b45dee6b1470428729ea03d0dac9a65e9da2048c7f99ce04c3329559fa0290eac1a9a42e1bae853424cfd3fada637d50067aa58d1d4d943015b03c2309ccaca5a209459c4bf2c301e4274b1e3045453a918d8a2e214db618dff718f0e8287927480d5a3ae118be697a50cff71b45211e65eacf0357d8d8084a5a7663313d67403bd7ecb9140c76fbaf2387e480f89302b47bcc31d1e6d7005b0b9e5c5d626e9a6619c4ce5587637b45c44a486f7c62e075adc727eb01cb2213bcb92edf2e81be9f2a49ad512fea35fd981727f3140cc114f24f28e9ce950f6e1d41030ca825b20c02bafa07da5283d31726260659ea0575a470e9f3862612594ae7da751efe342fbf919a5ac503522de63fd4cdb644d7560d435f237f7c22eabffdcda551aacaa33bfbfd7b264b0de538421e87c5b6112d4c10fa837a670872ab3fd883ca6be74ec4a41888ea76efccbda23a48f3876c20007bb5e8fd6608bcf1f22036ccf763f19291926e1ae21061c1381f42bb82af11bc3bb8f34f1d25c566966289b286af824e626be44fa609fb9f8e5c280b11ab25da9eae47547d1134ba4a20c37fa9f9c9119b3f0946765dc30ae229db3828469d37c4af2a3d43255889f4da75691e9009aed63ec762f684754cc16909e6f55c5269ff4ed7626917c8a264883124b54e4c661f42400179867e83256dd6c8b5c70a6eecf33f69fd2e3c04920a72f5f2249b63e1e08881e8273da02e502582e13dbe9d0b5d79b1b226a8698609fc8716a362b03401118b9ffd1a8d4151e25d3aab6864751a3f82db0affa91866f5748099a7a04ecf6c582401622c3aee7b56e9926ed231141c26f1034408199b216c95816741706ac3a241e10725fb30888123fbcd7609ae4cc233041e412d88d21b96c2c899f9d3464d3be3ada75bfa0a939522cb1222f6fd2d201250460ce0f1a7fb9074a0f6fa3b172e262e1724003855416d8485c2d780f07f1195cae1b14cd3e8b70a23cf4667c9913433b4f2ca4488685c0d83f58f24902023ce9091da8bd491e977134e6d24ba3e0d62afba9b9f0c9596ce5e207a75f02e05a488621b6b1b2f82eab998582f6da1b507d69d16de20539f5ea50ca1e89bf3043c3d2bb158ea3be35187e000b92298549448cd4d5b896af4c10ff7c292a9b6234888e4059dd68e03f9d2f6ba50284c55a63594c727efccb615a7dc0336d3641caec9c366f569ff0f2cecb46452264a800c5963aafcc862f4ef840e4839dc1c25c64913bb18e4943f1bca953e7bc4ca7238ffb61f87ccb9b4f411cca65172d82a9b37ff2faf9238ab9a8cd548f01a5c06a2f0c852b173ed8bb2f16e4e49e7491a0841bc464caab75ddb9afac16a3c4c9979a16067072e41ec38ac45384fc7db175ce8432e258234b709460386d667b25e97f32010e41cec2cab1c959c68d23b09d014d1421ec2b3fecd14fa25e9c288f5e508fa5606c89a8678bf87d7b7c938002486328b9b29b627d0f6ebf8a77cb2e8a8038986c07da3a8b718c3fb80602705be98a904bb2f54859cc16434cde0b7e8a6de36b5ec8a6ea22f10f227be5b060f1dcb994fad2f44350307e50d5481ce1a3cde7147806383acb56cf1cb59bd25244acc1d62e14a82954b17b519a1cb03b5d4103beb62feb96e13350c9c7fa5bb07bd3a2c8ea44417d8a935a9e6099406c7b36215c35285a0614326bf40fccab901a9a0fcb2319ffbade7f8d15ced7f3f51f979809c892ac9f6bfd4c9a2a1a5aa04a128248d3cb749415d418a4384da9bebaeca135d0f28ba169e4947a16ca1f52b82b51b5263c99d50552714a9a2fea1a5d0bd680d2fe441f8abe121960053680300e3f56615a750fc1d8e8bbdc1a74feb42de4f686920a55b33c9d37364c1261f9637c6e7ca508cd210914189000700c7299658e9f007bacaa37980f64bd6cb249cc42f44e8f86a0afca5ea0f982453b72208aa9fbb69df5b2d91e1cbcefebc21027e3be6e9bafc8efa9000169e7fdc38fe27e534e786e5b363fd2fb3c706ca24bb1c43e8e4d331239503d6c0820a75a673526b2b4c8e33d810f0cd432caeac2961cae418d33d3f5fc24a83452e897af28d3d2cb3e94d177fd1eccfd030ebc1186974863f6fbeed1d5b16ff8eb4fff18629f25349caeda0f588bee9bf6342c42f5b264f5b6c882fa61c8e9d80c4fd68019c6220c9c04a4fb50fda44c1294f0417e5c37beb768112252becc0cb87f4f7b3668e7acf882e119b83517186944b6402d290f7c84aca9901c8c2e39bf76cdc6e6dc58affb9a31cc2eda3b04ffb86700a2cffaa7330da0604b73af6be18bb8c028958709e814773086372e6f300e1a31df307488514ba7a83b1776aadac1d20c306b12776e8050edc6d21e3cd6e23910a90de67c3d41d621aa294394bfad19c0cade227c268ab93bd9c69fea9d554c4253a34614ea3eb7565f89a7b76c6294edc4359819409b436da27f11206bd097b26c2e4d4e0b551afde56514b47c71407c1b1faf7470958c3cd29ddc98d4d57782f6fafc9c9b673e30ba8387fc5d09b2d6af4fb50260aad9cd5efbc5232f9c79b30612b7f03f037f16a8a5b587aa1556ceacd8c1e3cb7a936a7dc132341ecb5d0067578f8637ba3fdb938deedfe74d89f87891021566578a7f2c238794d73d0571a77b344f2989ebe251e7e1ff9029bd686e1608af6d7c5b275671f67d2083830134ab23a331c5f5d8208748b39f75da694aa518406fcb20273227668c7e598121a8f82dfb5d84f876fba9a258d1a1817968395ee75da396f59abbd3bc8d3143a11a434282eeac18c25ed67af3b2733e69375ecc8b0e604e168f2f6bdf41a4c9f0ba4b7b622754e332144a32a7678575beeffc8cd293964509b5d9bc054a0792e2d967ff97e72a34fcde286456d9a94e248963c6fd50338647d8996d23f27298118e03f44699f28d7ff31f31c5cc651b47df9ec3b9b869ac0bc4f66bef4e5b14c0e186b0227bba2e618527749c1c041b9e4b5c39abb3a77e9328fcbc0cb025d4225f3bb62ce3504f467d07fc5ece3b1ba8a5ad7682a503bc7a6502f37f4f0fe13106355c8bda71d12797aaca34008c3a006fdd066b64da246671b4d0f37152bd4", - "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c92208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f72b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c22f5ffac9352fe35d950a4ce548798309f5d896dd11f304f93d83cdfe284b37a6" + "proof_hex": "0x0000000000000000000000000000000000000000000000038e146b049f5a2ebc0000000000000000000000000000000000000000000000018fbadc502ad14036000000000000000000000000000000000000000000000008a2cf332394158a790000000000000000000000000000000000000000000000000001f710378be6e900000000000000000000000000000000000000000000000c08a0a49dcc1784f700000000000000000000000000000000000000000000000479a1f04a4849464900000000000000000000000000000000000000000000000ecdd4f70261b68b8e0000000000000000000000000000000000000000000000000002bdc67e780e270000000000000000000000000000000000000000000000068c088e96890e815e0000000000000000000000000000000000000000000000019927ec72a8a0dea300000000000000000000000000000000000000000000000bc30ab5296a7e92200000000000000000000000000000000000000000000000000001b1768dcaf05300000000000000000000000000000000000000000000000514324a6e0f591c0a000000000000000000000000000000000000000000000002e31090bcd0a36cca00000000000000000000000000000000000000000000000eee316dba6c9c144300000000000000000000000000000000000000000000000000000fea047711602896aebdb264dac06725b3966345b9e4ef9019cf6b810f3dcc51860c5764920f2014eca6c2f04e7cf3b91e2628946b6a3718640954ab71d723bde7bf502a9d1a2f34db5ecb0806f11af806f3bb030c60338ec88b64ffe73e839f5c5870542b041441320f7b67e34ad210322d2f5fb516ac74a4f27d68106a4b6bb8862f403fea0fde735d225bf57a92f107fcaf3280cbae16c9c7029987ff0a7f4ee7a20274e10bff5a51024341ee9a5663df40a9283f6b04fc17bae3b7bb5a1581785ff55d700c1e06cfb9c565754142bcaa7b5b4cbdfe8d1a68400b427ad5a3859a91572c9a07739d0572ceabbe0d52bd4b99c0f620ca8a5321b1b804a8a139818f04a251b12a1ddf209a0d7b853ec134beb579539c5f1c5c12272c86207ade7f36723a0c1808564b49f500341dc967a931ef95cdf14fe2d6e213f59a4c08241a0a630c0a20094d9edbb54cc50d8085bd1ae3f24c32b350465caa4ee2c1f3f0e98d99ef1f280d566dbffac05a37803f1edd0b55a4915e64dd5cc8040dfa02d698b48ed45c2b0512682837918095625fc2f6808d921af9f7809b5769a1d571d5ae35c1ecedd22eb10c42bf1697e343247de4678b4739d8468c15ff49d0789daad865c2a4dfd520a710c69acd71d2e39b1b66bca361a77383686b095267d483e9a65a7106fbc129b9018e43e5304f0560ef448d298fd8c68d0bbfa7f415422f83b3a7c0c1149d100a6294b4a17081217254d550f784ac02a667e1d23540040f976e34a6d2059b0ee597f3b9db445f113f30e5498a99ff0bbc642ec6ec2902fcf42cfc0dc9c8ce290fc0e267a0be0f576ff0af58e4a68a01ccc44047c891eff4f54421844362872cc5f85a0b7a4a2c5683e6927c0129ebf5aabd8df471e02ae39565e11f3ce0831c28465d49309ddf5bce3974f68fefaa445b99e5aec1d308991ef090362bc9611951db034f99dd86a24366d10c24747aa78338a374f51595dec1de2f4e35604f0fda9c6dc2f4a1d965603db25df75368cfdcaf4a4e5c8defa96b67e839008bf02d308407c26a577d97b478ea360252b08fae8d535c3c54649a262d2c9725450e1db0494dbc8db5b571cb6e856c23cbd330154826c02ce5ba29b4bb2720ae713e0e751541f9b404bc211cacc9691d3c0917a149cb51e4ac5ddd944b7d1bc9668d2bc5615a7d472255e69005a613c4f949a3bab81819ae67ec962bbcc2635ab94e1dce72569ae638d012c6ca6cf21634eb535f80177106ee176eea6d1a4c3c3f032e3839c2b0f5c9165b77e0af36691c7451341d88be0a519f7a87b72170406ea908f56f676ef12fdbd5cdd673d77a9fe1106d29f5be7ab396ef83fc3cdbbf70152b69c3d9cf3c2da2f6e32224297dfd0d86e41bcbd3a04343c5c05547f1fa94412933a7e458060e19ce1faf9db3c9ca6e069a30c1c4ee3368ea06f3f29022bfdb118c7245fc64000f57ffdb78ef63bc87ee99108ec5ae0f3b79c18eb49a5a87e8174a8fdb7cacc0af74eb2668d45b1d999a92c3ac7234b0c504b22981c0f44fce2d45cd8817e21eea6a471a53ceb7aad4e2f20efe82889e70284075c63df31f1802f49619073b093b611c5d89f0b69bbe6510cd5c0098862491d94c229b9fefeb301a1359c653d0d5a264cc62a8ae6c08b988e588ea96f11355bdf497f26207530a887415ad40f0da37e84607ef267fef2a4392874e3e035db64ed8b4e804a5bf27ca07f83779e8ae2cf14654363fe4f88f25093ed819e433a9510367bea91d9f2db1e26383a4c8a0df489007f12ea00f91d6d00a39eef342cfce4049cb1005331a81e08d651c53177fee573a87fb0652bf0f8b6ae2eabdab0cd53a222a97bb1f2599e409604b872622a24d9f0eda3e56a7cc78ec9780f28a329e531e56b416540277caf2f5367e0c6ab8463bb850496fb67960433bd6fbfdc0f384b3734ce64902f8aaa512adc680bf8b4ee6e8805006c28ac3fe58625d3eff6c55ed1418b2d80af9a08acd5b9cb6bc4dc70c131282f2aaa7b77da31a88736f45781f136f23841e512e5a081aa544a6d5b4b7da8fcfb871abc7c2aeffb7d5c5f36c3a47ef542d0a2f0e77853444ef1bd396138b4692c68bcbc71b49273d6b85c43bd3d636d8171f9b8f9e1a6cce6b155ca1b8941e29f9384fb8675df1b925fe9d049934b57d2e0344065e99856c0f58560c7b61969d8f8a1b22185d7cee49e155dc41eeddaf392055b9fbff12a248c53aa009b31d8d898fa411dc0ae2f8832e8286f7a2502af118c76caca54feb76a5660e2e925f0a2fc3e76a09df485fad25d6454f2078345d247e5986d23f8130dde014cc70ce4d6600102584d37d2ae1ee3339cca8ebaacd1b0bc9c2cfeba8bf0d26baf663766f1a41511b6643f268b8a4b18e72dfc133af07299dd37a8e816a3301dfeb7186b727fcf0d937e36aedf38061ec699e43aa9622ba36764425a1f2d8e63cf07b25d5eea48a81658ceafff1eb99284fd40ba0ea0e8070b0f18b508f8dc8b73ad007a52a97b95997da9c88e51d725fb0322dfcb52b15678857422cd69a0a38d0b739c4640cb3a65bb1387a18b9c597802a7008b307335192168f583e82957952e50c1bb0872a59617318249d8f63aa6db33db7d90fa9601322e78210e75087f0f74d98f6de55c577d149bb5323f10274cc323a571e1a58d0bd353b5cea492d943f2b9ce4a7c1c597290edf32132364b69af83381298d05687cd395bfa6845e299ba59cfc67314a07cc4347892ef618d0f36a3fb62442db09f8bd01600b9d4797b1a1855cdd9c2fcbe56ddcf7aa3c9a6f7808d7d602c5ecf4a2882da2d6b21cb057b030bf46f1c58e6177a97ba2aed6b67bdbf42715a3daede130d745cf8524e663319b106661a34762cedd498bc1f8670ea78a042158e87a0661db99505ee3a6aae62d8ca251044674fde2521bbe3dd236f6c35a2d7f3bed582584f5791362da4c8b52a5c85a7d9d698e0d2ca59ea2fc4aea21c2256ee0cbe2b992e43e977d082c1f7979c49189644d6e4f56a293347de835599703e0ada4a22af6d5fde5b44821ad479e26d73e1af7994d26d04794cbeb8ec61c05d6afa779be840e98bd1c3ed40836c5741c99fc55edb8f7203823f6a50dd44e19c061d28ad101a955c8358078cd5490855af88af9eb62704daeea6382ddc35400d11c2b43f4373b045010f285bd78c8912d74b74f7d294126ac5e72155dcea405e9031605c04becdb06f8b84e244e6baad4146c7dab4572f9d1d4ba038d452018a742401211aea375ff3f2b56acfdb64639a433002aca37cf20cebb3be5c60715ef324c76ea04192181fa5121703321e84713478c02f63a9192f50067ae13421931e93801854687d8e60ddd847100095fcd9bb2ede66d370e647fe4f0d7fc3a2f363c468eef65a3793cd4bfb3e56d79ab68a3b09be67ab2bc3c2da6cf38eb631310185da9dfb841b5467e8183bc21132f88582589c25856117e167d80dd692c076cbda0ce6d6d2d5dcfd89311658456916c62de398c5638e43a843366d817cc0ddc7d7de125fc1c474608004580282688063f5aca6f06690dfc491a8ae9c5c02ecb8837abd0b8af52d74854a8f15b57dad2ce4346c3a57b9f16e8708730a6cb064b6fe9fb0089a65dd35b6aa970b411c78f7b2aa5b9400d87fcc1acacb3e369171c47f3230a9f6a371d420d11eafa30fd4658145178f4dcf5c28c34bb19e1a123b681a4887939e0a351aaf8fe25680434596806ca11ea526051c082247bb87a209e2e9c586721be984a8f864c9d3be80c8c7523c21113e1b18b3b706aa7d8c21d657c054d8769747e011d716dddfbefdc0cc7fa8d471e1c58ca31cf89c1d196218dc8b201131c3a34389ce3fee2ec9ed61ff3959c451e5a0f020c8033efcdfb0b5bcf51236b0b6541029662b8a3c50376f053a85124291f173411ced41748f009c22127832ab9f36659c9c87f116365b90af9e2392f5e92086b801e6fe6376a2ac461be885eea8608ddbfa4db732feb4e936819e6446f9697249b38e1f716292348b3287168eb24314e2425373dd6756868bf66717995f106cd9c871a5a68572cee61ede06b11f37d231a609bea864675dec910763e710754c461b778f906bd095df757103c44f4bdebdbc1409647a6892f67ceadc6bad48589824e199226432fafb8eda60a1202f598713bcfcb94681048b4980b98760455343316b5b35a9b295941a67f7b4ac4f1a24c8b3ed045247feb50d3670a5a918355ad64e5cf7e302775f423dd2acf738d94430a22803d157ac60ae399707c08adcec7d41e03d118052a31d8a16b8a3aef7c9830df6815906f0440531b33a10571f1ed27d84cb75510495df8efcb714197c94f8c9fd53b198efe93334064d84b5c6c22c822c643d02c3a053da66d5c4cc774b5fd04740d89c3839be5157aa9ad2d2169bdecfb020c002199232930c8eb7fd2e59c70ca689f838a282b03d2372e35f3b52fb332d8e7118f60a499489c36626769c99700537dd8f5e80422d92dd0683b5efc3f21690e288089688a1421854d4653247bb20bed1dccd20b56c9dc4daa52cab2a984fa03024b5ad9af64d50703a4de10752bf3a82f835938640c7ab80e0a0190b0a2261d2ab6a42d129566586bf0db21334d4336c2754b368cd5ce3bce6196007c51a2b52cb2a7f29f13b502784ba00d49726cdd8f2315488c2511acb7f64acb5e86a6fd2506d1d03a141d4247fee5336d6210577a55cb80c60607c3ddf33d9e3ca1c46f0d62d50ce69948edfbb234bbf9216a1aff75647b311f859620385c0acaa4193f2e5f309fabc40436ac440f39da8db73698e0a85edafc0836ff33e234321278352394536d28d050f56da00b11fbb40fd5f8a4566c72be8e5ecfacaa7d38c2cf3e0f9e2384d56265c064dc420fdbc6a73f8c0e6e04b644e9cbc61df97d88e9c529145184b011f9eb91fc5096842a3aaec5b6caee0899dbc249e19b2d0a176d35b82cb08a43f16b3b82da140b313618120d6906492df6fe2f189703dcb70023b64c28fe97dc5ee19d42a34cc5e7abadd24ffd49c250e34a4741c8956e6616ec6efe20ae8c503e52ce778350c20b65b6f09a5051a7e95694565ea4bf3cf1f7a93dba00e28eaf5dbcae67ebfdba29b7068b913ea43126ce9a9a39170ce0e05aff43bc00119d5e9e43798de0b003442bbf4b3e07af206186e1a96eb324bb77eb0f8c242b4d00117d41cb40900378b7235ea285c4f1dd4cc09843501eb4e301e1a7fe7606f17485221a20da729dca07ec410db76f9c69f7b723e209e79a605b5fbfdc431f6420d8788a71c1af68e3f2d8a68768e632346dd1cd22d6a4a2c6268c6224810247c6cc0c8cd333a78b736a02f0d029e760f7f68589298a4919b407290a97dd2083d9af9cb142c55efde8b4c69193ac087c976a0dee9ee7b5f2630707a1632209af5ed7b86d5f08d8abcde1dc1ff387ad7a31e9388ca089e2f5f8340d54aeb0232f5ad00ce41cec1f6647b1edd63ccd207531485b06b8874ac25d769e17b22c1c8f6d4b1b5251c23e315c2f0a6b18b5521fe3ffb7fc36c156e935e34a3d918c2ab04f3bbdcb1307886b001258e4ca0efe37f22a80fbc035788ea7704d8681a4277cf318460ff17c8f2790f78a22a05264e33715a085c7429e7181944af22fb329334c5f59fed35528ec577f296de352adb6d95822e85dd1493d81be726aaef9165c3501ed7bc205db385083ad76207c9f586daae7f8c78b4ce4eb3de59338c426c7fb9191c9f897feec6174af661fd70b303f6f50a23266aa7cee73e15d320a0cad09345c02e98c86f16b6b80b94c18edf982583b4f96e047c1fbcc45c4205618b64c24cd8c14c082dc62b338b3c048a4293481ee0bef71dd3cdadd40fbd4f419a02b9f28c205241b18241d5e117306e89dd23a884a3fc54cb8079a924bebbe11deebc2e717ffce26862b8b7804bdd09db9704b0623a397c429054666741d2302640a3ab0551496af654bf68fd032082432e7e8596aefae1ed73ec7546b6fe41f13b641fdaa7396a87890b29dd70425b2c60daf38e4fac39375c7da128620a42cb6b472caa894b66488e4056e20adee4b5051750d58f3c8f9e21e5f69dcb03f0eac9e69b653a0485052211d6495de5aa9b499a123aad61eb126d430172d07e2090d8abf78ae916581c51b2e3abfa8c68740a7dd7f47dc90d8ea573a6460059a273621d0211dc09cf4274902a2c2681eb4d62fbb27ffdc5483473734e87e78621e05869d22c3adc90ced561fa5d5b023d494c41c392a690b001dedbda3dd7fdd20f479ce52973dba498a97f2358bb08cbd67c571c90d6cdc69a1fee0ad59de442bfb9bcdbd21e349693af3522519fb8c32f21a588e1f426b1d95683e4446ef340e9b664df02bf2b8ff0ad4148f50d517ff24acb4b9b33a01c3e9e0d13ebc789c0b4686f8844ffc6f638b7a90dc67bbe0a6b00d0174f3135d48eedab7fddaaf4c165a4460fca15426dde3b0718d64c4697d296912edc702e1f5258f0d26939ff4220fb00ff5792b4efb016789b009d6812f1c81d5a4dd82dbee54d9864a15068121598b4701b74147dce1853d2ce745710ef36f85432d5bee18b5fca0662be8bd0a622e484d95516e6a425a4919a4fc52c8764d106b4bcb6242dea3a8b3cb8b58306313fc37588ec9f6aa94c22ab4151908b675806ad6c9a95df02143ddc913ef054bf6e35f383708a2d5259d0a2336ddbc2a4ef93772e289a8a1710c78898caf0dc63980253765e3ec76174601394171065781698d149637a81dbf295fc6fb80020d85ea2aadf1f6b2d34f425aaf14d4a7b2d32afcd5eafa3013f9f76d12d0e70ca208763a96a78410d65d925326ac2a85f000eb77f77517672807b5b279bbba1914c7955fbdd9ba738021e16940413339f9fc19702af01758a8964a3648eb5f2b38cb8f8170de80e05194bae8de9fc3fc98e7d99456bf7ed4e9280dcd188a2909e5b07d0f283c47d1f7f9f0703a70c3d068d48040b976324abea0d70af38dad20fd871e487c34122fb7fbc2e808ce4923f17beea6b0c78baf481c0e7a7782e6301d886ffd464f271910ff70eae27784767ee6cad1b7c706568b8448f2ae1db00007db90b02058d7c9a26e29392545cc731e629682b34cc76007a0e2c672503c02b62b0a2861aa8b1541eafe096469fd0fc7671db638eac0d7a3c861af4e041b06358dd08a1d1fabf41e2caf56c93619dcaa1c152f84f123b433b5de6f526d652dfdbb3cf6ca3b495bf4e119d3192744aaa057e59b25c4dbcbcb1551531c925a1fed585cbba1e3e4832dbe93ef798ea3104aed3c15ebb35567a1b0fba49c080f1b259db8e480c19b86f74d3f12242ad8ddf276551b5579f889a925699391618b2fc3fb82abb5dabc4b164696a158efa88188d644926aa14601c5e62ef22038b12fe800ae4bbd980802c5321da39fa532fb6546a3996cd3d7c89e3a22e390bba90555e59695339ceeaa217f88e74826783c3a1efa8de6714f2275cd9067e427bd21bf502ff800c2cdef2d00ff1baab89b91b68c2c0541964433c87a099d90ddbe0a8798761cd55fee0f4d5ff34e9a58ba87decf37f3cf420687bf0371171ca8252f6e8b2f4617c0fa96b9c65542f0cb40662789e0f2e00b7f05db17c528cfe216108f9162d4eefd554a028a1db73813040a371fad8c790996a7084a20eaf0e67507873b7fa254aa0638156ce5e8b739179fc965a19dffd739a07b3a1a6a71bfb42c1b02e90a38270855f423d40e83bfa56b31993377625174ab859dd3a66a2c880c1aa42a078a47519c83c3718af991f3475f0951012ec4643a53495feb2f534715a02251c6da883227283e90750f5c68b6d14a712291ad78b16e8663ba0ada3d25809bd3d1c6bf98b92f28bc97921c7b6cf346f57797a2f7d8355f8247f29fcf1e70b2e59b61d4a90e90204e3e843a475f1525a80f27f2c139aa440bee68db3b1b272f57c8719c8d01e533e12e6d01c477823c46343278281d910d26a6c4f3c32f8a20f59a9785a883d74bfd8f621578d57a4b149b01eb7cfb4b6918dcb7b68f139b1ba1b7a052b79be15a40085bf9b91ec9937f78e54d385f2a09a747a7138f2e909b486a842c9943fac57c89900e32d4c0eccd701f3d439fe00aa9bf910d2827178724f0614e8084a6c472f74c0289623b10ba0edf264e88f45dc9d80079cb17ba5e3ece6e6c3c76f57b6b4459f8536cbb511054face7d1636809a52d7904d0bad02e0ef3e5992417d9c6c356646d4ae6f8ef5ca2f8b45d00d22e7f3f43a321d402a81c0794c86c6cbcc0b8f0af9cdf907836ecc4dcc159ac76e7e9b2d4ab90476fefeceb4acff387ae5e1eb6abe96c97be936ff5523f2b2767e3e3b1f0d4128ba97d019c749862c304731f9462a2d22583235f00df1f812f7fe18ba6afd24260768907290435becbac3106b72c5289bda8f55ac47bc0b45a0337cef3f77f51faac7cc16a64e8d42db7fe0de579a842761f013e5c1ac835ec7a7a4d5a1c50e061863ba5f8315e1d829c7c1f0c507a69075f5c5d39c659a49f0e766abbf080711f8623621c44771dc91147349f6208f5578b294a9ebb3fc3ed64da4c19c98982ef5d818424ad862d1f5dbd75d9200c40ae912792a93633ca6fbf0bb199000da10ca86481b98e624f81d5c0a4acb3bc1abc06c5d113683ec1c40e345ec9ac66607bb8da713163665fd11d4c11bc6ea47bf2647ba10234b4f4e7a20c32f8ced0d125d027030726358be036baa138cbef9ad966d57399ed14cce73aa45921d5254099bd00d5f347f292bae143b759c9448fcbcca7ea6cbee944f8a00fc8aeceae222362079175d66ee0c74e798127385d4e7ab0103a050e05e16345d82f4e3f98b1ac557f7a3dc1a6d7fda9a7b5ac1ed91ed23fbaa48afe8376e6b3c52e0c40bd10574bcd3a407a0a700420cf3ff6bf4e9d2c92603b0e3ba2fe76a86601f5b58b32b1d5c029f31a366f0a64a899988644c135db3aabe1188c1937e0e67d43784cb259c6df89d2678b2eda7d8f7c59d3742b405fc7dcbe70eb947e80cdabfe853cd0ab21de8f0cd93b0e975f77dfa85612421e01fb87848c26875a6423b34de9a820d6ca2314c7d276f56fa815f374af9a3c15ade27bfd6c74a94d8df257ec68dc401969ab545a44962b4480d7787827e9220791ccfab4ec5ed310f331ac84d41d620b975acd3d6d7244e0c9bb86cc0a45d5b86e6a8e0a8387020337602fb9664fa1f42a5e9bace6f4a0adf7a680459ac831e9148dd067aec1e6247b703bf3fd541262169439bbbced69403ebe737c5b1bf602ec254585f3533875af458d75f4b6b04904c93b04484c354815a73fa7a2f84c69b33822bd91db96ba88c06b4e7083f11ad2f5ee09720c05014344b0a80575011bfc838f5691b8401628a0bf8f137e10ad62be00adddc2e69fdc00dac0bba8a8dd99116dd8c0475879304f478e4d0b419780617665f19a173d472938bde6fc5443a221a8bb6ae4fce38148cb97e9a3c2faca00d80752a8daf6e02a7af2b014f0ad3d05f16ad4cb3155fc320b777805b1e7a4de59fd26fec3113ec4ff03fe8cace4e8a5c352032020155f6df973172802d4120638812c9ee71880c4e97cb48d2e1cddc2777d093912a2b6ee71de7d54e12e877b827611ca16f81f1dc67a480230e997c5b8437c9b4a99997b227bc034216731efbe60e9f9b3fc4a0f877a973aed8e2a29baf5de5b7a63406987c15e0670464c90090c236014d4b77d3455b4d09048f5123409f32d104cb7f6d59f3a2550f13d808471f826a1338c92e579bd02cc4f12e97833c52547dc31c1c207fb3f7024ca9687a18b57a64626be3d6c34563e9bf612404c60dff70ca16804759818329d8f0fae167bb6649e51caa8831278645f950c16888e24ececc5378976da9c82c894e200a8ef1fd2dc188d41e70e336ea373cf1ef81751147d768bf50c2aad418eb384eb68613754260e9d2bea84c850924d171bbda4c856a78b74f4bf9e5242bebf28c2c3c53437b81528c77b5e14145392417ac24b77b634ea8709293dffc03b4d4d57e67ce15a550d6d3003bc839d1beadcddd0abc5157f06c3542ecf35a030ef12b79dd6dbd1f86bafdef9613f63ad08a03cf7978dbcbcb9ee67aff50230c810be573ac148f794c0650424b0e7992692d96f482481aa5638d16c88e31812f60dcb63c920e69f88d14ff0d18d076f8fd0faa93c4b8c2bbc6f41fa2c0857f2a656284d2218612e128640002e4af05c6be31686edf9974791630d4ae3197b8211e31a04979b215d23a9834258fe87c12d45d77944db3606cdfbabdd010a05c0d3e23bc252d9cd18a0cdf2d9cc94865bfd0a803a4ec27de80a01ffa5dee9b9e0ca42cb9c506e92f339d0f1504199b9e624a71e581ea1e531e48659f75e7ed992c62e94ad931b28ae7495f5fec6b73eb59707c8cdf58149dc4ac231f7b2b770911fe287c9f702d993205d349896c2a4e491b9a6c5f923c6195ef8dd2d064cce0249c1753b3c38b42b2e67c8e75d0eba600108c3c400fbbbc2da699a5fb635a0707212d96fea2841bdb3a184a7bee263731ef6f4184fc903ae392c4012977f4e32b41f65e453c9892c2f685f43636eecf49d451735342f95e842551c9f2817dd104b05865b8c0e3fca15f8c9f236b8ac32287d9b79f1a2b2a978641c76bf86e1a280d8af6b20b402423863ffad8270c35656613c8eaa25d2bddf64ed473508f442a0ddcaf8fb65ff255b991762c99df15a0f3e406b47e367a5290e9a93502cda808f5003de148bdafa621f1056d271f7d5b3d9ed3b3564f7ca2321bcf5e9f728e15e13d7b117abfc92f42a4ab68c260e37f27187224246a90a2040d0e133ceb3b194a4dfe23338883d6cedf803623c9e7e319320fa414208a7e9e083e334e7a102ffaf3053c5ec4dbc1afc1fd0a1e2ec41fb236993d167299e4ac66fb39a6ff7b2dddf7de35183aa28dbd3e7f55085df1e85823b7a2a1415afa97ce1fba342d0c1b76ce8c291da4eab121b9bf1ed337fc3eaa48c0519f81beb378a21cf30de0211a36c491e17075f9d390f74af0c916b4b72a52cca839a8ec77453f5069a11ee606a0a221273d418f367330a250f685df6535c0f011535a661f5e0329507faba7086cb2591cb49bb2d7f9f36cb318bcb71b3a0c36a764de9159da2b359b42bfa40c2be677dbac3cbf3257f23add29381424b2c501161f347275a0eeaabfe55c84297b2d313b2aefea9bea73663caf91539278db07b06b54ade556bc5f863552870016e61cddec04e209f65b23a294dec5f51ada9a71877524071a6ea37e323abf10477b2c9ed66ea8cdd54cea94061109bb65d0c735cc408c908a3f03d4082a2208a7fc84c0a72caf1ffc0e6ed1e76508a1b12281c8c2c9c689539c18df34d9110b40a9a11243c885df0264afe639e088f4eb0b58e37f66646193e462e31328bb219dab1e270ed096487efaaeffea549d1155f6880c13af3e2cb5b0911d2a464a01415ef211740de758492628c66e9dd91e7f4e34764d67f6752df3b1d916f8d2145e0e5f0f89f34315af664917caa889c19aea42b0d33dc42316513ad09688731a9d0847762e6e23049166ec03a3806de98ca8f294bb1d5a048d4edcf28e099f109305e145b3d4eeb96745bbccf43a9539ee56cf4e98272ab0748490a629e802252bbfe3be8fe6b8385583cc012128fe518bcaa30542f39f988b9fb67442b0fb0e7504643c613e4d19af19ac28c4c5836e764648abd2e35ffa5686349d7809c70ce07a4620bc5fd09ebb0ceeab9325bab28cccbbef4dd184df360140a1d4615c202b344fe389b9dc87b319c09e0dbb55a2e43baeb87f1436120c1252986972141ba8ecc47065d5ec5f8cb112daf5090184e5feb2a314be75e75d274315c55af2213ffd564c562bc0cca937ecbdc9f30ff98354721e0dbf5cde8c067ce35cd6851ee308e251e86c55bba859435f09869c38758ea576ece6900fa2b8be2f89abd908ff57ade95004643698340099abbfd7e03c84b0318c007f4f05278d99644dd92a8c2dcc238e60a4dbf7d3d55c9944a71904123e26ffa3fb020ee020881ec78e002a17629d2c4c3964192849fda0e6adb70e34bc84f1467ebb9e22b1c565fe9e01bd5f58733c2da2eba1144d50afec878d11547b4b6c50bafd213a7dfea0081c28601df6765d0e4ee2c62ea450f53ce44526df86f32d1defd826168a72a6b920213190556ae50722f3880e4af3b1edd283ca0350c6a283fdf00ad92bdaa18bf2157c350f6ee718a1a3cde639733fc2349d24c78270fdc21200b686dad3608802168371758ded5ccc436d431141f79d62b38cef19f5a47555da701ed8ae18231825b7c906ce453bb755d3bc1676eec754c0dab65f27c40e59a55398dc0fba641a0892b81895428beefb7ed284886e138b82014911fb433cd864aa73aa714613402189ecfbeeb18f9b16b55c507adb769f2c4c5f835c7648df6b5262fb07fd0a2817fe3da73410af4651d7a63b68e23928fd166e3f267fce54cf78292a56ab39e20fb21d359fa63fb3e1ec851eca417207415d1a31c3d555e7f37ecf615cfc28880368029a43fb583cc9ec4a17a734ae0a8049756ee5ff77ff74f8111ec484ae801983d6932fb35c81a7b46486eba0913f01076f46d206f3a489a23d140886dd571cbda8df5b840b54f0a7d916fba3a2a113386c7dbe0f549148d3e707d9a9d6b6072a9b6033b5be4c53b68aa9f7b477c40091461f2e2f02e16d4415d7a2ecd09024f69b28b9794c45f96282ae002d52b4a8220df57fefd31e37c19c2fd036b7ff2cb77fa1ccfc2c67146766aa5f35a98f4dcd212d7279c899818285fbb9b83e6c259c7a955c3493e431b5bbde4227647df6a83c86e3769f1f0d9c89e2c5a480e91738e2dc7e605d15367ea67ce4c71cc11f99bc44eb2182437c564e6773e46a9a0d5a77619a1a6331ecf0ff39d75636169e1c0b4e38fcfd6ed67c848b93022faf1d8e51615c0774abeea8a79ffc4d2caec987768a90d93237d016e7713eb8118316c1bc2b8b9ccc7aa4faf4a50be342596ad7b4c8edc8ed7fe5a7ff5f5939591900df5a5284d15388b073a0af365eb1c24592b64b1c82d038ed563e553092e6d7048e132898602a07c0f960b2570b85d5fa2e1b4decabecfccdf1112b8b861bd626aa202c37ebec7772441b5f603bc932a664de98260e9d49ccb0755d9c7d822428b6d3ed257e5ac8ea166103154e36c20aa5f22c7939d0c03ddc9792cde75d4826794267b7ce2691bcf03f39f3d22b0880db9d479b70844c8a8a77fa1c60907e14d06a5afc5131c18866c61c265c54a18724f68c19c8b82709b1bf888b5c73292ba7cc7276f19d4202234cf42211e8469e760aa814e2c294e8c2382e7e99294b127ee3cc14a3c6cfe5134b9a6bade9114d816f61113f52b41198c21ff59a6c6820d1486a96ba8b765442a01b6038a1fd73d5ffaa332b3e550ee414c45e231d0e2d1423d06f425b2ce5df3a31e84885399841165521a56b4eeb752ff05e0c40310e9ce5f10b9e04a82d4b4a8ded7f583b45545e1f9d1c968ebf17db91821b9c873011955b2e280d34e3438df5326ad21c9f1ed98d6381eafec7d79a0080d0acab0b34e2d1daaf718033987daa5b33ac16ca645236f57a240a68b56bb18876f6592b1ed337f1e257d929a91f65e86638d0acab0b6888283c291fe9ef9e954ea08510e39719e12227711908eab00635ef7924f1faa04716e4a34d4c8807bedb8997256608e44ac98289e2fcd9b02c0b0ab0f8052577ff841ff5e5b79488f0b7fd432ca2ca5e920866c51eeaccec44e4058cb5117f0616d9d116aa4fc7022499ee1b10b9f5fc832077a33189c4aadd1556c83f06b77f925691ccc5686fb376c6c49a17937868fb780b8e3f922a603dcff6f04c6dcecb61c049f57ab204b26d9ede8e28d4ac29a8bb31d996cb32fa7ecc34188e070a5aabdd6cf44e901df3fe0324cd0b4d45d3abfe6f26bf5518fedaded225a1035af7ce5718cfcc45f680f1cdd5462201443bbb8b3c7a170e5a6c149204fc0aaf929c596dc73e0892cc48491a58cf1be03855cc91be6d8bf06a7fa80feddce1fd960863852ecfb7cdc46a7b286f0c29a51bf98b25b04881d1a0212e05e6969f39fa4b7952e2413bcbf31fcd2734362db6cd58cc76bf3b72c19014acff4c67ef4cdc24684f1320d63c269a189be80521208b163f2041974ed84a52863c756b994eaf8742f40c82c0cffffc9181184626865e3d548de611cb99f1a9b17e2b56f32db2f9191c07a9a604cbe44c3ad0182de47bd199a0fb45ee54a0822cd8e96d3557e8038a3d6e349979edd20261bfb92ff749ce13423317b1f5c5ea4098f22e55539d2ddb4aa1ba6629460f46976276157df52e4c2296fae7f02811a91b132aa57ebbbfcd9fefd8d553ed1a0214e0a21bf873377ecb470024fb9ecfed2e815120d5d428ca9f0b05de050081fe146e7d02a448638f8bcfea0900ff7c9f3708147674b69b2c7ca201efdf77cbb1318a2f1bde19219ba1ed0a9d8e938573fc5f080b86b7b9806980f26e202cde0805f99f2632811d2237127a1461fb0a2e7f96966c3217f6cef17878964d4858dcb3d8d40e779558453eee9fc19844fce26ad920446fbb4196dbf31525e5453e2dbf01a61cc54ed7971d0096ba4dd210313a41b3b826ddcec371851e8c1f65505a210e3e2c3916f5eafc1c6d84ee035d17bec092aca97468727448cb5af027e190a40a00", + "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000cb80a0dfbeaeb79af355a70d8f19855c00000000000000000000000000000000915681ec51465d5a3404f61907428b712208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f714ae98e2b0527277d74d7898e5a1d76041a613ee74f2db890bd6db2fd3a76e9d30329528532c70174aa6a80f652ef9fb86217d722ac4e756dac8ec699873be87009de2f6efc0e2420bebb740d52be9795045be2c08d2f61c665fd8c2946f25a70037344482bad1ff4c46d1ed3887ee2f37d4bc2dd3a1c34c3c6a0b0d75d4fa63077e46b34c8c83b27e71c711f27ad90e03f985d92330211d3eb4c6c88abb18ae" }, "decryption_aggregator": { - "proof_hex": "0x0000000000000000000000000000000000000000000000046462eded1bbde4bf000000000000000000000000000000000000000000000009f2fdc2ae91c95310000000000000000000000000000000000000000000000000b2ea26392102492c00000000000000000000000000000000000000000000000000004afc85764e79000000000000000000000000000000000000000000000004c469c087756779e800000000000000000000000000000000000000000000000d3dc12dc70ceba4e500000000000000000000000000000000000000000000000d09dfd159e66bd2360000000000000000000000000000000000000000000000000001d0742afdbf00000000000000000000000000000000000000000000000009104c8f5d91fa2bf500000000000000000000000000000000000000000000000836b1660e0992fc3c0000000000000000000000000000000000000000000000031380efa2b7ff3ce80000000000000000000000000000000000000000000000000001d837d5ff0a5500000000000000000000000000000000000000000000000db4b2385e73fbe68b00000000000000000000000000000000000000000000000e4f7388c73406d61700000000000000000000000000000000000000000000000e8623e231e09b7f3a000000000000000000000000000000000000000000000000000132c5f9251f0e1f3f0033a84e44de526080bdfdef3d2c4e9be76761bf24b2d0e31ac487ab12b12f3b1b559ce6d9ce7c97b5f31aae0e6529acf2d329858733b8c0215b3e3314c51eb174b690f7d4f832df6574dd9e4bfdd41cc47539b0301c66a88984cc0347a312b53b6e93390fa5cff0fbb34c9a55b3bfa61ab7841f31779fec5644f07e5f9c17d0eb1194403db98035cef3a0d47e70ddedfbfb47bd33c44fc0f7f4921d50180f10ba9e083af9917dde07ffbe45a53e65983092dc0f35e1c84f92b7189595390810dddf63209c3f94b856eae91571feb451a665cd1a1035b990c887ecc43cba2433686433d56359e92f6bacf0501d6d7662ce00d33aebf14dbe64164c036caf16519af50842a2f1478375a011705e1b6e3cc8d1427b463a370002e3a70896ad27996d20e8ff12e70916e72471a5dbaef7f378d7232905aba8cecc91e8c077c72a272b8af0c6bc297b6b8563e218a17a1d510b271bf12dd8d3aa90e56d6519a32d29635466a7bd4dfdf51438ccceb51d486058596bd2e201d320c9b63fe239251273008b48d56b8a01c916ad52aace95cfaaae3232df4a22af016a830f00b2280dc4090249ebc6c4f4977b3d2f3e92603ca1e86d0c65fb91f757747e5b3c64f229c98b47e1adbdae651e8c93d1b5e22b26a27a4004b26a4cc6dece078c1e1f420e928f4810f467c371466a5ad780e27de4b7cc7d34c4bcf9bc4d1a8d69382b5c26a661451205b6074193728e248ef7585707509e09eae306e78710f1fb3288251b20d0edf70c5a978f32b92a869fe73975d955f5281b23392981a6f19a2f0f58271d29171ed9a120fb70186dd4f920f56f767149d663c103ad123bb86de7d5ab1678aaa63cc7e6039049df417f336220a6115e3e860bf85aa96a44d22587562527980f69c45463860ee9df44482e0f52c5a10107aa1bd1fa8a3ffa215868740307471504d1532b017118223f73cec88bdb2514519f0eb31f1c14aa42fccc0d541f5b3ea28bd0f5a00d90defbeeb8e25bdb73835f623fa6bfee1e5ea3032e649608c59db0e1d6d963d552f4b4633c5767b0e8bddd6f350c179ed032448ecb129c15f26bb9e1ca1f3529eef918c8ff9788b597b6befad98ef7b67aa0eb9134d5b4210870b8989b0adbbeabe3dfebe6ea30bd7d60659f4e39e266184b8243691b2e0552aae18e9cd627f7c5ec8693c5fa0d609eded0c8d047ec9ea6c224418278250112560148af7398eef1f9dd48561fa9c0441801dbdd69c8bd46e5c4af99ffe4109f7ed54f9cb7f83e617e6b2f93b922bce7e1c07151e676a7ece630dd747bcc142aeccbc157a93950eed8401606e1b16e66571b7f8ef663ccf4e93fb82995212bc9e169ea1c0594356b171e607e54fc02180a1059edf5acb8983064734fd9f8209e394bb72f81f207edcfaf9f2ed0b8c0689b85c47181abcfc8d36e08b0c5ae05832a231dcb4f6da7f1ff2ac341a08b210acdf2bf48f315d5920175f3d5cec908b4961c1e62cb178fd865c31734ac843d19fe58175f36c3bd84dd20e8069f6b0a624f48a0e47210130d1e6697959e2fc351c28cdc4228fe9fe37fd5abaa897e269391d032b12bcccdfe6c5b4157526f2325f20b13b8a81f8ee1492e0008cc770dc6aa7a1be35998418d9d54e3eeb7bc87d1ae47d20d47645c4973519428a79b15d9121c942e03cc0ccd93c2158ec1e33c2b59bb09173e7b7c9662f2baba474b01d7183e0570580d82094361ca63a614ebb95d875ff403af22cabd79cc4e8f400424df7b0904cb8d32decc3e907e6a0b511bec4ce2fe9331afdf13b26b4b010b0b3f755f1a444cc372cbbb860872fa0493f8c492a7f23867ff93dda7b637a2e90efee7b19c99a3476e8f399e1c68944d4bac5b988ae883dac4f406ec18abb7ac0c45dca453ba41f5b9c28dcca7905816fc9600d2298b31a000bda8fd06731f6f26b119a02a7fc2a4ae33307e9f2798f2502ba08f0cba86fa82624386a8a7cf1a1bcc4583eb73ca4ac0b5285cef38d0a4a6302218e5ff0184f9c9908eca82c01a26e711272a4d2dc6c57ad49857ecb1b4adab7807061cb4f5edfdde5930058ee71e8bc31fedd59323de5af8e5713c7ceb21d48c3f73611979e7e2712684589c4711f24d5a2b83af9233bea1cf2268234eac399129feda79cc48c5cd77735d9b000d2d0a40be4f80ee8003228f36bd9602fff33d890bec95c8c23e00e7cae127c41075162afa6c2b18a81f299e7eae0f590c18989adf321dfd19a846924be23a7918c08a3dd27cd71b39d3e3562973ca37074ec000f69aa3824710bc344bd5ea292ae856b14a9a14635879ccbf87f082eddf2dbd240b9fa4702f5fc5d88ee72b2a096808a9862cbf8ae30240058d22dd69272b346467520339fda95a07f6a8403325be8b2eb1744fef4514ad7158c52488019b3eafa4d8cf7df694c3bf47c3a59c1a1fc4a36060bc9af14ebf2eb79d07cfda2528c398b145317c2cc84de6137d44221226dbd233ab0c7c0263ddef6811487072f0e218b94a6c72fc7e39bc27141401b11d54d55aba111509422d31d6cb92dea31534ace379c0427c6c3a92d4f90f02251ec156fd36045aa90adf78403f49e8479b8ca9778326da1425427815fbd6274bf80015317e3ecdca8795d60e025b58a56dc77f5a4b9acb453d3e78deee5c14a5cd11d5d16c49fef05d37c81548bcc270ef9f21a79b449a30d540ee0b9a8307e991ea9b46f57eb69d7c1ebe87a8fdf4f8048b4f41d4808162f8e0f7cfb3e11a5c3cdec6eecb39b1351814f82418b9170740fff3f5681d48fce7310e527b6a03d5ae1282295d1a77b314268efd8cac07fc028290a1c8657e45227f4b6ee63a24de15adf0cd89fa38336b41277d5fbb3dd19e281b815b2c0d2e9c3e5511797e00e3f664895d68974f56c39c9889994ab59f8cab6b8a8ecb2d703646a2236aa91198ae08f77f02dfc879c800f7c84e398ed8a43f20f2aea496b004cc58fcf4780b4966b97a4be732ee4358cbdfa657c322cd78549ee3780e0350374ea4a3d8900f6418d683ec071a1273be730e362daf287de8c335727321143ba40ee0115c241f9ecad9f72e9925e3de109f1e566341752dd39b45d512d2388dacf23b281ce3108c6659ba45864d7bb6836042b9097f20f3f2a053046045dc613ef501a2eea81281834f5e8f9d7140f709b79d53fd264556d70139ed47d25666027aa69c58e42b34757fdfd0b9d122191696b4b4bf4eca578e24fd9b844b34d73c31ac3ddfa801ff012ca13ccd6b0f5e561e0e6430b4be2b0b2d374ea129e470491ed7c3b13b183e35a8ce4cd1853425fe864158971415c7a65f11dfe69d5074f950809a122a28063e5d27dab57ae4f0b3ae9fc361028adf438677dfb5dfb988f8b8b6ae57a3076e5ea1a0b2f974df0ffbfe0a060e3637c3127776fa81d9b01ea0d657dabda901d6807a490c87e46e4e394192ec98b42ed04f334b5a1c0f5057e4a4c557c01a1043c947cc968219522c1abe82b20d6a4db9c2b388a24b71cced988a4b883a481d25e3810be1b850a40508bc3fa632b2e5fcff0a7b1a49340915865bd52f501f1f78f68122727eb366d619ffc67d08d4d38cf0e91ea66920699c3a2dcd37656724c44f6274e12fa976dcaa3d6cb603e2b323f8dcacbcfda2d2371a78596fd7e42e52e2ba095e30b7021a480d05dad2f2838df1fbbaf38e0df2af304c8b1175b80cb447d4e3ba6d26637a624eec49a5617a8380020addb7879bc212fe1c600425147bf057f7596b5f2c938d61049a8cd1889ab5c40ab9386a13aa7022d00ad42d0e1b1338ff64fa766858440e350fa32f5fc613ccd8b46c95090843de78343271006a68bf23479c2223f60d11d64a98c2865fd34f92e687eb8c2b7a28deca830818a2208adc5a09a52dcfdc04f6e0e53bbc232b046d60528ffb794238b3e64b5417d6dfa423205c8a1ab17a90537a19ef9e5931c33a757771a62c7288865270fa1762f450463e4256850c78446525c13e0f55928414615620e1c41fc5f2a62bbe2ec7351b1d969bf8399ed677fcdd79a6689cb8c08682aa486ed19431e2fea30326c3b9cb74eb998fdd93bd366ca54253b2ac0e213d65275b7e15be6f55b87e5326ae1e2391bfc71e63cb96fe8badbb8eea59b1bfbbc89d9fa9c76214c968a9ef10f4697708d257e2caa80176fce9c9dd0bf7f08db787efff22e8c50a2b3d851c03bc1a26a9f96c6c7519fa1f9f69e89d21f3a846cfe8c7825b32cd3b1df30dba1d5dbd1e3883130ed44f9f1708164d7c037f7110fc42a2569005b099cd6c03561737abb36b4e6751c47f50a1749e53575fb2d60336dfb156eea527c39e6044fd0fa4c2d07af7ebb89646540fb42fce47fe70faaf75eeef6741a570a90a39e2212e25594c3141f72b1caabdbcdd33ae4e941fe07d0a9fd16698c6856b74619a810c0cfff04663e7bee110fefa623a4db43fad9bd5d72829e65d9a71427a0d5c2f0bc57df006430e7edbc07970858f1f006b42a6b8e510300fc604821abe5d9ed612879f61da683c655452ab2d76bf7345c63299b02b7ccaf83dcb03552c5048020354ed28001f40fb66a7fc55ff272aa8de1844409e56a6c2a3fcb89c94218f082674c00541fe4443178d227abef44644d4abfd5e9303fff2e68bf3e1d140235910cd28a2f0ac9cd54303522a68c80e618d937536c1d26897c8f85b4acf4aa1060c9d7cd154657431320a619ea0a603fd86a52edf39d34cbfd2327b95c4d02dac2df4af3fda5d0fb1411f7499e9228e69f7baba0aeed7b1e31f6d17aad11c2c4a1fda0b5b8b20f3f9963100e0dcd1ab2346b12f602bc30a2bb453e615a7c7df7a1e209bafa91194c0e3da621aea9637db24336c649d82aed522c6ebdc9e3079df264481411d0846d3f82043e90df085bb974d2ff8c5094b56b0721e622a3ece1e2c72afee9cce381b1ab0a042d7a2a0d32bfe34d76e43ae24cfa00802038ce3482e328423d2b01a87447801dd0fc1e2c33563bf35c6bf7ee27f8ea7808e26da630559777a16193d3cbaa060b686fdf5756e395ca3c942d5415ea321930d9bc4581da056043fe7c53176c8fcd66835b9ef69bd815564d1bc7fc71c1373bfc03d480b7aacb1b71a1d5cb97773bd8420ecedb4abeac3762884e73eb9701d5e192c1e2798ea30398aa7c6aa711720294d914419284a52a9794048ad73895e21c73f2e27ee0aeb6cbfc1ed89266847420a154aa825df70f24eb1b1f0b958c046771a911baa6453cba72219ac18b8e31e8b660828ed849b2cd1b642f2494e7430d440552ccde3ff0ea6860a58505c5d2c029cbb4f5fa598fe4ac011751790e0e606322c13d130dfeb16f00ef3a180f8e4886c9a3f367a36a4b2fa215a60511d77a4d1ab2e1d16ee35174f5333bacc3170eb79641811ab8d999ca78bd6aeb778c04bfe4b022405d81d43d828fa1363837759b1d688f9bc423f18067c1e63e7ddcae77b252d284dd9ebdebff77e6f0f9e799a7461a0f4899e54c7d34d148cb21cc3d29c76292c218095c7386baa2c5304e29fd4c70bd82e6062cf160ebb110230f444c2d71f3187f7f4932fda50d850dc56b3ef554e7745311c76955f8eb116e15d3f72d81a267864acfa25775724ef1f370daa11bc9983df1f6d897ed8f040899ecaad740b34dfb34c45c325bd21c38b8634103c639653053c1a72bf63e3a074b437a978169d1d7cf13831621c414bd3d03f307e7bf3c2bc654afdd857c43c0e1e47c4e726f6703cf414f9bef59c90a72eb596013447cc3cf7a8576cecd1674d8607f22e0cf5d470df13e216be6c2e9e8ea0ff0a179c00cac95375a14e02787cacb54a901fd68c2ae7eb0ee85c442aae3d8304039a8209c8090040c83dc3e287ce5187731d9b8e516f91b73a1475c4c4077544dbd325ee6ccfa3aabd1144352afdbd457c2f39054b92189663deb023ab48081a80b8e28841188838be3981e7e93840ac35199412daa396d472a6a2cd403a92a8a80995a1092e99f0dab79a2c024258bff1009161d190a6eff111d46fd2e4fb3528475d89ed0f4acbd2a83ed488cd0d14fb2864e012328fa9fa8234ef040bd7e64c94d8e5dd435f85564aed36b3c59bf50b0611681a82df36ab4b44b009b20fe02e8689ea64df791546cd3db0588d08074c241768008e3feedafd677164b7e19c9dfb2a1f5b4c91abe6b2716dda9027a8cc020340435acab05014bc0b5fba39be8a5dfe4751ec0cd65a54e3acaf559fc45310001c7116a956f02d2c919493c1972c2a8c37c5c7018371de046603f943861d069b44e45d5c59aa7b8e14620ef592b42e43dcd8022db7fd3d652bc292fdebed066f0db566282ce6fd05432d2bf063cc05c2372a442e4eb1ae95d943750660e129ab4a81c1e699cb974b471bb885af4c42f8be4be4a44f6c16cf33b22bfacc7122532e5f78e3da9ca7b8da40ad3651c7a3cde3c028a39f76ee378a5df8e7122d117b0ada3274420f4169996ff8ad7f0644294b964bd02e43b573e027e56c31f30281ded22c23f5318715d0f675c4c697a2c257f22112738f30ba3d9abfab6ffa1b183c5a511100513b515950e7995ccf0f082cb5b0d62b57269e2c7cf3e4c0eb0bffb7398d6bcbc8c3a742ce3bfb2975d365c391409d9590552733692a1a62b7138cea6bdb50758166ea8db309d0ad52dd680140c79d82fe429ceced64c6c214103a90d27fdc490bf99777ffd713275db4329c3cc42f5d631a29af86d0e449a5298a95d8f509c980f7b11abc90efb5b6ec019ba2abe05ff97971e3cc4494a8d11c320855d17be84acbdabcc29634e10451cc55f7b18e63385dee746c70927b271c9d654c07f30212ba3d0b34ab1c7d81484353e7303ba8739122ea0b4d054c5905b3b6fc0e3482c9d1f92261d1caf129228350cf179238a409ae49a4887645050da8ffcf50fa4ef02b22e5cd98fee5625348f8dd72c4806e6a6028da2514e05314cfd201047def07dbac92fc3f31197a0f94659a6264cd08c7b7b1b73f5cc19d074a8c82be071ccd4ad401a5907a3816735668a55c2c2254cc94d9fd42d2990419d4ce2b03bae535d55557127ecd66370f5a555fa5b3d3255b0e3e18040e7c9718c174a2ecd7916c33c950e946764b5a2490252a6c8b4e8a3f4e4200160e12472f73ca99a627089fc5affdb380da4506c458ac642d6a8f151f60b09261abddb2092edcbd085881db6d5b96013108dabdc80a3b38fa8d2e638be26c78465bbeaf2c06f7a360a2cc343d6d9dc2db8f6424302e9718b8884ec3d80e99407770f64a15777f2da7f4aab9408e3ae2f3414bbffd74a290cd14195ef7fff6b31d737bc22f2a1491ff76b75114b2105c8ced13258e504a7b36c5102321daa348581a551729aa4fd8e5c8a411c89f29eb17ccbbdb02141008bbdf46c7274a2c719595fb7a2f5854c0f6543b85ec7221d1e15b8e867c80848016f25e72617f27e537cd721c05b0e96a7801ee3fc43a534ff819962b1e8282ed7fe5437df8026f12931affc92a45a488eb039c2285d69ebb9cc721d0c0481ade8e27986cc2424bd63f09ea13281fa330a68c69d21ea8262d617a8784c5c3130f9fe2638a28d3cdda46fa9c3f110d509bb1dfefd5f72c7af0c2d6cf3036ea02de515f6876005bbac8d8c51d6300ac7fc8e451e423e4ba7c25eb016814dbbe5a9847c8d72a519dee86749635c52fc29948f9db22787989d3d2630dabd1b4a74528011a8a2df437b4fa220098850a3e4753fadb485e012a8cc00da2cd5919c51598f8ff98d82ff813d03bedaedf2fea1e5e0c489ac9b0400e0d949e92115373e6350c76f49c345b79f2ff56bcd001b956017661a2f6b6b167f5f12b4894a32157334c60f29d0521166d13d15de9302c9b0c0bb5c7880f6acbf2acb78d97a66d12e898b7ec0d8c7322bf62e51aec263770b749a733e1842af368cb129d636ca2c23890cd74c44f69a427e6dcd095119e080b300e82de5fc8b08396e97d027b8e781f0cbd63489c5d496949b3daee08a8d6b94c056c4b29bdbefdfb87f715725d48e2aa2c8ecb2e72e4a6329b861c11a1733f664320f80bb5e0624c847534380dd10bf3b9d7b0b3f3cbc00ed6d8d72ea19cc0831d47f5142d7699110a13d154190e29363598288d9e87582621ec8518aa844817a33cf7385a4867a624e6d35d19f08689cb4694d9eb4068a5a89cee14fe3cfd331d9edeba664f921f4e2af1b182eff2ffca37f65408d3d1b42eb324193f27aea86433f3a6aaa1f0ccc589243d364a6d284678652a5727709a417c212fda27231f6e20f52417f284a5641a6d80362cf3dd402b81e6dda971168329fc14aa355a2874208110adb7169be2c53ef263bdf59336ba78618087e36be1f3c9277257150ffe847f6d86895f1197dcaa651987c33d02f5ba5b482311f817d2752f96ec1b41838e338b2af7008a0d5949ff9f18dc7be79ef5739211659f266d621462e12c1be7bc4b4934fdb8699f993d2caf7b1c570cf25b802c98a25d69b994073b68b01ae79b6a69eb211a914afceb059e78d2a636d304a48f6b05bc32993f1eb3e3e642813c85af8b5f1de0888cb13c53fdc5d5d479c70ccbbe61744c1239002bf3e62cebcaeb5e7303b3e12a1f30fd0db795b804875570e7fed3230bd8052871b4aea05a90ad7b63f4113cef0be4dda60693a503b2378172a58c4624a2eb2d5737e779f841a33c28eb444b62d3d23520278eb740181fda57f544f3aee7e30252680255206ff7d0a4a519afb865069d8a05da1132d795895891fa94f8f29904f4324c26aceac3104c97d39759801a293e22e65a381aa86de3b50159ca9fca09cf8f2106c1ecba895d547e1dc71ed0d9e8db14b9db90888777e29ef793b337179e7d5a6852b64d326b7fff80c4434942d3585902b1c534450562c6ffd93e8c0d157d7f9386ead62dbe90615399b0c8fb87e5fca00721a2a3fb68d14d36819b09983dca3f3792b69abc99b3f3f5afd33d28cba0999b8968f72566f05de37d660436b2b1da3001d81f914ab0dc20eb913507e7e526d5f96f842dbc6d461b96df2c259d17a61cc499cc16d0ecb910225076caef6a3d6c2fa282b2bf34a1deb87129450563c1427f7e543ebbd7aa31c01a16f7f9ef377414182129f183ba1246f91048c125b304aec8c136a897807710b6a9a73cf827b2c53e733c2eb1df3fb2fb0210be8239b1631db74571a2e30ff1dc5e77a7be632890b5bed671bdaf6e2a960bdf75213571caa91dd52fab661a9e4fa96e23b6d814748fc4bd6a032ca1cbc71a6e2915cc8eb694d63a591eae3f5aec153656fb2311b4ba339a7e871ef872702461a3b251b88bd55cea092af0c1cf7ec9f96f35e46cd359223da833d8e518990775f13a8a47b5df373d0d46a9098a0b23c1dd6aa76b286f6708e7713811025a23a510820428c3768c4dfc8965fb9d34ed574ea7ca0d2eb426afe5f8fe0681ef02a7a0700344716e9466f589aaee49d2860b604052684091a9e0ea23dc12661a18dc0d314a36ae8c600e6a886d4343a2b42f3419ba19e793ede386baaa303a402f234bee6497b9e517ac24aaab0fb01afb0b1707423554a869d1bea6a5bf743f11f32baa913c5500c9df9db6f92f49d30b7adb7cda475c5cbcfbdab514f391b12c3e705687b7ca3381e1363090cbf57ff7a86d1fc0c9b110893d6537435e223b0966964c5e464aa76eb466b04c4d5c7037f19229aa437e7e490d1bf9b19eec700cee2eaf6b694fa0b6b912b86ac7bddd35f7270baf326abc5234e02798047f6100dd53510a41dc8fe6369350a1b458bf042e8204d4453a359a5d107ad266d8bd0a393b07ac21e86143568945dea47579f961da100dcd17c89e800299d1dac22413736f9bcfe75432de59c190fec427a659e0878ae078596ccfca7ce18070706a27da89553eba9079775003ae2cb47c214b82ea2eefefe40da9638e912cfe60822ae649c2057442471a471a9f952eff46a2babf1a83e19a4ddab3794cdac562db101a09b7a278b2dd6fb6e2a9d393579c92db556817f6dbdc7733599f140cb4382e6b319f574a62dbebd2af4cc151e0773ab2fec6239c69681263c76fd83cff19220271f850682cc592aaaa6eea28d6bc0cb73007fd4bd1bd995a099bd82c024f0a2f782d188ed96e6e3bf4087c26d3d237a2b12204a57053ad307c344ae34fa4213bd4ee4f056cb822fe91f44421f97b9553e372050526ddbec9a9810f0b77cd05e7dd08def408b601eea156f02988103cdc6af0fb020894d84e00680fc6cb690af351f694b00379f093c09cf78cb4261d49857bea4be6f8dd3f4a7b32f59a230e3189ede5dddf5124a8caf0482b511ba11705b8e97b0ae8daff89770da3fb7e20cb1d9be77cebb1509930887487b1599d90171d64bffc6e929ec317bf7e5aec2f7740dbe894189de87675d7e978196e17b528ac35b67d091c6b144dccebf6770a01a0077e8d2c021ad57fb50a34e3158e55e57f94290cb6db1438e73c26dd5f03367dc364867a8930faff385c5e3805b3f5d61b5fe90d04e51b1e14373054971a06811bbc967bd9eec33c892ae9c4ef7274d5e4351abbabd2bcb803a5f5c70f02860e11bdf399a1dfe38969bf66e7da76086fd1368b68e47963db7f9565b3130d69008e708fc5f8961db2d87f77fe3aa722795a6c711cdaa2d4da8f38eaef83196be74e4cf2a837a79259f60cff97b4901f7f48941807f07784fcbf4db57ba80c897759c9ca3474d1f1c03abf18050430fe347a17998ad5498c2afd48c8a98415b32a411758c283b4c28c1d5ca62fc3dc246c62fae1b7450b168c5854c3f6f81f25ec496179eca02a8934cceb7f9ab9fc81a0b5c0982e045b5742e3d21791562bf709a403a8ff7f4c3d3ed483bd94c60426e37242a53fb501d465f6771bdd1c01e4364a0bcd48a20d4fff5363f75c85d02ce552a1c66784f8c393df093885b02286068214029f61fe4946410855f18276aac139a43f0b328f281a40f607dc65006694ad288bedeb4d931e515e3d739eb1c0cc95d7c5284f3ae66f43286a98a92480eb742d842cf382e23e07211c6767bc86b65efcaf58199ad5bbcfaeb67b8116ab42ba4929e61ef40822b47b3b77e1411f2f8dbc72c1e930cd08e4eb38f37b0bea7c2020d7ee87ea54c1b3696fdbc146aa584a23042c69e6efe33f7556c1762b3e3ba8c773badbf5104256d6a599fd5b20145beefa188347e080716230875a1f23ad5fd8f118d14793db2a001b386410d01cae4501bde987126554aa1f943809b93a34284851e0092f0ddadd9d27066c2568c8ebf91bbd5e1554c3ebbc6eb509d2ea1544f9c6b1dce97cd8584418ced461121ffcdec9e3dd1fa14f95d2e4ea08cf6a13709220c27a0a49bb9334e11e182a621cde1bde5f384b1b0ad51d19833033a68bb8ad808d6c9c7b07ab154119abbcbb85ef10d40fb251969dc2108c90221aec3e7930b31580525bb5871112787777aea6ad16614536a5a667520debea18baad0cb9f041125b510f5f1fd48f41cd60268c6ec4149908e7f9c9a1c3fcf813a79c776a2eea726579809ef20c267d839f3a208120f8736779796e9dfa7bca253a82820d90f4f5f7fb4b94a870394dbb688fdcc0bb429e43546b8b88743f3f114951cfc8e34a21110a24cfb809985668ea681e55f068d8f260436dfcbaffc91c32edff5ee1777d2ff82c80ea797077225f691258424cf5f5d64b3c312d6f48257b1ca15daafc6d830d0d38e74669add7907e870f0f8e896837af7e0104b15d103b7ad3d25f8de54bcc7ba08a0b3bb9f30e238fe4e3321a523728c554b162110489739821241ca55e4ecbcc5f6f25bfb44763a32e19fe977940ce8800db666317bdcdf1f29834dc6b2f07c60f7076ea4f4c73881e89734d46a31a160380704e0e4d0b30a60fae2bd517e3c939cf57682a076d4f6f364126bb2975f940ee8fdd0976ffd75e34827735162044bff075b04735ee3468e51d80a7a09bed81b48942205981554c5a78f504f763531fdb93d2861ef929be2f873e59dcec85631807b70484a7f6ec144b234ea375bdd48188e15ada0eb56ad1b987d75ed356bb65d5e01bdf10e80176f7be41f9d31008b8274f1b0a8e37108cfd37fa80c5be57fdc6450207b4b4209e983e6aa97e26c81afb4d57059de74276ffde689f883d5ed67abd2ccd9fbc681e291fed67130e43f755601bae4c8849909e9030484cf3c71aea07227f95a36dffb88c8d0131284fba36b597c0607f0ab3b1bb6c215a19fd80cdc92c0ae72660453b0973ebfa890718c2939f1207e3ae5ae18bde7208392f708a9a093cbd866e4426034e015fa66d8b9379509021996d41841c44374f7e4ec77a8e0bcb2604e0783d0a7981f77addc0d9d00dcdba536950e38f1893b138696ca20828cc6f843d63ce7c068774807ac2f6a52bd2adc3649c130a35b854e2965dda9a2a39eab89126cc289a350e2bf0eda765a5bc5da8640047bf12423fd54224d0d6053505f8e689a403440ad11e799b653515ad5ebadac03c3c17a2e6a40ad8b829025a520eabe6104fefc0349b83a1292f1713d4e56131708398777532728fcd2b0b712b5802b896281422c1bc219dfd0d9df3be491a20443ed5119ae62c468d2a22adda3834369b2cefc68f1033b217baa21720c4e4813cd4b52388f3bce5eb3e21ea381f1ee6305814a07f4742b52311720f7aa836766eb57fc2504d89fa68be03b747454dcc12f3b4434342eab6d95eb64c2dccd735983c553b76a009c18e6e2e8e783a9067cda1f5109f7371fcaa2cc1658c50fc513effba99a62f84479ede1c2afee100f375462e12d611ca1e58b350f4713e43436f8d44ed129d36d741772485b57e2cde8766e19956bf15fe1a3f715f462ff70e3e66911ca8b0349ccac50d0c4d59a71edabc1844172fce54e10bcba6f74cbd36c2c6cb8cdf86b07fcf272bbeae03157930ff5bf215a8fbeae00610efde36e8803758c44b71611ad8b7530f77a243c70d35bcbdf3d390e0e1f358a53bb7b271748486ae81cecf1d3bd7b312e91ec7312eb62f7f4465e9f133c90b40b5aabadf897db276170982e3682959027d4f12eee5a6720a6863c259d5569d30604063277d5afa87a04ff1db7cbbd8048b5f6ba32e0972746c42fada98ecc8139f01726b225c661b7131c962a6af431c46da1b18603580e0eb36b1b3a78803784752e95178856a52cdf293be7d20ca28afd91216a390b4c5fa200d13796d66a09e67c0dcad2e5cc6ba969843f35de110a26385709f5eaa333c3dbc672805554507e4492dd88e4e1fb37d30f00b100b0b73ef0f2805b3dcee77ee3c518d4d2b5e10a6e74139d13ec437fb20caf21f6b14258b219d2d7c874ff865b46668c9a98a6f95f7513c4840eac1fcd8c0d5983f2fc417fa028662ef9622af0c8b9a8370044569515953bc3351e7879256a1682f1913c6f71242142890f49878f50a88254967744ee98fc8f39b077df24f896475138b353992d43a58ab679391c189def4abb585c95fc78f8560ed1578334a54ef06c4e242e99763949a6214d2f1e6b2e0084716b5acb458f0132451aca43dbe9002d447989ac471fc47f751e9ab748f01dba71e41478c7ba6cba186c59ab2b22e0ae2cd982005400350f2b1fd5804f85a943324dcd60e01aec98058ec872048fc2a3aa79499437917db1f087117dc5627fb97486c24adaf4cdea43ec70a5ecb1115482b3eccdfff6508c76655387abd8db57b01b170d5e1870faa1384ba4365e7054331d92e9f9f0b1ad97b7b530669a248a4c26ceaf174cf6479efbc7da6ea54099c32957b8e46d8efb77b50d25e3c705ec7679f33abae0b097fd615c45f9d461b03dc3ca49cfa79c813d003bc5fe4ec9cfa94a1acd523d23b88d35ebb5a6e9a2fea8939d0a2391f5ba2f337b17972354cbee48f9f4316611ca4747267fb899a2c3deec9cbe9907b63384af2f8949c18e82d0599a14c794393c329254a5497322b8f054ff0c0c835e7176bef90eebe63c9caf6838cf97942832984d19839ab9a15ff2ee3f4e15a05e49a92af53f986375f372836e8b0871f4c4ec28a690e5e631d2dd1341b55a31770bbeb958ca73627dcb539875b11846f8129714880e708ee260d85ed2385e58c520b25866ee5635882ab26c9019999d6fbf748f1e799852121d87673b8e00f15076cdc02771e293c1ca49972b74ab8790702fbc2d08d89be1c26e413b5ff7ff00f30fdc45ed794caecb4f8e943d341f7ec1f9cbe686e8408302d278a2da6ae5b6328b996f93480f99dcbf66ac56244f780859f9bc5e6d61413b5b5724c4eaff5aa4d5113e19f2072cc4486528b4f864c0b2fdd8dc12c07f70799dcb83bccdf08e82e1fb2354171bec979c58e2dc82f05fb9018859906ff3803fc845ea83bfb869fca929234cedf2c7ed172a4a857031bd37d4a65886916451a01d80e3d40970692f0ab31cc53c737a8ffd3408641dd18b12b59d00880a1a51bf1201219c156f4687c89b3bba7966515c263a6da337c70219b18cbd7e1a7ca07f77287b04f62adf3ba0a00f903a4de156b402aa92faec6737f1be36050a4810eb34741a55cfc86f22b7e734e338f600f09df00574221e9f861fe9b68b5f8501ec4c809a6c85ede1d9410633478be10ac32a14f2d77664f050a16f2b0a73b470a99616c27a96a7b747c365d9191b9a0a92f96eb10e8cfd399d35a186ab9ee7f1680f3102a604fc69d1b1e5058113ac9d917ec32e1cb339755b5536e4715758f", - "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c91806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f75128000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c2000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "proof_hex": "0x00000000000000000000000000000000000000000000000b848f7067da04597300000000000000000000000000000000000000000000000cfb8eacb33dc5026500000000000000000000000000000000000000000000000a140f61a0ae7da9830000000000000000000000000000000000000000000000000001b35adc769dec000000000000000000000000000000000000000000000005529748f6a3df43120000000000000000000000000000000000000000000000041e2b70da2e142a38000000000000000000000000000000000000000000000002489ca9f8182b061f000000000000000000000000000000000000000000000000000069af3676bfe700000000000000000000000000000000000000000000000bc2f7c98198f35216000000000000000000000000000000000000000000000001de57a498803e7aa800000000000000000000000000000000000000000000000e40fd6827d61103c1000000000000000000000000000000000000000000000000000084230bc9e5b600000000000000000000000000000000000000000000000c20745ecc0a9b617d0000000000000000000000000000000000000000000000098615242b257ddaf500000000000000000000000000000000000000000000000d98b13f67a2b6a5220000000000000000000000000000000000000000000000000000df689a73e63b1a953e2b375615edc3058e7d1ca5cc4bcc12d1207ee7045581b3b6d157383acb008447d2b42adf3a25787526050a2c464130614c700a9cf0b996d758e0ea3101207ba4219ef22ce8b381e24a97633135e5ae877a3905824a6b2117d2c52adb6121f18d2e4a72128ba85103c6084065a0a957c369df32907adc47c59f3457ae3524cfaf1eb600e22c8222d5c7f3d05b89dfb6a6252ed158a263b28cf1d704e00722d53425971fa3915cb5842157f175ef3b6c978bd686b6bb911469c0a0bb04e524323ec504346fc6d320aefb7c8a5aadf1728b7a7598f38ee2595296f4aa053a27c5f74b1cb2d97320928bf8e5004eaea31e7ec579c77a628c4d06c323adffc62948c40c873573a3a100f76bfd152510de33a30d61a10d2b6d1c93de13958e1c18776cc9836534e19b35a0f44676561fe123ab6651f2926973483bed88c85e5c1ed56dc8a8651f15acc31d6d6bcf2e97b303b160aff49148eaae0a509cf30cd9294fb69b8ae360bd1e988a8fd0b940a61d4811e0e8b18fb2e137aa4e76109a151c5d96eba7b16f7543505b2e217df4e672a962b340c011df2743b79be60e0b0c05a2155c315ad8038be74112c3c0a11a2ba8bb424fe02ff440851c431f3a0117298170f8e7b02639cab117e04b8477192bc4e2feb6dc7d675bd3a0e09017ee842e52ae8728ea51e4a18da1ff28088f42d38ff18e60317977213e9c0d60b20d2828872222cc0f34e17aa5c9267d627f4ac25bf6c7969cc08f69079cc5182cf58c183cfc115d5179470fd7395d888cfca34a1913a3ac918e490bb8231b59359ef909ab3830af8794f8ad241303954d3f8c794969cf24f7f171196fd1267895e1e42daea16482d70b755aeac34e0b1442d10765de80604fff4fb678bc7f0400f5db1065748acf1169718b09f0aa0f601350f0a894f8d32b81d408c6d02b1906426515448f4e0aae668ea694fbbc2a7280df188e758dcd850ad11c7fa74268dd35f910a27b4c1b260afcbcd7f6837d48fc77d4a6da0cb7b8500247d05df4e3154c3024c6488ba33e3fd241db9e47eadb5c7ecdd8246a0532a85ea5c12ad12077537a256a2e262e3ba7450fcb7709ad3b2b9857b7326ea74d55f5ceb973b149f8cf7e0a8ab8172a80b043633b24ae10967d6489570114fe7c5bcc2419711b7d4be07121e64aa42315eaadd92cad2c7dfaba18ac683b7ba27997eec1d15edee22fd752017f1e3ea133164724fb9730c4863fac6f3c79aac2204d17c154dfeaa11ad78e1c9c38a7affeb9d26b70487b79b739b2daa0d2b89b0a57de0138d35875d5fd23095e676dc7daa828b9c87b55c7a3518b138dda5947ad0dd1b14a160547a853850ac087cc19fc4ec1f18b50f448df96d3b691645c3f02ae58749b108889cc72921344c9da4fcc45375fc89d97646e8a7d8ac9fc1507cfcb38fe716ca264c886db2146a1ccd11f884504c374b7a78a3396c92c52412791af6611b89bec51c8ba232444938535ffa6ecb26f8768571cb60fcb4e413ce7ccf34fd796bee3fbe9753322b0fa40c4ece2d11c0a0fbe4caf01ee10217d9713e3da320a49d2e44fa046fa2d95725fde9404a03323ed83df779d69954a6159ad108a0cec3860d6790373240e33e2916f05c0e22bf7ef3e1e55026f54b16bac5918c42a031e3cd75368a3ea0911eacdce98241da2874f049bf64456301ae43029d846360fc0b7157f3ccf9c2a618b5f19970b3569e8e6c296cb17ded8d661167462409d035d33f8f410a1af1c9d7c94e3a65ea5bb07307f1472f9bf7d3a77d4d738bafefbbb37b11d76a8592e9c374b65d060a6e0432b1e507bc3121a282a9098f9b1c56d75ca6cca35104415bbbb52c70c65afa557e9a5bda404374ce6d4933c90b2bb75b3ad3579f19bfe2479c82140a0b2d0dc1c358799373f4b0054141fdc63e26b4d40735cb98b412e0a92db0db0d383f17d1a3c5b32c14c7e00ecce12114b2a14c522a007a951e5d02562699300507e2202c4ec98a17de017c2a98f9f696f74f012a63e0ddea2b4c40e88b9ea9eba8c6a614ee3d17688edd8bfd425e968efd6e41f18fa4212a02c471d1fe5f93f27bbc092530c4ac6f2ad3a6560773c63da42312ebe80209dbe7af5221fa153635f616561ac1701355d572dda9f76e1858ec211a1cddd0c571472cc1524e7a358c86623765427aff53132b5a43e43735b0724cf4f7888341d96c119180e1cfed86fd01c41ab9a7b9ed10c57679055a8590d57581a6d0249971f93e12e33f3c568d000fc1ed9f0f12a741a42ec1a40e365c1ef0da413aa7292fdb61c0e994f53d3698d0be3dafb87348b8de14bef041dc9e7928909858239b791c7981e35ba93a4550b707bde9024dae26188c586bd851e350ae151480f3b787fc3db0053c854ac64e45cfd0c7526783b1d9066c34520dad62fd5c2195a94f05dcd90289201e5ef356c1ef34c84a1f3908211af44c1a9e44f63c34bc1077872f831cf2d6227dc6d78093ae08420e2c0ee695b9b8876fcbe7a5e27f7167c33cdfb5d3315023c8573bd927cd69c7dd64f6725215180bc8ec72d43dbe87904c30e01fd9e1315d81e115af8bef91661176d0d43d12c5281e09189e4fa8da6ef31867c5e49229b5e9f1e762ad2820ad879257ae7d089e7c29665fe3350109b817b2ba1cff502fc48497b60a3fcf25520e674245d397e728388b0885fb2254724111ce886c3008b363f7fa3ee256c6d2b66b2ee8ee6706129c381a305f06e0bf6427cb59d072472eff3c40da57fa40bad15e9c5fe639305d72ea09049d2a4598b72aa20bbe11559f8a819c5bc7278828b036cbf3cdbad90b31710103fa95493a3e1d95f14311b48fe60856b93baa932b1264f8203a3c78d7c04f10295c5ee7acf6d430edc2028a04b182925feeca261c0d7721177163c92b273fdc65ea9e8dc8644588ea1090684ef3bb32cd09380e7e12c59b402bd772c4418952cc8a294ff4ff1e46364041d295cc8f55949a82ae31c2f69523f349d5a258bacccd74e56231e958f8fee981ff582ff63854b92917c9bba45cf614b18c5b4632989de011ed79138ab62362d26ef1fd5eebd2afa99b676fdf9ba3ca972fba10c25aa0cb6dab126aa1a390be51ea1f33fa64b2d28c185a7a108e7dab30c1b6294c066e945d9eb6fec0dfdb9691c8121fe3c63bf90063464f2d891bdd627ae213e03b80d381676e921583af3cd21a651eee9fed187b8e69df4575045e3917026bfd4bf22a9aa5c680ce3d5cc0e0637807164449acdb6f3b12ac057411106e158e043f2ae60fa35ef009d6037eb0a92bf7d3b84212cfbf3ec6bc236b7821f0b17152ef2f1dbd536790d8060aaf81de5ee880095e83cca416a8783a531b8b9affc26f14848e3ea28aa826521b91d20a4b663e9fd51696ad6dcfd0068afb00e91ff5a813da0d09e4e27ddd02d477c294acafe501df227625d3ec378c8ebce32a57811927421e2e173efe6c0222a7e111dce77d8ff4a253b43bb277027570fc74609252c0c480a91326a694214312e2f5cab400667c72202f583340647f2910a182c44d174422c75d287fbed54792a2d54d768ea9152152da70641eabc415a78251bf2cd430f248bd5e22089e8d574231c9823edd290fb508e77034dc2f163bdc0126715cc646beef40785e43a7a6c10fad4cc07ac70c73a86c05ab097eae1fa23091df78485e6c00c7e9ab187eb8e09177ebd746640f923894114b8a1c066370d18c21cd3f0779187e04600feb1150e3812fe80bee8cd54a1622028c75ca001bb037e569533f3d9c127b688309a0026ba070ab9931c737db933bff1d7feff03fde071d5d47462704098e3f37e18e509b1f0c3190850f84d9ce5bef54dd7fc2f3c3c578ee43c40ee92e6256f605ded019e527d4b2c3f5e4e29cc85841ff0e94697affc8a31cb61062bc94a0e39bb140656f880eae3933627a1239b9b85969b64e9bffd551162916a19cd0f363259ab1a90ab085bb36bace2486c4692d034964ac64b049d51febf49b19dcfa5f99c170b17bd9df0b4b930338d1ef2d5696406b529fbd7dc830fc8c14c6bd8c16f00f1253c1e478d7e88fd2978ba0bd327c11331425cf4b985fd58b14d879b9839e0620bf079427cc9eeb05626ce30880a78ce60ae4d6ac366b9102d69765aec3752b40bc86f3d0a057736d13e3c85e4713dca46a775894be6a86afc029e0f8a76cea40f2182cf1d36ca720da6d2b5b679660c5061add32b5fc152a820b782087ba66b0a07809344a0134d129ba7f4e19956cfd5c47b410b7302525494b9a3e397b51e3039ad1ef896965dec0bff9b358868014c10ca1fb842e3b901b0e510122498551f2e9d7794aad63d358a377b42b0c6d1d78415abe68f58df1e04dc5bf11769b1051f90bc0f3e131d24df2c55c2e7050e7d33d22ad39e5db73e9af8b1c00128081b8ce0ce2a9951c146ffeff3e1fcd55fa601217e3da2193448fb858c06be8d3c1f7e565a2e821cba698bc9a13112bdfe25250d3823deb0e7a8fb419d9e772a922eb38484d054315f4418bb9a05efd6b9bfc3e5ee052f6a7fde779216815dfc4f13478e9b3d87b0a82f217cd9b0e782f61a8697d5d6707d5e0e04ae039d32acb62cbb49c04501ac0643d2524d3709215390667fd7b63a2c0c85f63f7a392074fd094f6c07f5ef4742de0a6aba56f4b16d8ce2e6f7270e5906c9cac6bbd72e0440214dd6d3ec8f200878ca27d4d36ca018bd7d9092b0aa1b86e6ea37d637778c3808dd56099b383a37fe3b60526ab524475ac22dd37d44a10380cf05c26d795edf159e18b8e9e07abf492f5ad7031ab13106b34b66be1ecc8366bb6933be7233d02250b41b4fb8f028195bc6f56fb47efbaa729680c0a89e3bab9c2d109baaf5231fed54a986f8a867b839ba0b3394cab7b638f79fb5fd0003bbb4f17236d2178b244a32093464ef748333651ad7798fefe65c89e14efea7bf094f077db3dcaaa2153a977ffc12f1b060c3fc74f4cac4c8852b1b86683e693d11c4978c2879e757056e7665ae4ec522122f631b3e7be0e247c70e498be722e31864c30b9e131f00069729385ad3114bbf616f1ee7b0da8bcacb110bf9e27cb437bedf1e283c5261008c0347f73b653e6bf9ac59c203749cca809dd1c666d128eaa074865773063400f1a2f125d9711cbd61c211db9c22d974b11205d2af4aff9f8f5795a2aa02991bc661edf40dcd7704f835795a7eaaa5d2ffca5a355400fd8eb053cd2eb201b72cecc94ff7c922e1351c74bec7068a18db3043d9048ebd87ffccbadae1ec64fa2b05d1c7370963c3ce5976f94d3c3cf4337bc97e2862c5371d7de5fbf8b08cf00a469637d69d340a3acad48ee62a6da336abe55d5a82d1c88e1a4cc0d64019010879351b300c0d4f5c8ec7593bcc6a9afa75eb0b529fe079bd4e99238be11c171ced5a0a55a72439584a10a548ff26452e842426bd0572527c6bb2e48b5f9ecb117d9f4717426d96ddc8c4e5c2d3cf707a3a44a20e5d564401a54fcbb2bec9c81527aed8153ecda2dfe77976690a75f4ad60038a20a8287fba638619b1bd8acf09bb43d9e15613ca424fb0f4b15117d8bf4cc72ceeabc45b803d6a4657ac4acf0bfbd262fd8158150068a3e2e6e6b97148231ab66fedb12b089fbf9ac106e6d70aec7ed98cc0a00c75a21498193b1249a75d4a121c8de1da14e8f03d76530171172b24f9cf58ed5bb84c96a80bff5fb44d8276815de2b11f63280aaf0910c977015b57b509baedf1a0abb96552ded4ffec30c1aa214f478ffbad0332bf585026177219291f2a7a5d6a47eb312e333eebadb57634b2c6b7b42340883b9444a9cf0d756bc8ec5020031ab0255720813527bf9ea6fa107c276178962953db9abb091a6dfccc0ee73df060ed6671445cb8ab79645d36a4c81daf02949f2ccac2f2b50d8c680c18a8038e2e4fb9e4613caec509de7e672fea040c71ade0f84ddf82442060de941d416b6cda862fb8a064d0721b031c7579c9b917480ee6e05054b3b70b73cb22a12cbebf44319d890f99ae72567d4fea41d298a200676aed5c9c440d2c1da3167a37b4a6a2a8e3cab3ab003d0a9bd75c1f3565db7440a50840afa0d820ed8e1a347f5ed5f14306fa9d6580d65de70e3d9e6ff372855141bf3baab542143e686a3af2c0ad7eec32397fea1d8261d7ad9fd280a835cfa66015b1e27517225c5c595103ebf1dae43c83a1b20d5d48a99fdd13de8804b71a36e29bb9220d2eb9e83179fb7d6d036a24a977f063539add79293bc4b8a514db5b794b578e0719e14d05a12be872f6d6d6eac49bf338195d99a4a767b10a4ef31b8df5c8b2240e27dfa9b58ab84a85882f45235480b01b40a00c94a4f7030f0963ce2a4cd8b0263bbcac11527f42563962bd99da5db443d0b1e0033795ca9a8c7903f1f339f6255933c3493c3bf79745abbd70be31483d1cac03942d2168a4f1847170fc85d302fff1ab84401d0eab0359433eeaf7cd60090c9d81e6c8e5c07e205eb049b2f10c72d392a223087ab81ca2a4f0077e20706fab5f7f305bdde4d0a9153a4915de2000df2d1e2967f24ba50b979f116f205af7492a83adf7a55eaa247ee60a2c421e43b1b9e7b656041004affd6a20ea5bb0ef63b91c467780eb487c6070fab41811ce6b8ad866d10db953803612ed1bbf5988481c603f79276edf7e871459055802f3d0053b41b8792dfdd36153e6ed6258f93c1f8e0b8ac021b9b94f525408b202a84935cd7589deadc831c1c5605db0e200fad3e10317a51d3fec67451b252c292e7b1774e660407e5a15b34a8c9f4d61f0e733d8c85ca0963b9f22a8435baa12070dbd1df83e54b0b2a1acc4171d1caee9a39fd3c7923cc6871804cd6a45241522f4d78196ee7eaffc973e1267f5890b93c6d0653bcef6aa53b62ed9b61b841e8bab815b1ded0129cb1b38c56e9a754538a399e7800b170ca36571923260a12d6bb79a7fb8e6dc8e5b7f00ca46cab952bce4483ad5cd24ffbf0f717380323122c46ba422755404ae8510b406b2fd907d9aaeb3963a3c27ececec99a33edaa9101f93327e93488b4a619d33020e760bf00158724f273434655cbb6f8d1a975c132e4be33e35e0efc792875a03699d80227c2f4a82cc1c41779b595ec6b229ca1aeb4878e91d33f5c1f8e6f4fc6dc0272fc57bc3f5695d22c8d8c48d43c1bdc31d2da24ac5e9316f10b959e21550a2cfd755abaefd3f38123733ffcd44e99736179ac0bae52fe1f58c61775f68d32f624cd87d2feb6cb4dbdbf4373a9d66b80d12eb80cbbe95ba9303626558d899af8106ced7de5ee279a67156c0a3c331aec02e87e29957b9c0ac857cce8a96e3f1df0c20d5de2919263398f66b9d600a73211678262826d92f4bcb00a9d6eb2c2eb2a7ecc4938a4014ac4c51c552607b6dc10cd7a5bd4df68a63f60e99a4217feee85660182837c340fc514b1babb8999f22252b722718ed1b4cba7feeea9e62cf1f928561264ffccd96ba25a5b2d251034805640ec7a51cd89c8c15551954728e5c4a0fb51a2eb096f42476fa59c85cede303994fab2c2b79d77b62c0e603897a38e965fefa190cb8f5a56e41f3e5d982ec1d07f555fdc8fb2ad2f4ffe98295c150230780734aa03b45309488fd2833b8381d4757d04fa792cc07684c3779d6e7925b62e7a9c7e4a480bbcb4d6fedac1f18128d5eb739d4b69e8b21071a541c26179848912a0d4cc4ff0a781cb6b31f878f02974c4f50900ed33f113e0bb0f26468012cc533c2ca3992893bab98c90ffc870737d1e3dbd4fefb3f3cad23798bd53a8582110eaffd4d743e338d4d722f37ce1ebcc857a827e17f21b11559e1967b8d77375ab7496a26c4b0b074a869f4dba80ee390a805941102fcfd60ae53451ffdea972f2540da6e0158f526d3746ad4151bbfc38f1e51d61b972ba86463812c0757425438511fb52e822cfaf6de308b220969aa9f86e81ea726dd04aec3f9d5ac8a8f17b0f20f9d609076fc00b14bd3fe02345a027a720c6e69b4a63e5e36183dbfd0f8cc4e91bbe3c3068c619a1b1d3403aff3af1b56cc7b1739136e60067ae287c98d9e2ce149030a202ac73941a6ff0f94086685820732dd374210e88b9f4b63485d7aedd71fb847236dba844137ce0450b47164a5f527c08afd78e8f640c14d87643dd72e423cf6fbba79157dcc420566c93478dd666c41ffa31c2ecd7be1dbce9d9bdcf168d4ada263e0d0f430011c777c6b3b40b46080e53d4119b244eff42c6e69ed48279af0cf4770293edaf511a16c3963ec99669ac601b463cbf66e3fab9c15d3060e000fa88e3d05bc656a1ba70800b6c5741a4423df85e4054f05520df212d9281dc34f7fb9249ecf8c3c0f4d2c8f7acc0e83aee1d251a75f8b91adcb606c62d3ede2690a94ac2278b8bb19f63535ec4947928b83cb975772c1af82294fa24b19f01347a7152ac2c3ddc62079fcae6d7b3760bb8b4c789def8284a0c967e2a4952a1a004f5f0eacd2cc75056ccd1e5bb952dff6b0d686e0518547fa090b71c61135b66143c3902bb940381e174fb8cabadfc57cf2b221173a632bb2118164ee73e7551653239d0f26b0ab0251555276a22689f249497e1aa2fdb80c1ccb10f1c9251356172afcc8daff492210ecc2afbe7e22c86e233d33094753e2d99ebb44400299bfadba8f00742d9d1a3407043af6996354516cea8df2b32a4a812e495d44034010823b33d462803f0b1a6e0bbd39623cc963d33a0a091ca88a609129f5ca0aa07564b4ddf1cf3fda284e629a09f29850659d05d665d6239a15df892595a38396e55b60e15f582ba40f41fd33fc05b2dfdcd49d4d2c57699a0ed952941b8cad789dcf07e30b681da50407bed8c5b81c23c1b3b42c545d849a58c8a573234b62976474fcf2a8fcaeb807573f4628bc97825e7f54b1a3ad043bea287e4445e53f042c4c6a15464841ae11ff27679c65cd10dc23087e03e9e29be3c1f3a2e9cb0ae4fa6854e09b9d26f1063cbac8be17a250addbc6bd7abaae71d56e594050d5c1d7df6b7442966069fd243dd956498931f6cf7cb430a0cd69c77ff8e4607c1afe006c8d74c3f813f7b41f1087a949f426d578c92f78fae3702cc06f55f93ea35fe7b8d001c8f615ce1d1ffe8754bad06ea54d0a3112a3810bf1bc5b9ae3baa0d2ab3c099718bf98bf4820658f23fbdb337379fddaaff6303ce007a89157a5a9a8b2509cb6f2e0c72114244743184f8156393fe128396af8c48d5a74dc8946b61d66137aa6ee7d4930cf264a587fdbd7aa0c068804057b2215c264c3c77663dd4cb58f2bdc2de0b5ab112b104da9bac0d9db69a448412588863ea869d4f890eb8e4faab5827299a111b42ee9463654303cd9f4dcff5e58afe88724abcd3fe4cb3168aef9b6ceb7e9b1a6156679fd2085e6db996c6540ada72d5ea0cff41cdb9483648f9f2685347e7bc512a9d6e535978c5ed0ce38f4cd903a38b3b61ddc2d79e14080166c08fb5272b52644a0ee32396a63c02d3746844ceec71816272a612ce308778c70a75119f5b0140916fafdc97b1f9dcadb8fd0735e7c5e57c9da0c017770e01e20ea42b73f771522f9921eeb1daa7e293c702d0f61b4569b5b516fca3820d31f6dd4ceda2f9728c0cfef56256dfb1a8d61cd21a3f45f7eb98e3091e05328e13e50cdbc6843ce2d8f397f0050b564b3ebd0acbafc6510b3e86e22b2d2047f8ec5257c46db621209a2a078c9dec80cc74b327642819a2f99c7be4b2e1d2199e2f2beac3dc2e5022d6d91efdb7b76b09877ceed20947ac3cab1538083c437423814930ef96dea4c15090bc6cebea4517d208e2e824892c6e16377ee7929e61d65ad7237dd82598010f7abaf49e01ec9e6e08a82c4e853284210a8c4702df5bf12123f71a1f0f5160d6794f9c7a9279b930eea4d341380919281804a4e94872273e2e5cc539c76901209f99b730c355459134fe2c0f31cf6b4588b1d44d3ebac7719797d95bd035208afabe41e1975f751fc36aaa3407e07c6d57413529e35a7c1b38491422ef1291f6a30c0c6daf41e7058c2591f6b097d9b2edde98f1b20ce6fd5aece66bea7fa2915ce7d77023ae83c91694c5f0b737add3e40e4843a6c18f6d707c3d2c85df0299529419ac1919c5bc769faedd0f12b209ccb799f5dc915a985c0708c680aa11d7895f6e24a12d2bfd0a60807702e15814b1520e87f3409de46e5209973232d01db4272a97d90adb153e7cc27dd74f4185a29398200a656d1d9dfd683aaf39313946aea52c413519d6b3bc374cc173cff10809b8909baa02b1e5159edee8bb62eac59720ed6dcacfe763a0b7cfe322855ebf7bff78973198d6938e4ffd88c8b0d32ae740ef3e56931f09c0c29f96ab6c186380f5071cec86000f7a22df3a72d0b895de61a03eac7b5558fbe848e4f10ff02e3908077f41ad02d24fd4cd0bb6b26e7e931f92d07c7fb6398efe14d166d8636b74c50f36125536e8d4e54b8ef490f9613caac20bc9595db4415c3c2220cdde4da13c96c7fc5aec1fe4d3959b9f924f963f560c6b25f8fde46885119f57a6f7371258532c6993d273a15634e653f02b022ab922892ed90592aab7ecbc248ca426f6c781699f05b94fd9699d9193d1fac31588805eb9e9c2b0f6d07d672153267fa971b65d7919fc06fed449ec9f10cfa1765e6fe32511c5cd9964110fa2e8c31067d0aca0594a8e30930f7cad4df0852eeb8f4ee0118d291d2d6bc65e845de6259392a3a23f0ebb4b8044e9b033a0e02436c782b517fd3571f5e640fc633f9eaea7afb3e5e52d7fb7ae85002bd43280e9cb71ca103b038f5440f07ffb5c07022c63c71b7b13c5d02d95611745d5e163831b7d8405c891e99b0187be7e16ee91a4df4d51a1f1e7471a4df113b506b27d0bd739504efa54410a51805c7a0c3473591bc5e1fb407b88989972bc23efa2d26ee8732f82d458d22a48f6f87698fb230510278f71e6110d3c824c27b0ad00a8cfa8560bd7d8182893afe1d9fb2977cb50d6fa56f26d1517d2f71e0ccb42622faa7016742636fb83a916407f2d4efc89f9946cc1cd1e0d1cc3807759633cb0510e8ac665d0a45803f1c9568411eef994874bfcc7931df17395ff51241ff5c124400dfcdd47389cb656691a889fe095e97f339d36646df49e57b965d8b33e214b4f694f8f0925e03fd5f40981b9286d6e407f18f92ffc8c85f83ef8d07181312b7055d28e732a517a8521eaaaa90a3ca67de07dc28e701b85c5dbdacfd5a5008b34dfe53d6fc73f30cb18c00518041bfc57d9f64b64e8678cd4c1188676e530227175b1eef5ff44203e722278bb91f98fe8b911573c39b9f761e7f73dad7a92dbf76cec5be468770a1978606c24b0d3e522f5120b693eff54d38b1d10cd80f114153657e6e4376f300f59fecc5e7709e17b7e14fe4af20642b7ad7bfe91bb0300fd5d9db6bf4df35703fe05bff89f997c274d95ccf9bf07eb5ddb8c8b8fc9c2f48ece63e29a592ea41c2685c73cd1097a2134a4c484b2b53d6b4bb234739e22abdc8030545ac4df71a37aaefb9c7a704ce293764bbbd30c4baadbcf6c369eb0ec714758cb33b4e304652545922f487f96c6a267ffd7fc0d7c11a25d6e0429608c2f6df6ef67e6390eff059329d36ba9db3eac7bca7a8f2ca56840f6141305f2c116a3db192620c0a131bbaefac4da65c54529043350d609fcad09ade9a24402e8725b3338cfb990f458ae7a4c52ccaf53ce9ae282547a422a26827d1d3dce2274e9b51112988587a3ab714f0ef42a1d876dc39e3c112a79cae3896a33d9a13072177ee6d6fea94f01eb78f935f8c9de33b62221ba77b347b9229a334d417182e59c4a6cd9fa657cc2a8667b92f6bb818b472c5d7715b61bc5989755ee041240801037f656357e6c0fa53e6b42cee535390a6876123780b29ba90d7c558da9f12d9a37b69f6f5b3e924428e8b08fc14af078a468c81f4c3c6c1e06c277de2a620a0f69e36deb8c19cddcb73fa68c8301e1ebdf39a72ade861dc02e5ab7f8ab428cab45a4ff96e733274d6d87f1df3e42f219b2d613394a1755de67872ffbeed131a535a20e7c418741bd73ca355044fa144a7936e930279ed6e3ded182d8cc61d89de26952b776cf1d17ed04d61968d07370a8d3950445b4bd9642394f003ff14a6c73f993dc9ca33bfb497cfcb7f27a2f3f2452fead8ab31a0182e3d3b70632ed9d410f61054261e79d7a3ac90233c3b473a3576fff10fcfd468e95749cbdd2add7cff11960f67ed0ef73691934d54f99e0ee94202a349f03436c695b561a81e808338a7d39286e584ac61ee5d2d94c0c22bd24bec13e7e10ef3125e2ae98301659ef85444a286a35b4837e5a034f3f1e4acfa0ae181957571788a160dec670c479afbd10fc99caf3c58705fe8f1fa7cd599743838be1e46742adc2f33a8691dd72b5b0c3d6dc48a5d7fb4bfe2e7112ab91b325348739db3d804d56d207583091d5c455a120de60438a23ac79f773c4f3a7d3f6ba689cd73e78d8fcb11c34928e9f3e2950b59524e5c978460553925ee38e93f4a36ba9fc319a2cb9554530603f73f6d43197e47255602bdbfa9d0f42763a3d7c827fe7652119d280f8bfcb1291f0c1ef60919a98e8fd034a894a276ef544c3c726a4d7765486549002b0b6401c820c3ae13077142caa1a9cf4dfeb037b575379d09bbfdfad8e9a3a65b442a2eb998ee8036dc8ee44b19765f3ad89c68250136bdaebcd6f20a64a2a996a0ee2a2e15af6c67e71250d9fb7dc0c4f5396938174d1836f598c91312cc390edbac22082e3de6dc6c42bbfc782edb4735308a414b581425c46fa1899c43dffab7ad1470b789f3a376d8f05e382e4eef5ad2f730a4c6ca71b3dc181bfedc014a3d2b00dea01112567d0465b3e605cbca12e841543e9f39ecc126724ac802fbccaa8c0238a905a0a909ba0c4d302b30dc3b77073e601e426d5ecdcd9e991f8cb9a833291480283cb121f1bdc9f638f91033e3aa6b5c64dc4006f5aae7cfd5d5bbfac70ba40e9cb25808a0a689515f811e67e7d63cc307cc75654506f44405570f452418754d1712c04993196cab35c7ea1f2144ecc1543a2c4e1591f3b94ecd495fee1c1bb846112a5aae6cb6a19faf9fb27b33277f32daa6f82c824fbb3055507bc818f96b1ec9ef2de81da729dca128fdcda775a39fab77b88be963fb242a099ccc0ece6a91d0bf6d93df0e19f975ad7da6e9f10a546949ebd6b18180ed3825a77e1c6e15d81d01398ec10c8fd8724f52c696b34de7e26ac48375189ddd503e19ed293aa1cd6eae95bcf17f3add4b4dfa09c3433fd656e0c3dadf111b8f1143a7e22516d371c2a38e182f341290fbb5056ac9f1ea51058ee260825918bb89a393502df87d475d7fc3d36b0dc606bcf6c369c51a422d9b95752543704a8d9c2ac374271e2d61cd899fffba684b47845bda1683342d76d8ee853a801288cb933b889f1446f0ad542d89e6721fae1dcf14690775cf7b2bea19715f14e2ce869bfe48871f14cafb1ebc870c16ad964b0571772d77f785dc4d116ac3ec7dd3e6e33a316d292cf4826fc7534dd40a58496a31a6698165ed2d15d6b65f4cf65a083b233081182c75c5c7256093280baf3e6039b485c966a4adc93e968128151b789e493478216822e156940e5383997307d339c3cd0f6e6b797ed3c20efa02724897beb560224117481c8293bacccec8b5930f3debc7459fa6c8e71d9f3277b14c5fa01c4311b90c808a4fdf40dc95186163f4e28cc568c5e5acbd27b7618bd0e5c9bb7efe2219af0f6204b7497060c1d9601f9efc7f317f7c6f7081ab661059f573305d9e2fd2eb51c85ac7b45341c2cc132192bc506304df61661e833aa262d3fa75e21208e014fe42ce0846f8b16168a662a57a5b55d0d27af4237a165855fd27f122851019b4c04fb5d022c676fc313ffb296a03eef785638c893853cfc602561ad32b101fd6ad5ea0fea4d4d5a492017fcad531bc307455c1b7905de3408a9d8850ce25e8e3a1e6ed3c3306eb705fe3785cb93b49278b24fb6a96793798d2f0a057052df7591a5099f3fcbb2e0e7410709cc4bbbe1b05460812d481bb5f02b59603601e677bb528c3a003004de33da4925468554eac3cb3229f138b19a1278a83d7ab15e9be54c76e61c983702c43cffc2d2e9b91907bc8d7274aa2212996a91e1c880655f31c7e5235bd1859b5362ec0d4e1c591b9da7391a509c56d6f7a331e6e7e0e8bd781ec70faec75a6bfb9b6405e5ad2ffe2acf7ab915b986a456f16b61c3f0e1244ad90e3f92e0b892ee2608af2845ec819cb196bfb2cdbda39e30bc912d81b93a3d81947e6fddc6b4f3efb313fed09e5c0d9f6a27b1e97bb5a143474d3b51a0a41cc70485edfc31889587523d0da2943be33c2ef8f1298b0a6194efa03ac29ccc15bb12ba5b38d399585be59fb9ece3a9e74f5a92249c7da5ff28469859c0770a3a910361232dad8292a99fcd4f06d55ad29a3abb22aa9de47d58d9567572260eb019429970a087a1bc0f84db9d940b67051598e083885cf0a54dd7c7f41166e9923e928060b8c82bd127cdddeb969328bd242e5043220916323ddef3e8f09c3519aac68604cb8f6cc8a01aa514bbe678b3c2d57c6a5d69459f3c6adbcd0064007fa97d6dd95157d7281574bb25d82d45f1357627bd5426b55360c9e1c022d671d7c295d43f13f84d21247c8b1ae74a060e60e8aca8c6e9520e0b6defe58", + "public_inputs_hex": "0x15c9b404c344691839abf638a132bd083b09e98c2fadb268923013737d217ad41dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000cb80a0dfbeaeb79af355a70d8f19855c00000000000000000000000000000000915681ec51465d5a3404f61907428b7100000000000000000000000000000000666965e3fee8efc43146d3b5cc157df900000000000000000000000000000000b2039cf00b2cd6017d01a8e38e5d2d102b26ea14534f288a6affb3a0aa4953baf450f69fa48000890435a8ee45258a0a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000214ae98e2b0527277d74d7898e5a1d76041a613ee74f2db890bd6db2fd3a76e9d30329528532c70174aa6a80f652ef9fb86217d722ac4e756dac8ec699873be87009de2f6efc0e2420bebb740d52be9795045be2c08d2f61c665fd8c2946f25a70037344482bad1ff4c46d1ed3887ee2f37d4bc2dd3a1c34c3c6a0b0d75d4fa630000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } From 244a455fad92a77077fb17d0605911bcf33c368f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 16 Jul 2026 20:54:42 +0500 Subject: [PATCH 21/52] chore(contracts): refresh C-03 predeployment storage baseline --- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 2 +- .../storage-layouts/BondingRegistry.json | 114 +++++----- .../CiphernodeRegistryOwnable.json | 128 +++++------ .../storage-layouts/E3RefundManager.json | 72 +++---- .../audits/storage-layouts/Interfold.json | 203 ++++++++---------- 9 files changed, 256 insertions(+), 271 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index e7747f68d..6be186057 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1188,5 +1188,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index d53ac98b7..acf21a739 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 0488792a4..290964674 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2428,5 +2428,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index b35935826..1f2c4ca80 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1450,5 +1450,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-a440a2961f63dbd5f95bc04c253460d7c2106815" + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 43141aa1d..155392ed0 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1384,5 +1384,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-60a110ad1306dba588e2305638dcf9f89d966d5a" + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" } \ No newline at end of file diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json index 3313a030b..1ad5b5b1c 100644 --- a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -3,24 +3,24 @@ "contract": "BondingRegistry", "source": "contracts/registry/BondingRegistry.sol", "baseline": { - "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", + "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", "sourceSha256": "63dce01cce37e34a48bee91ebcf5d01f62cc85472418e4ac8e08cbce415e6097" }, "storage": [ { - "astId": 26811, + "astId": 26719, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketToken", "offset": 0, "slot": "0", - "type": "t_contract(InterfoldTicketToken)38747" + "type": "t_contract(InterfoldTicketToken)38646" }, { - "astId": 26815, + "astId": 26723, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseToken", "offset": 0, @@ -28,15 +28,15 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 26819, + "astId": 26727, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registry", "offset": 0, "slot": "2", - "type": "t_contract(ICiphernodeRegistry)21722" + "type": "t_contract(ICiphernodeRegistry)21717" }, { - "astId": 26822, + "astId": 26730, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashingManager", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_address" }, { - "astId": 26827, + "astId": 26735, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributors", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 26830, + "astId": 26738, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributorCount", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 26849, + "astId": 26757, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedFundsTreasury", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_address" }, { - "astId": 26852, + "astId": 26760, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketPrice", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_uint256" }, { - "astId": 26855, + "astId": 26763, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseRequiredBond", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_uint256" }, { - "astId": 26858, + "astId": 26766, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "minTicketBalance", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_uint256" }, { - "astId": 26861, + "astId": 26769, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitDelay", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_uint64" }, { - "astId": 26864, + "astId": 26772, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseActiveBps", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_uint256" }, { - "astId": 26867, + "astId": 26775, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "numActiveOperators", "offset": 0, @@ -116,15 +116,15 @@ "type": "t_uint256" }, { - "astId": 26885, + "astId": 26793, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operators", "offset": 0, "slot": "13", - "type": "t_mapping(t_address,t_struct(Operator)26879_storage)" + "type": "t_mapping(t_address,t_struct(Operator)26787_storage)" }, { - "astId": 26888, + "astId": 26796, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedTicketBalance", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 26891, + "astId": 26799, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedLicenseBond", "offset": 0, @@ -140,15 +140,15 @@ "type": "t_uint256" }, { - "astId": 26895, + "astId": 26803, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_exits", "offset": 0, "slot": "16", - "type": "t_struct(ExitQueueState)24594_storage" + "type": "t_struct(ExitQueueState)24575_storage" }, { - "astId": 26898, + "astId": 26806, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityConfigurationLocked", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_bool" }, { - "astId": 26901, + "astId": 26809, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "reservedSlashedTicketBalance", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_uint256" }, { - "astId": 29126, + "astId": 29034, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "__gap", "offset": 0, @@ -178,8 +178,8 @@ "label": "address", "numberOfBytes": "20" }, - "t_array(t_struct(ExitTranche)24563_storage)dyn_storage": { - "base": "t_struct(ExitTranche)24563_storage", + "t_array(t_struct(ExitTranche)24544_storage)dyn_storage": { + "base": "t_struct(ExitTranche)24544_storage", "encoding": "dynamic_array", "label": "struct ExitQueueLib.ExitTranche[]", "numberOfBytes": "32" @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICiphernodeRegistry)21722": { + "t_contract(ICiphernodeRegistry)21717": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" @@ -205,17 +205,17 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(InterfoldTicketToken)38747": { + "t_contract(InterfoldTicketToken)38646": { "encoding": "inplace", "label": "contract InterfoldTicketToken", "numberOfBytes": "20" }, - "t_mapping(t_address,t_array(t_struct(ExitTranche)24563_storage)dyn_storage)": { + "t_mapping(t_address,t_array(t_struct(ExitTranche)24544_storage)dyn_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", "numberOfBytes": "32", - "value": "t_array(t_struct(ExitTranche)24563_storage)dyn_storage" + "value": "t_array(t_struct(ExitTranche)24544_storage)dyn_storage" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", @@ -224,19 +224,19 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_struct(Operator)26879_storage)": { + "t_mapping(t_address,t_struct(Operator)26787_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct BondingRegistry.Operator)", "numberOfBytes": "32", - "value": "t_struct(Operator)26879_storage" + "value": "t_struct(Operator)26787_storage" }, - "t_mapping(t_address,t_struct(PendingAmounts)24569_storage)": { + "t_mapping(t_address,t_struct(PendingAmounts)24550_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", "numberOfBytes": "32", - "value": "t_struct(PendingAmounts)24569_storage" + "value": "t_struct(PendingAmounts)24550_storage" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -245,20 +245,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(ExitQueueState)24594_storage": { + "t_struct(ExitQueueState)24575_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitQueueState", "members": [ { - "astId": 24576, + "astId": 24557, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operatorQueues", "offset": 0, "slot": "0", - "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24563_storage)dyn_storage)" + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24544_storage)dyn_storage)" }, { - "astId": 24580, + "astId": 24561, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexTicket", "offset": 0, @@ -266,7 +266,7 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24584, + "astId": 24565, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexLicense", "offset": 0, @@ -274,15 +274,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24589, + "astId": 24570, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "pendingTotals", "offset": 0, "slot": "3", - "type": "t_mapping(t_address,t_struct(PendingAmounts)24569_storage)" + "type": "t_mapping(t_address,t_struct(PendingAmounts)24550_storage)" }, { - "astId": 24593, + "astId": 24574, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "liveTrancheCount", "offset": 0, @@ -292,12 +292,12 @@ ], "numberOfBytes": "160" }, - "t_struct(ExitTranche)24563_storage": { + "t_struct(ExitTranche)24544_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitTranche", "members": [ { - "astId": 24558, + "astId": 24539, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "unlockTimestamp", "offset": 0, @@ -305,7 +305,7 @@ "type": "t_uint64" }, { - "astId": 24560, + "astId": 24541, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -313,7 +313,7 @@ "type": "t_uint256" }, { - "astId": 24562, + "astId": 24543, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, @@ -323,12 +323,12 @@ ], "numberOfBytes": "96" }, - "t_struct(Operator)26879_storage": { + "t_struct(Operator)26787_storage": { "encoding": "inplace", "label": "struct BondingRegistry.Operator", "members": [ { - "astId": 26870, + "astId": 26778, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseBond", "offset": 0, @@ -336,7 +336,7 @@ "type": "t_uint256" }, { - "astId": 26872, + "astId": 26780, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitUnlocksAt", "offset": 0, @@ -344,7 +344,7 @@ "type": "t_uint64" }, { - "astId": 26874, + "astId": 26782, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registered", "offset": 8, @@ -352,7 +352,7 @@ "type": "t_bool" }, { - "astId": 26876, + "astId": 26784, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitRequested", "offset": 9, @@ -360,7 +360,7 @@ "type": "t_bool" }, { - "astId": 26878, + "astId": 26786, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "active", "offset": 10, @@ -370,12 +370,12 @@ ], "numberOfBytes": "64" }, - "t_struct(PendingAmounts)24569_storage": { + "t_struct(PendingAmounts)24550_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.PendingAmounts", "members": [ { - "astId": 24566, + "astId": 24547, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -383,7 +383,7 @@ "type": "t_uint256" }, { - "astId": 24568, + "astId": 24549, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json index e6baa466b..7d66b1403 100644 --- a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -3,32 +3,32 @@ "contract": "CiphernodeRegistryOwnable", "source": "contracts/registry/CiphernodeRegistryOwnable.sol", "baseline": { - "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", + "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", "sourceSha256": "033ae30ce7a728081ca6cf34e050c970a5ed23fbab21ca81b66f4c8bd759af5e" }, "storage": [ { - "astId": 29194, + "astId": 29102, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23293" + "type": "t_contract(IInterfold)23274" }, { - "astId": 29198, + "astId": 29106, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21073" + "type": "t_contract(IBondingRegistry)21068" }, { - "astId": 29202, + "astId": 29110, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numCiphernodes", "offset": 0, @@ -36,7 +36,7 @@ "type": "t_uint256" }, { - "astId": 29205, + "astId": 29113, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "sortitionSubmissionWindow", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_uint256" }, { - "astId": 29225, + "astId": 29133, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodes", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_struct(LazyIMTData)14046_storage" }, { - "astId": 29230, + "astId": 29138, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeEnabled", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 29235, + "astId": 29143, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeTreeIndex", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_address,t_uint40)" }, { - "astId": 29240, + "astId": 29148, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "roots", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 29245, + "astId": 29153, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKeyHashes", "offset": 0, @@ -84,31 +84,31 @@ "type": "t_mapping(t_uint256,t_bytes32)" }, { - "astId": 29251, + "astId": 29159, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committees", "offset": 0, "slot": "10", - "type": "t_mapping(t_uint256,t_struct(Committee)21134_storage)" + "type": "t_mapping(t_uint256,t_struct(Committee)21129_storage)" }, { - "astId": 29255, + "astId": 29163, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashingManager", "offset": 0, "slot": "11", - "type": "t_contract(ISlashingManager)23940" + "type": "t_contract(ISlashingManager)23921" }, { - "astId": 29259, + "astId": 29167, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgFoldAttestationVerifier", "offset": 0, "slot": "12", - "type": "t_contract(IDkgFoldAttestationVerifier)21825" + "type": "t_contract(IDkgFoldAttestationVerifier)21811" }, { - "astId": 29266, + "astId": 29174, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifier", "offset": 0, @@ -116,7 +116,7 @@ "type": "t_address" }, { - "astId": 29268, + "astId": 29176, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifierAt", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint256" }, { - "astId": 29271, + "astId": 29179, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "accusationVoteValidity", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 29282, + "astId": 29190, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidity", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_uint256" }, { - "astId": 29284, + "astId": 29192, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidityAt", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_uint256" }, { - "astId": 29290, + "astId": 29198, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgPartyIds", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" }, { - "astId": 29295, + "astId": 29203, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgSkAggCommits", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29300, + "astId": 29208, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgEsmAggCommits", "offset": 0, @@ -172,15 +172,15 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29316, + "astId": 29224, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "_committeeDependencies", "offset": 0, "slot": "21", - "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29310_storage)" + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29218_storage)" }, { - "astId": 31840, + "astId": 31748, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "__gap", "offset": 0, @@ -234,32 +234,32 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21073": { + "t_contract(IBondingRegistry)21068": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IDkgFoldAttestationVerifier)21825": { + "t_contract(IDkgFoldAttestationVerifier)21811": { "encoding": "inplace", "label": "contract IDkgFoldAttestationVerifier", "numberOfBytes": "20" }, - "t_contract(IInterfold)23293": { + "t_contract(IInterfold)23274": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23940": { + "t_contract(ISlashingManager)23921": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeStage)21097": { + "t_enum(CommitteeStage)21092": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.CommitteeStage", "numberOfBytes": "1" }, - "t_enum(MemberStatus)21091": { + "t_enum(MemberStatus)21086": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.MemberStatus", "numberOfBytes": "1" @@ -271,12 +271,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_enum(MemberStatus)21091)": { + "t_mapping(t_address,t_enum(MemberStatus)21086)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", "numberOfBytes": "32", - "value": "t_enum(MemberStatus)21091" + "value": "t_enum(MemberStatus)21086" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -313,19 +313,19 @@ "numberOfBytes": "32", "value": "t_bytes32" }, - "t_mapping(t_uint256,t_struct(Committee)21134_storage)": { + "t_mapping(t_uint256,t_struct(Committee)21129_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", "numberOfBytes": "32", - "value": "t_struct(Committee)21134_storage" + "value": "t_struct(Committee)21129_storage" }, - "t_mapping(t_uint256,t_struct(CommitteeDependencies)29310_storage)": { + "t_mapping(t_uint256,t_struct(CommitteeDependencies)29218_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", "numberOfBytes": "32", - "value": "t_struct(CommitteeDependencies)29310_storage" + "value": "t_struct(CommitteeDependencies)29218_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -334,20 +334,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(Committee)21134_storage": { + "t_struct(Committee)21129_storage": { "encoding": "inplace", "label": "struct ICiphernodeRegistry.Committee", "members": [ { - "astId": 21101, + "astId": 21096, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "stage", "offset": 0, "slot": "0", - "type": "t_enum(CommitteeStage)21097" + "type": "t_enum(CommitteeStage)21092" }, { - "astId": 21103, + "astId": 21098, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "seed", "offset": 0, @@ -355,7 +355,7 @@ "type": "t_uint256" }, { - "astId": 21105, + "astId": 21100, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "requestBlock", "offset": 0, @@ -363,7 +363,7 @@ "type": "t_uint256" }, { - "astId": 21107, + "astId": 21102, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeDeadline", "offset": 0, @@ -371,7 +371,7 @@ "type": "t_uint256" }, { - "astId": 21109, + "astId": 21104, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKey", "offset": 0, @@ -379,7 +379,7 @@ "type": "t_bytes32" }, { - "astId": 21113, + "astId": 21108, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "threshold", "offset": 0, @@ -387,7 +387,7 @@ "type": "t_array(t_uint32)2_storage" }, { - "astId": 21116, + "astId": 21111, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "topNodes", "offset": 0, @@ -395,7 +395,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 21118, + "astId": 21113, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeHash", "offset": 0, @@ -403,7 +403,7 @@ "type": "t_bytes32" }, { - "astId": 21122, + "astId": 21117, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "submitted", "offset": 0, @@ -411,7 +411,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 21126, + "astId": 21121, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "scoreOf", "offset": 0, @@ -419,15 +419,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 21131, + "astId": 21126, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "memberStatus", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_enum(MemberStatus)21091)" + "type": "t_mapping(t_address,t_enum(MemberStatus)21086)" }, { - "astId": 21133, + "astId": 21128, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "activeCount", "offset": 0, @@ -437,33 +437,33 @@ ], "numberOfBytes": "384" }, - "t_struct(CommitteeDependencies)29310_storage": { + "t_struct(CommitteeDependencies)29218_storage": { "encoding": "inplace", "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", "members": [ { - "astId": 29303, + "astId": 29211, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfoldContract", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23293" + "type": "t_contract(IInterfold)23274" }, { - "astId": 29306, + "astId": 29214, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bonding", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21073" + "type": "t_contract(IBondingRegistry)21068" }, { - "astId": 29309, + "astId": 29217, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23940" + "type": "t_contract(ISlashingManager)23921" } ], "numberOfBytes": "96" diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json index cfa8c019b..906ac91fb 100644 --- a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -3,11 +3,11 @@ "contract": "E3RefundManager", "source": "contracts/E3RefundManager.sol", "baseline": { - "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", + "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", "sourceSha256": "a6ba37835c555d69dc3592a8b47089a2a34e5dfbbe87dfa1b95f1b318875a21b" }, "storage": [ @@ -17,7 +17,7 @@ "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23293" + "type": "t_contract(IInterfold)23274" }, { "astId": 15244, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21073" + "type": "t_contract(IBondingRegistry)21068" }, { "astId": 15247, @@ -41,7 +41,7 @@ "label": "_workAllocation", "offset": 0, "slot": "3", - "type": "t_struct(WorkValueAllocation)21930_storage" + "type": "t_struct(WorkValueAllocation)21916_storage" }, { "astId": 15257, @@ -49,7 +49,7 @@ "label": "_distributions", "offset": 0, "slot": "4", - "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21964_storage)" + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21950_storage)" }, { "astId": 15264, @@ -113,7 +113,7 @@ "label": "_e3PolicySnapshots", "offset": 0, "slot": "12", - "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21943_storage)" + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21929_storage)" }, { "astId": 15309, @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(IBondingRegistry)21073": { + "t_contract(IBondingRegistry)21068": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" @@ -205,7 +205,7 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IInterfold)23293": { + "t_contract(IInterfold)23274": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" @@ -280,19 +280,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21943_storage)": { + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21929_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", "numberOfBytes": "32", - "value": "t_struct(E3PolicySnapshot)21943_storage" + "value": "t_struct(E3PolicySnapshot)21929_storage" }, - "t_mapping(t_uint256,t_struct(RefundDistribution)21964_storage)": { + "t_mapping(t_uint256,t_struct(RefundDistribution)21950_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", "numberOfBytes": "32", - "value": "t_struct(RefundDistribution)21964_storage" + "value": "t_struct(RefundDistribution)21950_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -301,20 +301,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(E3PolicySnapshot)21943_storage": { + "t_struct(E3PolicySnapshot)21929_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.E3PolicySnapshot", "members": [ { - "astId": 21934, + "astId": 21920, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "allocation", "offset": 0, "slot": "0", - "type": "t_struct(WorkValueAllocation)21930_storage" + "type": "t_struct(WorkValueAllocation)21916_storage" }, { - "astId": 21936, + "astId": 21922, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -322,7 +322,7 @@ "type": "t_address" }, { - "astId": 21938, + "astId": 21924, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, @@ -330,7 +330,7 @@ "type": "t_address" }, { - "astId": 21940, + "astId": 21926, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "version", "offset": 20, @@ -338,7 +338,7 @@ "type": "t_uint64" }, { - "astId": 21942, + "astId": 21928, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "initialized", "offset": 28, @@ -348,12 +348,12 @@ ], "numberOfBytes": "96" }, - "t_struct(RefundDistribution)21964_storage": { + "t_struct(RefundDistribution)21950_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.RefundDistribution", "members": [ { - "astId": 21946, + "astId": 21932, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "requesterAmount", "offset": 0, @@ -361,7 +361,7 @@ "type": "t_uint256" }, { - "astId": 21948, + "astId": 21934, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeAmount", "offset": 0, @@ -369,7 +369,7 @@ "type": "t_uint256" }, { - "astId": 21950, + "astId": 21936, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolAmount", "offset": 0, @@ -377,7 +377,7 @@ "type": "t_uint256" }, { - "astId": 21952, + "astId": 21938, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "totalSlashed", "offset": 0, @@ -385,7 +385,7 @@ "type": "t_uint256" }, { - "astId": 21954, + "astId": 21940, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeCount", "offset": 0, @@ -393,7 +393,7 @@ "type": "t_uint256" }, { - "astId": 21956, + "astId": 21942, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "calculated", "offset": 0, @@ -401,7 +401,7 @@ "type": "t_bool" }, { - "astId": 21959, + "astId": 21945, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "feeToken", "offset": 1, @@ -409,7 +409,7 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 21961, + "astId": 21947, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "originalPayment", "offset": 0, @@ -417,7 +417,7 @@ "type": "t_uint256" }, { - "astId": 21963, + "astId": 21949, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "perNodeAmount", "offset": 0, @@ -427,12 +427,12 @@ ], "numberOfBytes": "256" }, - "t_struct(WorkValueAllocation)21930_storage": { + "t_struct(WorkValueAllocation)21916_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.WorkValueAllocation", "members": [ { - "astId": 21921, + "astId": 21907, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "committeeFormationBps", "offset": 0, @@ -440,7 +440,7 @@ "type": "t_uint16" }, { - "astId": 21923, + "astId": 21909, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "dkgBps", "offset": 2, @@ -448,7 +448,7 @@ "type": "t_uint16" }, { - "astId": 21925, + "astId": 21911, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "decryptionBps", "offset": 4, @@ -456,7 +456,7 @@ "type": "t_uint16" }, { - "astId": 21927, + "astId": 21913, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolBps", "offset": 6, @@ -464,7 +464,7 @@ "type": "t_uint16" }, { - "astId": 21929, + "astId": 21915, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "successSlashedNodeBps", "offset": 8, diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json index 06b0f6b95..17dc83d4f 100644 --- a/packages/interfold-contracts/audits/storage-layouts/Interfold.json +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -3,12 +3,12 @@ "contract": "Interfold", "source": "contracts/Interfold.sol", "baseline": { - "buildInfoId": "solc-0_8_28-846b01f057eb6489ff758d75eecbd293c04a77af", + "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "53eb54e092b64f37f1a8efc0053f5521dd1d2131", - "sourceSha256": "aaddcad10578e4520e1588c68fd25f7cf437fbbb2d8845e50bc11759f6954bab" + "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", + "sourceSha256": "56dfa8a201f3b2cb0381cd92bce0f2011a6b028ec0c280c196ed39fb4daef858" }, "storage": [ { @@ -17,7 +17,7 @@ "label": "ciphernodeRegistry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21722" + "type": "t_contract(ICiphernodeRegistry)21717" }, { "astId": 17672, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21073" + "type": "t_contract(IBondingRegistry)21068" }, { "astId": 17676, @@ -33,7 +33,7 @@ "label": "e3RefundManager", "offset": 0, "slot": "2", - "type": "t_contract(IE3RefundManager)22344" + "type": "t_contract(IE3RefundManager)22330" }, { "astId": 17680, @@ -41,7 +41,7 @@ "label": "slashingManager", "offset": 0, "slot": "3", - "type": "t_contract(ISlashingManager)23940" + "type": "t_contract(ISlashingManager)23921" }, { "astId": 17684, @@ -73,7 +73,7 @@ "label": "e3Programs", "offset": 0, "slot": "7", - "type": "t_mapping(t_contract(IE3Program)21911,t_bool)" + "type": "t_mapping(t_contract(IE3Program)21897,t_bool)" }, { "astId": 17702, @@ -81,7 +81,7 @@ "label": "e3s", "offset": 0, "slot": "8", - "type": "t_mapping(t_uint256,t_struct(E3)21871_storage)" + "type": "t_mapping(t_uint256,t_struct(E3)21857_storage)" }, { "astId": 17708, @@ -89,7 +89,7 @@ "label": "decryptionVerifiers", "offset": 0, "slot": "9", - "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21798)" + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21784)" }, { "astId": 17714, @@ -97,7 +97,7 @@ "label": "pkVerifiers", "offset": 0, "slot": "10", - "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23331)" + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23312)" }, { "astId": 17719, @@ -121,7 +121,7 @@ "label": "_e3Stages", "offset": 0, "slot": "13", - "type": "t_mapping(t_uint256,t_enum(E3Stage)22373)" + "type": "t_mapping(t_uint256,t_enum(E3Stage)22359)" }, { "astId": 17736, @@ -129,7 +129,7 @@ "label": "_e3Deadlines", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22405_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22391_storage)" }, { "astId": 17742, @@ -137,7 +137,7 @@ "label": "_e3FailureReasons", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_enum(FailureReason)22389)" + "type": "t_mapping(t_uint256,t_enum(FailureReason)22375)" }, { "astId": 17747, @@ -161,7 +161,7 @@ "label": "committeeThresholds", "offset": 0, "slot": "18", - "type": "t_mapping(t_enum(CommitteeSize)22364,t_array(t_uint32)2_storage)" + "type": "t_mapping(t_enum(CommitteeSize)22350,t_array(t_uint32)2_storage)" }, { "astId": 17766, @@ -185,7 +185,7 @@ "label": "_timeoutConfig", "offset": 0, "slot": "21", - "type": "t_struct(E3TimeoutConfig)22397_storage" + "type": "t_struct(E3TimeoutConfig)22383_storage" }, { "astId": 17779, @@ -193,7 +193,7 @@ "label": "_pricingConfig", "offset": 0, "slot": "24", - "type": "t_struct(PricingConfig)22437_storage" + "type": "t_struct(PricingConfig)22423_storage" }, { "astId": 17789, @@ -238,18 +238,10 @@ { "astId": 20545, "contract": "project/contracts/Interfold.sol:Interfold", - "label": "_consumedDecryptionProofs", - "offset": 0, - "slot": "38", - "type": "t_mapping(t_bytes32,t_bool)" - }, - { - "astId": 20550, - "contract": "project/contracts/Interfold.sol:Interfold", "label": "__gap", "offset": 0, - "slot": "39", - "type": "t_array(t_uint256)50_storage" + "slot": "38", + "type": "t_array(t_uint256)49_storage" } ], "types": { @@ -264,11 +256,11 @@ "label": "uint256[2]", "numberOfBytes": "64" }, - "t_array(t_uint256)50_storage": { + "t_array(t_uint256)49_storage": { "base": "t_uint256", "encoding": "inplace", - "label": "uint256[50]", - "numberOfBytes": "1600" + "label": "uint256[49]", + "numberOfBytes": "1568" }, "t_array(t_uint32)2_storage": { "base": "t_uint32", @@ -291,27 +283,27 @@ "label": "bytes", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21073": { + "t_contract(IBondingRegistry)21068": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(ICiphernodeRegistry)21722": { + "t_contract(ICiphernodeRegistry)21717": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IDecryptionVerifier)21798": { + "t_contract(IDecryptionVerifier)21784": { "encoding": "inplace", "label": "contract IDecryptionVerifier", "numberOfBytes": "20" }, - "t_contract(IE3Program)21911": { + "t_contract(IE3Program)21897": { "encoding": "inplace", "label": "contract IE3Program", "numberOfBytes": "20" }, - "t_contract(IE3RefundManager)22344": { + "t_contract(IE3RefundManager)22330": { "encoding": "inplace", "label": "contract IE3RefundManager", "numberOfBytes": "20" @@ -321,27 +313,27 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IPkVerifier)23331": { + "t_contract(IPkVerifier)23312": { "encoding": "inplace", "label": "contract IPkVerifier", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23940": { + "t_contract(ISlashingManager)23921": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeSize)22364": { + "t_enum(CommitteeSize)22350": { "encoding": "inplace", "label": "enum IInterfold.CommitteeSize", "numberOfBytes": "1" }, - "t_enum(E3Stage)22373": { + "t_enum(E3Stage)22359": { "encoding": "inplace", "label": "enum IInterfold.E3Stage", "numberOfBytes": "1" }, - "t_enum(FailureReason)22389": { + "t_enum(FailureReason)22375": { "encoding": "inplace", "label": "enum IInterfold.FailureReason", "numberOfBytes": "1" @@ -360,30 +352,23 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_bytes32,t_bool)": { - "encoding": "mapping", - "key": "t_bytes32", - "label": "mapping(bytes32 => bool)", - "numberOfBytes": "32", - "value": "t_bool" - }, - "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21798)": { + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21784)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IDecryptionVerifier)", "numberOfBytes": "32", - "value": "t_contract(IDecryptionVerifier)21798" + "value": "t_contract(IDecryptionVerifier)21784" }, - "t_mapping(t_bytes32,t_contract(IPkVerifier)23331)": { + "t_mapping(t_bytes32,t_contract(IPkVerifier)23312)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IPkVerifier)", "numberOfBytes": "32", - "value": "t_contract(IPkVerifier)23331" + "value": "t_contract(IPkVerifier)23312" }, - "t_mapping(t_contract(IE3Program)21911,t_bool)": { + "t_mapping(t_contract(IE3Program)21897,t_bool)": { "encoding": "mapping", - "key": "t_contract(IE3Program)21911", + "key": "t_contract(IE3Program)21897", "label": "mapping(contract IE3Program => bool)", "numberOfBytes": "32", "value": "t_bool" @@ -402,9 +387,9 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_enum(CommitteeSize)22364,t_array(t_uint32)2_storage)": { + "t_mapping(t_enum(CommitteeSize)22350,t_array(t_uint32)2_storage)": { "encoding": "mapping", - "key": "t_enum(CommitteeSize)22364", + "key": "t_enum(CommitteeSize)22350", "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", "numberOfBytes": "32", "value": "t_array(t_uint32)2_storage" @@ -423,19 +408,19 @@ "numberOfBytes": "32", "value": "t_contract(IERC20)4293" }, - "t_mapping(t_uint256,t_enum(E3Stage)22373)": { + "t_mapping(t_uint256,t_enum(E3Stage)22359)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.E3Stage)", "numberOfBytes": "32", - "value": "t_enum(E3Stage)22373" + "value": "t_enum(E3Stage)22359" }, - "t_mapping(t_uint256,t_enum(FailureReason)22389)": { + "t_mapping(t_uint256,t_enum(FailureReason)22375)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.FailureReason)", "numberOfBytes": "32", - "value": "t_enum(FailureReason)22389" + "value": "t_enum(FailureReason)22375" }, "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { "encoding": "mapping", @@ -444,19 +429,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3)21871_storage)": { + "t_mapping(t_uint256,t_struct(E3)21857_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct E3)", "numberOfBytes": "32", - "value": "t_struct(E3)21871_storage" + "value": "t_struct(E3)21857_storage" }, - "t_mapping(t_uint256,t_struct(E3Deadlines)22405_storage)": { + "t_mapping(t_uint256,t_struct(E3Deadlines)22391_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", "numberOfBytes": "32", - "value": "t_struct(E3Deadlines)22405_storage" + "value": "t_struct(E3Deadlines)22391_storage" }, "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)": { "encoding": "mapping", @@ -486,12 +471,12 @@ "numberOfBytes": "32", "value": "t_bytes_storage" }, - "t_struct(E3)21871_storage": { + "t_struct(E3)21857_storage": { "encoding": "inplace", "label": "struct E3", "members": [ { - "astId": 21838, + "astId": 21824, "contract": "project/contracts/Interfold.sol:Interfold", "label": "seed", "offset": 0, @@ -499,15 +484,15 @@ "type": "t_uint256" }, { - "astId": 21841, + "astId": 21827, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeSize", "offset": 0, "slot": "1", - "type": "t_enum(CommitteeSize)22364" + "type": "t_enum(CommitteeSize)22350" }, { - "astId": 21843, + "astId": 21829, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requestBlock", "offset": 0, @@ -515,7 +500,7 @@ "type": "t_uint256" }, { - "astId": 21847, + "astId": 21833, "contract": "project/contracts/Interfold.sol:Interfold", "label": "inputWindow", "offset": 0, @@ -523,7 +508,7 @@ "type": "t_array(t_uint256)2_storage" }, { - "astId": 21849, + "astId": 21835, "contract": "project/contracts/Interfold.sol:Interfold", "label": "encryptionSchemeId", "offset": 0, @@ -531,15 +516,15 @@ "type": "t_bytes32" }, { - "astId": 21852, + "astId": 21838, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Program", "offset": 0, "slot": "6", - "type": "t_contract(IE3Program)21911" + "type": "t_contract(IE3Program)21897" }, { - "astId": 21854, + "astId": 21840, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSet", "offset": 20, @@ -547,7 +532,7 @@ "type": "t_uint8" }, { - "astId": 21856, + "astId": 21842, "contract": "project/contracts/Interfold.sol:Interfold", "label": "customParams", "offset": 0, @@ -555,23 +540,23 @@ "type": "t_bytes_storage" }, { - "astId": 21859, + "astId": 21845, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifier", "offset": 0, "slot": "8", - "type": "t_contract(IDecryptionVerifier)21798" + "type": "t_contract(IDecryptionVerifier)21784" }, { - "astId": 21862, + "astId": 21848, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifier", "offset": 0, "slot": "9", - "type": "t_contract(IPkVerifier)23331" + "type": "t_contract(IPkVerifier)23312" }, { - "astId": 21864, + "astId": 21850, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeePublicKey", "offset": 0, @@ -579,7 +564,7 @@ "type": "t_bytes32" }, { - "astId": 21866, + "astId": 21852, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphertextOutput", "offset": 0, @@ -587,7 +572,7 @@ "type": "t_bytes32" }, { - "astId": 21868, + "astId": 21854, "contract": "project/contracts/Interfold.sol:Interfold", "label": "plaintextOutput", "offset": 0, @@ -595,7 +580,7 @@ "type": "t_bytes_storage" }, { - "astId": 21870, + "astId": 21856, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requester", "offset": 0, @@ -605,12 +590,12 @@ ], "numberOfBytes": "448" }, - "t_struct(E3Deadlines)22405_storage": { + "t_struct(E3Deadlines)22391_storage": { "encoding": "inplace", "label": "struct IInterfold.E3Deadlines", "members": [ { - "astId": 22400, + "astId": 22386, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgDeadline", "offset": 0, @@ -618,7 +603,7 @@ "type": "t_uint256" }, { - "astId": 22402, + "astId": 22388, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeDeadline", "offset": 0, @@ -626,7 +611,7 @@ "type": "t_uint256" }, { - "astId": 22404, + "astId": 22390, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionDeadline", "offset": 0, @@ -646,7 +631,7 @@ "label": "registry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21722" + "type": "t_contract(ICiphernodeRegistry)21717" }, { "astId": 17810, @@ -654,7 +639,7 @@ "label": "refundManager", "offset": 0, "slot": "1", - "type": "t_contract(IE3RefundManager)22344" + "type": "t_contract(IE3RefundManager)22330" }, { "astId": 17813, @@ -662,17 +647,17 @@ "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23940" + "type": "t_contract(ISlashingManager)23921" } ], "numberOfBytes": "96" }, - "t_struct(E3TimeoutConfig)22397_storage": { + "t_struct(E3TimeoutConfig)22383_storage": { "encoding": "inplace", "label": "struct IInterfold.E3TimeoutConfig", "members": [ { - "astId": 22392, + "astId": 22378, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgWindow", "offset": 0, @@ -680,7 +665,7 @@ "type": "t_uint256" }, { - "astId": 22394, + "astId": 22380, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeWindow", "offset": 0, @@ -688,7 +673,7 @@ "type": "t_uint256" }, { - "astId": 22396, + "astId": 22382, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionWindow", "offset": 0, @@ -698,12 +683,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PricingConfig)22437_storage": { + "t_struct(PricingConfig)22423_storage": { "encoding": "inplace", "label": "struct IInterfold.PricingConfig", "members": [ { - "astId": 22408, + "astId": 22394, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenFixedPerNode", "offset": 0, @@ -711,7 +696,7 @@ "type": "t_uint256" }, { - "astId": 22410, + "astId": 22396, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenPerEncryptionProof", "offset": 0, @@ -719,7 +704,7 @@ "type": "t_uint256" }, { - "astId": 22412, + "astId": 22398, "contract": "project/contracts/Interfold.sol:Interfold", "label": "coordinationPerPair", "offset": 0, @@ -727,7 +712,7 @@ "type": "t_uint256" }, { - "astId": 22414, + "astId": 22400, "contract": "project/contracts/Interfold.sol:Interfold", "label": "availabilityPerNodePerSec", "offset": 0, @@ -735,7 +720,7 @@ "type": "t_uint256" }, { - "astId": 22416, + "astId": 22402, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionPerNode", "offset": 0, @@ -743,7 +728,7 @@ "type": "t_uint256" }, { - "astId": 22418, + "astId": 22404, "contract": "project/contracts/Interfold.sol:Interfold", "label": "publicationBase", "offset": 0, @@ -751,7 +736,7 @@ "type": "t_uint256" }, { - "astId": 22420, + "astId": 22406, "contract": "project/contracts/Interfold.sol:Interfold", "label": "verificationPerProof", "offset": 0, @@ -759,7 +744,7 @@ "type": "t_uint256" }, { - "astId": 22422, + "astId": 22408, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolTreasury", "offset": 0, @@ -767,7 +752,7 @@ "type": "t_address" }, { - "astId": 22424, + "astId": 22410, "contract": "project/contracts/Interfold.sol:Interfold", "label": "marginBps", "offset": 20, @@ -775,7 +760,7 @@ "type": "t_uint16" }, { - "astId": 22426, + "astId": 22412, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolShareBps", "offset": 22, @@ -783,7 +768,7 @@ "type": "t_uint16" }, { - "astId": 22428, + "astId": 22414, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgUtilizationBps", "offset": 24, @@ -791,7 +776,7 @@ "type": "t_uint16" }, { - "astId": 22430, + "astId": 22416, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeUtilizationBps", "offset": 26, @@ -799,7 +784,7 @@ "type": "t_uint16" }, { - "astId": 22432, + "astId": 22418, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptUtilizationBps", "offset": 28, @@ -807,7 +792,7 @@ "type": "t_uint16" }, { - "astId": 22434, + "astId": 22420, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minCommitteeSize", "offset": 0, @@ -815,7 +800,7 @@ "type": "t_uint32" }, { - "astId": 22436, + "astId": 22422, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minThreshold", "offset": 4, From 8ca2d921486fe9587df7dab8f8f139b01d5a7246 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 14:29:51 +0500 Subject: [PATCH 22/52] refactor(protocol): remove request fee consent fields [H-02] --- agent/flow-trace/00_INDEX.md | 2 +- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 9 +--- crates/evm-helpers/src/contracts.rs | 11 ---- .../interfaces/IInterfold.sol/IInterfold.json | 54 +------------------ .../contracts/Interfold.sol | 5 +- .../contracts/interfaces/IInterfold.sol | 10 ---- .../contracts/lib/InterfoldPricing.sol | 14 +---- .../interfold-contracts/tasks/interfold.ts | 7 +-- .../test/E3Lifecycle/E3Integration.spec.ts | 18 ++----- .../test/E3Lifecycle/Sortition.spec.ts | 7 +-- .../test/Interfold.spec.ts | 28 ---------- .../test/Pricing/DustRotation.spec.ts | 9 +--- .../test/Pricing/Pricing.spec.ts | 26 +++------ .../Pricing/PullPaymentsAndAllowlist.spec.ts | 26 +++------ .../CiphernodeRegistryOwnable.spec.ts | 4 +- .../test/Slashing/CommitteeExpulsion.spec.ts | 6 +-- .../test/fixtures/helpers.ts | 23 ++------ .../test/fixtures/system.ts | 2 - .../src/contracts/contract-client.ts | 6 --- packages/interfold-sdk/src/contracts/types.ts | 4 -- packages/interfold-sdk/src/interfold-sdk.ts | 2 - 21 files changed, 32 insertions(+), 241 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index a873fb31c..709c288e2 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -211,7 +211,7 @@ _Found during source-code cross-referencing of these trace documents._ | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy, and the minimum ticket requirement can never be zero. | -| 25 | **Requester fee consent (AUD H-02)** | Resolved | Every E3 request now includes a maximum acceptable live fee and an execution deadline. Validation rejects repriced or stale requests before transferring fee tokens or creating E3 state. | +| 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | | 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 79add82ba..1ec62c21b 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -30,9 +30,7 @@ Requester calls: Interfold.request({ e3Program:
, // computation program contract e3ProgramParams: , // ABI-encoded program parameters computeProviderParams: , - customParams: , - maxFee: , // maximum live quote the requester accepts - requestDeadline: // last timestamp at which the request may execute + customParams: }) │ ├─ VALIDATION: @@ -41,15 +39,12 @@ Requester calls: Interfold.request({ │ ├─ inputWindow[0] >= block.timestamp (start in future) │ ├─ inputWindow[1] >= inputWindow[0] (end after start) │ ├─ total duration < maxDuration -│ ├─ e3Programs[e3Program] == true (program whitelisted) -│ ├─ block.timestamp <= requestDeadline -│ └─ live fee quote <= maxFee +│ └─ e3Programs[e3Program] == true (program whitelisted) │ ├─ FEE CALCULATION: │ ├─ fee = getE3Quote() │ │ → InterfoldPricing quote from committee threshold, time windows, │ │ proof counts, availability, decryption/publication costs, and margin -│ ├─ InterfoldPricing enforces the requester's maxFee and deadline │ ├─ feeToken.transferFrom(requester, address(this), fee) │ └─ e3Payments[e3Id] = fee (stored per-E3) │ _e3FeeTokens[e3Id] = feeToken (survives global token rotation) diff --git a/crates/evm-helpers/src/contracts.rs b/crates/evm-helpers/src/contracts.rs index 71485a17a..4a24987be 100644 --- a/crates/evm-helpers/src/contracts.rs +++ b/crates/evm-helpers/src/contracts.rs @@ -72,8 +72,6 @@ sol! { uint8 paramSet; bytes computeProviderParams; bytes customParams; - uint256 maxFee; - uint256 requestDeadline; } #[derive(Debug, PartialEq)] @@ -383,8 +381,6 @@ where paramSet: param_set, computeProviderParams: compute_provider_params, customParams: Bytes::new(), - maxFee: U256::MAX, - requestDeadline: input_window[0], }; let contract = Interfold::new(self.contract_address, &self.provider); @@ -457,15 +453,8 @@ impl InterfoldWrite for InterfoldContract { paramSet: param_set, computeProviderParams: compute_provider_params.clone(), customParams: custom_params.clone(), - maxFee: U256::MAX, - requestDeadline: input_window[0], }; - let max_fee = contract.getE3Quote(e3_request.clone()).call().await?; - let e3_request = E3RequestParams { - maxFee: max_fee, - ..e3_request - }; let builder = contract.request(e3_request).nonce(nonce); let receipt = builder.send().await?.get_receipt().await?; e3_utils::require_successful_receipt("request E3", &receipt)?; diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 290964674..4c93dc703 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -177,22 +177,6 @@ "name": "FailureConditionNotMet", "type": "error" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "quotedFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxFee", - "type": "uint256" - } - ], - "name": "FeeExceedsMax", - "type": "error" - }, { "inputs": [ { @@ -440,22 +424,6 @@ "name": "ProofRequired", "type": "error" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "requestDeadline", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "currentTimestamp", - "type": "uint256" - } - ], - "name": "RequestExpired", - "type": "error" - }, { "inputs": [ { @@ -1559,16 +1527,6 @@ "internalType": "bytes", "name": "customParams", "type": "bytes" - }, - { - "internalType": "uint256", - "name": "maxFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "requestDeadline", - "type": "uint256" } ], "internalType": "struct IInterfold.E3RequestParams", @@ -2035,16 +1993,6 @@ "internalType": "bytes", "name": "customParams", "type": "bytes" - }, - { - "internalType": "uint256", - "name": "maxFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "requestDeadline", - "type": "uint256" } ], "internalType": "struct IInterfold.E3RequestParams", @@ -2428,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" + "buildInfoId": "solc-0_8_28-04326bbd3714d0d7d56214e0d6ea5dc646f6c4ab" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 47ec7c8ca..97db76eec 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -270,10 +270,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { block.timestamp, _timeoutConfig.computeWindow, _timeoutConfig.decryptionWindow, - maxDuration, - e3Fee, - requestParams.maxFee, - requestParams.requestDeadline + maxDuration ); e3Id = nexte3Id; diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index e2bddf717..c3b1be9c6 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -477,12 +477,6 @@ interface IInterfold { /// @param token The disallowed token. error FeeTokenNotAllowed(IERC20 token); - /// @notice The live quote exceeds the maximum fee authorized by the requester. - error FeeExceedsMax(uint256 quotedFee, uint256 maxFee); - - /// @notice The bounded request was mined after its requester-authorized deadline. - error RequestExpired(uint256 requestDeadline, uint256 currentTimestamp); - /// @notice Caller has no balance to claim for the given E3 / treasury / token. error NothingToClaim(); @@ -506,10 +500,6 @@ interface IInterfold { uint8 paramSet; bytes computeProviderParams; bytes customParams; - /// @notice Maximum fee-token amount authorized for this request. - uint256 maxFee; - /// @notice Last timestamp at which this request may execute. - uint256 requestDeadline; } //////////////////////////////////////////////////////////// diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 5615e17c4..117e343a6 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -304,7 +304,7 @@ library InterfoldPricing { revert IInterfold.BelowMinThreshold(threshold[0], minThreshold); } - /// @notice Mirrors the request-consent, input-window, and duration gates + /// @notice Mirrors the input-window and duration gates /// of {Interfold.request}. Reverts with the same selectors so off- /// chain `revertedWithCustomError(interfold, ...)` lookups keep /// working. @@ -313,23 +313,13 @@ library InterfoldPricing { /// @param computeWindow `_timeoutConfig.computeWindow`. /// @param decryptionWindow `_timeoutConfig.decryptionWindow`. /// @param maxDuration The Interfold-wide upper bound. - /// @param quotedFee Fee returned by {InterfoldPricing.quote}. - /// @param maxFee Maximum fee authorized by the requester. - /// @param requestDeadline Last timestamp at which this quote is valid. function validateRequest( uint256[2] calldata inputWindow, uint256 nowTs, uint256 computeWindow, uint256 decryptionWindow, - uint256 maxDuration, - uint256 quotedFee, - uint256 maxFee, - uint256 requestDeadline + uint256 maxDuration ) external pure { - if (nowTs > requestDeadline) - revert IInterfold.RequestExpired(requestDeadline, nowTs); - if (quotedFee > maxFee) - revert IInterfold.FeeExceedsMax(quotedFee, maxFee); if (inputWindow[0] < nowTs) revert IInterfold.InvalidInputDeadlineStart(inputWindow[0]); if (inputWindow[1] < inputWindow[0]) diff --git a/packages/interfold-contracts/tasks/interfold.ts b/packages/interfold-contracts/tasks/interfold.ts index 1388b3adf..4708b8f48 100644 --- a/packages/interfold-contracts/tasks/interfold.ts +++ b/packages/interfold-contracts/tasks/interfold.ts @@ -231,8 +231,6 @@ export const requestCommittee = task( paramSet, computeProviderParams, customParams, - maxFee: ethers.MaxUint256, - requestDeadline: inputWindowStart, }; console.log("Request parameters:", requestParams); @@ -259,10 +257,7 @@ export const requestCommittee = task( await approveTx.wait(); console.log("USDC approved"); - const tx = await interfoldContract.request({ - ...requestParams, - maxFee: fee, - }); + const tx = await interfoldContract.request(requestParams); console.log("Requesting committee... ", tx.hash); await tx.wait(); diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 3d30eb8cb..6bc91348f 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -138,15 +138,11 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: startTime + 100, }; const fee = await interfold.getE3Quote(requestParams); await requestToken.connect(signer).approve(interfoldAddress, fee); - await interfold - .connect(signer) - .request({ ...requestParams, maxFee: fee }); + await interfold.connect(signer).request(requestParams); return { e3Id: 0 }; }; @@ -1621,14 +1617,10 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: startTime, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold - .connect(requester) - .request({ ...requestParams, maxFee: fee }); + await interfold.connect(requester).request(requestParams); return n; }; @@ -1707,14 +1699,10 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: startTime, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold - .connect(requester) - .request({ ...requestParams, maxFee: fee }); + await interfold.connect(requester).request(requestParams); } // Fail both diff --git a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts index 81aba0483..04c6c115e 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts @@ -114,13 +114,8 @@ async function deployStack() { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 10, } as any; - return interfold.connect(requester).request({ - ...req, - maxFee: await interfold.getE3Quote(req), - }); + return interfold.connect(requester).request(req); }; return { diff --git a/packages/interfold-contracts/test/Interfold.spec.ts b/packages/interfold-contracts/test/Interfold.spec.ts index de7c45c4a..b2e1205ca 100644 --- a/packages/interfold-contracts/test/Interfold.spec.ts +++ b/packages/interfold-contracts/test/Interfold.spec.ts @@ -375,35 +375,9 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - maxFee: ethers.MaxUint256, - requestDeadline: request.inputWindow[0], }), ).to.be.revertedWithCustomError(usdcToken, "ERC20InsufficientAllowance"); }); - it("AUD-H02: reverts when the live quote exceeds maxFee", async function () { - const { interfold, request, usdcToken } = await loadFixture(setup); - const fee = await interfold.getE3Quote(request); - await usdcToken.approve(await interfold.getAddress(), fee); - - await expect(interfold.request({ ...request, maxFee: fee - 1n })) - .to.be.revertedWithCustomError(interfold, "FeeExceedsMax") - .withArgs(fee, fee - 1n); - }); - it("AUD-H02: reverts after the requester-authorized deadline", async function () { - const { interfold, request } = await loadFixture(setup); - const deadline = BigInt(request.inputWindow[0].toString()); - await time.increaseTo(deadline + 1n); - - await expect( - interfold.request.staticCall({ - ...request, - maxFee: ethers.MaxUint256, - requestDeadline: deadline, - }), - ) - .to.be.revertedWithCustomError(interfold, "RequestExpired") - .withArgs(deadline, deadline + 1n); - }); it("reverts if committee size is not configured", async function () { const { interfold, request } = await loadFixture(setup); const configuredSizes = COMMITTEE_THRESHOLDS_DEFAULT.map( @@ -418,8 +392,6 @@ describe("Interfold", function () { paramSet: request.paramSet, computeProviderParams: request.computeProviderParams, customParams: request.customParams, - maxFee: ethers.MaxUint256, - requestDeadline: request.inputWindow[0], }; // `CommitteeSizeNotConfigured(3)` reverts correctly on-chain; ethers cannot // decode the custom error when the enum arg is not a named variant (0..2). diff --git a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts index 6715f27c6..5febe1a7e 100644 --- a/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts +++ b/packages/interfold-contracts/test/Pricing/DustRotation.spec.ts @@ -112,8 +112,6 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now0 + 10, } as any; }; @@ -132,14 +130,9 @@ describe("Pricing — per-E3 dust rotation across consecutive E3s", function () ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 10, }; await feeToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request({ - ...req, - maxFee: await interfold.getE3Quote(req), - }); + await interfold.request(req); // topNodes are sorted by ascending address; operator3 < operator1 < operator2 const nodes = [ await operator3.getAddress(), diff --git a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts index f3ce47d7c..86263fc46 100644 --- a/packages/interfold-contracts/test/Pricing/Pricing.spec.ts +++ b/packages/interfold-contracts/test/Pricing/Pricing.spec.ts @@ -358,16 +358,11 @@ describe("E3 Pricing", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 100, }; // Make request with large approval to avoid fee mismatch await usdcToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request({ - ...freshRequest, - maxFee: await interfold.getE3Quote(freshRequest), - }); + await interfold.request(freshRequest); const e3Id = 0; const fee = await interfold.e3Payments(e3Id); @@ -449,16 +444,11 @@ describe("E3 Pricing", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 100, }; // Make request with large approval await usdcToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request({ - ...freshRequest, - maxFee: await interfold.getE3Quote(freshRequest), - }); + await interfold.request(freshRequest); const e3Id = 0; const fee = await interfold.e3Payments(e3Id); @@ -548,7 +538,7 @@ describe("E3 Pricing", function () { const balanceBefore = await usdcToken.balanceOf(ownerAddr); await usdcToken.approve(await interfold.getAddress(), fee); - await interfold.request({ ...request, maxFee: fee }); + await interfold.request(request); const balanceAfter = await usdcToken.balanceOf(ownerAddr); expect(balanceBefore - balanceAfter).to.equal(fee); @@ -560,12 +550,10 @@ describe("E3 Pricing", function () { // Approve only 1 unit await usdcToken.approve(await interfold.getAddress(), 1); - await expect( - interfold.request({ - ...request, - maxFee: await interfold.getE3Quote(request), - }), - ).to.be.revertedWithCustomError(usdcToken, "ERC20InsufficientAllowance"); + await expect(interfold.request(request)).to.be.revertedWithCustomError( + usdcToken, + "ERC20InsufficientAllowance", + ); }); }); diff --git a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts index 43d980905..372103e25 100644 --- a/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts +++ b/packages/interfold-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts @@ -101,8 +101,6 @@ describe("Interfold — pull payments + fee-token allow-list", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 10, }; return { @@ -135,10 +133,7 @@ describe("Interfold — pull payments + fee-token allow-list", function () { request, } = ctx; await feeToken.approve(await interfold.getAddress(), ethers.MaxUint256); - await interfold.request({ - ...request, - maxFee: await interfold.getE3Quote(request), - }); + await interfold.request(request); const e3Id = 0; const nodes = [ await operator1.getAddress(), @@ -190,12 +185,8 @@ describe("Interfold — pull payments + fee-token allow-list", function () { const req2 = { ...request, inputWindow: [now + 10, now + inputWindowDuration] as [number, number], - requestDeadline: now + 10, }; - await interfold.request({ - ...req2, - maxFee: await interfold.getE3Quote(req2), - }); + await interfold.request(req2); const e3Id2 = 1; await setupAndPublishCommittee( ctx.ciphernodeRegistryContract, @@ -289,18 +280,15 @@ describe("Interfold — pull payments + fee-token allow-list", function () { const fresh = { ...request, inputWindow: [now + 10, now + inputWindowDuration] as [number, number], - requestDeadline: now + 10, }; - await expect( - interfold.request({ ...fresh, maxFee: ethers.MaxUint256 }), - ).to.be.revertedWithCustomError(interfold, "FeeTokenNotAllowed"); + await expect(interfold.request(fresh)).to.be.revertedWithCustomError( + interfold, + "FeeTokenNotAllowed", + ); // Re-allow restores request(). await interfold.setFeeTokenAllowed(await feeToken.getAddress(), true); - await interfold.request({ - ...fresh, - maxFee: await interfold.getE3Quote(fresh), - }); // should not revert + await interfold.request(fresh); // should not revert }); }); }); diff --git a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index acf2e6283..726e68f12 100644 --- a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -78,8 +78,6 @@ describe("CiphernodeRegistryOwnable", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: currentTime + 100, }; const fee = await interfold.getE3Quote(requestParams); @@ -87,7 +85,7 @@ describe("CiphernodeRegistryOwnable", function () { const interfoldContract = signer ? interfold.connect(signer) : interfold; await tokenContract.approve(await interfold.getAddress(), fee); - return interfoldContract.request({ ...requestParams, maxFee: fee }); + return interfoldContract.request(requestParams); } describe("constructor / initialize()", function () { diff --git a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts index da3dadbcc..82922513a 100644 --- a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -145,15 +145,11 @@ describe("Committee Expulsion & Fault Tolerance", function () { ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: startTime + 100, }; const fee = await interfold.getE3Quote(requestParams); await usdcToken.connect(requester).approve(interfoldAddress, fee); - await interfold - .connect(requester) - .request({ ...requestParams, maxFee: fee }); + await interfold.connect(requester).request(requestParams); } async function finalizeCommitteeWithOperators( diff --git a/packages/interfold-contracts/test/fixtures/helpers.ts b/packages/interfold-contracts/test/fixtures/helpers.ts index 88c16f3b0..3c9aaab2f 100644 --- a/packages/interfold-contracts/test/fixtures/helpers.ts +++ b/packages/interfold-contracts/test/fixtures/helpers.ts @@ -91,30 +91,15 @@ export const setupAndPublishCommittee = async ( export const makeRequest = async ( interfold: Interfold, usdcToken: MockUSDC, - requestParams: Omit< - IInterfold.E3RequestParamsStruct, - "maxFee" | "requestDeadline" - > & - Partial< - Pick - >, + requestParams: IInterfold.E3RequestParamsStruct, signer?: Signer, ): Promise => { - const normalizedParams: IInterfold.E3RequestParamsStruct = { - ...requestParams, - maxFee: requestParams.maxFee ?? ethers.MaxUint256, - requestDeadline: - requestParams.requestDeadline ?? requestParams.inputWindow[0], - }; - const fee = await interfold.getE3Quote(normalizedParams); + const fee = await interfold.getE3Quote(requestParams); const tokenContract = signer ? usdcToken.connect(signer) : usdcToken; const interfoldContract = signer ? interfold.connect(signer) : interfold; await tokenContract.approve(await interfold.getAddress(), fee); - return interfoldContract.request({ - ...normalizedParams, - maxFee: fee, - }); + return interfoldContract.request(requestParams); }; /** Options for {@link buildRequestParams}. */ @@ -164,7 +149,5 @@ export const buildRequestParams = async ( ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + startOffset, }; }; diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index 7c4ca0e99..584b47ecd 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -564,8 +564,6 @@ export async function deployInterfoldSystem( ["address"], ["0x1234567890123456789012345678901234567890"], ), - maxFee: ethers.MaxUint256, - requestDeadline: now + 10, }; return { diff --git a/packages/interfold-sdk/src/contracts/contract-client.ts b/packages/interfold-sdk/src/contracts/contract-client.ts index bf3214c77..62737d4f1 100644 --- a/packages/interfold-sdk/src/contracts/contract-client.ts +++ b/packages/interfold-sdk/src/contracts/contract-client.ts @@ -145,8 +145,6 @@ export class ContractClient { } const committeeSize = validateCommitteeSize(params.committeeSize) - const maxFee = params.maxFee ?? (await this.getE3Quote(params)) - const requestDeadline = params.requestDeadline ?? params.inputWindow[0] const { request } = await this.publicClient.simulateContract({ address: this.contracts.interfold, @@ -160,8 +158,6 @@ export class ContractClient { paramSet: params.paramSet, computeProviderParams: params.computeProviderParams, customParams: params.customParams || '0x', - maxFee, - requestDeadline, }, ], account, @@ -236,8 +232,6 @@ export class ContractClient { paramSet: requestParams.paramSet, computeProviderParams: requestParams.computeProviderParams, customParams: requestParams.customParams || '0x', - maxFee: requestParams.maxFee ?? 0n, - requestDeadline: requestParams.requestDeadline ?? requestParams.inputWindow[0], }, ], }) diff --git a/packages/interfold-sdk/src/contracts/types.ts b/packages/interfold-sdk/src/contracts/types.ts index bfe215138..36162b15e 100644 --- a/packages/interfold-sdk/src/contracts/types.ts +++ b/packages/interfold-sdk/src/contracts/types.ts @@ -60,10 +60,6 @@ export interface E3RequestParams extends RequestParams { paramSet: number computeProviderParams: `0x${string}` customParams?: `0x${string}` - /** Maximum fee-token amount authorized for this request. Defaults to a fresh quote. */ - maxFee?: bigint - /** Last timestamp at which the request may execute. Defaults to inputWindow[0]. */ - requestDeadline?: bigint } export enum E3Stage { diff --git a/packages/interfold-sdk/src/interfold-sdk.ts b/packages/interfold-sdk/src/interfold-sdk.ts index 2bc190ac1..2a176d843 100644 --- a/packages/interfold-sdk/src/interfold-sdk.ts +++ b/packages/interfold-sdk/src/interfold-sdk.ts @@ -140,8 +140,6 @@ export class InterfoldSDK { paramSet: number computeProviderParams: `0x${string}` customParams?: `0x${string}` - maxFee?: bigint - requestDeadline?: bigint gasLimit?: bigint }): Promise { return this.contractClient.requestE3(params) From 66735a2f375fc2614d6cdf3140106f68cff7cec0 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 14:36:51 +0500 Subject: [PATCH 23/52] fix(protocol): govern eligibility policy by version [M-03] --- agent/flow-trace/00_INDEX.md | 2 +- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 25 +++---- .../src/sortition/handlers/registry.rs | 27 ++++++-- .../sortition/src/sortition/node_registry.rs | 35 +++++++--- .../src/sortition/node_registry_tests.rs | 22 ++++-- .../IBondingRegistry.json | 52 +++++++++++--- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../contracts/interfaces/IBondingRegistry.sol | 35 +++++++--- .../contracts/registry/BondingRegistry.sol | 59 ++++++++++++---- .../test/Registry/BondingRegistry.spec.ts | 68 ++++++++++++++----- .../CiphernodeRegistryOwnable.spec.ts | 43 ++++++++++++ 13 files changed, 289 insertions(+), 85 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 709c288e2..775c95365 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -210,7 +210,7 @@ _Found during source-code cross-referencing of these trace documents._ | 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | | 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | | 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | -| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | The first operator registration permanently locks ticket price, license thresholds, and minimum ticket policy, and the minimum ticket requirement can never be zero. | +| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | Governance may update ticket price, license thresholds, and minimum-ticket policy. Each effective update advances a policy version and invalidates cached activity in O(1); registered operators fail closed until a permissionless refresh re-evaluates them under the new version, keeping on-chain counts and Rust sortition state synchronized. | | 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | | 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index 6cd3366c2..051001b00 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -92,12 +92,11 @@ Before a node can register, it must stake two types of collateral: └───────────────────────────────────────────────────────────┘ ``` -Bonding-asset rotation is liability-gated. A replacement ticket wrapper cannot -be configured while the old wrapper has issued tickets or a payable balance, -and the FOLD license token cannot change while the bonding registry holds any -of the old token. Replacement assets must be deployed contracts; the only zero -exception is the one-time license-token placeholder used to resolve the circular -FOLD/BondingRegistry deployment. +Bonding-asset rotation is liability-gated. A replacement ticket wrapper cannot be configured while +the old wrapper has issued tickets or a payable balance, and the FOLD license token cannot change +while the bonding registry holds any of the old token. Replacement assets must be deployed +contracts; the only zero exception is the one-time license-token placeholder used to resolve the +circular FOLD/BondingRegistry deployment. --- @@ -173,12 +172,14 @@ _updateOperatorStatus(operator): emit OperatorActivationChanged(operator, true) ``` -The eligibility policy (`ticketPrice`, `licenseRequiredBond`, -`licenseActiveBps`, and `minTicketBalance`) is deployment-time configuration. -The first successful operator registration permanently locks all four values; -`minTicketBalance` must be nonzero. Consequently cached `active` state, -`numActiveOperators`, and the ticket units used for later sortition cannot be -invalidated by a governance update. +Governance may update `ticketPrice`, `licenseRequiredBond`, `licenseActiveBps`, and +`minTicketBalance`; `minTicketBalance` must remain nonzero. Every effective update advances +`eligibilityConfigurationVersion`, resets `numActiveOperators`, and makes all previously cached +operator statuses fail closed. Operators or governance then call `refreshOperatorStatus` (or its +batch form) to re-evaluate registered operators under the new policy. Only operators refreshed into +the current version count as active, so committee requests cannot rely on status cached under an +older policy. The Rust sortition state consumes the same `ConfigurationUpdated` event and marks its +chain-local operators inactive until matching `OperatorActivationChanged` refresh events arrive. --- diff --git a/crates/sortition/src/sortition/handlers/registry.rs b/crates/sortition/src/sortition/handlers/registry.rs index 2d8c05ec8..f99e6a0ee 100644 --- a/crates/sortition/src/sortition/handlers/registry.rs +++ b/crates/sortition/src/sortition/handlers/registry.rs @@ -96,7 +96,12 @@ impl Handler> for Sortition { let (msg, ec) = msg.into_components(); trap(EType::Sortition, &self.bus.with_ec(&ec), || { self.node_state.try_mutate(&ec, |mut state_map| { - NodeRegistry::set_operator_active(&mut state_map, msg.operator.clone(), msg.active); + NodeRegistry::set_operator_active( + &mut state_map, + msg.chain_id, + msg.operator.clone(), + msg.active, + ); Ok(state_map) }) }) @@ -113,12 +118,22 @@ impl Handler> for Sortition { ) -> Self::Result { let (msg, ec) = msg.into_components(); trap(EType::Sortition, &self.bus.with_ec(&ec), || { - if msg.parameter == "ticketPrice" { - self.node_state.try_mutate(&ec, |mut state_map| { - NodeRegistry::set_ticket_price(&mut state_map, msg.chain_id, msg.new_value); - Ok(state_map) - })?; + let eligibility_parameter = matches!( + msg.parameter.as_str(), + "ticketPrice" | "licenseRequiredBond" | "licenseActiveBps" | "minTicketBalance" + ); + + if !eligibility_parameter { + return Ok(()); } + + self.node_state.try_mutate(&ec, |mut state_map| { + if msg.parameter == "ticketPrice" { + NodeRegistry::set_ticket_price(&mut state_map, msg.chain_id, msg.new_value); + } + NodeRegistry::invalidate_operator_activity(&mut state_map, msg.chain_id); + Ok(state_map) + })?; Ok(()) }) } diff --git a/crates/sortition/src/sortition/node_registry.rs b/crates/sortition/src/sortition/node_registry.rs index 1d4f5cfa0..39c9b9c67 100644 --- a/crates/sortition/src/sortition/node_registry.rs +++ b/crates/sortition/src/sortition/node_registry.rs @@ -136,21 +136,22 @@ impl NodeRegistry { ); } - /// Update an operator's active status across every chain it appears on. + /// Update an operator's active status on the chain that emitted the event. pub fn set_operator_active( store: &mut HashMap, + chain_id: u64, operator: String, active: bool, ) { - for chain_state in store.values_mut() { - let node = chain_state.nodes.entry(operator.clone()).or_default(); - node.active = active; - info!( - operator = %operator, - active = active, - "Updated operator active status" - ); - } + let chain_state = store.entry(chain_id).or_default(); + let node = chain_state.nodes.entry(operator.clone()).or_default(); + node.active = active; + info!( + operator = %operator, + chain_id = chain_id, + active = active, + "Updated operator active status" + ); } /// Set the ticket price for a chain. @@ -168,6 +169,20 @@ impl NodeRegistry { ); } + /// Mark every operator on one chain inactive after an eligibility-policy + /// update. On-chain status also fails closed until each operator refreshes + /// against the new policy version. + pub fn invalidate_operator_activity(store: &mut HashMap, chain_id: u64) { + let chain_state = store.entry(chain_id).or_default(); + for node in chain_state.nodes.values_mut() { + node.active = false; + } + info!( + chain_id = chain_id, + "Invalidated operator activity after eligibility-policy update" + ); + } + /// Record a published committee and increment active-job counters for each /// of its members. pub fn record_committee_published( diff --git a/crates/sortition/src/sortition/node_registry_tests.rs b/crates/sortition/src/sortition/node_registry_tests.rs index fa17617d2..6d92cd855 100644 --- a/crates/sortition/src/sortition/node_registry_tests.rs +++ b/crates/sortition/src/sortition/node_registry_tests.rs @@ -28,7 +28,7 @@ fn available_tickets_accounts_for_price_and_jobs() { let mut store = HashMap::new(); NodeRegistry::set_ticket_price(&mut store, 1, U256::from(10)); NodeRegistry::set_ticket_balance(&mut store, 1, "0xabc".into(), U256::from(55)); - NodeRegistry::set_operator_active(&mut store, "0xabc".into(), true); + NodeRegistry::set_operator_active(&mut store, 1, "0xabc".into(), true); // floor(55 / 10) = 5 tickets, no active jobs yet. assert_eq!(store[&1].available_tickets("0xabc"), 5); @@ -47,12 +47,26 @@ fn zero_price_yields_no_tickets() { } #[test] -fn operator_active_applies_across_chains() { +fn operator_active_applies_only_to_the_event_chain() { let mut store = HashMap::new(); NodeRegistry::add_node(&mut store, 1, "0xabc".into()); NodeRegistry::add_node(&mut store, 2, "0xabc".into()); - NodeRegistry::set_operator_active(&mut store, "0xabc".into(), true); + NodeRegistry::set_operator_active(&mut store, 1, "0xabc".into(), true); assert!(store[&1].nodes["0xabc"].active); + assert!(!store[&2].nodes["0xabc"].active); +} + +#[test] +fn eligibility_update_invalidates_only_the_target_chain() { + let mut store = HashMap::new(); + NodeRegistry::add_node(&mut store, 1, "0xabc".into()); + NodeRegistry::add_node(&mut store, 2, "0xabc".into()); + NodeRegistry::set_operator_active(&mut store, 1, "0xabc".into(), true); + NodeRegistry::set_operator_active(&mut store, 2, "0xabc".into(), true); + + NodeRegistry::invalidate_operator_activity(&mut store, 1); + + assert!(!store[&1].nodes["0xabc"].active); assert!(store[&2].nodes["0xabc"].active); } @@ -79,7 +93,7 @@ fn get_nodes_with_tickets_filters_inactive_and_empty() { let mut store = HashMap::new(); NodeRegistry::set_ticket_price(&mut store, 1, U256::from(10)); NodeRegistry::set_ticket_balance(&mut store, 1, "active".into(), U256::from(30)); - NodeRegistry::set_operator_active(&mut store, "active".into(), true); + NodeRegistry::set_operator_active(&mut store, 1, "active".into(), true); // Inactive node with balance is excluded. NodeRegistry::set_ticket_balance(&mut store, 1, "inactive".into(), U256::from(30)); diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 6be186057..b5566352a 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -18,11 +18,6 @@ "name": "CiphernodeBanned", "type": "error" }, - { - "inputs": [], - "name": "EligibilityConfigurationLocked", - "type": "error" - }, { "inputs": [ { @@ -185,6 +180,19 @@ "name": "ConfigurationUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "EligibilityConfigurationVersionUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -501,12 +509,12 @@ }, { "inputs": [], - "name": "eligibilityConfigurationLocked", + "name": "eligibilityConfigurationVersion", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], "stateMutability": "view", @@ -812,6 +820,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "refreshOperatorStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "operators", + "type": "address[]" + } + ], + "name": "refreshOperatorStatuses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "registerOperator", @@ -1188,5 +1222,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" + "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index acf21a739..3a3cdaa07 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" + "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 4c93dc703..e1d32c9b8 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2376,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-04326bbd3714d0d7d56214e0d6ea5dc646f6c4ab" + "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 1f2c4ca80..dab0c5469 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1450,5 +1450,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" + "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 972505bef..bf71fcaa8 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -39,8 +39,6 @@ interface IBondingRegistry { /// @notice A bonding asset cannot rotate while balances remain denominated in it. error OutstandingAssetLiabilities(address asset, uint256 amount); - /// @notice Eligibility policy becomes immutable when the first operator registers. - error EligibilityConfigurationLocked(); error InvalidConfiguration(); error NoPendingDeregistration(); error OnlyRewardDistributor(); @@ -124,6 +122,12 @@ interface IBondingRegistry { uint256 newValue ); + /** + * @notice Emitted when an eligibility setting invalidates cached operator status. + * @param version New eligibility-policy version. + */ + event EligibilityConfigurationVersionUpdated(uint256 indexed version); + /** * @notice Emitted when a reward distributor is authorized or revoked * @param distributor Address of the distributor @@ -275,8 +279,21 @@ interface IBondingRegistry { */ function numActiveOperators() external view returns (uint256); - /// @notice Whether eligibility-affecting configuration is permanently locked. - function eligibilityConfigurationLocked() external view returns (bool); + /// @notice Current eligibility-policy version. + function eligibilityConfigurationVersion() external view returns (uint256); + + /** + * @notice Re-evaluate one registered operator under the current eligibility policy. + * @dev Permissionless so operators or governance can restore current status after + * an eligibility configuration update. + */ + function refreshOperatorStatus(address operator) external; + + /** + * @notice Re-evaluate a batch of registered operators under the current policy. + * @dev Intended for governance/operator automation after a policy update. + */ + function refreshOperatorStatuses(address[] calldata operators) external; /** * @notice Check if operator has deregistration in progress @@ -489,30 +506,28 @@ interface IBondingRegistry { /** * @notice Set ticket price * @param newTicketPrice New price per ticket - * @dev Only callable by contract owner; rotation requires zero outstanding - * ticket supply and zero payable balance in the old wrapper. + * @dev Only callable by contract owner. Invalidates cached operator status. */ function setTicketPrice(uint256 newTicketPrice) external; /** * @notice Set license bond price required * @param newLicenseRequiredBond New license bond price - * @dev Only callable by contract owner; rotation requires a zero registry - * balance in the old license token. + * @dev Only callable by contract owner. Invalidates cached operator status. */ function setLicenseRequiredBond(uint256 newLicenseRequiredBond) external; /** * @notice Set license active BPS * @param newBps New license active BPS - * @dev Only callable by contract owner + * @dev Only callable by contract owner. Invalidates cached operator status. */ function setLicenseActiveBps(uint256 newBps) external; /** * @notice Set minimum ticket balance required for activation * @param newMinTicketBalance New minimum ticket balance - * @dev Only callable by contract owner + * @dev Only callable by contract owner. Invalidates cached operator status. */ function setMinTicketBalance(uint256 newMinTicketBalance) external; diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index 5b4585126..ce659711e 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -132,6 +132,7 @@ contract BondingRegistry is bool registered; bool exitRequested; bool active; + uint256 eligibilityVersion; } /// @notice Maps operator address to their state data @@ -150,8 +151,10 @@ contract BondingRegistry is /// @dev Internal state for managing exit queue of tickets and licenses ExitQueueLib.ExitQueueState private _exits; - /// @notice One-way lock for every parameter that affects operator eligibility. - bool public eligibilityConfigurationLocked; + /// @notice Version of the current operator-eligibility policy. + /// @dev Every eligibility update advances the version and resets + /// {numActiveOperators}. Operators fail closed until refreshed. + uint256 public eligibilityConfigurationVersion; /// @notice Slashed tickets committed to retryable E3 refund routes. uint256 public reservedSlashedTicketBalance; @@ -326,7 +329,24 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function isActive(address operator) external view returns (bool) { - return operators[operator].active; + Operator storage op = operators[operator]; + return + op.eligibilityVersion == eligibilityConfigurationVersion && + op.active; + } + + /// @inheritdoc IBondingRegistry + function refreshOperatorStatus(address operator) public { + require(operators[operator].registered, NotRegistered()); + _updateOperatorStatus(operator); + } + + /// @inheritdoc IBondingRegistry + function refreshOperatorStatuses(address[] calldata operatorList) external { + uint256 len = operatorList.length; + for (uint256 i = 0; i < len; i++) { + refreshOperatorStatus(operatorList[i]); + } } /// @inheritdoc IBondingRegistry @@ -358,7 +378,6 @@ contract BondingRegistry is ); operators[msg.sender].registered = true; - eligibilityConfigurationLocked = true; // CiphernodeRegistry already emits an event when a ciphernode is added registry.addCiphernode(msg.sender); @@ -698,11 +717,12 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setTicketPrice(uint256 newTicketPrice) public onlyOwner { - _requireEligibilityConfigurationMutable(); require(newTicketPrice != 0, InvalidConfiguration()); uint256 oldValue = ticketPrice; + if (oldValue == newTicketPrice) return; ticketPrice = newTicketPrice; + _invalidateEligibilityStatuses(); emit ConfigurationUpdated("ticketPrice", oldValue, newTicketPrice); } @@ -711,11 +731,12 @@ contract BondingRegistry is function setLicenseRequiredBond( uint256 newLicenseRequiredBond ) public onlyOwner { - _requireEligibilityConfigurationMutable(); require(newLicenseRequiredBond != 0, InvalidConfiguration()); uint256 oldValue = licenseRequiredBond; + if (oldValue == newLicenseRequiredBond) return; licenseRequiredBond = newLicenseRequiredBond; + _invalidateEligibilityStatuses(); emit ConfigurationUpdated( "licenseRequiredBond", @@ -726,21 +747,23 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setLicenseActiveBps(uint256 newBps) public onlyOwner { - _requireEligibilityConfigurationMutable(); require(newBps > 0 && newBps <= BPS_BASE, InvalidConfiguration()); uint256 oldValue = licenseActiveBps; + if (oldValue == newBps) return; licenseActiveBps = newBps; + _invalidateEligibilityStatuses(); emit ConfigurationUpdated("licenseActiveBps", oldValue, newBps); } /// @inheritdoc IBondingRegistry function setMinTicketBalance(uint256 newMinTicketBalance) public onlyOwner { - _requireEligibilityConfigurationMutable(); require(newMinTicketBalance != 0, InvalidConfiguration()); uint256 oldValue = minTicketBalance; + if (oldValue == newMinTicketBalance) return; minTicketBalance = newMinTicketBalance; + _invalidateEligibilityStatuses(); emit ConfigurationUpdated( "minTicketBalance", @@ -937,12 +960,17 @@ contract BondingRegistry is /// @param operator Address of the operator to update function _updateOperatorStatus(address operator) internal { Operator storage op = operators[operator]; + uint256 currentVersion = eligibilityConfigurationVersion; + bool oldActiveStatus = op.eligibilityVersion == currentVersion && + op.active; bool newActiveStatus = op.registered && op.licenseBond >= _minLicenseBond() && (ticketToken.balanceOf(operator) / ticketPrice >= minTicketBalance); - if (op.active != newActiveStatus) { - op.active = newActiveStatus; + op.eligibilityVersion = currentVersion; + op.active = newActiveStatus; + + if (oldActiveStatus != newActiveStatus) { if (newActiveStatus) { numActiveOperators++; } else { @@ -959,9 +987,14 @@ contract BondingRegistry is return (licenseRequiredBond * licenseActiveBps) / BPS_BASE; } - function _requireEligibilityConfigurationMutable() internal view { - if (eligibilityConfigurationLocked) - revert EligibilityConfigurationLocked(); + /// @dev Invalidates every cached active status in O(1). Operators are + /// considered inactive until they refresh under the new version. + function _invalidateEligibilityStatuses() internal { + eligibilityConfigurationVersion++; + numActiveOperators = 0; + emit EligibilityConfigurationVersionUpdated( + eligibilityConfigurationVersion + ); } /// @dev `safeTransfer` of the license token, measuring the RECIPIENT-side delta diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index 1f12bcaf2..407e8c936 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -915,9 +915,16 @@ describe("BondingRegistry", function () { }); }); - it("AUD-M03: locks every eligibility parameter at first registration", async function () { - const { bondingRegistry, licenseToken, operator1 } = - await loadFixture(setup); + it("AUD-M03: governs eligibility parameters and refreshes cached status by policy version", async function () { + const { + bondingRegistry, + licenseToken, + usdcToken, + ticketToken, + operator1, + operator2, + } = await loadFixture(setup); + const operator = await operator1.getAddress(); await licenseToken .connect(operator1) @@ -927,21 +934,48 @@ describe("BondingRegistry", function () { .bondLicense(LICENSE_REQUIRED_BOND); await bondingRegistry.connect(operator1).registerOperator(); - expect(await bondingRegistry.eligibilityConfigurationLocked()).to.equal( - true, + const ticketAmount = TICKET_PRICE * BigInt(MIN_TICKET_BALANCE); + await usdcToken + .connect(operator1) + .approve(await ticketToken.getAddress(), ticketAmount); + await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); + + expect(await bondingRegistry.isActive(operator)).to.equal(true); + expect(await bondingRegistry.numActiveOperators()).to.equal(1); + + const initialVersion = + await bondingRegistry.eligibilityConfigurationVersion(); + await bondingRegistry.setTicketPrice(TICKET_PRICE * 2n); + + expect(await bondingRegistry.eligibilityConfigurationVersion()).to.equal( + initialVersion + 1n, ); - for (const call of [ - () => bondingRegistry.setTicketPrice(TICKET_PRICE + 1n), - () => - bondingRegistry.setLicenseRequiredBond(LICENSE_REQUIRED_BOND + 1n), - () => bondingRegistry.setLicenseActiveBps(9000n), - () => bondingRegistry.setMinTicketBalance(MIN_TICKET_BALANCE + 1), - ]) { - await expect(call()).to.be.revertedWithCustomError( - bondingRegistry, - "EligibilityConfigurationLocked", - ); - } + expect(await bondingRegistry.isActive(operator)).to.equal(false); + expect(await bondingRegistry.numActiveOperators()).to.equal(0); + + // Refresh is permissionless and evaluates the new policy. The doubled + // ticket price leaves this operator below the five-ticket threshold. + await bondingRegistry.connect(operator2).refreshOperatorStatus(operator); + expect(await bondingRegistry.isActive(operator)).to.equal(false); + expect(await bondingRegistry.numActiveOperators()).to.equal(0); + + await bondingRegistry.setTicketPrice(TICKET_PRICE); + await bondingRegistry.refreshOperatorStatuses([operator]); + expect(await bondingRegistry.isActive(operator)).to.equal(true); + expect(await bondingRegistry.numActiveOperators()).to.equal(1); + + await bondingRegistry.setLicenseRequiredBond(LICENSE_REQUIRED_BOND * 2n); + await bondingRegistry.refreshOperatorStatus(operator); + expect(await bondingRegistry.isActive(operator)).to.equal(false); + + await bondingRegistry.setLicenseRequiredBond(LICENSE_REQUIRED_BOND); + await bondingRegistry.setLicenseActiveBps(9_000); + await bondingRegistry.refreshOperatorStatus(operator); + expect(await bondingRegistry.isActive(operator)).to.equal(true); + + await bondingRegistry.setMinTicketBalance(MIN_TICKET_BALANCE + 1); + await bondingRegistry.refreshOperatorStatus(operator); + expect(await bondingRegistry.isActive(operator)).to.equal(false); }); it("AUD-M03: rejects a zero minimum ticket requirement", async function () { diff --git a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index 726e68f12..8976b0e79 100644 --- a/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/interfold-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -207,6 +207,49 @@ describe("CiphernodeRegistryOwnable", function () { ); expect(await registry.rootAt(0)).to.not.equal(0); }); + + it("AUD-M03: fails closed after governance updates until operators refresh", async function () { + const { + registry, + interfold, + bondingRegistry, + usdcToken, + mockE3Program, + mockDecryptionVerifier, + operator1, + operator2, + operator3, + } = await loadFixture(setup); + + await bondingRegistry.setLicenseActiveBps(9_000); + expect(await bondingRegistry.numActiveOperators()).to.equal(0); + + await expect( + makeRequest( + interfold, + usdcToken, + mockE3Program, + mockDecryptionVerifier, + ), + ) + .to.be.revertedWithCustomError(registry, "InsufficientCiphernodes") + .withArgs(3, 0); + + await bondingRegistry.refreshOperatorStatuses([ + await operator1.getAddress(), + await operator2.getAddress(), + await operator3.getAddress(), + ]); + expect(await bondingRegistry.numActiveOperators()).to.equal(3); + + await makeRequest( + interfold, + usdcToken, + mockE3Program, + mockDecryptionVerifier, + ); + expect(await registry.rootAt(0)).to.equal(await registry.root()); + }); }); describe("publishCommittee()", function () { From 90345cdc361499555da75ca55e9f26a59104bec7 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 14:38:12 +0500 Subject: [PATCH 24/52] chore(contracts): refresh M-03 storage baselines --- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 2 +- .../storage-layouts/BondingRegistry.json | 130 +++++++------ .../CiphernodeRegistryOwnable.json | 128 ++++++------- .../storage-layouts/E3RefundManager.json | 72 +++---- .../audits/storage-layouts/Interfold.json | 180 +++++++++--------- 9 files changed, 264 insertions(+), 256 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index b5566352a..e3deb1ccd 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1222,5 +1222,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 3a3cdaa07..fd4ed7715 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index e1d32c9b8..7d74551e0 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2376,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index dab0c5469..fc33b36ab 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1450,5 +1450,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-b5ad2db57b6cd55bb4869e59d4f06b85621bf463" + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 155392ed0..893c81042 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1384,5 +1384,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f" + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" } \ No newline at end of file diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json index 1ad5b5b1c..109144116 100644 --- a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -3,24 +3,24 @@ "contract": "BondingRegistry", "source": "contracts/registry/BondingRegistry.sol", "baseline": { - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", - "sourceSha256": "63dce01cce37e34a48bee91ebcf5d01f62cc85472418e4ac8e08cbce415e6097" + "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", + "sourceSha256": "b358813d6fff792e03c34b2b2e8d01c1b68dd52a0b909a3161dafd991be723c4" }, "storage": [ { - "astId": 26719, + "astId": 26681, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketToken", "offset": 0, "slot": "0", - "type": "t_contract(InterfoldTicketToken)38646" + "type": "t_contract(InterfoldTicketToken)38713" }, { - "astId": 26723, + "astId": 26685, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseToken", "offset": 0, @@ -28,15 +28,15 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 26727, + "astId": 26689, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registry", "offset": 0, "slot": "2", - "type": "t_contract(ICiphernodeRegistry)21717" + "type": "t_contract(ICiphernodeRegistry)21727" }, { - "astId": 26730, + "astId": 26692, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashingManager", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_address" }, { - "astId": 26735, + "astId": 26697, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributors", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 26738, + "astId": 26700, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributorCount", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 26757, + "astId": 26719, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedFundsTreasury", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_address" }, { - "astId": 26760, + "astId": 26722, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketPrice", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_uint256" }, { - "astId": 26763, + "astId": 26725, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseRequiredBond", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_uint256" }, { - "astId": 26766, + "astId": 26728, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "minTicketBalance", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_uint256" }, { - "astId": 26769, + "astId": 26731, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitDelay", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_uint64" }, { - "astId": 26772, + "astId": 26734, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseActiveBps", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_uint256" }, { - "astId": 26775, + "astId": 26737, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "numActiveOperators", "offset": 0, @@ -116,15 +116,15 @@ "type": "t_uint256" }, { - "astId": 26793, + "astId": 26757, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operators", "offset": 0, "slot": "13", - "type": "t_mapping(t_address,t_struct(Operator)26787_storage)" + "type": "t_mapping(t_address,t_struct(Operator)26751_storage)" }, { - "astId": 26796, + "astId": 26760, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedTicketBalance", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 26799, + "astId": 26763, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedLicenseBond", "offset": 0, @@ -140,23 +140,23 @@ "type": "t_uint256" }, { - "astId": 26803, + "astId": 26767, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_exits", "offset": 0, "slot": "16", - "type": "t_struct(ExitQueueState)24575_storage" + "type": "t_struct(ExitQueueState)24565_storage" }, { - "astId": 26806, + "astId": 26770, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", - "label": "eligibilityConfigurationLocked", + "label": "eligibilityConfigurationVersion", "offset": 0, "slot": "21", - "type": "t_bool" + "type": "t_uint256" }, { - "astId": 26809, + "astId": 26773, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "reservedSlashedTicketBalance", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_uint256" }, { - "astId": 29034, + "astId": 29101, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "__gap", "offset": 0, @@ -178,8 +178,8 @@ "label": "address", "numberOfBytes": "20" }, - "t_array(t_struct(ExitTranche)24544_storage)dyn_storage": { - "base": "t_struct(ExitTranche)24544_storage", + "t_array(t_struct(ExitTranche)24534_storage)dyn_storage": { + "base": "t_struct(ExitTranche)24534_storage", "encoding": "dynamic_array", "label": "struct ExitQueueLib.ExitTranche[]", "numberOfBytes": "32" @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICiphernodeRegistry)21717": { + "t_contract(ICiphernodeRegistry)21727": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" @@ -205,17 +205,17 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(InterfoldTicketToken)38646": { + "t_contract(InterfoldTicketToken)38713": { "encoding": "inplace", "label": "contract InterfoldTicketToken", "numberOfBytes": "20" }, - "t_mapping(t_address,t_array(t_struct(ExitTranche)24544_storage)dyn_storage)": { + "t_mapping(t_address,t_array(t_struct(ExitTranche)24534_storage)dyn_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", "numberOfBytes": "32", - "value": "t_array(t_struct(ExitTranche)24544_storage)dyn_storage" + "value": "t_array(t_struct(ExitTranche)24534_storage)dyn_storage" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", @@ -224,19 +224,19 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_struct(Operator)26787_storage)": { + "t_mapping(t_address,t_struct(Operator)26751_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct BondingRegistry.Operator)", "numberOfBytes": "32", - "value": "t_struct(Operator)26787_storage" + "value": "t_struct(Operator)26751_storage" }, - "t_mapping(t_address,t_struct(PendingAmounts)24550_storage)": { + "t_mapping(t_address,t_struct(PendingAmounts)24540_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", "numberOfBytes": "32", - "value": "t_struct(PendingAmounts)24550_storage" + "value": "t_struct(PendingAmounts)24540_storage" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -245,20 +245,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(ExitQueueState)24575_storage": { + "t_struct(ExitQueueState)24565_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitQueueState", "members": [ { - "astId": 24557, + "astId": 24547, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operatorQueues", "offset": 0, "slot": "0", - "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24544_storage)dyn_storage)" + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24534_storage)dyn_storage)" }, { - "astId": 24561, + "astId": 24551, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexTicket", "offset": 0, @@ -266,7 +266,7 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24565, + "astId": 24555, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexLicense", "offset": 0, @@ -274,15 +274,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24570, + "astId": 24560, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "pendingTotals", "offset": 0, "slot": "3", - "type": "t_mapping(t_address,t_struct(PendingAmounts)24550_storage)" + "type": "t_mapping(t_address,t_struct(PendingAmounts)24540_storage)" }, { - "astId": 24574, + "astId": 24564, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "liveTrancheCount", "offset": 0, @@ -292,12 +292,12 @@ ], "numberOfBytes": "160" }, - "t_struct(ExitTranche)24544_storage": { + "t_struct(ExitTranche)24534_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitTranche", "members": [ { - "astId": 24539, + "astId": 24529, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "unlockTimestamp", "offset": 0, @@ -305,7 +305,7 @@ "type": "t_uint64" }, { - "astId": 24541, + "astId": 24531, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -313,7 +313,7 @@ "type": "t_uint256" }, { - "astId": 24543, + "astId": 24533, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, @@ -323,12 +323,12 @@ ], "numberOfBytes": "96" }, - "t_struct(Operator)26787_storage": { + "t_struct(Operator)26751_storage": { "encoding": "inplace", "label": "struct BondingRegistry.Operator", "members": [ { - "astId": 26778, + "astId": 26740, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseBond", "offset": 0, @@ -336,7 +336,7 @@ "type": "t_uint256" }, { - "astId": 26780, + "astId": 26742, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitUnlocksAt", "offset": 0, @@ -344,7 +344,7 @@ "type": "t_uint64" }, { - "astId": 26782, + "astId": 26744, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registered", "offset": 8, @@ -352,7 +352,7 @@ "type": "t_bool" }, { - "astId": 26784, + "astId": 26746, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitRequested", "offset": 9, @@ -360,22 +360,30 @@ "type": "t_bool" }, { - "astId": 26786, + "astId": 26748, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "active", "offset": 10, "slot": "1", "type": "t_bool" + }, + { + "astId": 26750, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "eligibilityVersion", + "offset": 0, + "slot": "2", + "type": "t_uint256" } ], - "numberOfBytes": "64" + "numberOfBytes": "96" }, - "t_struct(PendingAmounts)24550_storage": { + "t_struct(PendingAmounts)24540_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.PendingAmounts", "members": [ { - "astId": 24547, + "astId": 24537, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -383,7 +391,7 @@ "type": "t_uint256" }, { - "astId": 24549, + "astId": 24539, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json index 7d66b1403..e40b3d16c 100644 --- a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -3,32 +3,32 @@ "contract": "CiphernodeRegistryOwnable", "source": "contracts/registry/CiphernodeRegistryOwnable.sol", "baseline": { - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", + "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", "sourceSha256": "033ae30ce7a728081ca6cf34e050c970a5ed23fbab21ca81b66f4c8bd759af5e" }, "storage": [ { - "astId": 29102, + "astId": 29169, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23274" + "type": "t_contract(IInterfold)23264" }, { - "astId": 29106, + "astId": 29173, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21068" + "type": "t_contract(IBondingRegistry)21078" }, { - "astId": 29110, + "astId": 29177, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numCiphernodes", "offset": 0, @@ -36,7 +36,7 @@ "type": "t_uint256" }, { - "astId": 29113, + "astId": 29180, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "sortitionSubmissionWindow", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_uint256" }, { - "astId": 29133, + "astId": 29200, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodes", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_struct(LazyIMTData)14046_storage" }, { - "astId": 29138, + "astId": 29205, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeEnabled", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 29143, + "astId": 29210, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeTreeIndex", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_address,t_uint40)" }, { - "astId": 29148, + "astId": 29215, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "roots", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 29153, + "astId": 29220, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKeyHashes", "offset": 0, @@ -84,31 +84,31 @@ "type": "t_mapping(t_uint256,t_bytes32)" }, { - "astId": 29159, + "astId": 29226, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committees", "offset": 0, "slot": "10", - "type": "t_mapping(t_uint256,t_struct(Committee)21129_storage)" + "type": "t_mapping(t_uint256,t_struct(Committee)21139_storage)" }, { - "astId": 29163, + "astId": 29230, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashingManager", "offset": 0, "slot": "11", - "type": "t_contract(ISlashingManager)23921" + "type": "t_contract(ISlashingManager)23911" }, { - "astId": 29167, + "astId": 29234, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgFoldAttestationVerifier", "offset": 0, "slot": "12", - "type": "t_contract(IDkgFoldAttestationVerifier)21811" + "type": "t_contract(IDkgFoldAttestationVerifier)21821" }, { - "astId": 29174, + "astId": 29241, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifier", "offset": 0, @@ -116,7 +116,7 @@ "type": "t_address" }, { - "astId": 29176, + "astId": 29243, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifierAt", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint256" }, { - "astId": 29179, + "astId": 29246, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "accusationVoteValidity", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 29190, + "astId": 29257, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidity", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_uint256" }, { - "astId": 29192, + "astId": 29259, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidityAt", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_uint256" }, { - "astId": 29198, + "astId": 29265, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgPartyIds", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" }, { - "astId": 29203, + "astId": 29270, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgSkAggCommits", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29208, + "astId": 29275, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgEsmAggCommits", "offset": 0, @@ -172,15 +172,15 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29224, + "astId": 29291, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "_committeeDependencies", "offset": 0, "slot": "21", - "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29218_storage)" + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29285_storage)" }, { - "astId": 31748, + "astId": 31815, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "__gap", "offset": 0, @@ -234,32 +234,32 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21068": { + "t_contract(IBondingRegistry)21078": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IDkgFoldAttestationVerifier)21811": { + "t_contract(IDkgFoldAttestationVerifier)21821": { "encoding": "inplace", "label": "contract IDkgFoldAttestationVerifier", "numberOfBytes": "20" }, - "t_contract(IInterfold)23274": { + "t_contract(IInterfold)23264": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23921": { + "t_contract(ISlashingManager)23911": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeStage)21092": { + "t_enum(CommitteeStage)21102": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.CommitteeStage", "numberOfBytes": "1" }, - "t_enum(MemberStatus)21086": { + "t_enum(MemberStatus)21096": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.MemberStatus", "numberOfBytes": "1" @@ -271,12 +271,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_enum(MemberStatus)21086)": { + "t_mapping(t_address,t_enum(MemberStatus)21096)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", "numberOfBytes": "32", - "value": "t_enum(MemberStatus)21086" + "value": "t_enum(MemberStatus)21096" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -313,19 +313,19 @@ "numberOfBytes": "32", "value": "t_bytes32" }, - "t_mapping(t_uint256,t_struct(Committee)21129_storage)": { + "t_mapping(t_uint256,t_struct(Committee)21139_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", "numberOfBytes": "32", - "value": "t_struct(Committee)21129_storage" + "value": "t_struct(Committee)21139_storage" }, - "t_mapping(t_uint256,t_struct(CommitteeDependencies)29218_storage)": { + "t_mapping(t_uint256,t_struct(CommitteeDependencies)29285_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", "numberOfBytes": "32", - "value": "t_struct(CommitteeDependencies)29218_storage" + "value": "t_struct(CommitteeDependencies)29285_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -334,20 +334,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(Committee)21129_storage": { + "t_struct(Committee)21139_storage": { "encoding": "inplace", "label": "struct ICiphernodeRegistry.Committee", "members": [ { - "astId": 21096, + "astId": 21106, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "stage", "offset": 0, "slot": "0", - "type": "t_enum(CommitteeStage)21092" + "type": "t_enum(CommitteeStage)21102" }, { - "astId": 21098, + "astId": 21108, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "seed", "offset": 0, @@ -355,7 +355,7 @@ "type": "t_uint256" }, { - "astId": 21100, + "astId": 21110, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "requestBlock", "offset": 0, @@ -363,7 +363,7 @@ "type": "t_uint256" }, { - "astId": 21102, + "astId": 21112, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeDeadline", "offset": 0, @@ -371,7 +371,7 @@ "type": "t_uint256" }, { - "astId": 21104, + "astId": 21114, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKey", "offset": 0, @@ -379,7 +379,7 @@ "type": "t_bytes32" }, { - "astId": 21108, + "astId": 21118, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "threshold", "offset": 0, @@ -387,7 +387,7 @@ "type": "t_array(t_uint32)2_storage" }, { - "astId": 21111, + "astId": 21121, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "topNodes", "offset": 0, @@ -395,7 +395,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 21113, + "astId": 21123, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeHash", "offset": 0, @@ -403,7 +403,7 @@ "type": "t_bytes32" }, { - "astId": 21117, + "astId": 21127, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "submitted", "offset": 0, @@ -411,7 +411,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 21121, + "astId": 21131, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "scoreOf", "offset": 0, @@ -419,15 +419,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 21126, + "astId": 21136, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "memberStatus", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_enum(MemberStatus)21086)" + "type": "t_mapping(t_address,t_enum(MemberStatus)21096)" }, { - "astId": 21128, + "astId": 21138, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "activeCount", "offset": 0, @@ -437,33 +437,33 @@ ], "numberOfBytes": "384" }, - "t_struct(CommitteeDependencies)29218_storage": { + "t_struct(CommitteeDependencies)29285_storage": { "encoding": "inplace", "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", "members": [ { - "astId": 29211, + "astId": 29278, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfoldContract", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23274" + "type": "t_contract(IInterfold)23264" }, { - "astId": 29214, + "astId": 29281, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bonding", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21068" + "type": "t_contract(IBondingRegistry)21078" }, { - "astId": 29217, + "astId": 29284, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23921" + "type": "t_contract(ISlashingManager)23911" } ], "numberOfBytes": "96" diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json index 906ac91fb..3b2e058d1 100644 --- a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -3,11 +3,11 @@ "contract": "E3RefundManager", "source": "contracts/E3RefundManager.sol", "baseline": { - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", + "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", "sourceSha256": "a6ba37835c555d69dc3592a8b47089a2a34e5dfbbe87dfa1b95f1b318875a21b" }, "storage": [ @@ -17,7 +17,7 @@ "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23274" + "type": "t_contract(IInterfold)23264" }, { "astId": 15244, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21068" + "type": "t_contract(IBondingRegistry)21078" }, { "astId": 15247, @@ -41,7 +41,7 @@ "label": "_workAllocation", "offset": 0, "slot": "3", - "type": "t_struct(WorkValueAllocation)21916_storage" + "type": "t_struct(WorkValueAllocation)21926_storage" }, { "astId": 15257, @@ -49,7 +49,7 @@ "label": "_distributions", "offset": 0, "slot": "4", - "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21950_storage)" + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21960_storage)" }, { "astId": 15264, @@ -113,7 +113,7 @@ "label": "_e3PolicySnapshots", "offset": 0, "slot": "12", - "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21929_storage)" + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21939_storage)" }, { "astId": 15309, @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(IBondingRegistry)21068": { + "t_contract(IBondingRegistry)21078": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" @@ -205,7 +205,7 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IInterfold)23274": { + "t_contract(IInterfold)23264": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" @@ -280,19 +280,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21929_storage)": { + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21939_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", "numberOfBytes": "32", - "value": "t_struct(E3PolicySnapshot)21929_storage" + "value": "t_struct(E3PolicySnapshot)21939_storage" }, - "t_mapping(t_uint256,t_struct(RefundDistribution)21950_storage)": { + "t_mapping(t_uint256,t_struct(RefundDistribution)21960_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", "numberOfBytes": "32", - "value": "t_struct(RefundDistribution)21950_storage" + "value": "t_struct(RefundDistribution)21960_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -301,20 +301,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(E3PolicySnapshot)21929_storage": { + "t_struct(E3PolicySnapshot)21939_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.E3PolicySnapshot", "members": [ { - "astId": 21920, + "astId": 21930, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "allocation", "offset": 0, "slot": "0", - "type": "t_struct(WorkValueAllocation)21916_storage" + "type": "t_struct(WorkValueAllocation)21926_storage" }, { - "astId": 21922, + "astId": 21932, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -322,7 +322,7 @@ "type": "t_address" }, { - "astId": 21924, + "astId": 21934, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, @@ -330,7 +330,7 @@ "type": "t_address" }, { - "astId": 21926, + "astId": 21936, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "version", "offset": 20, @@ -338,7 +338,7 @@ "type": "t_uint64" }, { - "astId": 21928, + "astId": 21938, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "initialized", "offset": 28, @@ -348,12 +348,12 @@ ], "numberOfBytes": "96" }, - "t_struct(RefundDistribution)21950_storage": { + "t_struct(RefundDistribution)21960_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.RefundDistribution", "members": [ { - "astId": 21932, + "astId": 21942, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "requesterAmount", "offset": 0, @@ -361,7 +361,7 @@ "type": "t_uint256" }, { - "astId": 21934, + "astId": 21944, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeAmount", "offset": 0, @@ -369,7 +369,7 @@ "type": "t_uint256" }, { - "astId": 21936, + "astId": 21946, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolAmount", "offset": 0, @@ -377,7 +377,7 @@ "type": "t_uint256" }, { - "astId": 21938, + "astId": 21948, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "totalSlashed", "offset": 0, @@ -385,7 +385,7 @@ "type": "t_uint256" }, { - "astId": 21940, + "astId": 21950, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeCount", "offset": 0, @@ -393,7 +393,7 @@ "type": "t_uint256" }, { - "astId": 21942, + "astId": 21952, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "calculated", "offset": 0, @@ -401,7 +401,7 @@ "type": "t_bool" }, { - "astId": 21945, + "astId": 21955, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "feeToken", "offset": 1, @@ -409,7 +409,7 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 21947, + "astId": 21957, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "originalPayment", "offset": 0, @@ -417,7 +417,7 @@ "type": "t_uint256" }, { - "astId": 21949, + "astId": 21959, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "perNodeAmount", "offset": 0, @@ -427,12 +427,12 @@ ], "numberOfBytes": "256" }, - "t_struct(WorkValueAllocation)21916_storage": { + "t_struct(WorkValueAllocation)21926_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.WorkValueAllocation", "members": [ { - "astId": 21907, + "astId": 21917, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "committeeFormationBps", "offset": 0, @@ -440,7 +440,7 @@ "type": "t_uint16" }, { - "astId": 21909, + "astId": 21919, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "dkgBps", "offset": 2, @@ -448,7 +448,7 @@ "type": "t_uint16" }, { - "astId": 21911, + "astId": 21921, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "decryptionBps", "offset": 4, @@ -456,7 +456,7 @@ "type": "t_uint16" }, { - "astId": 21913, + "astId": 21923, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolBps", "offset": 6, @@ -464,7 +464,7 @@ "type": "t_uint16" }, { - "astId": 21915, + "astId": 21925, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "successSlashedNodeBps", "offset": 8, diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json index 17dc83d4f..2295fe2f5 100644 --- a/packages/interfold-contracts/audits/storage-layouts/Interfold.json +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -3,12 +3,12 @@ "contract": "Interfold", "source": "contracts/Interfold.sol", "baseline": { - "buildInfoId": "solc-0_8_28-b6081d4a087889d124a71337ceb3c3c46650c56f", + "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "c6cb4d17e63b99ca159e88feb750a28c977eb043", - "sourceSha256": "56dfa8a201f3b2cb0381cd92bce0f2011a6b028ec0c280c196ed39fb4daef858" + "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", + "sourceSha256": "1fad6efd22f9160e2e91c83201a256ed3bd9aef71cb7c0dadeca34c84d964d27" }, "storage": [ { @@ -17,7 +17,7 @@ "label": "ciphernodeRegistry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21717" + "type": "t_contract(ICiphernodeRegistry)21727" }, { "astId": 17672, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21068" + "type": "t_contract(IBondingRegistry)21078" }, { "astId": 17676, @@ -33,7 +33,7 @@ "label": "e3RefundManager", "offset": 0, "slot": "2", - "type": "t_contract(IE3RefundManager)22330" + "type": "t_contract(IE3RefundManager)22340" }, { "astId": 17680, @@ -41,7 +41,7 @@ "label": "slashingManager", "offset": 0, "slot": "3", - "type": "t_contract(ISlashingManager)23921" + "type": "t_contract(ISlashingManager)23911" }, { "astId": 17684, @@ -73,7 +73,7 @@ "label": "e3Programs", "offset": 0, "slot": "7", - "type": "t_mapping(t_contract(IE3Program)21897,t_bool)" + "type": "t_mapping(t_contract(IE3Program)21907,t_bool)" }, { "astId": 17702, @@ -81,7 +81,7 @@ "label": "e3s", "offset": 0, "slot": "8", - "type": "t_mapping(t_uint256,t_struct(E3)21857_storage)" + "type": "t_mapping(t_uint256,t_struct(E3)21867_storage)" }, { "astId": 17708, @@ -89,7 +89,7 @@ "label": "decryptionVerifiers", "offset": 0, "slot": "9", - "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21784)" + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21794)" }, { "astId": 17714, @@ -97,7 +97,7 @@ "label": "pkVerifiers", "offset": 0, "slot": "10", - "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23312)" + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23302)" }, { "astId": 17719, @@ -121,7 +121,7 @@ "label": "_e3Stages", "offset": 0, "slot": "13", - "type": "t_mapping(t_uint256,t_enum(E3Stage)22359)" + "type": "t_mapping(t_uint256,t_enum(E3Stage)22369)" }, { "astId": 17736, @@ -129,7 +129,7 @@ "label": "_e3Deadlines", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22391_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22401_storage)" }, { "astId": 17742, @@ -137,7 +137,7 @@ "label": "_e3FailureReasons", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_enum(FailureReason)22375)" + "type": "t_mapping(t_uint256,t_enum(FailureReason)22385)" }, { "astId": 17747, @@ -161,7 +161,7 @@ "label": "committeeThresholds", "offset": 0, "slot": "18", - "type": "t_mapping(t_enum(CommitteeSize)22350,t_array(t_uint32)2_storage)" + "type": "t_mapping(t_enum(CommitteeSize)22360,t_array(t_uint32)2_storage)" }, { "astId": 17766, @@ -185,7 +185,7 @@ "label": "_timeoutConfig", "offset": 0, "slot": "21", - "type": "t_struct(E3TimeoutConfig)22383_storage" + "type": "t_struct(E3TimeoutConfig)22393_storage" }, { "astId": 17779, @@ -193,7 +193,7 @@ "label": "_pricingConfig", "offset": 0, "slot": "24", - "type": "t_struct(PricingConfig)22423_storage" + "type": "t_struct(PricingConfig)22433_storage" }, { "astId": 17789, @@ -236,7 +236,7 @@ "type": "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)" }, { - "astId": 20545, + "astId": 20540, "contract": "project/contracts/Interfold.sol:Interfold", "label": "__gap", "offset": 0, @@ -283,27 +283,27 @@ "label": "bytes", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21068": { + "t_contract(IBondingRegistry)21078": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(ICiphernodeRegistry)21717": { + "t_contract(ICiphernodeRegistry)21727": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IDecryptionVerifier)21784": { + "t_contract(IDecryptionVerifier)21794": { "encoding": "inplace", "label": "contract IDecryptionVerifier", "numberOfBytes": "20" }, - "t_contract(IE3Program)21897": { + "t_contract(IE3Program)21907": { "encoding": "inplace", "label": "contract IE3Program", "numberOfBytes": "20" }, - "t_contract(IE3RefundManager)22330": { + "t_contract(IE3RefundManager)22340": { "encoding": "inplace", "label": "contract IE3RefundManager", "numberOfBytes": "20" @@ -313,27 +313,27 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IPkVerifier)23312": { + "t_contract(IPkVerifier)23302": { "encoding": "inplace", "label": "contract IPkVerifier", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23921": { + "t_contract(ISlashingManager)23911": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeSize)22350": { + "t_enum(CommitteeSize)22360": { "encoding": "inplace", "label": "enum IInterfold.CommitteeSize", "numberOfBytes": "1" }, - "t_enum(E3Stage)22359": { + "t_enum(E3Stage)22369": { "encoding": "inplace", "label": "enum IInterfold.E3Stage", "numberOfBytes": "1" }, - "t_enum(FailureReason)22375": { + "t_enum(FailureReason)22385": { "encoding": "inplace", "label": "enum IInterfold.FailureReason", "numberOfBytes": "1" @@ -352,23 +352,23 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21784)": { + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21794)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IDecryptionVerifier)", "numberOfBytes": "32", - "value": "t_contract(IDecryptionVerifier)21784" + "value": "t_contract(IDecryptionVerifier)21794" }, - "t_mapping(t_bytes32,t_contract(IPkVerifier)23312)": { + "t_mapping(t_bytes32,t_contract(IPkVerifier)23302)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IPkVerifier)", "numberOfBytes": "32", - "value": "t_contract(IPkVerifier)23312" + "value": "t_contract(IPkVerifier)23302" }, - "t_mapping(t_contract(IE3Program)21897,t_bool)": { + "t_mapping(t_contract(IE3Program)21907,t_bool)": { "encoding": "mapping", - "key": "t_contract(IE3Program)21897", + "key": "t_contract(IE3Program)21907", "label": "mapping(contract IE3Program => bool)", "numberOfBytes": "32", "value": "t_bool" @@ -387,9 +387,9 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_enum(CommitteeSize)22350,t_array(t_uint32)2_storage)": { + "t_mapping(t_enum(CommitteeSize)22360,t_array(t_uint32)2_storage)": { "encoding": "mapping", - "key": "t_enum(CommitteeSize)22350", + "key": "t_enum(CommitteeSize)22360", "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", "numberOfBytes": "32", "value": "t_array(t_uint32)2_storage" @@ -408,19 +408,19 @@ "numberOfBytes": "32", "value": "t_contract(IERC20)4293" }, - "t_mapping(t_uint256,t_enum(E3Stage)22359)": { + "t_mapping(t_uint256,t_enum(E3Stage)22369)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.E3Stage)", "numberOfBytes": "32", - "value": "t_enum(E3Stage)22359" + "value": "t_enum(E3Stage)22369" }, - "t_mapping(t_uint256,t_enum(FailureReason)22375)": { + "t_mapping(t_uint256,t_enum(FailureReason)22385)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.FailureReason)", "numberOfBytes": "32", - "value": "t_enum(FailureReason)22375" + "value": "t_enum(FailureReason)22385" }, "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { "encoding": "mapping", @@ -429,19 +429,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3)21857_storage)": { + "t_mapping(t_uint256,t_struct(E3)21867_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct E3)", "numberOfBytes": "32", - "value": "t_struct(E3)21857_storage" + "value": "t_struct(E3)21867_storage" }, - "t_mapping(t_uint256,t_struct(E3Deadlines)22391_storage)": { + "t_mapping(t_uint256,t_struct(E3Deadlines)22401_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", "numberOfBytes": "32", - "value": "t_struct(E3Deadlines)22391_storage" + "value": "t_struct(E3Deadlines)22401_storage" }, "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)": { "encoding": "mapping", @@ -471,12 +471,12 @@ "numberOfBytes": "32", "value": "t_bytes_storage" }, - "t_struct(E3)21857_storage": { + "t_struct(E3)21867_storage": { "encoding": "inplace", "label": "struct E3", "members": [ { - "astId": 21824, + "astId": 21834, "contract": "project/contracts/Interfold.sol:Interfold", "label": "seed", "offset": 0, @@ -484,15 +484,15 @@ "type": "t_uint256" }, { - "astId": 21827, + "astId": 21837, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeSize", "offset": 0, "slot": "1", - "type": "t_enum(CommitteeSize)22350" + "type": "t_enum(CommitteeSize)22360" }, { - "astId": 21829, + "astId": 21839, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requestBlock", "offset": 0, @@ -500,7 +500,7 @@ "type": "t_uint256" }, { - "astId": 21833, + "astId": 21843, "contract": "project/contracts/Interfold.sol:Interfold", "label": "inputWindow", "offset": 0, @@ -508,7 +508,7 @@ "type": "t_array(t_uint256)2_storage" }, { - "astId": 21835, + "astId": 21845, "contract": "project/contracts/Interfold.sol:Interfold", "label": "encryptionSchemeId", "offset": 0, @@ -516,15 +516,15 @@ "type": "t_bytes32" }, { - "astId": 21838, + "astId": 21848, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Program", "offset": 0, "slot": "6", - "type": "t_contract(IE3Program)21897" + "type": "t_contract(IE3Program)21907" }, { - "astId": 21840, + "astId": 21850, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSet", "offset": 20, @@ -532,7 +532,7 @@ "type": "t_uint8" }, { - "astId": 21842, + "astId": 21852, "contract": "project/contracts/Interfold.sol:Interfold", "label": "customParams", "offset": 0, @@ -540,23 +540,23 @@ "type": "t_bytes_storage" }, { - "astId": 21845, + "astId": 21855, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifier", "offset": 0, "slot": "8", - "type": "t_contract(IDecryptionVerifier)21784" + "type": "t_contract(IDecryptionVerifier)21794" }, { - "astId": 21848, + "astId": 21858, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifier", "offset": 0, "slot": "9", - "type": "t_contract(IPkVerifier)23312" + "type": "t_contract(IPkVerifier)23302" }, { - "astId": 21850, + "astId": 21860, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeePublicKey", "offset": 0, @@ -564,7 +564,7 @@ "type": "t_bytes32" }, { - "astId": 21852, + "astId": 21862, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphertextOutput", "offset": 0, @@ -572,7 +572,7 @@ "type": "t_bytes32" }, { - "astId": 21854, + "astId": 21864, "contract": "project/contracts/Interfold.sol:Interfold", "label": "plaintextOutput", "offset": 0, @@ -580,7 +580,7 @@ "type": "t_bytes_storage" }, { - "astId": 21856, + "astId": 21866, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requester", "offset": 0, @@ -590,12 +590,12 @@ ], "numberOfBytes": "448" }, - "t_struct(E3Deadlines)22391_storage": { + "t_struct(E3Deadlines)22401_storage": { "encoding": "inplace", "label": "struct IInterfold.E3Deadlines", "members": [ { - "astId": 22386, + "astId": 22396, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgDeadline", "offset": 0, @@ -603,7 +603,7 @@ "type": "t_uint256" }, { - "astId": 22388, + "astId": 22398, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeDeadline", "offset": 0, @@ -611,7 +611,7 @@ "type": "t_uint256" }, { - "astId": 22390, + "astId": 22400, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionDeadline", "offset": 0, @@ -631,7 +631,7 @@ "label": "registry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21717" + "type": "t_contract(ICiphernodeRegistry)21727" }, { "astId": 17810, @@ -639,7 +639,7 @@ "label": "refundManager", "offset": 0, "slot": "1", - "type": "t_contract(IE3RefundManager)22330" + "type": "t_contract(IE3RefundManager)22340" }, { "astId": 17813, @@ -647,17 +647,17 @@ "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23921" + "type": "t_contract(ISlashingManager)23911" } ], "numberOfBytes": "96" }, - "t_struct(E3TimeoutConfig)22383_storage": { + "t_struct(E3TimeoutConfig)22393_storage": { "encoding": "inplace", "label": "struct IInterfold.E3TimeoutConfig", "members": [ { - "astId": 22378, + "astId": 22388, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgWindow", "offset": 0, @@ -665,7 +665,7 @@ "type": "t_uint256" }, { - "astId": 22380, + "astId": 22390, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeWindow", "offset": 0, @@ -673,7 +673,7 @@ "type": "t_uint256" }, { - "astId": 22382, + "astId": 22392, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionWindow", "offset": 0, @@ -683,12 +683,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PricingConfig)22423_storage": { + "t_struct(PricingConfig)22433_storage": { "encoding": "inplace", "label": "struct IInterfold.PricingConfig", "members": [ { - "astId": 22394, + "astId": 22404, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenFixedPerNode", "offset": 0, @@ -696,7 +696,7 @@ "type": "t_uint256" }, { - "astId": 22396, + "astId": 22406, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenPerEncryptionProof", "offset": 0, @@ -704,7 +704,7 @@ "type": "t_uint256" }, { - "astId": 22398, + "astId": 22408, "contract": "project/contracts/Interfold.sol:Interfold", "label": "coordinationPerPair", "offset": 0, @@ -712,7 +712,7 @@ "type": "t_uint256" }, { - "astId": 22400, + "astId": 22410, "contract": "project/contracts/Interfold.sol:Interfold", "label": "availabilityPerNodePerSec", "offset": 0, @@ -720,7 +720,7 @@ "type": "t_uint256" }, { - "astId": 22402, + "astId": 22412, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionPerNode", "offset": 0, @@ -728,7 +728,7 @@ "type": "t_uint256" }, { - "astId": 22404, + "astId": 22414, "contract": "project/contracts/Interfold.sol:Interfold", "label": "publicationBase", "offset": 0, @@ -736,7 +736,7 @@ "type": "t_uint256" }, { - "astId": 22406, + "astId": 22416, "contract": "project/contracts/Interfold.sol:Interfold", "label": "verificationPerProof", "offset": 0, @@ -744,7 +744,7 @@ "type": "t_uint256" }, { - "astId": 22408, + "astId": 22418, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolTreasury", "offset": 0, @@ -752,7 +752,7 @@ "type": "t_address" }, { - "astId": 22410, + "astId": 22420, "contract": "project/contracts/Interfold.sol:Interfold", "label": "marginBps", "offset": 20, @@ -760,7 +760,7 @@ "type": "t_uint16" }, { - "astId": 22412, + "astId": 22422, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolShareBps", "offset": 22, @@ -768,7 +768,7 @@ "type": "t_uint16" }, { - "astId": 22414, + "astId": 22424, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgUtilizationBps", "offset": 24, @@ -776,7 +776,7 @@ "type": "t_uint16" }, { - "astId": 22416, + "astId": 22426, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeUtilizationBps", "offset": 26, @@ -784,7 +784,7 @@ "type": "t_uint16" }, { - "astId": 22418, + "astId": 22428, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptUtilizationBps", "offset": 28, @@ -792,7 +792,7 @@ "type": "t_uint16" }, { - "astId": 22420, + "astId": 22430, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minCommitteeSize", "offset": 0, @@ -800,7 +800,7 @@ "type": "t_uint32" }, { - "astId": 22422, + "astId": 22432, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minThreshold", "offset": 4, From db3221939ef6c6a6174b94755db2b6c138788851 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:39:45 +0500 Subject: [PATCH 25/52] fix(contracts): prioritize failed-E3 requesters [H-01] --- .../contracts/E3RefundManager.sol | 68 ++++--- .../contracts/Interfold.sol | 23 +-- .../contracts/interfaces/IE3RefundManager.sol | 10 +- .../test/E3Lifecycle/E3Integration.spec.ts | 185 +++++++++++++++++- 4 files changed, 237 insertions(+), 49 deletions(-) diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index f001723cb..27ec6469e 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -17,6 +17,7 @@ import { import { IE3RefundManager } from "./interfaces/IE3RefundManager.sol"; import { IInterfold } from "./interfaces/IInterfold.sol"; import { IBondingRegistry } from "./interfaces/IBondingRegistry.sol"; +import { ICiphernodeRegistry } from "./interfaces/ICiphernodeRegistry.sol"; /** * @title E3RefundManager @@ -79,7 +80,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { internal _pendingTrackedTreasury; /// @notice Protected slashed-fund liabilities for each token. mapping(IERC20 token => uint256 amount) internal _tokenLiability; - /// @notice Whether completion recorded the honest-node set for slash settlement. + /// @notice Whether successful completion enabled slash settlement for this E3. mapping(uint256 e3Id => bool ready) internal _successSettlementReady; //////////////////////////////////////////////////////////// // // @@ -478,14 +479,10 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { /// @inheritdoc IE3RefundManager function distributeSlashedFundsOnSuccess( uint256 e3Id, - address[] calldata honestNodes, IERC20 paymentToken ) external onlyE3Interfold(e3Id) { require(address(paymentToken) != address(0), "Invalid fee token"); require(!_successSettlementReady[e3Id], "Already initialized"); - for (uint256 i = 0; i < honestNodes.length; i++) { - _honestNodes[e3Id].push(honestNodes[i]); - } _successSettlementReady[e3Id] = true; if (_pendingSlashedByToken[e3Id][paymentToken] > 0) { @@ -496,48 +493,69 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function _settleSlashedFunds(uint256 e3Id, IERC20 token) internal { uint256 amount = _pendingSlashedByToken[e3Id][token]; _pendingSlashedByToken[e3Id][token] = 0; + (address[] memory nodes, ) = ICiphernodeRegistry( + _e3PolicySnapshots[e3Id].registry + ).getActiveCommitteeNodes(e3Id); if (_distributions[e3Id].calculated) { - _settleFailedE3Slash(e3Id, token, amount); + _settleFailedE3Slash(e3Id, token, amount, nodes); } else { - _settleSuccessfulE3Slash(e3Id, token, amount); + _settleSuccessfulE3Slash(e3Id, token, amount, nodes); } } function _settleFailedE3Slash( uint256 e3Id, IERC20 token, - uint256 amount + uint256 amount, + address[] memory nodes ) internal { - address[] storage nodes = _honestNodes[e3Id]; + RefundDistribution storage dist = _distributions[e3Id]; uint256 toRequester; uint256 toHonestNodes; - if (nodes.length == 0) { + uint256 toProtocol; + bool sameFeeToken = token == dist.feeToken; + + if (!sameFeeToken) { + // No oracle-free comparison can establish an exact fee-token + // shortfall, so the requester gets priority compensation in the + // actual slash asset without relabeling it as exact repayment. toRequester = amount; } else { - (uint16 completedBps, uint16 remainingBps) = _calculateWorkValue( - _getFailedAtStage(e3Id), - _allocationFor(e3Id) - ); - uint256 compensationBps = uint256(completedBps) + remainingBps; - toRequester = compensationBps == 0 - ? amount - : (amount * remainingBps) / compensationBps; + // Same-token slashes first fill the requester's exact fee-token + // shortfall. `totalSlashed` records cumulative same-token slash + // settlement so later slashes cannot refill the same gap. + uint256 originalGap = dist.originalPayment > dist.requesterAmount + ? dist.originalPayment - dist.requesterAmount + : 0; + uint256 alreadyFilled = dist.totalSlashed < originalGap + ? dist.totalSlashed + : originalGap; + uint256 remainingGap = originalGap - alreadyFilled; + toRequester = amount < remainingGap ? amount : remainingGap; toHonestNodes = amount - toRequester; + if (nodes.length == 0) { + // Preserve the requester cap when no honest-node recipient + // exists; the residual remains protocol-owned, not a windfall. + toProtocol = toHonestNodes; + toHonestNodes = 0; + } } + if (sameFeeToken) dist.totalSlashed += amount; address requester = _interfoldFor(e3Id).getRequester(e3Id); _creditSlashedClaim(e3Id, token, requester, toRequester); _creditNodeSlashedClaims(e3Id, token, nodes, toHonestNodes); + _creditTrackedTreasury(_treasuryFor(e3Id), token, toProtocol); emit SlashedFundsApplied(e3Id, token, toRequester, toHonestNodes); } function _settleSuccessfulE3Slash( uint256 e3Id, IERC20 token, - uint256 amount + uint256 amount, + address[] memory nodes ) internal { - address[] storage nodes = _honestNodes[e3Id]; uint256 toNodes = (amount * _successSlashedNodeBpsFor(e3Id)) / BPS_BASE; uint256 toProtocol = amount - toNodes; if (nodes.length == 0) { @@ -553,7 +571,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function _creditNodeSlashedClaims( uint256 e3Id, IERC20 token, - address[] storage nodes, + address[] memory nodes, uint256 amount ) internal { if (amount == 0) return; @@ -657,13 +675,18 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { } /// @inheritdoc IE3RefundManager - function snapshotE3Policy(uint256 e3Id) external onlyInterfold { + function snapshotE3Policy( + uint256 e3Id, + address registry + ) external onlyInterfold { E3PolicySnapshot storage snapshot = _e3PolicySnapshots[e3Id]; require(!snapshot.initialized, "Policy already snapshotted"); + require(registry != address(0), "Invalid registry"); if (policyVersion == 0) policyVersion = 1; snapshot.allocation = _workAllocation; snapshot.treasury = treasury; snapshot.interfold = msg.sender; + snapshot.registry = registry; snapshot.version = policyVersion; snapshot.initialized = true; emit E3PolicySnapshotted( @@ -671,6 +694,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { policyVersion, treasury, msg.sender, + registry, _workAllocation ); } diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 97db76eec..a51edd596 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -279,7 +279,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { dependencies.registry = ciphernodeRegistry; dependencies.refundManager = e3RefundManager; dependencies.slashManager = slashingManager; - dependencies.refundManager.snapshotE3Policy(e3Id); + dependencies.refundManager.snapshotE3Policy( + e3Id, + address(dependencies.registry) + ); // Seed uses block.prevrandao combined with e3Id as additional entropy. // While prevrandao is not cryptographically unpredictable (validator-controlled), // the combination with the unique, incrementing e3Id mitigates manipulation. @@ -479,11 +482,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { IERC20 paymentToken = _e3FeeTokens[e3Id]; if (totalAmount == 0) { - refundManager.distributeSlashedFundsOnSuccess( - e3Id, - activeNodes, - paymentToken - ); + refundManager.distributeSlashedFundsOnSuccess(e3Id, paymentToken); return; } @@ -495,11 +494,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { if (requester != address(0)) { paymentToken.safeTransfer(requester, totalAmount); } - refundManager.distributeSlashedFundsOnSuccess( - e3Id, - activeNodes, - paymentToken - ); + refundManager.distributeSlashedFundsOnSuccess(e3Id, paymentToken); return; } @@ -537,11 +532,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { emit RewardsDistributed(e3Id, activeNodes, amounts); - refundManager.distributeSlashedFundsOnSuccess( - e3Id, - activeNodes, - paymentToken - ); + refundManager.distributeSlashedFundsOnSuccess(e3Id, paymentToken); } /// @notice Credits per-node reward balances and emits `RewardCredited`. diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index 80b05f6a4..09bc5df56 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -31,6 +31,7 @@ interface IE3RefundManager { WorkValueAllocation allocation; address treasury; address interfold; + address registry; uint64 version; bool initialized; } @@ -39,7 +40,7 @@ interface IE3RefundManager { uint256 requesterAmount; // Amount for requester uint256 honestNodeAmount; // Total amount for honest nodes uint256 protocolAmount; // Amount for protocol treasury - uint256 totalSlashed; // Legacy same-token aggregate; new token-aware escrows do not mutate it + uint256 totalSlashed; // Cumulative fee-token slash settlement used for requester-first compensation uint256 honestNodeCount; // Number of honest nodes bool calculated; // Whether distribution is calculated IERC20 feeToken; // The fee token used for this E3's payment (stored per-E3 to survive token rotations) @@ -122,6 +123,7 @@ interface IE3RefundManager { uint64 indexed version, address indexed treasury, address interfold, + address registry, WorkValueAllocation allocation ); /// @notice Emitted when the Interfold address is set @@ -169,9 +171,9 @@ interface IE3RefundManager { IERC20 paymentToken ) external; - /// @notice Freeze the current allocation and treasury for an E3. + /// @notice Freeze the current allocation, treasury, and committee registry for an E3. /// @dev Only Interfold may call this, exactly once, during request creation. - function snapshotE3Policy(uint256 e3Id) external; + function snapshotE3Policy(uint256 e3Id, address registry) external; /// @notice Requester claims their refund /// @param e3Id The failed E3 ID @@ -224,11 +226,9 @@ interface IE3RefundManager { /// @notice Distribute escrowed slashed funds on success /// @param e3Id The E3 ID - /// @param honestNodes Honest node addresses /// @param paymentToken The fee token for this E3 function distributeSlashedFundsOnSuccess( uint256 e3Id, - address[] calldata honestNodes, IERC20 paymentToken ) external; diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 6bc91348f..b67171c24 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -250,6 +250,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect(snapshot.initialized).to.equal(true); expect(snapshot.version).to.equal(1); expect(snapshot.treasury).to.equal(originalTreasury); + expect(snapshot.registry).to.equal(await registry.getAddress()); expect(snapshot.allocation.committeeFormationBps).to.equal(1000); await e3RefundManager.connect(owner).setWorkAllocation({ @@ -329,6 +330,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const policy = await e3RefundManager.getE3PolicySnapshot(0); expect(policy.interfold).to.equal(interfoldAddress); + expect(policy.registry).to.equal(registryAddress); const dependencies = await slashingManager.getE3Dependencies(0); expect(dependencies.bonding).to.equal(bondingAddress); expect(dependencies.registry).to.equal(registryAddress); @@ -836,7 +838,24 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ); - expect(requesterSlashClaim).to.be.gt(0); + const requesterGap = + distributionBefore.originalPayment - distributionBefore.requesterAmount; + expect(requesterSlashClaim).to.equal( + actualSlashedAmount < requesterGap ? actualSlashedAmount : requesterGap, + ); + expect(distributionAfter.totalSlashed).to.equal(actualSlashedAmount); + + let honestSlashClaims = 0n; + for (const node of [operator1, operator2, operator3]) { + honestSlashClaims += await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await node.getAddress(), + ); + } + expect(honestSlashClaims).to.equal( + actualSlashedAmount - requesterSlashClaim, + ); // 7. The requester pulls the base refund and slash entitlement separately. const requesterBalanceBefore = await usdcToken.balanceOf( @@ -1049,6 +1068,22 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); } expect(totalSlashCredits).to.equal(actualSlash); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await requester.getAddress(), + ), + ).to.equal(actualSlash); + for (const node of [operator1, operator2, operator3]) { + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await node.getAddress(), + ), + ).to.equal(0); + } expect(await e3RefundManager.tokenLiability(underlyingAddress)).to.equal( totalSlashCredits, ); @@ -1086,6 +1121,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { slashingManager, usdcToken, makeRequest, + owner, operator1, operator2, operator3, @@ -1095,6 +1131,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await setupOperator(operator1); await setupOperator(operator2); await setupOperator(operator3); + await slashingManager.connect(owner).setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: ethers.parseEther("100"), + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: true, + failureReason: 0, + }); // 1. Request E3, form committee, publish key await makeRequest(undefined, 0); @@ -1149,6 +1196,20 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await operator2.getAddress(), ); expect(op2SlashClaim).to.be.gt(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator1.getAddress(), + ), + ).to.equal(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator3.getAddress(), + ), + ).to.equal(op2SlashClaim); // 5. operator2 (honest node) claims their share const op2BalanceBefore = await usdcToken.balanceOf( @@ -1169,13 +1230,14 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); }); - it("requester-first priority: requester gets filled before honest nodes", async function () { + it("caps same-token requester compensation when no honest nodes exist", async function () { const { interfold, e3RefundManager, usdcToken, makeRequest, requester, + treasury, operator1, operator2, operator3, @@ -1195,6 +1257,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distributionBefore = await e3RefundManager.getRefundDistribution(0); const slashedAmount = ethers.parseUnits("100", 6); + await usdcToken.mint(await e3RefundManager.getAddress(), slashedAmount); // Call from the Interfold frozen in the E3 policy snapshot. Rotating the // manager's live pointer must not grant settlement authority for old E3s. @@ -1214,6 +1277,12 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ]); const distributionAfter = await e3RefundManager.getRefundDistribution(0); + const requesterGap = + distributionBefore.originalPayment - distributionBefore.requesterAmount; + const expectedRequester = + slashedAmount < requesterGap ? slashedAmount : requesterGap; + const expectedProtocol = slashedAmount - expectedRequester; + const tokenAddress = await usdcToken.getAddress(); expect(distributionAfter.requesterAmount).to.equal( distributionBefore.requesterAmount, @@ -1224,15 +1293,109 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect( await e3RefundManager.pendingSlashedClaim( 0, - await usdcToken.getAddress(), + tokenAddress, await requester.getAddress(), ), - ).to.equal(slashedAmount); + ).to.equal(expectedRequester); + expect( + await e3RefundManager.pendingTreasuryClaim( + await treasury.getAddress(), + tokenAddress, + ), + ).to.equal(distributionBefore.protocolAmount + expectedProtocol); expect( - await e3RefundManager.tokenLiability(await usdcToken.getAddress()), + await e3RefundManager.tokenLiability(tokenAddress), ).to.equal(slashedAmount); }); + it("caps cumulative same-token requester compensation before crediting honest nodes", async function () { + const { + interfold, + e3RefundManager, + registry, + usdcToken, + makeRequest, + requester, + operator1, + operator2, + operator3, + setupOperator, + } = await loadFixture(setup); + + await setupOperator(operator1); + await setupOperator(operator2); + await setupOperator(operator3); + + await makeRequest(requester); + await registry.connect(operator1).submitTicket(0, 1); + await registry.connect(operator2).submitTicket(0, 1); + await registry.connect(operator3).submitTicket(0, 1); + await time.increase(SORTITION_SUBMISSION_WINDOW + 1); + await registry.finalizeCommittee(0); + await time.increase(defaultTimeoutConfig.dkgWindow + 1); + await interfold.markE3Failed(0); + await interfold.processE3Failure(0); + + const distribution = await e3RefundManager.getRefundDistribution(0); + const requesterGap = + distribution.originalPayment - distribution.requesterAmount; + const firstSlash = requesterGap / 2n; + const secondSlash = requesterGap; + const totalSlash = firstSlash + secondSlash; + const refundManagerAddress = await e3RefundManager.getAddress(); + await usdcToken.mint(refundManagerAddress, totalSlash); + + const originalInterfold = await e3RefundManager.interfold(); + await ethers.provider.send("hardhat_impersonateAccount", [ + originalInterfold, + ]); + await ethers.provider.send("hardhat_setBalance", [ + originalInterfold, + "0x1000000000000000000", + ]); + const interfoldSigner = await ethers.getSigner(originalInterfold); + const usdcAddress = await usdcToken.getAddress(); + + await e3RefundManager + .connect(interfoldSigner) + .escrowSlashedFunds(0, usdcAddress, firstSlash); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await requester.getAddress(), + ), + ).to.equal(firstSlash); + + await e3RefundManager + .connect(interfoldSigner) + .escrowSlashedFunds(0, usdcAddress, secondSlash); + await ethers.provider.send("hardhat_stopImpersonatingAccount", [ + originalInterfold, + ]); + + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await requester.getAddress(), + ), + ).to.equal(requesterGap); + + let honestSlashClaims = 0n; + for (const node of [operator1, operator2, operator3]) { + honestSlashClaims += await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await node.getAddress(), + ); + } + expect(honestSlashClaims).to.equal(totalSlash - requesterGap); + expect( + (await e3RefundManager.getRefundDistribution(0)).totalSlashed, + ).to.equal(totalSlash); + }); + it("queues slashed funds arriving before processE3Failure and applies on calculate", async function () { const { interfold, @@ -1240,6 +1403,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcToken, makeRequest, requester, + treasury, operator1, operator2, operator3, @@ -1257,6 +1421,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await interfold.markE3Failed(0); const slashedAmount = ethers.parseUnits("50", 6); + await usdcToken.mint(await e3RefundManager.getAddress(), slashedAmount); // Escrow slashed funds BEFORE processE3Failure — should be queued const originalInterfold = await e3RefundManager.interfold(); @@ -1284,6 +1449,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distAfter = await e3RefundManager.getRefundDistribution(0); expect(distAfter.calculated).to.be.true; const usdcAddress = await usdcToken.getAddress(); + const requesterGap = + distAfter.originalPayment - distAfter.requesterAmount; expect( await e3RefundManager.pendingSlashedFunds(0, usdcAddress), ).to.equal(0); @@ -1293,7 +1460,13 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ), - ).to.equal(slashedAmount); + ).to.equal(requesterGap); + expect( + await e3RefundManager.pendingTreasuryClaim( + await treasury.getAddress(), + usdcAddress, + ), + ).to.equal(distAfter.protocolAmount + slashedAmount - requesterGap); }); }); From e80007925b3e8b53ce5536034fe2d7d407f6c838 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:41:57 +0500 Subject: [PATCH 26/52] fix(contracts): retain slashing authority across rotation [M-04] --- .../contracts/interfaces/IBondingRegistry.sol | 34 ++++++ .../contracts/registry/BondingRegistry.sol | 114 ++++++++++++++---- .../contracts/slashing/SlashingManager.sol | 11 +- .../test/E3Lifecycle/E3Integration.spec.ts | 8 ++ .../test/Slashing/SlashingLanes.spec.ts | 67 ++++++++++ 5 files changed, 207 insertions(+), 27 deletions(-) diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index bf71fcaa8..df7780572 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -180,6 +180,14 @@ interface IBondingRegistry { address indexed next ); + /// @notice Emitted whenever a slashing manager gains or loses authority. + /// @dev Replaced managers remain authorized until governance explicitly + /// revokes them so snapshotted E3s and open proposals can finish. + event SlashingManagerAuthorizationUpdated( + address indexed slashingManager, + bool authorized + ); + /** * @notice Emitted whenever a `licenseToken.safeTransfer` performed by the * registry sends FEWER tokens than requested (typical of @@ -573,6 +581,32 @@ interface IBondingRegistry { */ function setSlashingManager(address newSlashingManager) external; + /** + * @notice Revoke a non-current slashing manager after every E3 and proposal + * that depends on it has reached a terminal state. + * @param oldSlashingManager Manager whose authority should be removed + */ + function revokeSlashingManager(address oldSlashingManager) external; + + /** + * @notice Whether a manager may slash collateral or route reserved slash funds. + */ + function isAuthorizedSlashingManager( + address candidate + ) external view returns (bool); + + /** + * @notice Number of currently authorized slashing managers. + */ + function authorizedSlashingManagerCount() external view returns (uint256); + + /** + * @notice Authorized slashing manager at `index`. + */ + function authorizedSlashingManagerAt( + uint256 index + ) external view returns (address); + /** * @notice Set reward distributor address * @param newRewardDistributor New reward distributor address diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index ce659711e..54672fc5c 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -159,13 +159,26 @@ contract BondingRegistry is /// @notice Slashed tickets committed to retryable E3 refund routes. uint256 public reservedSlashedTicketBalance; + /// @notice Slashing managers that may finish snapshotted E3 lifecycles. + /// @dev Rotating the current manager does not revoke its predecessor. + address[] internal _authorizedSlashingManagers; + + /// @dev One-based index into {_authorizedSlashingManagers}; zero means unauthorized. + mapping(address manager => uint256 indexPlusOne) + internal _authorizedSlashingManagerIndex; + + /// @notice Maximum number of concurrently authorized slashing managers. + uint256 public constant MAX_AUTHORIZED_SLASHING_MANAGERS = 32; + // ====================== // Modifiers // ====================== - /// @dev Restricts function access to only the slashing manager - modifier onlySlashingManager() { - if (msg.sender != slashingManager) revert Unauthorized(); + /// @dev Restricts function access to current or retained historical managers. + modifier onlyAuthorizedSlashingManager() { + if (_authorizedSlashingManagerIndex[msg.sender] == 0) { + revert Unauthorized(); + } _; } @@ -188,12 +201,14 @@ contract BondingRegistry is /// @dev Keeps active and already-queued collateral available while any /// financial slash proposal against the operator is unresolved. modifier noOpenSlashProposal(address operator) { - address sm = slashingManager; - if ( - sm != address(0) && - ISlashingManager(sm).hasOpenSlashProposal(operator) - ) { - revert OperatorUnderSlash(); + uint256 len = _authorizedSlashingManagers.length; + for (uint256 i = 0; i < len; i++) { + if ( + ISlashingManager(_authorizedSlashingManagers[i]) + .hasOpenSlashProposal(operator) + ) { + revert OperatorUnderSlash(); + } } _; } @@ -355,6 +370,25 @@ contract BondingRegistry is return op.exitRequested && block.timestamp < op.exitUnlocksAt; } + /// @inheritdoc IBondingRegistry + function isAuthorizedSlashingManager( + address candidate + ) external view returns (bool) { + return _authorizedSlashingManagerIndex[candidate] != 0; + } + + /// @inheritdoc IBondingRegistry + function authorizedSlashingManagerCount() external view returns (uint256) { + return _authorizedSlashingManagers.length; + } + + /// @inheritdoc IBondingRegistry + function authorizedSlashingManagerAt( + uint256 index + ) external view returns (address) { + return _authorizedSlashingManagers[index]; + } + // ====================== // Operator Functions // ====================== @@ -367,10 +401,16 @@ contract BondingRegistry is operators[msg.sender].exitUnlocksAt = 0; } - require( - !ISlashingManager(slashingManager).isBanned(msg.sender), - CiphernodeBanned() - ); + require(slashingManager != address(0), ZeroAddress()); + uint256 managerCount = _authorizedSlashingManagers.length; + for (uint256 i = 0; i < managerCount; i++) { + require( + !ISlashingManager(_authorizedSlashingManagers[i]).isBanned( + msg.sender + ), + CiphernodeBanned() + ); + } require(!operators[msg.sender].registered, AlreadyRegistered()); require( operators[msg.sender].licenseBond >= licenseRequiredBond, @@ -544,7 +584,7 @@ contract BondingRegistry is address operator, uint256 requestedSlashAmount, bytes32 slashReason - ) external onlySlashingManager returns (uint256) { + ) external onlyAuthorizedSlashingManager returns (uint256) { require(requestedSlashAmount != 0, ZeroAmount()); (uint256 pendingTicketBalance, ) = _exits.getPendingAmounts(operator); @@ -598,7 +638,7 @@ contract BondingRegistry is address operator, uint256 requestedSlashAmount, bytes32 slashReason - ) external onlySlashingManager nonReentrant { + ) external onlyAuthorizedSlashingManager nonReentrant { require(requestedSlashAmount != 0, ZeroAmount()); Operator storage operatorData = operators[operator]; @@ -649,7 +689,7 @@ contract BondingRegistry is function redirectSlashedTicketFunds( address to, uint256 amount - ) external onlySlashingManager { + ) external onlyAuthorizedSlashingManager { require(to != address(0), ZeroAddress()); require(amount > 0, ZeroAmount()); require( @@ -664,7 +704,7 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function reserveSlashedTicketFunds( uint256 amount - ) external onlySlashingManager { + ) external onlyAuthorizedSlashingManager { require(amount > 0, ZeroAmount()); require( amount <= slashedTicketBalance - reservedSlashedTicketBalance, @@ -677,7 +717,7 @@ contract BondingRegistry is function redirectReservedSlashedTicketFunds( address to, uint256 amount - ) external onlySlashingManager { + ) external onlyAuthorizedSlashingManager { require(to != address(0), ZeroAddress()); require(amount > 0, ZeroAmount()); require(amount <= reservedSlashedTicketBalance, InsufficientBalance()); @@ -854,14 +894,46 @@ contract BondingRegistry is /// @inheritdoc IBondingRegistry function setSlashingManager(address newSlashingManager) public onlyOwner { - // zero-address protection and explicit event so a missed setter - // call is observable off-chain. require(newSlashingManager != address(0), ZeroAddress()); + if (_authorizedSlashingManagerIndex[newSlashingManager] == 0) { + require( + _authorizedSlashingManagers.length < + MAX_AUTHORIZED_SLASHING_MANAGERS, + InvalidConfiguration() + ); + _authorizedSlashingManagers.push(newSlashingManager); + _authorizedSlashingManagerIndex[ + newSlashingManager + ] = _authorizedSlashingManagers.length; + emit SlashingManagerAuthorizationUpdated(newSlashingManager, true); + } address oldValue = slashingManager; slashingManager = newSlashingManager; emit SlashingManagerUpdated(oldValue, newSlashingManager); } + /// @inheritdoc IBondingRegistry + function revokeSlashingManager( + address oldSlashingManager + ) external onlyOwner { + require(oldSlashingManager != slashingManager, InvalidConfiguration()); + uint256 indexPlusOne = _authorizedSlashingManagerIndex[ + oldSlashingManager + ]; + if (indexPlusOne == 0) revert Unauthorized(); + + uint256 index = indexPlusOne - 1; + uint256 lastIndex = _authorizedSlashingManagers.length - 1; + if (index != lastIndex) { + address moved = _authorizedSlashingManagers[lastIndex]; + _authorizedSlashingManagers[index] = moved; + _authorizedSlashingManagerIndex[moved] = index + 1; + } + _authorizedSlashingManagers.pop(); + delete _authorizedSlashingManagerIndex[oldSlashingManager]; + emit SlashingManagerAuthorizationUpdated(oldSlashingManager, false); + } + /// @notice Disabled. Reverts unconditionally. function renounceOwnership() public view override onlyOwner { revert RenounceOwnershipDisabled(); @@ -1039,5 +1111,5 @@ contract BondingRegistry is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[50] private __gap; + uint256[48] private __gap; } diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index 0092b3d8a..b3a2a37f0 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -732,14 +732,13 @@ contract SlashingManager is } /// @dev Executes a slash: applies financial penalties, optional ban, and committee expulsion. - /// Lane B: if the operator deregistered or exited during the appeal window, penalties - /// gracefully become 0 (BondingRegistry uses min(requested, available)). Accepted tradeoff. + /// BondingRegistry keeps active and queued collateral locked while any + /// proposal from a retained slashing manager remains unresolved. /// @dev `p.executed = true` is deferred until AFTER the two `bondingRegistry.slash*` /// calls succeed but BEFORE any other external interaction. This protects the /// proposal from being permanently marked as executed when the financial leg /// reverts (e.g. an attacker griefs the operator's exit queue with enough - /// tranches to OOG `_takeAssetsFromQueue` — a Lane B operator could otherwise - /// lose all retry attempts). The `MAX_ACTIVE_TRANCHES` cap in ExitQueueLib is + /// tranches to OOG `_takeAssetsFromQueue`). The `MAX_ACTIVE_TRANCHES` cap is /// the primary defence; this ordering provides defence-in-depth. function _executeSlash(uint256 proposalId, Lane lane) internal { SlashProposal storage p = _proposals[proposalId]; @@ -767,8 +766,8 @@ contract SlashingManager is // Financial penalties succeeded — commit `executed` before any further // external interaction (committee expulsion, refund escrow self-call, // interfold routing) so that reentrancy via those paths cannot re-enter - // _executeSlash for the same proposal, while still allowing Lane B - // to retry if either bondingRegistry.slash* leg above reverts. + // _executeSlash for the same proposal, while still allowing a deferred + // proposal to retry if either bondingRegistry.slash* leg above reverts. p.executed = true; // Ban node if snapshotted policy requires it diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index b67171c24..1ec6c3e44 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -352,6 +352,9 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await registry.connect(owner).setInterfold(rotatedRegistry); await registry.connect(owner).setBondingRegistry(rotatedBonding); await registry.connect(owner).setSlashingManager(rotatedSlashingManager); + await bondingRegistry + .connect(owner) + .setSlashingManager(rotatedSlashingManager); await e3RefundManager.connect(owner).setInterfold(rotatedRegistry); await slashingManager.connect(owner).setBondingRegistry(rotatedBonding); await slashingManager @@ -394,6 +397,11 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await operator1.getAddress(), proof, ); + expect( + await bondingRegistry.isAuthorizedSlashingManager( + await slashingManager.getAddress(), + ), + ).to.equal(true); expect(await usdcToken.balanceOf(refundManagerAddress)).to.be.gt( refundBalanceBefore, ); diff --git a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts index 89dfaee9f..83148e98b 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts @@ -236,6 +236,73 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function .false; }); + it("retains an old manager's collateral authority and exit gate across rotation", async function () { + const ctx = await loadFixture(setup); + const { + owner, + slashingManager, + bondingRegistry, + slasher, + operator, + operatorAddress, + } = ctx; + await registerOperatorForExit(ctx); + await setupLaneBPolicy(slashingManager); + + await slashingManager + .connect(slasher) + .proposeSlashEvidence( + 0, + operatorAddress, + REASON_INACTIVITY, + ethers.toUtf8Bytes("rotation"), + ); + + const replacement = await ethers.deployContract("SlashingManager", [ + DEFAULT_ADMIN_DELAY, + await owner.getAddress(), + ]); + await bondingRegistry.setSlashingManager(await replacement.getAddress()); + + expect( + await bondingRegistry.isAuthorizedSlashingManager( + await slashingManager.getAddress(), + ), + ).to.equal(true); + expect( + await bondingRegistry.isAuthorizedSlashingManager( + await replacement.getAddress(), + ), + ).to.equal(true); + expect(await bondingRegistry.authorizedSlashingManagerCount()).to.equal( + 2, + ); + + await expect( + bondingRegistry.connect(operator).unbondLicense(1), + ).to.be.revertedWithCustomError(bondingRegistry, "OperatorUnderSlash"); + + await time.increase(APPEAL_WINDOW + 1); + await slashingManager.executeSlash(0); + + expect(await bondingRegistry.getLicenseBond(operatorAddress)).to.equal( + ethers.parseEther("950"), + ); + await bondingRegistry.connect(operator).unbondLicense(1); + + await bondingRegistry.revokeSlashingManager( + await slashingManager.getAddress(), + ); + expect( + await bondingRegistry.isAuthorizedSlashingManager( + await slashingManager.getAddress(), + ), + ).to.equal(false); + expect(await bondingRegistry.authorizedSlashingManagerCount()).to.equal( + 1, + ); + }); + it("deregisterOperator reverts OperatorUnderSlash while Lane B proposal open", async function () { const ctx = await loadFixture(setup); const { From 99545842da93e3df4ed90a9eb29f7bd5a4bf28d9 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:42:04 +0500 Subject: [PATCH 27/52] fix(contracts): count revived exit queue tails [M-01] --- .../contracts/lib/ExitQueueLib.sol | 33 ++++++++- .../contracts/test/ExitQueueHarness.sol | 74 +++++++++++++++++++ .../test/Registry/ExitQueueLib.spec.ts | 44 +++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 packages/interfold-contracts/contracts/test/ExitQueueHarness.sol create mode 100644 packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts diff --git a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol index 9abbe9653..6cd142d6f 100644 --- a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol +++ b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol @@ -163,7 +163,12 @@ library ExitQueueLib { bool merged; if (len != 0) { ExitTranche storage lastTranche = operatorQueue[len - 1]; - if (lastTranche.unlockTimestamp == unlockTimestamp) { + bool lastTrancheIsLive = lastTranche.ticketAmount != 0 || + lastTranche.licenseAmount != 0; + if ( + lastTrancheIsLive && + lastTranche.unlockTimestamp == unlockTimestamp + ) { if (ticketAmount != 0) lastTranche.ticketAmount += ticketAmount; if (licenseAmount != 0) { lastTranche.licenseAmount += licenseAmount; @@ -520,5 +525,31 @@ library ExitQueueLib { } else { state.queueHeadIndexLicense[operator] = head; } + _pruneEmptyTail(state, operator); + } + + /// @dev Remove fully drained tail entries so repeated queue/claim cycles + /// cannot grow the scanned history behind an earlier locked tranche. + /// Both per-asset heads are clamped because either may have advanced + /// past an entry that is now removed. + function _pruneEmptyTail( + ExitQueueState storage state, + address operator + ) private { + ExitTranche[] storage operatorQueue = state.operatorQueues[operator]; + uint256 len = operatorQueue.length; + while (len != 0) { + ExitTranche storage tail = operatorQueue[len - 1]; + if (tail.ticketAmount != 0 || tail.licenseAmount != 0) break; + operatorQueue.pop(); + len--; + } + + if (state.queueHeadIndexTicket[operator] > len) { + state.queueHeadIndexTicket[operator] = len; + } + if (state.queueHeadIndexLicense[operator] > len) { + state.queueHeadIndexLicense[operator] = len; + } } } diff --git a/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol b/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol new file mode 100644 index 000000000..0cb6e8eff --- /dev/null +++ b/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +pragma solidity 0.8.28; + +import { ExitQueueLib } from "../lib/ExitQueueLib.sol"; + +contract ExitQueueHarness { + using ExitQueueLib for ExitQueueLib.ExitQueueState; + + ExitQueueLib.ExitQueueState private _state; + + function queue( + address operator, + uint64 delay, + uint256 ticketAmount, + uint256 licenseAmount + ) external { + _state.queueAssetsForExit(operator, delay, ticketAmount, licenseAmount); + } + + function slash( + address operator, + uint256 ticketAmount, + uint256 licenseAmount + ) external returns (uint256 ticketsSlashed, uint256 licensesSlashed) { + return + _state.slashPendingAssets( + operator, + ticketAmount, + licenseAmount, + true + ); + } + + function claim( + address operator, + uint256 ticketAmount, + uint256 licenseAmount + ) external returns (uint256 ticketsClaimed, uint256 licensesClaimed) { + return _state.claimAssets(operator, ticketAmount, licenseAmount); + } + + function queueSlashQueue( + address operator, + uint64 delay, + uint256 firstTicketAmount, + uint256 secondTicketAmount + ) external { + _state.queueTicketsForExit(operator, delay, firstTicketAmount); + _state.slashPendingAssets(operator, firstTicketAmount, 0, true); + _state.queueTicketsForExit(operator, delay, secondTicketAmount); + } + + function liveTrancheCount( + address operator + ) external view returns (uint256) { + return _state.liveTrancheCount[operator]; + } + + function tranche( + address operator, + uint256 index + ) external view returns (ExitQueueLib.ExitTranche memory) { + return _state.operatorQueues[operator][index]; + } + + function queueLength(address operator) external view returns (uint256) { + return _state.operatorQueues[operator].length; + } +} diff --git a/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts b/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts new file mode 100644 index 000000000..473f9c3b9 --- /dev/null +++ b/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +import { expect } from "chai"; + +import { ethers } from "../fixtures"; + +describe("ExitQueueLib", function () { + it("counts a new tranche when a fully slashed tail has the same unlock timestamp", async function () { + const [operator] = await ethers.getSigners(); + const harness = await ethers.deployContract("ExitQueueHarness"); + const operatorAddress = await operator.getAddress(); + + await harness.queueSlashQueue(operatorAddress, 7 * 24 * 60 * 60, 10, 20); + + expect(await harness.liveTrancheCount(operatorAddress)).to.equal(1); + expect(await harness.queueLength(operatorAddress)).to.equal(1); + expect((await harness.tranche(operatorAddress, 0)).ticketAmount).to.equal( + 20, + ); + }); + + it("prunes drained tails and reuses both asset heads safely", async function () { + const [operator] = await ethers.getSigners(); + const harness = await ethers.deployContract("ExitQueueHarness"); + const operatorAddress = await operator.getAddress(); + + await harness.queue(operatorAddress, 0, 10, 20); + await harness.claim(operatorAddress, 10, 20); + expect(await harness.queueLength(operatorAddress)).to.equal(0); + expect(await harness.liveTrancheCount(operatorAddress)).to.equal(0); + + await harness.queue(operatorAddress, 0, 30, 40); + expect(await harness.claim.staticCall(operatorAddress, 30, 40)).to.deep.equal( + [30n, 40n], + ); + await harness.claim(operatorAddress, 30, 40); + expect(await harness.queueLength(operatorAddress)).to.equal(0); + expect(await harness.liveTrancheCount(operatorAddress)).to.equal(0); + }); +}); From a72a5bea3d8f88b539591212de4dcb1c3f09cd55 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:44:31 +0500 Subject: [PATCH 28/52] refactor(contracts): remove legacy slash routes [M-05] --- .../contracts/interfaces/IBondingRegistry.sol | 9 --------- .../contracts/interfaces/ISlashingManager.sol | 10 ---------- .../contracts/registry/BondingRegistry.sol | 16 --------------- .../contracts/slashing/SlashingManager.sol | 20 ------------------- .../test/E3Lifecycle/E3Integration.spec.ts | 6 +++--- 5 files changed, 3 insertions(+), 58 deletions(-) diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index df7780572..a0a1e700e 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -471,15 +471,6 @@ interface IBondingRegistry { bytes32 reason ) external; - /** - * @notice Redirect slashed ticket funds to a specified address - * @param to Address to receive the slashed funds (underlying stablecoin) - * @param amount Amount of slashed ticket balance to redirect - * @dev Only callable by authorized slashing manager. Pays out underlying stablecoin - * from burned ticket tokens. Assumes underlying stablecoin matches the E3 fee token. - */ - function redirectSlashedTicketFunds(address to, uint256 amount) external; - /// @notice Reserve slashed ticket funds so treasury cannot withdraw them. /// @dev Only callable by the configured slashing manager. function reserveSlashedTicketFunds(uint256 amount) external; diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index 10c7e9dee..92dd66edf 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -627,16 +627,6 @@ interface ISlashingManager { */ function executeSlash(uint256 proposalId) external; - /** - * @notice Atomically redirects slashed ticket funds to E3RefundManager escrow - * @dev Only callable by this contract (self-call pattern for try/catch atomicity). - * Transfers underlying stablecoin from BondingRegistry to E3RefundManager - * and calls Interfold.escrowSlashedFunds to update the escrow balance. - * @param e3Id ID of the E3 computation - * @param amount Amount of slashed ticket balance to escrow - */ - function escrowSlashedFundsToRefund(uint256 e3Id, uint256 amount) external; - /** * @notice Returns the EIP-712 domain separator used to authenticate attestation votes */ diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index 54672fc5c..9c4d598d6 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -685,22 +685,6 @@ contract BondingRegistry is _updateOperatorStatus(operator); } - /// @inheritdoc IBondingRegistry - function redirectSlashedTicketFunds( - address to, - uint256 amount - ) external onlyAuthorizedSlashingManager { - require(to != address(0), ZeroAddress()); - require(amount > 0, ZeroAmount()); - require( - amount <= slashedTicketBalance - reservedSlashedTicketBalance, - ReservedSlashedFunds() - ); - - slashedTicketBalance -= amount; - ticketToken.payout(to, amount); - } - /// @inheritdoc IBondingRegistry function reserveSlashedTicketFunds( uint256 amount diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index b3a2a37f0..d51851ba7 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -846,26 +846,6 @@ contract SlashingManager is ); } - /// @inheritdoc ISlashingManager - /// @dev Atomically redirects slashed funds to E3RefundManager escrow. - /// External with self-only access for try/catch atomicity. - function escrowSlashedFundsToRefund(uint256 e3Id, uint256 amount) external { - require(msg.sender == address(this), Unauthorized()); - E3Dependencies memory dependencies = _dependenciesFor(e3Id); - address refundManager = address(dependencies.refundManager); - require(refundManager != address(0), ZeroAddress()); - address token = address( - dependencies.bonding.ticketToken().underlying() - ); - dependencies.bonding.redirectSlashedTicketFunds(refundManager, amount); - dependencies.interfoldContract.escrowSlashedFunds( - e3Id, - IERC20(token), - amount - ); - emit SlashedFundsEscrowedToRefund(e3Id, token, amount); - } - /// @inheritdoc ISlashingManager function routePendingSlashFunds( uint256 proposalId diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 1ec6c3e44..717b1a2dc 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -801,9 +801,9 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { // Record BondingRegistry's slashedTicketBalance before slash const slashedBalanceBefore = await bondingRegistry.slashedTicketBalance(); - // 4. Slash operator1 via proposeSlash (Lane A) — real on-chain flow - // This triggers: _executeSlash → slashTicketBalance → redirectSlashedTicketFunds - // → ticketToken.payout(refundManager, amount) → interfold.escrowSlashedFunds → e3RefundManager.escrowSlashedFunds + // 4. Slash operator1 via proposeSlash (Lane A) — real on-chain flow. + // The manager reserves the slash, then atomically routes the reserved + // underlying through Interfold into E3RefundManager escrow. const proof = await signAndEncodeAttestation( [operator2, operator3], 0, From ecb4acb4a94219c17f3e5948af17f00e23b4d1c7 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:44:56 +0500 Subject: [PATCH 29/52] refactor(contracts): initialize pricing without storage slots [H-04] --- .../contracts/Interfold.sol | 13 ++-- .../contracts/lib/InterfoldPricing.sol | 73 ------------------- .../test/InterfoldPricingStorageHarness.sol | 51 ------------- .../ignition/modules/interfold.ts | 18 +++++ .../scripts/deployAndSave/interfold.ts | 17 +++++ .../scripts/protocol/deployContracts.ts | 3 +- .../scripts/storageLayouts.ts | 47 ------------ .../scripts/validateUpgrade.ts | 5 -- .../test/Pricing/StorageAnchors.spec.ts | 39 ---------- 9 files changed, 44 insertions(+), 222 deletions(-) delete mode 100644 packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol delete mode 100644 packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index a51edd596..964fd4db1 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -211,7 +211,8 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { IE3RefundManager _e3RefundManager, IERC20 _feeToken, uint256 _maxDuration, - E3TimeoutConfig calldata config + E3TimeoutConfig calldata config, + PricingConfig calldata pricingConfig ) public initializer { require(_owner != address(0), "Invalid owner"); __Ownable_init(msg.sender); @@ -222,11 +223,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { setFeeToken(_feeToken); _setTimeoutConfig(config); - // Default pricing parameters applied via the linked InterfoldPricing - // library (assembly SSTOREs against the caller's _pricingConfig - // slots) so the 15-field literal stays out of Interfold's runtime - // bytecode (EIP-170 24,576-byte cap). - InterfoldPricing.applyDefaultPricingConfig(); + _setPricingConfig(pricingConfig); if (_owner != owner()) _transferOwnership(_owner); } @@ -1014,6 +1011,10 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { function setPricingConfig( PricingConfig calldata config ) external onlyOwner { + _setPricingConfig(config); + } + + function _setPricingConfig(PricingConfig calldata config) internal { // Validation is delegated to {InterfoldPricing.validatePricingConfig} // (external library link) to keep the deployed Interfold runtime // bytecode under the EIP-170 24,576-byte cap. Revert selectors are diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 117e343a6..e72ebd868 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -125,79 +125,6 @@ library InterfoldPricing { } } - /// @notice Writes the default {IInterfold.PricingConfig} directly to - /// the linked {Interfold} storage starting at slot 24. Called - /// via DELEGATECALL from {Interfold.initialize}, so SSTORE - /// targets the caller's storage. Hosted in the library so - /// the 15-field literal stays out of Interfold runtime bytecode - /// (EIP-170 24,576-byte cap). - /// @dev Slot map for `_pricingConfig` (struct field order in - /// {IInterfold.PricingConfig}): - /// 24: keyGenFixedPerNode - /// 25: keyGenPerEncryptionProof - /// 26: coordinationPerPair - /// 27: availabilityPerNodePerSec - /// 28: decryptionPerNode - /// 29: publicationBase - /// 30: verificationPerProof - /// 31: packed { protocolTreasury(20) | marginBps(2) | - /// protocolShareBps(2) | dkgUtilizationBps(2) | - /// computeUtilizationBps(2) | decryptUtilizationBps(2) } - /// 32: packed { minCommitteeSize(4) | minThreshold(4) } - /// The contract storage layout snapshot in - /// `audits/storage-layouts/Interfold.json` MUST keep these - /// slots stable; any storage reordering requires updating the - /// constants below. - function applyDefaultPricingConfig() external { - // Packed slot 31: - // marginBps = 1000 << 160 - // protocolShareBps = 0 << 176 - // dkgUtilizationBps = 2500 << 192 - // computeUtilizationBps = 5000 << 208 - // decryptUtilizationBps = 2500 << 224 - // protocolTreasury (low 160 bits) and trailing padding are zero. - uint256 slot31 = (uint256(1000) << 160) | - (uint256(2500) << 192) | - (uint256(5000) << 208) | - (uint256(2500) << 224); - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(24, 100000) // keyGenFixedPerNode = 0.10 USDC - sstore(25, 50000) // keyGenPerEncryptionProof = 0.05 USDC - sstore(26, 10000) // coordinationPerPair = 0.01 USDC - sstore(27, 50) // availabilityPerNodePerSec = 0.00005 USDC - sstore(28, 300000) // decryptionPerNode = 0.30 USDC - sstore(29, 1000000) // publicationBase = 1.00 USDC - sstore(30, 5000) // verificationPerProof = 0.005 USDC - sstore(31, slot31) - sstore(32, 0) // minCommitteeSize | minThreshold - } - } - - /// @notice Returns the default {IInterfold.PricingConfig} applied by - /// {Interfold.initialize}. Hosted in the external library so the - /// 15-field literal stays out of the Interfold runtime bytecode - /// (EIP-170 24,576-byte cap). - function defaultPricingConfig() - external - pure - returns (IInterfold.PricingConfig memory cfg) - { - cfg.keyGenFixedPerNode = 100000; // 0.10 USDC - cfg.keyGenPerEncryptionProof = 50000; // 0.05 USDC - cfg.coordinationPerPair = 10000; // 0.01 USDC - cfg.availabilityPerNodePerSec = 50; // 0.00005 USDC - cfg.decryptionPerNode = 300000; // 0.30 USDC - cfg.publicationBase = 1000000; // 1.00 USDC - cfg.verificationPerProof = 5000; // 0.005 USDC - cfg.marginBps = 1000; // 10% - cfg.dkgUtilizationBps = 2500; // 25% - cfg.computeUtilizationBps = 5000; // 50% - cfg.decryptUtilizationBps = 2500; // 25% - // protocolTreasury, protocolShareBps, minCommitteeSize, minThreshold - // remain zero by default and use the struct zero-initialization. - } - /// @notice Mirrors the four validation gates at the top of /// {Interfold.publishCiphertextOutput}. /// @param current ABI-encoded as `uint8` to avoid qualified enum names in diff --git a/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol b/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol deleted file mode 100644 index d7bb1a2a6..000000000 --- a/packages/interfold-contracts/contracts/test/InterfoldPricingStorageHarness.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity 0.8.28; - -import { IInterfold } from "../interfaces/IInterfold.sol"; -import { InterfoldPricing } from "../lib/InterfoldPricing.sol"; - -/// @dev Pins sentinels immediately before and after the pricing struct so the -/// assembly-backed initializer can be tested against its documented slots. -contract InterfoldPricingStorageHarness { - uint256[23] private _prefix; - bytes32 public leftSentinel; - IInterfold.PricingConfig private _pricingConfig; - bytes32 public rightSentinel; - - constructor(bytes32 left, bytes32 right) { - leftSentinel = left; - rightSentinel = right; - } - - function dirtyPricing() external { - _pricingConfig = IInterfold.PricingConfig({ - keyGenFixedPerNode: type(uint256).max, - keyGenPerEncryptionProof: type(uint256).max, - coordinationPerPair: type(uint256).max, - availabilityPerNodePerSec: type(uint256).max, - decryptionPerNode: type(uint256).max, - publicationBase: type(uint256).max, - verificationPerProof: type(uint256).max, - protocolTreasury: address(type(uint160).max), - marginBps: type(uint16).max, - protocolShareBps: type(uint16).max, - dkgUtilizationBps: type(uint16).max, - computeUtilizationBps: type(uint16).max, - decryptUtilizationBps: type(uint16).max, - minCommitteeSize: type(uint32).max, - minThreshold: type(uint32).max - }); - } - - function applyDefaultPricingConfig() external { - InterfoldPricing.applyDefaultPricingConfig(); - } - - function getPricingConfig() - external - view - returns (IInterfold.PricingConfig memory) - { - return _pricingConfig; - } -} diff --git a/packages/interfold-contracts/ignition/modules/interfold.ts b/packages/interfold-contracts/ignition/modules/interfold.ts index b67421608..b82e01f9f 100644 --- a/packages/interfold-contracts/ignition/modules/interfold.ts +++ b/packages/interfold-contracts/ignition/modules/interfold.ts @@ -17,6 +17,23 @@ export default buildModule("Interfold", (m) => { computeWindow: 86400, decryptionWindow: 3600, }); + const pricingConfig = m.getParameter("pricingConfig", { + keyGenFixedPerNode: 100000, + keyGenPerEncryptionProof: 50000, + coordinationPerPair: 10000, + availabilityPerNodePerSec: 50, + decryptionPerNode: 300000, + publicationBase: 1000000, + verificationPerProof: 5000, + protocolTreasury: "0x0000000000000000000000000000000000000000", + marginBps: 1000, + protocolShareBps: 0, + dkgUtilizationBps: 2500, + computeUtilizationBps: 5000, + decryptUtilizationBps: 2500, + minCommitteeSize: 0, + minThreshold: 0, + }); // Pure pricing math is delegated to the InterfoldPricing external library // (DELEGATECALL link) so the deployed Interfold runtime stays under the @@ -34,6 +51,7 @@ export default buildModule("Interfold", (m) => { feeToken, maxDuration, timeoutConfig, + pricingConfig, ]); const interfold = m.contract("TransparentUpgradeableProxy", [ diff --git a/packages/interfold-contracts/scripts/deployAndSave/interfold.ts b/packages/interfold-contracts/scripts/deployAndSave/interfold.ts index 36a954158..391e045d4 100644 --- a/packages/interfold-contracts/scripts/deployAndSave/interfold.ts +++ b/packages/interfold-contracts/scripts/deployAndSave/interfold.ts @@ -106,6 +106,23 @@ export const deployAndSaveInterfold = async ({ feeToken, maxDuration, timeoutConfig, + { + keyGenFixedPerNode: 100000, + keyGenPerEncryptionProof: 50000, + coordinationPerPair: 10000, + availabilityPerNodePerSec: 50, + decryptionPerNode: 300000, + publicationBase: 1000000, + verificationPerProof: 5000, + protocolTreasury: "0x0000000000000000000000000000000000000000", + marginBps: 1000, + protocolShareBps: 0, + dkgUtilizationBps: 2500, + computeUtilizationBps: 5000, + decryptUtilizationBps: 2500, + minCommitteeSize: 0, + minThreshold: 0, + }, ]); const ProxyCF = await ethers.getContractFactory( diff --git a/packages/interfold-contracts/scripts/protocol/deployContracts.ts b/packages/interfold-contracts/scripts/protocol/deployContracts.ts index 575caed0c..6bca3d5ca 100644 --- a/packages/interfold-contracts/scripts/protocol/deployContracts.ts +++ b/packages/interfold-contracts/scripts/protocol/deployContracts.ts @@ -4,7 +4,7 @@ import { ADDRESS_ONE } from "./constants"; import { ensurePoseidonT3 } from "./poseidon"; import { deployProxy } from "./proxies"; import type { ProtocolConfigFile, ProtocolDeployResult } from "./types"; -import { deployedAddress, timeoutConfig } from "./values"; +import { deployedAddress, pricingConfig, timeoutConfig } from "./values"; export async function deployProtocolContracts( ethers: any, @@ -73,6 +73,7 @@ export async function deployProtocolContracts( config.feeToken, BigInt(config.interfold.maxDuration), timeoutConfig(config.interfold.timeoutConfig), + pricingConfig(config.interfold.pricing), ]), ); diff --git a/packages/interfold-contracts/scripts/storageLayouts.ts b/packages/interfold-contracts/scripts/storageLayouts.ts index b6a4c412c..162d3bf35 100644 --- a/packages/interfold-contracts/scripts/storageLayouts.ts +++ b/packages/interfold-contracts/scripts/storageLayouts.ts @@ -352,50 +352,3 @@ export function diffLayouts( return errors; } - -export function assertInterfoldPricingSlots(layout: StorageLayout): string[] { - const errors: string[] = []; - const pricing = layout.storage.find( - (entry) => entry.label === "_pricingConfig", - ); - if (!pricing || pricing.slot !== "24" || pricing.offset !== 0) { - return ["Interfold: _pricingConfig must begin at slot 24+0."]; - } - - const expected = [ - ["keyGenFixedPerNode", "0", 0], - ["keyGenPerEncryptionProof", "1", 0], - ["coordinationPerPair", "2", 0], - ["availabilityPerNodePerSec", "3", 0], - ["decryptionPerNode", "4", 0], - ["publicationBase", "5", 0], - ["verificationPerProof", "6", 0], - ["protocolTreasury", "7", 0], - ["marginBps", "7", 20], - ["protocolShareBps", "7", 22], - ["dkgUtilizationBps", "7", 24], - ["computeUtilizationBps", "7", 26], - ["decryptUtilizationBps", "7", 28], - ["minCommitteeSize", "8", 0], - ["minThreshold", "8", 4], - ] as const; - const members = layout.types[pricing.type]?.members ?? []; - for (const [label, slot, offset] of expected) { - const member = members.find((candidate) => candidate.label === label); - if (!member || member.slot !== slot || member.offset !== offset) { - errors.push( - `Interfold: PricingConfig.${label} must remain at absolute slot ` + - `${24n + BigInt(slot)}+${offset}.`, - ); - } - } - const next = layout.storage.find( - (entry) => entry.label === "_feeTokenAllowed", - ); - if (!next || next.slot !== "33" || next.offset !== 0) { - errors.push( - "Interfold: _feeTokenAllowed must remain adjacent at slot 33+0.", - ); - } - return errors; -} diff --git a/packages/interfold-contracts/scripts/validateUpgrade.ts b/packages/interfold-contracts/scripts/validateUpgrade.ts index 44b10ef3a..b6f760459 100644 --- a/packages/interfold-contracts/scripts/validateUpgrade.ts +++ b/packages/interfold-contracts/scripts/validateUpgrade.ts @@ -9,7 +9,6 @@ import { SNAPSHOT_DIR, type StorageSnapshot, UPGRADEABLE_CONTRACTS, - assertInterfoldPricingSlots, diffLayouts, findCurrentLayout, } from "./storageLayouts"; @@ -51,10 +50,6 @@ async function main(): Promise { `${contract}: candidate compiler settings differ from the production baseline.`, ); } - if (contract === "Interfold") { - errors.push(...assertInterfoldPricingSlots(candidate.layout)); - } - if (errors.length === 0) { console.log( ` ✓ ${contract}: compatible with ${snapshot.baseline.sourceCommit} ` + diff --git a/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts b/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts deleted file mode 100644 index aff1e5809..000000000 --- a/packages/interfold-contracts/test/Pricing/StorageAnchors.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -import { expect } from "chai"; - -import { ethers } from "../fixtures"; - -describe("InterfoldPricing storage anchors", function () { - it("AUD-H04: writes every PricingConfig field without touching adjacent slots", async function () { - const pricing = await ethers.deployContract("InterfoldPricing"); - const harnessFactory = await ethers.getContractFactory( - "InterfoldPricingStorageHarness", - { libraries: { InterfoldPricing: await pricing.getAddress() } }, - ); - const left = ethers.keccak256(ethers.toUtf8Bytes("left-slot-23")); - const right = ethers.keccak256(ethers.toUtf8Bytes("right-slot-33")); - const harness = await harnessFactory.deploy(left, right); - - await harness.dirtyPricing(); - await harness.applyDefaultPricingConfig(); - - const config = await harness.getPricingConfig(); - expect(config.keyGenFixedPerNode).to.equal(100000n); - expect(config.keyGenPerEncryptionProof).to.equal(50000n); - expect(config.coordinationPerPair).to.equal(10000n); - expect(config.availabilityPerNodePerSec).to.equal(50n); - expect(config.decryptionPerNode).to.equal(300000n); - expect(config.publicationBase).to.equal(1000000n); - expect(config.verificationPerProof).to.equal(5000n); - expect(config.protocolTreasury).to.equal(ethers.ZeroAddress); - expect(config.marginBps).to.equal(1000); - expect(config.protocolShareBps).to.equal(0); - expect(config.dkgUtilizationBps).to.equal(2500); - expect(config.computeUtilizationBps).to.equal(5000); - expect(config.decryptUtilizationBps).to.equal(2500); - expect(config.minCommitteeSize).to.equal(0); - expect(config.minThreshold).to.equal(0); - expect(await harness.leftSentinel()).to.equal(left); - expect(await harness.rightSentinel()).to.equal(right); - }); -}); From 54a64659ce83ad20d9090588d8add06b90862810 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:45:38 +0500 Subject: [PATCH 30/52] fix(ci): use source-aligned proof fixtures --- .github/workflows/ci.yml | 48 ++++++++++++-- .../crisp-contracts/hardhat.config.ts | 1 + templates/default/hardhat.config.ts | 1 + tests/integration/lib/prebuild.sh | 63 +++++++++++-------- 4 files changed, 83 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d0d6108c..1dc24f1af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,7 +172,7 @@ jobs: GH_TOKEN: ${{ github.token }} rust_integration_tests: - needs: [detect_changes] + needs: [detect_changes, integration_prebuild] if: needs.detect_changes.outputs.rust_integration_tests == 'true' timeout-minutes: 45 runs-on: @@ -211,9 +211,22 @@ jobs: - name: 'Install the dependencies' run: 'pnpm install --frozen-lockfile' + - name: Download source-aligned ZK fixtures + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: integration-noir-artifacts + path: .ci-noir/ + + - name: Stage source-aligned circuit fixtures + run: | + chmod +x .ci-noir/bin/bb + mkdir -p dist/circuits/insecure-512 + cp -R .ci-noir/circuits/insecure-512/minimum dist/circuits/insecure-512/ + - name: Run Integration Tests env: CIPHERNODE_SKIP_PROOF_AGGREGATION: 'true' + E3_CUSTOM_BB: ${{ github.workspace }}/.ci-noir/bin/bb run: 'cargo test --test integration -- --nocapture' zk_prover_integration: @@ -444,6 +457,22 @@ jobs: with: toolchain: ${{ env.NOIR_TOOLCHAIN }} + - name: Cache Barretenberg binary + id: cache-integration-bb + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: /usr/local/bin/bb + key: bb-bin-${{ env.BB_VERSION }}-amd64-linux + + - name: Install Barretenberg (bb) + if: steps.cache-integration-bb.outputs.cache-hit != 'true' + run: | + curl -fsSL "https://github.com/gnosisguild/aztec-packages/releases/download/v${{ env.BB_VERSION }}/barretenberg-amd64-linux.tar.gz" -o bb.tar.gz + mkdir -p bb_extract && tar -xzf bb.tar.gz -C bb_extract + BB_BIN=$(find bb_extract -name bb -type f | head -n 1) + sudo mv "$BB_BIN" /usr/local/bin/bb + chmod +x /usr/local/bin/bb + - name: Install solc run: | sudo add-apt-repository ppa:ethereum/ethereum \ @@ -471,6 +500,15 @@ jobs: target/debug/pack_e3_params if-no-files-found: error + - name: Upload source-aligned ZK fixtures + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: integration-noir-artifacts + include-hidden-files: true + path: tests/integration/.interfold/noir/ + retention-days: 1 + if-no-files-found: error + check-nix-flake: runs-on: ubuntu-latest steps: @@ -521,6 +559,11 @@ jobs: with: name: build-artifacts path: target/debug/ + - name: 'Download source-aligned ZK fixtures' + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: integration-noir-artifacts + path: tests/integration/.interfold/noir/ - name: 'Download interfold binary' uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -544,9 +587,6 @@ jobs: chmod +x target/debug/fake_encrypt chmod +x target/debug/pack_e3_params chmod +x ~/.cargo/bin/interfold - - name: 'Setup ZK prover' - run: | - interfold noir setup - name: 'Run ${{ matrix.test-suite }} tests' run: 'pnpm test:integration ${{ matrix.test-suite }} --no-prebuild' - name: 'Add test summary' diff --git a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts index aa9d956c4..b14e8acec 100644 --- a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts +++ b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts @@ -135,6 +135,7 @@ const config: HardhatUserConfig = { '@interfold/contracts/contracts/test/MockCiphernodeRegistry.sol', '@interfold/contracts/contracts/test/MockComputeProvider.sol', '@interfold/contracts/contracts/test/MockDecryptionVerifier.sol', + '@interfold/contracts/contracts/test/MockDkgFoldAttestationVerifier.sol', '@interfold/contracts/contracts/test/MockPkVerifier.sol', '@interfold/contracts/contracts/test/MockE3Program.sol', '@interfold/contracts/contracts/test/MockSlashingVerifier.sol', diff --git a/templates/default/hardhat.config.ts b/templates/default/hardhat.config.ts index 1aa00a599..7d74fab3a 100644 --- a/templates/default/hardhat.config.ts +++ b/templates/default/hardhat.config.ts @@ -131,6 +131,7 @@ const config: HardhatUserConfig = { '@interfold/contracts/contracts/test/MockCiphernodeRegistry.sol', '@interfold/contracts/contracts/test/MockComputeProvider.sol', '@interfold/contracts/contracts/test/MockDecryptionVerifier.sol', + '@interfold/contracts/contracts/test/MockDkgFoldAttestationVerifier.sol', '@interfold/contracts/contracts/test/MockE3Program.sol', '@interfold/contracts/contracts/test/MockPkVerifier.sol', '@interfold/contracts/contracts/test/MockSlashingVerifier.sol', diff --git a/tests/integration/lib/prebuild.sh b/tests/integration/lib/prebuild.sh index 79caf5ba0..356d751c5 100755 --- a/tests/integration/lib/prebuild.sh +++ b/tests/integration/lib/prebuild.sh @@ -15,17 +15,17 @@ echo "" echo "FINISHED PREBUILDING BINARIES" echo "" -if [[ "${FULL_PROOF_AGGREGATION:-false}" == "true" ]]; then - echo "" - echo "BUILDING ZK CIRCUITS + ON-CHAIN VERIFIERS (proof aggregation enabled)..." - echo "" +echo "" +echo "BUILDING SOURCE-ALIGNED ZK CIRCUITS..." +echo "" - # Nodes must prove with the same VKs as deployed Honk verifiers. `interfold noir setup` - # otherwise installs the release tarball (crates/zk-prover/versions.json), which can - # disagree with locally rebuilt circuits/verifiers after `build:circuits`. - rm -rf "${INTEGRATION_NOIR}/circuits" - mkdir -p "${INTEGRATION_NOIR}/circuits" +# The ciphernode always generates leaf proofs, even when recursive aggregation +# is skipped. Never let integration tests combine current Rust witnesses with +# the older release circuit ABI. +rm -rf "${INTEGRATION_NOIR}/circuits" +mkdir -p "${INTEGRATION_NOIR}/circuits" "${INTEGRATION_NOIR}/bin" +if [[ "${FULL_PROOF_AGGREGATION:-false}" == "true" ]]; then (cd "$ROOT_DIR" && pnpm build:circuits --preset insecure-512 -o "${INTEGRATION_NOIR}/circuits") # `--check`: verify the committed Honk Solidity verifiers in # packages/interfold-contracts/contracts/verifiers/bfv/honk/ match the @@ -33,22 +33,33 @@ if [[ "${FULL_PROOF_AGGREGATION:-false}" == "true" ]]; then # silently rewriting committed contracts mid-test. If this errors, run # `pnpm generate:verifiers --write` and commit the diff. (cd "$ROOT_DIR" && pnpm generate:verifiers --check --no-compile --no-clean-targets) +else + # C5/C7 final aggregation is skipped, but DKG and decryption leaf proofs + # still execute. Build only those two source groups for the fast CI profile. + (cd "$ROOT_DIR" && pnpm build:circuits --preset insecure-512 --group dkg,threshold -o "${INTEGRATION_NOIR}/circuits") +fi - if ! command -v jq >/dev/null 2>&1; then - echo "jq is required to pin noir/version.json for integration ZK fixtures" >&2 - exit 1 - fi - REQUIRED_BB="$(jq -r '.required_bb_version' "$VERSIONS_JSON")" - REQUIRED_CIRCUITS="$(jq -r '.required_circuits_version' "$VERSIONS_JSON")" - jq -n \ - --arg bb "$REQUIRED_BB" \ - --arg circuits "$REQUIRED_CIRCUITS" \ - '{bb_version: $bb, circuits_version: $circuits}' \ - > "${INTEGRATION_NOIR}/version.json" - - echo "Staged circuits under ${INTEGRATION_NOIR}/circuits/insecure-512" - echo "Pinned noir version.json (bb=${REQUIRED_BB}, circuits=${REQUIRED_CIRCUITS})" - echo "" - echo "FINISHED BUILDING ZK CIRCUITS + VERIFIERS" - echo "" +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required to pin noir/version.json for integration ZK fixtures" >&2 + exit 1 fi +if ! command -v bb >/dev/null 2>&1; then + echo "bb is required to build source-aligned integration circuits" >&2 + exit 1 +fi + +REQUIRED_BB="$(jq -r '.required_bb_version' "$VERSIONS_JSON")" +REQUIRED_CIRCUITS="$(jq -r '.required_circuits_version' "$VERSIONS_JSON")" +jq -n \ + --arg bb "$REQUIRED_BB" \ + --arg circuits "$REQUIRED_CIRCUITS" \ + '{bb_version: $bb, circuits_version: $circuits}' \ + > "${INTEGRATION_NOIR}/version.json" +cp "$(command -v bb)" "${INTEGRATION_NOIR}/bin/bb" +chmod +x "${INTEGRATION_NOIR}/bin/bb" + +echo "Staged circuits under ${INTEGRATION_NOIR}/circuits/insecure-512" +echo "Pinned noir version.json (bb=${REQUIRED_BB}, circuits=${REQUIRED_CIRCUITS})" +echo "" +echo "FINISHED BUILDING SOURCE-ALIGNED ZK CIRCUITS" +echo "" From 9dbfa508e7dc46b49541be47f7908f3debcdf51d Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:45:50 +0500 Subject: [PATCH 31/52] docs(protocol): align lifecycle traces with audit fixes --- agent/flow-trace/00_INDEX.md | 14 ++-- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 4 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 2 + .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 83 ++++++++++++------- .../06_DEACTIVATION_AND_COMPLETION.md | 17 ++-- 5 files changed, 72 insertions(+), 48 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 775c95365..2e7291bae 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -149,10 +149,10 @@ │ │ (fault, penalties) │ │ (USDC wrapper) │ │ └─────────┬──────────┘ └──────────────────┘ │ │ - │ │ escrowSlashedFundsToRefund: - │ │ BondingRegistry.redirectSlashedTicketFunds - │ │ → ticketToken.payout(refundMgr, USDC) - │ │ Interfold.escrowSlashedFunds + │ │ retryable reserved-fund route: + │ │ BondingRegistry.reserveSlashedTicketFunds + │ │ → redirectReservedSlashedTicketFunds + │ │ → Interfold.escrowSlashedFunds │ ▼ ├────→ E3Program (validate, verify computation) ├────→ DecryptionVerifier (verify plaintext) @@ -187,7 +187,7 @@ _Found during source-code cross-referencing of these trace documents._ | # | Concern | Severity | Detail | | --- | --------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | **Deregister-before-slash race** | Accepted | SlashingManager Lane B (evidence+appeal) has a window during which the operator can deregister and claim their exit. If they do, the slash executes against 0 funds. The contract comments acknowledge this as an accepted tradeoff for the appeal window design. | +| 1 | **Deregister-before-slash race** | Resolved | One unresolved-proposal counter covers both lanes. Every authorized current or retained historical slashing manager participates in the BondingRegistry exit gate, so rotation cannot release collateral belonging to an in-flight E3. Ticket withdrawal, license unbonding, deregistration, and exit claims remain blocked until execution, upheld appeal, or permissionless expiry terminates the proposal. | | 2 | **Committee publication decentralized** | Resolved | `publishCommittee()` is permissionless. Off-chain role selection chooses the active aggregator, while on-chain C5 proof verification and the single-publish guard prevent invalid or duplicate committee publication. | | 3 | **`gracePeriod` is dead code** | Medium | `gracePeriod` is stored and validated during config updates but never actually used in any timeout check. Either the deadlines already bake in sufficient buffer, or this is a missing feature. | | 4 | **`activate` CLI command is misleading** | Low | Named "activate" but actually calls "register" — will fail for already-registered operators. There's no standalone way to trigger re-evaluation of active status; instead, `_updateOperatorStatus()` runs automatically inside `addTicketBalance()`, `bondLicense()`, etc. | @@ -213,11 +213,11 @@ _Found during source-code cross-referencing of these trace documents._ | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | Governance may update ticket price, license thresholds, and minimum-ticket policy. Each effective update advances a policy version and invalidates cached activity in O(1); registered operators fail closed until a permissionless refresh re-evaluates them under the new version, keeping on-chain counts and Rust sortition state synchronized. | | 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | -| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, bad gap consumption, or any change to Interfold's hard-coded pricing slots; baseline creation is a separate explicit maintainer command. | +| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, or bad gap consumption; baseline creation is a separate explicit maintainer command. Initial pricing is now passed as a typed initializer struct and written through the same validated setter path, with no hard-coded storage-slot writer. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | -| 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Fee-token refunds never absorb or relabel slash amounts, arbitrary-token orphan withdrawal is removed, and every outbound transfer preserves a protected per-token slash liability. | +| 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Matching fee-token slashes fill the requester’s exact original-payment shortfall before rewarding honest nodes; different-token slashes go to the requester as priority compensation without asserting oracle-free equivalence. Fee-token refunds never relabel slash amounts, and every outbound transfer preserves a protected per-token liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | | 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | | 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 1ec62c21b..72a03b62c 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -54,9 +54,9 @@ Requester calls: Interfold.request({ │ ├─ Snapshot Interfold dependencies for this E3: │ │ registry, refund manager, and slashing manager │ │ → later global rotations apply only to new requests -│ ├─ snapshottedRefundManager.snapshotE3Policy(e3Id) +│ ├─ snapshottedRefundManager.snapshotE3Policy(e3Id, registry) │ │ → freezes refund/slash allocation, treasury, policy version, -│ │ and the request-time Interfold authorized to settle this E3 +│ │ request-time Interfold, and request-time committee registry │ ├─ seed = uint256(keccak256(block.prevrandao, e3Id)) │ │ → On chains without `prevrandao`, the value is still deterministic │ │ per-block; downstream sortition relies on the per-E3 snapshot of diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 62445748e..aacb759e5 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -640,6 +640,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected ├─ Encodes the DkgAggregator proof in production ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C5 proof as a │ mock-verifier placeholder; this does not bypass contract verification + │ and every node in a test swarm must use the same flag value └─ Calls contract.publishCommittee(e3_id, publicKey, pkCommitment, proof) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ @@ -910,6 +911,7 @@ InterfoldSolReader decodes CiphertextOutputPublished event ├─ Encodes the final DecryptionAggregator proof in production ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C7 proof as a │ mock-verifier placeholder; this does not bypass contract verification + │ and every node in a test swarm must use the same flag value └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) │ │ ┌─── ON-CHAIN (Interfold.sol) ─────────────────────────┐ diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index e17d9bed1..84982f98b 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -146,8 +146,6 @@ Anyone calls: Interfold.processE3Failure(e3Id) │ │ │ → A pending entry matching paymentToken may settle │ │ │ │ now; other tokens remain permissionlessly │ │ │ │ settleable without being relabeled │ -│ │ │ → Legacy untyped pending state is migrated only │ -│ │ │ to the E3's historically recorded fee token │ │ │ │ │ │ │ │ M-09: snapshot the base fee-token per-node payout: │ │ │ │ if honestNodeCount > 0: │ @@ -218,36 +216,40 @@ Scenario: E3 fails at KeyPublished stage (compute timeout) Protocol fee: 5% → protocolAmount = 50,000 Each honest node claims: 400,000 / 3 = 133,333 - Last honest node claims: 133,333 + 1 (dust) = 133,334 + The 1-unit division dust is credited to the treasury pull ledger Requester claims: 550,000 - Treasury receives: 50,000 (immediately) + Treasury claims: 50,001 ``` ### Refund Example (With Slashed Funds) ``` Same scenario as above, then 2 nodes are slashed for 300,000 units of -TICKET-USD each (which may be a different token/decimal scheme from USDC): +USDC each: Before slash: requesterAmount = 550,000 honestNodeAmount = 400,000 originalPayment = 1,000,000 - Each slash is recorded as (e3Id, TICKET-USD, 300,000). - Failure compensation excludes the 5% protocol weight: - requester weight = 55 / (55 + 40) - honest-node weight = 40 / (55 + 40) + Requester fee-token shortfall: 1,000,000 - 550,000 = 450,000. + The first 300,000 slash is credited entirely to the requester. + From the second 300,000 slash: + requester receives the remaining 150,000 shortfall + honest nodes receive the 150,000 surplus Final: - Base USDC claims remain: requester 550,000; nodes 400,000 total - Separate TICKET-USD claims: - requester: 347,368 - honest nodes: 252,632 total (dust assigned deterministically) + Base USDC claims remain: requester 550,000; nodes 400,000 total. + Separate slashed-USDC claims: + requester: 450,000 + honest nodes: 150,000 total (dust assigned deterministically) Treasury base USDC credit: 50,000 - No TICKET-USD amount is compared to or relabeled as USDC. + If the slash asset were TICKET-USD instead, the whole TICKET-USD amount + would be credited to the requester as priority compensation. It would not + be described as filling the numerical USDC shortfall because no trusted + conversion price exists. ``` --- @@ -809,12 +811,19 @@ settleSlashedFunds(e3Id, actualToken): │ ├─ Read and clear _pendingSlashedByToken[e3Id][actualToken] │ -├─ If there are no honest nodes: credit the whole amount to requester +├─ If actualToken differs from the E3 fee token: +│ credit the whole amount to the requester as priority compensation +│ → Do not claim that unrelated token units fill an exact fee-token gap │ -├─ Otherwise split with dimensionless failure-stage work weights: -│ toRequester = amount * workRemainingBps / -│ (workRemainingBps + workCompletedBps) +├─ Otherwise, for a matching fee token: +│ originalGap = originalPayment - requesterAmount +│ remainingGap = originalGap - requester compensation already credited +│ toRequester = min(amount, remainingGap) │ toHonestNodes = amount - toRequester +│ → Cumulative same-token settlement prevents later slashes from +│ refilling the same requester shortfall +│ → If no active honest node exists, credit the post-cap residual to +│ the snapshotted treasury rather than creating a requester windfall │ ├─ Credit _pendingSlashedClaims[e3Id][actualToken][recipient] │ → Base fee-token RefundDistribution fields never change @@ -825,25 +834,27 @@ settleSlashedFunds(e3Id, actualToken): Design rationale: The protocol cannot compare or cap amounts across unrelated token units - without a trusted conversion price. A dimensionless stage-weight split - preserves the intended requester/honest-node priorities while every - recipient claims the exact asset that was slashed. + without a trusted conversion price. Matching fee-token slashes can fill the + requester's exact shortfall before rewarding honest nodes. Different-token + slashes preserve their denomination and go to the requester first without + asserting cross-token economic equivalence. ``` ### Slashed Funds Distribution (Success Path): distributeSlashedFundsOnSuccess() ``` -distributeSlashedFundsOnSuccess(e3Id, activeNodes, paymentToken): +distributeSlashedFundsOnSuccess(e3Id, paymentToken): │ ├─ Called by Interfold._distributeRewards() when E3 completes successfully │ -├─ Store the activeNodes set and mark success settlement ready -├─ Legacy untyped escrow may migrate only to the recorded paymentToken +├─ Mark success settlement ready ├─ Every explicitly recorded token settles independently via │ settleSlashedFunds(e3Id, actualToken) │ ├─ Load the immutable E3PolicySnapshot captured by Interfold.request -│ (allocation, treasury, policy version) +│ (allocation, treasury, Interfold, registry, policy version) +├─ Read activeNodes from the request-time registry at settlement time +│ → Expelled nodes cannot receive a later slash-funded bonus ├─ Split using snapshot.allocation.successSlashedNodeBps (default 5000): │ toNodes = escrowed * successSlashedNodeBps / 10000 │ toTreasury = escrowed - toNodes @@ -888,9 +899,14 @@ Every slash and settlement route resolves the dependency graph frozen when the E - `SlashingManager` uses the per-E3 bonding registry, ciphernode registry, Interfold, and refund manager for attestations, penalties, expulsion, failure callbacks, and fund routing. - `E3RefundManager` accepts lifecycle calls from the Interfold recorded in the E3 policy snapshot. +- `E3RefundManager` reads slash recipients from the committee registry recorded in that snapshot. +- `BondingRegistry` retains replaced slashing managers as authorized until governance explicitly + revokes them, so an old manager can finish snapshotted penalties and remains part of the exit gate. Admin setters update the live defaults for future requests only. Each E3 must have a complete -request-time snapshot; lifecycle calls fail closed if that invariant is not satisfied. +request-time snapshot; lifecycle calls fail closed if that invariant is not satisfied. Governance +must revoke a replaced slashing manager only after all E3s, proposals, and pending slash routes that +depend on it are terminal. ### Slashed Funds Ordering: Escrow → Terminal State Resolution @@ -921,7 +937,8 @@ Case 3: Multiple slashes on same E3 (failure) Case 4: E3 completes successfully with escrowed slashed funds → _distributeRewards calls distributeSlashedFundsOnSuccess - → Stores active nodes and enables per-token permissionless settlement + → Enables per-token permissionless settlement + → Reads active nodes from the request-time registry when each token settles → Nodes receive successSlashedNodeBps portion (default 50%) → Treasury receives the remainder ``` @@ -935,14 +952,14 @@ SlashPolicy { requiresProof: bool // Lane A (true) or Lane B (false) proofVerifier: address // verifier address (Lane A: used in policy lookup) banNode: bool // permanently ban operator - appealWindow: uint256 // seconds for appeal (Lane B only, 0 for Lane A) + appealWindow: uint256 // seconds; required for Lane B, optional for Lane A enabled: bool // policy active affectsCommittee: bool // expel from E3 committee failureReason: uint8 // FailureReason enum (0 = no E3 failure) } Constraints: -- If requiresProof: appealWindow must be 0 (atomic execution, no appeal) +- If requiresProof: appealWindow may be 0 (atomic) or > 0 (deferred challenge) - If !requiresProof: appealWindow must be > 0 (delayed execution, with appeal) - At least one penalty must be non-zero @@ -1043,7 +1060,10 @@ STEP 1: ESCROWING (always, at slash time) STEP 2a: E3 FAILS → Token-specific compensation Triggered by: terminal escrow or permissionless settleSlashedFunds Flow: settleSlashedFunds(e3Id, actualToken) - → Requester/honest-node weights come from failure-stage work BPS + → Matching fee-token slashes fill the requester shortfall first; + any surplus is divided among honest nodes + → Different-token slashes go to the requester as priority compensation + without an oracle-dependent cross-token comparison → Credits stay in actualToken; fee-token refund buckets are unchanged Claims: claimSlashedFunds(e3Id, actualToken) @@ -1166,6 +1186,9 @@ Applied audit findings: **C-05, H-05, H-06, H-07, H-09, H-10, H-24, M-14, M-15, - `BondingRegistry` reverts `OperatorUnderSlash()` on `removeTicketBalance`, `unbondLicense`, `deregisterOperator`, and `claimExits` while the gate is raised. Both active collateral and assets already queued for exit therefore remain slashable. +- That check covers every authorized current or retained historical slashing manager. Rotation + therefore cannot release collateral for an old manager's in-flight proposal. Governance revokes + an old manager only after its E3s, proposals, and pending routes are terminal. - A filed appeal cannot freeze collateral indefinitely: after the policy appeal window plus the seven-day governance resolution grace, `expireAppeal` permissionlessly upholds it and clears its gate. diff --git a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md index 7939e6c5a..8ab1a5eaa 100644 --- a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md +++ b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md @@ -163,10 +163,9 @@ publishPlaintextOutput() succeeds │ │ │ _pendingTreasury[snapshottedTreasury][token] += protocolAmount │ │ ├─ _creditRewards(e3Id, nodes, amounts, token) │ │ │ → Credits pull-payment rewards to each registered operator -│ │ ├─ e3RefundManager.distributeSlashedFundsOnSuccess( -│ │ │ e3Id, activeNodes, paymentToken -│ │ │ ) +│ │ ├─ e3RefundManager.distributeSlashedFundsOnSuccess(e3Id, paymentToken) │ │ │ → If any escrowed slashed funds exist for this E3: +│ │ │ read the currently active committee from the request-time registry │ │ │ split by successSlashedNodeBps (default 50%) │ │ │ nodes portion distributed evenly to activeNodes │ │ │ remainder sent to protocol treasury @@ -505,12 +504,12 @@ GOVERNANCE lifts ban: ## Cluster 6 Audit Addendum (deregistration & bans) -- **Deregistration is blocked while a Lane B slash is open** (H-05). - `BondingRegistry.deregisterOperator()` calls - `ISlashingManager(sm).hasOpenLaneBProposal(msg.sender)` and reverts `OperatorUnderSlash()` until - `executeSlash` or `resolveAppeal(upheld)` unwinds the open-proposal counter. Lane A is permitted - to proceed through the normal exit queue because Lane A is either atomic or closes within the H-06 - challenge window. +- **Collateral exit is blocked while a slash is open** (H-05, AUD H-03). + `BondingRegistry` checks `hasOpenSlashProposal(operator)` on every authorized current or retained + historical manager and reverts `OperatorUnderSlash()` from ticket withdrawal, license unbonding, + deregistration, and exit claims. Execution, an upheld appeal, or permissionless appeal expiry + unwinds the counter. After manager rotation, governance must retain the old manager until every + E3 and proposal that depends on it is terminal, then explicitly revoke it. - **Two-step ban** (M-14, M-15): bans now require `proposeBan` → `confirmBan` from a **distinct** signer holding `GOVERNANCE_ROLE`. `cancelBan` rescinds an unconfirmed proposal. Legacy direct-set From 191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:46:33 +0500 Subject: [PATCH 32/52] chore(contracts): refresh registry interface artifacts --- .../IBondingRegistry.json | 103 ++++++++++++++---- .../ISlashingManager.json | 20 +--- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index e3deb1ccd..f94b154b0 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -351,6 +351,25 @@ "name": "SlashedFundsWithdrawn", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "slashingManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "authorized", + "type": "bool" + } + ], + "name": "SlashingManagerAuthorizationUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -427,6 +446,38 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "authorizedSlashingManagerAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "authorizedSlashingManagerCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -659,6 +710,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "candidate", + "type": "address" + } + ], + "name": "isAuthorizedSlashingManager", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -802,24 +872,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "redirectSlashedTicketFunds", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -905,6 +957,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "oldSlashingManager", + "type": "address" + } + ], + "name": "revokeSlashingManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1222,5 +1287,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" + "buildInfoId": "solc-0_8_28-71239178664546533acc7c8081aa35389f2d5fe4" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index fc33b36ab..481d7648c 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -754,24 +754,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "e3Id", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "escrowSlashedFundsToRefund", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1450,5 +1432,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" + "buildInfoId": "solc-0_8_28-71239178664546533acc7c8081aa35389f2d5fe4" } \ No newline at end of file From 3434bd3cef795306d0afad314b3eb2b7d70d659f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:48:01 +0500 Subject: [PATCH 33/52] chore(contracts): refresh reviewed storage baselines --- .../storage-layouts/BondingRegistry.json | 150 ++++++----- .../CiphernodeRegistryOwnable.json | 128 ++++----- .../storage-layouts/E3RefundManager.json | 128 ++++----- .../audits/storage-layouts/Interfold.json | 250 +++++++++--------- 4 files changed, 343 insertions(+), 313 deletions(-) diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json index 109144116..c7d46584b 100644 --- a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -3,24 +3,24 @@ "contract": "BondingRegistry", "source": "contracts/registry/BondingRegistry.sol", "baseline": { - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", - "sourceSha256": "b358813d6fff792e03c34b2b2e8d01c1b68dd52a0b909a3161dafd991be723c4" + "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceSha256": "e19b9a9e0acc5ec0011912679d609d226db627feaf7289bdc742a974de748e11" }, "storage": [ { - "astId": 26681, + "astId": 26669, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketToken", "offset": 0, "slot": "0", - "type": "t_contract(InterfoldTicketToken)38713" + "type": "t_contract(InterfoldTicketToken)38769" }, { - "astId": 26685, + "astId": 26673, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseToken", "offset": 0, @@ -28,15 +28,15 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 26689, + "astId": 26677, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registry", "offset": 0, "slot": "2", - "type": "t_contract(ICiphernodeRegistry)21727" + "type": "t_contract(ICiphernodeRegistry)21820" }, { - "astId": 26692, + "astId": 26680, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashingManager", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_address" }, { - "astId": 26697, + "astId": 26685, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributors", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 26700, + "astId": 26688, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributorCount", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 26719, + "astId": 26707, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedFundsTreasury", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_address" }, { - "astId": 26722, + "astId": 26710, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketPrice", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_uint256" }, { - "astId": 26725, + "astId": 26713, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseRequiredBond", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_uint256" }, { - "astId": 26728, + "astId": 26716, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "minTicketBalance", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_uint256" }, { - "astId": 26731, + "astId": 26719, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitDelay", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_uint64" }, { - "astId": 26734, + "astId": 26722, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseActiveBps", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_uint256" }, { - "astId": 26737, + "astId": 26725, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "numActiveOperators", "offset": 0, @@ -116,15 +116,15 @@ "type": "t_uint256" }, { - "astId": 26757, + "astId": 26745, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operators", "offset": 0, "slot": "13", - "type": "t_mapping(t_address,t_struct(Operator)26751_storage)" + "type": "t_mapping(t_address,t_struct(Operator)26739_storage)" }, { - "astId": 26760, + "astId": 26748, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedTicketBalance", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 26763, + "astId": 26751, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedLicenseBond", "offset": 0, @@ -140,15 +140,15 @@ "type": "t_uint256" }, { - "astId": 26767, + "astId": 26755, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_exits", "offset": 0, "slot": "16", - "type": "t_struct(ExitQueueState)24565_storage" + "type": "t_struct(ExitQueueState)24653_storage" }, { - "astId": 26770, + "astId": 26758, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityConfigurationVersion", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_uint256" }, { - "astId": 26773, + "astId": 26761, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "reservedSlashedTicketBalance", "offset": 0, @@ -164,12 +164,28 @@ "type": "t_uint256" }, { - "astId": 29101, + "astId": 26765, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", - "label": "__gap", + "label": "_authorizedSlashingManagers", "offset": 0, "slot": "23", - "type": "t_array(t_uint256)50_storage" + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 26770, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "_authorizedSlashingManagerIndex", + "offset": 0, + "slot": "24", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 29247, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "__gap", + "offset": 0, + "slot": "25", + "type": "t_array(t_uint256)48_storage" } ], "types": { @@ -178,24 +194,30 @@ "label": "address", "numberOfBytes": "20" }, - "t_array(t_struct(ExitTranche)24534_storage)dyn_storage": { - "base": "t_struct(ExitTranche)24534_storage", + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(ExitTranche)24622_storage)dyn_storage": { + "base": "t_struct(ExitTranche)24622_storage", "encoding": "dynamic_array", "label": "struct ExitQueueLib.ExitTranche[]", "numberOfBytes": "32" }, - "t_array(t_uint256)50_storage": { + "t_array(t_uint256)48_storage": { "base": "t_uint256", "encoding": "inplace", - "label": "uint256[50]", - "numberOfBytes": "1600" + "label": "uint256[48]", + "numberOfBytes": "1536" }, "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICiphernodeRegistry)21727": { + "t_contract(ICiphernodeRegistry)21820": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" @@ -205,17 +227,17 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(InterfoldTicketToken)38713": { + "t_contract(InterfoldTicketToken)38769": { "encoding": "inplace", "label": "contract InterfoldTicketToken", "numberOfBytes": "20" }, - "t_mapping(t_address,t_array(t_struct(ExitTranche)24534_storage)dyn_storage)": { + "t_mapping(t_address,t_array(t_struct(ExitTranche)24622_storage)dyn_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", "numberOfBytes": "32", - "value": "t_array(t_struct(ExitTranche)24534_storage)dyn_storage" + "value": "t_array(t_struct(ExitTranche)24622_storage)dyn_storage" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", @@ -224,19 +246,19 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_struct(Operator)26751_storage)": { + "t_mapping(t_address,t_struct(Operator)26739_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct BondingRegistry.Operator)", "numberOfBytes": "32", - "value": "t_struct(Operator)26751_storage" + "value": "t_struct(Operator)26739_storage" }, - "t_mapping(t_address,t_struct(PendingAmounts)24540_storage)": { + "t_mapping(t_address,t_struct(PendingAmounts)24628_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", "numberOfBytes": "32", - "value": "t_struct(PendingAmounts)24540_storage" + "value": "t_struct(PendingAmounts)24628_storage" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -245,20 +267,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(ExitQueueState)24565_storage": { + "t_struct(ExitQueueState)24653_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitQueueState", "members": [ { - "astId": 24547, + "astId": 24635, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operatorQueues", "offset": 0, "slot": "0", - "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24534_storage)dyn_storage)" + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24622_storage)dyn_storage)" }, { - "astId": 24551, + "astId": 24639, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexTicket", "offset": 0, @@ -266,7 +288,7 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24555, + "astId": 24643, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexLicense", "offset": 0, @@ -274,15 +296,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24560, + "astId": 24648, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "pendingTotals", "offset": 0, "slot": "3", - "type": "t_mapping(t_address,t_struct(PendingAmounts)24540_storage)" + "type": "t_mapping(t_address,t_struct(PendingAmounts)24628_storage)" }, { - "astId": 24564, + "astId": 24652, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "liveTrancheCount", "offset": 0, @@ -292,12 +314,12 @@ ], "numberOfBytes": "160" }, - "t_struct(ExitTranche)24534_storage": { + "t_struct(ExitTranche)24622_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitTranche", "members": [ { - "astId": 24529, + "astId": 24617, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "unlockTimestamp", "offset": 0, @@ -305,7 +327,7 @@ "type": "t_uint64" }, { - "astId": 24531, + "astId": 24619, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -313,7 +335,7 @@ "type": "t_uint256" }, { - "astId": 24533, + "astId": 24621, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, @@ -323,12 +345,12 @@ ], "numberOfBytes": "96" }, - "t_struct(Operator)26751_storage": { + "t_struct(Operator)26739_storage": { "encoding": "inplace", "label": "struct BondingRegistry.Operator", "members": [ { - "astId": 26740, + "astId": 26728, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseBond", "offset": 0, @@ -336,7 +358,7 @@ "type": "t_uint256" }, { - "astId": 26742, + "astId": 26730, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitUnlocksAt", "offset": 0, @@ -344,7 +366,7 @@ "type": "t_uint64" }, { - "astId": 26744, + "astId": 26732, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registered", "offset": 8, @@ -352,7 +374,7 @@ "type": "t_bool" }, { - "astId": 26746, + "astId": 26734, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitRequested", "offset": 9, @@ -360,7 +382,7 @@ "type": "t_bool" }, { - "astId": 26748, + "astId": 26736, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "active", "offset": 10, @@ -368,7 +390,7 @@ "type": "t_bool" }, { - "astId": 26750, + "astId": 26738, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityVersion", "offset": 0, @@ -378,12 +400,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PendingAmounts)24540_storage": { + "t_struct(PendingAmounts)24628_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.PendingAmounts", "members": [ { - "astId": 24537, + "astId": 24625, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -391,7 +413,7 @@ "type": "t_uint256" }, { - "astId": 24539, + "astId": 24627, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json index e40b3d16c..9a00a8b31 100644 --- a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -3,32 +3,32 @@ "contract": "CiphernodeRegistryOwnable", "source": "contracts/registry/CiphernodeRegistryOwnable.sol", "baseline": { - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", + "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", "sourceSha256": "033ae30ce7a728081ca6cf34e050c970a5ed23fbab21ca81b66f4c8bd759af5e" }, "storage": [ { - "astId": 29169, + "astId": 29315, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23264" + "type": "t_contract(IInterfold)23360" }, { - "astId": 29173, + "astId": 29319, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21078" + "type": "t_contract(IBondingRegistry)21171" }, { - "astId": 29177, + "astId": 29323, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numCiphernodes", "offset": 0, @@ -36,7 +36,7 @@ "type": "t_uint256" }, { - "astId": 29180, + "astId": 29326, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "sortitionSubmissionWindow", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_uint256" }, { - "astId": 29200, + "astId": 29346, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodes", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_struct(LazyIMTData)14046_storage" }, { - "astId": 29205, + "astId": 29351, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeEnabled", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 29210, + "astId": 29356, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeTreeIndex", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_address,t_uint40)" }, { - "astId": 29215, + "astId": 29361, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "roots", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 29220, + "astId": 29366, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKeyHashes", "offset": 0, @@ -84,31 +84,31 @@ "type": "t_mapping(t_uint256,t_bytes32)" }, { - "astId": 29226, + "astId": 29372, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committees", "offset": 0, "slot": "10", - "type": "t_mapping(t_uint256,t_struct(Committee)21139_storage)" + "type": "t_mapping(t_uint256,t_struct(Committee)21232_storage)" }, { - "astId": 29230, + "astId": 29376, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashingManager", "offset": 0, "slot": "11", - "type": "t_contract(ISlashingManager)23911" + "type": "t_contract(ISlashingManager)23999" }, { - "astId": 29234, + "astId": 29380, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgFoldAttestationVerifier", "offset": 0, "slot": "12", - "type": "t_contract(IDkgFoldAttestationVerifier)21821" + "type": "t_contract(IDkgFoldAttestationVerifier)21914" }, { - "astId": 29241, + "astId": 29387, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifier", "offset": 0, @@ -116,7 +116,7 @@ "type": "t_address" }, { - "astId": 29243, + "astId": 29389, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifierAt", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint256" }, { - "astId": 29246, + "astId": 29392, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "accusationVoteValidity", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 29257, + "astId": 29403, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidity", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_uint256" }, { - "astId": 29259, + "astId": 29405, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidityAt", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_uint256" }, { - "astId": 29265, + "astId": 29411, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgPartyIds", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" }, { - "astId": 29270, + "astId": 29416, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgSkAggCommits", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29275, + "astId": 29421, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgEsmAggCommits", "offset": 0, @@ -172,15 +172,15 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29291, + "astId": 29437, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "_committeeDependencies", "offset": 0, "slot": "21", - "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29285_storage)" + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29431_storage)" }, { - "astId": 31815, + "astId": 31961, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "__gap", "offset": 0, @@ -234,32 +234,32 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21078": { + "t_contract(IBondingRegistry)21171": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IDkgFoldAttestationVerifier)21821": { + "t_contract(IDkgFoldAttestationVerifier)21914": { "encoding": "inplace", "label": "contract IDkgFoldAttestationVerifier", "numberOfBytes": "20" }, - "t_contract(IInterfold)23264": { + "t_contract(IInterfold)23360": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23911": { + "t_contract(ISlashingManager)23999": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeStage)21102": { + "t_enum(CommitteeStage)21195": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.CommitteeStage", "numberOfBytes": "1" }, - "t_enum(MemberStatus)21096": { + "t_enum(MemberStatus)21189": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.MemberStatus", "numberOfBytes": "1" @@ -271,12 +271,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_enum(MemberStatus)21096)": { + "t_mapping(t_address,t_enum(MemberStatus)21189)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", "numberOfBytes": "32", - "value": "t_enum(MemberStatus)21096" + "value": "t_enum(MemberStatus)21189" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -313,19 +313,19 @@ "numberOfBytes": "32", "value": "t_bytes32" }, - "t_mapping(t_uint256,t_struct(Committee)21139_storage)": { + "t_mapping(t_uint256,t_struct(Committee)21232_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", "numberOfBytes": "32", - "value": "t_struct(Committee)21139_storage" + "value": "t_struct(Committee)21232_storage" }, - "t_mapping(t_uint256,t_struct(CommitteeDependencies)29285_storage)": { + "t_mapping(t_uint256,t_struct(CommitteeDependencies)29431_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", "numberOfBytes": "32", - "value": "t_struct(CommitteeDependencies)29285_storage" + "value": "t_struct(CommitteeDependencies)29431_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -334,20 +334,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(Committee)21139_storage": { + "t_struct(Committee)21232_storage": { "encoding": "inplace", "label": "struct ICiphernodeRegistry.Committee", "members": [ { - "astId": 21106, + "astId": 21199, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "stage", "offset": 0, "slot": "0", - "type": "t_enum(CommitteeStage)21102" + "type": "t_enum(CommitteeStage)21195" }, { - "astId": 21108, + "astId": 21201, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "seed", "offset": 0, @@ -355,7 +355,7 @@ "type": "t_uint256" }, { - "astId": 21110, + "astId": 21203, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "requestBlock", "offset": 0, @@ -363,7 +363,7 @@ "type": "t_uint256" }, { - "astId": 21112, + "astId": 21205, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeDeadline", "offset": 0, @@ -371,7 +371,7 @@ "type": "t_uint256" }, { - "astId": 21114, + "astId": 21207, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKey", "offset": 0, @@ -379,7 +379,7 @@ "type": "t_bytes32" }, { - "astId": 21118, + "astId": 21211, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "threshold", "offset": 0, @@ -387,7 +387,7 @@ "type": "t_array(t_uint32)2_storage" }, { - "astId": 21121, + "astId": 21214, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "topNodes", "offset": 0, @@ -395,7 +395,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 21123, + "astId": 21216, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeHash", "offset": 0, @@ -403,7 +403,7 @@ "type": "t_bytes32" }, { - "astId": 21127, + "astId": 21220, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "submitted", "offset": 0, @@ -411,7 +411,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 21131, + "astId": 21224, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "scoreOf", "offset": 0, @@ -419,15 +419,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 21136, + "astId": 21229, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "memberStatus", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_enum(MemberStatus)21096)" + "type": "t_mapping(t_address,t_enum(MemberStatus)21189)" }, { - "astId": 21138, + "astId": 21231, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "activeCount", "offset": 0, @@ -437,33 +437,33 @@ ], "numberOfBytes": "384" }, - "t_struct(CommitteeDependencies)29285_storage": { + "t_struct(CommitteeDependencies)29431_storage": { "encoding": "inplace", "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", "members": [ { - "astId": 29278, + "astId": 29424, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfoldContract", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23264" + "type": "t_contract(IInterfold)23360" }, { - "astId": 29281, + "astId": 29427, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bonding", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21078" + "type": "t_contract(IBondingRegistry)21171" }, { - "astId": 29284, + "astId": 29430, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23911" + "type": "t_contract(ISlashingManager)23999" } ], "numberOfBytes": "96" diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json index 3b2e058d1..0cbed7f92 100644 --- a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -3,32 +3,32 @@ "contract": "E3RefundManager", "source": "contracts/E3RefundManager.sol", "baseline": { - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", - "sourceSha256": "a6ba37835c555d69dc3592a8b47089a2a34e5dfbbe87dfa1b95f1b318875a21b" + "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceSha256": "b53052d82326cff5eb5d756a27de0d34598237d109f1bac18ba6208ca2fc11e8" }, "storage": [ { - "astId": 15240, + "astId": 15242, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23264" + "type": "t_contract(IInterfold)23360" }, { - "astId": 15244, + "astId": 15246, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21078" + "type": "t_contract(IBondingRegistry)21171" }, { - "astId": 15247, + "astId": 15249, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -36,23 +36,23 @@ "type": "t_address" }, { - "astId": 15251, + "astId": 15253, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_workAllocation", "offset": 0, "slot": "3", - "type": "t_struct(WorkValueAllocation)21926_storage" + "type": "t_struct(WorkValueAllocation)22019_storage" }, { - "astId": 15257, + "astId": 15259, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_distributions", "offset": 0, "slot": "4", - "type": "t_mapping(t_uint256,t_struct(RefundDistribution)21960_storage)" + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)22055_storage)" }, { - "astId": 15264, + "astId": 15266, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_requesterClaimed", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" }, { - "astId": 15269, + "astId": 15271, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_claimCount", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 15274, + "astId": 15276, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodeClaimCount", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 15279, + "astId": 15281, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_totalHonestNodePaid", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 15285, + "astId": 15287, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodes", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_mapping(t_uint256,t_array(t_address)dyn_storage)" }, { - "astId": 15293, + "astId": 15295, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingTreasury", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 15300, + "astId": 15302, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_honestNodeClaimed", "offset": 0, @@ -108,15 +108,15 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" }, { - "astId": 15306, + "astId": 15308, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_e3PolicySnapshots", "offset": 0, "slot": "12", - "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21939_storage)" + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22034_storage)" }, { - "astId": 15309, + "astId": 15311, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "policyVersion", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint64" }, { - "astId": 15317, + "astId": 15319, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingSlashedByToken", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 15327, + "astId": 15329, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingSlashedClaims", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_mapping(t_uint256,t_mapping(t_contract(IERC20)4293,t_mapping(t_address,t_uint256)))" }, { - "astId": 15335, + "astId": 15337, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_pendingTrackedTreasury", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 15341, + "astId": 15343, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_tokenLiability", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, { - "astId": 15346, + "astId": 15348, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "_successSettlementReady", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_bool)" }, { - "astId": 17605, + "astId": 17656, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "__gap", "offset": 0, @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(IBondingRegistry)21078": { + "t_contract(IBondingRegistry)21171": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" @@ -205,7 +205,7 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IInterfold)23264": { + "t_contract(IInterfold)23360": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" @@ -280,19 +280,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3PolicySnapshot)21939_storage)": { + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22034_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", "numberOfBytes": "32", - "value": "t_struct(E3PolicySnapshot)21939_storage" + "value": "t_struct(E3PolicySnapshot)22034_storage" }, - "t_mapping(t_uint256,t_struct(RefundDistribution)21960_storage)": { + "t_mapping(t_uint256,t_struct(RefundDistribution)22055_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", "numberOfBytes": "32", - "value": "t_struct(RefundDistribution)21960_storage" + "value": "t_struct(RefundDistribution)22055_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -301,20 +301,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(E3PolicySnapshot)21939_storage": { + "t_struct(E3PolicySnapshot)22034_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.E3PolicySnapshot", "members": [ { - "astId": 21930, + "astId": 22023, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "allocation", "offset": 0, "slot": "0", - "type": "t_struct(WorkValueAllocation)21926_storage" + "type": "t_struct(WorkValueAllocation)22019_storage" }, { - "astId": 21932, + "astId": 22025, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -322,7 +322,7 @@ "type": "t_address" }, { - "astId": 21934, + "astId": 22027, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, @@ -330,30 +330,38 @@ "type": "t_address" }, { - "astId": 21936, + "astId": 22029, + "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", + "label": "registry", + "offset": 0, + "slot": "3", + "type": "t_address" + }, + { + "astId": 22031, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "version", "offset": 20, - "slot": "2", + "slot": "3", "type": "t_uint64" }, { - "astId": 21938, + "astId": 22033, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "initialized", "offset": 28, - "slot": "2", + "slot": "3", "type": "t_bool" } ], - "numberOfBytes": "96" + "numberOfBytes": "128" }, - "t_struct(RefundDistribution)21960_storage": { + "t_struct(RefundDistribution)22055_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.RefundDistribution", "members": [ { - "astId": 21942, + "astId": 22037, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "requesterAmount", "offset": 0, @@ -361,7 +369,7 @@ "type": "t_uint256" }, { - "astId": 21944, + "astId": 22039, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeAmount", "offset": 0, @@ -369,7 +377,7 @@ "type": "t_uint256" }, { - "astId": 21946, + "astId": 22041, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolAmount", "offset": 0, @@ -377,7 +385,7 @@ "type": "t_uint256" }, { - "astId": 21948, + "astId": 22043, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "totalSlashed", "offset": 0, @@ -385,7 +393,7 @@ "type": "t_uint256" }, { - "astId": 21950, + "astId": 22045, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeCount", "offset": 0, @@ -393,7 +401,7 @@ "type": "t_uint256" }, { - "astId": 21952, + "astId": 22047, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "calculated", "offset": 0, @@ -401,7 +409,7 @@ "type": "t_bool" }, { - "astId": 21955, + "astId": 22050, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "feeToken", "offset": 1, @@ -409,7 +417,7 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 21957, + "astId": 22052, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "originalPayment", "offset": 0, @@ -417,7 +425,7 @@ "type": "t_uint256" }, { - "astId": 21959, + "astId": 22054, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "perNodeAmount", "offset": 0, @@ -427,12 +435,12 @@ ], "numberOfBytes": "256" }, - "t_struct(WorkValueAllocation)21926_storage": { + "t_struct(WorkValueAllocation)22019_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.WorkValueAllocation", "members": [ { - "astId": 21917, + "astId": 22010, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "committeeFormationBps", "offset": 0, @@ -440,7 +448,7 @@ "type": "t_uint16" }, { - "astId": 21919, + "astId": 22012, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "dkgBps", "offset": 2, @@ -448,7 +456,7 @@ "type": "t_uint16" }, { - "astId": 21921, + "astId": 22014, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "decryptionBps", "offset": 4, @@ -456,7 +464,7 @@ "type": "t_uint16" }, { - "astId": 21923, + "astId": 22016, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolBps", "offset": 6, @@ -464,7 +472,7 @@ "type": "t_uint16" }, { - "astId": 21925, + "astId": 22018, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "successSlashedNodeBps", "offset": 8, diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json index 2295fe2f5..2dba96856 100644 --- a/packages/interfold-contracts/audits/storage-layouts/Interfold.json +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -3,48 +3,48 @@ "contract": "Interfold", "source": "contracts/Interfold.sol", "baseline": { - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8", + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "e9b5fa84d4bbe43df694b489ab50220de508c84a", - "sourceSha256": "1fad6efd22f9160e2e91c83201a256ed3bd9aef71cb7c0dadeca34c84d964d27" + "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceSha256": "c47078c161d1661c35246109c4d67e43c9b23bba2df96a81f0bb126afa76ec1a" }, "storage": [ { - "astId": 17668, + "astId": 17719, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphernodeRegistry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21727" + "type": "t_contract(ICiphernodeRegistry)21820" }, { - "astId": 17672, + "astId": 17723, "contract": "project/contracts/Interfold.sol:Interfold", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21078" + "type": "t_contract(IBondingRegistry)21171" }, { - "astId": 17676, + "astId": 17727, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3RefundManager", "offset": 0, "slot": "2", - "type": "t_contract(IE3RefundManager)22340" + "type": "t_contract(IE3RefundManager)22436" }, { - "astId": 17680, + "astId": 17731, "contract": "project/contracts/Interfold.sol:Interfold", "label": "slashingManager", "offset": 0, "slot": "3", - "type": "t_contract(ISlashingManager)23911" + "type": "t_contract(ISlashingManager)23999" }, { - "astId": 17684, + "astId": 17735, "contract": "project/contracts/Interfold.sol:Interfold", "label": "feeToken", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 17687, + "astId": 17738, "contract": "project/contracts/Interfold.sol:Interfold", "label": "maxDuration", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 17690, + "astId": 17741, "contract": "project/contracts/Interfold.sol:Interfold", "label": "nexte3Id", "offset": 0, @@ -68,39 +68,39 @@ "type": "t_uint256" }, { - "astId": 17696, + "astId": 17747, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Programs", "offset": 0, "slot": "7", - "type": "t_mapping(t_contract(IE3Program)21907,t_bool)" + "type": "t_mapping(t_contract(IE3Program)22000,t_bool)" }, { - "astId": 17702, + "astId": 17753, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3s", "offset": 0, "slot": "8", - "type": "t_mapping(t_uint256,t_struct(E3)21867_storage)" + "type": "t_mapping(t_uint256,t_struct(E3)21960_storage)" }, { - "astId": 17708, + "astId": 17759, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifiers", "offset": 0, "slot": "9", - "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21794)" + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21887)" }, { - "astId": 17714, + "astId": 17765, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifiers", "offset": 0, "slot": "10", - "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23302)" + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23398)" }, { - "astId": 17719, + "astId": 17770, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSetRegistry", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_mapping(t_uint8,t_bytes_storage)" }, { - "astId": 17724, + "astId": 17775, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Payments", "offset": 0, @@ -116,31 +116,31 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 17730, + "astId": 17781, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Stages", "offset": 0, "slot": "13", - "type": "t_mapping(t_uint256,t_enum(E3Stage)22369)" + "type": "t_mapping(t_uint256,t_enum(E3Stage)22465)" }, { - "astId": 17736, + "astId": 17787, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Deadlines", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22401_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22497_storage)" }, { - "astId": 17742, + "astId": 17793, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3FailureReasons", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_enum(FailureReason)22385)" + "type": "t_mapping(t_uint256,t_enum(FailureReason)22481)" }, { - "astId": 17747, + "astId": 17798, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Requesters", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_mapping(t_uint256,t_address)" }, { - "astId": 17753, + "astId": 17804, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3FeeTokens", "offset": 0, @@ -156,15 +156,15 @@ "type": "t_mapping(t_uint256,t_contract(IERC20)4293)" }, { - "astId": 17761, + "astId": 17812, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeThresholds", "offset": 0, "slot": "18", - "type": "t_mapping(t_enum(CommitteeSize)22360,t_array(t_uint32)2_storage)" + "type": "t_mapping(t_enum(CommitteeSize)22456,t_array(t_uint32)2_storage)" }, { - "astId": 17766, + "astId": 17817, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3ProtocolShareBps", "offset": 0, @@ -172,7 +172,7 @@ "type": "t_mapping(t_uint256,t_uint16)" }, { - "astId": 17771, + "astId": 17822, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3ProtocolTreasury", "offset": 0, @@ -180,23 +180,23 @@ "type": "t_mapping(t_uint256,t_address)" }, { - "astId": 17775, + "astId": 17826, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_timeoutConfig", "offset": 0, "slot": "21", - "type": "t_struct(E3TimeoutConfig)22393_storage" + "type": "t_struct(E3TimeoutConfig)22489_storage" }, { - "astId": 17779, + "astId": 17830, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pricingConfig", "offset": 0, "slot": "24", - "type": "t_struct(PricingConfig)22433_storage" + "type": "t_struct(PricingConfig)22529_storage" }, { - "astId": 17789, + "astId": 17840, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_feeTokenAllowed", "offset": 0, @@ -204,7 +204,7 @@ "type": "t_mapping(t_contract(IERC20)4293,t_bool)" }, { - "astId": 17796, + "astId": 17847, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pendingRewards", "offset": 0, @@ -212,7 +212,7 @@ "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))" }, { - "astId": 17804, + "astId": 17855, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_pendingTreasury", "offset": 0, @@ -220,7 +220,7 @@ "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)4293,t_uint256))" }, { - "astId": 17817, + "astId": 17868, "contract": "project/contracts/Interfold.sol:Interfold", "label": "markFailedGracePeriod", "offset": 0, @@ -228,15 +228,15 @@ "type": "t_uint256" }, { - "astId": 17823, + "astId": 17874, "contract": "project/contracts/Interfold.sol:Interfold", "label": "_e3Dependencies", "offset": 0, "slot": "37", - "type": "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Dependencies)17865_storage)" }, { - "astId": 20540, + "astId": 20606, "contract": "project/contracts/Interfold.sol:Interfold", "label": "__gap", "offset": 0, @@ -283,27 +283,27 @@ "label": "bytes", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21078": { + "t_contract(IBondingRegistry)21171": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(ICiphernodeRegistry)21727": { + "t_contract(ICiphernodeRegistry)21820": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IDecryptionVerifier)21794": { + "t_contract(IDecryptionVerifier)21887": { "encoding": "inplace", "label": "contract IDecryptionVerifier", "numberOfBytes": "20" }, - "t_contract(IE3Program)21907": { + "t_contract(IE3Program)22000": { "encoding": "inplace", "label": "contract IE3Program", "numberOfBytes": "20" }, - "t_contract(IE3RefundManager)22340": { + "t_contract(IE3RefundManager)22436": { "encoding": "inplace", "label": "contract IE3RefundManager", "numberOfBytes": "20" @@ -313,27 +313,27 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IPkVerifier)23302": { + "t_contract(IPkVerifier)23398": { "encoding": "inplace", "label": "contract IPkVerifier", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23911": { + "t_contract(ISlashingManager)23999": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeSize)22360": { + "t_enum(CommitteeSize)22456": { "encoding": "inplace", "label": "enum IInterfold.CommitteeSize", "numberOfBytes": "1" }, - "t_enum(E3Stage)22369": { + "t_enum(E3Stage)22465": { "encoding": "inplace", "label": "enum IInterfold.E3Stage", "numberOfBytes": "1" }, - "t_enum(FailureReason)22385": { + "t_enum(FailureReason)22481": { "encoding": "inplace", "label": "enum IInterfold.FailureReason", "numberOfBytes": "1" @@ -352,23 +352,23 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21794)": { + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21887)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IDecryptionVerifier)", "numberOfBytes": "32", - "value": "t_contract(IDecryptionVerifier)21794" + "value": "t_contract(IDecryptionVerifier)21887" }, - "t_mapping(t_bytes32,t_contract(IPkVerifier)23302)": { + "t_mapping(t_bytes32,t_contract(IPkVerifier)23398)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IPkVerifier)", "numberOfBytes": "32", - "value": "t_contract(IPkVerifier)23302" + "value": "t_contract(IPkVerifier)23398" }, - "t_mapping(t_contract(IE3Program)21907,t_bool)": { + "t_mapping(t_contract(IE3Program)22000,t_bool)": { "encoding": "mapping", - "key": "t_contract(IE3Program)21907", + "key": "t_contract(IE3Program)22000", "label": "mapping(contract IE3Program => bool)", "numberOfBytes": "32", "value": "t_bool" @@ -387,9 +387,9 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_enum(CommitteeSize)22360,t_array(t_uint32)2_storage)": { + "t_mapping(t_enum(CommitteeSize)22456,t_array(t_uint32)2_storage)": { "encoding": "mapping", - "key": "t_enum(CommitteeSize)22360", + "key": "t_enum(CommitteeSize)22456", "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", "numberOfBytes": "32", "value": "t_array(t_uint32)2_storage" @@ -408,19 +408,19 @@ "numberOfBytes": "32", "value": "t_contract(IERC20)4293" }, - "t_mapping(t_uint256,t_enum(E3Stage)22369)": { + "t_mapping(t_uint256,t_enum(E3Stage)22465)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.E3Stage)", "numberOfBytes": "32", - "value": "t_enum(E3Stage)22369" + "value": "t_enum(E3Stage)22465" }, - "t_mapping(t_uint256,t_enum(FailureReason)22385)": { + "t_mapping(t_uint256,t_enum(FailureReason)22481)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.FailureReason)", "numberOfBytes": "32", - "value": "t_enum(FailureReason)22385" + "value": "t_enum(FailureReason)22481" }, "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { "encoding": "mapping", @@ -429,26 +429,26 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3)21867_storage)": { + "t_mapping(t_uint256,t_struct(E3)21960_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct E3)", "numberOfBytes": "32", - "value": "t_struct(E3)21867_storage" + "value": "t_struct(E3)21960_storage" }, - "t_mapping(t_uint256,t_struct(E3Deadlines)22401_storage)": { + "t_mapping(t_uint256,t_struct(E3Deadlines)22497_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", "numberOfBytes": "32", - "value": "t_struct(E3Deadlines)22401_storage" + "value": "t_struct(E3Deadlines)22497_storage" }, - "t_mapping(t_uint256,t_struct(E3Dependencies)17814_storage)": { + "t_mapping(t_uint256,t_struct(E3Dependencies)17865_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct Interfold.E3Dependencies)", "numberOfBytes": "32", - "value": "t_struct(E3Dependencies)17814_storage" + "value": "t_struct(E3Dependencies)17865_storage" }, "t_mapping(t_uint256,t_uint16)": { "encoding": "mapping", @@ -471,12 +471,12 @@ "numberOfBytes": "32", "value": "t_bytes_storage" }, - "t_struct(E3)21867_storage": { + "t_struct(E3)21960_storage": { "encoding": "inplace", "label": "struct E3", "members": [ { - "astId": 21834, + "astId": 21927, "contract": "project/contracts/Interfold.sol:Interfold", "label": "seed", "offset": 0, @@ -484,15 +484,15 @@ "type": "t_uint256" }, { - "astId": 21837, + "astId": 21930, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeSize", "offset": 0, "slot": "1", - "type": "t_enum(CommitteeSize)22360" + "type": "t_enum(CommitteeSize)22456" }, { - "astId": 21839, + "astId": 21932, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requestBlock", "offset": 0, @@ -500,7 +500,7 @@ "type": "t_uint256" }, { - "astId": 21843, + "astId": 21936, "contract": "project/contracts/Interfold.sol:Interfold", "label": "inputWindow", "offset": 0, @@ -508,7 +508,7 @@ "type": "t_array(t_uint256)2_storage" }, { - "astId": 21845, + "astId": 21938, "contract": "project/contracts/Interfold.sol:Interfold", "label": "encryptionSchemeId", "offset": 0, @@ -516,15 +516,15 @@ "type": "t_bytes32" }, { - "astId": 21848, + "astId": 21941, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Program", "offset": 0, "slot": "6", - "type": "t_contract(IE3Program)21907" + "type": "t_contract(IE3Program)22000" }, { - "astId": 21850, + "astId": 21943, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSet", "offset": 20, @@ -532,7 +532,7 @@ "type": "t_uint8" }, { - "astId": 21852, + "astId": 21945, "contract": "project/contracts/Interfold.sol:Interfold", "label": "customParams", "offset": 0, @@ -540,23 +540,23 @@ "type": "t_bytes_storage" }, { - "astId": 21855, + "astId": 21948, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifier", "offset": 0, "slot": "8", - "type": "t_contract(IDecryptionVerifier)21794" + "type": "t_contract(IDecryptionVerifier)21887" }, { - "astId": 21858, + "astId": 21951, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifier", "offset": 0, "slot": "9", - "type": "t_contract(IPkVerifier)23302" + "type": "t_contract(IPkVerifier)23398" }, { - "astId": 21860, + "astId": 21953, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeePublicKey", "offset": 0, @@ -564,7 +564,7 @@ "type": "t_bytes32" }, { - "astId": 21862, + "astId": 21955, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphertextOutput", "offset": 0, @@ -572,7 +572,7 @@ "type": "t_bytes32" }, { - "astId": 21864, + "astId": 21957, "contract": "project/contracts/Interfold.sol:Interfold", "label": "plaintextOutput", "offset": 0, @@ -580,7 +580,7 @@ "type": "t_bytes_storage" }, { - "astId": 21866, + "astId": 21959, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requester", "offset": 0, @@ -590,12 +590,12 @@ ], "numberOfBytes": "448" }, - "t_struct(E3Deadlines)22401_storage": { + "t_struct(E3Deadlines)22497_storage": { "encoding": "inplace", "label": "struct IInterfold.E3Deadlines", "members": [ { - "astId": 22396, + "astId": 22492, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgDeadline", "offset": 0, @@ -603,7 +603,7 @@ "type": "t_uint256" }, { - "astId": 22398, + "astId": 22494, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeDeadline", "offset": 0, @@ -611,7 +611,7 @@ "type": "t_uint256" }, { - "astId": 22400, + "astId": 22496, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionDeadline", "offset": 0, @@ -621,43 +621,43 @@ ], "numberOfBytes": "96" }, - "t_struct(E3Dependencies)17814_storage": { + "t_struct(E3Dependencies)17865_storage": { "encoding": "inplace", "label": "struct Interfold.E3Dependencies", "members": [ { - "astId": 17807, + "astId": 17858, "contract": "project/contracts/Interfold.sol:Interfold", "label": "registry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21727" + "type": "t_contract(ICiphernodeRegistry)21820" }, { - "astId": 17810, + "astId": 17861, "contract": "project/contracts/Interfold.sol:Interfold", "label": "refundManager", "offset": 0, "slot": "1", - "type": "t_contract(IE3RefundManager)22340" + "type": "t_contract(IE3RefundManager)22436" }, { - "astId": 17813, + "astId": 17864, "contract": "project/contracts/Interfold.sol:Interfold", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23911" + "type": "t_contract(ISlashingManager)23999" } ], "numberOfBytes": "96" }, - "t_struct(E3TimeoutConfig)22393_storage": { + "t_struct(E3TimeoutConfig)22489_storage": { "encoding": "inplace", "label": "struct IInterfold.E3TimeoutConfig", "members": [ { - "astId": 22388, + "astId": 22484, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgWindow", "offset": 0, @@ -665,7 +665,7 @@ "type": "t_uint256" }, { - "astId": 22390, + "astId": 22486, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeWindow", "offset": 0, @@ -673,7 +673,7 @@ "type": "t_uint256" }, { - "astId": 22392, + "astId": 22488, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionWindow", "offset": 0, @@ -683,12 +683,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PricingConfig)22433_storage": { + "t_struct(PricingConfig)22529_storage": { "encoding": "inplace", "label": "struct IInterfold.PricingConfig", "members": [ { - "astId": 22404, + "astId": 22500, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenFixedPerNode", "offset": 0, @@ -696,7 +696,7 @@ "type": "t_uint256" }, { - "astId": 22406, + "astId": 22502, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenPerEncryptionProof", "offset": 0, @@ -704,7 +704,7 @@ "type": "t_uint256" }, { - "astId": 22408, + "astId": 22504, "contract": "project/contracts/Interfold.sol:Interfold", "label": "coordinationPerPair", "offset": 0, @@ -712,7 +712,7 @@ "type": "t_uint256" }, { - "astId": 22410, + "astId": 22506, "contract": "project/contracts/Interfold.sol:Interfold", "label": "availabilityPerNodePerSec", "offset": 0, @@ -720,7 +720,7 @@ "type": "t_uint256" }, { - "astId": 22412, + "astId": 22508, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionPerNode", "offset": 0, @@ -728,7 +728,7 @@ "type": "t_uint256" }, { - "astId": 22414, + "astId": 22510, "contract": "project/contracts/Interfold.sol:Interfold", "label": "publicationBase", "offset": 0, @@ -736,7 +736,7 @@ "type": "t_uint256" }, { - "astId": 22416, + "astId": 22512, "contract": "project/contracts/Interfold.sol:Interfold", "label": "verificationPerProof", "offset": 0, @@ -744,7 +744,7 @@ "type": "t_uint256" }, { - "astId": 22418, + "astId": 22514, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolTreasury", "offset": 0, @@ -752,7 +752,7 @@ "type": "t_address" }, { - "astId": 22420, + "astId": 22516, "contract": "project/contracts/Interfold.sol:Interfold", "label": "marginBps", "offset": 20, @@ -760,7 +760,7 @@ "type": "t_uint16" }, { - "astId": 22422, + "astId": 22518, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolShareBps", "offset": 22, @@ -768,7 +768,7 @@ "type": "t_uint16" }, { - "astId": 22424, + "astId": 22520, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgUtilizationBps", "offset": 24, @@ -776,7 +776,7 @@ "type": "t_uint16" }, { - "astId": 22426, + "astId": 22522, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeUtilizationBps", "offset": 26, @@ -784,7 +784,7 @@ "type": "t_uint16" }, { - "astId": 22428, + "astId": 22524, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptUtilizationBps", "offset": 28, @@ -792,7 +792,7 @@ "type": "t_uint16" }, { - "astId": 22430, + "astId": 22526, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minCommitteeSize", "offset": 0, @@ -800,7 +800,7 @@ "type": "t_uint32" }, { - "astId": 22432, + "astId": 22528, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minThreshold", "offset": 4, From bd2d5623c9dea9c9b0e2f62827e3711068e33767 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:48:17 +0500 Subject: [PATCH 34/52] chore(contracts): align artifacts with reviewed build --- .../interfaces/IBondingRegistry.sol/IBondingRegistry.json | 2 +- .../interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json | 2 +- .../contracts/interfaces/IInterfold.sol/IInterfold.json | 2 +- .../interfaces/ISlashingManager.sol/ISlashingManager.json | 2 +- .../token/InterfoldTicketToken.sol/InterfoldTicketToken.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index f94b154b0..6c5400e29 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1287,5 +1287,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-71239178664546533acc7c8081aa35389f2d5fe4" + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index fd4ed7715..a7f59ca8e 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 7d74551e0..5ce4c74cd 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2376,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 481d7648c..aee47d83e 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1432,5 +1432,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-71239178664546533acc7c8081aa35389f2d5fe4" + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 893c81042..e7ad1f7b0 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1384,5 +1384,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-55dd7663dd02dbcbbaae0bd720ef7dedb80392a8" + "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" } \ No newline at end of file From cab0677f32f5770e2339022f7287402b96d251ce Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 16:51:21 +0500 Subject: [PATCH 35/52] test(contracts): stabilize ticket snapshot fixtures --- .../test/E3Lifecycle/E3Integration.spec.ts | 4 ++++ .../test/Slashing/CommitteeExpulsion.spec.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 717b1a2dc..bfb163781 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -123,6 +123,10 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { committeeSize: number = 0, requestToken = usdcToken, ): Promise<{ e3Id: number }> => { + // Ticket voting power is snapshotted at request timestamp - 1. EDR may + // mine consecutive setup transactions with the same timestamp, so move + // the request clock forward before taking that conservative snapshot. + await time.increase(1); const startTime = (await time.latest()) + 100; const requestParams = { diff --git a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts index 82922513a..f3155a864 100644 --- a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -131,6 +131,10 @@ describe("Committee Expulsion & Fault Tolerance", function () { } async function makeRequest(committeeSize: number = COMMITTEE_SIZE_MINIMUM) { + // Ticket voting power is snapshotted at request timestamp - 1. EDR may + // mine consecutive setup transactions with the same timestamp, so move + // the request clock forward before taking that conservative snapshot. + await time.increase(1); const startTime = (await time.latest()) + 100; const requestParams = { committeeSize, From 1f3b287945fa62ea84aefcace0a7c8cfc680d2c2 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 17:39:47 +0500 Subject: [PATCH 36/52] fix(ci): skip proof aggregation in template integration [C-02] --- crates/config/src/app_config.rs | 30 +++++++++++++++++-- templates/default/scripts/test_integration.sh | 8 +++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/config/src/app_config.rs b/crates/config/src/app_config.rs index 73ffe252c..e220d9d19 100644 --- a/crates/config/src/app_config.rs +++ b/crates/config/src/app_config.rs @@ -58,7 +58,9 @@ pub struct NodeDefinition { #[serde(default = "default_multithread_reserve_threads")] pub multithread_reserve_threads: usize, /// Max concurrent CPU-bound jobs (ZK proofs + TrBFV). When unset, defaults to all CPUs minus - /// `multithread_reserve_threads`. Override with env `E3_NODE__MULTITHREAD_CONCURRENT_JOBS`. + /// `multithread_reserve_threads`. Override the default profile with env + /// `E3_NODE__MULTITHREAD_CONCURRENT_JOBS`, or a named profile with + /// `E3_NODES____MULTITHREAD_CONCURRENT_JOBS`. pub multithread_concurrent_jobs: Option, /// Hard deadline for construction and initial synchronization. A node that cannot reach live /// protocol operation before this deadline exits non-zero instead of remaining falsely alive. @@ -509,7 +511,7 @@ pub fn load_config( let config: UnscopedAppConfig = Figment::from(Serialized::defaults(&UnscopedAppConfig::default())) .merge(Yaml::string(&loaded_yaml)) - .merge(Env::prefixed("E3_")) + .merge(Env::prefixed("E3_").split("__")) .merge(Serialized::defaults(&CliOverrides { otel, found_config_file: Some(resolved_config_path), @@ -846,6 +848,30 @@ node: Ok(()) } + #[test] + fn test_skip_proof_aggregation_can_be_enabled_for_named_node_via_env() { + Jail::expect_with(|jail| { + jail.set_env("E3_NODES__CN1__SKIP_PROOF_AGGREGATION", "true"); + + let config: UnscopedAppConfig = + Figment::from(Serialized::defaults(&UnscopedAppConfig::default())) + .merge(Env::prefixed("E3_").split("__")) + .extract() + .map_err(|err| err.to_string())?; + let config = config + .into_scoped_with_defaults( + "cn1", + &PathBuf::from("/default/data"), + &PathBuf::from("/default/config"), + &PathBuf::from("/my/cwd"), + ) + .map_err(|err| err.to_string())?; + + assert!(config.skip_proof_aggregation()); + Ok(()) + }); + } + #[test] fn test_startup_timeout_config_and_default() -> Result<()> { let configured: UnscopedAppConfig = serde_yaml::from_str( diff --git a/templates/default/scripts/test_integration.sh b/templates/default/scripts/test_integration.sh index fd5141d64..79e14edbe 100755 --- a/templates/default/scripts/test_integration.sh +++ b/templates/default/scripts/test_integration.sh @@ -4,6 +4,14 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." +# The template integration deploys mock proof verifiers. Keep recursive proof +# aggregation enabled by default for users, but skip it in this bounded CI test. +export E3_NODES__CN1__SKIP_PROOF_AGGREGATION=true +export E3_NODES__CN2__SKIP_PROOF_AGGREGATION=true +export E3_NODES__CN3__SKIP_PROOF_AGGREGATION=true +export E3_NODES__CN4__SKIP_PROOF_AGGREGATION=true +export E3_NODES__CN5__SKIP_PROOF_AGGREGATION=true + passed_message() { echo "" echo "------------------------" From f4c9ba955f437ae8c607b464c859b7809718be86 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:07:31 +0500 Subject: [PATCH 37/52] fix(contracts): sweep licence-token surplus safely [M-08] --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 25 +++++----- .../contracts/interfaces/IBondingRegistry.sol | 28 +++++++++++ .../contracts/registry/BondingRegistry.sol | 21 +++++++- .../test/Registry/BondingRegistry.spec.ts | 49 +++++++++++++++++++ 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index 051001b00..2362c7371 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -93,10 +93,13 @@ Before a node can register, it must stake two types of collateral: ``` Bonding-asset rotation is liability-gated. A replacement ticket wrapper cannot be configured while -the old wrapper has issued tickets or a payable balance, and the FOLD license token cannot change -while the bonding registry holds any of the old token. Replacement assets must be deployed -contracts; the only zero exception is the one-time license-token placeholder used to resolve the -circular FOLD/BondingRegistry deployment. +the old wrapper has issued tickets or a payable balance. The registry tracks `totalLicenseLiability` +across active FOLD bonds, queued exits, and slashed funds; it decreases only when a claim or +treasury withdrawal actually consumes an obligation. Unsolicited old-token dust is therefore +distinguishable from operator liabilities and can be sent to `slashedFundsTreasury` with +`sweepLicenseSurplus()` before rotation. The FOLD license token still cannot change until its raw +registry balance is zero. Replacement assets must be deployed contracts; the only zero exception is +the one-time license-token placeholder used to resolve the circular FOLD/BondingRegistry deployment. --- @@ -130,9 +133,10 @@ User runs: interfold ciphernode license bond --amount 50000 │ │ │ → FOLD _update can see the pre-recorded bond │ │ │ │ and enforce locked-floor accounting │ │ │ │ → FOLD tokens move from operator → contract │ -│ │ │ 4. _updateOperatorStatus(msg.sender) │ +│ │ │ 4. totalLicenseLiability += amount │ +│ │ │ 5. _updateOperatorStatus(msg.sender) │ │ │ │ → May activate if all conditions now met │ -│ │ │ 5. Emit LicenseBondUpdated(msg.sender, newBond) │ +│ │ │ 6. Emit LicenseBondUpdated(msg.sender, newBond) │ │ │ │ } │ │ │ └──────────────────────────────────────────────────────┘ │ │ @@ -360,12 +364,9 @@ User runs: interfold ciphernode license claim [--max-ticket 50] [--max-license 1 │ │ │ │ │ underlying.safeTransfer(to, amount) │ │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ │ │ -│ │ │ 3. licenseAmount = _claimLicenseExits( │ -│ │ │ msg.sender, maxLicense │ -│ │ │ ) │ -│ │ │ → Each FOLD source pays its withdrawalAddress │ -│ │ │ → Receiver callback gets (operator, amount, │ -│ │ │ sourceId) when supported │ +│ │ │ 3. if licenseAmount > 0: │ +│ │ │ totalLicenseLiability -= licenseAmount │ +│ │ │ licenseToken.safeTransfer(msg.sender, amount) │ │ │ │ → Pending FOLD is removed from totalBonded() │ │ │ │ as returned FOLD reaches the wallet │ │ │ │ } │ diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index a0a1e700e..af5e2f14f 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -168,6 +168,19 @@ interface IBondingRegistry { */ event LicenseTokenSet(address indexed licenseToken); + /** + * @notice Emitted when governance removes license tokens that are not + * backing any active bond, pending exit, or slashed-fund claim. + * @param token License token whose surplus was swept + * @param to Slashed-funds treasury that received the surplus + * @param amount Amount requested for transfer + */ + event LicenseSurplusSwept( + address indexed token, + address indexed to, + uint256 amount + ); + /** * @notice Emitted when the registry is set * @param registry Address of the registry @@ -219,6 +232,13 @@ interface IBondingRegistry { */ function getLicenseToken() external view returns (address); + /** + * @notice Total license-token obligations held by the registry. + * @dev Covers active bonds, queued exits, and slashed funds awaiting + * treasury withdrawal. + */ + function totalLicenseLiability() external view returns (uint256); + /** * @notice Get ticket token address * @return Ticket token address @@ -551,6 +571,14 @@ interface IBondingRegistry { */ function setLicenseToken(IERC20 newLicenseToken) external; + /** + * @notice Send unaccounted license-token surplus to the slashed-funds treasury. + * @dev Never transfers active bonds, queued exits, or slashed-fund liabilities. + * This is the governance path for clearing donated dust before rotation. + * @return amount Amount requested for transfer + */ + function sweepLicenseSurplus() external returns (uint256 amount); + /** * @notice Set slashed funds treasury address * @param newSlashedFundsTreasury New slashed funds treasury address diff --git a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol index 9c4d598d6..ec6004809 100644 --- a/packages/interfold-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/interfold-contracts/contracts/registry/BondingRegistry.sol @@ -170,6 +170,9 @@ contract BondingRegistry is /// @notice Maximum number of concurrently authorized slashing managers. uint256 public constant MAX_AUTHORIZED_SLASHING_MANAGERS = 32; + /// @inheritdoc IBondingRegistry + uint256 public totalLicenseLiability; + // ====================== // Modifiers // ====================== @@ -571,6 +574,7 @@ contract BondingRegistry is if (ticketClaim > 0) ticketToken.payout(msg.sender, ticketClaim); if (licenseClaim > 0) { + totalLicenseLiability -= licenseClaim; _safeTransferLicenseWithDeltaCheck(msg.sender, licenseClaim); } } @@ -870,6 +874,19 @@ contract BondingRegistry is emit LicenseTokenSet(next); } + /// @inheritdoc IBondingRegistry + function sweepLicenseSurplus() external onlyOwner returns (uint256 amount) { + IERC20 current = licenseToken; + uint256 balance = current.balanceOf(address(this)); + uint256 liabilities = totalLicenseLiability; + if (balance <= liabilities) return 0; + + amount = balance - liabilities; + address treasury = slashedFundsTreasury; + _safeTransferLicenseWithDeltaCheck(treasury, amount); + emit LicenseSurplusSwept(address(current), treasury, amount); + } + /// @inheritdoc IBondingRegistry function setRegistry(ICiphernodeRegistry newRegistry) public onlyOwner { registry = newRegistry; @@ -972,6 +989,7 @@ contract BondingRegistry is if (licenseAmount > 0) { slashedLicenseBond -= licenseAmount; + totalLicenseLiability -= licenseAmount; _safeTransferLicenseWithDeltaCheck( slashedFundsTreasury, licenseAmount @@ -1000,6 +1018,7 @@ contract BondingRegistry is uint256 actualReceived = licenseToken.balanceOf(address(this)) - balanceBefore; require(actualReceived == amount, InvalidAmount()); + totalLicenseLiability += amount; emit LicenseBondUpdated( operator, @@ -1095,5 +1114,5 @@ contract BondingRegistry is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[48] private __gap; + uint256[47] private __gap; } diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index 407e8c936..839d50db1 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -132,6 +132,9 @@ describe("BondingRegistry", function () { expect( await bondingRegistry.totalBonded(await operator1.getAddress()), ).to.equal(bondAmount); + expect(await bondingRegistry.totalLicenseLiability()).to.equal( + bondAmount, + ); }); it("reverts if amount is zero", async function () { @@ -289,6 +292,9 @@ describe("BondingRegistry", function () { bondAmount - slashAmount, ); expect(await bondingRegistry.slashedLicenseBond()).to.equal(slashAmount); + expect(await bondingRegistry.totalLicenseLiability()).to.equal( + bondAmount, + ); }); }); @@ -1360,6 +1366,49 @@ describe("BondingRegistry", function () { .withArgs(await licenseToken.getAddress(), bondAmount); }); + it("AUD-M08: sweeps donated license-token dust without touching liabilities", async function () { + const { + bondingRegistry, + licenseToken, + operator1, + treasury, + treasuryAddress, + } = await loadFixture(setup); + + const registryAddress = await bondingRegistry.getAddress(); + const dust = ethers.parseEther("1"); + await licenseToken.connect(operator1).transfer(registryAddress, dust); + + expect(await bondingRegistry.totalLicenseLiability()).to.equal(0); + + const replacement = await ( + await ethers.getContractFactory("MockFeeOnTransferToken") + ).deploy(0); + await expect( + bondingRegistry.setLicenseToken(await replacement.getAddress()), + ) + .to.be.revertedWithCustomError( + bondingRegistry, + "OutstandingAssetLiabilities", + ) + .withArgs(await licenseToken.getAddress(), dust); + + const treasuryBefore = await licenseToken.balanceOf(treasuryAddress); + await expect(bondingRegistry.sweepLicenseSurplus()) + .to.emit(bondingRegistry, "LicenseSurplusSwept") + .withArgs(await licenseToken.getAddress(), treasuryAddress, dust); + expect(await licenseToken.balanceOf(treasury)).to.equal( + treasuryBefore + dust, + ); + expect(await licenseToken.balanceOf(registryAddress)).to.equal(0); + + await expect( + bondingRegistry.setLicenseToken(await replacement.getAddress()), + ) + .to.emit(bondingRegistry, "LicenseTokenSet") + .withArgs(await replacement.getAddress()); + }); + it("AUD-M08: blocks ticket-token rotation until supply and payouts are drained", async function () { const { bondingRegistry, From 7dbe2c88405283d49ca66be6eaf7a1509b08211e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:11:59 +0500 Subject: [PATCH 38/52] chore(contracts): refresh M-08 reviewed artifacts --- .../IBondingRegistry.json | 53 +++++- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 2 +- .../storage-layouts/BondingRegistry.json | 140 +++++++------- .../CiphernodeRegistryOwnable.json | 128 ++++++------- .../storage-layouts/E3RefundManager.json | 74 ++++---- .../audits/storage-layouts/Interfold.json | 176 +++++++++--------- 9 files changed, 319 insertions(+), 260 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 6c5400e29..d85ce57bb 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -224,6 +224,31 @@ "name": "LicenseBondUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LicenseSurplusSwept", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -1204,6 +1229,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "sweepLicenseSurplus", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "ticketPrice", @@ -1249,6 +1287,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "totalLicenseLiability", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1287,5 +1338,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index a7f59ca8e..2ca9fec12 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 5ce4c74cd..f4f6c0ebc 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2376,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index aee47d83e..86d35c26d 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1432,5 +1432,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index e7ad1f7b0..a82e9edd6 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1384,5 +1384,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5" + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" } \ No newline at end of file diff --git a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json index c7d46584b..1c30ae515 100644 --- a/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json +++ b/packages/interfold-contracts/audits/storage-layouts/BondingRegistry.json @@ -3,24 +3,24 @@ "contract": "BondingRegistry", "source": "contracts/registry/BondingRegistry.sol", "baseline": { - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", - "sourceSha256": "e19b9a9e0acc5ec0011912679d609d226db627feaf7289bdc742a974de748e11" + "sourceCommit": "f4c9ba955f437ae8c607b464c859b7809718be86", + "sourceSha256": "4d982b7b2b4ef8ea7d7db4b75891007f774afe8e4ec6815b20abd12fd998c5d9" }, "storage": [ { - "astId": 26669, + "astId": 26785, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketToken", "offset": 0, "slot": "0", - "type": "t_contract(InterfoldTicketToken)38769" + "type": "t_contract(InterfoldTicketToken)38979" }, { - "astId": 26673, + "astId": 26789, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseToken", "offset": 0, @@ -28,15 +28,15 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 26677, + "astId": 26793, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registry", "offset": 0, "slot": "2", - "type": "t_contract(ICiphernodeRegistry)21820" + "type": "t_contract(ICiphernodeRegistry)21841" }, { - "astId": 26680, + "astId": 26796, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashingManager", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_address" }, { - "astId": 26685, + "astId": 26801, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributors", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 26688, + "astId": 26804, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "authorizedDistributorCount", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_uint256" }, { - "astId": 26707, + "astId": 26823, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedFundsTreasury", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_address" }, { - "astId": 26710, + "astId": 26826, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketPrice", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_uint256" }, { - "astId": 26713, + "astId": 26829, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseRequiredBond", "offset": 0, @@ -84,7 +84,7 @@ "type": "t_uint256" }, { - "astId": 26716, + "astId": 26832, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "minTicketBalance", "offset": 0, @@ -92,7 +92,7 @@ "type": "t_uint256" }, { - "astId": 26719, + "astId": 26835, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitDelay", "offset": 0, @@ -100,7 +100,7 @@ "type": "t_uint64" }, { - "astId": 26722, + "astId": 26838, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseActiveBps", "offset": 0, @@ -108,7 +108,7 @@ "type": "t_uint256" }, { - "astId": 26725, + "astId": 26841, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "numActiveOperators", "offset": 0, @@ -116,15 +116,15 @@ "type": "t_uint256" }, { - "astId": 26745, + "astId": 26861, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operators", "offset": 0, "slot": "13", - "type": "t_mapping(t_address,t_struct(Operator)26739_storage)" + "type": "t_mapping(t_address,t_struct(Operator)26855_storage)" }, { - "astId": 26748, + "astId": 26864, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedTicketBalance", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 26751, + "astId": 26867, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "slashedLicenseBond", "offset": 0, @@ -140,15 +140,15 @@ "type": "t_uint256" }, { - "astId": 26755, + "astId": 26871, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_exits", "offset": 0, "slot": "16", - "type": "t_struct(ExitQueueState)24653_storage" + "type": "t_struct(ExitQueueState)24674_storage" }, { - "astId": 26758, + "astId": 26874, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityConfigurationVersion", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_uint256" }, { - "astId": 26761, + "astId": 26877, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "reservedSlashedTicketBalance", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_uint256" }, { - "astId": 26765, + "astId": 26881, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_authorizedSlashingManagers", "offset": 0, @@ -172,7 +172,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 26770, + "astId": 26886, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "_authorizedSlashingManagerIndex", "offset": 0, @@ -180,12 +180,20 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 29247, + "astId": 26893, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", - "label": "__gap", + "label": "totalLicenseLiability", "offset": 0, "slot": "25", - "type": "t_array(t_uint256)48_storage" + "type": "t_uint256" + }, + { + "astId": 29436, + "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", + "label": "__gap", + "offset": 0, + "slot": "26", + "type": "t_array(t_uint256)47_storage" } ], "types": { @@ -200,24 +208,24 @@ "label": "address[]", "numberOfBytes": "32" }, - "t_array(t_struct(ExitTranche)24622_storage)dyn_storage": { - "base": "t_struct(ExitTranche)24622_storage", + "t_array(t_struct(ExitTranche)24643_storage)dyn_storage": { + "base": "t_struct(ExitTranche)24643_storage", "encoding": "dynamic_array", "label": "struct ExitQueueLib.ExitTranche[]", "numberOfBytes": "32" }, - "t_array(t_uint256)48_storage": { + "t_array(t_uint256)47_storage": { "base": "t_uint256", "encoding": "inplace", - "label": "uint256[48]", - "numberOfBytes": "1536" + "label": "uint256[47]", + "numberOfBytes": "1504" }, "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICiphernodeRegistry)21820": { + "t_contract(ICiphernodeRegistry)21841": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" @@ -227,17 +235,17 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(InterfoldTicketToken)38769": { + "t_contract(InterfoldTicketToken)38979": { "encoding": "inplace", "label": "contract InterfoldTicketToken", "numberOfBytes": "20" }, - "t_mapping(t_address,t_array(t_struct(ExitTranche)24622_storage)dyn_storage)": { + "t_mapping(t_address,t_array(t_struct(ExitTranche)24643_storage)dyn_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.ExitTranche[])", "numberOfBytes": "32", - "value": "t_array(t_struct(ExitTranche)24622_storage)dyn_storage" + "value": "t_array(t_struct(ExitTranche)24643_storage)dyn_storage" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", @@ -246,19 +254,19 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_struct(Operator)26739_storage)": { + "t_mapping(t_address,t_struct(Operator)26855_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct BondingRegistry.Operator)", "numberOfBytes": "32", - "value": "t_struct(Operator)26739_storage" + "value": "t_struct(Operator)26855_storage" }, - "t_mapping(t_address,t_struct(PendingAmounts)24628_storage)": { + "t_mapping(t_address,t_struct(PendingAmounts)24649_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct ExitQueueLib.PendingAmounts)", "numberOfBytes": "32", - "value": "t_struct(PendingAmounts)24628_storage" + "value": "t_struct(PendingAmounts)24649_storage" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -267,20 +275,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(ExitQueueState)24653_storage": { + "t_struct(ExitQueueState)24674_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitQueueState", "members": [ { - "astId": 24635, + "astId": 24656, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "operatorQueues", "offset": 0, "slot": "0", - "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24622_storage)dyn_storage)" + "type": "t_mapping(t_address,t_array(t_struct(ExitTranche)24643_storage)dyn_storage)" }, { - "astId": 24639, + "astId": 24660, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexTicket", "offset": 0, @@ -288,7 +296,7 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24643, + "astId": 24664, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "queueHeadIndexLicense", "offset": 0, @@ -296,15 +304,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 24648, + "astId": 24669, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "pendingTotals", "offset": 0, "slot": "3", - "type": "t_mapping(t_address,t_struct(PendingAmounts)24628_storage)" + "type": "t_mapping(t_address,t_struct(PendingAmounts)24649_storage)" }, { - "astId": 24652, + "astId": 24673, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "liveTrancheCount", "offset": 0, @@ -314,12 +322,12 @@ ], "numberOfBytes": "160" }, - "t_struct(ExitTranche)24622_storage": { + "t_struct(ExitTranche)24643_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.ExitTranche", "members": [ { - "astId": 24617, + "astId": 24638, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "unlockTimestamp", "offset": 0, @@ -327,7 +335,7 @@ "type": "t_uint64" }, { - "astId": 24619, + "astId": 24640, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -335,7 +343,7 @@ "type": "t_uint256" }, { - "astId": 24621, + "astId": 24642, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, @@ -345,12 +353,12 @@ ], "numberOfBytes": "96" }, - "t_struct(Operator)26739_storage": { + "t_struct(Operator)26855_storage": { "encoding": "inplace", "label": "struct BondingRegistry.Operator", "members": [ { - "astId": 26728, + "astId": 26844, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseBond", "offset": 0, @@ -358,7 +366,7 @@ "type": "t_uint256" }, { - "astId": 26730, + "astId": 26846, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitUnlocksAt", "offset": 0, @@ -366,7 +374,7 @@ "type": "t_uint64" }, { - "astId": 26732, + "astId": 26848, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "registered", "offset": 8, @@ -374,7 +382,7 @@ "type": "t_bool" }, { - "astId": 26734, + "astId": 26850, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "exitRequested", "offset": 9, @@ -382,7 +390,7 @@ "type": "t_bool" }, { - "astId": 26736, + "astId": 26852, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "active", "offset": 10, @@ -390,7 +398,7 @@ "type": "t_bool" }, { - "astId": 26738, + "astId": 26854, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "eligibilityVersion", "offset": 0, @@ -400,12 +408,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PendingAmounts)24628_storage": { + "t_struct(PendingAmounts)24649_storage": { "encoding": "inplace", "label": "struct ExitQueueLib.PendingAmounts", "members": [ { - "astId": 24625, + "astId": 24646, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "ticketAmount", "offset": 0, @@ -413,7 +421,7 @@ "type": "t_uint256" }, { - "astId": 24627, + "astId": 24648, "contract": "project/contracts/registry/BondingRegistry.sol:BondingRegistry", "label": "licenseAmount", "offset": 0, diff --git a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json index 9a00a8b31..f9492f062 100644 --- a/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json +++ b/packages/interfold-contracts/audits/storage-layouts/CiphernodeRegistryOwnable.json @@ -3,32 +3,32 @@ "contract": "CiphernodeRegistryOwnable", "source": "contracts/registry/CiphernodeRegistryOwnable.sol", "baseline": { - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceCommit": "f4c9ba955f437ae8c607b464c859b7809718be86", "sourceSha256": "033ae30ce7a728081ca6cf34e050c970a5ed23fbab21ca81b66f4c8bd759af5e" }, "storage": [ { - "astId": 29315, + "astId": 29504, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23360" + "type": "t_contract(IInterfold)23381" }, { - "astId": 29319, + "astId": 29508, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21171" + "type": "t_contract(IBondingRegistry)21192" }, { - "astId": 29323, + "astId": 29512, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "numCiphernodes", "offset": 0, @@ -36,7 +36,7 @@ "type": "t_uint256" }, { - "astId": 29326, + "astId": 29515, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "sortitionSubmissionWindow", "offset": 0, @@ -44,7 +44,7 @@ "type": "t_uint256" }, { - "astId": 29346, + "astId": 29535, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodes", "offset": 0, @@ -52,7 +52,7 @@ "type": "t_struct(LazyIMTData)14046_storage" }, { - "astId": 29351, + "astId": 29540, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeEnabled", "offset": 0, @@ -60,7 +60,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 29356, + "astId": 29545, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "ciphernodeTreeIndex", "offset": 0, @@ -68,7 +68,7 @@ "type": "t_mapping(t_address,t_uint40)" }, { - "astId": 29361, + "astId": 29550, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "roots", "offset": 0, @@ -76,7 +76,7 @@ "type": "t_mapping(t_uint256,t_uint256)" }, { - "astId": 29366, + "astId": 29555, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKeyHashes", "offset": 0, @@ -84,31 +84,31 @@ "type": "t_mapping(t_uint256,t_bytes32)" }, { - "astId": 29372, + "astId": 29561, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committees", "offset": 0, "slot": "10", - "type": "t_mapping(t_uint256,t_struct(Committee)21232_storage)" + "type": "t_mapping(t_uint256,t_struct(Committee)21253_storage)" }, { - "astId": 29376, + "astId": 29565, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashingManager", "offset": 0, "slot": "11", - "type": "t_contract(ISlashingManager)23999" + "type": "t_contract(ISlashingManager)24020" }, { - "astId": 29380, + "astId": 29569, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgFoldAttestationVerifier", "offset": 0, "slot": "12", - "type": "t_contract(IDkgFoldAttestationVerifier)21914" + "type": "t_contract(IDkgFoldAttestationVerifier)21935" }, { - "astId": 29387, + "astId": 29576, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifier", "offset": 0, @@ -116,7 +116,7 @@ "type": "t_address" }, { - "astId": 29389, + "astId": 29578, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingDkgFoldAttestationVerifierAt", "offset": 0, @@ -124,7 +124,7 @@ "type": "t_uint256" }, { - "astId": 29392, + "astId": 29581, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "accusationVoteValidity", "offset": 0, @@ -132,7 +132,7 @@ "type": "t_uint256" }, { - "astId": 29403, + "astId": 29592, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidity", "offset": 0, @@ -140,7 +140,7 @@ "type": "t_uint256" }, { - "astId": 29405, + "astId": 29594, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "pendingAccusationVoteValidityAt", "offset": 0, @@ -148,7 +148,7 @@ "type": "t_uint256" }, { - "astId": 29411, + "astId": 29600, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgPartyIds", "offset": 0, @@ -156,7 +156,7 @@ "type": "t_mapping(t_uint256,t_array(t_uint256)dyn_storage)" }, { - "astId": 29416, + "astId": 29605, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgSkAggCommits", "offset": 0, @@ -164,7 +164,7 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29421, + "astId": 29610, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "dkgEsmAggCommits", "offset": 0, @@ -172,15 +172,15 @@ "type": "t_mapping(t_uint256,t_array(t_bytes32)dyn_storage)" }, { - "astId": 29437, + "astId": 29626, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "_committeeDependencies", "offset": 0, "slot": "21", - "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29431_storage)" + "type": "t_mapping(t_uint256,t_struct(CommitteeDependencies)29620_storage)" }, { - "astId": 31961, + "astId": 32150, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "__gap", "offset": 0, @@ -234,32 +234,32 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21171": { + "t_contract(IBondingRegistry)21192": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(IDkgFoldAttestationVerifier)21914": { + "t_contract(IDkgFoldAttestationVerifier)21935": { "encoding": "inplace", "label": "contract IDkgFoldAttestationVerifier", "numberOfBytes": "20" }, - "t_contract(IInterfold)23360": { + "t_contract(IInterfold)23381": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23999": { + "t_contract(ISlashingManager)24020": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeStage)21195": { + "t_enum(CommitteeStage)21216": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.CommitteeStage", "numberOfBytes": "1" }, - "t_enum(MemberStatus)21189": { + "t_enum(MemberStatus)21210": { "encoding": "inplace", "label": "enum ICiphernodeRegistry.MemberStatus", "numberOfBytes": "1" @@ -271,12 +271,12 @@ "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_address,t_enum(MemberStatus)21189)": { + "t_mapping(t_address,t_enum(MemberStatus)21210)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => enum ICiphernodeRegistry.MemberStatus)", "numberOfBytes": "32", - "value": "t_enum(MemberStatus)21189" + "value": "t_enum(MemberStatus)21210" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", @@ -313,19 +313,19 @@ "numberOfBytes": "32", "value": "t_bytes32" }, - "t_mapping(t_uint256,t_struct(Committee)21232_storage)": { + "t_mapping(t_uint256,t_struct(Committee)21253_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct ICiphernodeRegistry.Committee)", "numberOfBytes": "32", - "value": "t_struct(Committee)21232_storage" + "value": "t_struct(Committee)21253_storage" }, - "t_mapping(t_uint256,t_struct(CommitteeDependencies)29431_storage)": { + "t_mapping(t_uint256,t_struct(CommitteeDependencies)29620_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct CiphernodeRegistryOwnable.CommitteeDependencies)", "numberOfBytes": "32", - "value": "t_struct(CommitteeDependencies)29431_storage" + "value": "t_struct(CommitteeDependencies)29620_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -334,20 +334,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(Committee)21232_storage": { + "t_struct(Committee)21253_storage": { "encoding": "inplace", "label": "struct ICiphernodeRegistry.Committee", "members": [ { - "astId": 21199, + "astId": 21220, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "stage", "offset": 0, "slot": "0", - "type": "t_enum(CommitteeStage)21195" + "type": "t_enum(CommitteeStage)21216" }, { - "astId": 21201, + "astId": 21222, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "seed", "offset": 0, @@ -355,7 +355,7 @@ "type": "t_uint256" }, { - "astId": 21203, + "astId": 21224, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "requestBlock", "offset": 0, @@ -363,7 +363,7 @@ "type": "t_uint256" }, { - "astId": 21205, + "astId": 21226, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeDeadline", "offset": 0, @@ -371,7 +371,7 @@ "type": "t_uint256" }, { - "astId": 21207, + "astId": 21228, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "publicKey", "offset": 0, @@ -379,7 +379,7 @@ "type": "t_bytes32" }, { - "astId": 21211, + "astId": 21232, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "threshold", "offset": 0, @@ -387,7 +387,7 @@ "type": "t_array(t_uint32)2_storage" }, { - "astId": 21214, + "astId": 21235, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "topNodes", "offset": 0, @@ -395,7 +395,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 21216, + "astId": 21237, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "committeeHash", "offset": 0, @@ -403,7 +403,7 @@ "type": "t_bytes32" }, { - "astId": 21220, + "astId": 21241, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "submitted", "offset": 0, @@ -411,7 +411,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 21224, + "astId": 21245, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "scoreOf", "offset": 0, @@ -419,15 +419,15 @@ "type": "t_mapping(t_address,t_uint256)" }, { - "astId": 21229, + "astId": 21250, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "memberStatus", "offset": 0, "slot": "10", - "type": "t_mapping(t_address,t_enum(MemberStatus)21189)" + "type": "t_mapping(t_address,t_enum(MemberStatus)21210)" }, { - "astId": 21231, + "astId": 21252, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "activeCount", "offset": 0, @@ -437,33 +437,33 @@ ], "numberOfBytes": "384" }, - "t_struct(CommitteeDependencies)29431_storage": { + "t_struct(CommitteeDependencies)29620_storage": { "encoding": "inplace", "label": "struct CiphernodeRegistryOwnable.CommitteeDependencies", "members": [ { - "astId": 29424, + "astId": 29613, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "interfoldContract", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23360" + "type": "t_contract(IInterfold)23381" }, { - "astId": 29427, + "astId": 29616, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "bonding", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21171" + "type": "t_contract(IBondingRegistry)21192" }, { - "astId": 29430, + "astId": 29619, "contract": "project/contracts/registry/CiphernodeRegistryOwnable.sol:CiphernodeRegistryOwnable", "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23999" + "type": "t_contract(ISlashingManager)24020" } ], "numberOfBytes": "96" diff --git a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json index 0cbed7f92..7e6a110b0 100644 --- a/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json +++ b/packages/interfold-contracts/audits/storage-layouts/E3RefundManager.json @@ -3,11 +3,11 @@ "contract": "E3RefundManager", "source": "contracts/E3RefundManager.sol", "baseline": { - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceCommit": "f4c9ba955f437ae8c607b464c859b7809718be86", "sourceSha256": "b53052d82326cff5eb5d756a27de0d34598237d109f1bac18ba6208ca2fc11e8" }, "storage": [ @@ -17,7 +17,7 @@ "label": "interfold", "offset": 0, "slot": "0", - "type": "t_contract(IInterfold)23360" + "type": "t_contract(IInterfold)23381" }, { "astId": 15246, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21171" + "type": "t_contract(IBondingRegistry)21192" }, { "astId": 15249, @@ -41,7 +41,7 @@ "label": "_workAllocation", "offset": 0, "slot": "3", - "type": "t_struct(WorkValueAllocation)22019_storage" + "type": "t_struct(WorkValueAllocation)22040_storage" }, { "astId": 15259, @@ -49,7 +49,7 @@ "label": "_distributions", "offset": 0, "slot": "4", - "type": "t_mapping(t_uint256,t_struct(RefundDistribution)22055_storage)" + "type": "t_mapping(t_uint256,t_struct(RefundDistribution)22076_storage)" }, { "astId": 15266, @@ -113,7 +113,7 @@ "label": "_e3PolicySnapshots", "offset": 0, "slot": "12", - "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22034_storage)" + "type": "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22055_storage)" }, { "astId": 15311, @@ -195,7 +195,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(IBondingRegistry)21171": { + "t_contract(IBondingRegistry)21192": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" @@ -205,7 +205,7 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IInterfold)23360": { + "t_contract(IInterfold)23381": { "encoding": "inplace", "label": "contract IInterfold", "numberOfBytes": "20" @@ -280,19 +280,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)4293,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22034_storage)": { + "t_mapping(t_uint256,t_struct(E3PolicySnapshot)22055_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.E3PolicySnapshot)", "numberOfBytes": "32", - "value": "t_struct(E3PolicySnapshot)22034_storage" + "value": "t_struct(E3PolicySnapshot)22055_storage" }, - "t_mapping(t_uint256,t_struct(RefundDistribution)22055_storage)": { + "t_mapping(t_uint256,t_struct(RefundDistribution)22076_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IE3RefundManager.RefundDistribution)", "numberOfBytes": "32", - "value": "t_struct(RefundDistribution)22055_storage" + "value": "t_struct(RefundDistribution)22076_storage" }, "t_mapping(t_uint256,t_uint256)": { "encoding": "mapping", @@ -301,20 +301,20 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_struct(E3PolicySnapshot)22034_storage": { + "t_struct(E3PolicySnapshot)22055_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.E3PolicySnapshot", "members": [ { - "astId": 22023, + "astId": 22044, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "allocation", "offset": 0, "slot": "0", - "type": "t_struct(WorkValueAllocation)22019_storage" + "type": "t_struct(WorkValueAllocation)22040_storage" }, { - "astId": 22025, + "astId": 22046, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "treasury", "offset": 0, @@ -322,7 +322,7 @@ "type": "t_address" }, { - "astId": 22027, + "astId": 22048, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "interfold", "offset": 0, @@ -330,7 +330,7 @@ "type": "t_address" }, { - "astId": 22029, + "astId": 22050, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "registry", "offset": 0, @@ -338,7 +338,7 @@ "type": "t_address" }, { - "astId": 22031, + "astId": 22052, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "version", "offset": 20, @@ -346,7 +346,7 @@ "type": "t_uint64" }, { - "astId": 22033, + "astId": 22054, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "initialized", "offset": 28, @@ -356,12 +356,12 @@ ], "numberOfBytes": "128" }, - "t_struct(RefundDistribution)22055_storage": { + "t_struct(RefundDistribution)22076_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.RefundDistribution", "members": [ { - "astId": 22037, + "astId": 22058, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "requesterAmount", "offset": 0, @@ -369,7 +369,7 @@ "type": "t_uint256" }, { - "astId": 22039, + "astId": 22060, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeAmount", "offset": 0, @@ -377,7 +377,7 @@ "type": "t_uint256" }, { - "astId": 22041, + "astId": 22062, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolAmount", "offset": 0, @@ -385,7 +385,7 @@ "type": "t_uint256" }, { - "astId": 22043, + "astId": 22064, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "totalSlashed", "offset": 0, @@ -393,7 +393,7 @@ "type": "t_uint256" }, { - "astId": 22045, + "astId": 22066, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "honestNodeCount", "offset": 0, @@ -401,7 +401,7 @@ "type": "t_uint256" }, { - "astId": 22047, + "astId": 22068, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "calculated", "offset": 0, @@ -409,7 +409,7 @@ "type": "t_bool" }, { - "astId": 22050, + "astId": 22071, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "feeToken", "offset": 1, @@ -417,7 +417,7 @@ "type": "t_contract(IERC20)4293" }, { - "astId": 22052, + "astId": 22073, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "originalPayment", "offset": 0, @@ -425,7 +425,7 @@ "type": "t_uint256" }, { - "astId": 22054, + "astId": 22075, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "perNodeAmount", "offset": 0, @@ -435,12 +435,12 @@ ], "numberOfBytes": "256" }, - "t_struct(WorkValueAllocation)22019_storage": { + "t_struct(WorkValueAllocation)22040_storage": { "encoding": "inplace", "label": "struct IE3RefundManager.WorkValueAllocation", "members": [ { - "astId": 22010, + "astId": 22031, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "committeeFormationBps", "offset": 0, @@ -448,7 +448,7 @@ "type": "t_uint16" }, { - "astId": 22012, + "astId": 22033, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "dkgBps", "offset": 2, @@ -456,7 +456,7 @@ "type": "t_uint16" }, { - "astId": 22014, + "astId": 22035, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "decryptionBps", "offset": 4, @@ -464,7 +464,7 @@ "type": "t_uint16" }, { - "astId": 22016, + "astId": 22037, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "protocolBps", "offset": 6, @@ -472,7 +472,7 @@ "type": "t_uint16" }, { - "astId": 22018, + "astId": 22039, "contract": "project/contracts/E3RefundManager.sol:E3RefundManager", "label": "successSlashedNodeBps", "offset": 8, diff --git a/packages/interfold-contracts/audits/storage-layouts/Interfold.json b/packages/interfold-contracts/audits/storage-layouts/Interfold.json index 2dba96856..4e517b848 100644 --- a/packages/interfold-contracts/audits/storage-layouts/Interfold.json +++ b/packages/interfold-contracts/audits/storage-layouts/Interfold.json @@ -3,11 +3,11 @@ "contract": "Interfold", "source": "contracts/Interfold.sol", "baseline": { - "buildInfoId": "solc-0_8_28-2f1d99bb64f0a03101b91d207f24a5e189e744e5", + "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2", "compiler": "0.8.28+commit.7893614a", "evmVersion": "paris", "optimizerRuns": 1, - "sourceCommit": "191ff1ffafc829672f26e7aa1c7f8bf2ec7c0f46", + "sourceCommit": "f4c9ba955f437ae8c607b464c859b7809718be86", "sourceSha256": "c47078c161d1661c35246109c4d67e43c9b23bba2df96a81f0bb126afa76ec1a" }, "storage": [ @@ -17,7 +17,7 @@ "label": "ciphernodeRegistry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21820" + "type": "t_contract(ICiphernodeRegistry)21841" }, { "astId": 17723, @@ -25,7 +25,7 @@ "label": "bondingRegistry", "offset": 0, "slot": "1", - "type": "t_contract(IBondingRegistry)21171" + "type": "t_contract(IBondingRegistry)21192" }, { "astId": 17727, @@ -33,7 +33,7 @@ "label": "e3RefundManager", "offset": 0, "slot": "2", - "type": "t_contract(IE3RefundManager)22436" + "type": "t_contract(IE3RefundManager)22457" }, { "astId": 17731, @@ -41,7 +41,7 @@ "label": "slashingManager", "offset": 0, "slot": "3", - "type": "t_contract(ISlashingManager)23999" + "type": "t_contract(ISlashingManager)24020" }, { "astId": 17735, @@ -73,7 +73,7 @@ "label": "e3Programs", "offset": 0, "slot": "7", - "type": "t_mapping(t_contract(IE3Program)22000,t_bool)" + "type": "t_mapping(t_contract(IE3Program)22021,t_bool)" }, { "astId": 17753, @@ -81,7 +81,7 @@ "label": "e3s", "offset": 0, "slot": "8", - "type": "t_mapping(t_uint256,t_struct(E3)21960_storage)" + "type": "t_mapping(t_uint256,t_struct(E3)21981_storage)" }, { "astId": 17759, @@ -89,7 +89,7 @@ "label": "decryptionVerifiers", "offset": 0, "slot": "9", - "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21887)" + "type": "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21908)" }, { "astId": 17765, @@ -97,7 +97,7 @@ "label": "pkVerifiers", "offset": 0, "slot": "10", - "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23398)" + "type": "t_mapping(t_bytes32,t_contract(IPkVerifier)23419)" }, { "astId": 17770, @@ -121,7 +121,7 @@ "label": "_e3Stages", "offset": 0, "slot": "13", - "type": "t_mapping(t_uint256,t_enum(E3Stage)22465)" + "type": "t_mapping(t_uint256,t_enum(E3Stage)22486)" }, { "astId": 17787, @@ -129,7 +129,7 @@ "label": "_e3Deadlines", "offset": 0, "slot": "14", - "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22497_storage)" + "type": "t_mapping(t_uint256,t_struct(E3Deadlines)22518_storage)" }, { "astId": 17793, @@ -137,7 +137,7 @@ "label": "_e3FailureReasons", "offset": 0, "slot": "15", - "type": "t_mapping(t_uint256,t_enum(FailureReason)22481)" + "type": "t_mapping(t_uint256,t_enum(FailureReason)22502)" }, { "astId": 17798, @@ -161,7 +161,7 @@ "label": "committeeThresholds", "offset": 0, "slot": "18", - "type": "t_mapping(t_enum(CommitteeSize)22456,t_array(t_uint32)2_storage)" + "type": "t_mapping(t_enum(CommitteeSize)22477,t_array(t_uint32)2_storage)" }, { "astId": 17817, @@ -185,7 +185,7 @@ "label": "_timeoutConfig", "offset": 0, "slot": "21", - "type": "t_struct(E3TimeoutConfig)22489_storage" + "type": "t_struct(E3TimeoutConfig)22510_storage" }, { "astId": 17830, @@ -193,7 +193,7 @@ "label": "_pricingConfig", "offset": 0, "slot": "24", - "type": "t_struct(PricingConfig)22529_storage" + "type": "t_struct(PricingConfig)22550_storage" }, { "astId": 17840, @@ -283,27 +283,27 @@ "label": "bytes", "numberOfBytes": "32" }, - "t_contract(IBondingRegistry)21171": { + "t_contract(IBondingRegistry)21192": { "encoding": "inplace", "label": "contract IBondingRegistry", "numberOfBytes": "20" }, - "t_contract(ICiphernodeRegistry)21820": { + "t_contract(ICiphernodeRegistry)21841": { "encoding": "inplace", "label": "contract ICiphernodeRegistry", "numberOfBytes": "20" }, - "t_contract(IDecryptionVerifier)21887": { + "t_contract(IDecryptionVerifier)21908": { "encoding": "inplace", "label": "contract IDecryptionVerifier", "numberOfBytes": "20" }, - "t_contract(IE3Program)22000": { + "t_contract(IE3Program)22021": { "encoding": "inplace", "label": "contract IE3Program", "numberOfBytes": "20" }, - "t_contract(IE3RefundManager)22436": { + "t_contract(IE3RefundManager)22457": { "encoding": "inplace", "label": "contract IE3RefundManager", "numberOfBytes": "20" @@ -313,27 +313,27 @@ "label": "contract IERC20", "numberOfBytes": "20" }, - "t_contract(IPkVerifier)23398": { + "t_contract(IPkVerifier)23419": { "encoding": "inplace", "label": "contract IPkVerifier", "numberOfBytes": "20" }, - "t_contract(ISlashingManager)23999": { + "t_contract(ISlashingManager)24020": { "encoding": "inplace", "label": "contract ISlashingManager", "numberOfBytes": "20" }, - "t_enum(CommitteeSize)22456": { + "t_enum(CommitteeSize)22477": { "encoding": "inplace", "label": "enum IInterfold.CommitteeSize", "numberOfBytes": "1" }, - "t_enum(E3Stage)22465": { + "t_enum(E3Stage)22486": { "encoding": "inplace", "label": "enum IInterfold.E3Stage", "numberOfBytes": "1" }, - "t_enum(FailureReason)22481": { + "t_enum(FailureReason)22502": { "encoding": "inplace", "label": "enum IInterfold.FailureReason", "numberOfBytes": "1" @@ -352,23 +352,23 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21887)": { + "t_mapping(t_bytes32,t_contract(IDecryptionVerifier)21908)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IDecryptionVerifier)", "numberOfBytes": "32", - "value": "t_contract(IDecryptionVerifier)21887" + "value": "t_contract(IDecryptionVerifier)21908" }, - "t_mapping(t_bytes32,t_contract(IPkVerifier)23398)": { + "t_mapping(t_bytes32,t_contract(IPkVerifier)23419)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => contract IPkVerifier)", "numberOfBytes": "32", - "value": "t_contract(IPkVerifier)23398" + "value": "t_contract(IPkVerifier)23419" }, - "t_mapping(t_contract(IE3Program)22000,t_bool)": { + "t_mapping(t_contract(IE3Program)22021,t_bool)": { "encoding": "mapping", - "key": "t_contract(IE3Program)22000", + "key": "t_contract(IE3Program)22021", "label": "mapping(contract IE3Program => bool)", "numberOfBytes": "32", "value": "t_bool" @@ -387,9 +387,9 @@ "numberOfBytes": "32", "value": "t_uint256" }, - "t_mapping(t_enum(CommitteeSize)22456,t_array(t_uint32)2_storage)": { + "t_mapping(t_enum(CommitteeSize)22477,t_array(t_uint32)2_storage)": { "encoding": "mapping", - "key": "t_enum(CommitteeSize)22456", + "key": "t_enum(CommitteeSize)22477", "label": "mapping(enum IInterfold.CommitteeSize => uint32[2])", "numberOfBytes": "32", "value": "t_array(t_uint32)2_storage" @@ -408,19 +408,19 @@ "numberOfBytes": "32", "value": "t_contract(IERC20)4293" }, - "t_mapping(t_uint256,t_enum(E3Stage)22465)": { + "t_mapping(t_uint256,t_enum(E3Stage)22486)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.E3Stage)", "numberOfBytes": "32", - "value": "t_enum(E3Stage)22465" + "value": "t_enum(E3Stage)22486" }, - "t_mapping(t_uint256,t_enum(FailureReason)22481)": { + "t_mapping(t_uint256,t_enum(FailureReason)22502)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => enum IInterfold.FailureReason)", "numberOfBytes": "32", - "value": "t_enum(FailureReason)22481" + "value": "t_enum(FailureReason)22502" }, "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { "encoding": "mapping", @@ -429,19 +429,19 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint256)" }, - "t_mapping(t_uint256,t_struct(E3)21960_storage)": { + "t_mapping(t_uint256,t_struct(E3)21981_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct E3)", "numberOfBytes": "32", - "value": "t_struct(E3)21960_storage" + "value": "t_struct(E3)21981_storage" }, - "t_mapping(t_uint256,t_struct(E3Deadlines)22497_storage)": { + "t_mapping(t_uint256,t_struct(E3Deadlines)22518_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct IInterfold.E3Deadlines)", "numberOfBytes": "32", - "value": "t_struct(E3Deadlines)22497_storage" + "value": "t_struct(E3Deadlines)22518_storage" }, "t_mapping(t_uint256,t_struct(E3Dependencies)17865_storage)": { "encoding": "mapping", @@ -471,12 +471,12 @@ "numberOfBytes": "32", "value": "t_bytes_storage" }, - "t_struct(E3)21960_storage": { + "t_struct(E3)21981_storage": { "encoding": "inplace", "label": "struct E3", "members": [ { - "astId": 21927, + "astId": 21948, "contract": "project/contracts/Interfold.sol:Interfold", "label": "seed", "offset": 0, @@ -484,15 +484,15 @@ "type": "t_uint256" }, { - "astId": 21930, + "astId": 21951, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeeSize", "offset": 0, "slot": "1", - "type": "t_enum(CommitteeSize)22456" + "type": "t_enum(CommitteeSize)22477" }, { - "astId": 21932, + "astId": 21953, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requestBlock", "offset": 0, @@ -500,7 +500,7 @@ "type": "t_uint256" }, { - "astId": 21936, + "astId": 21957, "contract": "project/contracts/Interfold.sol:Interfold", "label": "inputWindow", "offset": 0, @@ -508,7 +508,7 @@ "type": "t_array(t_uint256)2_storage" }, { - "astId": 21938, + "astId": 21959, "contract": "project/contracts/Interfold.sol:Interfold", "label": "encryptionSchemeId", "offset": 0, @@ -516,15 +516,15 @@ "type": "t_bytes32" }, { - "astId": 21941, + "astId": 21962, "contract": "project/contracts/Interfold.sol:Interfold", "label": "e3Program", "offset": 0, "slot": "6", - "type": "t_contract(IE3Program)22000" + "type": "t_contract(IE3Program)22021" }, { - "astId": 21943, + "astId": 21964, "contract": "project/contracts/Interfold.sol:Interfold", "label": "paramSet", "offset": 20, @@ -532,7 +532,7 @@ "type": "t_uint8" }, { - "astId": 21945, + "astId": 21966, "contract": "project/contracts/Interfold.sol:Interfold", "label": "customParams", "offset": 0, @@ -540,23 +540,23 @@ "type": "t_bytes_storage" }, { - "astId": 21948, + "astId": 21969, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionVerifier", "offset": 0, "slot": "8", - "type": "t_contract(IDecryptionVerifier)21887" + "type": "t_contract(IDecryptionVerifier)21908" }, { - "astId": 21951, + "astId": 21972, "contract": "project/contracts/Interfold.sol:Interfold", "label": "pkVerifier", "offset": 0, "slot": "9", - "type": "t_contract(IPkVerifier)23398" + "type": "t_contract(IPkVerifier)23419" }, { - "astId": 21953, + "astId": 21974, "contract": "project/contracts/Interfold.sol:Interfold", "label": "committeePublicKey", "offset": 0, @@ -564,7 +564,7 @@ "type": "t_bytes32" }, { - "astId": 21955, + "astId": 21976, "contract": "project/contracts/Interfold.sol:Interfold", "label": "ciphertextOutput", "offset": 0, @@ -572,7 +572,7 @@ "type": "t_bytes32" }, { - "astId": 21957, + "astId": 21978, "contract": "project/contracts/Interfold.sol:Interfold", "label": "plaintextOutput", "offset": 0, @@ -580,7 +580,7 @@ "type": "t_bytes_storage" }, { - "astId": 21959, + "astId": 21980, "contract": "project/contracts/Interfold.sol:Interfold", "label": "requester", "offset": 0, @@ -590,12 +590,12 @@ ], "numberOfBytes": "448" }, - "t_struct(E3Deadlines)22497_storage": { + "t_struct(E3Deadlines)22518_storage": { "encoding": "inplace", "label": "struct IInterfold.E3Deadlines", "members": [ { - "astId": 22492, + "astId": 22513, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgDeadline", "offset": 0, @@ -603,7 +603,7 @@ "type": "t_uint256" }, { - "astId": 22494, + "astId": 22515, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeDeadline", "offset": 0, @@ -611,7 +611,7 @@ "type": "t_uint256" }, { - "astId": 22496, + "astId": 22517, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionDeadline", "offset": 0, @@ -631,7 +631,7 @@ "label": "registry", "offset": 0, "slot": "0", - "type": "t_contract(ICiphernodeRegistry)21820" + "type": "t_contract(ICiphernodeRegistry)21841" }, { "astId": 17861, @@ -639,7 +639,7 @@ "label": "refundManager", "offset": 0, "slot": "1", - "type": "t_contract(IE3RefundManager)22436" + "type": "t_contract(IE3RefundManager)22457" }, { "astId": 17864, @@ -647,17 +647,17 @@ "label": "slashManager", "offset": 0, "slot": "2", - "type": "t_contract(ISlashingManager)23999" + "type": "t_contract(ISlashingManager)24020" } ], "numberOfBytes": "96" }, - "t_struct(E3TimeoutConfig)22489_storage": { + "t_struct(E3TimeoutConfig)22510_storage": { "encoding": "inplace", "label": "struct IInterfold.E3TimeoutConfig", "members": [ { - "astId": 22484, + "astId": 22505, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgWindow", "offset": 0, @@ -665,7 +665,7 @@ "type": "t_uint256" }, { - "astId": 22486, + "astId": 22507, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeWindow", "offset": 0, @@ -673,7 +673,7 @@ "type": "t_uint256" }, { - "astId": 22488, + "astId": 22509, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionWindow", "offset": 0, @@ -683,12 +683,12 @@ ], "numberOfBytes": "96" }, - "t_struct(PricingConfig)22529_storage": { + "t_struct(PricingConfig)22550_storage": { "encoding": "inplace", "label": "struct IInterfold.PricingConfig", "members": [ { - "astId": 22500, + "astId": 22521, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenFixedPerNode", "offset": 0, @@ -696,7 +696,7 @@ "type": "t_uint256" }, { - "astId": 22502, + "astId": 22523, "contract": "project/contracts/Interfold.sol:Interfold", "label": "keyGenPerEncryptionProof", "offset": 0, @@ -704,7 +704,7 @@ "type": "t_uint256" }, { - "astId": 22504, + "astId": 22525, "contract": "project/contracts/Interfold.sol:Interfold", "label": "coordinationPerPair", "offset": 0, @@ -712,7 +712,7 @@ "type": "t_uint256" }, { - "astId": 22506, + "astId": 22527, "contract": "project/contracts/Interfold.sol:Interfold", "label": "availabilityPerNodePerSec", "offset": 0, @@ -720,7 +720,7 @@ "type": "t_uint256" }, { - "astId": 22508, + "astId": 22529, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptionPerNode", "offset": 0, @@ -728,7 +728,7 @@ "type": "t_uint256" }, { - "astId": 22510, + "astId": 22531, "contract": "project/contracts/Interfold.sol:Interfold", "label": "publicationBase", "offset": 0, @@ -736,7 +736,7 @@ "type": "t_uint256" }, { - "astId": 22512, + "astId": 22533, "contract": "project/contracts/Interfold.sol:Interfold", "label": "verificationPerProof", "offset": 0, @@ -744,7 +744,7 @@ "type": "t_uint256" }, { - "astId": 22514, + "astId": 22535, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolTreasury", "offset": 0, @@ -752,7 +752,7 @@ "type": "t_address" }, { - "astId": 22516, + "astId": 22537, "contract": "project/contracts/Interfold.sol:Interfold", "label": "marginBps", "offset": 20, @@ -760,7 +760,7 @@ "type": "t_uint16" }, { - "astId": 22518, + "astId": 22539, "contract": "project/contracts/Interfold.sol:Interfold", "label": "protocolShareBps", "offset": 22, @@ -768,7 +768,7 @@ "type": "t_uint16" }, { - "astId": 22520, + "astId": 22541, "contract": "project/contracts/Interfold.sol:Interfold", "label": "dkgUtilizationBps", "offset": 24, @@ -776,7 +776,7 @@ "type": "t_uint16" }, { - "astId": 22522, + "astId": 22543, "contract": "project/contracts/Interfold.sol:Interfold", "label": "computeUtilizationBps", "offset": 26, @@ -784,7 +784,7 @@ "type": "t_uint16" }, { - "astId": 22524, + "astId": 22545, "contract": "project/contracts/Interfold.sol:Interfold", "label": "decryptUtilizationBps", "offset": 28, @@ -792,7 +792,7 @@ "type": "t_uint16" }, { - "astId": 22526, + "astId": 22547, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minCommitteeSize", "offset": 0, @@ -800,7 +800,7 @@ "type": "t_uint32" }, { - "astId": 22528, + "astId": 22549, "contract": "project/contracts/Interfold.sol:Interfold", "label": "minThreshold", "offset": 4, From 9b3446223f69223d508c362f34a66c0957a17f48 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:15:13 +0500 Subject: [PATCH 39/52] fix(tooling): require final committee proof payloads [C-02] --- .../dkg_recursive_aggregation_complete.rs | 3 ++- .../interfold_event/publickey_aggregated.rs | 5 ++-- .../interfaces/ICiphernodeRegistry.sol | 7 +++-- .../contracts/interfaces/IInterfold.sol | 5 ++-- .../interfold-contracts/tasks/interfold.ts | 27 +++++++++++++++---- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/crates/events/src/interfold_event/dkg_recursive_aggregation_complete.rs b/crates/events/src/interfold_event/dkg_recursive_aggregation_complete.rs index 6baa81dbe..903aac368 100644 --- a/crates/events/src/interfold_event/dkg_recursive_aggregation_complete.rs +++ b/crates/events/src/interfold_event/dkg_recursive_aggregation_complete.rs @@ -14,7 +14,8 @@ use crate::{E3id, Proof, SignedDkgFoldAttestation}; use serde::{Deserialize, Serialize}; /// NodeProofAggregator -> PublicKeyAggregator: fully aggregated DKG node proof. -/// When proof aggregation is disabled for the E3, `aggregated_proof` is `None`. +/// The test/CI node-level skip path reports `None`; the public-key aggregator +/// converts that internal state into a non-empty mock-verifier placeholder. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct DKGRecursiveAggregationComplete { pub e3_id: E3id, diff --git a/crates/events/src/interfold_event/publickey_aggregated.rs b/crates/events/src/interfold_event/publickey_aggregated.rs index 8ba4c61dd..bd2d39bfa 100644 --- a/crates/events/src/interfold_event/publickey_aggregated.rs +++ b/crates/events/src/interfold_event/publickey_aggregated.rs @@ -31,10 +31,11 @@ pub struct PublicKeyAggregated { /// Passed as `pkCommitment` to `publishCommittee`. pub pk_commitment: [u8; 32], /// EVM DKG recursive proof (`CircuitName::DkgAggregator`) carrying node folds + C5 - /// for on-chain verification. `None` when proof aggregation is disabled. + /// for on-chain verification. Test/CI skip mode carries a non-empty C5 + /// placeholder accepted only by mock verifiers. pub dkg_aggregator_proof: Option, /// ABI-encoded `(Attestation[], PartySlotBinding[])` for on-chain fold attestation verify. - /// Required when `dkg_aggregator_proof` is present; `None` otherwise. + /// Test/CI skip mode carries a non-empty placeholder accepted only by its mock verifier. pub dkg_attestation_bundle: Option, } diff --git a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol index b8e0a2350..5310c85c7 100644 --- a/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -116,11 +116,10 @@ interface ICiphernodeRegistry { /// @param e3Id ID of the E3 for which the committee was selected. /// @param publicKey Serialized public-key transport hint. Consumers MUST /// deserialize it and recompute `pkCommitment` before using it for - /// encryption. The commitment is C5-proven when proof aggregation is - /// enabled and trusted by the development aggregator otherwise. + /// encryption. The commitment is proven by the mandatory final DKG + /// proof; test-only mock verifiers may accept placeholder payloads. /// @param pkCommitment Hash-based aggregated PK commitment for the committee. - /// @param proof DkgAggregator (EVM) proof bytes verified prior to publication, - /// or empty bytes when proof aggregation is disabled for the E3. + /// @param proof Non-empty DkgAggregator (EVM) proof bytes verified prior to publication. event CommitteePublished( uint256 indexed e3Id, address[] nodes, diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index c3b1be9c6..bc1b95f17 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -534,9 +534,8 @@ interface IInterfold { /// @dev This function MUST emit the PlaintextOutputPublished event. /// @param e3Id ID of the E3. /// @param plaintextOutput ABI encoded plaintext output. - /// @param proof DecryptionAggregator (EVM) proof ABI-encoded - /// `(bytes rawProof, bytes32[] publicInputs)`, or empty bytes when proof - /// aggregation is disabled for the E3. + /// @param proof Required DecryptionAggregator (EVM) proof ABI-encoded + /// `(bytes rawProof, bytes32[] publicInputs)`. function publishPlaintextOutput( uint256 e3Id, bytes calldata plaintextOutput, diff --git a/packages/interfold-contracts/tasks/interfold.ts b/packages/interfold-contracts/tasks/interfold.ts index 4708b8f48..a2f540c08 100644 --- a/packages/interfold-contracts/tasks/interfold.ts +++ b/packages/interfold-contracts/tasks/interfold.ts @@ -318,20 +318,29 @@ export const publishCommittee = task( }) .addOption({ name: "pkCommitment", - description: - "Hash-based aggregated PK commitment (bytes32 hex); required even when proof aggregation is disabled", + description: "Hash-based aggregated PK commitment (bytes32 hex); required", defaultValue: "", type: ArgumentType.STRING, }) .addOption({ name: "proof", description: - "ABI-encoded DkgAggregator (EVM) proof (bytes rawProof, bytes32[] publicInputs); pass 0x when proof aggregation is disabled", + "Required ABI-encoded DkgAggregator (EVM) proof (bytes rawProof, bytes32[] publicInputs)", + defaultValue: "0x", + type: ArgumentType.STRING, + }) + .addOption({ + name: "dkgAttestationBundle", + description: + "Required ABI-encoded DKG fold attestation bundle (Attestation[], PartySlotBinding[])", defaultValue: "0x", type: ArgumentType.STRING, }) .setAction(async () => ({ - default: async ({ e3Id, nodes, publicKey, pkCommitment, proof }, hre) => { + default: async ( + { e3Id, nodes, publicKey, pkCommitment, proof, dkgAttestationBundle }, + hre, + ) => { const { deployAndSaveCiphernodeRegistryOwnable } = await import( "../scripts/deployAndSave/ciphernodeRegistryOwnable" ); @@ -375,13 +384,21 @@ export const publishCommittee = task( "publicKey is required and must be a non-empty hex string", ); } + if (!isHexString(proof) || proof === "0x") { + throw new Error("proof is required and must be a non-empty hex string"); + } + if (!isHexString(dkgAttestationBundle) || dkgAttestationBundle === "0x") { + throw new Error( + "dkgAttestationBundle is required and must be a non-empty hex string", + ); + } const tx = await ciphernodeRegistry.publishCommittee( e3Id, publicKey, pkCommitment, proof, - "0x", + dkgAttestationBundle, ); console.log("Publishing committee... ", tx.hash); From 511602f969d41347222dbbce741a94c7a52a8386 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:24:47 +0500 Subject: [PATCH 40/52] fix(sdk): reject unbound committee public keys [C-01] --- agent/flow-trace/00_INDEX.md | 4 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 8 ++-- packages/interfold-sdk/README.md | 19 ++++++++ .../src/events/event-listener.ts | 7 ++- packages/interfold-sdk/src/events/types.ts | 1 + packages/interfold-sdk/src/interfold-sdk.ts | 18 ++++++++ packages/interfold-sdk/tests/events.test.ts | 45 ++++++++++++++++++- packages/interfold-sdk/tests/sdk.test.ts | 12 +++++ .../src/pages/steps/RequestComputation.tsx | 41 ++++++++++++----- 9 files changed, 135 insertions(+), 20 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 2e7291bae..73089b21b 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -213,11 +213,11 @@ _Found during source-code cross-referencing of these trace documents._ | 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | Governance may update ticket price, license thresholds, and minimum-ticket policy. Each effective update advances a policy version and invalidates cached activity in O(1); registered operators fail closed until a permissionless refresh re-evaluates them under the new version, keeping on-chain counts and Rust sortition state synchronized. | | 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | | 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | -| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, or bad gap consumption; baseline creation is a separate explicit maintainer command. Initial pricing is now passed as a typed initializer struct and written through the same validated setter path, with no hard-coded storage-slot writer. | +| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, or bad gap consumption; baseline creation is a separate explicit maintainer command. Initial pricing is now passed as a typed initializer struct and written through the same validated setter path, with no hard-coded storage-slot writer. | | 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Matching fee-token slashes fill the requester’s exact original-payment shortfall before rewarding honest nodes; different-token slashes go to the requester as priority compensation without asserting oracle-free equivalence. Fee-token refunds never relabel slash amounts, and every outbound transfer preserves a protected per-token liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | -| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. The BFV client decodes the key and recomputes its circuit commitment; the indexer stores it only when that value equals the on-chain commitment, so calldata substitution cannot become a different client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | +| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | | 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index aacb759e5..bb849b676 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -684,9 +684,11 @@ ThresholdKeyshare receives AllThresholdSharesCollected The serialized `publicKey` event field is a transport hint, not on-chain authority. Before `e3-indexer` stores it in `E3.committee_public_key`, it decodes the BFV key, recomputes the circuit's public-key commitment using the request's parameter set, and requires equality with the -event's on-chain `pkCommitment`. Malformed bytes or bytes for a different key fail closed and never -reach encryption clients. The commitment is C5-proven when proof aggregation is enabled; the -explicitly unsafe development mode trusts it from the aggregator. +event's on-chain `pkCommitment`. TypeScript event consumers receive the same `pkCommitment` and use +`InterfoldSDK.validatePublicKeyCommitment()` before accepting the bytes; the default application +does this before advancing to encryption. Malformed bytes or bytes for a different key fail closed +and never reach first-party encryption clients. The commitment is C5-proven when proof aggregation +is enabled; the explicitly unsafe development mode trusts it from the aggregator. > **C-08 (BfvPkVerifier domain binding) — implemented** The wrapper exposes a > `verify(e3Id, committeeRoot, sortedNodes, pkCommitment, committeeHash, proof)` signature. diff --git a/packages/interfold-sdk/README.md b/packages/interfold-sdk/README.md index dc2c06bd5..280b2b83f 100644 --- a/packages/interfold-sdk/README.md +++ b/packages/interfold-sdk/README.md @@ -351,6 +351,24 @@ await sdk.startEventPolling(); sdk.stopEventPolling(); ``` +`CommitteePublished.publicKey` is an untrusted transport value. Validate it against the event's +on-chain commitment before using it for encryption: + +```typescript +import { hexToBytes } from 'viem' + +await sdk.onInterfoldEvent(RegistryEventType.COMMITTEE_PUBLISHED, async (event) => { + const publicKey = hexToBytes(event.data.publicKey) + const expectedCommitment = hexToBytes(event.data.pkCommitment) + + if (!(await sdk.validatePublicKeyCommitment(publicKey, expectedCommitment))) { + throw new Error('Committee public-key commitment mismatch') + } + + // The key is now safe to pass to the encryption methods. +}) +``` + #### Encryption ```typescript @@ -360,6 +378,7 @@ const params = await sdk.getThresholdBfvParamsSet(); // Key generation const publicKey = await sdk.generatePublicKey(); const commitment = await sdk.computePublicKeyCommitment(publicKey); +const isValid = await sdk.validatePublicKeyCommitment(publicKey, commitment); // Encrypt data const encrypted = await sdk.encryptNumber(data: bigint, publicKey: Uint8Array); diff --git a/packages/interfold-sdk/src/events/event-listener.ts b/packages/interfold-sdk/src/events/event-listener.ts index e48c585b9..dfaf651c5 100644 --- a/packages/interfold-sdk/src/events/event-listener.ts +++ b/packages/interfold-sdk/src/events/event-listener.ts @@ -218,7 +218,12 @@ export class EventListener implements SDKEventEmitter { if (callbacks) { callbacks.forEach((callback) => { try { - void (callback as EventCallback)(event) + const result = (callback as EventCallback)(event) + if (result) { + void result.catch((error) => { + console.error(`Error in event callback for ${event.type}:`, error) + }) + } } catch (error) { console.error(`Error in event callback for ${event.type}:`, error) } diff --git a/packages/interfold-sdk/src/events/types.ts b/packages/interfold-sdk/src/events/types.ts index add662a21..b1583615f 100644 --- a/packages/interfold-sdk/src/events/types.ts +++ b/packages/interfold-sdk/src/events/types.ts @@ -93,6 +93,7 @@ export interface CommitteePublishedData { e3Id: bigint nodes: string[] publicKey: string + pkCommitment: string proof: string } diff --git a/packages/interfold-sdk/src/interfold-sdk.ts b/packages/interfold-sdk/src/interfold-sdk.ts index 2a176d843..6ade6fd76 100644 --- a/packages/interfold-sdk/src/interfold-sdk.ts +++ b/packages/interfold-sdk/src/interfold-sdk.ts @@ -103,6 +103,24 @@ export class InterfoldSDK { return computePublicKeyCommitment(publicKey, this.thresholdBfvParamsPresetName) } + /** + * Validate serialized BFV public-key bytes before using them for encryption. + * + * `CommitteePublished.publicKey` is an untrusted transport value. Consumers + * must compare its semantic BFV commitment with the event's on-chain + * `pkCommitment` before accepting it. + */ + public async validatePublicKeyCommitment(publicKey: Uint8Array, expectedCommitment: Uint8Array): Promise { + if (expectedCommitment.length !== 32) { + return false + } + + const actualCommitment = await this.computePublicKeyCommitment(publicKey) + return ( + actualCommitment.length === expectedCommitment.length && actualCommitment.every((byte, index) => byte === expectedCommitment[index]) + ) + } + public async encryptNumber(data: bigint, publicKey: Uint8Array): Promise { return encryptNumber(data, publicKey, this.thresholdBfvParamsPresetName) } diff --git a/packages/interfold-sdk/tests/events.test.ts b/packages/interfold-sdk/tests/events.test.ts index f800d675b..836fe5056 100644 --- a/packages/interfold-sdk/tests/events.test.ts +++ b/packages/interfold-sdk/tests/events.test.ts @@ -5,9 +5,15 @@ // or FITNESS FOR A PARTICULAR PURPOSE. import { CiphernodeRegistryOwnable__factory } from '@interfold/contracts/types' -import { describe, expect, it } from 'vitest' +import type { Log, PublicClient } from 'viem' +import { afterEach, describe, expect, it, vi } from 'vitest' -import { RegistryEventType } from '../src/events/types' +import { EventListener } from '../src/events/event-listener' +import { RegistryEventType, type InterfoldEvent } from '../src/events/types' + +afterEach(() => { + vi.restoreAllMocks() +}) describe('RegistryEventType', () => { it('uses the finalized-committee event name exposed by the registry ABI', () => { @@ -16,4 +22,39 @@ describe('RegistryEventType', () => { expect(RegistryEventType.COMMITTEE_FINALIZED).toBe('SortitionCommitteeFinalized') expect(registryEventNames).toContain(RegistryEventType.COMMITTEE_FINALIZED) }) + + it('handles asynchronous event callback failures', async () => { + const error = new Error('invalid committee key') + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) + const listener = new EventListener({ + publicClient: {} as PublicClient, + contracts: { + interfold: '0x0000000000000000000000000000000000000001', + ciphernodeRegistry: '0x0000000000000000000000000000000000000002', + feeToken: '0x0000000000000000000000000000000000000003', + }, + }) + const event: InterfoldEvent = { + type: RegistryEventType.COMMITTEE_PUBLISHED, + data: { + e3Id: 1n, + nodes: [], + publicKey: '0x', + pkCommitment: `0x${'00'.repeat(32)}`, + proof: '0x', + }, + log: {} as Log, + timestamp: new Date(), + blockNumber: 1n, + transactionHash: '0x', + } + + listener.on(RegistryEventType.COMMITTEE_PUBLISHED, async () => { + throw error + }) + listener.emit(event) + await Promise.resolve() + + expect(consoleError).toHaveBeenCalledWith(`Error in event callback for ${RegistryEventType.COMMITTEE_PUBLISHED}:`, error) + }) }) diff --git a/packages/interfold-sdk/tests/sdk.test.ts b/packages/interfold-sdk/tests/sdk.test.ts index 098a14165..a945fbc3a 100644 --- a/packages/interfold-sdk/tests/sdk.test.ts +++ b/packages/interfold-sdk/tests/sdk.test.ts @@ -50,6 +50,18 @@ describe('encryptNumber', () => { expect(value.length).to.equal(9_242) }) + it('should validate a committee public key against its on-chain commitment', async () => { + const publicKey = await sdk.generatePublicKey() + const commitment = await sdk.computePublicKeyCommitment(publicKey) + + expect(await sdk.validatePublicKeyCommitment(publicKey, commitment)).to.equal(true) + + const differentCommitment = commitment.slice() + differentCommitment[0] ^= 1 + expect(await sdk.validatePublicKeyCommitment(publicKey, differentCommitment)).to.equal(false) + expect(await sdk.validatePublicKeyCommitment(publicKey, new Uint8Array(31))).to.equal(false) + }) + it('should encrypt a vector and generate a proof without crashing in a node environent', async () => { const publicKey = await sdk.generatePublicKey() diff --git a/templates/default/client/src/pages/steps/RequestComputation.tsx b/templates/default/client/src/pages/steps/RequestComputation.tsx index f6596f42e..62d66fc3f 100644 --- a/templates/default/client/src/pages/steps/RequestComputation.tsx +++ b/templates/default/client/src/pages/steps/RequestComputation.tsx @@ -6,6 +6,8 @@ import React, { useState, useEffect } from 'react' import { CalculatorIcon } from '@phosphor-icons/react' +import { hexToBytes } from 'viem' +import type { CommitteePublishedData } from '@interfold/sdk' import CardContent from '../components/CardContent' import Spinner from '../components/Spinner' import ErrorDisplay from '../components/ErrorDisplay' @@ -45,18 +47,33 @@ const RequestComputation: React.FC = () => { })) } - const handleCommitteePublished = (event: any) => { - const { e3Id, publicKey } = event.data - setE3State((prev) => { - if (prev.id !== null && e3Id === prev.id) { - return { - ...prev, - isCommitteePublished: true, - publicKey: publicKey as `0x${string}`, - } + const handleCommitteePublished = async (event: { data: CommitteePublishedData }) => { + const { e3Id, publicKey, pkCommitment } = event.data + if (e3State.id === null || e3Id !== e3State.id || !sdk.sdk) return + + try { + const isBoundKey = await sdk.sdk.validatePublicKeyCommitment( + hexToBytes(publicKey as `0x${string}`), + hexToBytes(pkCommitment as `0x${string}`), + ) + if (!isBoundKey) { + throw new Error(`Rejected committee public key for E3 ${e3Id}: commitment mismatch`) } - return prev - }) + + setE3State((prev) => { + if (prev.id !== null && e3Id === prev.id) { + return { + ...prev, + isCommitteePublished: true, + publicKey: publicKey as `0x${string}`, + } + } + return prev + }) + } catch (error) { + setRequestError(error) + console.error(`Rejected committee public key for E3 ${e3Id}:`, error) + } } onInterfoldEvent(InterfoldEventType.E3_REQUESTED, handleE3Requested) @@ -66,7 +83,7 @@ const RequestComputation: React.FC = () => { off(InterfoldEventType.E3_REQUESTED, handleE3Requested) off(RegistryEventType.COMMITTEE_PUBLISHED, handleCommitteePublished) } - }, [isInitialized, onInterfoldEvent, off, InterfoldEventType, RegistryEventType, setE3State]) + }, [isInitialized, onInterfoldEvent, off, InterfoldEventType, RegistryEventType, setE3State, e3State.id, sdk.sdk]) // Auto-advance to next step when committee publishes useEffect(() => { From 065ebf50c1096633c13643c04aa9b4f5550ece55 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:45:18 +0500 Subject: [PATCH 41/52] fix(contracts): fail closed across dependency rotations [M-04] --- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 9 +- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 17 ++- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 39 +++++- .../ISlashingManager.json | 2 +- .../contracts/Interfold.sol | 6 + .../contracts/interfaces/IInterfold.sol | 10 ++ .../contracts/interfaces/ISlashingManager.sol | 2 +- .../contracts/lib/InterfoldPricing.sol | 118 ++++++++++++++++++ .../contracts/slashing/SlashingManager.sol | 4 + .../test/E3Lifecycle/E3Integration.spec.ts | 24 +++- .../test/Slashing/SlashingLanes.spec.ts | 1 + .../test/Slashing/SlashingManager.spec.ts | 23 ++++ 14 files changed, 243 insertions(+), 16 deletions(-) diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 72a03b62c..76a9f5532 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -39,7 +39,10 @@ Requester calls: Interfold.request({ │ ├─ inputWindow[0] >= block.timestamp (start in future) │ ├─ inputWindow[1] >= inputWindow[0] (end after start) │ ├─ total duration < maxDuration -│ └─ e3Programs[e3Program] == true (program whitelisted) +│ ├─ e3Programs[e3Program] == true (program whitelisted) +│ └─ registry / bonding / refund / slashing pointers form one +│ mutually consistent deployed-contract graph +│ → partial multi-transaction admin rotations fail closed │ ├─ FEE CALCULATION: │ ├─ fee = getE3Quote() @@ -410,7 +413,9 @@ If any deadline is missed → anyone can call markE3Failed() slashing, refund, and Interfold relationships. Admin rotation changes defaults for later E3s but cannot redirect or brick committee callbacks, proof checks, failure settlement, rewards, or slashed-fund routing for an in-flight E3. A request atomically records the complete graph before - committee formation begins. + committee formation begins. Because applying a new graph requires several governance + transactions, request-time validation rejects every intermediate state; a requester can only + freeze the fully old or fully new graph. --- diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 84982f98b..805f3f174 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -576,12 +576,16 @@ SLASHER_ROLE calls: SlashingManager.proposeSlashEvidence( │ require(!policy.requiresProof) → evidence-based only │ → reason is an explicit bytes32, not derived from proof │ -├─ 2. Replay protection: +├─ 2. Require the snapshotted E3 dependency graph exists and +│ registry.isCommitteeMember(e3Id, operator) +│ → Evidence cannot slash an unrelated operator into another E3's escrow +│ +├─ 3. Replay protection: │ evidenceHash = keccak256(abi.encode(e3Id, operator, keccak256(evidence))) │ require(!evidenceConsumed[evidenceHash]) │ evidenceConsumed[evidenceHash] = true │ -├─ 3. Create proposal with SNAPSHOTTED policy values: +├─ 4. Create proposal with SNAPSHOTTED policy values: │ proposal = SlashProposal { │ e3Id, operator, reason, │ ticketAmount: policy.ticketPenalty, @@ -595,7 +599,7 @@ SLASHER_ROLE calls: SlashingManager.proposeSlashEvidence( │ → NOT executed immediately │ → Increment the same unresolved financial proposal count │ -└─ 4. Emit SlashProposed(proposalId, e3Id, operator, reason) +└─ 5. Emit SlashProposed(proposalId, e3Id, operator, reason) ─── APPEAL WINDOW OPENS ───────────────────────────────────── @@ -901,7 +905,8 @@ Every slash and settlement route resolves the dependency graph frozen when the E - `E3RefundManager` accepts lifecycle calls from the Interfold recorded in the E3 policy snapshot. - `E3RefundManager` reads slash recipients from the committee registry recorded in that snapshot. - `BondingRegistry` retains replaced slashing managers as authorized until governance explicitly - revokes them, so an old manager can finish snapshotted penalties and remains part of the exit gate. + revokes them, so an old manager can finish snapshotted penalties and remains part of the exit + gate. Admin setters update the live defaults for future requests only. Each E3 must have a complete request-time snapshot; lifecycle calls fail closed if that invariant is not satisfied. Governance @@ -1187,8 +1192,8 @@ Applied audit findings: **C-05, H-05, H-06, H-07, H-09, H-10, H-24, M-14, M-15, `deregisterOperator`, and `claimExits` while the gate is raised. Both active collateral and assets already queued for exit therefore remain slashable. - That check covers every authorized current or retained historical slashing manager. Rotation - therefore cannot release collateral for an old manager's in-flight proposal. Governance revokes - an old manager only after its E3s, proposals, and pending routes are terminal. + therefore cannot release collateral for an old manager's in-flight proposal. Governance revokes an + old manager only after its E3s, proposals, and pending routes are terminal. - A filed appeal cannot freeze collateral indefinitely: after the policy appeal window plus the seven-day governance resolution grace, `expireAppeal` permissionlessly upholds it and clears its gate. diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index d85ce57bb..d17ca01a7 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1338,5 +1338,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" + "buildInfoId": "solc-0_8_28-b08e2b9fb42fa5cb0f49eb46a9e6e77e295b46b0" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 2ca9fec12..05db5169d 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1345,5 +1345,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" + "buildInfoId": "solc-0_8_28-b08e2b9fb42fa5cb0f49eb46a9e6e77e295b46b0" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index f4f6c0ebc..05979878f 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -111,6 +111,43 @@ "name": "CommitteeSizeTooSmall", "type": "error" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "relation", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "expected", + "type": "address" + }, + { + "internalType": "address", + "name": "actual", + "type": "address" + } + ], + "name": "DependencyGraphMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "dependency", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "DependencyNotContract", + "type": "error" + }, { "inputs": [ { @@ -2376,5 +2413,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" + "buildInfoId": "solc-0_8_28-b08e2b9fb42fa5cb0f49eb46a9e6e77e295b46b0" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 86d35c26d..2c825db95 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1432,5 +1432,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-cbb48f089c866ef144e6f8c2927a419cbfdb1af2" + "buildInfoId": "solc-0_8_28-a061e0a52134a96e9721aff61615698a1eae6886" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 964fd4db1..1c353a912 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -269,6 +269,12 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { _timeoutConfig.decryptionWindow, maxDuration ); + InterfoldPricing.validateDependencyGraph( + address(ciphernodeRegistry), + address(bondingRegistry), + address(e3RefundManager), + address(slashingManager) + ); e3Id = nexte3Id; nexte3Id++; diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index bc1b95f17..85c2f57d3 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -381,6 +381,16 @@ interface IInterfold { /// @param bondingRegistry The invalid bonding registry address. error InvalidBondingRegistry(IBondingRegistry bondingRegistry); + /// @notice A required lifecycle dependency is not deployed contract code. + error DependencyNotContract(bytes32 dependency, address target); + + /// @notice Cross-contract lifecycle pointers do not describe one coherent deployment. + error DependencyGraphMismatch( + bytes32 relation, + address expected, + address actual + ); + /// @notice Thrown when attempting to set an invalid fee token address. /// @param feeToken The invalid fee token address. error InvalidFeeToken(IERC20 feeToken); diff --git a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol index 92dd66edf..eb6ccdd63 100644 --- a/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/ISlashingManager.sol @@ -66,7 +66,7 @@ interface ISlashingManager { /** * @notice Slash proposal details tracking the full lifecycle of a slash * @dev Stores all state needed for proposal, appeal, and execution workflows - * @param e3Id ID of the E3 computation this slash relates to (0 for non-E3 slashes) + * @param e3Id ID of the E3 computation this slash relates to * @param operator Address of the ciphernode operator being slashed * @param reason Hash of the slash reason (maps to SlashPolicy configuration) * @param ticketAmount Amount of ticket collateral to slash (copied from policy at proposal time) diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index e72ebd868..27fce0ebe 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -9,6 +9,34 @@ import { IInterfold } from "../interfaces/IInterfold.sol"; import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; import { IDecryptionVerifier } from "../interfaces/IDecryptionVerifier.sol"; +interface ICiphernodeRegistryDependencyView { + function interfold() external view returns (address); + + function bondingRegistry() external view returns (address); + + function slashingManager() external view returns (address); +} + +interface IBondingRegistryDependencyView { + function registry() external view returns (address); + + function slashingManager() external view returns (address); +} + +interface IRefundManagerDependencyView { + function interfold() external view returns (address); +} + +interface ISlashingManagerDependencyView { + function bondingRegistry() external view returns (address); + + function ciphernodeRegistry() external view returns (address); + + function interfold() external view returns (address); + + function e3RefundManager() external view returns (address); +} + /** * @title InterfoldPricing * @notice External library extracted from {Interfold} to keep its deployed @@ -73,6 +101,96 @@ library InterfoldPricing { ); } + /// @notice Reject E3 creation while a multi-contract dependency rotation is + /// only partially applied. + /// @dev Requests are permissionless, while governance updates the dependency + /// graph across several transactions. Freezing a mixed graph would make + /// the resulting E3 impossible to complete or slash. + function validateDependencyGraph( + address registry, + address bonding, + address refundManager, + address slashManager + ) external view { + address interfold = address(this); + + _requireContract(bytes32("registry"), registry); + _requireContract(bytes32("bonding"), bonding); + _requireContract(bytes32("refundManager"), refundManager); + _requireContract(bytes32("slashManager"), slashManager); + + _requireRelation( + bytes32("registry.interfold"), + interfold, + ICiphernodeRegistryDependencyView(registry).interfold() + ); + _requireRelation( + bytes32("registry.bonding"), + bonding, + ICiphernodeRegistryDependencyView(registry).bondingRegistry() + ); + _requireRelation( + bytes32("registry.slashing"), + slashManager, + ICiphernodeRegistryDependencyView(registry).slashingManager() + ); + _requireRelation( + bytes32("bonding.registry"), + registry, + IBondingRegistryDependencyView(bonding).registry() + ); + _requireRelation( + bytes32("bonding.slashing"), + slashManager, + IBondingRegistryDependencyView(bonding).slashingManager() + ); + _requireRelation( + bytes32("refund.interfold"), + interfold, + IRefundManagerDependencyView(refundManager).interfold() + ); + _requireRelation( + bytes32("slashing.bonding"), + bonding, + ISlashingManagerDependencyView(slashManager).bondingRegistry() + ); + _requireRelation( + bytes32("slashing.registry"), + registry, + ISlashingManagerDependencyView(slashManager).ciphernodeRegistry() + ); + _requireRelation( + bytes32("slashing.interfold"), + interfold, + ISlashingManagerDependencyView(slashManager).interfold() + ); + _requireRelation( + bytes32("slashing.refund"), + refundManager, + ISlashingManagerDependencyView(slashManager).e3RefundManager() + ); + } + + function _requireContract(bytes32 dependency, address target) private view { + if (target.code.length == 0) { + revert IInterfold.DependencyNotContract(dependency, target); + } + } + + function _requireRelation( + bytes32 relation, + address expected, + address actual + ) private pure { + if (actual != expected) { + revert IInterfold.DependencyGraphMismatch( + relation, + expected, + actual + ); + } + } + function verifyPlaintext( address verifierAddress, address registryAddress, diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index d51851ba7..4e17cea78 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -548,6 +548,10 @@ contract SlashingManager is SlashPolicy memory policy = slashPolicies[reason]; require(policy.enabled, SlashReasonDisabled()); require(!policy.requiresProof, InvalidPolicy()); + require( + _dependenciesFor(e3Id).registry.isCommitteeMember(e3Id, operator), + OperatorNotInCommittee() + ); // Evidence replay protection — reason-independent to prevent cross-reason replay bytes32 evidenceKey = keccak256( diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index bfb163781..fe0d49ca5 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -425,6 +425,24 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect(await interfold.getE3Stage(0)).to.equal(5); // Complete }); + + it("AUD-M04: rejects requests during a partial dependency rotation", async function () { + const { interfold, registry, slashingManager, makeRequest, owner } = + await loadFixture(setup); + + const configuredSlashingManager = await slashingManager.getAddress(); + const partialRotationTarget = await owner.getAddress(); + await registry.connect(owner).setSlashingManager(partialRotationTarget); + + await expect(makeRequest()) + .to.be.revertedWithCustomError(interfold, "DependencyGraphMismatch") + .withArgs( + ethers.encodeBytes32String("registry.slashing"), + configuredSlashingManager, + partialRotationTarget, + ); + expect(await interfold.nexte3Id()).to.equal(0); + }); }); describe("Committee Formed Integration", function () { @@ -1315,9 +1333,9 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { tokenAddress, ), ).to.equal(distributionBefore.protocolAmount + expectedProtocol); - expect( - await e3RefundManager.tokenLiability(tokenAddress), - ).to.equal(slashedAmount); + expect(await e3RefundManager.tokenLiability(tokenAddress)).to.equal( + slashedAmount, + ); }); it("caps cumulative same-token requester compensation before crediting honest nodes", async function () { diff --git a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts index 83148e98b..a55feb629 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts @@ -129,6 +129,7 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function .connect(await ethers.getSigner(addressOne)) .snapshotE3Dependencies(0); await networkHelpers.stopImpersonatingAccount(addressOne); + await mockCiphernodeRegistry.setCommitteeNodes(0, [operatorAddress]); return { owner, diff --git a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts index 2b7cf03bd..7c249a53f 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts @@ -157,6 +157,8 @@ describe("SlashingManager", function () { .connect(await ethers.getSigner(addressOne)) .snapshotE3Dependencies(1); await networkHelpers.stopImpersonatingAccount(addressOne); + await mockCiphernodeRegistry.setCommitteeNodes(0, [operatorAddress]); + await mockCiphernodeRegistry.setCommitteeNodes(1, [operatorAddress]); return { owner, @@ -1103,6 +1105,27 @@ describe("SlashingManager", function () { ), ).to.be.revertedWithCustomError(slashingManager, "ZeroAddress"); }); + + it("should reject evidence for an operator outside the E3 committee", async function () { + const { slashingManager, slasher, notTheOwner } = + await loadFixture(setup); + + await setupPolicies(slashingManager); + + await expect( + slashingManager + .connect(slasher) + .proposeSlashEvidence( + 0, + await notTheOwner.getAddress(), + REASON_INACTIVITY, + ethers.toUtf8Bytes("unrelated operator"), + ), + ).to.be.revertedWithCustomError( + slashingManager, + "OperatorNotInCommittee", + ); + }); }); describe("executeSlash() — Lane B execution", function () { From 6cf16d09bbe3d5f3364cd27912acee3545b801aa Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:48:08 +0500 Subject: [PATCH 42/52] fix(protocol): keep audit event catalogs ABI-aligned --- Cargo.lock | 1 + crates/evm/Cargo.toml | 1 + crates/evm/src/event_decoding/catalog.rs | 126 +++++++++++++++++- .../contracts/Interfold.sol | 1 + .../test/E3Lifecycle/E3Integration.spec.ts | 4 +- 5 files changed, 128 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cec0d491d..0c68128cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3439,6 +3439,7 @@ dependencies = [ "hex", "num-bigint", "serde", + "serde_json", "tokio", "tracing", "tracing-subscriber", diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 063a22218..8415a992c 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -40,5 +40,6 @@ e3-entrypoint = { workspace = true } e3-events = { workspace = true, features = ["test-helpers"] } e3-evm = { workspace = true } e3-test-helpers = { workspace = true } +serde_json = { workspace = true } tokio = { workspace = true, features = ["test-util"] } tracing-subscriber = { workspace = true } diff --git a/crates/evm/src/event_decoding/catalog.rs b/crates/evm/src/event_decoding/catalog.rs index 6c8c28473..9dd2af6e7 100644 --- a/crates/evm/src/event_decoding/catalog.rs +++ b/crates/evm/src/event_decoding/catalog.rs @@ -71,7 +71,7 @@ const INTERFOLD: &[EvmEventDefinition] = &[ EvmEventDefinition::new("E3RefundManagerSet", "E3RefundManagerSet(address)", None), EvmEventDefinition::new( "E3Requested", - "E3Requested(uint256,(uint256,uint8,uint256,uint256[2],bytes32,address,uint8,bytes,address,address,bytes32,bytes32,bytes,address,bool),address)", + "E3Requested(uint256,(uint256,uint8,uint256,uint256[2],bytes32,address,uint8,bytes,address,address,bytes32,bytes32,bytes,address),address)", None, ), EvmEventDefinition::new("E3StageChanged", "E3StageChanged(uint256,uint8,uint8)", Some(1)), @@ -139,7 +139,7 @@ const INTERFOLD: &[EvmEventDefinition] = &[ ), EvmEventDefinition::new( "SlashedFundsEscrowed", - "SlashedFundsEscrowed(uint256,uint256)", + "SlashedFundsEscrowed(uint256,address,uint256)", Some(1), ), EvmEventDefinition::new("SlashingManagerSet", "SlashingManagerSet(address)", None), @@ -181,6 +181,11 @@ const BONDING_REGISTRY: &[EvmEventDefinition] = &[ "ConfigurationUpdated(bytes32,uint256,uint256)", None, ), + EvmEventDefinition::new( + "EligibilityConfigurationVersionUpdated", + "EligibilityConfigurationVersionUpdated(uint256)", + None, + ), EvmEventDefinition::new("Initialized", "Initialized(uint64)", None), EvmEventDefinition::new( "LicenseBondUpdated", @@ -188,6 +193,11 @@ const BONDING_REGISTRY: &[EvmEventDefinition] = &[ None, ), EvmEventDefinition::new("LicenseTokenSet", "LicenseTokenSet(address)", None), + EvmEventDefinition::new( + "LicenseSurplusSwept", + "LicenseSurplusSwept(address,address,uint256)", + None, + ), EvmEventDefinition::new( "LicenseTransferShortfall", "LicenseTransferShortfall(address,uint256,uint256)", @@ -229,6 +239,11 @@ const BONDING_REGISTRY: &[EvmEventDefinition] = &[ "SlashedFundsWithdrawn(address,uint256,uint256)", None, ), + EvmEventDefinition::new( + "SlashingManagerAuthorizationUpdated", + "SlashingManagerAuthorizationUpdated(address,bool)", + None, + ), EvmEventDefinition::new( "SlashingManagerUpdated", "SlashingManagerUpdated(address,address)", @@ -439,9 +454,19 @@ const SLASHING_MANAGER: &[EvmEventDefinition] = &[ "SlashProposed(uint256,uint256,address,bytes32,uint256,uint256,uint256,address,uint8)", Some(2), ), + EvmEventDefinition::new( + "SlashRouteCompleted", + "SlashRouteCompleted(uint256,uint256,address,uint256)", + Some(2), + ), + EvmEventDefinition::new( + "SlashRoutePending", + "SlashRoutePending(uint256,uint256,address,uint256)", + Some(2), + ), EvmEventDefinition::new( "SlashedFundsEscrowedToRefund", - "SlashedFundsEscrowedToRefund(uint256,uint256)", + "SlashedFundsEscrowedToRefund(uint256,address,uint256)", Some(1), ), ]; @@ -449,7 +474,85 @@ const SLASHING_MANAGER: &[EvmEventDefinition] = &[ #[cfg(test)] mod tests { use super::*; - use std::collections::HashSet; + use serde_json::Value; + use std::collections::{HashMap, HashSet}; + use std::fs; + use std::path::PathBuf; + + const CONTRACT_ARTIFACTS: &[(&str, &str)] = &[ + ( + "Interfold", + "artifacts/contracts/Interfold.sol/Interfold.json", + ), + ( + "BondingRegistry", + "artifacts/contracts/registry/BondingRegistry.sol/BondingRegistry.json", + ), + ( + "CiphernodeRegistry", + "artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json", + ), + ( + "SlashingManager", + "artifacts/contracts/slashing/SlashingManager.sol/SlashingManager.json", + ), + ]; + + fn artifact_path(relative: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../packages/interfold-contracts") + .join(relative) + } + + fn canonical_type(input: &Value) -> String { + let abi_type = input["type"].as_str().expect("ABI input type"); + let Some(tuple_suffix) = abi_type.strip_prefix("tuple") else { + return abi_type.to_owned(); + }; + let components = input["components"] + .as_array() + .expect("tuple ABI components") + .iter() + .map(canonical_type) + .collect::>() + .join(","); + format!("({components}){tuple_suffix}") + } + + fn artifact_events(relative: &str) -> HashMap> { + let artifact: Value = + serde_json::from_str(&fs::read_to_string(artifact_path(relative)).unwrap()).unwrap(); + artifact["abi"] + .as_array() + .expect("artifact ABI") + .iter() + .filter(|item| item["type"] == "event") + .map(|event| { + let inputs = event["inputs"].as_array().expect("event inputs"); + let signature = format!( + "{}({})", + event["name"].as_str().expect("event name"), + inputs + .iter() + .map(canonical_type) + .collect::>() + .join(",") + ); + let mut topic_position = 1; + let mut e3_id_topic = None; + for input in inputs { + if !input["indexed"].as_bool().unwrap_or(false) { + continue; + } + if input["name"].as_str() == Some("e3Id") { + e3_id_topic = Some(topic_position); + } + topic_position += 1; + } + (signature, e3_id_topic) + }) + .collect() + } #[test] fn every_contract_catalog_has_unique_topics() { @@ -483,4 +586,19 @@ mod tests { assert_eq!(definition.name, "TreasuryCredited"); assert_eq!(definition.e3_id_topic, Some(1)); } + + #[test] + fn every_watched_contract_catalog_matches_its_current_abi() { + for (contract, artifact) in CONTRACT_ARTIFACTS { + let expected = artifact_events(artifact); + let actual = catalog(contract) + .iter() + .map(|event| (event.signature.to_owned(), event.e3_id_topic)) + .collect::>(); + assert_eq!( + actual, expected, + "{contract} event catalog drifted from {artifact}" + ); + } + } } diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 1c353a912..1bf24be53 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -763,6 +763,7 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { address(_slashingManagerFor(e3Id)) ); _refundManagerFor(e3Id).escrowSlashedFunds(e3Id, token, amount); + emit SlashedFundsEscrowed(e3Id, token, amount); } /// @inheritdoc IInterfold diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index fe0d49ca5..0614a53cb 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -985,7 +985,9 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await usdcToken.balanceOf(refundManagerAddress); await expect(slashingManager.connect(requester).retrySlashRoute(0)) .to.emit(slashingManager, "SlashRouteCompleted") - .withArgs(0, 0, await usdcToken.getAddress(), pending.amount); + .withArgs(0, 0, await usdcToken.getAddress(), pending.amount) + .and.to.emit(interfold, "SlashedFundsEscrowed") + .withArgs(0, await usdcToken.getAddress(), pending.amount); expect( (await usdcToken.balanceOf(refundManagerAddress)) - refundBalanceBefore, From a550e46a8882ae0f3effe4dede8aa8bfe85a5890 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 18:54:36 +0500 Subject: [PATCH 43/52] fix(protocol): name canonical committee ordering precisely [C-03] --- agent/ARCHITECTURE.md | 2 +- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 13 ++++++------- crates/aggregator/src/committee.rs | 4 ++-- .../public_key_aggregation/keyshare_buffer.rs | 2 +- crates/events/src/committee.rs | 2 +- .../interfold_event/committee_finalized.rs | 2 +- .../interfold_event/publickey_aggregated.rs | 2 +- crates/evm/src/ciphernode_registry/events.rs | 2 +- .../src/ciphernode_selection/handlers.rs | 2 +- .../src/sortition/handlers/committee.rs | 2 +- crates/tests/tests/integration.rs | 5 +++-- .../src/circuits/aggregation/node_dkg_fold.rs | 2 +- .../src/proof_verification/handlers.rs | 2 +- .../src/share_verification/handlers.rs | 2 +- docs/pages/internals/sortition.mdx | 19 ++++++++++--------- 15 files changed, 32 insertions(+), 31 deletions(-) diff --git a/agent/ARCHITECTURE.md b/agent/ARCHITECTURE.md index c11b03c73..1a64a6e3e 100644 --- a/agent/ARCHITECTURE.md +++ b/agent/ARCHITECTURE.md @@ -175,7 +175,7 @@ error handling, read the clock, or mutate actor state. Protocol-specific invariants must be named and tested. Important examples include: -- runtime `party_id` is derived from the finalized committee normalized by ascending ticket score; +- runtime `party_id` is derived from the finalized committee normalized by ascending address; - the active aggregator is the lowest non-expelled `party_id`; - the DKG aggregation circuit receives exactly `H` canonical honest NodeFold proofs and exactly `N` ordered committee addresses; diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 76a9f5532..b140c13c9 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -339,7 +339,7 @@ CiphernodeRegistrySolReader decodes SortitionCommitteeFinalized event ├─ Sortition actor: │ └─ Stores finalized committee as a `Committee` struct in persistent map │ → Provides O(1) address→party_id lookup for later expulsion handling -│ → `CommitteeFinalized` is normalized into ascending ticket-score order before storage +│ → `CommitteeFinalized` is normalized into ascending address order before storage │ ├─ CiphernodeSelector: │ ├─ Checks if this node's address is in the committee list @@ -352,7 +352,7 @@ CiphernodeRegistrySolReader decodes SortitionCommitteeFinalized event │ │ Publishes AggregatorChanged { │ │ e3_id, │ │ is_aggregator = (my node has the lowest non-expelled party_id in the -│ │ score-sorted finalized committee) +│ │ address-sorted finalized committee) │ │ } │ └─ If NO: does nothing for this E3 │ @@ -394,14 +394,13 @@ If any deadline is missed → anyone can call markE3Failed() 2. **Snapshot-based eligibility**: Ticket balances are checked at `requestBlock - 1`, preventing front-running manipulation. -3. **Runtime committee order**: the Rust runtime normalizes `CommitteeFinalized` into ascending - ticket-score order before `Sortition` and `CiphernodeSelector` derive `party_id`. That makes - `party_id` in the runtime equivalent to score order, even though the raw on-chain `topNodes` - array is not itself score-sorted. +3. **Runtime committee order**: both the on-chain registry and Rust runtime normalize the finalized + committee into ascending address order before deriving `party_id`. This keeps party IDs, + aggregator failover, proof inputs, and `CommitteeHashLib.hash(topNodes)` aligned. 4. **Active aggregator selection**: `CiphernodeSelector` derives `AggregatorChanged` from the finalized committee plus enriched `CommitteeMemberExpelled` events. The active aggregator is the - lowest non-expelled `party_id` in the score-sorted runtime committee. + lowest non-expelled `party_id` in the address-sorted runtime committee. 5. **Permissionless finalization**: Anyone can call `finalizeCommittee()` after the deadline — no single point of failure. diff --git a/crates/aggregator/src/committee.rs b/crates/aggregator/src/committee.rs index ed13bea53..4129f338e 100644 --- a/crates/aggregator/src/committee.rs +++ b/crates/aggregator/src/committee.rs @@ -22,10 +22,10 @@ pub fn committee_addresses_from_nodes(nodes: &OrderedSet) -> Result, diff --git a/crates/aggregator/src/public_key_aggregation/keyshare_buffer.rs b/crates/aggregator/src/public_key_aggregation/keyshare_buffer.rs index 8af5b9bab..17c97afbd 100644 --- a/crates/aggregator/src/public_key_aggregation/keyshare_buffer.rs +++ b/crates/aggregator/src/public_key_aggregation/keyshare_buffer.rs @@ -124,7 +124,7 @@ impl Handler for KeyshareCreatedFilterBuffer { }, InterfoldEventData::CommitteeFinalized(data) => { let mut data = data.clone(); - data.sort_by_score(); + data.sort_by_address(); self.committee = Some(data.committee); self.process_buffered_events(); } diff --git a/crates/events/src/committee.rs b/crates/events/src/committee.rs index b65a064ec..c2ac8b05f 100644 --- a/crates/events/src/committee.rs +++ b/crates/events/src/committee.rs @@ -143,7 +143,7 @@ mod tests { #[test] fn picks_lowest_non_expelled_party_in_sorted_committee_as_aggregator() { - // Committee order is already score-sorted before it is stored. + // Committee order is canonical address-ascending before it is stored. let committee = Committee::new(vec![ "0xbbb".to_string(), "0xccc".to_string(), diff --git a/crates/events/src/interfold_event/committee_finalized.rs b/crates/events/src/interfold_event/committee_finalized.rs index 01f602952..13156c1b6 100644 --- a/crates/events/src/interfold_event/committee_finalized.rs +++ b/crates/events/src/interfold_event/committee_finalized.rs @@ -24,7 +24,7 @@ impl CommitteeFinalized { /// `topNodes` layout (see `_sortTopNodesByAscendingAddress` in `CiphernodeRegistryOwnable`). /// The node with the numerically lowest address ends up at index 0 (= party 0). /// Address comparison is done in lowercase to be independent of EIP-55 checksumming. - pub fn sort_by_score(&mut self) { + pub fn sort_by_address(&mut self) { let mut indices: Vec = (0..self.committee.len()).collect(); indices.sort_by_key(|&i| self.committee[i].to_lowercase()); diff --git a/crates/events/src/interfold_event/publickey_aggregated.rs b/crates/events/src/interfold_event/publickey_aggregated.rs index bd2d39bfa..bf79afa34 100644 --- a/crates/events/src/interfold_event/publickey_aggregated.rs +++ b/crates/events/src/interfold_event/publickey_aggregated.rs @@ -20,7 +20,7 @@ pub struct PublicKeyAggregated { pub pubkey: ArcBytes, // TODO: ArcBytes ? pub e3_id: E3id, pub nodes: OrderedSet, - /// Full registered committee (`topNodes`) addresses in ascending `party_id` (score) order. + /// Full registered committee (`topNodes`) addresses in ascending `party_id` (address) order. /// Length `N`. Used by `DecryptionAggregator` for `committee_hash_*` public-input binding. pub committee_addresses: Vec
, /// Honest subset of the committee (size `H ≤ N`) in ascending `party_id` order. diff --git a/crates/evm/src/ciphernode_registry/events.rs b/crates/evm/src/ciphernode_registry/events.rs index aa49d2e95..c636f3ef7 100644 --- a/crates/evm/src/ciphernode_registry/events.rs +++ b/crates/evm/src/ciphernode_registry/events.rs @@ -115,7 +115,7 @@ impl From for CommitteeFinalized { scores: value.0.scores.iter().map(|s| s.to_string()).collect(), chain_id: value.1, }; - result.sort_by_score(); + result.sort_by_address(); result } } diff --git a/crates/sortition/src/ciphernode_selection/handlers.rs b/crates/sortition/src/ciphernode_selection/handlers.rs index e2f7479e4..a5f50bc7a 100644 --- a/crates/sortition/src/ciphernode_selection/handlers.rs +++ b/crates/sortition/src/ciphernode_selection/handlers.rs @@ -136,7 +136,7 @@ impl Handler> for CiphernodeSelector { &self.bus.with_ec(msg.get_ctx()), move || { let (mut msg, ec) = msg.into_components(); - msg.sort_by_score(); + msg.sort_by_address(); info!("CiphernodeSelector received CommitteeFinalized."); let bus = self.bus.clone(); info!("Getting selector state..."); diff --git a/crates/sortition/src/sortition/handlers/committee.rs b/crates/sortition/src/sortition/handlers/committee.rs index fb724d2ea..efcd725e0 100644 --- a/crates/sortition/src/sortition/handlers/committee.rs +++ b/crates/sortition/src/sortition/handlers/committee.rs @@ -13,7 +13,7 @@ impl Handler> for Sortition { _ctx: &mut Self::Context, ) -> Self::Result { let (mut msg, ec) = msg.into_components(); - msg.sort_by_score(); + msg.sort_by_address(); trap(EType::Sortition, &self.bus.with_ec(&ec), || { info!( e3_id = %msg.e3_id, diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index d19c2c14a..1a2513df8 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -877,7 +877,8 @@ fn determine_committee( Ok((committee, committee_scores, buffer_nodes)) } -/// Lowest-address committee member after `CommitteeFinalized::sort_by_score` (party 0 / active aggregator). +/// Lowest-address committee member after `CommitteeFinalized::sort_by_address` +/// (party 0 / active aggregator). fn active_aggregator_address( committee: &[String], scores: &[String], @@ -890,7 +891,7 @@ fn active_aggregator_address( scores: scores.to_vec(), chain_id, }; - finalized.sort_by_score(); + finalized.sort_by_address(); finalized .committee .first() diff --git a/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs b/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs index a92ffb1f4..0198e4a07 100644 --- a/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs +++ b/crates/zk-prover/src/circuits/aggregation/node_dkg_fold.rs @@ -375,7 +375,7 @@ pub struct DkgAggregationInput<'a> { pub c5_proof: &'a Proof, /// Honest party ids in the same order as `node_fold_proofs` (e.g. sorted ascending). pub party_ids: &'a [u64], - /// Ordered committee addresses (`topNodes` / sortition order) for `committee_hash_*` public inputs. + /// Address-ordered committee (`topNodes` / party order) for `committee_hash_*` public inputs. pub committee_addresses: &'a [Address], } diff --git a/crates/zk-prover/src/proof_verification/handlers.rs b/crates/zk-prover/src/proof_verification/handlers.rs index a95b4eba4..a13b7c253 100644 --- a/crates/zk-prover/src/proof_verification/handlers.rs +++ b/crates/zk-prover/src/proof_verification/handlers.rs @@ -25,7 +25,7 @@ impl Handler for ProofVerificationActor { InterfoldEventData::CommitteeFinalized(mut data) => { // The EVM decoder already emits canonical address order, but sorting again keeps // this trust boundary correct for replayed/test-produced events as well. - data.sort_by_score(); + data.sort_by_address(); self.store_committee(data.e3_id, &data.committee); } InterfoldEventData::EncryptionKeyReceived(data) => { diff --git a/crates/zk-prover/src/share_verification/handlers.rs b/crates/zk-prover/src/share_verification/handlers.rs index 096c868b2..6f9b21d8a 100644 --- a/crates/zk-prover/src/share_verification/handlers.rs +++ b/crates/zk-prover/src/share_verification/handlers.rs @@ -17,7 +17,7 @@ impl Handler for ShareVerificationActor { InterfoldEventData::CommitteeFinalized(mut data) => { // Mirror the C0 verifier's canonical ordering at this trust boundary. Replayed and // test-produced events are not assumed to have passed through the EVM decoder. - data.sort_by_score(); + data.sort_by_address(); self.store_committee(data.e3_id, &data.committee); } InterfoldEventData::ShareVerificationDispatched(data) => { diff --git a/docs/pages/internals/sortition.mdx b/docs/pages/internals/sortition.mdx index f11a74b35..b4b570ce2 100644 --- a/docs/pages/internals/sortition.mdx +++ b/docs/pages/internals/sortition.mdx @@ -151,23 +151,24 @@ After the submission window closes, anyone can call `finalizeCommittee(e3Id)`: emitted - If fewer → `CommitteeFormationFailed` is emitted and the E3 fails with a refund -`CommitteeFinalized` carries the committee as an ordered list of addresses. The Rust runtime then -**normalizes** this list by **sorting it by ascending score** to produce a canonical party ordering: +Before emitting `CommitteeFinalized`, the registry **normalizes** the selected committee by +**sorting it by ascending address**. The Rust runtime repeats that normalization at its trust +boundary: ``` -party_id = index in score-sorted committee (lowest score = 0) +party_id = index in address-sorted committee (lowest address = 0) ``` -This normalization is done in `CommitteeFinalized::sort_by_score()` and ensures every node -independently derives the same `party_id` for each member, regardless of the order the contract -happened to emit them. +The runtime normalization is done in `CommitteeFinalized::sort_by_address()` and ensures every node +independently derives the same `party_id` for each member, including replayed or test-produced +events that did not pass through the EVM decoder. --- ## Aggregator Assignment -The **active aggregator** is the node with `party_id = 0` — the node that won the lowest overall -score. It is responsible for: +The **active aggregator** is the node with `party_id = 0` — the selected committee member with the +lowest address. It is responsible for: - Collecting and verifying ZK proofs from all other committee members - Aggregating decryption shares @@ -221,7 +222,7 @@ sequenceDiagram Chain->>NodeA: CommitteeFinalized([NodeA, NodeB], [scoreA, scoreB]) Chain->>NodeB: CommitteeFinalized([NodeA, NodeB], [scoreA, scoreB]) - Note over NodeA,NodeB: Both sort by score → same party_ids
Lowest score = party_id 0 (aggregator) + Note over NodeA,NodeB: Both sort by address → same party_ids
Lowest address = party_id 0 (aggregator) ``` --- From 9021c9fe7d9965e092906caafc602d838942ccb6 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 19:06:13 +0500 Subject: [PATCH 44/52] fix(contracts): bound physical exit queue scans [M-01] --- .../contracts/lib/ExitQueueLib.sol | 54 ++++++++++++++++++- .../contracts/test/ExitQueueHarness.sol | 10 ++++ .../test/Registry/ExitQueueLib.spec.ts | 50 +++++++++++++++-- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol index 6cd142d6f..77787b5b0 100644 --- a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol +++ b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol @@ -159,6 +159,12 @@ library ExitQueueLib { ExitTranche[] storage operatorQueue = state.operatorQueues[operator]; + // Keep both asset heads canonical before enforcing the scan-span cap. + // An asset-specific head may otherwise lag behind ticket-only or + // license-only tranches that the other asset's operation drained. + _advanceEmptyHeads(state, operator); + _pruneEmptyTail(state, operator); + uint256 len = operatorQueue.length; bool merged; if (len != 0) { @@ -169,8 +175,19 @@ library ExitQueueLib { lastTrancheIsLive && lastTranche.unlockTimestamp == unlockTimestamp ) { - if (ticketAmount != 0) lastTranche.ticketAmount += ticketAmount; + uint256 lastIndex = len - 1; + if (ticketAmount != 0) { + // This asset head may already have advanced past a + // ticket-empty tranche that is still live for licences. + if (state.queueHeadIndexTicket[operator] > lastIndex) { + state.queueHeadIndexTicket[operator] = lastIndex; + } + lastTranche.ticketAmount += ticketAmount; + } if (licenseAmount != 0) { + if (state.queueHeadIndexLicense[operator] > lastIndex) { + state.queueHeadIndexLicense[operator] = lastIndex; + } lastTranche.licenseAmount += licenseAmount; } merged = true; @@ -178,8 +195,14 @@ library ExitQueueLib { } if (!merged) { + uint256 ticketHead = state.queueHeadIndexTicket[operator]; + uint256 licenseHead = state.queueHeadIndexLicense[operator]; + uint256 earliestHead = ticketHead < licenseHead + ? ticketHead + : licenseHead; require( - state.liveTrancheCount[operator] < MAX_ACTIVE_TRANCHES, + state.liveTrancheCount[operator] < MAX_ACTIVE_TRANCHES && + len - earliestHead < MAX_ACTIVE_TRANCHES, TooManyTranches() ); @@ -525,9 +548,36 @@ library ExitQueueLib { } else { state.queueHeadIndexLicense[operator] = head; } + _advanceEmptyHeads(state, operator); _pruneEmptyTail(state, operator); } + /// @dev Advance both asset heads across tranches that are empty for that + /// asset. This keeps the physical scan span aligned with live work, + /// including when only one asset class is claimed or slashed. + function _advanceEmptyHeads( + ExitQueueState storage state, + address operator + ) private { + ExitTranche[] storage operatorQueue = state.operatorQueues[operator]; + uint256 len = operatorQueue.length; + uint256 ticketHead = state.queueHeadIndexTicket[operator]; + while ( + ticketHead < len && operatorQueue[ticketHead].ticketAmount == 0 + ) { + ticketHead++; + } + state.queueHeadIndexTicket[operator] = ticketHead; + + uint256 licenseHead = state.queueHeadIndexLicense[operator]; + while ( + licenseHead < len && operatorQueue[licenseHead].licenseAmount == 0 + ) { + licenseHead++; + } + state.queueHeadIndexLicense[operator] = licenseHead; + } + /// @dev Remove fully drained tail entries so repeated queue/claim cycles /// cannot grow the scanned history behind an earlier locked tranche. /// Both per-asset heads are clamped because either may have advanced diff --git a/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol b/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol index 0cb6e8eff..1af73b03f 100644 --- a/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol +++ b/packages/interfold-contracts/contracts/test/ExitQueueHarness.sol @@ -55,6 +55,16 @@ contract ExitQueueHarness { _state.queueTicketsForExit(operator, delay, secondTicketAmount); } + function queueTicketThenLicense( + address operator, + uint64 delay, + uint256 ticketAmount, + uint256 licenseAmount + ) external { + _state.queueTicketsForExit(operator, delay, ticketAmount); + _state.queueLicensesForExit(operator, delay, licenseAmount); + } + function liveTrancheCount( address operator ) external view returns (uint256) { diff --git a/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts b/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts index 473f9c3b9..2f2c1cc20 100644 --- a/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts +++ b/packages/interfold-contracts/test/Registry/ExitQueueLib.spec.ts @@ -3,7 +3,6 @@ // This file is provided WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. - import { expect } from "chai"; import { ethers } from "../fixtures"; @@ -34,11 +33,54 @@ describe("ExitQueueLib", function () { expect(await harness.liveTrancheCount(operatorAddress)).to.equal(0); await harness.queue(operatorAddress, 0, 30, 40); - expect(await harness.claim.staticCall(operatorAddress, 30, 40)).to.deep.equal( - [30n, 40n], - ); + expect( + await harness.claim.staticCall(operatorAddress, 30, 40), + ).to.deep.equal([30n, 40n]); await harness.claim(operatorAddress, 30, 40); expect(await harness.queueLength(operatorAddress)).to.equal(0); expect(await harness.liveTrancheCount(operatorAddress)).to.equal(0); }); + + it("revives an asset head when a same-timestamp merge adds that asset", async function () { + const [operator] = await ethers.getSigners(); + const harness = await ethers.deployContract("ExitQueueHarness"); + const operatorAddress = await operator.getAddress(); + + await harness.queueTicketThenLicense(operatorAddress, 0, 10, 20); + expect(await harness.queueLength(operatorAddress)).to.equal(1); + expect( + await harness.claim.staticCall(operatorAddress, 10, 20), + ).to.deep.equal([10n, 20n]); + }); + + it("caps the physical scan span when fully drained holes remain between live tranches", async function () { + const [operator] = await ethers.getSigners(); + const harness = await ethers.deployContract("ExitQueueHarness"); + const operatorAddress = await operator.getAddress(); + + // Keep live licence-only tranches between ticket-only tranches so slashing + // the tickets creates interior holes that tail pruning cannot remove. + let ticketTotal = 0n; + for (let i = 0; i < 64; i++) { + const isTicket = i % 2 === 1; + await harness.queue( + operatorAddress, + i + 1, + isTicket ? 1 : 0, + isTicket ? 0 : 1, + ); + if (isTicket) ticketTotal++; + } + + await harness.slash(operatorAddress, ticketTotal, 0); + expect(await harness.queueLength(operatorAddress)).to.equal(63); + expect(await harness.liveTrancheCount(operatorAddress)).to.equal(32); + + // One new tranche fills the remaining span slot; another would make a + // claim/slash scan exceed MAX_ACTIVE_TRANCHES despite only 34 live entries. + await harness.queue(operatorAddress, 100, 1, 0); + await expect( + harness.queue(operatorAddress, 101, 1, 0), + ).to.be.revertedWithCustomError(harness, "TooManyTranches"); + }); }); From 8243e939566b231f8926b0ed43c16be267687864 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 19:09:00 +0500 Subject: [PATCH 45/52] docs(protocol): align proof and sortition guarantees --- agent/flow-trace/00_INDEX.md | 26 +++++++++---------- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 5 ++-- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 4 +-- crates/bfv-client/src/client.rs | 2 +- .../src/interfold_event/e3_requested.rs | 2 +- crates/tests/tests/integration.rs | 2 +- docs/pages/building-with-interfold.mdx | 4 +-- .../tickets-and-sortition.mdx | 2 +- docs/pages/computation-flow.mdx | 4 +-- docs/pages/cryptography.mdx | 10 +++---- docs/pages/internals/sortition.mdx | 15 +++++------ .../contracts/Interfold.sol | 7 +++-- .../contracts/interfaces/IInterfold.sol | 2 +- 13 files changed, 40 insertions(+), 45 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 73089b21b..11a24ec46 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -170,18 +170,18 @@ _Found during source-code cross-referencing of these trace documents._ ### Critical Doc Inaccuracies (now fixed) -| # | Description | Where | Fix Applied | -| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | --------------- | -| 1 | `addTicketBalance` does NOT multiply by `ticketPrice` — raw stablecoin units are passed directly to `ticketToken.depositFrom()`. `ticketPrice` is only used in the activation check. | BondingRegistry.sol:371 | 02_TOKENS | -| 2 | `removeTicketBalance` does NOT multiply by `ticketPrice` — raw amount passed to `ticketToken.burnTickets()`. | BondingRegistry.sol:395 | 02_TOKENS | -| 3 | `gracePeriod` is NOT added to deadline checks in `_checkFailureCondition()`. All timeout checks compare `block.timestamp` directly against the raw deadline. `gracePeriod` is only validated in `_setTimeoutConfig` but never referenced in failure detection. | Interfold.sol:860-887 | 05_FAILURE | -| 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION | -| 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IInterfold.sol:82 | 03_E3_REQUEST | -| 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | -| 7 | `publishCommittee()` is now permissionless. The effective access control is DKG proof verification plus the single-publish guard `publicKeyHashes[e3Id] == 0`; the old `onlyOwner` note is obsolete. | CiphernodeRegistryOwnable.sol | 04_DKG | -| 8 | `CommitteePublished` emits `(e3Id, nodes, publicKey, pkCommitment, proof)`. The full PK bytes are an untrusted transport hint that the indexer decodes and checks against the on-chain commitment before storage; that commitment is C5-proven when proof aggregation is enabled. | CiphernodeRegistryOwnable.sol | 04_DKG | -| 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | -| 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | +| # | Description | Where | Fix Applied | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | --------------- | +| 1 | `addTicketBalance` does NOT multiply by `ticketPrice` — raw stablecoin units are passed directly to `ticketToken.depositFrom()`. `ticketPrice` is only used in the activation check. | BondingRegistry.sol:371 | 02_TOKENS | +| 2 | `removeTicketBalance` does NOT multiply by `ticketPrice` — raw amount passed to `ticketToken.burnTickets()`. | BondingRegistry.sol:395 | 02_TOKENS | +| 3 | `gracePeriod` is NOT added to deadline checks in `_checkFailureCondition()`. All timeout checks compare `block.timestamp` directly against the raw deadline. `gracePeriod` is only validated in `_setTimeoutConfig` but never referenced in failure detection. | Interfold.sol:860-887 | 05_FAILURE | +| 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION | +| 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IInterfold.sol:82 | 03_E3_REQUEST | +| 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | +| 7 | `publishCommittee()` is now permissionless. The effective access control is DKG proof verification plus the single-publish guard `publicKeyHashes[e3Id] == 0`; the old `onlyOwner` note is obsolete. | CiphernodeRegistryOwnable.sol | 04_DKG | +| 8 | `CommitteePublished` emits `(e3Id, nodes, publicKey, pkCommitment, proof)`. The full PK bytes are an untrusted transport hint that the indexer decodes and checks against the on-chain commitment before storage; that commitment is C5-proven by the mandatory final DKG proof. | CiphernodeRegistryOwnable.sol | 04_DKG | +| 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | +| 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | ### Protocol Design Concerns @@ -219,5 +219,5 @@ _Found during source-code cross-referencing of these trace documents._ | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Matching fee-token slashes fill the requester’s exact original-payment shortfall before rewarding honest nodes; different-token slashes go to the requester as priority compensation without asserting oracle-free equivalence. Fee-token refunds never relabel slash amounts, and every outbound transfer preserves a protected per-token liability. | | 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | -| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven only when proof aggregation is enabled. | +| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven by the mandatory final DKG proof. | | 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index b140c13c9..c9e315866 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -61,9 +61,8 @@ Requester calls: Interfold.request({ │ │ → freezes refund/slash allocation, treasury, policy version, │ │ request-time Interfold, and request-time committee registry │ ├─ seed = uint256(keccak256(block.prevrandao, e3Id)) -│ │ → On chains without `prevrandao`, the value is still deterministic -│ │ per-block; downstream sortition relies on the per-E3 snapshot of -│ │ ticket balances at `requestBlock - 1` for manipulation resistance. +│ │ → Shared per-E3 ticket-scoring input only; not BFV key material and +│ │ not relied upon for cryptographic unpredictability. │ │ │ ├─ encryptionSchemeId = e3Program.validate( │ │ e3Id, seed, e3ProgramParams, computeProviderParams, customParams diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index bb849b676..42152f276 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -687,8 +687,8 @@ circuit's public-key commitment using the request's parameter set, and requires event's on-chain `pkCommitment`. TypeScript event consumers receive the same `pkCommitment` and use `InterfoldSDK.validatePublicKeyCommitment()` before accepting the bytes; the default application does this before advancing to encryption. Malformed bytes or bytes for a different key fail closed -and never reach first-party encryption clients. The commitment is C5-proven when proof aggregation -is enabled; the explicitly unsafe development mode trusts it from the aggregator. +and never reach first-party encryption clients. Production verifies the C5-backed final DKG proof +on-chain; the explicit test/CI skip mode works only with mock verifiers that trust its placeholder. > **C-08 (BfvPkVerifier domain binding) — implemented** The wrapper exposes a > `verify(e3Id, committeeRoot, sortedNodes, pkCommitment, committeeHash, proof)` signature. diff --git a/crates/bfv-client/src/client.rs b/crates/bfv-client/src/client.rs index 167b28011..7f26a2037 100644 --- a/crates/bfv-client/src/client.rs +++ b/crates/bfv-client/src/client.rs @@ -186,7 +186,7 @@ pub fn compute_pk_commitment( } /// Validate client-consumed BFV public-key bytes against the on-chain -/// commitment (C5-proven when proof aggregation is enabled). +/// commitment (C5-proven by the mandatory final DKG proof). /// /// Validation is semantic rather than byte-for-byte: `fhe.rs` normalizes an /// internal variable-time flag while decoding threshold-aggregated keys, so a diff --git a/crates/events/src/interfold_event/e3_requested.rs b/crates/events/src/interfold_event/e3_requested.rs index 069cdf025..3bbc6f546 100644 --- a/crates/events/src/interfold_event/e3_requested.rs +++ b/crates/events/src/interfold_event/e3_requested.rs @@ -20,7 +20,7 @@ pub struct E3Requested { pub threshold_m: usize, /// The total committee size for the round pub threshold_n: usize, - /// A seed to provide randomness for the round + /// Shared per-E3 seed for deterministic ticket scoring. pub seed: Seed, /// The error size for the FHE computation. This can be calculated for the E3 program based on /// the size of the ciphertext and the depth of the program [tbd add link] diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 1a2513df8..6b6610280 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -800,7 +800,7 @@ fn compute_committee_scores(committee: &[String], e3_id: &E3id, seed: Seed) -> V /// /// # Arguments /// * `e3_id` - The E3 computation ID -/// * `seed` - The random seed for sortition +/// * `seed` - The shared seed for deterministic sortition /// * `threshold_m` - Minimum nodes required for decryption /// * `threshold_n` - Committee size /// * `registered_addrs` - List of node addresses eligible for selection diff --git a/docs/pages/building-with-interfold.mdx b/docs/pages/building-with-interfold.mdx index 0af92edb7..30979247c 100644 --- a/docs/pages/building-with-interfold.mdx +++ b/docs/pages/building-with-interfold.mdx @@ -155,8 +155,8 @@ function getTimeoutConfig() external view returns (E3TimeoutConfig memory config ) external returns (uint256 e3Id); ``` -2. Contract validates the parameters, estimates the fee, and stores an `E3` record seeded with a - random value derived from block entropy. +2. Contract validates the parameters, estimates the fee, and stores an `E3` record with the shared + seed used for deterministic ticket scoring. 3. The E3 program's `validate` hook returns the encryption scheme ID, which is persisted on the `E3` struct. 4. Committee selection is delegated to the `ciphernodeRegistry` via `requestCommittee`. diff --git a/docs/pages/ciphernode-operators/tickets-and-sortition.mdx b/docs/pages/ciphernode-operators/tickets-and-sortition.mdx index 9ef9b0fef..faa470b99 100644 --- a/docs/pages/ciphernode-operators/tickets-and-sortition.mdx +++ b/docs/pages/ciphernode-operators/tickets-and-sortition.mdx @@ -76,7 +76,7 @@ Look for the "Ticket balance" and "available" fields in the output. ## Sortition Algorithm -The sortition process selects committees deterministically based on a random seed: +The sortition process selects committees deterministically from a shared per-E3 seed: ```mermaid flowchart TD diff --git a/docs/pages/computation-flow.mdx b/docs/pages/computation-flow.mdx index a80221194..c46ed940d 100644 --- a/docs/pages/computation-flow.mdx +++ b/docs/pages/computation-flow.mdx @@ -36,8 +36,8 @@ server, Compute Providers, or other network participants. ``` The contract validates the parameters, transfers the required fee from the requester, creates an - `E3` record with a random seed derived from `block.prevrandao`, and delegates committee selection - to the `CiphernodeRegistry`. + `E3` record with a per-E3 ticket-scoring seed derived from `block.prevrandao`, and delegates + committee selection to the `CiphernodeRegistry`. You can estimate the fee before submitting by calling `getE3Quote`: diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx index 893bc99f2..3529f0baa 100644 --- a/docs/pages/cryptography.mdx +++ b/docs/pages/cryptography.mdx @@ -37,11 +37,11 @@ rather than on blind trust in operators. Each confidential workload is an FHE program running inside an **E3**. After someone requests such a computation, a committee of **N** [ciphernodes](https://blog.theinterfold.com/ciphernodes/) is drawn -using **sortition**, so membership is tied to verifiable randomness rather than to a central picker. -There is **no trusted dealer** handing out key material: every ciphernode contributes its own share -of the work and takes part in **PVDKG** (publicly verifiable DKG), producing proofs alongside -messages so that deviating parties can be identified and excluded without exposing other nodes’ -secrets. +using **sortition**, so membership follows publicly checkable ticket scoring rather than a central +picker. There is **no trusted dealer** handing out key material: every ciphernode contributes its +own share of the work and takes part in **PVDKG** (publicly verifiable DKG), producing proofs +alongside messages so that deviating parties can be identified and excluded without exposing other +nodes’ secrets. Write **A** for the **authorised** committee for that E3—initially all **N** members, then shrinking as proofs fail and bad actors drop out of the active path. When DKG finishes, the network publishes diff --git a/docs/pages/internals/sortition.mdx b/docs/pages/internals/sortition.mdx index b4b570ce2..316ae955d 100644 --- a/docs/pages/internals/sortition.mdx +++ b/docs/pages/internals/sortition.mdx @@ -18,7 +18,7 @@ each E3 request. The design goals are: --- -## Randomness Source +## Sortition Seed The seed for every sortition round is derived from the EVM's `block.prevrandao` (EIP-4399) combined with the E3 identifier: @@ -27,13 +27,10 @@ with the E3 identifier: seed = uint256(keccak256(abi.encode(block.prevrandao, e3Id))); ``` -`prevrandao` is the RANDAO reveal from the beacon chain. It is unpredictable before the block is -proposed but publicly verifiable afterwards. Combining it with `e3Id` ensures different rounds -produce independent seeds even within the same block. - -> **Limitation:** `prevrandao` is weak against a block proposer who can selectively withhold their -> block. This is an accepted trade-off for now; future versions may use a commit-reveal scheme or an -> external VRF. +This is a convenient shared per-E3 value for deterministic ticket scoring. It is not fed into BFV +key generation, and the protocol does not treat it as cryptographically unbiasable randomness. +`prevrandao` can be influenced by block production; a future design can use a stronger randomness +source if the sortition threat model comes to require one. The seed is emitted with `E3Requested` and used by all nodes to deterministically compute the same committee. @@ -231,7 +228,7 @@ sequenceDiagram | Property | Mechanism | | ----------------------- | ------------------------------------------------------------------- | -| **Unpredictability** | `seed` derived from `prevrandao` — unknown until block proposal | +| **Shared seed** | `prevrandao` and `e3Id` provide a common per-E3 scoring input | | **Proportionality** | Expected min-score over N tickets scales with N | | **Sybil-resistance** | Splitting tickets across wallets doesn't improve expected min-score | | **Determinism** | Hash function is identical on-chain (Solidity) and off-chain (Rust) | diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index 1bf24be53..cb0817555 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -286,10 +286,9 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { e3Id, address(dependencies.registry) ); - // Seed uses block.prevrandao combined with e3Id as additional entropy. - // While prevrandao is not cryptographically unpredictable (validator-controlled), - // the combination with the unique, incrementing e3Id mitigates manipulation. - // The seed is used solely for weighted sortition, not for cryptographic key generation. + // The seed is only a shared per-E3 input to deterministic ticket + // scoring; it is not BFV key material and the protocol does not rely + // on it for cryptographic unpredictability. uint256 seed = uint256(keccak256(abi.encode(block.prevrandao, e3Id))); e3Payments[e3Id] = e3Fee; diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index 85c2f57d3..bfb0575f2 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -354,7 +354,7 @@ interface IInterfold { /// @param output The invalid output data. error InvalidOutput(bytes output); - /// @notice Thrown when proof aggregation is enabled but no proof was supplied. + /// @notice Thrown when a mandatory final proof was not supplied. error ProofRequired(); /// @notice Thrown when the committee size has not been configured with thresholds. From 66e3f49859ca5b7d649326c23b74af5d9c16b856 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 19:13:55 +0500 Subject: [PATCH 46/52] refactor(contracts): keep exit queue checks lint-clean [M-01] --- .../contracts/lib/ExitQueueLib.sol | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol index 77787b5b0..ebe55b546 100644 --- a/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol +++ b/packages/interfold-contracts/contracts/lib/ExitQueueLib.sol @@ -166,33 +166,13 @@ library ExitQueueLib { _pruneEmptyTail(state, operator); uint256 len = operatorQueue.length; - bool merged; - if (len != 0) { - ExitTranche storage lastTranche = operatorQueue[len - 1]; - bool lastTrancheIsLive = lastTranche.ticketAmount != 0 || - lastTranche.licenseAmount != 0; - if ( - lastTrancheIsLive && - lastTranche.unlockTimestamp == unlockTimestamp - ) { - uint256 lastIndex = len - 1; - if (ticketAmount != 0) { - // This asset head may already have advanced past a - // ticket-empty tranche that is still live for licences. - if (state.queueHeadIndexTicket[operator] > lastIndex) { - state.queueHeadIndexTicket[operator] = lastIndex; - } - lastTranche.ticketAmount += ticketAmount; - } - if (licenseAmount != 0) { - if (state.queueHeadIndexLicense[operator] > lastIndex) { - state.queueHeadIndexLicense[operator] = lastIndex; - } - lastTranche.licenseAmount += licenseAmount; - } - merged = true; - } - } + bool merged = _mergeIntoTail( + state, + operator, + unlockTimestamp, + ticketAmount, + licenseAmount + ); if (!merged) { uint256 ticketHead = state.queueHeadIndexTicket[operator]; @@ -229,6 +209,45 @@ library ExitQueueLib { ); } + /** + * @dev Merges assets into the live tail when its timestamp matches. + * Revives an asset-specific head if that asset had previously been + * drained from a tranche still kept alive by the other asset. + */ + function _mergeIntoTail( + ExitQueueState storage state, + address operator, + uint64 unlockTimestamp, + uint256 ticketAmount, + uint256 licenseAmount + ) private returns (bool merged) { + ExitTranche[] storage operatorQueue = state.operatorQueues[operator]; + uint256 len = operatorQueue.length; + if (len == 0) return false; + + uint256 lastIndex = len - 1; + ExitTranche storage lastTranche = operatorQueue[lastIndex]; + bool lastTrancheIsLive = lastTranche.ticketAmount != 0 || + lastTranche.licenseAmount != 0; + if ( + !lastTrancheIsLive || lastTranche.unlockTimestamp != unlockTimestamp + ) return false; + + if (ticketAmount != 0) { + if (state.queueHeadIndexTicket[operator] > lastIndex) { + state.queueHeadIndexTicket[operator] = lastIndex; + } + lastTranche.ticketAmount += ticketAmount; + } + if (licenseAmount != 0) { + if (state.queueHeadIndexLicense[operator] > lastIndex) { + state.queueHeadIndexLicense[operator] = lastIndex; + } + lastTranche.licenseAmount += licenseAmount; + } + return true; + } + /** * @notice Queues only tickets for exit with a time delay * @dev Convenience function that calls queueAssetsForExit with licenseAmount = 0 From 50d5916444d6bd75d3a97aa52f3279b57f7754a4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 19:33:39 +0500 Subject: [PATCH 47/52] fix(ci): compile ABI artifacts before catalog tests --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dc24f1af..9dd0800d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,9 @@ jobs: - name: Checking code format rust run: pnpm rust:lint + - name: Compile contracts for ABI catalog tests + run: pnpm evm:build + - name: Run Unit Tests run: 'cargo test --lib && cargo test --doc' From 4157b8068e089be2b98a0781869a3f9bf65591dd Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 20:25:49 +0500 Subject: [PATCH 48/52] fix(ci): preserve source-aligned Noir fixtures --- tests/integration/lib/clean_folders.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration/lib/clean_folders.sh b/tests/integration/lib/clean_folders.sh index db12e5a43..b121d2901 100755 --- a/tests/integration/lib/clean_folders.sh +++ b/tests/integration/lib/clean_folders.sh @@ -4,7 +4,15 @@ clean_folders() { # Delete output artifacts rm -rf "$SCRIPT_DIR/output/"* - rm -rf "$SCRIPT_DIR/.interfold/" + + # Reset per-run node state without deleting the source-aligned Noir + # artifacts staged by prebuild.sh. Removing the whole .interfold directory + # makes `interfold noir setup` download the released circuit bundle, which + # can have an older ABI than the Rust witness code under test. + rm -rf \ + "$SCRIPT_DIR/.interfold/config" \ + "$SCRIPT_DIR/.interfold/data" \ + "$SCRIPT_DIR/.interfold/noir/work" } clean_folders $1 From e8a2f7ea1f9c7b357ae3c5e1d6db6ef56da86bb6 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 21:25:54 +0500 Subject: [PATCH 49/52] fix(ci): restore proof runner executable bit --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dd0800d4..c1ff9346d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -590,6 +590,7 @@ jobs: chmod +x target/debug/fake_encrypt chmod +x target/debug/pack_e3_params chmod +x ~/.cargo/bin/interfold + chmod +x tests/integration/.interfold/noir/bin/bb - name: 'Run ${{ matrix.test-suite }} tests' run: 'pnpm test:integration ${{ matrix.test-suite }} --no-prebuild' - name: 'Add test summary' From 461a3475ca66a538f9462421e808cbe3a02136d0 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 17 Jul 2026 22:19:03 +0500 Subject: [PATCH 50/52] refactor(contracts): remove request graph validation [M-04] --- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 5 +- .../interfaces/IInterfold.sol/IInterfold.json | 39 +----- .../contracts/Interfold.sol | 6 - .../contracts/interfaces/IInterfold.sol | 10 -- .../contracts/lib/InterfoldPricing.sol | 118 ------------------ .../test/E3Lifecycle/E3Integration.spec.ts | 18 --- 6 files changed, 2 insertions(+), 194 deletions(-) diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index c9e315866..a34508c2d 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -39,10 +39,7 @@ Requester calls: Interfold.request({ │ ├─ inputWindow[0] >= block.timestamp (start in future) │ ├─ inputWindow[1] >= inputWindow[0] (end after start) │ ├─ total duration < maxDuration -│ ├─ e3Programs[e3Program] == true (program whitelisted) -│ └─ registry / bonding / refund / slashing pointers form one -│ mutually consistent deployed-contract graph -│ → partial multi-transaction admin rotations fail closed +│ └─ e3Programs[e3Program] == true (program whitelisted) │ ├─ FEE CALCULATION: │ ├─ fee = getE3Quote() diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 05979878f..84e5fc500 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -111,43 +111,6 @@ "name": "CommitteeSizeTooSmall", "type": "error" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "relation", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "expected", - "type": "address" - }, - { - "internalType": "address", - "name": "actual", - "type": "address" - } - ], - "name": "DependencyGraphMismatch", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "dependency", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "DependencyNotContract", - "type": "error" - }, { "inputs": [ { @@ -2413,5 +2376,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-b08e2b9fb42fa5cb0f49eb46a9e6e77e295b46b0" + "buildInfoId": "solc-0_8_28-cba8eb9c8c72239d405341adafff7527f0fb462b" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/Interfold.sol b/packages/interfold-contracts/contracts/Interfold.sol index cb0817555..d714936c6 100644 --- a/packages/interfold-contracts/contracts/Interfold.sol +++ b/packages/interfold-contracts/contracts/Interfold.sol @@ -269,12 +269,6 @@ contract Interfold is IInterfold, Ownable2StepUpgradeable { _timeoutConfig.decryptionWindow, maxDuration ); - InterfoldPricing.validateDependencyGraph( - address(ciphernodeRegistry), - address(bondingRegistry), - address(e3RefundManager), - address(slashingManager) - ); e3Id = nexte3Id; nexte3Id++; diff --git a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol index bfb0575f2..acf74791f 100644 --- a/packages/interfold-contracts/contracts/interfaces/IInterfold.sol +++ b/packages/interfold-contracts/contracts/interfaces/IInterfold.sol @@ -381,16 +381,6 @@ interface IInterfold { /// @param bondingRegistry The invalid bonding registry address. error InvalidBondingRegistry(IBondingRegistry bondingRegistry); - /// @notice A required lifecycle dependency is not deployed contract code. - error DependencyNotContract(bytes32 dependency, address target); - - /// @notice Cross-contract lifecycle pointers do not describe one coherent deployment. - error DependencyGraphMismatch( - bytes32 relation, - address expected, - address actual - ); - /// @notice Thrown when attempting to set an invalid fee token address. /// @param feeToken The invalid fee token address. error InvalidFeeToken(IERC20 feeToken); diff --git a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol index 27fce0ebe..e72ebd868 100644 --- a/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol +++ b/packages/interfold-contracts/contracts/lib/InterfoldPricing.sol @@ -9,34 +9,6 @@ import { IInterfold } from "../interfaces/IInterfold.sol"; import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; import { IDecryptionVerifier } from "../interfaces/IDecryptionVerifier.sol"; -interface ICiphernodeRegistryDependencyView { - function interfold() external view returns (address); - - function bondingRegistry() external view returns (address); - - function slashingManager() external view returns (address); -} - -interface IBondingRegistryDependencyView { - function registry() external view returns (address); - - function slashingManager() external view returns (address); -} - -interface IRefundManagerDependencyView { - function interfold() external view returns (address); -} - -interface ISlashingManagerDependencyView { - function bondingRegistry() external view returns (address); - - function ciphernodeRegistry() external view returns (address); - - function interfold() external view returns (address); - - function e3RefundManager() external view returns (address); -} - /** * @title InterfoldPricing * @notice External library extracted from {Interfold} to keep its deployed @@ -101,96 +73,6 @@ library InterfoldPricing { ); } - /// @notice Reject E3 creation while a multi-contract dependency rotation is - /// only partially applied. - /// @dev Requests are permissionless, while governance updates the dependency - /// graph across several transactions. Freezing a mixed graph would make - /// the resulting E3 impossible to complete or slash. - function validateDependencyGraph( - address registry, - address bonding, - address refundManager, - address slashManager - ) external view { - address interfold = address(this); - - _requireContract(bytes32("registry"), registry); - _requireContract(bytes32("bonding"), bonding); - _requireContract(bytes32("refundManager"), refundManager); - _requireContract(bytes32("slashManager"), slashManager); - - _requireRelation( - bytes32("registry.interfold"), - interfold, - ICiphernodeRegistryDependencyView(registry).interfold() - ); - _requireRelation( - bytes32("registry.bonding"), - bonding, - ICiphernodeRegistryDependencyView(registry).bondingRegistry() - ); - _requireRelation( - bytes32("registry.slashing"), - slashManager, - ICiphernodeRegistryDependencyView(registry).slashingManager() - ); - _requireRelation( - bytes32("bonding.registry"), - registry, - IBondingRegistryDependencyView(bonding).registry() - ); - _requireRelation( - bytes32("bonding.slashing"), - slashManager, - IBondingRegistryDependencyView(bonding).slashingManager() - ); - _requireRelation( - bytes32("refund.interfold"), - interfold, - IRefundManagerDependencyView(refundManager).interfold() - ); - _requireRelation( - bytes32("slashing.bonding"), - bonding, - ISlashingManagerDependencyView(slashManager).bondingRegistry() - ); - _requireRelation( - bytes32("slashing.registry"), - registry, - ISlashingManagerDependencyView(slashManager).ciphernodeRegistry() - ); - _requireRelation( - bytes32("slashing.interfold"), - interfold, - ISlashingManagerDependencyView(slashManager).interfold() - ); - _requireRelation( - bytes32("slashing.refund"), - refundManager, - ISlashingManagerDependencyView(slashManager).e3RefundManager() - ); - } - - function _requireContract(bytes32 dependency, address target) private view { - if (target.code.length == 0) { - revert IInterfold.DependencyNotContract(dependency, target); - } - } - - function _requireRelation( - bytes32 relation, - address expected, - address actual - ) private pure { - if (actual != expected) { - revert IInterfold.DependencyGraphMismatch( - relation, - expected, - actual - ); - } - } - function verifyPlaintext( address verifierAddress, address registryAddress, diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 0614a53cb..da04ab29a 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -425,24 +425,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect(await interfold.getE3Stage(0)).to.equal(5); // Complete }); - - it("AUD-M04: rejects requests during a partial dependency rotation", async function () { - const { interfold, registry, slashingManager, makeRequest, owner } = - await loadFixture(setup); - - const configuredSlashingManager = await slashingManager.getAddress(); - const partialRotationTarget = await owner.getAddress(); - await registry.connect(owner).setSlashingManager(partialRotationTarget); - - await expect(makeRequest()) - .to.be.revertedWithCustomError(interfold, "DependencyGraphMismatch") - .withArgs( - ethers.encodeBytes32String("registry.slashing"), - configuredSlashingManager, - partialRotationTarget, - ); - expect(await interfold.nexte3Id()).to.equal(0); - }); }); describe("Committee Formed Integration", function () { From 0459b0ed8e25e51a18b3ca4ee3eb6784a02b046a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 18 Jul 2026 14:42:26 +0500 Subject: [PATCH 51/52] fix: gate proof aggregation skip behind test feature --- .github/workflows/ci.yml | 6 ++- agent/flow-trace/00_INDEX.md | 2 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 4 +- crates/ciphernode-builder/Cargo.toml | 3 ++ .../src/ciphernode_builder.rs | 1 + crates/cli/Cargo.toml | 5 ++ crates/config/src/app_config.rs | 5 +- crates/entrypoint/Cargo.toml | 5 ++ crates/entrypoint/src/start/start.rs | 53 ++++++++++++++++--- crates/tests/Cargo.toml | 3 +- docs/pages/internals/dkg.mdx | 8 +-- examples/CRISP/interfold.config.yaml | 3 +- templates/default/interfold.config.yaml | 3 +- tests/integration/fns.sh | 10 +++- tests/integration/interfold.config.yaml | 3 +- 15 files changed, 90 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1ff9346d..7093df2e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -592,7 +592,9 @@ jobs: chmod +x ~/.cargo/bin/interfold chmod +x tests/integration/.interfold/noir/bin/bb - name: 'Run ${{ matrix.test-suite }} tests' - run: 'pnpm test:integration ${{ matrix.test-suite }} --no-prebuild' + run: | + INTERFOLD_BIN="$HOME/.cargo/bin/interfold" \ + pnpm test:integration ${{ matrix.test-suite }} --no-prebuild - name: 'Add test summary' run: | echo "## Test results for ${{ matrix.test-suite }}" >> $GITHUB_STEP_SUMMARY @@ -628,7 +630,7 @@ jobs: && sudo apt-get install -y solc protobuf-compiler - name: Build interfold CLI - run: cargo install --locked --path crates/cli --bin interfold + run: cargo install --locked --path crates/cli --bin interfold --features test-only-skip-proof-aggregation - name: Upload interfold binary uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 11a24ec46..757e47f4c 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -218,6 +218,6 @@ _Found during source-code cross-referencing of these trace documents._ | 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | | 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | | 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Matching fee-token slashes fill the requester’s exact original-payment shortfall before rewarding honest nodes; different-token slashes go to the requester as priority compensation without asserting oracle-free equivalence. Fee-token refunds never relabel slash amounts, and every outbound transfer preserves a protected per-token liability. | -| 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. A ciphernode-only `skip_proof_aggregation` test/CI flag skips recursive workers while mock deployments verify non-empty C5/C7 placeholders; production verifiers reject those placeholders. | +| 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. Only binaries compiled with the `test-only-skip-proof-aggregation` Cargo feature can honor the ciphernode `skip_proof_aggregation` test/CI setting; production builds reject it. Feature-gated test nodes use non-empty C5/C7 placeholders accepted by mock deployments, while production verifiers reject those placeholders. | | 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven by the mandatory final DKG proof. | | 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 42152f276..e05d56ae4 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -638,7 +638,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm committee public key is still unset ├─ Encodes the DkgAggregator proof in production - ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C5 proof as a + ├─ Feature-gated test/CI nodes with `skip_proof_aggregation` reuse the non-empty C5 proof as a │ mock-verifier placeholder; this does not bypass contract verification │ and every node in a test swarm must use the same flag value └─ Calls contract.publishCommittee(e3_id, publicKey, pkCommitment, proof) @@ -911,7 +911,7 @@ InterfoldSolReader decodes CiphertextOutputPublished event ├─ Requires active_aggregators[e3_id] == true ├─ Reads chain state to confirm plaintextOutput is still empty ├─ Encodes the final DecryptionAggregator proof in production - ├─ Test/CI nodes with `skip_proof_aggregation` reuse the non-empty C7 proof as a + ├─ Feature-gated test/CI nodes with `skip_proof_aggregation` reuse the non-empty C7 proof as a │ mock-verifier placeholder; this does not bypass contract verification │ and every node in a test swarm must use the same flag value └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) diff --git a/crates/ciphernode-builder/Cargo.toml b/crates/ciphernode-builder/Cargo.toml index be212b33c..aeb68865f 100644 --- a/crates/ciphernode-builder/Cargo.toml +++ b/crates/ciphernode-builder/Cargo.toml @@ -6,6 +6,9 @@ license.workspace = true description = "E3 - Ciphernode Builder" repository = "https://github.com/theinterfold/interfold/crates/ciphernode-builder" +[features] +test-only-skip-proof-aggregation = [] + [dependencies] actix.workspace = true alloy.workspace = true diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index b88a566c8..571fc8856 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -332,6 +332,7 @@ impl CiphernodeBuilder { /// This only changes local ciphernode work. Contracts still verify the final DKG and /// decryption proof payloads, so this setting is only useful with test deployments whose /// configured mock verifiers accept the non-empty C5/C7 placeholder payloads. + #[cfg(feature = "test-only-skip-proof-aggregation")] pub fn with_proof_aggregation_disabled_for_testing(mut self) -> Self { self.proof_aggregation_enabled = false; self diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 7239c9df3..b77de1f77 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -7,6 +7,11 @@ description = "E3 - Interfold CLI" repository = "https://github.com/theinterfold/interfold/crates/cli" build = "build.rs" +[features] +test-only-skip-proof-aggregation = [ + "e3-entrypoint/test-only-skip-proof-aggregation", +] + [[bin]] name = "interfold" path = "src/main.rs" diff --git a/crates/config/src/app_config.rs b/crates/config/src/app_config.rs index e220d9d19..b53d657ba 100644 --- a/crates/config/src/app_config.rs +++ b/crates/config/src/app_config.rs @@ -77,7 +77,8 @@ pub struct NodeDefinition { #[serde(default = "default_max_buffered_net_bytes")] pub max_buffered_net_bytes: usize, /// Test/CI-only escape hatch that skips recursive DKG and decryption proof aggregation. - /// On-chain verification remains mandatory, so this requires mock verifiers. + /// On-chain verification remains mandatory, so this requires mock verifiers and a binary + /// compiled with the `test-only-skip-proof-aggregation` Cargo feature. pub skip_proof_aggregation: bool, } @@ -425,7 +426,7 @@ impl AppConfig { self.node_def().max_buffered_net_bytes } - /// Whether this node skips recursive proof aggregation for test/CI runs. + /// Whether this node requests the compile-time-gated proof aggregation skip for test/CI runs. pub fn skip_proof_aggregation(&self) -> bool { self.node_def().skip_proof_aggregation } diff --git a/crates/entrypoint/Cargo.toml b/crates/entrypoint/Cargo.toml index 72f64194e..5762f93a5 100644 --- a/crates/entrypoint/Cargo.toml +++ b/crates/entrypoint/Cargo.toml @@ -6,6 +6,11 @@ license.workspace = true description = "E3 - CLI Entrypoints" repository = "https://github.com/theinterfold/interfold/crates/entrypoint" +[features] +test-only-skip-proof-aggregation = [ + "e3-ciphernode-builder/test-only-skip-proof-aggregation", +] + [dependencies] actix = { workspace = true } e3-aggregator = { workspace = true } diff --git a/crates/entrypoint/src/start/start.rs b/crates/entrypoint/src/start/start.rs index f6c81c2a2..b0c986104 100644 --- a/crates/entrypoint/src/start/start.rs +++ b/crates/entrypoint/src/start/start.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use e3_ciphernode_builder::{CiphernodeBuilder, CiphernodeHandle}; use e3_config::AppConfig; use e3_crypto::Cipher; @@ -14,7 +14,9 @@ use rand_chacha::ChaCha20Rng; use std::future::Future; use std::sync::{Arc, Mutex}; use std::time::Duration; -use tracing::{info, instrument, warn}; +#[cfg(feature = "test-only-skip-proof-aggregation")] +use tracing::warn; +use tracing::{info, instrument}; async fn await_startup(future: F, timeout: Duration) -> Result where @@ -25,8 +27,20 @@ where .with_context(|| format!("ciphernode startup did not complete within {timeout:?}"))? } +fn validate_proof_aggregation_mode(skip_proof_aggregation: bool) -> Result<()> { + if skip_proof_aggregation && !cfg!(feature = "test-only-skip-proof-aggregation") { + bail!( + "`skip_proof_aggregation` is test/CI-only and this binary was built without the \ + `test-only-skip-proof-aggregation` Cargo feature" + ); + } + Ok(()) +} + #[instrument(name = "app", skip_all)] pub async fn execute(config: &AppConfig) -> Result { + validate_proof_aggregation_mode(config.skip_proof_aggregation())?; + let rng = Arc::new(Mutex::new( ChaCha20Rng::try_from_os_rng().context("failed to seed ChaCha20 RNG from OS")?, )); @@ -48,7 +62,7 @@ pub async fn execute(config: &AppConfig) -> Result { "Ciphernode startup deadline configured" ); - let mut builder = CiphernodeBuilder::new(rng.clone(), cipher.clone()) + let builder = CiphernodeBuilder::new(rng.clone(), cipher.clone()) .with_name(&config.name()) .with_logging() .with_persistence(&config.log_file(), &config.db_file()) @@ -71,13 +85,18 @@ pub async fn execute(config: &AppConfig) -> Result { .with_net(config.peers(), config.quic_port()) .with_shared_store() .with_shared_eventstore(); - if config.skip_proof_aggregation() { + + #[cfg(feature = "test-only-skip-proof-aggregation")] + let builder = if config.skip_proof_aggregation() { warn!( - "Skipping recursive proof aggregation for this test/CI node; \ + "Skipping recursive proof aggregation for this feature-gated test/CI node; \ on-chain final proof verification remains mandatory" ); - builder = builder.with_proof_aggregation_disabled_for_testing(); - } + builder.with_proof_aggregation_disabled_for_testing() + } else { + builder + }; + let build = builder.build(); let node = await_startup(build, startup_timeout).await?; @@ -86,7 +105,7 @@ pub async fn execute(config: &AppConfig) -> Result { #[cfg(test)] mod tests { - use super::await_startup; + use super::{await_startup, validate_proof_aggregation_mode}; use anyhow::{bail, Result}; use std::time::Duration; @@ -114,4 +133,22 @@ mod tests { } Ok(()) } + + #[cfg(not(feature = "test-only-skip-proof-aggregation"))] + #[test] + fn production_build_rejects_proof_aggregation_skip() { + let error = validate_proof_aggregation_mode(true) + .expect_err("production build must reject proof aggregation skipping"); + assert!(error + .to_string() + .contains("test-only-skip-proof-aggregation")); + assert!(validate_proof_aggregation_mode(false).is_ok()); + } + + #[cfg(feature = "test-only-skip-proof-aggregation")] + #[test] + fn test_feature_allows_proof_aggregation_skip() { + assert!(validate_proof_aggregation_mode(true).is_ok()); + assert!(validate_proof_aggregation_mode(false).is_ok()); + } } diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index bbe0f2e52..b0b02c0f6 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -18,7 +18,7 @@ clap = { workspace = true } e3-aggregator = { workspace = true } e3-config = { workspace = true } e3-crypto = { workspace = true } -e3-ciphernode-builder = { workspace = true } +e3-ciphernode-builder = { workspace = true, features = ["test-only-skip-proof-aggregation"] } e3-data = { workspace = true } e3-events = { workspace = true } e3-evm = { workspace = true } @@ -54,4 +54,3 @@ zeroize = { workspace = true } [dev-dependencies] e3-events = { workspace = true, features = ["test-helpers"] } - diff --git a/docs/pages/internals/dkg.mdx b/docs/pages/internals/dkg.mdx index 6c9a1c5aa..730281e78 100644 --- a/docs/pages/internals/dkg.mdx +++ b/docs/pages/internals/dkg.mdx @@ -335,9 +335,11 @@ per-circuit proofs on-chain. Instead the aggregator submits two recursive proofs for decryption — together with per-node attestations that bind each surfaced commitment to a specific registered operator. -Ciphernodes expose a test/CI-only `skip_proof_aggregation` node setting. It skips the recursive -workers and reuses the already-generated C5/C7 proofs as non-empty placeholders for mock verifiers. -Contracts still execute their verifier calls, so this flag cannot weaken a production deployment. +Ciphernodes built with the `test-only-skip-proof-aggregation` Cargo feature expose a test/CI-only +`skip_proof_aggregation` node setting. It skips the recursive workers and reuses the +already-generated C5/C7 proofs as non-empty placeholders for mock verifiers. Production builds do +not contain the disabling path and reject configurations that request it. Contracts still execute +their verifier calls. ### Canonical party_id assignment diff --git a/examples/CRISP/interfold.config.yaml b/examples/CRISP/interfold.config.yaml index 951fd8c46..5c584033e 100644 --- a/examples/CRISP/interfold.config.yaml +++ b/examples/CRISP/interfold.config.yaml @@ -45,7 +45,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 - # # Test/CI only. Requires mock on-chain proof verifiers. + # # Test/CI only. Requires mock on-chain proof verifiers and an Interfold binary built with + # # the `test-only-skip-proof-aggregation` Cargo feature. # skip_proof_aggregation: false nodes: cn1: diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index acc55da65..fda781360 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -43,7 +43,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 - # # Test/CI only. Requires mock on-chain proof verifiers. + # # Test/CI only. Requires mock on-chain proof verifiers and an Interfold binary built with + # # the `test-only-skip-proof-aggregation` Cargo feature. # skip_proof_aggregation: false nodes: cn1: diff --git a/tests/integration/fns.sh b/tests/integration/fns.sh index 32a0a6bd5..8256b4eb7 100644 --- a/tests/integration/fns.sh +++ b/tests/integration/fns.sh @@ -34,7 +34,15 @@ CIPHERNODE_ADDRESS_4="0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" CIPHERNODE_ADDRESS_5="0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" -if command -v interfold >/dev/null 2>&1; then +if [[ -n "${INTERFOLD_BIN:-}" ]]; then + if [[ ! -x "$INTERFOLD_BIN" ]]; then + echo "Configured INTERFOLD_BIN is not executable: $INTERFOLD_BIN" >&2 + exit 1 + fi +elif [[ "${CIPHERNODE_SKIP_PROOF_AGGREGATION:-false}" == "true" ]]; then + cargo build --locked --bin interfold --features e3-cli/test-only-skip-proof-aggregation + INTERFOLD_BIN="$ROOT_DIR/target/debug/interfold" +elif command -v interfold >/dev/null 2>&1; then INTERFOLD_BIN="interfold" elif [[ -f "$ROOT_DIR/target/debug/interfold" ]]; then INTERFOLD_BIN="$ROOT_DIR/target/debug/interfold" diff --git a/tests/integration/interfold.config.yaml b/tests/integration/interfold.config.yaml index bc75c672d..16fd9f85e 100644 --- a/tests/integration/interfold.config.yaml +++ b/tests/integration/interfold.config.yaml @@ -45,7 +45,8 @@ program: # node: # multithread_reserve_threads: 1 # multithread_concurrent_jobs: 8 - # # Test/CI only. Requires mock on-chain proof verifiers. + # # Test/CI only. Requires mock on-chain proof verifiers and an Interfold binary built with + # # the `test-only-skip-proof-aggregation` Cargo feature. # skip_proof_aggregation: false nodes: From 247da43d0237413cfa428a42ebcee2036f355cbb Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 18 Jul 2026 15:57:56 +0500 Subject: [PATCH 52/52] fix: attribute failed E3 settlement by fault --- agent/flow-trace/00_INDEX.md | 88 ++++--- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 171 ++++++------ .../contracts/E3RefundManager.sol | 96 +++---- .../contracts/interfaces/IE3RefundManager.sol | 20 +- .../contracts/slashing/SlashingManager.sol | 10 + .../test/E3Lifecycle/E3Integration.spec.ts | 243 +++++++++++++----- .../test/Slashing/SlashingManager.spec.ts | 36 +++ 7 files changed, 426 insertions(+), 238 deletions(-) diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 757e47f4c..2f140a9d2 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -8,7 +8,7 @@ | 2 | [02_TOKENS_AND_ACTIVATION.md](02_TOKENS_AND_ACTIVATION.md) | FOLD license bonding, USDC→tFOLD ticket purchasing, unbonding, burning, exit queue, claiming. Activation thresholds and the `_updateOperatorStatus` formula. | | 3 | [03_E3_REQUEST_AND_COMMITTEE.md](03_E3_REQUEST_AND_COMMITTEE.md) | E3 request on-chain flow, fee payment, committee request, IMT snapshot. Rust-side sortition (score-based), on-chain ticket submission, committee finalization, `CiphernodeSelected` event. | | 4 | [04_DKG_AND_COMPUTATION.md](04_DKG_AND_COMPUTATION.md) | Full DKG with ZK proof pipeline: BFV keygen → C0 proof → encryption key exchange → TrBFV share generation → C1/C2/C3 proofs → share verification → Shamir secret sharing → encrypted share broadcast → C4 proofs → decryption key reconstruction. C5 proof for PK aggregation. Ciphertext output → C6 proof for decryption shares → C7 proof for plaintext → rewards. | -| 5 | [05_FAILURE_REFUND_SLASHING.md](05_FAILURE_REFUND_SLASHING.md) | Timeout-based failure detection, `markE3Failed`, `processE3Failure`. Refund calculation (work-value allocation). Off-chain AccusationManager quorum protocol (proof failure → accusation → voting → quorum). Lane A (attestation-based, atomic) and Lane B (evidence-based, with appeals) slashing. Ticket/license slashing. Slashed funds escrow and routing. | +| 5 | [05_FAILURE_REFUND_SLASHING.md](05_FAILURE_REFUND_SLASHING.md) | Timeout-based failure detection, `markE3Failed`, `processE3Failure`. Fault-attributed refunds: requester/DP/CP failures pay completed work from fee escrow; supplier/ciphernode failures return all fee escrow and compensate honest nodes from ticket slashes. Off-chain accusation, Lane A/B slashing, and slashed-fund routing. | | 6 | [06_DEACTIVATION_AND_COMPLETION.md](06_DEACTIVATION_AND_COMPLETION.md) | Voluntary deactivation (ticket/license withdrawal), full deregistration (IMT removal), E3 happy-path completion, node shutdown, sync/restart, exit queue timing, ban/unban. | --- @@ -125,14 +125,18 @@ 16. PROCESS Anyone calls processE3Failure(e3Id) → Payment transferred to E3RefundManager - → Work-value allocation calculated (BPS-based) + → FailureReason mapped to Requester or Ciphernodes liability -17. REFUND Requester claims proportional refund - Honest nodes claim proportional compensation - Protocol treasury gets 5% +17. REFUND Requester/DP/CP fault: + → completed work + protocol share paid from fee escrow + → requester claims the unspent remainder + Supplier/ciphernode fault: + → requester claims 100% of fee escrow + → no protocol fee or base fee-funded node payment 18. SLASHED $ If slashed funds escrowed: - → Failure: requester filled FIRST, surplus → honest nodes + → Failure: actual slash asset → active honest nodes + → No honest nodes: slash asset → snapshotted treasury → Success: nodes + treasury split (successSlashedNodeBps) ``` @@ -185,39 +189,39 @@ _Found during source-code cross-referencing of these trace documents._ ### Protocol Design Concerns -| # | Concern | Severity | Detail | -| --- | --------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | **Deregister-before-slash race** | Resolved | One unresolved-proposal counter covers both lanes. Every authorized current or retained historical slashing manager participates in the BondingRegistry exit gate, so rotation cannot release collateral belonging to an in-flight E3. Ticket withdrawal, license unbonding, deregistration, and exit claims remain blocked until execution, upheld appeal, or permissionless expiry terminates the proposal. | -| 2 | **Committee publication decentralized** | Resolved | `publishCommittee()` is permissionless. Off-chain role selection chooses the active aggregator, while on-chain C5 proof verification and the single-publish guard prevent invalid or duplicate committee publication. | -| 3 | **`gracePeriod` is dead code** | Medium | `gracePeriod` is stored and validated during config updates but never actually used in any timeout check. Either the deadlines already bake in sufficient buffer, or this is a missing feature. | -| 4 | **`activate` CLI command is misleading** | Low | Named "activate" but actually calls "register" — will fail for already-registered operators. There's no standalone way to trigger re-evaluation of active status; instead, `_updateOperatorStatus()` runs automatically inside `addTicketBalance()`, `bondLicense()`, etc. | -| 5 | **Active-job load balancing bug fixed** | Info | The Rust `NodeStateStore.available_tickets()` subtracts `active_jobs` from total tickets, reducing the chance of busy nodes being selected for new E3s. Previously, the `Sortition` actor's `Handler` was missing match arms for `E3Failed` and `E3StageChanged`, causing these events to fall to the default `_ => ()` — the typed handlers for decrementing jobs were dead code. This has been fixed: E3Failed and E3StageChanged are now routed to their handlers, and `finalized_committees` is cleaned up in `decrement_jobs_for_e3` to prevent unbounded memory growth. | -| 6 | **Committee member expulsion** | Resolved | `SlashingManager` can call `expelCommitteeMember()` mid-DKG. The `Sortition` actor enriches the raw `CommitteeMemberExpelled` event with the expelled member's `party_id` (resolved from its stored `Committee` list) and re-publishes it. `ThresholdKeyshare` uses the enriched `party_id` to update its collectors. The public-key keyshare gate and aggregation reducer normalize both expulsion records and self-reported keyshare nodes to `alloy::Address`, so an expelled member cannot re-enter by changing address casing. `ThresholdKeyshare` itself does not hold committee state. | -| 7 | **ProofRequestActor failure bridge fixed** | Info | `ProofRequestActor` no longer leaves proof publication suppressed under log-only "will not be published" exits. `ComputeRequestError` and local proof-signing failures for DKG-path proofs (`C0` through `C5`) now emit `E3Failed { failed_at_stage: CommitteeFinalized, reason: DKGInvalidShares }`, while decryption-path proofs (`C6` and `C7`) emit `E3Failed { failed_at_stage: CiphertextReady, reason: DecryptionInvalidShares }`. | -| 8 | **Settlement receipts isolated** | Resolved | Durable EVM receipts such as `RewardCredited` and `RewardClaimed` are global audit/projection facts. The E3 router no longer sends them into a completed per-E3 context, so reward fan-out cannot reopen a finished E3 or produce false `AlreadyCompleted` failures. | -| 9 | **Replay-safe compute effects** | Resolved | `ComputeEffectGate` subscribes before EventStore replay, buffers `ComputeRequest`s while effects are disabled, deduplicates equivalent requests, prefers the newest hydrated retry, cancels terminal E3 work, and releases pending effects only after `EffectsEnabled`. This closes the mid-E3 compute-loss window without changing durable event order. | -| 10 | **Restarted active aggregator decryption recovery** | Resolved | A node killed after key publication but before ciphertext publication could miss the one-shot `AggregatorChanged` role event before its plaintext buffer existed, lack recovered committee dependencies when ciphertext arrived, or rehydrate without the per-E3 `CommitmentConsistencyChecker` needed to answer C6 verification gates. `ThresholdPlaintextAggregatorExtension` records aggregator role, remembers pending ciphertext until committee facts arrive, recovers full committee data from public-key state/replayed public events, recovers the honest subset from public-key or threshold-keyshare state, `CommitmentConsistencyCheckerExtension` recreates its per-E3 checker from recovered meta, and restart recovery never blocks the router on synchronous store reads. | -| 11 | **Large EventStore replay startup failure** | Resolved | Restart replay now awaits each EventBus handler result before submitting the next persisted event. A backlog larger than the 2,560-entry EventBus mailbox no longer fails startup with `SendError::Full`; progress is logged every 10,000 EventBus-handled events. The EventBus still fans out with unacknowledged `do_send`, and replay still materializes the full range in memory, so end-to-end queue and memory bounds remain open. | -| 12 | **Canonical DKG H/N aggregation shape** | Resolved | The Rust DKG aggregation path no longer asserts that honest proof count `H` equals full committee size `N`. Every supported preset has `H < N`; validation now requires exactly `H` NodeFold proofs with unique in-range party IDs and exactly `N` ordered committee addresses, matching the producer and compiled Noir witness. The removed assertion affected debug/test binaries; circuit semantics and public inputs are unchanged. | -| 13 | **Forward-compatible E3 version skew** | Resolved | A well-formed `E3Requested` carrying an unsupported committee-size or BFV-preset enum is now classified as a benign skip for that older binary. The parser emits an internal `Processed` marker so historical ordering and startup can advance without dispatching the E3. ABI decode failures and missing indexed topics remain rejected and fail the chain pipeline closed. | -| 14 | **Post-startup network lag recovery** | Resolved | `NetEventBuffer` still fails readiness closed if its broadcast input lags during startup buffering, because historical/live reconciliation would be incomplete. Once `SyncEnded` has moved it to `Running`, a `Lagged(n)` signal is warned and skipped, and the ingress receive loop continues from the oldest retained event instead of stopping the buffer and permanently deafening gossip consumers. | -| 15 | **Local/network event redelivery collision** | Resolved | EventStore now treats the same HLC timestamp plus stable event ID and equal payload as an idempotent duplicate even when transport context differs (`Local` versus `Net`). It retains the first stored context and still fails closed when different payloads claim the same timestamp, preserving collision detection without panicking on sync/resync redelivery. | -| 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | -| 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | -| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity. | -| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, and callers can query either role explicitly. | -| 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | -| 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | -| 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | -| 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | -| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | Governance may update ticket price, license thresholds, and minimum-ticket policy. Each effective update advances a policy version and invalidates cached activity in O(1); registered operators fail closed until a permissionless refresh re-evaluates them under the new version, keeping on-chain counts and Rust sortition state synchronized. | -| 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | -| 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | -| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, or bad gap consumption; baseline creation is a separate explicit maintainer command. Initial pricing is now passed as a typed initializer struct and written through the same validated setter path, with no hard-coded storage-slot writer. | -| 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | -| 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | -| 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | -| 31 | **Token-aware slash accounting (AUD H-01)** | Resolved | Ticket slashes carry their actual underlying ERC-20 through routing, settlement, and token-specific pull claims. Matching fee-token slashes fill the requester’s exact original-payment shortfall before rewarding honest nodes; different-token slashes go to the requester as priority compensation without asserting oracle-free equivalence. Fee-token refunds never relabel slash amounts, and every outbound transfer preserves a protected per-token liability. | -| 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. Only binaries compiled with the `test-only-skip-proof-aggregation` Cargo feature can honor the ciphernode `skip_proof_aggregation` test/CI setting; production builds reject it. Feature-gated test nodes use non-empty C5/C7 placeholders accepted by mock deployments, while production verifiers reject those placeholders. | -| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven by the mandatory final DKG proof. | -| 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | +| # | Concern | Severity | Detail | +| --- | --------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | **Deregister-before-slash race** | Resolved | One unresolved-proposal counter covers both lanes. Every authorized current or retained historical slashing manager participates in the BondingRegistry exit gate, so rotation cannot release collateral belonging to an in-flight E3. Ticket withdrawal, license unbonding, deregistration, and exit claims remain blocked until execution, upheld appeal, or permissionless expiry terminates the proposal. | +| 2 | **Committee publication decentralized** | Resolved | `publishCommittee()` is permissionless. Off-chain role selection chooses the active aggregator, while on-chain C5 proof verification and the single-publish guard prevent invalid or duplicate committee publication. | +| 3 | **`gracePeriod` is dead code** | Medium | `gracePeriod` is stored and validated during config updates but never actually used in any timeout check. Either the deadlines already bake in sufficient buffer, or this is a missing feature. | +| 4 | **`activate` CLI command is misleading** | Low | Named "activate" but actually calls "register" — will fail for already-registered operators. There's no standalone way to trigger re-evaluation of active status; instead, `_updateOperatorStatus()` runs automatically inside `addTicketBalance()`, `bondLicense()`, etc. | +| 5 | **Active-job load balancing bug fixed** | Info | The Rust `NodeStateStore.available_tickets()` subtracts `active_jobs` from total tickets, reducing the chance of busy nodes being selected for new E3s. Previously, the `Sortition` actor's `Handler` was missing match arms for `E3Failed` and `E3StageChanged`, causing these events to fall to the default `_ => ()` — the typed handlers for decrementing jobs were dead code. This has been fixed: E3Failed and E3StageChanged are now routed to their handlers, and `finalized_committees` is cleaned up in `decrement_jobs_for_e3` to prevent unbounded memory growth. | +| 6 | **Committee member expulsion** | Resolved | `SlashingManager` can call `expelCommitteeMember()` mid-DKG. The `Sortition` actor enriches the raw `CommitteeMemberExpelled` event with the expelled member's `party_id` (resolved from its stored `Committee` list) and re-publishes it. `ThresholdKeyshare` uses the enriched `party_id` to update its collectors. The public-key keyshare gate and aggregation reducer normalize both expulsion records and self-reported keyshare nodes to `alloy::Address`, so an expelled member cannot re-enter by changing address casing. `ThresholdKeyshare` itself does not hold committee state. | +| 7 | **ProofRequestActor failure bridge fixed** | Info | `ProofRequestActor` no longer leaves proof publication suppressed under log-only "will not be published" exits. `ComputeRequestError` and local proof-signing failures for DKG-path proofs (`C0` through `C5`) now emit `E3Failed { failed_at_stage: CommitteeFinalized, reason: DKGInvalidShares }`, while decryption-path proofs (`C6` and `C7`) emit `E3Failed { failed_at_stage: CiphertextReady, reason: DecryptionInvalidShares }`. | +| 8 | **Settlement receipts isolated** | Resolved | Durable EVM receipts such as `RewardCredited` and `RewardClaimed` are global audit/projection facts. The E3 router no longer sends them into a completed per-E3 context, so reward fan-out cannot reopen a finished E3 or produce false `AlreadyCompleted` failures. | +| 9 | **Replay-safe compute effects** | Resolved | `ComputeEffectGate` subscribes before EventStore replay, buffers `ComputeRequest`s while effects are disabled, deduplicates equivalent requests, prefers the newest hydrated retry, cancels terminal E3 work, and releases pending effects only after `EffectsEnabled`. This closes the mid-E3 compute-loss window without changing durable event order. | +| 10 | **Restarted active aggregator decryption recovery** | Resolved | A node killed after key publication but before ciphertext publication could miss the one-shot `AggregatorChanged` role event before its plaintext buffer existed, lack recovered committee dependencies when ciphertext arrived, or rehydrate without the per-E3 `CommitmentConsistencyChecker` needed to answer C6 verification gates. `ThresholdPlaintextAggregatorExtension` records aggregator role, remembers pending ciphertext until committee facts arrive, recovers full committee data from public-key state/replayed public events, recovers the honest subset from public-key or threshold-keyshare state, `CommitmentConsistencyCheckerExtension` recreates its per-E3 checker from recovered meta, and restart recovery never blocks the router on synchronous store reads. | +| 11 | **Large EventStore replay startup failure** | Resolved | Restart replay now awaits each EventBus handler result before submitting the next persisted event. A backlog larger than the 2,560-entry EventBus mailbox no longer fails startup with `SendError::Full`; progress is logged every 10,000 EventBus-handled events. The EventBus still fans out with unacknowledged `do_send`, and replay still materializes the full range in memory, so end-to-end queue and memory bounds remain open. | +| 12 | **Canonical DKG H/N aggregation shape** | Resolved | The Rust DKG aggregation path no longer asserts that honest proof count `H` equals full committee size `N`. Every supported preset has `H < N`; validation now requires exactly `H` NodeFold proofs with unique in-range party IDs and exactly `N` ordered committee addresses, matching the producer and compiled Noir witness. The removed assertion affected debug/test binaries; circuit semantics and public inputs are unchanged. | +| 13 | **Forward-compatible E3 version skew** | Resolved | A well-formed `E3Requested` carrying an unsupported committee-size or BFV-preset enum is now classified as a benign skip for that older binary. The parser emits an internal `Processed` marker so historical ordering and startup can advance without dispatching the E3. ABI decode failures and missing indexed topics remain rejected and fail the chain pipeline closed. | +| 14 | **Post-startup network lag recovery** | Resolved | `NetEventBuffer` still fails readiness closed if its broadcast input lags during startup buffering, because historical/live reconciliation would be incomplete. Once `SyncEnded` has moved it to `Running`, a `Lagged(n)` signal is warned and skipped, and the ingress receive loop continues from the oldest retained event instead of stopping the buffer and permanently deafening gossip consumers. | +| 15 | **Local/network event redelivery collision** | Resolved | EventStore now treats the same HLC timestamp plus stable event ID and equal payload as an idempotent duplicate even when transport context differs (`Local` versus `Net`). It retains the first stored context and still fails closed when different payloads claim the same timestamp, preserving collision detection without panicking on sync/resync redelivery. | +| 16 | **Crash-torn event-log tail** | Resolved | Startup validates active-segment frames against the commitlog index, truncates only an unindexed CRC/length-invalid physical suffix, and restores complete CRC-valid/decodable records whose tail index entry was lost. Indexed corruption remains fatal. Runtime reads return correlated errors instead of panicking query handlers, and `node validate --repair` exposes the same narrowly bounded recovery explicitly. | +| 17 | **DAppNode v0.1.8 schema upgrade** | Resolved | DAppNode v0.2.3 is shipped as the required compatibility bridge. Its entrypoint atomically renames the legacy `/data/.enclave` state root to `/data/.interfold` (and refuses ambiguous dual roots), preserves existing encrypted identities, and then relies on the v0.2.3 release's one-time schema-1 stamp. Later fail-closed binaries therefore see a proven marker instead of permanently rejecting the shipped v0.1.8 datastore. | +| 18 | **Exit-queue stale-head cap (AUD M-01)** | Resolved | `ExitQueueLib` caps the explicit number of non-empty tranches instead of deriving a shared count from the minimum of two asset-specific heads. Fully drained ticket-only or license-only tranches release capacity. | +| 19 | **Dual-role refund claims (AUD M-02)** | Resolved | `E3RefundManager` tracks requester-refund and honest-node claims in independent ledgers. An account that legitimately holds both roles can claim both allocations once, and callers can query either role explicitly. | +| 20 | **Sale deployment front-running (AUD L-01)** | Resolved | `InterfoldTokenSaleDeployer` snapshots the factory deployer as its immutable deployment operator. Only that wallet can consume a sale config, preventing copied mempool calldata from triggering the one-shot launch while preserving the configured Safe as FOLD owner. | +| 21 | **Invalid verifier trust anchors (AUD L-02)** | Resolved | BFV verifier constructors now require a deployed circuit-verifier contract and nonzero recursive VK hashes. Invalid production parameters fail at deployment instead of creating permanently unusable or incorrectly anchored wrappers. | +| 22 | **Core contract bytecode headroom (AUD L-03)** | Resolved | Contracts CI now measures the production runtime bytecode for `Interfold`, `DkgAggregatorVerifier`, and `DecryptionAggregatorVerifier`, failing releases that leave less than 256 bytes below the EIP-170 limit. | +| 23 | **Bonding asset rotation (AUD M-08)** | Resolved | Ticket and license assets can rotate only after all balances denominated in the old asset have drained. New assets must be deployed contracts, with a narrowly scoped initial zero FOLD placeholder retained only for circular deployment. | +| 24 | **Mutable eligibility policy (AUD M-03)** | Resolved | Governance may update ticket price, license thresholds, and minimum-ticket policy. Each effective update advances a policy version and invalidates cached activity in O(1); registered operators fail closed until a permissionless refresh re-evaluates them under the new version, keeping on-chain counts and Rust sortition state synchronized. | +| 25 | **Requester fee consent (AUD H-02)** | Accepted | Requesters can call `getE3Quote` immediately before `request`; the existing input-window start bounds when a request can execute. The protocol intentionally does not add per-request fee-slippage or duplicate deadline fields to the core request ABI. | +| 26 | **Deferred slash collateral gate (AUD H-03)** | Resolved | One unresolved-proposal counter now covers both slashing lanes. Ticket withdrawals, license unbonding, deregistration, and queued exit claims remain blocked until execution, an upheld appeal, or the permissionless appeal-expiry path terminates every proposal. | +| 27 | **Upgradeable storage baselines (AUD H-04)** | Resolved | The remediated pre-deployment layouts are committed as the first production baselines with source/build provenance. The read-only CI gate fails on missing baselines, compiler drift, nested layout incompatibility, or bad gap consumption; baseline creation is a separate explicit maintainer command. Initial pricing is now passed as a typed initializer struct and written through the same validated setter path, with no hard-coded storage-slot writer. | +| 28 | **Per-E3 settlement policy (AUD M-07)** | Resolved | Each request snapshots the refund/slash work allocation, treasury, and policy version. Later governance updates cannot retroactively alter failure refunds, success-path slash splits, dust, residual routing, or orphan recovery for an in-flight E3. | +| 29 | **In-flight dependency rotation (AUD M-04)** | Resolved | Each request freezes the complete lifecycle dependency graph across Interfold, committee registry, slashing manager, bonding registry, and refund manager. Rotations set defaults for later E3s while existing E3s continue callbacks, verification, rewards, failure settlement, expulsion, and slash routing through the original deployments. | +| 30 | **Retryable slashed-fund routing (AUD M-05)** | Resolved | Every ticket slash reserves its underlying asset against treasury withdrawal and records a durable proposal-scoped route before escrow is attempted. Temporary payout or accounting failures leave the route permissionlessly retryable; transfer, escrow accounting, and route consumption remain atomic and completed retries are idempotent. | +| 31 | **Fault-attributed, token-aware failure settlement (AUD H-01)** | Resolved | `FailureReason` is exhaustively classified as requester-side or supplier/ciphernode-side. Requester/DP/CP failures pay completed work from the request-time fee allocation. Supplier/ciphernode failures return 100% of fee escrow with no protocol cut; honest nodes are compensated only from actual ticket slashes. Slash assets retain their ERC-20 denomination in independent pull claims, so no oracle conversion or relabeling occurs. Failure-triggering slash policies must expel the faulty operator before honest recipients are resolved. | +| 32 | **Proof-disabled publication bypass (AUD C-02)** | Fixed | E3 requests no longer carry a proof-aggregation switch and both final verifier calls are mandatory. Only binaries compiled with the `test-only-skip-proof-aggregation` Cargo feature can honor the ciphernode `skip_proof_aggregation` test/CI setting; production builds reject it. Feature-gated test nodes use non-empty C5/C7 placeholders accepted by mock deployments, while production verifiers reject those placeholders. | +| 33 | **Client public-key commitment binding (AUD C-01)** | Resolved | Serialized public-key event bytes are treated as an untrusted transport hint. Rust indexers decode the key and store it only when its recomputed circuit commitment equals the on-chain value; the TypeScript SDK exposes the event commitment and a semantic validator, which the default app runs before encryption. Calldata substitution therefore cannot become a different first-party client encryption key. The commitment itself is C5-proven by the mandatory final DKG proof. | +| 34 | **Cross-E3 decryption-proof replay (AUD C-03)** | Resolved | Every secret-bearing C6 proof commits to a domain over `(chainId, Interfold address, e3Id, committeeHash, ciphertextOutputHash, committeePublicKey)`. C6 folding requires one common domain, the final DecryptionAggregator proof exposes it, and the BFV wrapper rejects any domain that differs from the value recomputed by `Interfold`. This prevents cross-chain, cross-deployment, cross-E3, cross-committee, cross-ciphertext, and cross-key replay without a global consumed-proof storage ledger. | diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 805f3f174..29886bd93 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -2,9 +2,16 @@ ## Overview -An E3 can fail at any stage due to timeouts, insufficient participants, or misbehavior. When failure -is detected, the protocol refunds the requester (proportional to work not completed), compensates -honest nodes, and slashes misbehaving operators. +An E3 can fail at any stage due to timeouts, insufficient participants, or misbehavior. Settlement +first attributes economic responsibility from `FailureReason`: + +- Requester, data-provider, or compute-provider failures pay completed work and the protocol share + from fee escrow; the requester receives the unspent remainder. +- Supplier/ciphernode failures return 100% of fee escrow to the requester with no protocol cut. + Honest nodes are compensated from actual ticket collateral slashed from faulty nodes. + +Slashed assets remain token-specific pull claims. Compensation is therefore limited to collateral +actually slashed and does not require an oracle or relabel one ERC-20 as another. --- @@ -103,30 +110,38 @@ Anyone calls: Interfold.processE3Failure(e3Id) │ │ │ │ ┌─── E3RefundManager.calculateRefund() ────────────────┐ │ │ │ │ -│ │ │ 1. Determine work completed based on failure stage: │ +│ │ │ 1. Read FailureReason and call getFailurePayer(): │ +│ │ │ │ +│ │ │ Requester liability: │ +│ │ │ NoInputsReceived, ComputeTimeout, │ +│ │ │ ComputeProviderExpired/Failed, RequesterCancelled │ │ │ │ │ -│ │ │ Stage at Failure │ Work Done │ Work Left │Proto │ -│ │ │ ─────────────────────┼───────────┼───────────┼────── │ -│ │ │ Requested / None │ 0 BPS │ 9500 BPS │ 500 │ -│ │ │ (no committee yet) │ (0%) │ (95%) │ (5%) │ -│ │ │ CommitteeFinalized │ 1000 BPS │ 8500 BPS │ 500 │ -│ │ │ (DKG failed) │ (10%) │ (85%) │ (5%) │ -│ │ │ KeyPublished │ 4000 BPS │ 5500 BPS │ 500 │ -│ │ │ (compute failed) │ (40%) │ (55%) │ (5%) │ -│ │ │ CiphertextReady │ 4000 BPS │ 5500 BPS │ 500 │ -│ │ │ (decryption failed) │ (40%) │ (55%) │ (5%) │ +│ │ │ Ciphernodes/supply liability: │ +│ │ │ CommitteeFormationTimeout, │ +│ │ │ InsufficientCommitteeMembers, DKGTimeout, │ +│ │ │ DKGInvalidShares, DecryptionTimeout, │ +│ │ │ DecryptionInvalidShares, VerificationFailed │ │ │ │ │ -│ │ │ NOTE: KeyPublished and CiphertextReady have the SAME │ -│ │ │ work-completed value (4000 BPS). The decryptionBps │ -│ │ │ (5500) is NOT added for CiphertextReady — decryption │ -│ │ │ work is not counted as completed until E3 is Complete.│ +│ │ │ None, _MAX_FAILURE_REASON, and future unclassified │ +│ │ │ reasons revert InvalidFailureReason (fail closed). │ │ │ │ │ -│ │ │ 2. Calculate amounts: │ -│ │ │ honestNodeAmount = payment * workDoneBps / 10000 │ -│ │ │ requesterAmount = payment * workLeftBps / 10000 │ -│ │ │ protocolAmount = payment - honest - requester │ +│ │ │ 2a. Ciphernodes/supply liability: │ +│ │ │ requesterAmount = payment (100%) │ +│ │ │ honestNodeAmount = 0 │ +│ │ │ protocolAmount = 0 │ +│ │ │ → honest-node compensation can come only from │ +│ │ │ actual slashed ticket collateral │ │ │ │ │ -│ │ │ 3. Credit protocol amount to treasury pull ledger │ +│ │ │ 2b. Requester liability: use request-time work BPS: │ +│ │ │ KeyPublished / CiphertextReady defaults: │ +│ │ │ honestNodeAmount = payment * 4000 / 10000 │ +│ │ │ requesterAmount = payment * 5500 / 10000 │ +│ │ │ protocolAmount = remaining 500 / 10000 │ +│ │ │ → if no honest recipient exists, fold the work │ +│ │ │ share back into requesterAmount │ +│ │ │ │ +│ │ │ 3. Credit any requester-fault protocol amount to the │ +│ │ │ snapshotted treasury pull ledger │ │ │ │ │ │ │ │ 4. Store RefundDistribution { │ │ │ │ honestNodeAmount, requesterAmount, │ @@ -135,15 +150,9 @@ Anyone calls: Interfold.processE3Failure(e3Id) │ │ │ originalPayment, perNodeAmount: 0 │ │ │ │ } │ │ │ │ │ -│ │ │ H-08: if honestNodes.length == 0 and │ -│ │ │ honestNodeAmount > 0, fold honestNodeAmount back │ -│ │ │ into requesterAmount before storing — the work- │ -│ │ │ completed share would otherwise have no eligible │ -│ │ │ honest-node claimant. │ -│ │ │ │ │ │ │ 5. Preserve slashed assets as separate claims: │ │ │ │ → New escrows are keyed by (e3Id, actual token) │ -│ │ │ → A pending entry matching paymentToken may settle │ +│ │ │ → A pending entry matching paymentToken may settle│ │ │ │ now; other tokens remain permissionlessly │ │ │ │ settleable without being relabeled │ │ │ │ │ @@ -172,8 +181,9 @@ REQUESTER claims: ├─ require(distribution calculated) ├─ require(msg.sender == requester from Interfold) ├─ require(!requester refund already claimed) -├─ requesterAmount includes BOTH: -│ • Base refund (from work-value BPS allocation) +├─ requesterAmount is either: +│ • 100% of fee escrow for ciphernodes/supply liability, or +│ • the unspent request-time work allocation for requester liability ├─ Transfer requesterAmount in the per-E3 fee token └─ Emit RefundClaimed(e3Id, requester, amount) @@ -185,7 +195,9 @@ HONEST NODE claims: ├─ require(!honest-node reward already claimed by this node) │ → This ledger is independent from the requester-refund claim ledger, so a │ requester who is also an honest node can receive both entitlements -├─ honestNodeAmount is base fee-token compensation from the work-value allocation +├─ honestNodeAmount exists only for requester-attributable failures +│ → ciphernodes/supply failures set this base amount to zero +│ → honest nodes claim later ticket slashes through claimSlashedFunds ├─ perNodeAmount = honestNodeAmount / honestNodeCount │ • SNAPSHOTTED at calculateRefund (M-09) and never changed by │ slashed assets, even when the slash token equals the fee token. @@ -204,7 +216,7 @@ SLASH RECIPIENT claims a token-specific entitlement: └─ Emit SlashedFundsClaimed(e3Id, caller, actualToken, amount) ``` -### Refund Example (Base Only) +### Refund Example: Requester/Compute-Provider Fault ``` Scenario: E3 fails at KeyPublished stage (compute timeout) @@ -222,34 +234,26 @@ Scenario: E3 fails at KeyPublished stage (compute timeout) Treasury claims: 50,001 ``` -### Refund Example (With Slashed Funds) +### Refund Example: Ciphernode Fault ``` -Same scenario as above, then 2 nodes are slashed for 300,000 units of -USDC each: - - Before slash: - requesterAmount = 550,000 - honestNodeAmount = 400,000 - originalPayment = 1,000,000 - - Requester fee-token shortfall: 1,000,000 - 550,000 = 450,000. - The first 300,000 slash is credited entirely to the requester. - From the second 300,000 slash: - requester receives the remaining 150,000 shortfall - honest nodes receive the 150,000 surplus - - Final: - Base USDC claims remain: requester 550,000; nodes 400,000 total. - Separate slashed-USDC claims: - requester: 450,000 - honest nodes: 150,000 total (dust assigned deterministically) - Treasury base USDC credit: 50,000 - - If the slash asset were TICKET-USD instead, the whole TICKET-USD amount - would be credited to the requester as priority compensation. It would not - be described as filling the numerical USDC shortfall because no trusted - conversion price exists. +Scenario: E3 fails during DKG because one member supplied invalid shares + Fee escrow: 1,000,000 USDC + Honest nodes after expulsion: 2 + Faulty node ticket slash: 300,000 TICKET-USD + + Base fee-token claims: + requester: 1,000,000 USDC (100%) + honest nodes: 0 USDC + protocol: 0 USDC + + Separate slash-token claims: + honest node 1: 150,000 TICKET-USD + honest node 2: 150,000 TICKET-USD + requester: 0 TICKET-USD + + If no ticket collateral is actually slashed, honest-node compensation is + zero; the requester refund never waits for or depends on slash execution. ``` --- @@ -815,33 +819,29 @@ settleSlashedFunds(e3Id, actualToken): │ ├─ Read and clear _pendingSlashedByToken[e3Id][actualToken] │ -├─ If actualToken differs from the E3 fee token: -│ credit the whole amount to the requester as priority compensation -│ → Do not claim that unrelated token units fill an exact fee-token gap +├─ Read current active committee nodes from the request-time registry +│ → Faulty operators expelled by failure-triggering policies are excluded +│ +├─ If active honest nodes exist: +│ divide the whole actualToken amount among them +│ → deterministic last-node dust assignment │ -├─ Otherwise, for a matching fee token: -│ originalGap = originalPayment - requesterAmount -│ remainingGap = originalGap - requester compensation already credited -│ toRequester = min(amount, remainingGap) -│ toHonestNodes = amount - toRequester -│ → Cumulative same-token settlement prevents later slashes from -│ refilling the same requester shortfall -│ → If no active honest node exists, credit the post-cap residual to -│ the snapshotted treasury rather than creating a requester windfall +├─ Otherwise: +│ credit the whole actualToken amount to the snapshotted treasury +│ → no requester windfall beyond return of their original fee escrow │ ├─ Credit _pendingSlashedClaims[e3Id][actualToken][recipient] │ → Base fee-token RefundDistribution fields never change │ → Decimal/unit differences cannot corrupt the base refund │ └─ Emit SlashedFundsApplied(e3Id, actualToken, - toRequester, toHonestNodes) + 0, toHonestNodes) Design rationale: - The protocol cannot compare or cap amounts across unrelated token units - without a trusted conversion price. Matching fee-token slashes can fill the - requester's exact shortfall before rewarding honest nodes. Different-token - slashes preserve their denomination and go to the requester first without - asserting cross-token economic equivalence. + Supplier/ciphernode failures already return 100% of the requester's fee + escrow. Ticket slashes compensate honest service providers in the slash + asset itself. No trusted conversion price is needed, and requester-fault + failures do not gain a slash-funded rebate for costs they caused. ``` ### Slashed Funds Distribution (Success Path): distributeSlashedFundsOnSuccess() @@ -931,7 +931,7 @@ Case 1: Slash happens BEFORE processE3Failure Case 2: Slash happens AFTER processE3Failure → escrowSlashedFunds sees dist.calculated - → token-specific requester/node pull credits are created immediately + → token-specific honest-node/treasury pull credits are created immediately → Base refund claims may already have started; ledgers remain independent Case 3: Multiple slashes on same E3 (failure) @@ -967,6 +967,9 @@ Constraints: - If requiresProof: appealWindow may be 0 (atomic) or > 0 (deferred challenge) - If !requiresProof: appealWindow must be > 0 (delayed execution, with appeal) - At least one penalty must be non-zero +- A nonzero failureReason must be below `_MAX_FAILURE_REASON` +- A policy with nonzero failureReason must set affectsCommittee=true + → The proven-faulty operator is expelled before honest slash recipients are resolved Slash Reasons (derived from ProofType for Lane A): reason = keccak256(abi.encodePacked(proofType)) @@ -1037,7 +1040,8 @@ Slash Reasons (derived from ProofType for Lane A): │ └─ Slashed USDC escrowed in E3RefundManager │ │ │ │ 10. FUND DISTRIBUTION (at E3 terminal state) │ -│ ├─ Failure: requester refunded first, surplus to honest │ +│ ├─ Failure: fee refund is fault-attributed; slashes pay │ +│ │ active honest nodes │ │ └─ Success: nodes + treasury split │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -1065,10 +1069,9 @@ STEP 1: ESCROWING (always, at slash time) STEP 2a: E3 FAILS → Token-specific compensation Triggered by: terminal escrow or permissionless settleSlashedFunds Flow: settleSlashedFunds(e3Id, actualToken) - → Matching fee-token slashes fill the requester shortfall first; - any surplus is divided among honest nodes - → Different-token slashes go to the requester as priority compensation - without an oracle-dependent cross-token comparison + → The entire actual-token slash is divided among active honest nodes + → If there are no honest recipients, the slash goes to the + snapshotted treasury → Credits stay in actualToken; fee-token refund buckets are unchanged Claims: claimSlashedFunds(e3Id, actualToken) diff --git a/packages/interfold-contracts/contracts/E3RefundManager.sol b/packages/interfold-contracts/contracts/E3RefundManager.sol index 27ec6469e..d7cf82753 100644 --- a/packages/interfold-contracts/contracts/E3RefundManager.sol +++ b/packages/interfold-contracts/contracts/E3RefundManager.sol @@ -179,13 +179,17 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { require(originalPayment > 0, "No payment"); require(address(paymentToken) != address(0), "Invalid fee token"); - // Calculate work value based on stage and the request-time policy. - IInterfold.E3Stage failedAt = _getFailedAtStage(e3Id); + // Attribute the failure before touching requester escrow. Supplier-side + // failures return the entire fee payment; requester-side failures pay + // completed work according to the request-time policy. + IInterfold.FailureReason reason = _interfoldFor(e3Id).getFailureReason( + e3Id + ); ( uint256 honestNodeAmount, uint256 requesterAmount, uint256 protocolAmount - ) = _baseDistribution(e3Id, originalPayment, failedAt); + ) = _baseDistribution(e3Id, originalPayment, reason); // No honest nodes: fold work share into the requester refund (mirrors // {Interfold._distributeRewards} success-path). @@ -250,7 +254,7 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { function _baseDistribution( uint256 e3Id, uint256 originalPayment, - IInterfold.E3Stage failedAt + IInterfold.FailureReason reason ) internal view @@ -260,6 +264,11 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { uint256 protocolAmount ) { + if (getFailurePayer(reason) == FailurePayer.Ciphernodes) { + return (0, originalPayment, 0); + } + + IInterfold.E3Stage failedAt = _getFailedAtStage(reason); ( uint16 workCompletedBps, uint16 workRemainingBps @@ -269,14 +278,39 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { protocolAmount = originalPayment - honestNodeAmount - requesterAmount; } - /// @notice Get the stage at which E3 failed (for work calculation) - function _getFailedAtStage( - uint256 e3Id - ) internal view returns (IInterfold.E3Stage) { - IInterfold.FailureReason reason = _interfoldFor(e3Id).getFailureReason( - e3Id - ); + /// @inheritdoc IE3RefundManager + function getFailurePayer( + IInterfold.FailureReason reason + ) public pure returns (FailurePayer payer) { + if ( + reason == IInterfold.FailureReason.NoInputsReceived || + reason == IInterfold.FailureReason.ComputeTimeout || + reason == IInterfold.FailureReason.ComputeProviderExpired || + reason == IInterfold.FailureReason.ComputeProviderFailed || + reason == IInterfold.FailureReason.RequesterCancelled + ) { + return FailurePayer.Requester; + } + if ( + reason == IInterfold.FailureReason.CommitteeFormationTimeout || + reason == IInterfold.FailureReason.InsufficientCommitteeMembers || + reason == IInterfold.FailureReason.DKGTimeout || + reason == IInterfold.FailureReason.DKGInvalidShares || + reason == IInterfold.FailureReason.DecryptionTimeout || + reason == IInterfold.FailureReason.DecryptionInvalidShares || + reason == IInterfold.FailureReason.VerificationFailed + ) { + return FailurePayer.Ciphernodes; + } + + revert InvalidFailureReason(reason); + } + + /// @notice Get the stage at which a requester-attributable E3 failed. + function _getFailedAtStage( + IInterfold.FailureReason reason + ) internal pure returns (IInterfold.E3Stage) { // Map failure reason to stage if ( reason == IInterfold.FailureReason.CommitteeFormationTimeout || @@ -511,43 +545,19 @@ contract E3RefundManager is IE3RefundManager, Ownable2StepUpgradeable { address[] memory nodes ) internal { RefundDistribution storage dist = _distributions[e3Id]; - uint256 toRequester; - uint256 toHonestNodes; + uint256 toHonestNodes = amount; uint256 toProtocol; - bool sameFeeToken = token == dist.feeToken; - - if (!sameFeeToken) { - // No oracle-free comparison can establish an exact fee-token - // shortfall, so the requester gets priority compensation in the - // actual slash asset without relabeling it as exact repayment. - toRequester = amount; - } else { - // Same-token slashes first fill the requester's exact fee-token - // shortfall. `totalSlashed` records cumulative same-token slash - // settlement so later slashes cannot refill the same gap. - uint256 originalGap = dist.originalPayment > dist.requesterAmount - ? dist.originalPayment - dist.requesterAmount - : 0; - uint256 alreadyFilled = dist.totalSlashed < originalGap - ? dist.totalSlashed - : originalGap; - uint256 remainingGap = originalGap - alreadyFilled; - toRequester = amount < remainingGap ? amount : remainingGap; - toHonestNodes = amount - toRequester; - if (nodes.length == 0) { - // Preserve the requester cap when no honest-node recipient - // exists; the residual remains protocol-owned, not a windfall. - toProtocol = toHonestNodes; - toHonestNodes = 0; - } + if (nodes.length == 0) { + // There is no honest service-provider recipient. Do not turn a + // punitive slash into requester over-compensation. + toProtocol = amount; + toHonestNodes = 0; } - if (sameFeeToken) dist.totalSlashed += amount; + if (token == dist.feeToken) dist.totalSlashed += amount; - address requester = _interfoldFor(e3Id).getRequester(e3Id); - _creditSlashedClaim(e3Id, token, requester, toRequester); _creditNodeSlashedClaims(e3Id, token, nodes, toHonestNodes); _creditTrackedTreasury(_treasuryFor(e3Id), token, toProtocol); - emit SlashedFundsApplied(e3Id, token, toRequester, toHonestNodes); + emit SlashedFundsApplied(e3Id, token, 0, toHonestNodes); } function _settleSuccessfulE3Slash( diff --git a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol index 09bc5df56..f859f3177 100644 --- a/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol +++ b/packages/interfold-contracts/contracts/interfaces/IE3RefundManager.sol @@ -13,6 +13,13 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; * @dev Handles refund calculation and claiming for failed E3s */ interface IE3RefundManager { + /// @notice Identifies which collateral pool bears a failed E3's completed-work cost. + enum FailurePayer { + None, + Requester, + Ciphernodes + } + //////////////////////////////////////////////////////////// // // // Structs // @@ -40,11 +47,11 @@ interface IE3RefundManager { uint256 requesterAmount; // Amount for requester uint256 honestNodeAmount; // Total amount for honest nodes uint256 protocolAmount; // Amount for protocol treasury - uint256 totalSlashed; // Cumulative fee-token slash settlement used for requester-first compensation + uint256 totalSlashed; // Cumulative settled slashes denominated in the E3 fee token uint256 honestNodeCount; // Number of honest nodes bool calculated; // Whether distribution is calculated IERC20 feeToken; // The fee token used for this E3's payment (stored per-E3 to survive token rotations) - uint256 originalPayment; // Original E3 payment amount (for making requester whole) + uint256 originalPayment; // Original E3 payment amount retained for settlement auditability uint256 perNodeAmount; // Snapshotted per-honest-node payout; 0 when honestNodeCount==0 } //////////////////////////////////////////////////////////// @@ -153,6 +160,8 @@ interface IE3RefundManager { error NothingToClaim(); /// @notice Recorded liabilities exceed the manager's balance of a token. error InsolventToken(IERC20 token, uint256 liability, uint256 balance); + /// @notice Failure reason has no configured economic responsibility. + error InvalidFailureReason(IInterfold.FailureReason reason); //////////////////////////////////////////////////////////// // // @@ -171,6 +180,13 @@ interface IE3RefundManager { IERC20 paymentToken ) external; + /// @notice Return the party whose collateral pays completed work for a failure reason. + /// @dev Requester failures pay completed work from fee escrow. Ciphernode/supply + /// failures return all fee escrow and use actual ticket slashes to pay honest nodes. + function getFailurePayer( + IInterfold.FailureReason reason + ) external pure returns (FailurePayer payer); + /// @notice Freeze the current allocation, treasury, and committee registry for an E3. /// @dev Only Interfold may call this, exactly once, during request creation. function snapshotE3Policy(uint256 e3Id, address registry) external; diff --git a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol index 4e17cea78..be1972fb9 100644 --- a/packages/interfold-contracts/contracts/slashing/SlashingManager.sol +++ b/packages/interfold-contracts/contracts/slashing/SlashingManager.sol @@ -346,6 +346,16 @@ contract SlashingManager is } // Cap the appeal window so governance cannot indefinitely delay slashing. require(policy.appealWindow <= MAX_APPEAL_WINDOW, InvalidPolicy()); + if (policy.failureReason > 0) { + // A policy that can fail an E3 must also remove the proven-faulty + // operator before the refund manager snapshots honest recipients. + require(policy.affectsCommittee, InvalidPolicy()); + require( + policy.failureReason < + uint8(IInterfold.FailureReason._MAX_FAILURE_REASON), + InvalidPolicy() + ); + } slashPolicies[reason] = policy; emit SlashPolicyUpdated(reason, policy); diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index da04ab29a..395f3e623 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -227,6 +227,24 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect(storedRequester).to.equal(await requester.getAddress()); }); + it("classifies every supported failure reason by economic responsibility", async function () { + const { e3RefundManager } = await loadFixture(setup); + + for (const reason of [5, 6, 7, 8, 9]) { + expect(await e3RefundManager.getFailurePayer(reason)).to.equal(1); + } + for (const reason of [1, 2, 3, 4, 10, 11, 12]) { + expect(await e3RefundManager.getFailurePayer(reason)).to.equal(2); + } + + await expect( + e3RefundManager.getFailurePayer(0), + ).to.be.revertedWithCustomError(e3RefundManager, "InvalidFailureReason"); + await expect( + e3RefundManager.getFailurePayer(13), + ).to.be.revertedWithCustomError(e3RefundManager, "InvalidFailureReason"); + }); + it("AUD-M07: snapshots failure allocation and treasury at request time", async function () { const { interfold, @@ -259,8 +277,8 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await e3RefundManager.connect(owner).setWorkAllocation({ committeeFormationBps: 2000, - dkgBps: 2000, - decryptionBps: 5500, + dkgBps: 3000, + decryptionBps: 4500, protocolBps: 500, successSlashedNodeBps: 1000, }); @@ -271,14 +289,23 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await registry.connect(operator3).submitTicket(0, 1); await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await registry.finalizeCommittee(0); + const publicKey = "0x1234567890abcdef1234567890abcdef"; + const pkCommitment = ethers.keccak256(publicKey); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); const deadlines = await interfold.getDeadlines(0); - await time.increaseTo(deadlines.dkgDeadline + 1n); + await time.increaseTo(deadlines.computeDeadline + 1n); await interfold.markE3Failed(0); await interfold.processE3Failure(0); const distribution = await e3RefundManager.getRefundDistribution(0); expect(distribution.honestNodeAmount).to.equal( - (distribution.originalPayment * 1000n) / 10000n, + (distribution.originalPayment * 4000n) / 10000n, ); expect( await e3RefundManager.pendingTreasuryClaim( @@ -623,7 +650,11 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distribution = await e3RefundManager.getRefundDistribution(0); expect(distribution.calculated).to.be.true; - expect(distribution.requesterAmount).to.be.gt(0); + expect(distribution.requesterAmount).to.equal( + distribution.originalPayment, + ); + expect(distribution.honestNodeAmount).to.equal(0); + expect(distribution.protocolAmount).to.equal(0); }); it("allows requester to claim refund after failure processing", async function () { @@ -747,7 +778,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { }); describe("Slashed Funds Escrow", function () { - it("E2E: slash via SlashingManager escrows actual USDC to refund manager and requester can claim", async function () { + it("E2E: slash via SlashingManager pays honest nodes without reducing the requester refund", async function () { const { interfold, e3RefundManager, @@ -756,6 +787,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { bondingRegistry, usdcToken, makeRequest, + owner, requester, operator1, operator2, @@ -766,6 +798,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await setupOperator(operator1); await setupOperator(operator2); await setupOperator(operator3); + await slashingManager.connect(owner).setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: ethers.parseEther("100"), + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: true, + failureReason: 0, + }); // 1. Request E3, form committee, publish key await makeRequest(requester, 0); @@ -850,11 +893,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ); - const requesterGap = - distributionBefore.originalPayment - distributionBefore.requesterAmount; - expect(requesterSlashClaim).to.equal( - actualSlashedAmount < requesterGap ? actualSlashedAmount : requesterGap, - ); + expect(requesterSlashClaim).to.equal(0); expect(distributionAfter.totalSlashed).to.equal(actualSlashedAmount); let honestSlashClaims = 0n; @@ -865,11 +904,29 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await node.getAddress(), ); } - expect(honestSlashClaims).to.equal( - actualSlashedAmount - requesterSlashClaim, + expect(honestSlashClaims).to.equal(actualSlashedAmount); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator1.getAddress(), + ), + ).to.equal(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator2.getAddress(), + ), + ).to.equal( + await e3RefundManager.pendingSlashedClaim( + 0, + usdcAddress, + await operator3.getAddress(), + ), ); - // 7. The requester pulls the base refund and slash entitlement separately. + // 7. The requester pulls only the fault-attributed base refund. const requesterBalanceBefore = await usdcToken.balanceOf( await requester.getAddress(), ); @@ -877,14 +934,11 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { expect( await usdcToken.balanceOf(await e3RefundManager.getAddress()), ).to.be.gte(await e3RefundManager.tokenLiability(usdcAddress)); - await e3RefundManager - .connect(requester) - .claimSlashedFunds(0, usdcAddress); const requesterBalanceAfter = await usdcToken.balanceOf( await requester.getAddress(), ); expect(requesterBalanceAfter - requesterBalanceBefore).to.equal( - distributionAfter.requesterAmount + requesterSlashClaim, + distributionAfter.requesterAmount, ); }); @@ -908,6 +962,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await setupOperator(operator1); await setupOperator(operator2); await setupOperator(operator3); + await slashingManager.connect(owner).setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: ethers.parseEther("100"), + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: true, + failureReason: 0, + }); await makeRequest(requester, 0); await registry.connect(operator1).submitTicket(0, 1); @@ -1003,6 +1068,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await setupOperator(operator1); await setupOperator(operator2); await setupOperator(operator3); + await slashingManager.connect(owner).setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: ethers.parseEther("100"), + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: true, + failureReason: 0, + }); const feeToken = await new MockUSDCFactory(owner).deploy(0); await feeToken.waitForDeployment(); @@ -1088,42 +1164,49 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { underlyingAddress, await requester.getAddress(), ), - ).to.equal(actualSlash); - for (const node of [operator1, operator2, operator3]) { - expect( - await e3RefundManager.pendingSlashedClaim( - 0, - underlyingAddress, - await node.getAddress(), - ), - ).to.equal(0); - } + ).to.equal(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await operator1.getAddress(), + ), + ).to.equal(0); + const operator2SlashClaim = await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await operator2.getAddress(), + ); + expect(operator2SlashClaim).to.be.gt(0); + expect( + await e3RefundManager.pendingSlashedClaim( + 0, + underlyingAddress, + await operator3.getAddress(), + ), + ).to.equal(operator2SlashClaim); expect(await e3RefundManager.tokenLiability(underlyingAddress)).to.equal( totalSlashCredits, ); const requesterAddress = await requester.getAddress(); const feeBefore = await feeToken.balanceOf(requesterAddress); + const operator2Address = await operator2.getAddress(); const underlyingBefore = - await ticketUnderlying.balanceOf(requesterAddress); - const slashClaim = await e3RefundManager.pendingSlashedClaim( - 0, - underlyingAddress, - requesterAddress, - ); + await ticketUnderlying.balanceOf(operator2Address); await e3RefundManager.connect(requester).claimRequesterRefund(0); await e3RefundManager - .connect(requester) + .connect(operator2) .claimSlashedFunds(0, underlyingAddress); expect((await feeToken.balanceOf(requesterAddress)) - feeBefore).to.equal( distribution.requesterAmount, ); expect( - (await ticketUnderlying.balanceOf(requesterAddress)) - underlyingBefore, - ).to.equal(slashClaim); + (await ticketUnderlying.balanceOf(operator2Address)) - underlyingBefore, + ).to.equal(operator2SlashClaim); expect(await e3RefundManager.tokenLiability(underlyingAddress)).to.equal( - totalSlashCredits - slashClaim, + totalSlashCredits - operator2SlashClaim, ); }); @@ -1244,7 +1327,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); }); - it("caps same-token requester compensation when no honest nodes exist", async function () { + it("routes failed-E3 slashes to treasury when no honest nodes exist", async function () { const { interfold, e3RefundManager, @@ -1264,7 +1347,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await makeRequest(); - // Fail the E3 at committee formation stage (no honest nodes, requester gets 95%) + // Fail at committee formation (no honest nodes, requester gets all escrow). await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await interfold.markE3Failed(0); await interfold.processE3Failure(0); @@ -1291,38 +1374,32 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ]); const distributionAfter = await e3RefundManager.getRefundDistribution(0); - const requesterGap = - distributionBefore.originalPayment - distributionBefore.requesterAmount; - const expectedRequester = - slashedAmount < requesterGap ? slashedAmount : requesterGap; - const expectedProtocol = slashedAmount - expectedRequester; const tokenAddress = await usdcToken.getAddress(); expect(distributionAfter.requesterAmount).to.equal( - distributionBefore.requesterAmount, - ); - expect(distributionAfter.honestNodeAmount).to.equal( - distributionBefore.honestNodeAmount, + distributionBefore.originalPayment, ); + expect(distributionAfter.honestNodeAmount).to.equal(0); + expect(distributionAfter.protocolAmount).to.equal(0); expect( await e3RefundManager.pendingSlashedClaim( 0, tokenAddress, await requester.getAddress(), ), - ).to.equal(expectedRequester); + ).to.equal(0); expect( await e3RefundManager.pendingTreasuryClaim( await treasury.getAddress(), tokenAddress, ), - ).to.equal(distributionBefore.protocolAmount + expectedProtocol); + ).to.equal(slashedAmount); expect(await e3RefundManager.tokenLiability(tokenAddress)).to.equal( slashedAmount, ); }); - it("caps cumulative same-token requester compensation before crediting honest nodes", async function () { + it("credits every failed-E3 slash to honest nodes without requester compensation", async function () { const { interfold, e3RefundManager, @@ -1351,10 +1428,12 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await interfold.processE3Failure(0); const distribution = await e3RefundManager.getRefundDistribution(0); - const requesterGap = - distribution.originalPayment - distribution.requesterAmount; - const firstSlash = requesterGap / 2n; - const secondSlash = requesterGap; + expect(distribution.requesterAmount).to.equal( + distribution.originalPayment, + ); + expect(distribution.honestNodeAmount).to.equal(0); + const firstSlash = ethers.parseUnits("25", 6); + const secondSlash = ethers.parseUnits("50", 6); const totalSlash = firstSlash + secondSlash; const refundManagerAddress = await e3RefundManager.getAddress(); await usdcToken.mint(refundManagerAddress, totalSlash); @@ -1379,7 +1458,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ), - ).to.equal(firstSlash); + ).to.equal(0); await e3RefundManager .connect(interfoldSigner) @@ -1394,7 +1473,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ), - ).to.equal(requesterGap); + ).to.equal(0); let honestSlashClaims = 0n; for (const node of [operator1, operator2, operator3]) { @@ -1404,7 +1483,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await node.getAddress(), ); } - expect(honestSlashClaims).to.equal(totalSlash - requesterGap); + expect(honestSlashClaims).to.equal(totalSlash); expect( (await e3RefundManager.getRefundDistribution(0)).totalSlashed, ).to.equal(totalSlash); @@ -1463,8 +1542,6 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const distAfter = await e3RefundManager.getRefundDistribution(0); expect(distAfter.calculated).to.be.true; const usdcAddress = await usdcToken.getAddress(); - const requesterGap = - distAfter.originalPayment - distAfter.requesterAmount; expect( await e3RefundManager.pendingSlashedFunds(0, usdcAddress), ).to.equal(0); @@ -1474,18 +1551,18 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { usdcAddress, await requester.getAddress(), ), - ).to.equal(requesterGap); + ).to.equal(0); expect( await e3RefundManager.pendingTreasuryClaim( await treasury.getAddress(), usdcAddress, ), - ).to.equal(distAfter.protocolAmount + slashedAmount - requesterGap); + ).to.equal(slashedAmount); }); }); - describe("Full Failure Flow - DKG Timeout", function () { - it("AUD-M02: a requester who is also an honest node can claim both roles", async function () { + describe("Failure Claim Roles and DKG Timeout", function () { + it("AUD-M02: a requester who is also a node can claim both requester-fault allocations", async function () { const { interfold, e3RefundManager, @@ -1507,7 +1584,19 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { await registry.connect(operator3).submitTicket(0, 1); await time.increase(SORTITION_SUBMISSION_WINDOW + 1); await registry.finalizeCommittee(0); - await time.increase(defaultTimeoutConfig.dkgWindow + 1); + const publicKey = "0x1234567890abcdef1234567890abcdef"; + const pkCommitment = ethers.keccak256(publicKey); + await registry.publishCommittee( + 0, + publicKey, + pkCommitment, + encodeMockDkgProof(pkCommitment), + "0x01", + ); + const e3 = await interfold.getE3(0); + await time.increaseTo( + Number(e3.inputWindow[1]) + defaultTimeoutConfig.computeWindow + 1, + ); await interfold.markE3Failed(0); await interfold.processE3Failure(0); @@ -1594,6 +1683,11 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); const distribution = await e3RefundManager.getRefundDistribution(0); + expect(distribution.requesterAmount).to.equal( + distribution.originalPayment, + ); + expect(distribution.honestNodeAmount).to.equal(0); + expect(distribution.protocolAmount).to.equal(0); expect(balanceAfter - balanceBefore).to.equal( distribution.requesterAmount, ); @@ -1674,6 +1768,17 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); const distribution = await e3RefundManager.getRefundDistribution(0); + expect(distribution.requesterAmount).to.equal( + (distribution.originalPayment * 5500n) / 10000n, + ); + expect(distribution.honestNodeAmount).to.equal( + (distribution.originalPayment * 4000n) / 10000n, + ); + expect(distribution.protocolAmount).to.equal( + distribution.originalPayment - + distribution.requesterAmount - + distribution.honestNodeAmount, + ); expect(balanceAfter - balanceBefore).to.equal( distribution.requesterAmount, ); @@ -1761,10 +1866,14 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { ); const distribution = await e3RefundManager.getRefundDistribution(0); + expect(distribution.requesterAmount).to.equal( + distribution.originalPayment, + ); + expect(distribution.honestNodeAmount).to.equal(0); + expect(distribution.protocolAmount).to.equal(0); expect(balanceAfter - balanceBefore).to.equal( distribution.requesterAmount, ); - expect(distribution.requesterAmount).to.be.gt(0); }); }); diff --git a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts index 7c249a53f..abbfe7d70 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts @@ -359,6 +359,42 @@ describe("SlashingManager", function () { ).to.be.revertedWithCustomError(slashingManager, "InvalidPolicy"); }); + it("rejects a failure policy that does not expel the faulty operator", async function () { + const { slashingManager } = await loadFixture(setup); + + await expect( + slashingManager.setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: 0, + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: false, + failureReason: 4, + }), + ).to.be.revertedWithCustomError(slashingManager, "InvalidPolicy"); + }); + + it("rejects a failure policy with the sentinel failure reason", async function () { + const { slashingManager } = await loadFixture(setup); + + await expect( + slashingManager.setSlashPolicy(REASON_PT_0, { + ticketPenalty: ethers.parseUnits("50", 6), + licensePenalty: 0, + requiresProof: true, + proofVerifier: ethers.ZeroAddress, + banNode: false, + appealWindow: 0, + enabled: true, + affectsCommittee: true, + failureReason: 13, + }), + ).to.be.revertedWithCustomError(slashingManager, "InvalidPolicy"); + }); + it("should allow proof-based policy without verifier (attestation model)", async function () { const { slashingManager } = await loadFixture(setup);