DEV Community

rim dinov
rim dinov

Posted on

How I Broke my Starknet Staking Contract with Simple Math: A Lesson on Rounding Errors

Every smart contract developer knows that floating-point numbers don't exist in Solidity or Cairo. We use integers. But knowing it and feeling the consequences are two different things.

In my recent security research on a Starknet staking protocol, I implemented a vulnerability that is so simple, yet so devastating: The "Division Before Multiplication" Bug.
The Vulnerable LogicImagine a standard staking reward formula. You want to calculate the reward increase based on time, a reward rate, and the total supply of staked tokens. To maintain precision, we use a multiplier (like $10^{18}$).Here is the line of code that caused the "ghost rewards" issue:
// ⚠️ VULNERABLE CODE
let reward_increase = (duration * rate) / supply * 1_000_000_000_000_000_000;
At first glance, it looks fine. We calculate the rate per time, divide by supply, and scale it up. Wrong.
Why It BreaksIn integer-based languages like Cairo, division is performed first. If (duration * rate) is smaller than supply, the result of the division is exactly 0.Even if you multiply that 0 by a quintillion ($10^{18}$), it remains 0. The rewards for your users simply vanish into the void.
Proving the Flaw (PoC)
To confirm this, I wrote a Proof of Concept using Starknet Foundry (snforge). I simulated a "Whale" entering the pool with a large supply, making the denominator huge.

[test]

fn test_math_precision_loss() {
let duration: u256 = 100;
let rate: u256 = 10000;

let supply: u256 = 1_000_000_000_000_000_000_000; // 1000 tokens
let precision: u256 = 1_000_000_000_000_000_000;

// Result: (1,000,000 / 10^21) = 0. Then 0 * 10^18 = 0
let reward_increase = (duration * rate) / supply * precision;
assert(reward_increase == 0, 'BUG_STILL_EXISTS');
Enter fullscreen mode Exit fullscreen mode

}
The test passed, meaning the rewards were indeed lost.

The Fix
The rule is simple: Always multiply before you divide. By reordering the operations, we keep the numbers large before the division "chops off" the precision.
// ✅ SECURE CODE
let reward_increase = (duration * rate * 1_000_000_000_000_000_000) / supply;
With this fix, even small rewards are captured and distributed correctly.
Conclusion
Precision loss is a silent killer in DeFi. One misplaced set of parentheses or a wrong order of operations can lead to frozen funds or broken incentives.

You can check out my full audit and the PoC code on my GitHub: rdin777/starknet-staking_audit1

starknet #cairo #security #blockchain #smartcontracts

Top comments (0)