Reproduced Exploit

Forte Float128 — [H-05] Precision loss in toPackedFloat for mid-range mantissas

1. Mantissas with 39–71 digits sit between M-max (38) and L-min (72). 2. toPackedFloat decides M vs L from the exponent alone. 3. For 2^235 with exponent -51, the encoder force-downcasts to 38 digits and loses 608871777363092441300193790394368 of significance.

Apr 2025Otheruntagged2 min read

Chain

Other

Category

untagged

Date

Apr 2025

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 55707-h-05-precision-loss-in-topackedfloat-function-when-mantissa. Standalone Foundry PoC and full write-up: 55707-h-05-precision-loss-in-topackedfloat-function-when-mantissa_exp in the evm-hack-registry mirror.


Vulnerability classes: integer-bounds · precision-loss · math-is-safe · fix-arithmetic

Reproduction: self-contained Foundry PoC with only forge-std — no fork, no RPC. Full trace: output.txt.


Key info#

ImpactHIGH — up to 33 significant digits permanently destroyed at encode time
ProtocolForte Float128 solidity library
Vulnerable codeFloat128.toPackedFloatisResultL ignores digit count
Bug classPrecision loss / wrong format selection
FindingCode4rena 2025-04-forte-float128-solidity-library · #55707 · hecker_trieu_tien
Reportcode4rena.com/reports/2025-04-forte-float128-solidity-library
SourceAuditVault
Compiler^0.8.24

TL;DR#

  1. Mantissas with 39–71 digits sit between M-max (38) and L-min (72).
  2. toPackedFloat decides M vs L from the exponent alone.
  3. For 2^235 with exponent -51, the encoder force-downcasts to 38 digits and loses 608871777363092441300193790394368 of significance.

The vulnerable code#

SOLIDITY
let isResultL := slt(MAXIMUM_EXPONENT, add(exponent, mantissaMultiplier)) // @> VULN
// FIX: isResultL := or(isResultL, gt(digitsMantissa, MAX_DIGITS_M))

Root cause#

Format selection ignores digitsMantissa. When isResultL stays false, the code divides by 10^(digits-38), zeroing low-order digits that should have been kept in L form.

Preconditions#

  • Mantissa in (MAX_M_DIGIT_NUMBER, MIN_L_DIGIT_NUMBER) and exponent low enough that isResultL remains false (finding PoC: man = 2^235, expo = -51).

Attack walkthrough#

  1. Encode 2^235 at exponent -51.
  2. Decode → 38-digit mantissa at exponent -18.
  3. Reverse-normalize: recovered value strictly less than input by the concrete residue above.

Diagrams#

flowchart TD A["mantissa 2^235<br/>71 significant digits"] --> B["isResultL from exponent only"] B --> C["isResultL = false"] C --> D["divide by 10^33<br/>keep 38 digits"] D --> E["permanent precision loss"]

Impact#

toPackedFloat is the foundation of the library; lost digits poison subsequent mul, sqrt, and comparisons — especially near the lower L boundary.

Taxonomy (AuditVault)#

  • [[integer-bounds]] · [[precision-loss]] · [[variant]] · [[math-is-safe]] · [[fix-arithmetic]]

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.