Reproduced Exploit

Forte Float128 — [H-01] Early 72-digit adjustment in sqrt wrong exponent

1. Large-path sqrt computes a 73-digit rMan via Uint512.sqrt512. 2. The code trims to 72 digits (rMan /= 10; ++rExp) before rExp /= 2. 3. Integer division drops the +1 (2195/2 = 1097 instead of 1098) → result is 10× too small.

Apr 2025Otheruntagged3 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: 55703-h-01-early-72-digit-adjustment-in-sqrt-will-lead-to-incorrec. Standalone Foundry PoC and full write-up: 55703-h-01-early-72-digit-adjustment-in-sqrt-will-lead-to-incorrec_exp in the evm-hack-registry mirror.


Vulnerability classes: integer-bounds · variant · misassumption/math-is-safe

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


Key info#

ImpactHIGH — sqrt of large floats returns a value 10× too small (exponent off-by-one)
ProtocolForte Float128 solidity library
Vulnerable codeFloat128.sqrt large-number branch — digit trim before exponent halve
Bug classIncorrect operation order / integer-division loss
FindingCode4rena 2025-04-forte-float128-solidity-library · #55703 · 0xcrazyboy999
Reportcode4rena.com/reports/2025-04-forte-float128-solidity-library
SourceAuditVault
Compiler^0.8.24

TL;DR#

  1. Large-path sqrt computes a 73-digit rMan via Uint512.sqrt512.
  2. The code trims to 72 digits (rMan /= 10; ++rExp) before rExp /= 2.
  3. Integer division drops the +1 (2195/2 = 1097 instead of 1098) → result is 10× too small.

The vulnerable code#

SOLIDITY
if (rMan > MAX_L_DIGIT_NUMBER) {
    rMan /= BASE;
    ++rExp;
}
rExp = (rExp) / 2; // @> VULN: halve AFTER 73-digit trim

Fix: halve first, then trim:

DIFF
+ rExp = (rExp) / 2;
  if (rMan > MAX_L_DIGIT_NUMBER) {
      rMan /= BASE;
      ++rExp;
  }
- rExp = (rExp) / 2;

Root cause#

Halving and digit-normalization do not commute under integer division. Incrementing rExp then dividing by 2 loses the carry that should survive into the final exponent.

Preconditions#

  • Input large enough to take the Uint512 sqrt path with a 73-digit intermediate mantissa (finding PoC: real value 3.82e2338).

Attack walkthrough#

  1. Build post-parity intermediates for the PoC float (aMan 73 digits, aExp = 2266).
  2. Run the buggy large path → exponent 1097.
  3. Run the fixed order → exponent 1098, same mantissa.
  4. Harm: off-by-one exponent = 10× magnitude error.

Diagrams#

flowchart TD A["rMan has 73 digits<br/>rExp = 2194"] --> B["trim: rMan/=10; ++rExp<br/>rExp = 2195"] B --> C["halve: rExp = 2195/2 = 1097"] C --> D["BUGGY result 10x too small"] A --> E["FIX: halve first<br/>rExp = 1097"] E --> F["then trim: ++rExp -> 1098"] F --> G["CORRECT magnitude"]

Impact#

Any protocol using Float128 sqrt on large values (prices, rates, geometric means) gets a systematically wrong result — not rounding dust, a full order-of-magnitude error.

Taxonomy (AuditVault)#

  • [[integer-bounds]] · [[variant]]

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.