# Gas usage of cross-chain messages is undercounted, causing discrepancy between L1 and L2 and impacting intrinsic gas calculation

CrossDomainMessenger.sendMessage undercounts gas by excluding the 140-byte relayMessage wrapper, so users underpay L1 gas and ResourceMetering misprices demand.

- Payout: Contest reward
- Severity: medium
- Program: Optimism
- Credited: Jeiwan
- Disclosed: 2023-03-20
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-03-optimism-judging/issues/88
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-03-optimism%3AM-2
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:fee-bypass

**Target:** CrossDomainMessenger / L1CrossDomainMessenger / L2CrossDomainMessenger Solidity contracts
**Root cause:** baseGas() is computed over the raw user _message rather than the abi-encoded relayMessage(nonce,sender,target,value,minGasLimit,message) calldata that is actually sent, omitting 140 bytes (4 selector + 32 nonce + 20 sender + 20 target + 32 value + 32 minGasLimit).
**Steps:**
1. Read CrossDomainMessenger.sendMessage: it calls baseGas(_message,_minGasLimit) but _sendMessage with abi.encodeWithSelector(relayMessage.selector, nonce, msg.sender, _target, msg.value, _minGasLimit, _message).
2. Note baseGas counts only _message bytes, not the 140-byte wrapper.
3. Compare with op-geth intrinsic gas (core/state_transition.go) which charges for the full data length.
4. Compare with op-chain-ops/crossdomain/migrate.go which computes gas on the full wrapped data.
5. Quantify: 140 bytes * 16 gas = 2240 gas underpriced per L1->L2 message (~10% of fixed base cost).
6. Fix: build wrappedMessage first and pass it to baseGas.
**How found:** Code reading of sendMessage vs baseGas, cross-checked against op-geth intrinsic gas calculation and the migration code path, revealing the wrapper bytes were excluded from the gas accounting.
**Insight:** When a contract charges users for gas of an encoded call, verify the byte length used in the cost formula matches the actual calldata emitted (selector + ABI-padded args), not just the inner payload; cross-check against the node's intrinsic gas and any migration/legacy code paths for the intended semantics.
