CCIP Contracts 2.0.0 support in Chainlink Local (V3) - #68
Open
andrejrakic wants to merge 6 commits into
Open
andrejrakic wants to merge 6 commits into
andrejrakic wants to merge 6 commits into
Conversation
* fix: Use abi.encode for v1.6 message sender in CCIPLocalSimulatorFork
fix: Use abi.encode for v1.6 message sender in CCIPLocalSimulatorFork
`_executePostV1dot6` built `Internal.Any2EVMRampMessage.sender` with
`abi.encodePacked(address)` (20 bytes). Production v1.6 lanes with an EVM
source chain deliver it as `abi.encode(address)` (32-byte word), which is
what `Client.Any2EVMMessage.sender` documents ("abi.decode(sender) if
coming from an EVM chain") and what the OffRamp passes straight through to
the receiver.
The 20-byte form broke receivers two ways during fork testing: an
`abi.decode(message.sender, (address))` reverted, and a raw-bytes
comparison against `abi.encode(trustedRemote)` fell through to the
untrusted-sender branch.
This completes the 20-vs-32-byte encoding corrections made in 0.2.9 for
`receiver` and `destTokenAddress`, and restores parity with local mode,
where `MockCCIPRouter` already uses `abi.encode(msg.sender)`.
Adds a unit regression test that drives `_executePostV1dot6` against a
recording OffRamp mock and asserts the delivered `sender` is 32 bytes and
decodes back to the original address. Verified non-vacuous: it fails with
`20 != 32` without the fix.
Closes #62
* test: Assert v1.6 sender matches an encoded trusted remote
Receivers consume `Client.Any2EVMMessage.sender` in two ways, and they fail
differently when it arrives as 20 packed bytes instead of a 32-byte ABI word:
- `abi.decode(sender, (address))` reverts, so the message is never delivered.
- `keccak256(sender) == keccak256(abi.encode(trustedRemote))` returns false,
so the receiver runs without error and silently takes its untrusted-sender
branch
The existing assertions covered only the first. This pins the raw-bytes
comparison as well, so both documented consumption patterns are checked
against the bytes `_executePostV1dot6` actually produces.
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".
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes Chainlink Local support CCIP 2.0 (CCV-based) lanes, the protocol version every tested testnet and mainnet lane now runs, as the basis for the breaking V3 release
0.3.0-beta.0:CCIPLocalSimulatorForkroutes CCIP 2.0 lanes and keeps pre-1.6 and 1.6 working.V3 targets Hardhat 3 for JavaScript tooling. Hardhat 2 JavaScript users stay on 0.2.x; see the support matrix in the CHANGELOG.
Base:
devrel-205/cl-local-v3(after #66). This branch contains:0d79fee, authorship preserved), adopted as the default CCIP 2.0 routing path.origin/develop(dee9195): brings in Correct v1.6 sender encoding in CCIPLocalSimulatorFork #65 (v1.6 sender encoding) and the v0.2.10-beta prep. Correct v1.6 sender encoding in CCIPLocalSimulatorFork #65 is ported intoCCIPForkAdapterV1dot6because V3 moved that logic into the adapter._findOffRampForOnRampreverted whenever a router listed a CCIP 2.0 OffRamp. The lookup is nowtypeAndVersion-dispatched and can't revert (details under Fixes).lib/chainlink-ccip→contracts-ccip-v2.0.0(c2c125c), with@chainlink/contracts-ccip2.0.0 inpackage.json. Includes thebytes4finality / FinalityCodec migration.evm_version = cancun, plus docs and the version bump to0.3.0-beta.0.CCIPLocalRouterreplaces upstreamMockCCIPRouterinCCIPLocalSimulator, with the same ABI.scripts/CCIPLocalSimulatorFork.jsandscripts/data-streams/*.jswere CommonJS withrequire("hardhat")and could not load under V3's ESM + Hardhat 3. They are now ESM and take aNetworkConnection. The CCIP helper gains 1.6 and 2.0 routing.filescasing fixed for the CCIP helper.abi/*.json: no longer used, and no longer shipped in the npm package, which is breaking for direct importers.scripts/examples/*.ts.test/**/*.spec.ts.ClientReportsVerifier.spec.tsis replaced byDataStreamsHelpers.test.js.Breaking changes
@chainlink/contracts-ccip1.6.2 → 2.0.0. Finality moves fromuint16block confirmations to abytes4FinalityCodecconfig, which changes the wire format:GenericExtraArgsV3:blockConfirmations→requestedFinalityConfig, and the base size goes from 17 to 19 bytes.MessageV1.finalityis nowbytes4.getCCVsAndMinBlockDepth→getCCVsAndFinalityConfig, returningbytes4.setMinBlockConfirmations→setAllowedFinalityConfig(bytes4).V2VerificationModeisOFFRAMP_DERIVED(wasHYBRID).STRICT,HYBRIDandSYNTHETIC_ONLYare still available.getCCVsAndFinalityConfig. Otherwise the OffRamp recordsFAILUREand the message is not delivered.evm_version = cancun. Deployed 2.0 contracts use Cancun opcodes, andparisfails withNotActivated.revm journaled_state.rs:402when an OffRamp 2.0 execution recordsFAILURE.ccipSendreverts withInvalidRequestedFinalitywhen Fast Transfers data targets a receiver that doesn't opt in.RequestedFinalityCanOnlyHaveOneModefor a malformed finality config.@nomicfoundation/hardhat-ethers:getCCIPMessages(connection, receipt)replacesgetEvm2EvmMessage(receipt).routeMessage(connection, routerAddresses, sent, options)replacesrouteMessage(routerAddress, message).requestLinkFromTheFaucet(connection, …)takes the connection first.new MockReportGenerator(connection, initialPrice)takes the connection first.abi/*.jsonis no longer shipped in the npm package. Import ABIs from the@chainlink/contracts-ccip/@chainlink/contractsartifacts instead.IRouterFork.OffRampis removed.getOffRamps()returnsCCIPForkAdapterTypes.RouterOffRamp[], which is ABI-identical.EncodeExtraArgsOffchain.encodeV3andencodeV3Basictake abytes4finality config.encodeV3BasicBlockDepth(uint32, uint16).BasicMessageReceiverWithCCVs.setMinBlockDepth→setAllowedFinalityConfig(uint64, bytes4).Migration guide
See
CHANGELOG.md→ Unreleased → Migration guide (0.2.x -> 0.3.0). In short:lib/chainlink-cciptocontracts-ccip-v2.0.0.evm_version = "cancun", use Foundry ≥ 1.5.1, and use Node 22 for Hardhat 3._getBasicEncodedExtraArgsV3(gas, FinalityCodec.WAIT_FOR_FINALITY_FLAG)for finality._getBasicEncodedExtraArgsV3BlockDepth(gas, n)for a Fast Transfers._getBasicEncodedExtraArgsV3FastConfirmationRule(gas)to wait for thesafetag.getCCVsAndMinBlockDepth→getCCVsAndFinalityConfigreturningbytes4. A receiver must opt in to receive Fast Transfers data messages.setMinBlockConfirmations(n)→setAllowedFinalityConfig(FinalityCodec._encodeBlockDepth(n)).setV2VerificationMode(HYBRID).getCCIPV2RouterAddress(block.chainid), and deploy receivers with the destination chain's CCIP 2.0 router.What changed and why
Fixes
getSourceChainConfig(uint64)selector but return different structs. A mismatched return payload fails to decode in the calling frame, andtry/catchdoesn't catch that, so any router listing a 2.0 OffRamp reverted the whole routing call.getStaticConfig()with the pre-1.6 selector but a 5-word struct instead of 7. So_findOffRampForOnRampalso reverted on a 1.6-only lane with two OffRamps, whenever the non-matching one came first.typeAndVersion.OffRamp 2.*is matched againstonRamps[].OffRamp 1.6*is matched againstonRamp.EVM2EVMOffRamp 1.*is matched viagetStaticConfig.typeAndVersionis unreadable, every shape is probed.try. @magiodev-cll's_findOffRampForLaneV2is folded into this single lookup.OFFRAMP_DERIVEDmarks messages processed only onSUCCESS.executedoesn't revert when verification or the receiver fails on a first attempt. It recordsFAILURE, and Zp/ga review2 #3 treated that as delivered.getExecutionState(keccak256(encodedMessage)).RESPECT_NO_EXEC/MANUAL_ONLYqueueing now works in this mode too. It needs only the event receipts, not the codec.setLaneDefaultCCVs.setLaneDefaultCCVs+CCVNoOpVerifierfrom Zp/ga review2 #3 still work.getCCVsForMessagereturn shapeowner()response on a CCV with no code (a high-leveltrydoesn't catch the no-code check)getOffRampsAny2EVMRampMessage.sender = abi.encode(address).Client.Any2EVMMessage.sender("abi.decode(sender) if coming from an EVM chain") and local mode'sabi.encode(msg.sender).20 != 32before the port. The new 1.6 fork test also fails with the pre-Correct v1.6 sender encoding in CCIPLocalSimulatorFork #65 encoding, because the receiver'sabi.decodereverts and the message isn't delivered.IRouterFork.OffRamp→CCIPForkAdapterTypes.RouterOffRamp._decodeEVMAddressandInvalidEVMAddressEncodingare gone. The V2 path takes the source OnRamp fromentry.emitter, which the OnRamp itself stamps asonRampAddress.Dependency bump
lib/chainlink-ccip:347f511→c2c125c(contracts-ccip-v2.0.0).@chainlink/contracts-ccip:2.0.0, pinned exactly like the other Chainlink deps.@chainlink/contractsstays at 1.5.0, which is what contracts-ccip 2.0.0 depends on.CCIPMessageSentevent, theReceiptstruct, OffRampSourceChainConfig, and theexecute/getCCVsForMessagesignatures. V2 event detection is unaffected.CCVNoOpVerifier's mirror types already usedbytes4finality. A new unit test pins its selectors to the upstreamICrossChainVerifierV1/ICrossChainVerifierResolver.Test changes
getAllowedFinalityConfig()from the live pool instead of hard-coding depth 1.BasicMessageReceiverWithCCVs), as OffRamp 2.0 requires. There is a new negative test showing that a receiver which doesn't opt in is not delivered.CCIPForkAdaptersUnitpins its harness toHYBRID, since those tests cover the opt-in local-codec path.Credit
Thanks to @magiodev-cll for the develop → V3 refresh (#66), and for the OffRamp-derived CCV execution (magiodev-cll#3). The latter is cherry-picked here with authorship preserved and is now the default CCIP 2.0 routing path.