# Unsafe downcast truncation in accounting

Narrowing casts of amounts, lengths, or scaled values that wrap, truncate to zero, or revert

_Area: DeFi accounting and math bugs. Seen in 37 disclosed reports; 2 confirmed bounties: $50,000 and $1,000,000; latest 2026-08-20. Updated 2026-10-23._

## How it works
A value is computed in a wide type (uint256, U256, u128) then stored, passed, or encoded in a narrower one (uint8, uint88, uint96, uint120, uint128, int256). The code assumes the value fits. When it does not, the cast wraps to a small number, truncates to zero, flips sign, or reverts. The broken assumption is that a bound on one input (totalSupply, a single fraction, a config value) bounds the final scaled result. Scaling factors, cross-multiplication, packed length fields, or a later user action break that bound. Three outcomes need different fixes: wrap/truncate silently corrupts accounting; truncate-to-zero makes a transfer move nothing while the caller credits the full amount [E2](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-astar-and-acala-library-truncation-bugfix-review-1m-payout.md); revert turns a routine action into a DoS [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md)[E8](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-6.md).

## Where it shows up
- Casts of user amounts after arithmetic (mulDiv, convertToShares, percentage math) into uint88/uint96/uint120/uint128.
- Length or count fields packed with a narrowing cast, later used to compute byte offsets [E1](https://bugbountylibrary.com/r/c4%3A2023-05-maia%3AH-12.md).
- Cross-VM value conversion where the source type is wider than the destination [E2](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-astar-and-acala-library-truncation-bugfix-review-1m-payout.md)[E3](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md).
- Products of scaling factors (denomination, multipliers, percentages) checked only against one factor's max [E4](https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md).
- Fractions stored in a packed type and cross-multiplied [E5](https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AH-01.md).
- Signed/unsigned mismatch: a balance tracked as int256 while siblings use uint256 [E7](https://bugbountylibrary.com/r/cantina%3Aeigenlayer_mar2025%3A3.1.1.md).
- Counters bit-packed into a shared word, incremented per user action [E8](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-6.md).

## How to review
For every narrowing cast, find the true upper bound of the pre-cast value and compare it to the target type's max. The invariant: for all reachable inputs, pre-cast value <= type max, and the truncated value is never reused where the original was intended. Check whether a passing bound on one input actually bounds the scaled product [E4](https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md). Check whether cross-multiplied fractions can exceed storage width [E5](https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AH-01.md). Check whether downstream code relies on overflow reverting [E7](https://bugbountylibrary.com/r/cantina%3Aeigenlayer_mar2025%3A3.1.1.md). Check whether the raw pre-cast amount feeds a second calculation [E3](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md). Diff declared return types across cross-contract calls and reject silent fallback branches [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md). Test boundary values like type(uint128).max+2 and multiples of 2^N.

## Variants seen
- uint8 length cast misaligns parser offsets, minting arbitrary tokens [E1](https://bugbountylibrary.com/r/c4%3A2023-05-maia%3AH-12.md).
- U256-to-u128 transfer truncation moves nothing while EVM sees full value [E2](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-astar-and-acala-library-truncation-bugfix-review-1m-payout.md).
- Cross-VM ERC-20 amount truncation mints into a DEX pool [E3](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md).
- Quorum downcast wraps to a tiny divisor, draining funds [E4](https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md).
- uint120 fraction truncates to 0/0, resetting fill state [E5](https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AH-01.md).
- U256-to-u128 health-factor narrowing panics on dust debt [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md).
- uint256-to-int256 wrap inflates share balance [E7](https://bugbountylibrary.com/r/cantina%3Aeigenlayer_mar2025%3A3.1.1.md).
- uint16 packed nonce overflow bricks withdrawals [E8](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-6.md).

## Why fixes fail
Bounds checks on a single input do not cover the scaled product [E4](https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md). Saturating one sentinel branch leaves neighboring states overflowing [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md). Fixing the cast but reusing the raw pre-cast amount elsewhere leaves the second path broken [E3](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md).

## Impact and severity
Full drain [E4](https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md), theft of approved tokens [E2](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-astar-and-acala-library-truncation-bugfix-review-1m-payout.md), permanent DoS and locked funds [E3](https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md)[E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md)[E8](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-6.md), unbacked participation [E7](https://bugbountylibrary.com/r/cantina%3Aeigenlayer_mar2025%3A3.1.1.md). Severity is highest when the cast feeds a payout or drainable balance, or a one-way state transition.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-05-maia%3AH-12.md
- E2: https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-moonbeam-astar-and-acala-library-truncation-bugfix-review-1m-payout.md
- E3: https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-astar-network-integer-truncation-error-bugfix-review.md
- E4: https://bugbountylibrary.com/r/sherlock%3A2024-08-flayer%3AH-3.md
- E5: https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AH-01.md
- E6: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-37.md
- E7: https://bugbountylibrary.com/r/cantina%3Aeigenlayer_mar2025%3A3.1.1.md
- E8: https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-6.md
