Skip to content

fix: #2308 declare CCIP approve with no outputs and narrow the LayerZero rationale - #2390

Open
suisuss wants to merge 1 commit into
stagingfrom
fix/ccip-approve-no-outputs
Open

fix: #2308 declare CCIP approve with no outputs and narrow the LayerZero rationale#2390
suisuss wants to merge 1 commit into
stagingfrom
fix/ccip-approve-no-outputs

Conversation

@suisuss

@suisuss suisuss commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #2389, from an independent review of that PR. Four fixes, none of which change runtime behaviour for a conforming token.

1. protocols/abis/ccip-erc20.json had the same defect, one file over

approve declared a bool output. Both contracts bound to that ABI - ccipBridgeToken and ccipFeeToken - are userSpecifiedAddress: true, and USDT is a CCIP-supported bridge and fee token. So on the EOA path the adapter's preflight staticCall decodes USDT's empty return as a bool, throws BAD_DATA, and the approve fails before it is broadcast - byte for byte the failure #2389 fixed for LayerZero.

The coverage suite cannot catch this. CCIP_ERC20_ADDRESSES points at WETH/WBNB/WMATIC/WAVAX, which all return bool, and both approve actions are skipped in the fixture regardless. Guarded here with a unit test instead; verified it fails when the bool declaration is restored.

2. #2389's comment overstated the failure, in the same direction as the comment it replaced

It said the mismatch "is not inert on the write path". Only the EOA path decodes:

Route Preflight staticCall?
EOA direct yes - breaks
Safe no - encodeFunctionData + estimateGas
Safe-role no - same
Turnkey sponsored no - encodeFunctionData only

The comment it corrected claimed the write path never decodes; its replacement implied it always does. Both wrong, opposite directions. This matters practically: the same node, ABI and token fails for an EOA connection and succeeds for a Safe one, so a repro that omits the signer mode proves nothing either way.

3. One assertion in #2389's test discriminated nothing

decodeFunctionResult("approve", bool32) not throwing holds under outputs: [bool] as well as outputs: [], so it documented rather than tested. Dropped.

Worth noting what this test still is not: a guard on the mechanism. Nothing exercises EvmChainAdapter.executeContractCall against a provider returning "0x". A stubbed-provider test there would cover every write ABI at once, including lib/contracts/abis/erc20.json, which still declares bool on approve/transfer/transferFrom. Both are left to the separately tracked ticket rather than widened onto a release branch.

4. docs/plugins/layerzero.md documented an output that never existed

The OFT Approve page listed result | bool | Result. buildOutputFieldsFromAction gates ABI outputs on action.type === "read", so a write action surfaces success, error, transactionHash, transactionLink. That row was wrong before this change and contradicted the shipped ABI after it.

Verification

  • tests/unit/protocol-layerzero.test.ts (22) and tests/unit/protocol-chainlink.test.ts (30) pass; protocol-calldata (508) unaffected
  • The new CCIP test fails when the bool declaration is restored, so it is a real guard
  • biome check clean on all five changed files

…ero rationale

Follow-up to #2389, from an independent review of it.

protocols/abis/ccip-erc20.json carried the same defect that PR fixed one
file over: approve declared a bool output. Both contracts bound to it
(ccipBridgeToken, ccipFeeToken) take a user-supplied token address, and
USDT is a CCIP-supported bridge and fee token, so the EOA preflight
staticCall decodes USDT's empty return as a bool and throws BAD_DATA
before the approve is broadcast. The coverage suite cannot catch it: its
reference tokens are WETH/WBNB/WMATIC/WAVAX, all bool-returning, and both
approve actions are skipped there.

#2389's replacement comment also overstated the failure in the same
direction as the comment it corrected. Only the EOA path decodes; Safe,
Safe-role and Turnkey-sponsored sends encode and estimate gas without a
staticCall, so a bool declaration survives them. That distinction decides
whether a repro reproduces.

The regression test #2389 added asserted that a 32-byte bool return
decodes, which holds under either declaration and so discriminated
nothing. Dropped, and an equivalent guard added for the CCIP ABI, which
has no coverage-suite backstop at all.

docs/plugins/layerzero.md documented `result | bool` for OFT Approve.
Write actions surface success/error/transactionHash/transactionLink -
buildOutputFieldsFromAction gates ABI outputs on read actions - so that
row was wrong before this change and contradicted the ABI after it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-issue-required PR exempt from the issue-first gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant