# Packed storage slot corruption in oracles

Assembly packing helpers with wrong slot offsets or missing shift masks that corrupt reserves and oracle values

_Area: DeFi oracles, liquidations and flash loans. Seen in 2 disclosed reports; latest 2023-10-05. Updated 2026-10-23._

## How it works
Packed storage puts several values in one 32-byte slot, addressed by a base slot plus an index and split by a shift mask. The code assumes the offset arithmetic and the mask pair are correct. When the offset omits a stride multiply, or a `shr` is not paired with its `shl`, the read or write lands on the wrong half of the wrong slot. The value written is still a plausible number, so nothing reverts; reserves and derived oracle prices are silently wrong [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md). A related assumption breaks in E1: a function overloads one argument to mean either an amount or an id, and disambiguates by magnitude, so a small legitimate amount is reinterpreted as an id.

## Where it shows up
- Assembly helpers doing `sload`/`sstore` with `add(slot, ...)` where the index is not multiplied by the slot stride [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)
- `shr`/`shl` mask pairs where one half of the pair is missing or asymmetric [E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md)
- Odd-length arrays or counts taking a different packing branch than even ones [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md)
- Sibling libraries implementing the same packing logic with differing offsets
- Functions where one parameter is both an amount and an identifier, branched on by size [E1](https://bugbountylibrary.com/r/dhl%3A20260909-omni404.md)
- Oracle values derived from stored reserves rather than read fresh

## How to review
For each packed helper, state the invariant: a value stored at index i and read back at index i returns the same bits, and the other half of the slot is unchanged. Check the offset includes the full stride (`mul(index, 32)`), and that every `shr` has its matching `shl` preserving the destination half [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md). Diff against a reference implementation of the same packing. Test with odd and even counts. For overloaded transfer-style functions, check that no external caller can pass an attacker-influenced small value that flips the interpretation [E1](https://bugbountylibrary.com/r/dhl%3A20260909-omni404.md).

## Variants seen
- Missing stride multiply in the slot offset, aliasing a lower-index slot on odd counts [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)
- Missing `shl(128,...)` mask, writing the source's upper half into the destination's lower half [E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md)
- Magnitude-based overloading of amount vs id, exploited through a DEX exact-output swap [E1](https://bugbountylibrary.com/r/dhl%3A20260909-omni404.md)

## Impact and severity
Corrupted reserves feed wrong oracle values, which can misprice liquidations and let flash-loan-funded attackers profit [E2](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md). E1 drained 2.4 WETH from a Uniswap V3 pool. Severity is high because the corruption is silent and persists until overwritten.

## Evidence
- E1: https://bugbountylibrary.com/r/dhl%3A20260909-omni404.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-02.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AM-03.md
