Crypto Training
Gas Griefing and Untrusted Reverts: Liveness Is a Security Property
Most protocol failures aren’t about stealing funds. They’re about stopping progress. This is the gas-side of that story.
A lot of audits over-focus on “can funds be stolen?”
The uncomfortable truth: many mainnet failures look like this instead:
users can’t withdraw keepers can’t settle liquidations don’t happen queues jam
No one needed a clever exploit. Someone just made progress too expensive or too fragile.
A minimal model: who can veto progress?#
Pick your protocol’s liveness-critical functions. Usually it’s one of:
withdraw()settle()liquidate()finalize()
Now list everything they call that isn’t purely internal. Each of those is a potential veto.
Here’s a useful table for reviews:
| External interaction | How it fails | Typical impact |
|---|---|---|
| token transfer | revert / hook / gas burn | withdrawals jam |
| ETH send | revert fallback | claims jam |
| oracle read | revert / stale | liquidations freeze |
| callback hook | revert / reenter | accounting breaks or halts |
One concrete footgun: “push ETH or revert”#
(bool ok,) = payable(user).call{value: amount}("");
require(ok, "send failed");
If user is a contract that reverts, your function reverts.
If withdraw() requires this send, users can be griefed.
A safer pattern is pull:
- record what’s owed
- let the user withdraw separately
Gas griefing as an economic attack#
Gas griefing is when the attacker turns your protocol into a slot machine:
- they can cause honest actors to waste gas repeatedly
- the honest actors stop trying
- liveness becomes optional
It’s not glamorous. It’s effective.
Design rule#
If a function must be live, it must have a bounded-work path.
That often means:
- chunked processing
- escape hatches
- optional hooks
- “best effort” calls with safe accounting
If you can’t bound the work, you can’t guarantee liveness.
Further reading#
- EVM opcode and gas reference: https://www.evm.codes/