# Array index corruption on removal or skip

Length checks after removal, skip offsets that never flip, swap-and-pop that writes the wrong element

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2026-02-05. Updated 2026-10-29._

## How it works
Code that removes or skips an element in an array assumes a position or length that no longer holds once the mutation happens. Three shapes: a length/emptiness check runs *after* the removal already shrank the array, so the boundary is off by one; a skip offset in a parallel-array loop never flips because its condition is derived from an assumed value rather than real state, so writes land at the wrong index; a swap-and-pop helper writes the target element back instead of the last element, so the wrong entry is popped. In each case the array no longer matches the accounting it represents, and later reads of balances, debt, or join amounts are wrong.

## Where it shows up
- A `length == 1` / `length == 0` check placed after a `remove*`/`pop` call on the same array [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AH-13.md)
- Swap-and-pop helpers where `set(index, ...)` is passed the target rather than `at(length-1)` [E3](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md)
- Loops with a `skipIndex`/offset that only advances under a condition comparing an assumed token to real pool state [E2](https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md)
- Parallel arrays (assets vs amounts, collaterals vs debts) written at `i` while an offset is meant to shift them [E2](https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md)
- Removal helpers whose stated intent (swap-with-last) differs from the code [E3](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md)

## How to review
For every array mutation, establish the invariant: after removal, the array length and each remaining entry must still correspond one-to-one with the accounting structure (debts, assets, amounts). Check ordering: any length/emptiness test must run *before* the removal it depends on, or be recomputed after [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AH-13.md). For skip loops, confirm the skip condition reads actual on-chain state (e.g. the real pool token) and that the write index advances only when the skip fires [E2](https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md). For swap-and-pop, confirm the written value is the last element, not the target [E3](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md). Trace one call with 1, 2, and N elements.

## Variants seen
- Post-removal length check flips both the last-element and two-element cases [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AH-13.md)
- Skip offset never triggers, duplicating amounts at the wrong index [E2](https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md)
- Self-assignment turns swap-and-pop into plain pop, removing the wrong asset [E3](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md)

## Impact and severity
Led to collateral withdrawal while debt remained or blocked liquidation [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AH-13.md), permanent revert of lever-increase for ERC4626 positions [E2](https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md), and corrupted perps asset lists on close [E3](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md). Severity rises when the corrupted array gates solvency or liquidation logic.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AH-13.md
- E2: https://bugbountylibrary.com/r/c4%3A2024-07-loopfi%3AM-06.md
- E3: https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-10.md
