Three words get used for the same event and they describe three different events. A rug pull is the people who control a token or a protocol taking the value out of it. An exploit is somebody with no special standing making the code do something its authors never intended, without breaking a rule the code enforces. A hack is access nobody granted, which in practice means a key or an account somebody else came to control. Which one it was is settled by three questions, and the transaction answers all three.
A shop till is short at close. Either somebody forced it, or somebody with a key opened it, or the till rang every item through exactly as programmed and the price file was wrong. Three findings, three different fixes, and it is the till roll that decides which, not the argument in the staff room.
On chain the till roll is public, permanent, and rarely read.
Contents
- The four labels, and what each looks like in a transaction
- One incident, two words, in the same announcement
- Three questions, and the chain answers all three
- Reading one: 173,600 ETH out of a bridge, and nothing broke
- Six incidents, read the same way
- The case a transaction cannot settle on its own
- The label decides what is actually in scope
- If it is your protocol, settle the label before you publish
- Frequently asked questions
The four labels, and what each looks like in a transaction {#four-labels}
This is the table to lift. The third column is the one no glossary gives you.
| The label | What it means | What it looks like on chain |
|---|---|---|
| Rug pull | The people who control the token or the protocol take the value out of it, using powers they legitimately held | A successful, ordinary call by an address the protocol itself privileged, or by a holder doing something they were always able to do. Frequently indistinguishable from routine treasury or liquidity management |
| Exploit | Somebody with no special standing makes the code do something its authors never intended, without breaking any rule the code enforces | A successful call from an address with no role, no ownership and no prior relationship, using functions anyone could have called |
| Hack | Access nobody granted. A key, a signer, a server, a CI pipeline or a publishing account | A valid, correctly signed transaction the code had no reason to reject. Nothing reverts, because nothing is wrong with the contract |
| Neither | The contract was never involved. The victim signed the transfer themselves after being shown something false | A voluntary transfer or token approval from the victim’s own address, with the protocol appearing nowhere in the trace |
Two notes before the method. “Hack” in ordinary speech covers all four, which is why it is close to useless in a post-mortem. And the fourth row is not a technicality: it is a growing share of what gets reported as a protocol being hacked, and it is the one case where auditing the contract harder would have changed nothing at all.
One incident, two words, in the same announcement {#two-words}
The clearest evidence that the vocabulary is broken is that the people writing the disclosures cannot hold it steady either.
On 29 March 2022, Sky Mavis published Community Alert: Ronin Validators Compromised. Its first key point reads: “The Ronin bridge has been exploited for 173,600 Ethereum and 25.5M USDC.” An update added the following day reads: “at this point we are certain that this was an external breach. All evidence points to this attack being socially engineered, rather than a technical flaw.”
Exploited, and not a technical flaw, in one document. Both sentences are honest. They are simply using “exploited” to mean “we lost money” while the technical finding was the opposite.
That is not pedantry, because the two readings send you to different places. If the bridge contract had a flaw, you re-audit the bridge. If five private keys were in the wrong hands, you can audit that contract for a year and find nothing.
Three questions, and the chain answers all three {#three-questions}
Every incident post-mortem worth reading answers the same three questions, in this order.
| The question | Where you read it | What the answer tells you |
|---|---|---|
| Who signed it, and had the protocol given that address anything? | The from field of the transaction, then the contract’s own ownership, role and admin events across its whole history |
A privileged address points at the team, or at whoever is holding the team’s keys. An address with no standing at all points at an exploit |
| Did anything the code enforces actually fail? | The receipt’s status, and whether any require, guard or signature check rejected anything on the way through |
A successful transaction that broke no check means the code permitted it. So the thing that went wrong was outside the code: the design, the keys, or the assumptions |
| What changed shortly before it? | The contract’s deployment and upgrade history, plus any parameter, role or ownership change in the days beforehand | An upgrade, a role grant or a fresh deployment just before is the tell that the target moved rather than that the attacker got cleverer |
A fourth question is worth asking and the chain cannot answer it: what was said off chain? A promise that liquidity is locked, or that a key was burned, lives in an announcement rather than in a contract. It is very often the thing that turns a lawful-looking transaction into fraud. Hold that thought until the Game Coin case below.
Read in this order it takes minutes rather than days, because a block explorer already shows you the from field, the status and the event logs on one screen, and the contract’s history on another.
Reading one: 173,600 ETH out of a bridge, and nothing broke {#reading-one}
Sky Mavis’s alert links the two withdrawals it is describing, so you can open the first one rather than take anyone’s word for it. Both were read from a public node while writing this.
curl -s -X POST https://rpc.mevblocker.io -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":[
"0xc28fad5e8d5e0ce6a2eaf67b6687be5d58113e16be590824d6cfa1a94467d0b7"],"id":1}'
# from 0x098b716b8aaf21512996dc57eb0615e2383e2f96
# to 0x1a2a1c938ce3ec39b6d47113c7955baa9dd454f2
# block 14442835 (2022-03-23T13:29:09Z)
# input 0x993e1c42 followed by 17 words
The from address is the one Sky Mavis names in the same post as the wallet holding the funds. The to address is the bridge. Decoding the 17 words after the selector gives the whole instruction:
| Word | Value | What it is |
|---|---|---|
| 0 | 2000000 |
the withdrawal id |
| 1 | 0x098b716b8aaf21512996dc57eb0615e2383e2f96 |
the recipient |
| 2 | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 |
the token, which is WETH |
| 3 | 173600000000000000000000 |
the amount, 173,600 at eighteen decimals |
| 4 and 5 | an offset, then a length of 330 |
the block of validator signatures that follows |
| 6 to 16 | 330 bytes | the signatures themselves |
Then the receipt, which is where the argument is won: status is 0x1. The transaction succeeded. Its two logs show WETH being unwrapped for exactly 173,600 and the bridge emitting its own withdrawal event for the same figure. Nothing reverted. No guard fired. The signature check ran and passed.
The second withdrawal landed in block 14442840, one minute and fifty-five seconds later, and moved 25500000000000 USDC units, which at six decimals is 25.5 million. Sky Mavis’s published figures match the chain to the unit.
Now run the three questions. Who signed it: an address the bridge was willing to accept, because the calldata carried signatures it recognised. Sky Mavis states the threshold plainly: “In order to recognize a Deposit event or a Withdrawal event, five out of the nine validator signatures are needed”, and later, “We have confirmed that the signature in the malicious withdrawals match up with the five suspected validators.” Did anything fail: no. What changed before it: nothing on chain. What changed was off chain, and the alert says so: “The attacker used hacked private keys in order to forge fake withdrawals.”
One more detail, and it is the reason the label matters. Both transactions are dated 23 March. The alert is dated 29 March, and says the team “discovered the attack this morning after a report from a user being unable to withdraw 5k ETH from the bridge”. The evidence sat in public for six days. Nobody was reading it, which is the entire argument for post-deployment monitoring tuned to a protocol’s own logic rather than to generic thresholds.
Six incidents, read the same way {#six-incidents}
Each row below is described in the words of whoever published the post-mortem, and each links to it.
| Incident | Who signed | Did the code’s own checks fail? | The honest label | Primary source |
|---|---|---|---|---|
| Ronin bridge, March 2022 | An address carrying five valid validator signatures | No. The threshold was met and the withdrawal succeeded | Key compromise. “socially engineered, rather than a technical flaw” | Sky Mavis community alert |
| Beanstalk, April 2022 | An ordinary address holding no prior stake | No. Governance ran, and Beanstalk’s own machinery moved the funds | Exploit, through the protocol’s own decision process. “The perpetrator used a flash loan to exploit the protocol’s governance mechanism and send the funds to a wallet they controlled” | Beanstalk Farms |
| Euler, March 2023 | An ordinary address calling public functions | No. Every call was permitted, and one required check did not exist | Exploit, code flaw. The function “lacked a health check to make sure that an existing user wouldn’t be able to put themselves in an unhealthy state just by donating to the reserves” | Euler Labs |
| Curve pools, July 2023 | An ordinary address re-entering a guarded function | No, and this is the interesting one: the guard was present in the source and did not work in the bytecode | Exploit, toolchain. “an improperly implemented re-entrancy guard that could be bypassed” in Vyper 0.2.15, 0.2.16 and 0.3.0 |
Vyper post-mortem, GHSA-5824-cm3x-3c38 |
| Game Coin (GME), alleged, SEC filed January 2025 | An address holding LP tokens it was entitled to hold | No. Withdrawing liquidity you hold is what the pool is for | Rug pull, alleged, settled without admission. “Zhu kept these LP tokens unlocked and used them to engage in a rug pull” | SEC Litigation Release 26223 |
npm debug, September 2025 |
The victims themselves | Not applicable. No protocol contract was involved | Neither. A publishing account was taken over and the payload rewrote addresses in the browser | GHSA-4×49-vf9v-38px |
Three of those six are not defects in anybody’s contract. A fourth was a defect no reader of the contract source could have found. The Vyper team’s own report puts the flaw in the compiler, and adds that “the impact to protocols using the vulnerable compilers was not realized at the time and they were not explicitly notified”. Euler’s account of its own incident is worth reading twice as well: the flawed function arrived as the fix for an earlier bug, and sat unnoticed until March 2023.
The npm case is the fourth label in practice, and Fidesium’s write-up of how that payload swapped wallet addresses in the browser covers the mechanism. The advisory’s own summary is the whole category in one sentence: a version was published “functionally identical to the previous patch version, but with a malware payload added attempting to redirect cryptocurrency transactions to the attacker’s own addresses from within browser environments”.
The case a transaction cannot settle on its own {#cannot-settle}
Being honest about the limit is more useful than another checklist, so here is the limit.
In January 2025 the SEC charged a blockchain engineer over the Game Coin token, in a matter it titled “SEC Charges New York Blockchain Engineer with Perpetrating ‘Rug Pull’ Fraud”. Read what the litigation release actually alleges the transaction looked like: “absent safeguards, the holders of LP tokens can, without warning, withdraw liquidity from a liquidity pool, sell significant amounts of crypto assets into the pool and cause losses to investors. Such trading behavior is commonly known in the crypto asset industry as a ‘rug pull.'”
Nothing in that description is a technical anomaly. A holder of LP tokens withdrew liquidity. On chain that is one of the most ordinary events there is.
What the Commission alleges made it fraud is off chain: that “Game Coin and its founders represented to investors in publicly-available social media posts that ‘liquidity’ was ‘locked'”, while certain LP tokens sat at an address under one person’s exclusive control, and that he “kept these LP tokens unlocked and used them to engage in a rug pull”, misappropriating “crypto assets worth approximately $553,000”. These are allegations. The release records that “Without admitting or denying the allegations in the SEC’s complaint, Zhu agreed to pay disgorgement and prejudgment interest of $672,992, and a civil penalty of $150,000”, subject to court approval.
So the transaction tells you what happened and who was able to do it. It does not tell you what anybody promised. That half is read out of announcements, documentation and, in the end, filings, and it is the half a scanner will never give you. Whether a team is trustworthy is a different judgement from whether a transaction was lawful, and an older post on when a rug pull is not a rug pull takes that side of it.
The label decides what is actually in scope {#in-scope}
Which is why getting it right is not a naming exercise.
| The finding | Would re-auditing the contract help? | What is actually in scope |
|---|---|---|
| Key or signer compromise | No. There was nothing wrong with the code | Key custody, thresholds, allowlists that were never revoked, and every machine that can sign |
| Code flaw | Yes, and so does re-auditing everything sharing the pattern | The fix, its own review, and every other contract built on the same assumption |
| The rules working as written | Partly. The design is the defect, not the implementation | Governance parameters, quorum, timelocks, and whether voting power can be borrowed for one block |
| Toolchain | Not on its own | Every contract compiled with the affected version, not only the one that lost money |
| Interface or supply chain | No | The build pipeline, dependency pinning, publishing accounts, and what a signer is actually shown before approving |
| Rug pull | No | Who held which keys, what was promised in public, and whether either was ever verifiable |
One of those six rows is answered by a code review, two are answered in part, and three are not answered by it at all. That is the honest shape of this work, and it is why continuous scanning on every commit, post-deployment monitoring and penetration testing are different products rather than one product with three names.
If it is your protocol, settle the label before you publish {#your-protocol}
The first disclosure sets the story, and a vague one costs more than a slow one. Five things worth doing before you post.
- Publish the transaction hashes. Everyone competent will find them anyway, and being the source of them is worth more than the hour it buys you.
- Say what did not fail, explicitly. Sky Mavis writing “rather than a technical flaw” is what stopped a week of speculation about the bridge contract.
- Separate what you know from what you infer. Beanstalk described the mechanism and called the cause “a previously-unknown issue with Beanstalk’s governance process”, which is precise about the boundary of what had been established.
- State the blast radius by cause, not by contract. If the cause is a compiler version or a dependency, the scope is every deployment carrying it.
- Never use “exploit” to mean “we lost money”. It has a technical meaning and this audience holds you to it.
None of that makes a protocol safe, and nothing does. What it does is make the next decision a decision rather than a guess. It is also why every audit we run ends on the portfolio page instead of in one client’s inbox: 23 reports covering 16 protocols across EVM chains and Solana, each written to chain so somebody deciding whether to hire us can verify the record instead of taking our word for it.
If the code half needs doing properly, that is what our manual smart contract audits are for. The core team reads the contracts line by line rather than handing them to a contractor with a scanner, on any chain and with the deepest work on EVM, and the review takes in economic and logic risk rather than implementation bugs alone. Fix verification is included twice over, pricing starts at $5,000, and the report is published. A report describes one commit, which is why it has a shelf life, and why the more useful exercise before an incident is attacking your own protocol first.
Frequently asked questions {#faq}
What is the difference between a rug pull and a hack?
A rug pull is done with powers the people behind the project legitimately held: a key they kept, LP tokens they owned, an admin role they never gave up. A hack is done with access nobody granted them. The practical difference on chain is that a rug pull usually needs no anomaly at all, because the transaction is something the actor was always permitted to do, while a hack means asking how the signer came to hold a key or a credential it should not have had.
Is an exploit the same thing as a hack?
No, although the press uses them interchangeably and so do a lot of disclosures. An exploit means the code did what it was written to do and what it was written to do was wrong, so an ordinary address with no special standing could take value out of it. A hack means the code was fine and the access was not. Sky Mavis’s Ronin alert contains both readings in one document: its first line says the bridge “has been exploited”, and an update the next day says the evidence points to the attack “being socially engineered, rather than a technical flaw”.
How can you tell a rug pull from an exploit on-chain?
Look at the caller’s standing before the transaction, not at the transaction. Read the contract’s ownership, admin and role history: if the signing address had been granted something by the protocol, or held LP tokens or a privileged position, you are looking at somebody using a power they had, which is the rug pull shape. If the signing address had nothing and the call still succeeded, the code permitted something it should not have, which is the exploit shape. The receipt’s status matters too, because a successful transaction that broke no check means nothing in the code failed.
Can a transaction prove that an incident was an inside job?
No. It can prove who signed, what they were able to do and whether anything was bypassed, and it cannot prove who held the key or what anyone promised. The SEC’s Game Coin case turns on exactly that gap: the alleged rug pull was an ordinary withdrawal of liquidity by a holder of LP tokens, and what the Commission alleges made it fraud is that the project “represented to investors in publicly-available social media posts that ‘liquidity’ was ‘locked'” while those tokens sat unlocked under one person’s control. That half is read out of announcements and filings, not out of a receipt.
Was it still an exploit if the code did exactly what it was written to do?
Yes, and most of them are. Beanstalk was attacked in April 2022 through its own governance mechanism, and its post-mortem describes a perpetrator who “used a flash loan to exploit the protocol’s governance mechanism and send the funds to a wallet they controlled”. No access control was bypassed. Euler’s March 2023 exploit is the same principle in a different place: by Euler’s own account the function “lacked a health check”, so every individual call was permitted. The defect is in what the rules allow, not in whether they were followed.
What should a protocol publish after an incident?
The transaction hashes, the mechanism, and an explicit statement of what did not fail, because that is the sentence that stops a week of speculation about the wrong component. State the blast radius by cause rather than by contract: if the cause was a compiler version or a compromised dependency, the scope is every deployment carrying it, which is what the Vyper team’s own report was for after the July 2023 Curve incidents. And separate what has been established from what is still inference.
Does an audit tell you what happened after an incident?
Only where the cause is a code defect, which on the evidence here is about half the time. An audit reviews specific code at a specific commit, so it can establish whether a function permitted what the transaction did and whether the deployed bytecode matches what was reviewed. It cannot tell you who was holding a private key, whether a publishing account was taken over, or what was promised on social media. Reading the transaction first tells you which kind of question you are actually asking.



