Repository navigation
Default delivery gas, float drain guard, LayerZero and Hyperlane fixes - #57
Conversation
OApp's _lzSend requires msg.value to equal the fee, so a LayerZero account paying from its balance reverted. LzMessage.send and quote are public and linked, which also frees 1.2 KB on every LayerZero transceiver. Every binding now runs a send through a real transmitter. Fixes #55
Without a gas attribute a bootstrap now gets 1,000,000 gas, a report 250,000, and a payload 200,000 (DeliveryGas), chosen from the payload so a quote and its send agree. The provider defaults were 50,000 to 200,000 against a 590,000-650,000 account creation, and LayerZero's empty options reverted on every endpoint. Fixes #50 Fixes #52
A reporting destination pays the report from its float, so a zero bootstrap fee let anyone drain it. The bootstrap and its quote now revert NoBootstrapFee there, and a fuzz test pins that a delivery cut short at any gas leaves the float untouched.
The Mailbox's fallback hook quotes 0 for a domain it does not route, and the dispatch is never delivered. HyperlaneMessage's quote now reverts NoHyperlaneRoute on zero and dispatch runs it first. The mock charges at least 1 wei on a routed domain, so shared send tests pay their quote. Fixes #56
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It reworks fee/gas-sensitive send and quote paths and the LayerZero payment flow across all five providers and changes bootstrap-acceptance behavior, so these cross-cutting, security-sensitive changes warrant final human review.
Review effort: Balanced
Findings: 1
What changed in this PR
This PR applies decisions D3/D4 from the chain checks (stacked on #54), introducing per-message-kind default destination gas and fixing three provider-specific delivery bugs across the native messaging bindings. It adds a DeliveryGas library (BOOTSTRAP 1,000,000 / REPORT 250,000 / PAYLOAD 200,000) and a new _defaultGas(payload) seam on OutboundBase, so a send and its quote always agree on gas chosen from the payload alone. It rewrites LayerZero's send/quote to pay the endpoint directly through a linked LzMessage library (fixing #55 and the empty-options revert #50), refuses zero Hyperlane quotes (#56), and guards against float drain by rejecting free bootstraps to reporting destinations.
Changes:
- New
DeliveryGasconstants +_defaultGasvirtual threaded through every provider'ssend/quote(replacing per-provider gateway defaults). - LayerZero binding pays the endpoint directly via linked
LzMessage.send/quotewith type-3lzReceiveoptions; Hyperlane refuses zero quotes (NoHyperlaneRoute); transceiver refuses zero-fee bootstraps to reporting chains (NoBootstrapFee). - New shared specs (
ProviderTransmitterSendSpec,ProviderDefaultGasSpec), a float-drain fuzz test, and docs/spec updates.
| File | Description |
|---|---|
src/messaging/DeliveryGas.sol |
New constants for bootstrap/report/payload default gas |
src/messaging/outbound/OutboundBase.sol |
New _defaultGas pure virtual |
src/messaging/outbound/TransmitterBase.sol |
_defaultGas → PAYLOAD |
src/messaging/transceiver/TransceiverBase.sol |
_defaultGas (report/bootstrap); NoBootstrapFee guard |
src/messaging/Envelope.sol |
isReceiverReport helper |
src/protocols/layerzero/LzMessage.sol |
Public linked send/quote; type-3 default options |
src/protocols/layerzero/Lz{Transmitter,Transceiver}.sol |
Route through LzMessage; drop _payNative/_lzSend |
src/protocols/hyperlane/HyperlaneMessage.sol |
NoHyperlaneRoute on zero quote; defaultGas param |
src/protocols/{ccip,op-stack,wormhole}/* |
Thread defaultGas through send/quote, drop local defaults |
test/protocols/* |
New ProviderGasFixture, send/default-gas specs, per-binding gas hooks |
test/ReceiverReport.t.sol, test/InboundAuth.t.sol |
Float-drain fuzz test, NoBootstrapFee tests, fee wiring |
docs/*.md, deploy/*.md, README.md |
Spec/doc updates for default gas, LZ/Hyperlane fixes, linking |
I verified the core logic: the _defaultGas quote/send agreement holds per binding, the LayerZero type-3 options encoding matches OptionsBuilder's executor lzReceive layout and the fixtures decode it correctly, removed constants/imports have no lingering references, the NoBootstrapFee guard covers all bootstrap paths uniformly, and only TransmitterBase/TransceiverBase extend OutboundBase (both implement the new virtual). The one finding is a documentation omission (ProviderTransmitterSendSpec missing from the §8 suite table).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Stacked on #54. Decisions D3 and D4 from the chain checks.
LzMessagethat pays the endpoint directly. OApp's_lzSendrequiredmsg.valueto equal the fee, so a LayerZero account paying from its balance reverted (LayerZero transmitter: a send paid from the account's balance reverts unless msg.value equals the fee #55). Frees 1.2 KB on every LayerZero transceiver. NewProviderTransmitterSendSpecsends through a real transmitter on every binding.DeliveryGas), picked from the payload so quote and send agree. LayerZero now sends type-3lzReceiveoptions instead of empty ones (LayerZero: empty options revert on every real endpoint, so default sends and every report fail #50); CCIP explicit in-orderextraArgs. NewProviderDefaultGasSpec(C32); spec R1.5 updated (Default destination gas is below a bootstrap's cost on every provider #52).NoBootstrapFeetoward a chain that reports while its fee is zero, so nobody can drain the float. A fuzz test pins that a delivery cut short at any gas leaves the float untouched. Spec R7.6.NoHyperlaneRoute(Hyperlane: a domain with no route quotes 0 and its dispatch is never delivered #56). The mock charges at least 1 wei on a routed domain, so shared send tests pay their quote.Measured on real lanes: CCIP, LayerZero, and Hyperlane quote 1,000,000 gas on every configured pair (deploy/CHECKS.md).
Fixes #50, #52, #55, #56. 775 tests; lint clean on both profiles; every transceiver under EIP-170.