Skip to content

DEVREL 205: Chainlink Local V3 - #54

Open
andrejrakic wants to merge 21 commits into
developfrom
devrel-205/cl-local-v3
Open

andrejrakic wants to merge 21 commits into
developfrom
devrel-205/cl-local-v3

Conversation

@andrejrakic

@andrejrakic andrejrakic commented Mar 10, 2026 •

Copy link
Copy Markdown
Collaborator

Chainlink Local V3 (0.3.0-beta)

Breaking release. CCIPLocalSimulator and CCIPLocalSimulatorFork move to @chainlink/contracts-ccip 2.0.0 and support the CCV-based lanes that live testnet and mainnet lanes run today, alongside 1.6 and pre-1.6 lanes. The full list of changes and the migration guide are in CHANGELOG.md (Unreleased).

Support matrix

Environment 0.3.x 0.2.x
Foundry (local + fork) Supported Supported
Hardhat 3 (Solidity tests, local + fork) Supported -
Hardhat 3 (JS/TS helpers) Supported (ESM, @nomicfoundation/hardhat-ethers) -
Hardhat 2 Not supported (stay on 0.2.x) Supported
Remix (local mode) Supported Supported

Breaking changes (summary)

  • Dependencies: @chainlink/contracts-ccip 1.6.2 -> 2.0.0. Finality moves from uint16 block confirmations to a bytes4 FinalityCodec config (extra args V3, receivers, token pools). lib/chainlink-ccip is pinned to tag contracts-ccip-v2.0.0.
  • Fork routing is strict by default. switchChainAndRouteMessage reverts with CCIPLocalSimulatorFork__MessageNotRouted / CCIPLocalSimulatorFork__MessageExecutionFailed (with the decoded reason) instead of logging and continuing. setStrictRouting(false) and getMessageStatus(messageId) are the opt-out.
  • Local mode applies the production OnRamp/OffRamp rules. CCIPLocalRouter replaces the upstream MockCCIPRouter (same ABI). Invalid sends now revert with the production error: finality, one token per message, zero amounts, tokenReceiver, receiver CCV config and gas limits above uint32. Local messageIds are unique per send.
  • Fast Transfers follow production rules: messages with data need a receiver that opts in through getCCVsAndFinalityConfig.
  • Fork tests need evm_version = "cancun" and Foundry >= 1.5.1. The migration guide shows a fork-only profile, so the deployed bytecode is not affected.
  • src/vendor/** removed: sources import @openzeppelin/contracts@4.8.3 / @5.3.0 and @chainlink/contracts(-ccip) directly.
  • Register: the data is regenerated from the CCIP directory API (134 chains, was 148; routers unchanged). wrappedNativeAddress was GHO on e.g. Ethereum, Sepolia, Arbitrum and Base, and is now the wrapped native token. Removed chains are listed in the CHANGELOG.
  • Hardhat JS helpers target Hardhat 3 and take the NetworkConnection (scripts/CCIPLocalSimulatorFork.js, scripts/data-streams/).

The public Solidity API is unchanged: CCIPLocalSimulator.configuration(), the CCIPLocalSimulatorFork function signatures and Register.NetworkDetails.

Highlights

  • CCV-based lane routing on forks: the destination OffRamp selects the CCVs and executes the message through its permissionless execute, so the production checks run. Owner-configurable verifier resolvers are pointed at a synthetic fork verifier, so live lanes work without mocking.
  • CCV selection matches the OffRamp quorum (required plus threshold optional CCVs).
  • Messages execute only on their destination fork. With one fork per call, other destinations stay QUEUED until a later call routes them.
  • OffRamp lookup by typeAndVersion, robust to routers that list OffRamps of several versions for a lane.
  • CCIPLocalSimulatorFork deploys in ~14.4M gas (was ~36M), so it fits on 30M-gas forks.
  • Hardhat 3 JS helper for every lane era, with decoded errors, TypeScript declarations and offline unit tests.
  • The npm package works for Hardhat 3 and npm-based Foundry projects (remappings.txt for node_modules layouts).
  • New CI workflow (.github/workflows/test.yml): build, fmt, non-fork tests, Hardhat Solidity + JS unit tests, and a tarball consumer smoke test. Fork suites run on manual dispatch once the RPC secrets are configured.

Testing

  • CI: Foundry (build, fmt --check, non-fork tests), Hardhat (Solidity + JS unit tests) and consumer smoke all pass.
  • Fork suites, run locally with RPC URLs: forge test -j 1 --match-path 'test/e2e/**' passes 33/33 and npm run hardhat-test-js passes 8/8. They cover Sepolia -> Arbitrum Sepolia, Sepolia <-> Fuji, Ethereum -> Arbitrum One mainnet, 1.6 lanes at pinned blocks, Fast Transfers and CCT token pools.

Known limitations

  • There are no live 1.6 lanes left on the tested testnets: 1.6 is covered by fork tests pinned to historical blocks (archive RPCs) and by unit tests. Pre-1.6 is covered by unit tests only.
  • CCVs are simulated on forks, not verified: there are no live attestations.
  • Some values are hand-maintained and can go stale: the dedicated router map (Ethereum Sepolia, Avalanche Fuji) and the known OffRamp versions. Unresolvable lanes fail loudly in strict mode.
  • wrappedNativeAddress is address(0) on Stable and Botanix, where the API has no unambiguous wrapped native token.

The full list is in the CHANGELOG.

* refactor: Migrate to HH3, update dependencies, delete vendor folder

* feat: Add ExtraArgsV3 test
@andrejrakic andrejrakic self-assigned this Mar 10, 2026
Conflict resolution:
- src/ccip/CCIPLocalSimulatorFork.sol: kept the V3 era routing + adapters and
  ported develop's routing fixes into them: exact on-ramp -> off-ramp matching
  (_findOffRampForOnRamp via getSourceChainConfig/getStaticConfig), 20/32-byte
  EVM address decoding, and success-only processed-message marking. V2 OffRamp
  discovery now derives the source on-ramp from the decoded message.
- CCIPForkAdapterV1dot6: sourcePoolAddress is abi-encoded, matching the v1.6
  OnRamp wire format (develop's fix); the adapter unit test now decodes it.
- Kept develop's new tests: CCIPLocalSimulatorForkRouting.t.sol (7 cases) and
  EthSepoliaToArbSepoliaMultiOffRampFork.t.sol (import adapted to the V3
  layout). Unit: 42/42, smoke: 28/28.
- docs/config/publish workflow: develop's versions; package.json/package-lock:
  V3 packaging kept (type: module, helper_doc scripts); develop's 0.2.9 version
  bump is superseded by 0.3.0-beta.
- scripts/data-streams/*.js: develop's (V3 changes were JSDoc-only).
- lib/chainlink-ccip: kept the V3 pin 347f5118b (newer than develop's 0e3e0fc5c).

Known follow-up (not in this merge): live lanes now encode MessageV1 finality
as bytes4 (chainlink-ccip #1898, 2026-04-08); the pinned dependency still
decodes uint16, so live E2E V2 routing fails with
InvalidDataLength(MESSAGE_FINAL_OFFSET). Bumping chainlink-ccip requires the
finality/blockConfirmations API migration across the V3 branch and should be
coordinated with the refresh owner.
andrejrakic and others added 3 commits September 23, 2026 13:04
chore: sync develop into cl-local-v3 (refresh #54)
Adds V2VerificationMode.OFFRAMP_DERIVED: the required CCV list comes from the
destination OffRamp (getCCVsForMessage) and the raw encoded message is executed
through the permissionless execute entrypoint, so fork routing does not depend
on the local MessageV1 codec. Keeps V2 fork tests working when the on-chain wire
format moves ahead of the pinned chainlink-ccip (today: finality uint16 -> bytes4,
chainlink-ccip #1898).

- CCIPLocalSimulatorFork: new mode + _routeV2MessageOffRampDerived /
  _executeOffRampDerivedV2; destination chain resolved from the event topics with
  no MessageV1 decode; falls back across candidate OffRamps and marks processed
  only on success.
- Fork-only test helpers ported onto V3: getOffRampForLane, setLaneDefaultCCVs,
  IOffRampSourceConfigV2Fork, plus the CCVNoOpVerifier test double.
- IOffRampExecuteV2 gains the bytes-based execute entrypoint.
- New e2e: test/e2e/ccip/CCIPv2Fork.t.sol (Sepolia -> Arb Sepolia message and
  token transfer over the live 2.0 lane with a no-op default CCV).
- Unit 42/42; live e2e 2/2 with public RPCs.
Brings in fdb0cc7 (Correct v1.6 sender encoding, #65) and dd13ac9
(v0.2.10-beta prep).

Conflict resolution:
- src/ccip/CCIPLocalSimulatorFork.sol: keep V3's adapter layout; drop
  develop's inline _executePreV1dot6/_executePostV1dot6 (V3 moved that
  logic into src/ccip/adapters/).
- Port #65 into CCIPForkAdapterV1dot6._toAny2EVMMessage: sender is now
  abi.encode(address) (32-byte word) instead of abi.encodePacked.
- test/unit/ccip/CCIPLocalSimulatorForkRouting.t.sol: port #65's
  regression test onto the adapter mirror types (pinned chainlink-ccip
  no longer ships the 1.6 Internal ramp structs). Verified it fails
  with 20 != 32 before the port.
- package.json / package-lock.json: keep V3's 0.3.0-beta version.
- CHANGELOG.md: keep both the Unreleased and 0.2.10-beta sections.
- Pin chainlink-ccip to contracts-ccip-v2.0.0 and @chainlink/contracts-ccip
  to 2.0.0; migrate finality to bytes4 FinalityCodec (extraArgs V3,
  receivers, pools, test helpers).
- CCIPLocalSimulatorFork: make OFFRAMP_DERIVED (magiodev-cll#3)
  the default V2 mode; mark processed only on SUCCESS, execute only the lane
  OffRamp, honour the NO_EXEC queue, synthetic results for resolver CCVs.
- Fix the OffRamp lookup reverting on mixed-era router lists: dispatch on
  typeAndVersion, isolate every decode, skip unknown shapes.
- Route dedicated CCIP 2.0 routers (getCCIPV2RouterAddress /
  setCCIPV2RouterAddress; Sepolia and Fuji seeded).
- Local mode: CCIPLocalRouter enforces CCIP 2.0 receiver finality for
  Fast Transfers.
- Hardhat 3 JS helpers: CCIP (all eras) and Data Streams rebuilt as ESM
  taking a network connection; fix npm files casing.
- Remove dead code: abi/*.json (no longer shipped), Hardhat 2 examples and
  .spec.ts tests, duplicate IRouterFork.OffRamp and _decodeEVMAddress.
- evm_version = cancun; version 0.3.0-beta.0.
- Tests: unit tests for lookup, V2 routing and local finality; fork suites
  for Sepolia<->Fuji 2.0, mainnet 2.0, pinned-block 1.6; Hardhat 3 JS tests.
- Docs: CHANGELOG (breaking changes, support matrix, migration, known
  limitations), README, AGENTS.md, api_reference.

BREAKING CHANGE: see CHANGELOG.md "Breaking changes", "Removed" and
"Migration guide".
- Fork routing: CCV quorum selection, 1.6 destination filter, strict routing
  with getMessageStatus, per-destination routing (QUEUED until its fork is
  routed), stamped OffRamp preference, resolver re-check after fork resets,
  zero token gas overrides, destination fork selected after routing
- Register data generated into code; simulator deploys in ~14.4M gas
- Register regenerated from the CCIP directory API (134 chains); wrapped native
  token selection fixed (was GHO on Ethereum, Sepolia, Arbitrum, Base)
- Local router: CCIP 2.0 OnRamp/OffRamp rules, unique messageIds,
  MessageGasLimitTooHigh above uint32
- Hardhat 3 JS helper: 1.6 destination check, decoded execution errors,
  TypeScript declarations, offline unit tests
- npm packaging for Hardhat 3 / Foundry consumers; chainlink-ccip pinned to
  contracts-ccip-v2.0.0
- CI workflow with tarball consumer smoke test

BREAKING CHANGE: see CHANGELOG "Breaking changes" and migration guide.
CCIP Contracts 2.0.0 support in Chainlink Local (V3)
@andrejrakic
andrejrakic marked this pull request as ready for review September 25, 2026 19:35
@andrejrakic
andrejrakic requested a review from a team as a code owner September 25, 2026 19:35

This branch has not been deployed

No deployments
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.

2 participants