Top 5 Mistakes in Move Smart Contracts
Writing in Move doesn’t make you immune to bugs.
After 100s of audits, we’ve seen certain mistakes appear again & again in move contracts.
Here are the top 5 bugs we keep reporting & how you can avoid them.
1. Lack of generic type checks
Move lets you pass generic types to public functions — but if you don’t check them, attackers can break logic.
Example: in cancel_order<T>, if you don’t check that T matches the stored type, users can withdraw coins they never deposited.
Fix: assert type_of<T>() == expected_type_info
2. Improper access control
Just because someone has a &signer doesn't mean they should be allowed to do things.
We’ve seen cancel_order() functions that don’t check if the signer actually owns the order.
The result? anyone can cancel anyone’s order.
Fix: assert address_of(user) == order.user_address
3. Unbounded execution (loop bombs)
Got a while loop that iterates over all open orders?
It’s a denial-of-service attack waiting to happen.
An attacker can fill the vector, then block swaps, liquidity adds, or order cancels — all by making the loop run out of gas.
Fix: avoid unbounded loops, add max iteration caps
4. Rounding errors = free trades
Move uses integers — so (small_number * fee_bps / 10000) can easily become zero.
We’ve seen protocols where small swaps bypass fees altogether.
Attackers just split large trades into tiny ones. sneaky, right?
Fix: Enforce min trade sizes or non-zero fees
5. Price oracles that aren’t oracles
If you're using token ratios as prices, you're vulnerable.
We audited an AMM that used its pool ratio as the “oracle” — the attacker manipulated it, then withdrew $ for free.
Fix: Use real price feeds. average over time. Don’t trust ratios.
These 5 bugs are just the tip.
Move helps reduce risk — but you still need to write, review, and test like it matters.
Until then, check your code for these mistakes.
Your users will thank you.