7. Rational
rational-math ZERO representation
Last updated
rational-math ZERO representation
Last updated
This is a vault implementation using rational math. The original rational math lib can be found here: .
The idea is to store numerator in high 128 bits and store denominator in low 128 bit. Then add sub multiply divide become 4 formulae where you can compute its numerator part and denominator part independently. By doing so you avoid doing division and kind of solve precision loss systematically.
Comment: Why βkind ofβ? Because in each step you have to simplify the rational number to avoid overflow (recall that the upper bound is only 2^128 - 1 now). Simplification just means dividing numerator and denominator by gcd. The probability of two non-equal integers to be coprime is 6 / (pi)^2, which is around 60% (). Being coprime means gcd = 1 therefore canβt be simplified. Also you have to consider the probability that the gcd is very small therefore simplification has little effect. In conclusion, the idea of rational number is great but after doing multiplication for many rounds you will trigger overflow protection, so this approach is not widely adopted.
The vulnerability exists in the sub
function of the Rational
library, which violates the fundamental mathematical property that x - 0 = x
. This breaks the vault's share accounting mechanism and allows complete fund drainage.
The bug stems from how ZERO
is defined and handled in arithmetic operations:
Rational.wrap(0)
creates a rational number with both numerator and denominator as 0 (representing 0/0
), not the mathematically correct 0/1
. When this malformed zero is used in subtraction, it corrupts the calculation.
(In the original codebase it was Rational constant ZERO = Rational.wrap(1);
)
In the sub
function, when subtracting zero from any rational number:
When y = ZERO
(which is 0/0
):
yNumerator = 0
, yDenominator = 0
numerator = xNumerator * 0 - 0 * xDenominator = 0
denominator = xDenominator * 0 = 0
Result: toRational(0, 0)
returns ZERO
Therefore, any number minus zero equals zero, which is mathematically incorrect.
Exploitation Path:
The vault uses rational numbers for share tracking. The attacker exploits this bug to corrupt the totalShares
variable:
Initial State: Vault has 5000e18 tokens, totalShares = 5000e18/1
redeem(0): Subtracting zero corrupts the state:
mint(1): With totalShares = ZERO
, the vault thinks it's empty:
The attacker gets 1 share for 1 token, and totalShares
becomes 1/1
.
redeem(1): The attacker now owns 100% of shares (1/1
out of 1/1
), so they can withdraw the entire vault balance.