Skip to content

Conversation

@reednaa
Copy link
Member

@reednaa reednaa commented Jul 11, 2025

Description

A multichain Input intent is an intent that has assets inputs, provided by the user, on multiple chains in exchange for a singular set of outputs. Such an intent has strong conditional guarantees on the outputs: Either all outputs are delivered in exchange for all inputs or none inputs are provided. An example of such an intent could be:

User provides:

  • 100 USDC on Arbitrum
  • 200 USDT on Optimism
  • 100 USDC on Optimism

In exchange for:

  • 0,01 Ether on Base
  • 250 USDC on Base

The system now supports 2 types of orders:

  • StandardOrder: Multiple inputs on 1 chain with multiple outputs on multiple chains.
  • MultichainOrderComponent: Multiple inputs on multiple chains with multiple outputs on multiple chains.

Additionally, 2 collection mechanisms have been implemented in accordance with discussions:

  • The Compact: Maps a MultichainOrderComponent to MultichainCompact with MultichainOrderComponent.additionalChains being element hashes.
  • Escrow: Each MultichainOrderComponent is a separate orders that has to be collected with MultichainOrderComponent.additionalChains being a custom hashing of component inputs.

Bug:
The current version has a severe bug related to the encoding of the oracles. Since oracle addresses vary by chain, it is likely that the same orderId cannot be replicated on different chains. The oracles should be considered moved into the input components.

Notable Quirks:

OrderComponents

A multichain order is never fully represented on chain. Multichain order can potentially be fairly big and it would significant gas and calldata overhead. As a result, each order is only represented on-chain in components. Thus they need a way to be passed off-chain. This PR does not relate to that structure. Additionally, like the StandardOrder each component is never signed in its entirety. It is signed by proxy through either Compact or Permit2. As a result, there are 3 types of orders in the system:

  • On-chain representation through MultichainOrderComponent
  • Signed Order through PermitBatchWitnessTransferFrom or MultichainCompact
  • Off-chain order representation

Compact claim hash

Instead of using a custom orderId for InputSettlerMultichainCompact the Compact claim hash is used instead. This ensures that the orderId stays fixed cross-chain. However, this requires re-ordering the action items inside the settler so the claim is settled before the outputs are verified.

Purchase logic.

Purchase logic has been removed. It does not fit cleanly inside these implementations.

Open Tasks

Compact:

  • Implement claim hash derivation.
  • Fix order id function.
  • Example usage.
  • Remove purchase logic from base implementation?
  • Outstanding implementation TODOs.
  • Testing.

Escrow:

  • Ensure you can't select a random input (implemented through secondaries) (Check how TheCompact does it)
  • No hash collision of order id on inputs
  • Example usage.
  • Remove purchase logic from base implementation?
  • Permit2 openFor
  • Encoding non-same address swaps? (Or decide out of scope)
  • Testing

Summary by CodeRabbit

  • New Features

    • Multichain input settlement (compact & escrow) with cross-chain finalisation, Permit2 and signature flows; comprehensive multichain test suites and tooling
  • Refactor

    • Hardened caller authorization checks in input settlement finalisation flows
  • Public API

    • MandateOutput extended with recipient, callbackData and context fields
  • Chores

    • Reduced fuzzing iterations to speed up test runs

@reednaa reednaa requested a review from frangio July 11, 2025 12:41
@codecov
Copy link

codecov bot commented Jul 11, 2025

@reednaa reednaa added this to the multichain milestone Jul 11, 2025
@reednaa reednaa self-assigned this Jul 14, 2025
@reednaa reednaa linked an issue Jul 14, 2025 that may be closed by this pull request
@reednaa reednaa removed a link to an issue Jul 14, 2025
@reednaa reednaa changed the title Compact multichain claim Multichain Compact claim Jul 14, 2025
@reednaa reednaa changed the title Multichain Compact claim Multichain Inputs Jul 14, 2025
@reednaa reednaa linked an issue Jul 14, 2025 that may be closed by this pull request
@reednaa reednaa moved this to In progress in Open Intents Framework Jul 14, 2025
@reednaa
Copy link
Member Author

reednaa commented Jul 14, 2025

  • Ensure you can't select a random input (potentially via a selector hash) (Check how TheCompact does it)

I believe if we make the originChainId field into a generic chainId field (which would be used differently between escrow and compact) then we could add it as the first 32 bytes of each hash.

