return vs abort Silently Corrupts State in MoveA real bug from Aptos core's trading engine that permanently deleted orders on an "error" path. The root cause? A return where there should have been an abort. This pattern applies to both Aptos and Sui Move.
In Move, abort and return behave very differently when state has been mutated. Most developers treat return as a safe "exit on error" — it's not. This confusion is a real bug class, and it showed up in Aptos core's order book.
If you mutate state and then return on a failure path, that mutation is permanent. The function exits, the transaction completes, and the corrupted state lives on-chain forever.
This was found in place_bulk_order in Aptos's experimental order book module during an audit by OtterSec (finding OS-ADP-ADV-05, Medium severity). Here's the flow:
The sequence:
self.orders — state is now mutatedreturn with a rejection responseThe order is permanently deleted. No new order replaces it. The order book is now in a state that can cause aborts in subsequent operations. All because return was used where abort should have been.
Two approaches, both valid:
return with abort. The transaction reverts and the original order survives.
The Aptos team resolved this in PR #17959.
This isn't an Aptos-specific bug. The abort vs return semantics are baked into the Move VM itself. Both Aptos and Sui inherit this behavior.
The difference is how state mutation looks on each chain:
move_to, move_from, borrow_global_mut.return after mutation = committed.
&mut parameters.return after mutation = committed.
The surface looks different, but the underlying principle is identical. If you audit Sui Move, look for functions that take &mut object references, mutate them, and then use return instead of abort on error paths.
What to grep for: Any function that mutates state (removes, inserts, updates) before a conditional check that uses return instead of abort on failure. That's your bug. Every time.
return after a mutation as suspicious. In Move, return commits all changes. It's a normal function exit, not a rollback.abort when state is already mutated. If you've already changed state and hit an error condition, abort is your only real rollback mechanism.abort and return the same way on both chains. The object model differs, but the bug class is identical.In Move, return is a commitment — not a safety net. Audit accordingly. 🔍