Conversation
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
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.
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
CCIPLocalSimulatorFork._executePostV1dot6builtInternal.Any2EVMRampMessage.senderwithabi.encodePacked(message.sender), producing 20 bytes. Production v1.6 lanes with an EVM source chain deliver it as a 32-byte ABI word.Both documented ways of consuming
Client.Any2EVMMessage.senderbroke, and they broke differently:abi.decode(message.sender, (address))reverts, so_ccipReceivereverts and the message is never delivered.keccak256(message.sender) == keccak256(abi.encode(trustedRemote))returns false, so the receiver runs without error and silently takes its untrusted-sender branch — the quieter and more misleading failure.This completes the 20-vs-32-byte corrections shipped in 0.2.9 for
receiver,destTokenAddressandsourcePoolAddress.senderwas the fourth address-shaped field in the same conversion block, and the only one whose symptom surfaces in application code rather than as a revert inside CCIP — which is why it was missed.Root cause
The source and destination structs are mirror images:
So the conversion must encode one field and decode the other, inverted, in the same struct literal. The decode direction was already correct via
_decodeEVMAddress; the encode direction used the wrong primitive.Pre-v1.6 lanes were never exposed:
EVM2EVMMessage.senderis a typedaddress, so the destination OffRamp performs the encoding, not the simulator. The simulator only inherited this responsibility at v1.6, when CCIP moved sender encoding offchain.The fix
One line in
src/ccip/CCIPLocalSimulatorFork.sol:Consistent with production: Chainlink's own onchain conversion in
OnRampOverSuperchainInterop.solusesabi.encode(message.sender);Client.Any2EVMMessage.senderis documented "abi.decode(sender) if coming from an EVM chain"; and the 2.0MessageV1codec likewise documentssenderas "abi encoded for EVM chains" whilereceiveris raw 20 bytes. The convention is stable across versions.Verification
Unit regression in
test/unit/ccip/CCIPLocalSimulatorForkRouting.t.soldrives_executePostV1dot6against a recording OffRamp mock and pins both consumption patterns:Non-vacuous: fails
20 != 32with the fix reverted. Full non-fork suite green — 57 passed, 0 failed.Additionally verified against live CCIP contracts during review, not committed:
OnRamp 1.6.0) — aCCIPReceivercallingabi.decode(message.sender, (address))fails0x0…0 != <sender>without the fix and passes with it. A second receiver doing the raw trusted-remote comparison showedreceived() == true/senderIsTrusted() == falsewithout the fix, confirming the silent-untrusted-branch failure mode rather than a revert.CCIPv1_5LockReleasePoolForkandCCIPv1_5ForkBurnMintPoolForkrun on that same v1.6 lane and pass with and without the fix. No regression; they are non-discriminating for this bug because they transfer to an EOA and never decodesender.EVM2EVMOnRamp 1.5.0) — passes with and without the fix at identical gas, i.e. byte-for-byte identical execution, confirming_executePreV1dot6never reaches the changed line.Changelog
Added under
## [Unreleased]→### Fixed. The heading should be converted to the target version with a date at release time.Context
Fixes #62
Thank you to @Yurii3721 for raising the issue and to @kurogami-ibrahim79 for their proposed solution in #63.