IncidentsSmartAlert7 min read

Ronin Bridge hack, August 2024: the vote threshold was 0 for 48 minutes before the first withdrawal, and no event said so

How the August 2024 Ronin bridge hack happened: an upgrade set the vote threshold to 0, the exploit step by step, and the alert that fires 45 minutes early.

On August 6, 2024 the Ronin team upgraded its bridge on Ethereum. 48 minutes later an MEV bot withdrew about 4,000 ETH from it, and half an hour after that another address took 2M USDC. In total about $12M left. The bot belonged to a whitehat, and the funds came back the same day123.

Most bridge hacks start with a stolen key. This one did not. Nobody broke in, and nothing unusual was emitted. The upgrade was the team's own, and the Upgraded event was expected. What broke was a number inside the contract, and only a question to the contract could show it.

How the Ronin bridge approves a withdrawal

Ronin is the blockchain behind Axie Infinity. Its bridge to Ethereum has two parts that matter here23:

  • The gateway 0x6419…AF08 on Ethereum holds the bridged ETH, USDC and other tokens. It is a proxy: the address stays, the code behind it can be replaced.
  • Bridge operators watch Ronin. When a user withdraws to Ethereum, operators sign the withdrawal. Each operator has a vote weight.

The gateway pays out only when the signers' total weight reaches a threshold. The view function minimumVoteWeight() returns it. Before August 6 it returned 1540, 70% of the operators' total weight34.

The gateway also limits how much one withdrawal may take, about 4,000 ETH or 2M USDC2. Keep that in mind. It capped the size of each withdrawal.

Changes to the gateway go through MainchainBridgeManager 0xa714…fab0: a proposal needs operator signatures, then anyone can relay it on-chain. For emergencies there is a separate PauseEnforcer 0xe514…bcd4 that can stop the gateway in one call.

What went wrong in the upgrade

An upgradeable contract keeps its state in the proxy. New code often needs new state, so it ships with an initializer that fills it. The gateway had several, numbered by version. Version 3 set _totalOperatorWeight, the total weight the threshold is computed from. Version 4 set a new helper contract for WETH23.

The upgrade on August 6 jumped straight to version 4. The proxy had never run version 3. So _totalOperatorWeight stayed 0, and 70% of 0 is 03.

The upgrade logs show this. They contain Upgraded and Initialized(4), but no Initialized(3)4.

The attack, step by step

All times UTC, block times from Ethereum. We read every transaction below on-chain ourselves4.

08:48:47 — the planned upgrade

Address 0xe880…f059 relayed proposal round 5 to MainchainBridgeManager with 16 operator signatures (0x855d…767e, block 20468438). The proposal made six calls to the gateway. Among them: add WBTC, switch the code to 0xfc27…20Ee and run initializeV4, then change the proxy admin. The last two calls failed, the first four succeeded4.

We read minimumVoteWeight() with eth_call on archive blocks4:

20468437
Whenthe block before the upgrade
minimumVoteWeight()1540
20468438
When08:48:47, the upgrade
minimumVoteWeight()0
20468678
Whenthe block before the first withdrawal
minimumVoteWeight()0

From this block, a withdrawal from the gateway needed no operator signatures at all. Anyone who noticed could take up to the per-withdrawal limit, as many times as they liked.

09:37:23 — 3,996 ETH

Address 0x1a56…3cc1 called its MEV contract 0x4Ab1…3c0B, which called submitWithdrawal on the gateway. The gateway paid out 3,996.09 ETH (0x2619…a6cb, block 20468679)4. This was 48 minutes after the upgrade.

10:11:47 — 2M USDC

Address 0xFDe0…455A withdrew 1,998,046.88 USDC to 0x6980…BDD0 (0xbce5…0ad8, block 20468848)4.

10:15:23 — the pause

Address 0x8B35…8f57 called triggerPause() on the PauseEnforcer (0x90a1…c090, block 20468866)4. That is 38 minutes after the first withdrawal and 86 minutes after the upgrade. Ronin said it paused the bridge about 40 minutes after the first withdrawal1.

After — the money came back

The whitehat behind the MEV bot returned the ETH, then the USDC about two hours later. Ronin paid a $500K bounty. According to The Block, the bridge held more than $850M at the time16. The per-withdrawal limit capped each transaction at about 4,000 ETH or 2M USDC2. SEAL 911 helped with the response3.

What no rule would have changed

How to set up this rule in SmartAlert

  1. Add the contract and open it. Contract-state rules are on Pro and above.
  2. Create a "Contract state" rule. Pick the function, for example minimumVoteWeight(), and press "Check function" to see the current answer.
  3. Choose the condition "Below threshold" with a threshold under the normal value, for example 1. For a bridge, call every minute.
  4. Create it before your next upgrade. Then the first call after the upgrade shows whether the number survived.
  5. Add the event rules too. Upgraded tells you when the code changed, the state rule tells you what it changed. For native ETH, add the gateway in "Addresses" and create a "Total address balance in $ below threshold" rule.
  6. Choose where alerts go. Telegram is free.

See all alert recipes.

Moving off OpenZeppelin Defender? See how each Defender monitor maps to a SmartAlert rule and the Defender comparison.

Sources

  1. 1.Ronin Network on X, incident statement: pause, about 40 minutes, the whitehat. ↩
  2. 2.Rekt, Ronin Network — Rekt II: the skipped initializer, the per-withdrawal limit, the amounts. ↩
  3. 3.Three Sigma, analysis of the Ronin Network exploit: initializeV3 and _totalOperatorWeight, the 70% threshold, SEAL 911. ↩
  4. 4.Checked by SmartAlert on-chain: minimumVoteWeight() with archive eth_call at blocks 20468437, 20468438 and 20468678; the upgrade transaction, its six calls, their results and its logs (Upgraded, Initialized(4), no Initialized(3)); the two withdrawals and the pause, with block times, senders and amounts, through the Blockscout API. ↩
  5. 5.SmartAlert, alert delay on Ethereum: 12 confirmations (about 144 s), head polling up to 12 s, storage up to 15 s (P95), rule check every 60 s on Pro. ↩
  6. 6.The Block, report on the Ronin bridge pause: returned funds, the $500K bounty, more than $850M in the bridge. ↩
  7. 7.CertiK, Bybit incident technical analysis. ↩
  8. 8.KiloEx, Security incident root cause analysis. ↩

Try it free

An address is enough. The first 14 days get Pro limits, and rules are checked on history before they go live.