Skip to content
This repository was archived by the owner on Feb 16, 2026. It is now read-only.

fix: Phase 0 pre-mainnet security fixes (6 findings)#31

Merged
jeremylongshore merged 6 commits intomainfrom
fix/phase-0-security
Feb 12, 2026
Merged

fix: Phase 0 pre-mainnet security fixes (6 findings)#31
jeremylongshore merged 6 commits intomainfrom
fix/phase-0-security

Conversation

@jeremylongshore
Copy link
Contributor

@jeremylongshore jeremylongshore commented Feb 12, 2026

Summary

Implements all 6 Phase 0 pre-mainnet security fixes from the pre-mortem analysis (039-AA-AUDT). These are the blockers identified before mainnet deployment.

Commits (in execution order)

Commit Finding Fix
0ffddfb PM-SC-022 Add address(0) nullity check after ECDSA.tryRecover in ReceiptV2Extension
d3fbd8d PM-SC-002 Validate expected nonce in NonceEnforcer (breaking terms format)
c626934 PM-EC-002 + PM-SC-023 Decouple arbitrator fee from slash (flat 0.005 ETH) + pull-pattern withdrawals
50f2cd3 PM-EC-001 Volume-proportional bond requirements (bondRatioBps=500, declaredVolume param)
16e3b34 PM-GV-001 TimelockController deployment script (48h delay) + integration tests

Breaking Changes

  • postReceipt(receipt)postReceipt(receipt, declaredVolume) — all callers updated
  • batchPostReceipts(receipts)batchPostReceipts(receipts, declaredVolumes) — all callers updated
  • NonceEnforcer terms format — now encodes expected nonce (not start nonce)
  • OptimisticDisputeModule — ETH returns via pendingWithdrawals + withdrawPending() (pull pattern)

Key Design Decisions

  • Volume is declared, not proven on-chain — OutcomeEnvelope is hashed. Lying about volume is already covered by ReceiptMismatch dispute reason.
  • Arbitrator flat fee (0.005 ETH) from protocol balance, not from slash. Removes perverse incentive to rule against solvers.
  • Pull pattern eliminates DoS via non-payable recipient contracts.
  • bondRatioBps (500 = 5%) is governable via setBondRatioBps() through timelock.

New Slash Distribution

Recipient Old New
User 70% 75%
Treasury 20% 25%
Arbitrator 10% 0% (flat fee instead)

Test Results

  • 474 tests passing, 0 failures (448 baseline + 26 new)
  • New tests cover all 6 changes: address(0) recovery, nonce validation, flat fee distribution, pull-pattern withdrawal, volume-bond checks, timelock integration

Test plan

  • forge build — compiles with via_ir, optimizer 200
  • forge test — 474/474 passing
  • TimelockIntegration: admin calls revert directly, succeed via timelock after delay
  • Volume bonds: receipt rejected if bond < required for declared volume
  • Arbitrator: receives flat fee in pendingWithdrawals, not slash percentage
  • Pull pattern: withdrawPending() sends ETH, double-withdraw returns nothing
  • NonceEnforcer: wrong expected nonce reverts with CaveatViolation
  • ReceiptV2Extension: address(0) from tryRecover is rejected

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added timelock governance mechanism with 48-hour security delay
    • Configurable bond-to-volume ratios for dynamic solver bond requirements
    • Declared volume requirement for receipt posting
    • Pull-based withdrawal pattern for fund claims
    • Flat-fee model for arbitration services
  • Bug Fixes

    • Enhanced signature validation against zero-address edge cases
    • Strengthened nonce validation for improved replay protection
  • Documentation

    • Comprehensive security pre-mortem analysis with risk identification and phased remediation roadmap

jeremylongshore and others added 6 commits February 12, 2026 02:08
… analysis

Comprehensive pre-mortem analysis covering:
- Top 5 project killers (bond ratio, no timelock, arbitrator incentive,
  no cross-chain, governance ossification)
