Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Smart contracts (Foundry)

Status (as of Stage 3 close + follow-up security pass): Phase 1 on-chain protocol is complete and internally audited. Escrow, EIP-712 hashing, cross-chain LayerZero V2 messaging, solver auction, and timeout-based recovery are all implemented and tested. The follow-up pass moved match validation (token + chain + amount) onto the destination chain so the matcher cannot bypass user minDestAmount / destToken (R-16), added the missing CONFIRM-leg source-EID guard (R-17), and segregated user ETH escrow from operator pre-fund so LayerZero fees can never debit user funds (R-18). Backend (Stage 4), frontend wiring (Stage 5), and testnet deployment (Stage 8) follow.

See also: Repository README · Architecture · MVP specification · Stage 1 audit · Stage 2 audit · Stage 3 audit · Stage 3 final review


What's in src/

File Status Notes
ChainPeerRegistry.sol Per-chain chainId → LayerZero EID table + (source, dest) route allowlist; owner-gated
IntentSettler.sol OApp + EIP-712 + ReentrancyGuard. submitIntent (escrow + totalEthEscrow ledger), cancelIntent, executeMatching(localHash, remoteHash) (no matcher-trusted price params), _lzReceive dispatching EXECUTE_MATCH (token + chain + both-sides amount checked against trusted data) / CONFIRM (R-17 source-EID guard), refundIfLzTimeout (6 hr), openAuction, setSolverAuction, withdrawOperatorFunds (owner-only, escrow-floor protected). Packed IntentMeta storage.
SolverAuction.sol Settler-gated setAuctionWindow, ECDSA-signed proposals over a chain-and-contract-bound digest, deterministic ranking, idempotent winner announcement, double-submit guard, DoS cap
interfaces/IIntentSettler.sol Intent struct (ERC-7683 aligned + refundTo), IntentState enum (with Locked reserved for Phase 2B async settlement), IntentMeta packed struct, full event set
interfaces/IChainPeerRegistry.sol Read interface for the registry
interfaces/ISolverAuction.sol Minimal surface for IntentSettler ↔ auction integration
libraries/IntentHash.sol EIP-712 type hash computation
libraries/SafeTransfer.sol Native ETH transfer helper; ERC-20 ops use OZ SafeERC20 directly
libraries/SignatureValidator.sol ECDSA tryRecover with safe error semantics

What's in test/

File Tests Notes
IntentSettler.t.sol 33 submit (incl. totalEthEscrow tracking), cancel, openAuction, executeMatching (lifecycle gates only), refundIfLzTimeout
IntentSettler.lz.t.sol 18 full cross-chain round-trip, full solver-auction round-trip, dropped-delivery → timeout refund, peer rejection, version/type rejection, R-01 source-EID rejection, explicit asymmetric-loss documentation, R-16 dest-side validation (token mismatch + both amount-below-min + wrong chain id), R-17 confirm-leg source-EID rejection, R-18 escrow-floor: return-leg fee never debits user escrow + withdrawOperatorFunds owner gate + escrow-floor enforcement, operatorBalance view
IntentSettler.solver.t.sol 5 IntentSettlerSolverAuction wiring, gating, executeMatching from Auctioning state
IntentSettler.invariant.t.sol 9 3 property-fuzz × 256 runs + 6 stateful invariants × 256 runs × ~500 calls ≈ 768k random call sequences (includes new totalEthEscrowFloor invariant — balance never dips below escrow ledger)
IntentHash.t.sol 5 EIP-712 parity (on-chain ↔ off-chain), hashIntent view matches submitIntent storage key
SolverAuction.t.sol 21 window setup, signed proposals, ranking, finalisation, gating, getProposals aggregate view
ChainPeerRegistry.t.sol 6 owner / EID / route configuration
Integration.t.sol 3 stack-deploys, submit-then-cancel, submit-match-auction lifecycle
mocks/ MockERC20, MockUSDT (non-bool returns), MockLzEndpoint (test-only MessageQueued event + deliverInbound() helper let backend E2E tests relay messages between two endpoint instances on separate Anvil chains)

Total: 100 unit/fuzz/integration tests + 6 stateful invariants × 256 runs × ~500 calls. All passing.

Tooling status

  • forge build — clean (45 contracts, no errors)
  • forge test — 100/100 passing
  • forge fmt --check — clean
  • Slither (--filter-paths "lib/|test/" --exclude-low --exclude-informational) — 0 medium+ findings across 41 contracts (after R-03 false-positive suppression and R-01/R-02/R-16/R-17/R-18 fixes from the Stage 3 final review + follow-up security pass)

Configuration

  • Solidity: 0.8.26 (foundry.toml)
  • Optimizer: enabled, 200 runs
  • Format: line length 120, tab width 4
  • remappings.txt:
    • forge-std/=lib/forge-std/src/
    • @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
    • @layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/
    • @layerzerolabs/lz-evm-protocol-v2/=lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/
    • @layerzerolabs/lz-evm-messagelib-v2/=lib/LayerZero-v2/packages/layerzero-v2/evm/messagelib/

Quickstart

cd contracts

# Install dependencies (vendored libs are gitignored, fetched on demand)
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0 --no-git --shallow
forge install LayerZero-Labs/devtools --no-git --shallow
forge install LayerZero-Labs/LayerZero-v2 --no-git --shallow

# Build + test
forge build
forge test

To refresh forge-std from upstream instead of the vendored copy:

forge install foundry-rs/forge-std --no-git --shallow

Design intent (high level)

  • Multi-chain by config, not by code. Same bytecode on every chain. EIDs and route allowlists live in ChainPeerRegistry storage. Adding a new chain is forge script Deploy.s.sol + setPeer + registry config — no contract change.
  • P2P-first matching, bonded-solver fallback. executeMatching accepts both Pending and Auctioning state — auction is a discovery layer, not a settlement lock. Phase 2A introduces solver bonding (production-proven via Across at $15B+ volume).
  • Versioned cross-chain payloads. Every LayerZero message starts with (uint8 messageVersion, uint8 messageType). Old peers reject unknown versions cleanly; new versions can ship without breaking V1 peers.
  • Atomic settlement Phase 1. Source goes Pending → Matched → Settled. Destination goes Pending → Settled (no observable Locked window). The Locked enum slot is reserved for Phase 2B async-settlement designs (HTLC, optimistic).
  • Recovery before correctness loss. If LayerZero fails to deliver, source user can self-refund via refundIfLzTimeout after 6 hours. The asymmetric-loss class (rare) is fully solved in Phase 2A by the bonded-solver model — see Stage 3 final review § R-06.
  • No proxy, no admin rescue, no Pausable. Decentralisation > recovery. The only privileged role is Ownable.owner, which controls setPeer, setSolverAuction, and ChainPeerRegistry config — must be transferred to multisig before mainnet.

Audits & analysis

  • Stage 1 audit (escrow + EIP-712 + state machine): docs/STAGE_1_AUDIT.md
  • Stage 2 audit (LayerZero OApp): docs/STAGE_2_AUDIT.md
  • Stage 3 audit (SolverAuction integration): docs/STAGE_3_AUDIT.md
  • Stage 3 final review (R-01 to R-15, doc cross-reference, tooling roadmap): docs/STAGE_3_FINAL_REVIEW.md

External audit + Echidna + Mythril + Halmos are scheduled for Stage 7 (security hardening) before testnet deployment.

PRs that change state machines, token flows, or cross-chain message shapes should cite the relevant audit and update tests accordingly.