@reednaa reednaa mentioned this pull request Jul 23, 2025
5 tasks
@reednaa reednaa changed the base branch from multichain-inputs-1 to main July 31, 2025 13:22
) internal view returns (bytes32) {
return keccak256(
abi.encodePacked(
address(this),
Copy link
Member Author

Choose a reason for hiding this comment

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

Do we wanna consider multichain orders with non-same address InputSettlers?
This is particularly relevant for cross-vm inputs.

My, current, best bad idea for this would be to encode it in the inputs instead of the core identifier.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm okay with keeping the assumption of same address for simplicity. We can reevaluate it for a future version.

This may be a dumb question but can you remind me why it's critical to include the address in the identifier? I think I can see some risk in the escrow variant, if you have two settlers whose order ids overlap, a single fill on destination could allow claiming funds from two separate orders, although this seems unlikely? In The Compact the user's signature will include a single arbiter per chain so I don't see a risk there. Am I missing something?

@reednaa reednaa marked this pull request as ready for review September 8, 2025 11:34
Copy link
Member Author

@reednaa reednaa left a comment

Choose a reason for hiding this comment

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

Self review:
Documentation sucks and MultichainCompactOrderType needs to be cleaned.

More documentation is needed but this sets a good initial full implementation

@reednaa reednaa requested review from luiz-lvj and pepebndc September 8, 2025 14:17
@reednaa reednaa moved this from In progress to In review in Open Intents Framework Sep 23, 2025
@reednaa
Copy link
Member Author

reednaa commented Oct 29, 2025

We need to figure out a way to handle the input oracle. It has to be the same address on every chain which is not gonna be true.

@coderabbitai
Copy link

coderabbitai bot commented Oct 29, 2025

Walkthrough

This PR adds multichain input settlement (compact and escrow), new multichain order/witness hashing types, Permit2 multichain witness support, refactors caller validation (adds UnexpectedCaller/_validateIsCaller and removes _orderOwnerIsCaller), renames test references from "Coin" → "Simple", updates snapshots, and reduces foundry fuzz runs.

Changes

Cohort / File(s) Summary
Configuration & Snapshots
\foundry.toml`, `snapshots/inputSettler.json`, `snapshots/outputSettler.json``
Reduced fuzz runs 10,000 → 1,000; updated numeric snapshot values and renamed snapshot keys from coin-prefixed to simple-prefixed variants.
Core Validation Refactor
\src/input/InputSettlerBase.sol`, `src/input/InputSettlerPurchase.sol`, `src/input/compact/InputSettlerCompact.sol`, `src/input/escrow/InputSettlerEscrow.sol``
Added UnexpectedCaller(bytes32) and internal _validateIsCaller(bytes32) in base; removed _orderOwnerIsCaller from purchase; replaced calls to the removed helper with _validateIsCaller in compact and escrow finalise flows.
Multichain Order Types
\src/input/types/MultichainOrderComponentType.sol`, `src/input/types/MultichainCompactOrderType.sol``
New MultichainOrderComponent struct and libraries for input hashing, chain-indexed input assembly, witness and compact order identifier hashing (assembly-optimized).
Permit2 Multichain Witness & MandateOutput
\src/input/escrow/Permit2MultichainWitnessType.sol`, `src/input/types/MandateOutputType.sol``
Added MultichainPermit2 witness type, hashing helpers and constants; MandateOutputType updated (stub field name change and memory-backed hashing helpers).
Multichain Settlers (New Contracts)
\src/input/compact/InputSettlerMultichainCompact.sol`, `src/input/escrow/InputSettlerMultichainEscrow.sol``
New contracts implementing multichain settlement flows: orderIdentifier helpers, finalise/finaliseWithSignature flows, lock resolution, escrow state machine, Permit2 support, signature handling, and optional callbacks.
Tests — Multichain Test Bases & Tests
\test/input/compact/InputSettlerMultichainCompact.base.t.sol`, `test/input/compact/InputSettlerMultichainCompact.t.sol`, `test/input/escrow/InputSettlerMultichainEscrow.base.t.sol`, `test/input/escrow/InputSettlerMultichainEscrow.t.sol``
New comprehensive test bases and tests: Wormhole VAA generation, EIP712 signing helpers, Permit2 signature assembly, multichain scenarios exercising open/finalise, signatures, and balance assertions.
Tests — Renames & Adjustments
\test/exploit/FillOutputsTwice.t.sol`, `test/input/compact/InputSettlerCompact.base.t.sol`, `test/input/compact/InputSettlerCompact.t.sol`, `test/input/escrow/InputSettlerEscrow.base.t.sol`, `test/input/escrow/InputSettlerEscrow.t.sol`, `test/integration/SettlerCompact.crosschain.t.sol`, `test/libs/IsContractLib.t.sol`, `test/output/OutputSettlerSimple.fill.t.sol`, `test/output/OutputSettlerSimple.fillOrderOutputs.t.sol`, `test/libs/MultichainOrderComponentsType.sol``
Renamed test variables from outputSettlerCoinoutputSettlerSimple and updated references; tests updated to expect UnexpectedCaller(bytes32) where applicable; added/adjusted test helpers and wrappers for multichain type testing.
Misc / Formatting
assorted test files
Minor whitespace/formatting edits and small event/signature expectation updates in tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant InputSettler as InputSettlerMultichain<br/>(Compact/Escrow)
    participant TheCompact
    participant OutputSettler as OutputSettlerSimple
    participant Wormhole

    rect rgb(230,240,255)
    Note over User,InputSettler: Phase 1 — Deposit / Open
    User->>InputSettler: open(order) or openFor(order, sig)
    InputSettler->>InputSettler: validate order, compute orderId
    InputSettler->>InputSettler: collect inputs (Permit2 / ERC20)
    InputSettler-->>User: Order Deposited (state saved)
    end

    rect rgb(240,255,230)
    Note over OutputSettler,Wormhole: Phase 2 — Output Execution (dest chain)
    OutputSettler->>OutputSettler: fill(outputs)
    OutputSettler->>Wormhole: emit MandateOutput payload (VAA)
    Wormhole-->>User: VAA (cross-chain proof)
    end

    rect rgb(255,250,230)
    Note over User,TheCompact: Phase 3 — Finalise / Claim
    User->>InputSettler: finalise(order, solveParams, destination, signatures/vaa)
    InputSettler->>InputSettler: _validateIsCaller / validate destination & fills
    InputSettler->>TheCompact: batchMultichainClaim or exogenousBatchClaim(claim, vaa)
    TheCompact->>TheCompact: verify claim, execute settlement
    InputSettler-->>User: Order Finalised (transfers completed)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus on:

  • InputSettlerMultichainCompact.sol & InputSettlerMultichainEscrow.sol — lifecycle, state transitions, reentrancy, external callbacks.
  • MultichainOrderComponentType.sol & MultichainCompactOrderType.sol — assembly-based hashing correctness and chain-index logic.
  • Caller validation changes — ensure _validateIsCaller semantics match prior expectations across call sites.
  • Permit2 witness and MandateOutput hashing/EIP712 interactions — type strings, hashes, and signature assembly.
  • Test cryptographic helpers (VAA/EIP712) — correctness of test harness utility functions.

Possibly related PRs

Suggested reviewers

  • luiz-lvj

Poem

"A rabbit hops through chains with glee, 🐇
Validators hum, and hashes flee,
Orders span both near and far,
Simple settlers light the star,
Multichain hops — settlements free!"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Multichain Inputs' is concise and directly describes the main feature addition to the codebase.
Description check ✅ Passed The PR description is comprehensive, covering multichain order types, implementation mechanisms, known bugs, notable quirks, and open tasks. It exceeds typical requirements with detailed technical context.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch compact-multichain-1

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

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: 4

🧹 Nitpick comments (5)
test/output/OutputSettlerSimple.fillOrderOutputs.t.sol (2)

4-9: Minor: Import formatting style changed.

Import statements have been reformatted to remove spaces inside curly braces (e.g., {Test} vs { Test }). While this is valid, ensure this aligns with the project's style guidelines.


60-66: Optional: Consider consistent formatting for multi-condition statements.

Several vm.assume and conditional statements have been reformatted to span multiple lines. While this can improve readability for long conditions, the formatting style varies throughout the file. Consider adopting a consistent approach.

Also applies to: 226-227, 257-259, 332-334, 423-425

test/output/OutputSettlerSimple.fill.t.sol (1)

4-10: Minor: Import formatting style changed.

Same as in the fillOrderOutputs test file, imports have been reformatted to a more compact style. Ensure consistency across the codebase.

test/input/escrow/InputSettlerMultichainEscrow.base.t.sol (2)

136-149: Document the hardcoded VAA structure values.

The hex literals (hex"000003e8", hex"00000001", hex"0000000000000539", hex"0f") represent specific fields in the Wormhole VAA structure but lack explanation, making the code harder to understand and maintain.

Consider adding inline comments or extracting to named constants:

 function _buildPreMessage(
     uint16 emitterChainId,
     bytes32 emitterAddress
 ) internal pure returns (bytes memory preMessage) {
     return
         abi.encodePacked(
-            hex"000003e8"
-            hex"00000001",
+            hex"000003e8" // timestamp
+            hex"00000001", // nonce
             emitterChainId,
             emitterAddress,
-            hex"0000000000000539"
-            hex"0f"
+            hex"0000000000000539" // sequence
+            hex"0f" // consistency level
         );
 }

247-287: Extract hardcoded type strings to use existing production constants.

The MandateOutput type string is duplicated identically in witnessHash (line 209), outputsHash (line 230), and getPermit2Signature (line 273). The Permit2 PermitBatchWitnessTransferFrom type string format is correct, but both concerns can be resolved by importing constants already defined in the production code (src/input/escrow/Permit2MultichainWitnessType.sol, src/input/types/MandateOutputType.sol) instead of hardcoding them inline.

Import and reuse the existing constants from the production type modules rather than duplicating type strings across test functions or creating redundant test-specific constants.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2078e92 and 68cc406.

📒 Files selected for processing (27)
  • foundry.toml (1 hunks)
  • snapshots/inputSettler.json (1 hunks)
  • snapshots/outputSettler.json (1 hunks)
  • src/input/InputSettlerBase.sol (2 hunks)
  • src/input/InputSettlerPurchase.sol (0 hunks)
  • src/input/compact/InputSettlerCompact.sol (1 hunks)
  • src/input/compact/InputSettlerMultichainCompact.sol (1 hunks)
  • src/input/escrow/InputSettlerEscrow.sol (18 hunks)
  • src/input/escrow/InputSettlerMultichainEscrow.sol (1 hunks)
  • src/input/escrow/Permit2MultichainWitnessType.sol (1 hunks)
  • src/input/types/MandateOutputType.sol (3 hunks)
  • src/input/types/MultichainCompactOrderType.sol (1 hunks)
  • src/input/types/MultichainOrderComponentType.sol (1 hunks)
  • test/exploit/FillOutputsTwice.t.sol (6 hunks)
  • test/input/compact/InputSettlerCompact.base.t.sol (2 hunks)
  • test/input/compact/InputSettlerCompact.t.sol (14 hunks)
  • test/input/compact/InputSettlerMultichainCompact.base.t.sol (1 hunks)
  • test/input/compact/InputSettlerMultichainCompact.t.sol (1 hunks)
  • test/input/escrow/InputSettlerEscrow.base.t.sol (5 hunks)
  • test/input/escrow/InputSettlerEscrow.t.sol (4 hunks)
  • test/input/escrow/InputSettlerMultichainEscrow.base.t.sol (1 hunks)
  • test/input/escrow/InputSettlerMultichainEscrow.t.sol (1 hunks)
  • test/integration/SettlerCompact.crosschain.t.sol (19 hunks)
  • test/libs/IsContractLib.t.sol (1 hunks)
  • test/libs/MultichainOrderComponentsType.sol (1 hunks)
  • test/output/OutputSettlerSimple.fill.t.sol (42 hunks)
  • test/output/OutputSettlerSimple.fillOrderOutputs.t.sol (20 hunks)
💤 Files with no reviewable changes (1)
  • src/input/InputSettlerPurchase.sol
🔇 Additional comments (21)
test/input/compact/InputSettlerCompact.base.t.sol (1)

58-58: LGTM: Clean variable rename.

The rename from outputSettlerCoin to outputSettlerSimple is consistent with the broader refactoring across the test suite.

Also applies to: 99-99

foundry.toml (1)

10-10: Verify adequacy of reduced fuzz iterations.

The fuzzing iterations have been reduced from 10,000 to 1,000 (90% reduction). While this speeds up test execution, it significantly reduces the probability of discovering edge cases, particularly in the new authorization logic (_validateIsCaller) and multichain settlement flows introduced in this PR.

Consider whether this reduction is appropriate given the scope of changes. If test times are a concern, you might instead reduce iterations selectively for specific test files rather than globally.

snapshots/outputSettler.json (1)

2-8: LGTM: Snapshot keys updated consistently.

All snapshot keys have been updated to reflect the OutputSettlerSimple naming. The preserved values confirm this is a mechanical rename with no gas cost impact.

test/libs/IsContractLib.t.sol (1)

22-22: LGTM: Consistent rename throughout.

All references to outputSettlerCoin have been correctly updated to outputSettlerSimple, including the variable declaration, initialization, and test assertions.

Also applies to: 30-30, 36-39

test/exploit/FillOutputsTwice.t.sol (1)

19-19: LGTM: Comprehensive rename applied correctly.

All references to outputSettlerCoin have been systematically updated to outputSettlerSimple across variable declarations, initialization, approvals, fill calls, MandateOutput construction, and attestation checks.

Also applies to: 25-25, 37-37, 42-42, 54-54, 75-75, 83-83, 96-96, 117-117

test/output/OutputSettlerSimple.fillOrderOutputs.t.sol (1)

22-36: LGTM: Variable declarations and setup updated correctly.

The test setup has been properly updated to use OutputSettlerSimple with consistent naming throughout.

test/output/OutputSettlerSimple.fill.t.sol (2)

28-46: LGTM: Test setup properly updated.

All test infrastructure variables have been correctly renamed to use OutputSettlerSimple, with addresses and references updated consistently.


77-86: LGTM: MandateOutput initialization includes all required fields.

The MandateOutput structs now include recipient, callbackData, and context fields, which aligns with the expanded MandateOutput structure mentioned in the AI summary.

Also applies to: 152-161, 194-203

snapshots/inputSettler.json (1)

4-6: LGTM: Gas snapshots reflect authorization refactor.

The small gas cost adjustments (±5-7 gas) are consistent with the replacement of _orderOwnerIsCaller with _validateIsCaller in the authorization paths mentioned in the AI summary.

Also applies to: 8-8

test/input/escrow/InputSettlerEscrow.t.sol (1)

345-353: Identifier-based settler wiring looks correct.

Routing outputSettlerSimple through toIdentifier() keeps the witness hashing and oracle expectations consistent with the new encoding path. Looks solid.

src/input/compact/InputSettlerCompact.sol (1)

150-155: Unified caller validation matches the base helper.

Switching to _validateIsCaller(orderOwner) aligns the compact flow with the shared UnexpectedCaller surface and removes bespoke checks. Nice consolidation.

src/input/InputSettlerBase.sol (1)

145-156: Caller guard helper is tight.

Leveraging LibAddress.fromIdentifier for the comparison and funnelling mismatches through UnexpectedCaller gives downstream settlers a single, predictable error path.

src/input/types/MandateOutputType.sol (1)

29-120: Type hash & memory helpers stay consistent.

Updating the type string to bytes call and mirroring calldata/memory hashing ensures Permit2 witnesses and Compact claims hash the same payloads everywhere.

src/input/escrow/InputSettlerEscrow.sol (1)

438-456: Escrow finalise now reuses the base caller gate.

Using _validateIsCaller(orderOwner) gives the escrow path the same protection and revert semantics that the tests are asserting. Everything lines up.

test/input/escrow/InputSettlerEscrow.base.t.sol (1)

151-186: Permit2 witness hashing update is accurate.

Pre-hashing each TokenPermissions entry and collapsing with a single keccak matches the EIP-712 array encoding and syncs the test helper with on-chain expectations.

test/integration/SettlerCompact.crosschain.t.sol (1)

239-327: Cross-chain witness helpers reflect the new schema.

Renaming to outputSettlerSimple and updating the witness/type strings to bytes call keeps the integration flow aligned with the contract changes.

test/input/escrow/InputSettlerMultichainEscrow.base.t.sol (5)

1-31: LGTM: Clean imports and interface declarations.

The imports are well-organized and the EIP712 interface provides a clean abstraction for accessing domain separators in tests.


33-49: LGTM: Appropriate test double for Wormhole messages.

The mock implementation correctly exposes protected methods for testing and provides a minimal publishMessage implementation suitable for test scenarios.


72-107: LGTM: Comprehensive test fixture setup.

The setUp function properly initializes all required contracts, oracles, test accounts, and the guardian set with appropriate validation.


151-196: LGTM: Correct cryptographic signature construction.

Both makeValidVAA and getOrderOpenSignature properly implement their respective signature schemes with correct hashing and ECDSA signature encoding.


198-245: Type strings verified—no issues found.

The EIP-712 type strings in witnessHash (line 209) and outputsHash (line 230) exactly match their counterparts in the implementation files:

  • Line 209 matches MULTICHAIN_PERMIT2_WITNESS_TYPE in src/input/escrow/Permit2MultichainWitnessType.sol:31
  • Line 230 matches MANDATE_OUTPUT_TYPE_STUB in src/input/types/MandateOutputType.sol:30

The MandateOutput type string duplication across multiple files is expected and correct for EIP-712, as the full type hierarchy must be included in witness type strings.

Comment on lines +223 to +225
chainIndex: order.chainIndex - 1, // We use chainIndex as the offset to elements array where compact
// uses it as offset to the notarized.
notarizedChainId: order.chainIdField
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard chainIndex - 1 to avoid underflow on exogenous chains.

In the exogenous path we do chainIndex: order.chainIndex - 1. If a component for a non-origin chain is positioned at index 0 in the global elements ordering (totally valid for two components ordered [remote, origin]), this subtraction underflows and hard reverts, making those orders impossible to finalise. Please require order.chainIndex > 0 (and revert with a clear error) before subtracting, or otherwise adjust the logic so we never subtract blindly.

🤖 Prompt for AI Agents
In src/input/compact/InputSettlerMultichainCompact.sol around lines 223 to 225,
the exogenous path subtracts 1 from order.chainIndex without checking and can
underflow when chainIndex == 0; add a guard that requires order.chainIndex > 0
(revert with a clear error string) before performing chainIndex - 1, or alter
the branch so it never performs the subtraction for index 0 (e.g., handle the
origin-at-zero case separately), and update any dependent variables to use the
validated value.

Comment on lines 132 to 158
function openFor(MultichainOrderComponent calldata order, address sponsor, bytes calldata signature) external {
// Validate the order structure.
_validateTimestampHasNotPassed(order.fillDeadline);
_validateTimestampHasNotPassed(order.expires);

bytes32 orderId = _orderIdentifier(order);

if (orderStatus[orderId] != OrderStatus.None) revert InvalidOrderStatus();
// Mark order as deposited. If we can't make the deposit, we will
// revert and it will unmark it. This acts as a reentry check.
orderStatus[orderId] = OrderStatus.Deposited;

// Check the first byte of the signature for signature type then collect inputs.
bytes1 signatureType = signature.length > 0 ? signature[0] : SIGNATURE_TYPE_SELF;
if (signatureType == SIGNATURE_TYPE_PERMIT2) {
_openForWithPermit2(order, orderId, sponsor, signature[1:], address(this));
} else if (msg.sender == sponsor && signatureType == SIGNATURE_TYPE_SELF) {
_open(order);
} else {
revert SignatureNotSupported(signatureType);
}

// Validate that there has been no reentrancy.
if (orderStatus[orderId] != OrderStatus.Deposited) revert ReentrancyDetected();

emit Open(orderId, abi.encode(order));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add _validateInputChain to openFor.

openFor currently skips the chain check, so a sponsor can deposit on the wrong chain. Later finalise/refund re-run _validateInputChain and revert, leaving the escrowed tokens stuck. Mirror the open path by validating the chain field before marking the order deposited.

         _validateTimestampHasNotPassed(order.fillDeadline);
         _validateTimestampHasNotPassed(order.expires);
+        _validateInputChain(order.chainIdField);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function openFor(MultichainOrderComponent calldata order, address sponsor, bytes calldata signature) external {
// Validate the order structure.
_validateTimestampHasNotPassed(order.fillDeadline);
_validateTimestampHasNotPassed(order.expires);
bytes32 orderId = _orderIdentifier(order);
if (orderStatus[orderId] != OrderStatus.None) revert InvalidOrderStatus();
// Mark order as deposited. If we can't make the deposit, we will
// revert and it will unmark it. This acts as a reentry check.
orderStatus[orderId] = OrderStatus.Deposited;
// Check the first byte of the signature for signature type then collect inputs.
bytes1 signatureType = signature.length > 0 ? signature[0] : SIGNATURE_TYPE_SELF;
if (signatureType == SIGNATURE_TYPE_PERMIT2) {
_openForWithPermit2(order, orderId, sponsor, signature[1:], address(this));
} else if (msg.sender == sponsor && signatureType == SIGNATURE_TYPE_SELF) {
_open(order);
} else {
revert SignatureNotSupported(signatureType);
}
// Validate that there has been no reentrancy.
if (orderStatus[orderId] != OrderStatus.Deposited) revert ReentrancyDetected();
emit Open(orderId, abi.encode(order));
}
function openFor(MultichainOrderComponent calldata order, address sponsor, bytes calldata signature) external {
// Validate the order structure.
_validateTimestampHasNotPassed(order.fillDeadline);
_validateTimestampHasNotPassed(order.expires);
_validateInputChain(order.chainIdField);
bytes32 orderId = _orderIdentifier(order);
if (orderStatus[orderId] != OrderStatus.None) revert InvalidOrderStatus();
// Mark order as deposited. If we can't make the deposit, we will
// revert and it will unmark it. This acts as a reentry check.
orderStatus[orderId] = OrderStatus.Deposited;
// Check the first byte of the signature for signature type then collect inputs.
bytes1 signatureType = signature.length > 0 ? signature[0] : SIGNATURE_TYPE_SELF;
if (signatureType == SIGNATURE_TYPE_PERMIT2) {
_openForWithPermit2(order, orderId, sponsor, signature[1:], address(this));
} else if (msg.sender == sponsor && signatureType == SIGNATURE_TYPE_SELF) {
_open(order);
} else {
revert SignatureNotSupported(signatureType);
}
// Validate that there has been no reentrancy.
if (orderStatus[orderId] != OrderStatus.Deposited) revert ReentrancyDetected();
emit Open(orderId, abi.encode(order));
}
🤖 Prompt for AI Agents
In src/input/escrow/InputSettlerMultichainEscrow.sol around lines 132 to 158,
openFor lacks the chain validation present in open, which allows sponsors to
deposit on the wrong chain and lock funds; call
_validateInputChain(order.inputChain) early (before marking orderStatus =
OrderStatus.Deposited and before performing any state changes) so the input
chain is checked and the function reverts on mismatch, mirroring the open path’s
behavior.

Comment on lines +144 to +153
Lock memory lock
) internal pure returns (uint256[2] memory arr) {
bytes12 lockTag = lock.lockTag;
address lockToken = lock.token;
uint256 elem0;
assembly {
elem0 := add(lockTag, lockToken)
}
arr[0] = elem0;
arr[1] = lock.amount;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix lock encoding so round-trips preserve the lock tag.

Adding lockTag and lockToken drops the lock tag into the low 12 bytes instead of the high 12 bytes, so uintToLock later recovers a zero lockTag. Pack the fields by shifting the 12‑byte tag left by 160 bits and OR-ing the 20‑byte token address, matching the identifier format expected on-chain.

-        assembly {
-            elem0 := add(lockTag, lockToken)
-        }
+        uint256 elem0 = (uint256(uint96(lockTag)) << 160) | uint256(uint160(lockToken));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test/input/compact/InputSettlerMultichainCompact.base.t.sol around lines 144
to 153, the current assembly uses add(lockTag, lockToken) which places the
12-byte lockTag into the low bytes instead of the high bytes and causes
uintToLock to recover a zero tag; replace the addition with a pack that shifts
the 12-byte tag left by 160 bits and ORs (or uses bitwise or) the 20-byte token
into the low 160 bits so the identifier matches on-chain format (i.e., compute
elem0 = (uint256(lockTag) << 160) | uint256(uint160(lockToken))).

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

♻️ Duplicate comments (4)
src/input/compact/InputSettlerMultichainCompact.sol (1)

208-223: Guard against chainIndex - 1 underflow in the exogenous claim.

Line 219 computes order.chainIndex - 1 with no prior check. If the exogenous component happens to occupy position 0 in the global ordering, this subtraction underflows and every exogenous finalise hard-reverts. Add an explicit guard (or adjust the arithmetic) before subtracting so index 0 is either handled separately or rejected with a clear error.

+        if (order.chainIdField != block.chainid) {
+            if (order.chainIndex == 0) revert InvalidChainIndex();
+        }
...
-                    chainIndex: order.chainIndex - 1,
+                    chainIndex: order.chainIndex - 1,

(Add error InvalidChainIndex(); near the other custom errors.)

src/input/escrow/InputSettlerMultichainEscrow.sol (1)

132-162: Validate the chain field in openFor to avoid trapping deposits.

Unlike open, openFor skips _validateInputChain. A sponsor can therefore deposit on the wrong chain, orderStatus flips to Deposited, and every later finalise/refund hits _validateInputChain and reverts—locking user funds. Call _validateInputChain(order.chainIdField); before computing the orderId to mirror the open path.

         _validateTimestampHasNotPassed(order.fillDeadline);
         _validateTimestampHasNotPassed(order.expires);
+        _validateInputChain(order.chainIdField);
 
         bytes32 orderId = _orderIdentifier(order);
src/input/types/MultichainCompactOrderType.sol (1)

16-116: Remove forge console from the production hashing path.

Line 108 still calls console.logBytes(...), and the import at Line 16 brings in Foundry’s cheatcode. On a live chain this external cheatcode address doesn’t exist, so any orderIdentifier call will revert, breaking every consumer (including InputSettlerMultichainCompact.orderIdentifier). Please drop the console import/call before shipping.

-import { console } from "forge-std/console.sol";
...
-        console.logBytes(
-            abi.encode(
-                ELEMENTS_COMPACT_TYPEHASH_WITH_WITNESS,
-                address(this),
-                block.chainid,
-                inputsToLocksHash(order.inputs),
-                witnessHash(order)
-            )
-        );
test/input/compact/InputSettlerMultichainCompact.base.t.sol (1)

125-134: Lock packing still stomps the tag bytes.

add(lockTag, lockToken) writes the tag into the low 160 bits, so uintToLock later recovers a zero lockTag. We need to left-shift the tag by 160 bits before OR-ing the token address to preserve the on-chain layout.

-        uint256 elem0;
-        assembly {
-            elem0 := add(lockTag, lockToken)
-        }
+        uint256 elem0 = (uint256(uint96(lockTag)) << 160) | uint256(uint160(lockToken));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68cc406 and d2e6096.

📒 Files selected for processing (19)
  • snapshots/inputSettler.json (1 hunks)
  • src/input/InputSettlerBase.sol (2 hunks)
  • src/input/compact/InputSettlerMultichainCompact.sol (1 hunks)
  • src/input/escrow/InputSettlerEscrow.sol (2 hunks)
  • src/input/escrow/InputSettlerMultichainEscrow.sol (1 hunks)
  • src/input/escrow/Permit2MultichainWitnessType.sol (1 hunks)
  • src/input/types/MandateOutputType.sol (2 hunks)
  • src/input/types/MultichainCompactOrderType.sol (1 hunks)
  • test/input/compact/InputSettlerCompact.base.t.sol (2 hunks)
  • test/input/compact/InputSettlerCompact.t.sol (6 hunks)
  • test/input/compact/InputSettlerMultichainCompact.base.t.sol (1 hunks)
  • test/input/compact/InputSettlerMultichainCompact.t.sol (1 hunks)
  • test/input/escrow/InputSettlerEscrow.base.t.sol (5 hunks)
  • test/input/escrow/InputSettlerMultichainEscrow.base.t.sol (1 hunks)
  • test/input/escrow/InputSettlerMultichainEscrow.t.sol (1 hunks)
  • test/integration/SettlerCompact.crosschain.t.sol (10 hunks)
  • test/libs/MultichainOrderComponentsType.sol (1 hunks)
  • test/output/OutputSettlerSimple.fill.t.sol (41 hunks)
  • test/output/OutputSettlerSimple.fillOrderOutputs.t.sol (18 hunks)
✅ Files skipped from review due to trivial changes (1)
  • test/input/compact/InputSettlerCompact.base.t.sol
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/input/escrow/InputSettlerEscrow.sol
  • test/input/compact/InputSettlerCompact.t.sol
  • test/output/OutputSettlerSimple.fillOrderOutputs.t.sol
  • test/integration/SettlerCompact.crosschain.t.sol
  • src/input/types/MandateOutputType.sol
  • src/input/InputSettlerBase.sol
  • snapshots/inputSettler.json
🔇 Additional comments (3)
test/output/OutputSettlerSimple.fill.t.sol (1)

24-865: LGTM! Clean and consistent refactoring.

The systematic renaming from OutputSettlerCoin to OutputSettlerSimple is complete and consistent throughout the test file. All variable declarations, address references, approvals, gas snapshot labels, and MandateOutput constructions have been properly updated. The test logic remains unchanged, ensuring continued coverage of the OutputSettler functionality.

test/input/escrow/InputSettlerEscrow.base.t.sol (2)

36-36: LGTM: Consistent renaming to OutputSettlerSimple.

The variable rename from outputSettlerCoin to outputSettlerSimple is applied consistently across declaration, initialization, and usage. This aligns with the broader test refactoring mentioned in the PR summary.

Also applies to: 72-72, 89-89


156-163: LGTM: Correct EIP-712 array encoding for Permit2.

The refactored token permissions hashing correctly implements EIP-712 encoding for arrays of structs: each TokenPermissions element is hashed individually, then the array of hashes is packed and hashed together. This aligns with the PermitBatchWitnessTransferFrom witness type structure on line 172.

Also applies to: 174-174

@reednaa
Copy link
Member Author

reednaa commented Nov 7, 2025

The current version has a severe bug related to the encoding of the oracles. Since oracle addresses vary by chain, it is likely that the same orderId cannot be replicated on different chains. The oracles should be considered moved into the input components.

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: 0

♻️ Duplicate comments (1)
src/input/types/MultichainCompactOrderType.sol (1)

16-16: Remove unused console import.

The console import is no longer used after the console.logBytes call was removed per the previous review. Development dependencies like forge-std should not remain in production code.

Apply this diff to remove the unused import:

-import { console } from "forge-std/console.sol";
🧹 Nitpick comments (2)
src/input/types/MultichainCompactOrderType.sol (2)

18-23: Update library documentation to match actual functionality.

The documentation references TYPE_PARTIAL, TYPE_STUB, and TYPE which are not used in the code. Consider replacing this with a description of the library's actual purpose: computing EIP-712 structured hashes and order identifiers for multichain compact orders.


83-83: Consider the suggested assembly optimization.

The TODO suggests replacing the ternary with sub(i, gt(i, index)) which would be equivalent and potentially save gas. This is a valid micro-optimization if gas efficiency is a priority.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2e6096 and d9d9768.

📒 Files selected for processing (2)
  • src/input/types/MultichainCompactOrderType.sol (1 hunks)
  • test/input/compact/InputSettlerMultichainCompact.base.t.sol (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Foundry Smart Contract Test
test/input/compact/InputSettlerMultichainCompact.base.t.sol

[error] 1-1: forge fmt --check found formatting diffs that need to be fixed in InputSettlerMultichainCompact.base.t.sol.

🔇 Additional comments (6)
src/input/types/MultichainCompactOrderType.sol (4)

43-62: LGTM!

The function correctly computes an EIP-712 compliant hash of the input locks. The memory-safe assembly usage is appropriate for efficient array construction.


64-90: LGTM!

The insertion and hashing logic is correct. The function properly inserts the new element at the specified index and adjusts array indices accordingly.


95-114: Verify cross-chain orderId consistency with contract address dependency.

The function uses address(this) (line 102) in the elementHash computation, which will differ if contracts are deployed at different addresses on different chains. This aligns with the known bug mentioned in the PR objectives: "oracle addresses vary by chain, making identical orderId replication across chains unlikely."

Please confirm this limitation is acceptable for the current implementation or if it requires immediate resolution before merge.


116-128: LGTM!

The witness hash computation correctly reuses the StandardOrder witness type and properly encodes the mandate fields. The pure function designation is appropriate.

test/input/compact/InputSettlerMultichainCompact.base.t.sol (2)

131-135: Fix lock identifier packing.

add(lockTag, lockToken) pushes the 12‑byte tag down into the low bits, so uintToLock later reads a zero tag; the lock identifier no longer round-trips. Please shift the tag into the upper 96 bits and OR the token into the low 160 bits so the encoding matches the on-chain format. Based on learningsduplicate_commentraise_critical_issue
Apply this diff:

-        assembly {
-            elem0 := add(lockTag, lockToken)
-        }
+        uint256 elem0 =
+            (uint256(uint96(lockTag)) << 160) | uint256(uint160(lockToken));

248-256: vm.sign breaks pure mutability.

This helper calls the cheatcode vm.sign, which is neither view nor pure. Marking the function pure makes the contract fail to compile. Drop the pure mutability (or move the signing out) so the code builds. raise_critical_issue
Apply this diff:

-    ) internal pure returns (bytes memory sig) {
+    ) internal returns (bytes memory sig) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multichain inputs

3 participants