- 83 findings across 8 categories (SC, EC, GV, OF, PK, AG, MC, LF)
- Economic attack scenarios with code-verified parameters
- Multi-chain feasibility (hub-and-spoke recommended)
- Infinite lifecycle requirements
- 4-phase remediation roadmap (pre-mainnet through governance)
- Risk matrix with likelihood x impact scoring

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add explicit nullity checks after both tryRecover calls in
ReceiptV2Extension to prevent address(0) from passing signature
validation when tryRecover silently returns zero address on
malformed signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change terms encoding to include expected nonce value. The enforcer
now validates that the current storage nonce matches the expected
value before incrementing, preventing replay attacks from blind
nonce increment. Breaking change to terms format (pre-mainnet only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…drawals (PM-EC-002, PM-SC-023)

Two related fixes in OptimisticDisputeModule:

1. Arbitrator incentive (PM-EC-002): Replace percentage-based arbitrator
   slash (10%) with flat fee (0.005 ETH) from protocol balance. New
   distribution: 75% user / 25% treasury / 0% arbitrator from slash.

2. Pull-pattern withdrawals (PM-SC-023): Replace push-based _transferETH
   with pendingWithdrawals mapping + withdrawPending() to prevent DoS
   via non-payable recipient contracts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add declaredVolume parameter to postReceipt/batchPostReceipts so
bond-to-volume ratio is enforced on-chain. SolverRegistry gets
bondRatioBps (default 5%, governable) and requiredBondForVolume()
view. Prevents rational fraud where a small bond covers arbitrarily
large transaction volumes.

Breaking change: postReceipt/batchPostReceipts signatures updated.
All callers (tests, scripts, fuzz, invariants) updated to pass
declaredVolume parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deploy OZ TimelockController with 48h minimum delay, Safe as sole
proposer/executor, and transfer ownership of all core contracts.
Includes integration tests verifying admin calls revert when called
directly and succeed through the timelock after delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces timelock-based governance controls, implements a declared volume-to-bond ratio validation system for solvers, migrates dispute resolution ETH transfers to a pull-based withdrawal pattern, hardens nonce enforcement to require exact matches, and includes comprehensive pre-mainnet security analysis documentation.

Changes

Cohort / File(s) Summary
Documentation & Analysis
docs/039-AA-AUDT-pre-mortem-analysis.md
Comprehensive pre-mortem security analysis with 83 findings, risk matrix, remediation roadmap, multi-chain architecture options, governance upgrade path, and threat model mappings.
Governance & Timelock
script/DeployTimelock.s.sol, test/TimelockIntegration.t.sol
New deployment script for 48-hour TimelockController transferring ownership to six core contracts; integration tests verify delayed execution and reversion on premature access.
Bond & Volume Management
src/SolverRegistry.sol, src/interfaces/ISolverRegistry.sol, src/IntentReceiptHub.sol, src/interfaces/IIntentReceiptHub.sol
Added configurable bond-to-volume ratio (bondRatioBps); new requiredBondForVolume() function; postReceipt/batchPostReceipts signatures extended to accept declaredVolume parameters with validation against solver bond availability; new InsufficientBondForVolume error.
Pull-Based Withdrawals & Fee Distribution
src/modules/OptimisticDisputeModule.sol, src/interfaces/IOptimisticDisputeModule.sol, test/OptimisticDispute.t.sol
Replaced push transfers with pendingWithdrawals mapping; adjusted slash distribution (75% user, 25% treasury, 0% arbitrator); added arbitrationFlatFee (0.005 ether) and withdrawPending() function; new WithdrawalPending/WithdrawalCompleted events; updated tests verify flat fee collection and pull-pattern mechanics.
Nonce Enforcement
src/enforcers/NonceEnforcer.sol, test/enforcers/NonceEnforcer.t.sol
Replaced range-based (startNonce) with exact-match (expectedNonce) validation; beforeHook reverts with CaveatViolation on mismatch; updated tests encode nonce values sequentially and validate isolation per delegation.
ECDSA Validation
src/extensions/ReceiptV2Extension.sol, test/ReceiptV2Extension.t.sol
Added explicit zero-address checks for solver and client signers before signature validation; new test_PostReceiptV2_RevertZeroAddressRecovery() validates recovery of address(0).
Receipt API & Call Site Updates
script/SeedReceiptsOnly.s.sol, script/SeedTestData.s.sol, test/AcrossAdapter.t.sol, test/DisputeModule.t.sol, test/ERC8004Integration.t.sol, test/IntentReceiptHub.t.sol, test/fuzz/IntentReceiptHubFuzz.t.sol, test/invariants/DisputeModule.invariants.t.sol, test/invariants/IntentReceiptHub.invariants.t.sol
Updated all postReceipt() calls to postReceipt(receipt, 0); batchPostReceipts() calls updated to include volumes array; new volume-based test scenarios exercise bond validation and high-bond edge cases in IntentReceiptHub.t.sol; added SolverRegistry bond ratio tests.

Sequence Diagrams

sequenceDiagram
    participant Safe as Safe (Proposer/Executor)
    participant Timelock as TimelockController
    participant Registry as SolverRegistry
    participant Hub as IntentReceiptHub

    Safe->>Timelock: schedule(operation, salt, predecessor, delay)
    Note over Timelock: Operation queued with 48-hour delay
    Note over Timelock: Wait: MIN_DELAY elapsed
    Safe->>Timelock: execute(operation, salt, predecessor)
    Timelock->>Registry: onlyOwner function (e.g., setBondRatioBps)
    Registry->>Registry: Update state
    Timelock->>Hub: onlyOwner function (e.g., setChallengeWindow)
    Hub->>Hub: Update state
Loading
sequenceDiagram
    participant Solver as Solver/User
    participant Hub as IntentReceiptHub
    participant Registry as SolverRegistry
    participant Dispute as OptimisticDisputeModule

    Solver->>Registry: requiredBondForVolume(volume)
    Registry-->>Solver: required bond amount
    Solver->>Hub: postReceipt(receipt, declaredVolume)
    Hub->>Registry: requiredBondForVolume(declaredVolume)
    Registry-->>Hub: required bond
    Hub->>Hub: Validate solver bond >= required
    alt Insufficient Bond
        Hub-->>Solver: InsufficientBondForVolume
    else Bond Sufficient
        Hub->>Hub: Store declaredVolume
        Hub-->>Solver: Receipt posted
    end
    Note over Hub,Dispute: On dispute resolution...
    Dispute->>Dispute: Determine winner, calculate slash
    Dispute->>Dispute: pendingWithdrawals[recipient] += amount
    Dispute-->>Dispute: emit WithdrawalPending
    Dispute->>Dispute: pendingWithdrawals[arbitrator] += flatFee
    Solver->>Dispute: withdrawPending()
    Dispute->>Dispute: pendingWithdrawals[solver] = 0
    Dispute-->>Solver: Transfer ETH
    Dispute-->>Dispute: emit WithdrawalCompleted
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 A timelock ticks while solvers declare,
Their volumes pledged with bond to spare,
No pushing ETH through the air—
We pull it back, withdrawal fair,
And nonces now must match just right,
Pre-mortem done, mainnet's in sight! ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: Phase 0 pre-mainnet security fixes (6 findings)' accurately summarizes the primary change: implementing security fixes from pre-mainnet analysis, and references the specific count of findings being addressed.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, commits, breaking changes, design decisions, test results, and addressing key sections of the template (summary, type of change, risk assessment, testing, breaking changes, security fixes).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/phase-0-security

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @jeremylongshore, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request implements six critical security and economic fixes identified in a pre-mortem analysis, crucial for the protocol's mainnet deployment. The changes enhance the system's resilience against rational fraud by introducing volume-proportional bond requirements, improve governance security through a timelock for administrative actions, and refine dispute resolution mechanics by decoupling arbitrator incentives and implementing a safer withdrawal pattern. These updates collectively strengthen the protocol's integrity and prepare it for a robust launch.

Highlights

  • Volume-Proportional Bond Requirements (PM-EC-001): Implemented a mechanism where solver bond requirements are proportional to the declared transaction volume, mitigating the 'Rational Fraud Cascade' risk where a small fixed bond could secure large transaction volumes. The bondRatioBps is set to 500 (5%) and is governable.
  • Timelock Controller for Admin Operations (PM-GV-001): Introduced an OpenZeppelin TimelockController with a 48-hour delay for all critical admin operations. This prevents instant execution of potentially malicious actions even if the Gnosis Safe multisig is compromised, addressing the 'No Timelock on Safe Operations' critical finding.
  • Decoupled Arbitrator Fee and Pull-Pattern Withdrawals (PM-EC-002, PM-SC-023): Removed the percentage-based arbitrator fee from slash distribution, replacing it with a flat 0.005 ETH fee paid from the protocol balance. This eliminates the 'Arbitrator Perverse Incentive'. Additionally, all ETH returns (e.g., counter-bonds) now use a pull-pattern (pendingWithdrawals and withdrawPending()) to prevent DoS attacks via non-payable recipient contracts.
  • NonceEnforcer Validation (PM-SC-002): Modified the NonceEnforcer to validate an expectedNonce instead of just incrementing a startNonce. This ensures that delegated executions require a specific, sequential nonce, preventing replay attacks within delegation scopes.
  • Address(0) Check in ECDSA Recovery (PM-SC-022): Added an explicit check in ReceiptV2Extension to ensure that ECDSA.tryRecover does not return address(0) for malformed signatures. This prevents invalid signatures from being mistakenly validated if solver.operator were ever address(0).
  • Updated Slash Distribution: Adjusted the slash distribution in OptimisticDisputeModule to allocate 75% to the user and 25% to the treasury, with 0% directly to the arbitrator (now covered by a flat fee). This improves economic alignment and addresses inconsistencies.
Changelog
  • 000-docs/039-AA-AUDT-pre-mortem-analysis.md
    • Added a comprehensive pre-mortem security analysis document detailing critical findings and remediation roadmap.
  • script/DeployTimelock.s.sol
    • Added a new deployment script for TimelockController to manage contract ownership with a 48-hour delay.
  • script/SeedReceiptsOnly.s.sol
    • Updated postReceipt calls to include a declaredVolume parameter.
  • script/SeedTestData.s.sol
    • Updated postReceipt calls to include a declaredVolume parameter.
  • src/IntentReceiptHub.sol
    • Added _receiptVolumes mapping to store declared volume per receipt.
    • Modified postReceipt and batchPostReceipts to accept declaredVolume and validate solver bond against it.
    • Updated finalize to pass _receiptVolumes[receiptId] to solverRegistry.updateScore.
  • src/SolverRegistry.sol
    • Added bondRatioBps state variable for volume-proportional bond calculation.
    • Implemented requiredBondForVolume function to calculate bond based on declared volume.
    • Added setBondRatioBps admin function to allow governance of the bond ratio.
  • src/enforcers/NonceEnforcer.sol
    • Modified beforeHook to validate an expectedNonce instead of a startNonce.
    • Updated comments to reflect the new nonce validation mechanism.
  • src/extensions/ReceiptV2Extension.sol
    • Added an explicit signer != address(0) check after ECDSA.tryRecover in postReceiptV2 to prevent invalid signatures recovering to the zero address.
  • src/interfaces/IIntentReceiptHub.sol
    • Updated postReceipt and batchPostReceipts function signatures to include declaredVolume parameter.
    • Added InsufficientBondForVolume error.
  • src/interfaces/IOptimisticDisputeModule.sol
    • Added WithdrawalPending and WithdrawalCompleted events for the new pull-pattern withdrawal mechanism.
  • src/interfaces/ISolverRegistry.sol
    • Added requiredBondForVolume to the interface.
  • src/modules/OptimisticDisputeModule.sol
    • Adjusted SLASH_USER_BPS to 7500 (75%) and SLASH_TREASURY_BPS to 2500 (25%).
    • Removed SLASH_ARBITRATOR_BPS and related logic, replacing it with arbitrationFlatFee.
    • Introduced pendingWithdrawals mapping and withdrawPending function for a pull-pattern ETH withdrawal.
    • Modified resolveByArbitration and resolveByTimeout to use pendingWithdrawals for counter-bond returns and arbitrator fees.
    • Added setArbitrationFlatFee admin function.
    • Deprecated the internal _transferETH function.
  • test/AcrossAdapter.t.sol
    • Updated hub.postReceipt calls to include declaredVolume parameter.
  • test/DisputeModule.t.sol
    • Updated receiptHub.postReceipt calls to include declaredVolume parameter.
  • test/ERC8004Integration.t.sol
    • Updated hub.postReceipt calls to include declaredVolume parameter.
  • test/IntentReceiptHub.t.sol
    • Updated hub.postReceipt and hub.batchPostReceipts calls to include declaredVolume parameters.
    • Added new tests for postReceipt with declaredVolume, InsufficientBondForVolume revert, and batchPostReceipts with volumes.
  • test/OptimisticDispute.t.sol
    • Updated test_ResolveByArbitration_SolverWins to assert pendingWithdrawals for the solver's counter-bond.
    • Added new tests for test_ResolveByArbitration_ArbitratorGetsFlatFee, test_ResolveByArbitration_SlashDistribution_75_25, test_SetArbitrationFlatFee_OnlyOwner, test_SetArbitrationFlatFee_Updates, test_WithdrawPending_AfterResolution, test_WithdrawPending_DoubleWithdrawReverts, and test_PendingWithdrawals_ArbitratorCanClaim.
  • test/ReceiptV2Extension.t.sol
    • Added test_PostReceiptV2_RevertZeroAddressRecovery to verify the address(0) check after tryRecover.
  • test/SolverRegistry.t.sol
    • Added new tests for requiredBondForVolume with default and custom ratios, and setBondRatioBps with owner checks and revert conditions.
  • test/TimelockIntegration.t.sol
    • Added a new integration test file for TimelockController deployment and its interaction with SolverRegistry and IntentReceiptHub.
  • test/enforcers/NonceEnforcer.t.sol
    • Updated beforeHook calls to pass expectedNonce.
    • Added new tests for test_BeforeHook_RevertWrongExpectedNonce, test_BeforeHook_RevertReplayAttempt, test_BeforeHook_RevertSkippedNonce, and test_BeforeHook_SequentialNoncesSucceed.
  • test/fuzz/IntentReceiptHubFuzz.t.sol
    • Updated hub.postReceipt calls to include declaredVolume parameter.
  • test/invariants/DisputeModule.invariants.t.sol
    • Updated hub.postReceipt calls to include declaredVolume parameter.
  • test/invariants/IntentReceiptHub.invariants.t.sol
    • Updated hub.postReceipt calls to include declaredVolume parameter.
Activity
  • A comprehensive pre-mortem security analysis document was added, outlining 83 unique findings across various categories, including critical, high, medium, and low severity.
  • New deployment scripts and integration tests for the TimelockController were introduced, demonstrating its functionality and ensuring administrative operations are subject to a 48-hour delay.
  • Extensive unit and integration tests were added or updated across multiple modules (IntentReceiptHub, OptimisticDisputeModule, SolverRegistry, ReceiptV2Extension, NonceEnforcer) to cover the new security fixes and economic model changes.
  • The test suite now includes specific tests for volume-proportional bond requirements, arbitrator flat fees, pull-pattern withdrawals, explicit address(0) checks in ECDSA recovery, and strict nonce validation in the NonceEnforcer.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements several critical pre-mainnet security fixes identified in a security audit. The changes are well-structured and address each finding effectively. Key improvements include introducing a TimelockController for governance actions, implementing volume-proportional bonds to mitigate rational fraud, redesigning the arbitrator incentive with a flat fee, and using a pull pattern for ETH withdrawals to prevent DoS attacks. The NonceEnforcer has also been strengthened to require an exact expected nonce. The code quality is high, and the changes are thoroughly tested with new unit and integration tests. I have one suggestion for the new deployment script to improve its verification logging.

Comment on lines +73 to +97
// Step 2-7: Transfer ownership of all contracts to TimelockController
console.log("[2/7] Transferring SolverRegistry ownership...");
Ownable(solverRegistry).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(solverRegistry).owner());

