# Precompile call-context confusion

Precompiles that accept DELEGATECALL or return wrong values in caller context, breaking msg.sender and signature assumptions

_Area: Smart contract access control and control flow. Seen in 2 disclosed reports; one confirmed bounty: $1,050,000; latest 2024-02-29. Updated 2026-10-23._

## How it works
A precompile is supposed to behave like a fixed native function: same inputs, same outputs, regardless of how it is reached. That assumption breaks when the precompile is implemented as a contract (Yul/Solidity) or dispatched by a layer that does not distinguish CALL, STATICCALL and DELEGATECALL. Under DELEGATECALL the code runs in the caller's storage and inherits the caller's `msg.sender`, so a precompile that trusts `msg.sender` now trusts whoever the calling contract was, not the contract itself. Alternatively the precompile returns a default value (e.g. `bytes32(0)`) because it took a different code path than the native one.

## Where it shows up
- Custom precompiles or native-bridge precompiles that read `msg.sender` for authorization [E1](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-missing-call-check-bugfix-review.md)
- Precompile dispatch code with no explicit CALL/DELEGATECALL/STATICCALL branch [E1](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-missing-call-check-bugfix-review.md)
- Precompiles implemented as Yul/Solidity contracts rather than native handlers [E3](https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-17.md)
- EVM-equivalent L2s claiming precompile parity without per-call-type tests [E3](https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-17.md)
- Accelerated precompiles with dynamic gas costs used inside fault-proof replay [E2](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AH-04.md)

## How to review
The invariant: for every precompile, the result and authorization must be identical under CALL, STATICCALL and DELEGATECALL, or DELEGATECALL must be rejected outright. Check the dispatch layer for an explicit call-type check before any `msg.sender`-based trust. Differential-test each precompile across all three call types and compare against the reference EVM. For fault proofs, verify that the L1 cost to reproduce any L2 precompile call (including EIP-150 63/64 forwarding and calldata overhead) stays under the L1 block gas limit [E2](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AH-04.md).

## Variants seen
- Missing call-type check lets a contract impersonate its caller and drain approved tokens via `transferFrom` [E1](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-missing-call-check-bugfix-review.md)
- Precompile returns `bytes32(0)` under DELEGATECALL instead of the recovered address, breaking signature validation [E3](https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-17.md)
- Dynamic-gas precompile call unreproducible on L1, stalling fault-proof `step()` [E2](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AH-04.md)

## Why fixes fail
E3 shows the gap persisting as a compatibility discrepancy even where the precompile exists: implementing it as a contract rather than a native handler leaves call-context behavior divergent, so parity claims need continuous differential testing, not a one-time fix.

## Impact and severity
E1 was critical: unauthorized token drainage through impersonated `msg.sender` (bounty median ~1.05M). E3 is medium: broken signature checks and EVM incompatibility. E2 is high: invalid-state challenges blocked, threatening bridge/fault-proof liveness. Severity rises when the precompile guards funds or underpins a fault-proof system.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-missing-call-check-bugfix-review.md
- E2: https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AH-04.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-17.md
