diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..9ccec6762 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,189 @@ +# Across Protocol Smart Contracts + +This repository contains production smart contracts for the Across Protocol cross-chain bridge. + +## Development Frameworks + +- **Foundry** (primary) - Used for new tests and deployment scripts +- **Hardhat** (legacy) - Some tests still use Hardhat; we're migrating to Foundry + +## Project Structure + +``` +contracts/ # Smart contract source files + chain-adapters/ # L1 chain adapters + interfaces/ # Interface definitions + libraries/ # Shared libraries +test/evm/ + foundry/ # Foundry tests (.t.sol) + local/ # Local unit tests + fork/ # Fork tests + hardhat/ # Legacy Hardhat tests (.ts) +script/ # Foundry deployment scripts (.s.sol) + utils/ # Script utilities (Constants.sol, DeploymentUtils.sol) +lib/ # External dependencies (git submodules) +``` + +## Build & Test Commands + +```bash +# Build contracts +forge build # Foundry +yarn build-evm # Hardhat + +# Run tests +yarn test-evm-foundry # Foundry local tests (recommended; uses FOUNDRY_PROFILE=local-test) +FOUNDRY_PROFILE=local-test forge test # Required for local Foundry tests in this repo +yarn test-evm-hardhat # Hardhat tests (legacy) + +# Run specific Foundry tests +FOUNDRY_PROFILE=local-test forge test --match-test testDeposit +FOUNDRY_PROFILE=local-test forge test --match-contract Router_Adapter +FOUNDRY_PROFILE=local-test forge test -vvv # Verbose output +``` + +Use `FOUNDRY_PROFILE=local-test` (or `yarn test-evm-foundry`) for local Foundry test runs; do not use plain `forge test`. + +## Naming Conventions + +### Contract Files + +- PascalCase with underscores for chain-specific: `Arbitrum_SpokePool.sol`, `OP_Adapter.sol` +- Interfaces: `I` prefix: `ISpokePool.sol`, `IArbitrumBridge.sol` +- Libraries: `Lib.sol` + +### Test Files + +- Foundry: `.t.sol` suffix: `Router_Adapter.t.sol`, `Arbitrum_Adapter.t.sol` +- Test contracts: `contract Test is Test { ... }` +- Test functions: `function test() public` + +### Deployment Scripts + +- Numbered with `.s.sol` suffix: `001DeployHubPool.s.sol`, `004DeployArbitrumAdapter.s.sol` +- Script contracts: `contract Deploy is Script, Test, Constants` + +## Writing Tests + +```solidity +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; +import { MyContract } from "../contracts/MyContract.sol"; + +contract MyContractTest is Test { + MyContract public myContract; + + function setUp() public { + myContract = new MyContract(); + } + + function testBasicFunctionality() public { + // Test implementation + assertEq(myContract.value(), expected); + } + + function testRevertOnInvalidInput() public { + vm.expectRevert(); + myContract.doSomething(invalidInput); + } +} +``` + +### Test Gotchas + +- **Mocks**: Check `contracts/test/` for existing mocks before creating new ones (MockCCTP.sol, ArbitrumMocks.sol, etc.) +- **MockSpokePool**: Requires UUPS proxy deployment: `new ERC1967Proxy(address(new MockSpokePool(weth)), abi.encodeCall(MockSpokePool.initialize, (...)))` +- **vm.mockCall pattern** (prefer over custom mocks for simple return values): + ```solidity + vm.etch(fakeAddr, hex"00"); // Bypass extcodesize check + vm.mockCall(fakeAddr, abi.encodeWithSelector(SELECTOR), abi.encode(returnVal)); + vm.expectCall(fakeAddr, msgValue, abi.encodeWithSelector(SELECTOR, arg1)); + ``` +- **Delegatecall context**: Adapter tests via HubPool emit events from HubPool's address; `vm.expectRevert()` may lose error data + +## Deployment Scripts + +Scripts follow a numbered pattern and use shared utilities from `script/utils/`. + +```solidity +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; +import { Constants } from "./utils/Constants.sol"; +import { MyContract } from "../contracts/MyContract.sol"; + +// How to run: +// 1. `source .env` where `.env` has MNEMONIC="x x x ... x" and ETHERSCAN_API_KEY="x" +// 2. forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 -vvvv +// 3. Verify simulation works +// 4. Deploy: forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 --broadcast --verify -vvvv + +contract DeployMyContract is Script, Test, Constants { + function run() external { + string memory deployerMnemonic = vm.envString("MNEMONIC"); + uint256 deployerPrivateKey = vm.deriveKey(deployerMnemonic, 0); + + uint256 chainId = block.chainid; + // Validate chain if needed + require(chainId == getChainId("MAINNET"), "Deploy on mainnet only"); + + vm.startBroadcast(deployerPrivateKey); + + MyContract myContract = new MyContract /* constructor args */(); + + console.log("Chain ID:", chainId); + console.log("MyContract deployed to:", address(myContract)); + + vm.stopBroadcast(); + } +} +``` + +For upgradeable contracts, use `DeploymentUtils` which provides `deployNewProxy()`. + +## Configuration + +See `foundry.toml` for Foundry configuration. Key settings: + +- Source: `contracts/` +- Tests: `test/evm/foundry/` +- Solidity: 0.8.30 +- EVM: Prague +- Optimizer: 800 runs with via-ir + +**Do not modify `foundry.toml` without asking** - explain what you want to change and why. + +## Security Practices + +- Follow CEI (Checks-Effects-Interactions) pattern +- Use OpenZeppelin for access control and upgrades +- Validate all inputs at system boundaries +- Use `_requireAdminSender()` for admin-only functions +- UUPS proxy pattern for upgradeable contracts +- Cross-chain ownership: HubPool owns all SpokePool contracts + +## Code Style + +**Prioritize succinctness.** Express features in the least lines possible. This often leads to the most elegant solution: + +- Consolidate duplicate code paths (e.g., one function call with different parameters instead of multiple branches with similar calls) +- Compute values before branching, then use them in a single code path +- Avoid redundant intermediate variables when the expression is clear (although consider gas cost implications, especially for mainnet contracts) +- Prefer early returns to reduce nesting + +## Linting + +```bash +yarn lint-solidity # Solhint for Solidity +yarn lint-js # Prettier for JS/TS +yarn lint-fix # Auto-fix all +``` + +## License + +BUSL-1.1 (see LICENSE file for exceptions) diff --git a/CLAUDE.md b/CLAUDE.md index 9ccec6762..43c994c2d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,189 +1 @@ -# Across Protocol Smart Contracts - -This repository contains production smart contracts for the Across Protocol cross-chain bridge. - -## Development Frameworks - -- **Foundry** (primary) - Used for new tests and deployment scripts -- **Hardhat** (legacy) - Some tests still use Hardhat; we're migrating to Foundry - -## Project Structure - -``` -contracts/ # Smart contract source files - chain-adapters/ # L1 chain adapters - interfaces/ # Interface definitions - libraries/ # Shared libraries -test/evm/ - foundry/ # Foundry tests (.t.sol) - local/ # Local unit tests - fork/ # Fork tests - hardhat/ # Legacy Hardhat tests (.ts) -script/ # Foundry deployment scripts (.s.sol) - utils/ # Script utilities (Constants.sol, DeploymentUtils.sol) -lib/ # External dependencies (git submodules) -``` - -## Build & Test Commands - -```bash -# Build contracts -forge build # Foundry -yarn build-evm # Hardhat - -# Run tests -yarn test-evm-foundry # Foundry local tests (recommended; uses FOUNDRY_PROFILE=local-test) -FOUNDRY_PROFILE=local-test forge test # Required for local Foundry tests in this repo -yarn test-evm-hardhat # Hardhat tests (legacy) - -# Run specific Foundry tests -FOUNDRY_PROFILE=local-test forge test --match-test testDeposit -FOUNDRY_PROFILE=local-test forge test --match-contract Router_Adapter -FOUNDRY_PROFILE=local-test forge test -vvv # Verbose output -``` - -Use `FOUNDRY_PROFILE=local-test` (or `yarn test-evm-foundry`) for local Foundry test runs; do not use plain `forge test`. - -## Naming Conventions - -### Contract Files - -- PascalCase with underscores for chain-specific: `Arbitrum_SpokePool.sol`, `OP_Adapter.sol` -- Interfaces: `I` prefix: `ISpokePool.sol`, `IArbitrumBridge.sol` -- Libraries: `Lib.sol` - -### Test Files - -- Foundry: `.t.sol` suffix: `Router_Adapter.t.sol`, `Arbitrum_Adapter.t.sol` -- Test contracts: `contract Test is Test { ... }` -- Test functions: `function test() public` - -### Deployment Scripts - -- Numbered with `.s.sol` suffix: `001DeployHubPool.s.sol`, `004DeployArbitrumAdapter.s.sol` -- Script contracts: `contract Deploy is Script, Test, Constants` - -## Writing Tests - -```solidity -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Test } from "forge-std/Test.sol"; -import { MyContract } from "../contracts/MyContract.sol"; - -contract MyContractTest is Test { - MyContract public myContract; - - function setUp() public { - myContract = new MyContract(); - } - - function testBasicFunctionality() public { - // Test implementation - assertEq(myContract.value(), expected); - } - - function testRevertOnInvalidInput() public { - vm.expectRevert(); - myContract.doSomething(invalidInput); - } -} -``` - -### Test Gotchas - -- **Mocks**: Check `contracts/test/` for existing mocks before creating new ones (MockCCTP.sol, ArbitrumMocks.sol, etc.) -- **MockSpokePool**: Requires UUPS proxy deployment: `new ERC1967Proxy(address(new MockSpokePool(weth)), abi.encodeCall(MockSpokePool.initialize, (...)))` -- **vm.mockCall pattern** (prefer over custom mocks for simple return values): - ```solidity - vm.etch(fakeAddr, hex"00"); // Bypass extcodesize check - vm.mockCall(fakeAddr, abi.encodeWithSelector(SELECTOR), abi.encode(returnVal)); - vm.expectCall(fakeAddr, msgValue, abi.encodeWithSelector(SELECTOR, arg1)); - ``` -- **Delegatecall context**: Adapter tests via HubPool emit events from HubPool's address; `vm.expectRevert()` may lose error data - -## Deployment Scripts - -Scripts follow a numbered pattern and use shared utilities from `script/utils/`. - -```solidity -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { Test } from "forge-std/Test.sol"; -import { console } from "forge-std/console.sol"; -import { Constants } from "./utils/Constants.sol"; -import { MyContract } from "../contracts/MyContract.sol"; - -// How to run: -// 1. `source .env` where `.env` has MNEMONIC="x x x ... x" and ETHERSCAN_API_KEY="x" -// 2. forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 -vvvv -// 3. Verify simulation works -// 4. Deploy: forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 --broadcast --verify -vvvv - -contract DeployMyContract is Script, Test, Constants { - function run() external { - string memory deployerMnemonic = vm.envString("MNEMONIC"); - uint256 deployerPrivateKey = vm.deriveKey(deployerMnemonic, 0); - - uint256 chainId = block.chainid; - // Validate chain if needed - require(chainId == getChainId("MAINNET"), "Deploy on mainnet only"); - - vm.startBroadcast(deployerPrivateKey); - - MyContract myContract = new MyContract /* constructor args */(); - - console.log("Chain ID:", chainId); - console.log("MyContract deployed to:", address(myContract)); - - vm.stopBroadcast(); - } -} -``` - -For upgradeable contracts, use `DeploymentUtils` which provides `deployNewProxy()`. - -## Configuration - -See `foundry.toml` for Foundry configuration. Key settings: - -- Source: `contracts/` -- Tests: `test/evm/foundry/` -- Solidity: 0.8.30 -- EVM: Prague -- Optimizer: 800 runs with via-ir - -**Do not modify `foundry.toml` without asking** - explain what you want to change and why. - -## Security Practices - -- Follow CEI (Checks-Effects-Interactions) pattern -- Use OpenZeppelin for access control and upgrades -- Validate all inputs at system boundaries -- Use `_requireAdminSender()` for admin-only functions -- UUPS proxy pattern for upgradeable contracts -- Cross-chain ownership: HubPool owns all SpokePool contracts - -## Code Style - -**Prioritize succinctness.** Express features in the least lines possible. This often leads to the most elegant solution: - -- Consolidate duplicate code paths (e.g., one function call with different parameters instead of multiple branches with similar calls) -- Compute values before branching, then use them in a single code path -- Avoid redundant intermediate variables when the expression is clear (although consider gas cost implications, especially for mainnet contracts) -- Prefer early returns to reduce nesting - -## Linting - -```bash -yarn lint-solidity # Solhint for Solidity -yarn lint-js # Prettier for JS/TS -yarn lint-fix # Auto-fix all -``` - -## License - -BUSL-1.1 (see LICENSE file for exceptions) +@AGENTS.md diff --git a/contracts/usdfree/Interfaces.sol b/contracts/usdfree/Interfaces.sol new file mode 100644 index 000000000..00c677d53 --- /dev/null +++ b/contracts/usdfree/Interfaces.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.30; + +struct TypedData { + uint8 typ; + bytes data; +} + +struct Step { + // Note: used for obfuscation of Merkle leaves + bytes32 salt; + address executor; + // Note: interpreted by executor + bytes message; +} + +struct RefundSettings { + address recipient; + uint256 deadline; // claim is possible _after_ deadline +} + +struct Funding { + TypedData[] funding; // type + data. Data includes amounts, per-type structs and signatures if needed for gasless +} + +struct Order { + // Note: used for enforcing orderId uniqueness when necessary (i.e. help an offchain actor maintain orderId => sponsorshipRebate mapping). + // If nonce == 0, orderId uniqueness is not enforced by the contract, which saves us SLOAD + SSTORE operations + bytes32 nonce; + // Note: used for orderId namespacing. Can let orderOwner use nonces for certain commitments + address orderOwner; + // Note: Merkle root that has Steps and RefundSettings as leaves + bytes32 root; +} + +struct SubmitterInputs { + Step step; + // Note: proof that step is a part of order.root Merkle tree + bytes32[] proof; + // Note: submitter funding, that commits to a different (more broad) witness + Funding funding; + // Note: when interpreting step.message provided by the user, step.executor will sometimes reach into executorMessage + // provided here for submitter-provided data. User defines commands + static values, this lets submitter augment + // execution with dynamic data (e.g. auction resolution or DEX swap instructions) + bytes executorMessage; +} + +interface IOrderGateway { + function submit( + Order calldata order, + // Note: Gateway has to check that orderOwnerFunding only pulls funds from order.orderOwner. orderOwner is used for orderId namespacing + Funding calldata orderOwnerFunding, + SubmitterInputs calldata submitterInputs + ) external payable; +} + +library USDFreeIdLib { + // TODO: this will come from a EIP-712 lib + function domainHash(uint32 chainId, address contractAddr) internal pure returns (bytes32) { + return keccak256(abi.encode("USDFree.Domain.V1", chainId, contractAddr)); + } + + function orderId(bytes32 domainH, Order calldata order) internal pure returns (bytes32) { + return keccak256(abi.encode("USDFreeIdLib.OrderId.V1", domainH, order.nonce, order.orderOwner, order.root)); + } + + // Note: called only when there's a merkle tree proof is presented. Otherwise, orderId is enough for the purposese of emitting a unique order execution path + function stepId(bytes32 orderId_, Step calldata step) internal pure returns (bytes32) { + return + keccak256( + abi.encode("USDFreeIdLib.StepId.V1", orderId_, step.salt, step.executor, keccak256(step.message)) + ); + } + + // Note: used as witness for submitter gasless funding, if any + function executionId( + bytes32 orderId_, + bytes32 stepId_, + SubmitterInputs calldata submitterInputs + ) internal pure returns (bytes32) { + return + keccak256( + // TODO: submitterSalt might be useful for varying submitter witness(==TWA nonce), similar to order.nonce? + abi.encode( + "USDFreeIdLib.ExecutionId.V1", + orderId_, // commit to user order + stepId_, // commit to a specific step being executed + keccak256(submitterInputs.executorMessage) // commit to own instructions provided + ) + ); + } +} diff --git a/contracts/usdfree/README.md b/contracts/usdfree/README.md new file mode 100644 index 000000000..13da6e6f9 --- /dev/null +++ b/contracts/usdfree/README.md @@ -0,0 +1,103 @@ +# USDFree + +A cross-chain order system that unifies CCTP, OFT, and Across bridge flows into a single architecture and allows for expanding to new underlying bridge types easily, as well as performing same-chain TXs with no cross-chain execution. + +## Goals + +- Bridge tokens cross-chain regardless of underlying mechanism +- Use auction systems for token swaps when needed +- Support same-chain actions with no bridging, as well as action chaining +- Single upgradeable entry point (no re-approvals needed) +- Support gasless flows and sponsorship with deferred sponsorship rebates for sponsoring submitters +- Execute arbitrary user actions after token delivery + +## Architecture + +``` +Source Chain: +User AND/OR Submitter → OrderGateway → Executor -custom-call-> OFT/CCTP/SpokePool/CustomAdapterIfNeeded + ↓ + [Bridge Message] + ↓ + Destination Chain: +BridgeCapitalProvider/CustomHandlerIfNeeded → OrderGateway → Executor -custom-call-> OFT/CCTP/SpokePool/CustomAdapterIfNeeded +``` + +### Components + +**OrderGateway** - Entry point for all order submissions + +- `submit()` calculates `orderId`, resolves the concrete `Step` (direct step or Merkle leaf + proof), pulls funding from `orderOwner` and/or submitter, and forwards all received assets into `Executor` without intentionally retaining balances. +- Core v1 funding adapters: + - allowance-based `approve + transferFrom` + - Permit2 witness-based transfers + - TransferWithAuthorization / EIP-3009-style funding, where the witness is effectively the authorization nonce +- Bridge-delivered funds are not a special funding type in the core. A bridge receiver / wrapper that already holds funds should fund the next step using a normal adapter, most likely allowance-based `approve + transferFrom`. +- `orderOwner` is a namespace + funding authority, not always `msg.sender`. However, if a receiver / wrapper funds a later step itself, that caller should usually also be the `orderOwner` for that step. +- For core purposes, `submitter` is always `msg.sender` to `submit()`. `OrderGateway` passes that address into `Executor`; `Executor` trusts `OrderGateway` one-way. The core does not accept protocol-specific "real submitter" addresses forwarded in from outside systems. +- Can store prefunded orders waiting for continuation when a bridge auto-delivers funds before fresh submitter input is available. +- v1 prefunded continuations use a monotonic local counter. A stored continuation can be resumed by any caller that satisfies the later `Executor` requirements, and `refundAddress` may withdraw associated funds after `refundReverseDeadline`. + +**Executor** - Executes a single Step + +Big features: + +- Command-based execution (Dispatcher pattern by Uniswap). The user defines the command sequence and static data. A command opts into reading submitter-provided dynamic data from `executorMessage` only when that command needs it. +- Can perform external calls to untrusted contracts from its own context. Calls can substitute current balance or partial balance into user-provided calldata, which is especially useful for DEX swaps and final bridge / user actions. +- The user can intentionally give a submitter a free-form custom-call window. In that case the submitter may move all assets out of the contract; the user's later requirements are the safety boundary. + +Some common functions/commands: + +- `balanceRequirement` +- `deadlineRequirement` +- `submitterRequirement` +- `offchainAuction`: the user precommits auction settings, including the authority. The command requires an authority signature over the result for that particular command. The result can augment user requirements, especially by adding `balanceRequirement` or `submitterRequirement`. +- `dutchOnChainAuction` +- `customCalls`: submitter-provided calls to DEXes or opaque submitter contracts, with balance or partial-balance substitution +- final user / bridge action: simple transfers, or calls into `IOFT`, Circle token messenger (`depositForBurnWithHook`), `V3SpokePoolInterface.depositV3()`, `SponsoredCCTPSrcPeriphery`, `SponsoredOFTSrcPeriphery`, etc. +- More advanced calldata substitution is plausible later (for example injecting data previously produced by a custom call), but balance-based substitution is the important first-class primitive. +- Initial v1 implementation should prioritize the non-auction primitives above. Auction / oracle-priced requirement commands can follow after the initial implementation. + +## Design Decisions + +**Order ID**: used as witness in `orderOwner` gasless funding. It identifies the order envelope, not necessarily a single executable leaf. If `nonce == 0`, the core does not enforce uniqueness for that `orderId`, which saves an `SLOAD + SSTORE`. If `nonce != 0`, uniqueness can be enforced and used as a clean primitive for offchain systems that want stable `(orderId => sponsorship / rebate / bookkeeping)` mappings. + +**Execution ID**: used as witness for submitter gasless funding. Since the core submitter is always `msg.sender` to `OrderGateway`, `executionId` is scoped to the direct caller of `submit()`, not to some bridge-specific actor behind that caller. + +**Step ID**: needed only when an order contains a Merkle root. In that case multiple leaves share the same `orderId`, and `stepId` disambiguates which disclosed path was actually executed. In v1, `stepId` includes the disclosed leaf salt. For non-Merkle orders, emitting `orderId` is enough. + +**TypedData**: `TypedData` is intentionally just `(typ, data)`. Each consumer defines its own local type registry and decoding rules. In particular, `order.stepOrMerkleRoot` is interpreted by `OrderGateway` as either a directly-encoded `Step` or a `bytes32` Merkle root. + +**Merkle execution**: absolutely in scope. The submitter provides the disclosed `Step` plus Merkle proof. The core does not enforce leaf-level single execution. Single execution comes mostly from gasless funding nonces and, when desired, from order-level uniqueness via `order.nonce`. + +**Merkle leaf hashing**: when `order.stepOrMerkleRoot` is a Merkle root, the v1 step / leaf hash is `keccak256(abi.encode(step.salt, step.executor, keccak256(step.message)))`. `stepId` should use the same salted step hash. + +**Refund settings**: this is for the user, not for sponsorship accounting. The minimal shape is versioned `(refundAddress, refundReverseDeadline)`-style liveness / escape-hatch configuration in case execution stalls at some step. Sponsorship refunds are handled offchain. + +**Prefunded continuations**: continuation storage should be keyed independently from `orderId`, using a monotonic local counter rather than a user-chosen or hash-derived predictable id. This avoids collisions / griefing around continuation identifiers and keeps the prefund mechanism as a local storage concern. + +**Obfuscation**: If an order contains a merkle tree root, each leaf gets a salt, which can be selectively disclosed by the API. + +**Upgradeability / trust**: `OrderGateway` is UUPS upgradeable. `step.executor` is user-chosen; users decide what executor code they trust. `OrderGateway` should treat executors as untrusted external contracts and must not rely on them beyond the narrow call boundary it controls. + +## Underlying bridge support + +Current focus is CCTP/SpokePool/OFT. Two interaction modes: + +### Hand-off mode + +Executor performs a single source-chain leg (e.g. approve + call bridge) and hands off to the existing bridge system. The bridge delivers tokens to whatever destination recipient / handler is already in place. No custom destination periphery is needed from USDFree's side. + +- **OFT**: Executor calls into `SponsoredOFTSrcPeriphery`, which handles quote validation, token pull, and `IOFT.send()` with compose. LayerZero endpoint delivers funds + compose to an existing `DstOFTHandler` on destination. +- **CCTP**: Executor calls into `SponsoredCCTPSrcPeriphery`, which handles quote validation, token pull, and `ITokenMessengerV2.depositForBurnWithHook()`. Circle attestation delivers funds to an existing `SponsoredCCTPDstPeriphery` on destination. +- **SpokePool**: Executor calls `V3SpokePoolInterface.depositV3()` (`V3SpokePoolInterface.sol`). Across relayer fills on destination; if a message is included, fill calls `AcrossMessageHandler.handleV3AcrossMessage()` (`SpokePoolMessageHandler.sol`) on the recipient. + +In all cases the source Executor just needs the right command to build the bridge call (with balance substitution via `makeCallWithBalance` if needed). + +### Integration mode + +Tokens are delivered into `OrderGateway` on the destination chain, or into a thin receiver / wrapper that funds and calls `OrderGateway` as the next `orderOwner`. This enables the full USDFree feature set on dst: custom action execution, user refund settings, and submitter-provided auction data. The goal is **atomic dst execution** wherever the bridge and wrapper model allow it, while keeping the core bridge-agnostic. + +- **OFT**: Atomic destination execution is possible only for fully precommitted destination logic encoded into compose data. Fresh destination-side submitter input is not available in that same tx. Therefore, if the destination step needs fresh submitter-provided data or funding, OFT degrades to a prefunded continuation: a receiver contract stores / owns the bridged funds, and a later `submit()` continues the flow as a new step. +- **CCTP**: Atomic execution is possible. A dst contract (e.g. `CCTPOrderReceiver`) accepts the Circle attestation, receives funds, and then calls `OrderGateway.submit()` in the same tx. Since the core does not trust protocol-specific claimed submitters, the receiver itself is the Gateway submitter for that step. If the receiver wants to enforce properties about the external attestation caller or other bridge-specific actor, it must do so before forwarding into the core. +- **SpokePool**: Atomic execution is possible via a `SpokePoolWrapper` used as the Across recipient. SpokePool calls the wrapper via `AcrossMessageHandler.handleV3AcrossMessage()`, which gives the wrapper the bridge-authenticated relayer address. The wrapper can check bridge-specific conditions there (for example relayer-based requirements or exclusivity assumptions), then fund and call `OrderGateway.submit()` itself. Core submitter identity still remains `msg.sender` to `OrderGateway`, i.e. the wrapper, not a relayer address forwarded through trust.