console.log("[3/7] Transferring IntentReceiptHub ownership...");
Ownable(intentReceiptHub).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(intentReceiptHub).owner());

console.log("[4/7] Transferring DisputeModule ownership...");
Ownable(disputeModule).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(disputeModule).owner());

console.log("[5/7] Transferring OptimisticDisputeModule ownership...");
Ownable(optimisticDisputeModule).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(optimisticDisputeModule).owner());

console.log("[6/7] Transferring EscrowVault ownership...");
Ownable(escrowVault).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(escrowVault).owner());

console.log("[7/7] Transferring ReceiptV2Extension ownership...");
Ownable(receiptV2Extension).transferOwnership(address(timelock));
console.log(" Owner:", Ownable(receiptV2Extension).owner());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The console.log statements that verify the new owner are placed within the vm.startBroadcast() block. In Forge scripts, state changes from broadcasted transactions are not applied until after vm.stopBroadcast(). As a result, these logs will incorrectly print the old owner address, not the new timelock address, which can be misleading during deployment.

To correctly verify the ownership changes, all the Ownable(...).owner() calls for verification should be moved to after the vm.stopBroadcast() call.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/OptimisticDisputeModule.sol (1)

254-313: ⚠️ Potential issue | 🟠 Major

Arbitration fee can over-allocate contract balance.
arbitrationFlatFee is credited based only on address(this).balance, which already includes counter-bonds that are also credited to pendingWithdrawals. If the module isn’t pre-funded, total pending can exceed free balance and later withdrawals will revert for either the arbitrator or the bond recipient.

