# MultiInvoker doesn't pay keepers refund for l1 calldata

MultiInvoker inherits base Kept whose _calldataFee returns 0, so keepers never get L1 calldata refunds on Optimism/Arbitrum, under-incentivizing order execution.

- Payout: Contest reward
- Severity: medium
- Program: Perennial
- Credited: rvierdiiev
- Disclosed: 2023-10-26
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-10-perennial-judging/issues/22
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AM-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:fee-bypass

**Target:** MultiInvoker.sol keeper fee accounting (Kept inheritance)
**Root cause:** MultiInvoker extends the base Kept contract, whose _calldataFee is a no-op returning 0; only Kept_Arbitrum and Kept_Optimism override it to compute L1 calldata fees.
**Steps:**
1. Read MultiInvoker.sol `_handleKeep` which builds KeepConfig with keepBufferCalldata and calls `keep` modifier.
2. Trace `keep` modifier in Kept.sol: computes calldataFee via `_calldataFee(applicableCalldata, multiplierCalldata, bufferCalldata)`.
3. Observe base Kept._calldataFee returns 0; only Kept_Arbitrum/Kept_Optimism override it.
4. Confirm MultiInvoker inherits Kept directly, so calldataFee is always 0 and keeperFee omits L1 calldata cost.
**How found:** Code-reading of the keeper fee path: noticed the `keep` modifier's calldata fee depends on an overridable `_calldataFee`, then checked MultiInvoker's inheritance and found it uses the base no-op implementation rather than the L2-specific overrides.
**Insight:** When a contract inherits a base with virtual no-op hooks meant to be overridden per-chain (L2 calldata pricing), verify the deployed contract actually inherits the chain-specific override, not the base.
