# Payable value dropped or reused

Check msg.value reuse across delegatecall loops, stranded ETH on swallowed failures, and non-payable execute blocking payouts

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2023-06-14. Updated 2026-11-02._

## How it works
ETH accounting assumes `msg.value` belongs to exactly one call and is delivered once. Three assumptions break: (1) `msg.value` is a property of the transaction, not the call frame, so it persists across `delegatecall` — a payable batch loop forwarding "the same" value lets each iteration spend the full amount from the contract's own balance [E1](https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md). (2) A batch that self-calls with value but swallows per-item failures leaves the failed item's ETH sitting in the caller, because the aggregate check compares against `msg.value` rather than the contract's balance [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md). (3) A non-payable entrypoint that should forward ETH cannot, so value held by a downstream contract (a Timelock) can never leave [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md).

## Where it shows up
- Payable `batch()`/multicall that `delegatecall`s a payable `execute()` in a loop [E1](https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md)
- Batch functions that self-call with `value:` inside a loop and do not revert on inner failure [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md)
- Aggregate-value checks comparing to `msg.value` instead of `address(this).balance` [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md)
- Governance `execute()` that is not `payable` while proposals carry ETH values [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md)
- Compound-fork governors forwarding `proposal.values` to `timelock.executeTransaction` [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md)

## How to review
- For any payable loop of `delegatecall`/`call` with value: does each iteration debit the caller, or can the same `msg.value` be spent twice against contract funds? The invariant: total ETH leaving the contract per transaction ≤ ETH actually supplied by the caller.
- For batch-with-swallowed-failure: on partial failure, is the failed item's value refunded or swept? Invariant: contract balance after the batch equals balance before plus exactly the value of successful items.
- For governance: is every entrypoint on the ETH payout path `payable`, and does it forward `proposal.values`? Invariant: any ETH the system can receive, it can also pay out.

## How finders spotted it
- Recalled samczsun's writeup on `msg.value` persistence in delegatecall loops and applied it to a proxy inheriting a payable batchable mixin; confirmed with a PoC doubling a deposit [E1](https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md)
- Read `forceDeployOnAddresses`: noticed a self-call with value inside a non-reverting loop, and that the check used `msg.value` not contract balance — implying stranded ETH on partial failure [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md)
- Experimented with governance contracts, noticed `execute()` non-payable while proposals specified ETH values, then tested a small payout proposal and saw the revert [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md)

## Variants seen
- Same `msg.value` re-spent per delegatecall iteration [E1](https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md)
- Per-item value stranded when inner call fails and loop continues [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md)
- Value trapped because the only payout path is non-payable [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md)

## Impact and severity
Drain of proxy ETH by an approved caller [E1](https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md); permanent loss of user ETH sent to a system contract on partial failure [E2](https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md); governance-held ETH made unspendable, bricking any ETH-paying proposal [E3](https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md). Severity rises with how much ETH the contract custodies and whether the path is reachable by ordinary users or only privileged callers.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2022-08-mimo%3AM-04.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AM-05.md
- E3: https://bugbountylibrary.com/r/c4%3A2022-01-openleverage%3AM-03.md