🛠️ Suggested fix (track free balance before crediting fees)
+    uint256 public totalPendingWithdrawals;
@@
-            pendingWithdrawals[dispute.challenger] += dispute.counterBond;
+            pendingWithdrawals[dispute.challenger] += dispute.counterBond;
+            totalPendingWithdrawals += dispute.counterBond;
@@
-            pendingWithdrawals[solverOperator] += dispute.counterBond;
+            pendingWithdrawals[solverOperator] += dispute.counterBond;
+            totalPendingWithdrawals += dispute.counterBond;
@@
-        if (arbitrationFlatFee > 0 && address(this).balance >= arbitrationFlatFee) {
+        uint256 freeBalance = address(this).balance - totalPendingWithdrawals;
+        if (arbitrationFlatFee > 0 && freeBalance >= arbitrationFlatFee) {
             pendingWithdrawals[arbitrator] += arbitrationFlatFee;
+            totalPendingWithdrawals += arbitrationFlatFee;
             emit WithdrawalPending(arbitrator, arbitrationFlatFee);
         }
@@
-        pendingWithdrawals[msg.sender] = 0;
+        pendingWithdrawals[msg.sender] = 0;
+        totalPendingWithdrawals -= amount;
-            pendingWithdrawals[dispute.challenger] += dispute.counterBond;
+            pendingWithdrawals[dispute.challenger] += dispute.counterBond;
+            totalPendingWithdrawals += dispute.counterBond;
🤖 Fix all issues with AI agents
In `@src/SolverRegistry.sol`:
- Around line 468-474: Replace the inline require string checks in
setBondRatioBps with custom errors: declare two errors (e.g.,
InvalidBondRatioZero() and BondRatioExceedsMax()) near the contract's error
declarations, then in setBondRatioBps(uint256 _bps) use conditional reverts
(e.g., if (_bps == 0) revert InvalidBondRatioZero(); if (_bps > BPS) revert
BondRatioExceedsMax();) and keep the assignment to bondRatioBps and the
onlyOwner modifier unchanged so callers see the new custom error types instead
of require strings.
🧹 Nitpick comments (3)
test/ReceiptV2Extension.t.sol (1)

555-575: Test duplicates existing coverage.

This new test test_PostReceiptV2_RevertZeroAddressRecovery uses the same corrupted signature bytes (bytes32(0), bytes32(0), uint8(27)) as the existing test_PostReceiptV2_RevertInvalidSolverSignature at lines 223-234. Both tests verify the same behavior—that InvalidSolverSignature() is reverted.

The added value is the PM-SC-022 documentation in comments, but consider consolidating or renaming one test to make the distinction clearer (e.g., one could test a signature that recovers to a wrong address vs. one that recovers to address(0)). Currently, both use the same malformed signature input.

script/DeployTimelock.s.sol (1)

54-98: Consider transaction failure handling for partial deployment scenarios.

The ownership transfers execute sequentially without error handling. If a transfer fails mid-script (e.g., contract not owned by deployer), the timelock will be deployed but only some contracts will have transferred ownership.

For production deployment, consider:

  1. Verifying deployer owns all contracts before starting transfers
  2. Documenting the expected pre-conditions clearly
🛡️ Optional: Add ownership verification before transfers
 vm.startBroadcast(deployerPrivateKey);

+// Verify deployer owns all contracts before proceeding
+require(Ownable(solverRegistry).owner() == deployer, "Deployer must own SolverRegistry");
+require(Ownable(intentReceiptHub).owner() == deployer, "Deployer must own IntentReceiptHub");
+require(Ownable(disputeModule).owner() == deployer, "Deployer must own DisputeModule");
+require(Ownable(optimisticDisputeModule).owner() == deployer, "Deployer must own OptimisticDisputeModule");
+require(Ownable(escrowVault).owner() == deployer, "Deployer must own EscrowVault");
+require(Ownable(receiptV2Extension).owner() == deployer, "Deployer must own ReceiptV2Extension");
+
 // Step 1: Deploy TimelockController
000-docs/039-AA-AUDT-pre-mortem-analysis.md (1)

839-852: Add language specifier to fenced code block.

The ASCII architecture diagram should have a language specifier for the fenced code block, or use text if no syntax highlighting is needed.

📝 Suggested fix
-```
+```text
                     ┌──────────────────────┐

Comment on lines +468 to +474
/// @notice Set bond-to-volume ratio (PM-EC-001, governable via timelock)
/// @param _bps New ratio in basis points (e.g. 500 = 5%)
function setBondRatioBps(uint256 _bps) external onlyOwner {
require(_bps > 0, "Ratio must be > 0");
require(_bps <= BPS, "Ratio exceeds 100%");
bondRatioBps = _bps;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use custom errors instead of require strings in the new setter.

🔧 Suggested change
+    error BondRatioZero();
+    error BondRatioTooHigh();
@@
     function setBondRatioBps(uint256 _bps) external onlyOwner {
-        require(_bps > 0, "Ratio must be > 0");
-        require(_bps <= BPS, "Ratio exceeds 100%");
+        if (_bps == 0) revert BondRatioZero();
+        if (_bps > BPS) revert BondRatioTooHigh();
         bondRatioBps = _bps;
     }

As per coding guidelines src/**/*.sol: Implement custom errors in Solidity (e.g., SolverNotActive()) instead of require strings.

🤖 Prompt for AI Agents
In `@src/SolverRegistry.sol` around lines 468 - 474, Replace the inline require
string checks in setBondRatioBps with custom errors: declare two errors (e.g.,
InvalidBondRatioZero() and BondRatioExceedsMax()) near the contract's error
declarations, then in setBondRatioBps(uint256 _bps) use conditional reverts
(e.g., if (_bps == 0) revert InvalidBondRatioZero(); if (_bps > BPS) revert
BondRatioExceedsMax();) and keep the assignment to bondRatioBps and the
onlyOwner modifier unchanged so callers see the new custom error types instead
of require strings.

@jeremylongshore jeremylongshore merged commit 82bd22d into main Feb 12, 2026
4 of 9 checks passed
@jeremylongshore jeremylongshore deleted the fix/phase-0-security branch February 12, 2026 19:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant