# Off-by-one boundary checks in contracts

Wrong edge in bounds, cursors, bitmasks, field widths or loop exits that skips or absorbs one element

_Area: Smart contract access control and control flow. Seen in 33 disclosed reports; latest 2026-02-05. Updated 2026-10-23._

## How it works
A boundary is computed or compared against the wrong edge, and the code assumes the boundary element is always present or never relevant. The failure takes a few shapes: (1) a count derived from an exclusive range (`end - start`) drops the last element; (2) a comparison uses the wrong strictness (`>` vs `>=`), so the equality point falls in a gap between two guards; (3) a cursor or sentinel is not a valid lower bound or index; (4) a read width exceeds the declared field width, absorbing adjacent data; (5) a bitmask shift touches neighboring bits; (6) a loop exits early or is bounded below the stored size. In each case one valid element is silently skipped, or adjacent data is absorbed into a check, and state diverges from intent with no error.

## Where it shows up
- Range-to-count conversions: grep `end - start`, `..end` passed to a count helper [E6](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.6.md).
- Complementary guards using `<` and `>` around the same timestamp/amount [E5](https://bugbountylibrary.com/r/sherlock%3A2024-03-axis-finance%3AH-4.md).
- Pagination cursors starting at 0 over keyed stores whose keys may all be >0 [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-12.md).
- Assembly reads: `mload` (32 bytes) of a 1-byte packed flag [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-4.md).
- Bitmap checks that shift the whole word instead of isolating one bit [E2](https://bugbountylibrary.com/r/sherlock%3A2024-06-boost-aa-wallet%3AH-2.md).
- `break`/`return` after the first match in multi-event scans [E4](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AM-19.md).
- A max-index constant used as both count and valid slot id, guarded with `>` [E7](https://bugbountylibrary.com/r/cantina%3Acircuit_puzzles%3A3.3.1.md).
- An array sized `1<<N` indexed with `1<<k` at the k=N boundary [E8](https://bugbountylibrary.com/r/cantina%3Ainfinitypools_october2024%3A3.2.1.md).

## How to review
The invariant: every valid element, slot, bit, event and list entry within the configured bound is processed exactly once, and no read crosses its declared field width — in both directions (no skip, no over-read). Check each bound comparison against the setter's storage bound and the highest valid id; test empty, single-element, last-element and maximum-parameter cases; confirm cursors are valid lower bounds for real key distributions; confirm each bit is isolated before comparison; confirm no early exit drops later events; confirm sentinels are checked before indexing; confirm a cap's removal path actually decrements the counted unit.

## Variants seen
- Wrong-width assembly read flipping a boolean flag [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-4.md).
- Whole-word bitmask shift leaking neighboring bits into a revert [E2](https://bugbountylibrary.com/r/sherlock%3A2024-06-boost-aa-wallet%3AH-2.md).
- Zero start cursor skipping all keys [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-12.md).
- Early break dropping later events, losing funds [E4](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AM-19.md).
- Strict/non-strict guard gap allowing cancel after conclusion [E5](https://bugbountylibrary.com/r/sherlock%3A2024-03-axis-finance%3AH-4.md).
- Exclusive range dropping the last commitment, halting the chain [E6](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.6.md).
- Max-index constant excluding the highest valid slot [E7](https://bugbountylibrary.com/r/cantina%3Acircuit_puzzles%3A3.3.1.md).
- `1<<N` cursor indexing one past a `1<<N`-sized array [E8](https://bugbountylibrary.com/r/cantina%3Ainfinitypools_october2024%3A3.2.1.md).

## Impact and severity
From blacklist bypass [E6](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.6.md) and DoS on claims [E2](https://bugbountylibrary.com/r/sherlock%3A2024-06-boost-aa-wallet%3AH-2.md) to locked bidder funds [E5](https://bugbountylibrary.com/r/sherlock%3A2024-03-axis-finance%3AH-4.md), lost bridge funds [E4](https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AM-19.md), frozen fee governance [E7](https://bugbountylibrary.com/r/cantina%3Acircuit_puzzles%3A3.3.1.md), permanently unusable pools [E8](https://bugbountylibrary.com/r/cantina%3Ainfinitypools_october2024%3A3.2.1.md), mis-parsed account permissions [E1](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-4.md) and stalled topic updates [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-12.md). Severity rises when the skipped element is last in a pipeline, when the check silently passes rather than erroring, and when no test exercises the boundary case.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-4.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2024-06-boost-aa-wallet%3AH-2.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-12.md
- E4: https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AM-19.md
- E5: https://bugbountylibrary.com/r/sherlock%3A2024-03-axis-finance%3AH-4.md
- E6: https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.6.md
- E7: https://bugbountylibrary.com/r/cantina%3Acircuit_puzzles%3A3.3.1.md
- E8: https://bugbountylibrary.com/r/cantina%3Ainfinitypools_october2024%3A3.2.1.md
