# Precision loss in the invariant function can lead to loss of funds

Numoen Pair.invariant() rounds before scaling, so a swap can extract token0 value while the invariant check still passes.

- Payout: Contest reward
- Severity: high
- Program: Numoen
- Credited: hansfriese
- Disclosed: 2023-03-03
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-01-numoen-findings/issues/264
- Library page: https://bugbountylibrary.com/r/c4%3A2023-01-numoen%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:precision-loss

**Target:** Pair.sol invariant() / swap() in Numoen lending pair
**Root cause:** invariant() computes FullMath.mulDiv(amount, 1e18, liquidity) then multiplies by tokenScale, so for low-decimal tokens with large liquidity the division rounds to zero before scaling, hiding reserve changes from the invariant check.
**Steps:**
1. Deploy pair with token0 6 decimals (token0Scale=1e12), token1 18 decimals, liquidity=1e24.
2. Add liquidity with amount0=1.5e6, amount1=2*(5e24-1e21).
3. Call `lendgine.swap(dennis, 5e5, 0, abi.encode(SwapCallbackData(...)))` with zero callback payment.
4. Observe invariant a+b-c-d stays 0 despite reserve0 changing 100%->150%, so swap succeeds and dennis receives 5e5 token0 for free.
**How found:** Author identified invariant() as the core guard, noticed mulDiv divides before multiplying by tokenScale, hypothesized rounding to zero for 6-decimal tokens with >24-decimal liquidity, and wrote a Foundry test comparing a+b-c-d across reserve changes.
**Exploitation:** Attacker calls swap() to withdraw token0 without paying, because the rounded invariant still evaluates as satisfied; repeatable to drain value from low-decimal pools.
**Insight:** When an invariant/health check scales values, check the order of multiply vs divide: division before scaling silently truncates small amounts to zero, letting state changes slip past the guard. Test with the lowest-decimal token and largest liquidity the protocol allows.
