On this page
An alert fires. A balance that should move slowly moved fast, a function that is never called was called, a pool’s reserve is not what it was a block ago. Someone has to decide what to do in the next few minutes, and the decision will be made under the worst possible conditions: incomplete information, a clock that is running, and an attacker who may not be finished.
The advice below is the on-chain half of that hour, in order. It assumes a protocol with some ability to pause, some ability to upgrade, and at least one person who can read a transaction. The off-chain half, the comms, the legal calls, the exchange outreach, matters as much and is a different article.
Minute 0 to 5: decide whether it is real
The first question is whether the alert is an exploit or a large legitimate action, and the way to answer it is to look at the transaction, not the balance. Open the transaction that moved the money and read its call trace. Three things distinguish an exploit from a whale in seconds:
- The caller. A fresh address funded minutes ago from a mixer or a bridge, with no history, is an attacker until proven otherwise. A known market maker’s address is probably not.
- The shape. Flash loan in, several contracts touched, flash loan out, profit to the caller: that is an exploit. A single deposit or withdrawal is not.
- The contract’s view of it. If the transaction succeeded through a path you did not know existed, or a path that should have reverted, it is an exploit.
If it is real, the rest of this article applies. If you cannot tell in five minutes, treat it as real; the cost of a false pause is embarrassment, and the cost of a missed one is the treasury.
Minute 5 to 15: stop the bleeding
Pause what you can, in this order:
- The function that was exploited. If the protocol has per-function pausing, pause the exact entry point. This stops a repeat without stopping everything.
- Anything that shares the vulnerable logic. An exploit in
withdrawusually meansemergencyWithdrawandredeemhave the same bug. Pause the family, not the instance. - The whole protocol, if you cannot be sure the first two cover it. A global pause loses you an hour of user goodwill. Not pausing loses you the rest of the funds.
Two things to know before you reach for the pause. First, who can call it, and are they awake? A pause behind a multisig with signers in three time zones is not a pause you can execute in ten minutes; that is a finding for your next audit, but right now it means your fastest lever is whichever signer answers. Second, whether pausing creates a new problem: some designs make a paused vault impossible to withdraw from, which turns a partial exploit into a full lock-up. Know your own pause semantics before you need them.
Do not upgrade the contract in this window. An upgrade written in fifteen minutes by a frightened engineer has a meaningful chance of making things worse, and an upgrade is the one action that cannot be reversed by un-pausing.
Minute 15 to 30: capture the state
Once the bleeding has stopped, the priority is evidence, because in an hour the chain will have moved on and the attacker’s addresses will have been emptied.
- Record the block number of the first exploit transaction and the block before it. Everything you reconstruct later is “state at block N-1 versus state at block N”.
- Fork at N-1. Spin up a fork of mainnet at the block before the exploit. This is the single most useful artefact of the hour, because it lets you replay the attack transaction against the exact pre-exploit state as many times as you need, and it lets you test a fix against the real attack before deploying it.
- Enumerate the attacker’s addresses. The caller, the contract it deployed (most exploits run through a throwaway contract), the address the profit went to, and the address that funded the caller. Write them down with the block at which each first appeared.
- Snapshot the balances. Every token the protocol holds, in every contract, at N-1 and now. The difference is the loss, and you will be asked for that number many times.
- Save the transaction traces. Not the explorer’s rendering, the raw trace: every internal call with its calldata and return data. Explorers prune, change, and go down.
If you have a case or investigation tool, open one now and put all of this in it. If you do not, a shared document with timestamps is fine. The point is that in three hours, six people will be asking the same questions, and the answers should be in one place.
Minute 30 to 60: understand the mechanism
With the fork and the trace, you can now read the attack properly, and the goal is one sentence: “the attacker did X, which the contract allowed because Y”. Until you have that sentence, you do not know whether your pause covers it, whether a fix is possible, or what to tell users.
Reading a trace for mechanism:
- Find the call that moved value out of your contract. Work backwards from it. What condition let it through?
- Find every read of state that fed that condition. Was the state what you expected? If not, which earlier call in the trace changed it?
- Find the first call in the trace that is not something an honest user would do. That is the entry, and the distance between the entry and the value-moving call is the attack.
Decompiling the attacker’s contract helps here, because attack contracts are usually small and their logic is the attack. The recovered code will show the sequence of external calls, and the sequence is the exploit in a form you can read top to bottom. It is faster than reconstructing it from the trace alone.
By the end of the hour you should be able to say which finding class this was (reentrancy, oracle manipulation, an access-control hole, a precision error), whether the exploit is repeatable against the paused state, and whether other deployed copies of the same code are exposed. That last point is easy to forget: if the vulnerable contract was forked from something, or something was forked from it, other teams need the call you are about to make.
What not to do
Do not negotiate from your main address. If you are going to message the attacker on-chain, and it is often worth trying, do it from a fresh address, and say only what you would be comfortable reading in a court filing.
Do not announce a number you have not verified. The loss figure will be quoted for years. Compute it from the balance snapshots, not from the first estimate.
Do not deploy a fix you have not run against the fork. Replay the attack transaction against the patched contract on the fork. If it still succeeds, the patch is wrong. If it reverts, check that legitimate transactions from the same block still succeed. This takes twenty minutes and it is the difference between a post-mortem and two.
Do not un-pause because it is quiet. Quiet means the attacker got what they came for or is waiting. Un-pause when you can explain the mechanism and have closed it, not before.
After the hour
Everything above is about the on-chain minutes. What follows, the disclosure, the recovery attempts, the audit of the fix, the retrospective on why the alert fired sixty seconds after the exploit rather than sixty seconds before, is slower and better documented elsewhere. But the retrospective has one question worth previewing: what signal existed before the exploit transaction that could have paged someone? Usually there is one. A never-called selector being called, a large approval to a fresh contract, a reserve drifting from its oracle, a flash-loan-sized inflow to a pool that never sees them. Monitoring that watches for the shape of an attack rather than for its aftermath is what turns this article from the first hour after into the first hour before.