<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aleksei Nazarov</title>
    <description>The latest articles on DEV Community by Aleksei Nazarov (@smartalert_alex).</description>
    <link>https://dev.to/smartalert_alex</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4203476%2F5ab0eec1-1e22-4c59-a8e2-cb8722571b71.png</url>
      <title>DEV Community: Aleksei Nazarov</title>
      <link>https://dev.to/smartalert_alex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smartalert_alex"/>
    <language>en</language>
    <item>
      <title>Smart contract monitoring in 5 minutes: what to watch and how to set it up</title>
      <dc:creator>Aleksei Nazarov</dc:creator>
      <pubDate>Sun, 25 Oct 2026 20:22:04 +0000</pubDate>
      <link>https://dev.to/smartalert_alex/smart-contract-monitoring-in-5-minutes-what-to-watch-and-how-to-set-it-up-14mg</link>
      <guid>https://dev.to/smartalert_alex/smart-contract-monitoring-in-5-minutes-what-to-watch-and-how-to-set-it-up-14mg</guid>
      <description>&lt;p&gt;A contract goes live after an audit. The audit checked the code on one day. From then on, the contract changes owner, gets upgraded, moves money and talks to bots, and somebody has to notice when one of those things is not supposed to happen.&lt;/p&gt;

&lt;p&gt;Most small teams notice from users, from a block explorer tab left open, or from a post on X. &lt;a href="https://smart-alert.io/?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;SmartAlert&lt;/a&gt; is a monitor for that gap: you paste a contract address, and it tells your team in Telegram, Slack or Discord when something on the contract is off.&lt;/p&gt;

&lt;p&gt;This post covers what it watches, and then takes a contract from "nobody is watching" to working alerts in about five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it watches
&lt;/h2&gt;

&lt;p&gt;SmartAlert reads public chain data on &lt;strong&gt;Ethereum, BNB Chain, Base and Arbitrum&lt;/strong&gt;. It does not touch your contract and needs no code change.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Admin changes.&lt;/strong&gt; Ownership transferred, a role granted or revoked, a proxy upgraded, the proxy admin changed, Safe owners or threshold changed, the contract paused. These are the first on-chain signs of most takeovers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Money, in dollars.&lt;/strong&gt; A transfer above $X, priced at the block and optionally filtered by sender. The total value of a treasury below a floor, including when a price drop crosses it. Prices come from Chainlink, with CoinGecko as a fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations.&lt;/strong&gt; A keeper or relayer wallet with gas for fewer than N days. A balance that is low and got no top-up in an hour. Failed transactions from your bots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activity.&lt;/strong&gt; Any event count or value per hour: transfers, swaps, deposits, claims. Spikes, and a contract that suddenly goes quiet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State.&lt;/strong&gt; Values read through view functions, such as &lt;code&gt;paused()&lt;/code&gt; returning true.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things keep the alerts useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A norm for each hour.&lt;/strong&gt; Activity on most contracts is low at night and high during the day. One fixed threshold is either noisy at peak hours or silent at night. SmartAlert can compare each hour with the same hour on previous days instead, so a burst at 3 a.m. is caught and a busy afternoon is not an alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A check on history before a rule runs.&lt;/strong&gt; Every rule shows, on a chart of the contract's real past, where it would have fired. If it would have fired forty times last month, you see that before it wakes anyone up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You need a contract address on one of the four networks and a Telegram account. The free plan is enough; the first 14 days of a project work with Pro limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1. Add the contract (1 minute)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://smart-alert.io/go/sign-up?ref=article-combined&amp;amp;utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; and create a project.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Add contract&lt;/strong&gt;, pick the network and paste the address.&lt;/li&gt;
&lt;li&gt;For a verified contract, the ABI is loaded for you. For an unverified one, paste the ABI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If SmartAlert has not seen the contract before, it first collects its history. The contract shows a "backfilling" status, and rules wait until it is done. If the contract is already tracked, its history is there at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn6ia6cz2o0gjbe1emtqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn6ia6cz2o0gjbe1emtqr.png" alt="The project's contracts: network, address and indexing state" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2. Turn on the first rules (2 minutes)
&lt;/h3&gt;

&lt;p&gt;Open the contract. SmartAlert suggests rules from its ABI: ERC-20, ERC-721 and ERC-1967 proxies get matching templates. Select the ones that fit and choose &lt;strong&gt;Enable selected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you are not sure what to start with, these six cover the most common ways a contract gets taken over or drained:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/ownership-transferred?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Ownership transferred&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A new owner can call every admin function&lt;/td&gt;
&lt;td&gt;Any event, Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/role-granted?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Role granted&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A new admin on an &lt;code&gt;AccessControl&lt;/code&gt; contract&lt;/td&gt;
&lt;td&gt;Any event, Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/proxy-upgraded?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Proxy upgraded&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The code behind the address changed&lt;/td&gt;
&lt;td&gt;Any event, Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/large-transfer-usd?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Transfer above $X&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The drain itself, priced at the block&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;from&lt;/code&gt; = your contract, a threshold above normal flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/transfer-spike?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Unusual transfer activity&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Bursts that a fixed number misses at night&lt;/td&gt;
&lt;td&gt;Against the norm for each hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://smart-alert.io/recipes/contract-went-quiet?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Contract went quiet&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A broken integration or a stuck bot&lt;/td&gt;
&lt;td&gt;Against the norm for each hour&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you run keepers or relayers, add their wallets and a &lt;a href="https://smart-alert.io/recipes/keeper-gas-runway?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;gas runway&lt;/a&gt; rule. For a treasury, add its address and a &lt;a href="https://smart-alert.io/recipes/treasury-below-usd?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;total below $X&lt;/a&gt; rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3. Check each rule on history (1 minute)
&lt;/h3&gt;

&lt;p&gt;Open a rule and look at the chart. It shows the metric, the norm or your threshold, and marks where the rule would have fired.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmflzqsfu1pdk80vzhr76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmflzqsfu1pdk80vzhr76.png" alt="A rule on a 7-day chart with the norm band and a mark where it would have fired" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin rules should show zero or a few marks, each one a change you remember making.&lt;/li&gt;
&lt;li&gt;For value and activity rules, too many marks means the threshold is too low. Raise it, or set a target such as "no more than 2 a week" and let SmartAlert pick the threshold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;strong&gt;against the norm for each hour&lt;/strong&gt; (the default) for activity, and &lt;strong&gt;own threshold&lt;/strong&gt; for amounts in dollars and admin events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4. Connect Telegram (1 minute)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Channels&lt;/strong&gt;, enter a name and press &lt;strong&gt;Add&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the link SmartAlert gives you and press &lt;strong&gt;Start&lt;/strong&gt; in the bot. For a team chat, add the bot to the group and run the command there.&lt;/li&gt;
&lt;li&gt;The bot replies with the project name. The channel is active.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The link works for 15 minutes. On Pro you can also add Slack, Discord, email, PagerDuty or a webhook.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an alert looks like
&lt;/h3&gt;

&lt;p&gt;The message has the value, the threshold or this hour's norm, the time and a link to the transaction. When the condition clears, a "Back to normal" message follows, and PagerDuty incidents close on their own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff21f33ts1jmloyu65pdn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff21f33ts1jmloyu65pdn.png" alt="An alert with the value, this hour's norm and a chart" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not pause or block anything.&lt;/strong&gt; It alerts. Send Critical alerts to people who hold a pause key; an alert that reaches nobody who can act is a log entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not read the mempool.&lt;/strong&gt; It reads confirmed blocks. On Ethereum that is 12 confirmations, about 144 seconds, then a rule check: every 5 minutes on Free, every minute on Pro, every 30 seconds on Business. If an attacker drains a contract within two minutes of the first sign, the first loss is gone before any confirmed-block monitor can speak. Later steps of the same attack are not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not replace an audit.&lt;/strong&gt; It is the part that notices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repeat on every chain you are deployed on.&lt;/strong&gt; Attackers often go chain by chain, and the first chain's alert is the warning for the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch contracts you depend on, not only your own.&lt;/strong&gt; A locker, a bridge or a vault that holds your tokens is public, and you can watch it without anyone's permission.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Price
&lt;/h2&gt;

&lt;p&gt;$0 gets 2 contracts, 3 rules and a Telegram channel, checked every 5 minutes. Pro is $30 a month, Business $99.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://smart-alert.io/guides/alert-recipes?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Alert recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://smart-alert.io/guides/post-audit-monitoring?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;Post-audit monitoring checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://smart-alert.io/guides/proxy-contract-monitoring?utm_source=devto&amp;amp;utm_medium=owner&amp;amp;utm_campaign=article-combined" rel="noopener noreferrer"&gt;How to monitor an upgradeable proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I build SmartAlert. Questions about a specific contract are welcome in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>web3</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Ronin Bridge, August 2024: the vote threshold was 0 for 48 minutes before the first withdrawal, and no event said so</title>
      <dc:creator>Aleksei Nazarov</dc:creator>
      <pubDate>Fri, 23 Oct 2026 22:15:49 +0000</pubDate>
      <link>https://dev.to/smartalert_alex/ronin-bridge-august-2024-the-vote-threshold-was-0-for-48-minutes-before-the-first-withdrawal-and-2c3d</link>
      <guid>https://dev.to/smartalert_alex/ronin-bridge-august-2024-the-vote-threshold-was-0-for-48-minutes-before-the-first-withdrawal-and-2c3d</guid>
      <description>&lt;p&gt;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 day&lt;sup&gt;[1]&lt;/sup&gt;&lt;sup&gt;[2]&lt;/sup&gt;&lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;Upgraded&lt;/code&gt; event was expected. What broke was a number inside the contract, and only a question to the contract could show it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Ronin bridge approves a withdrawal
&lt;/h2&gt;

&lt;p&gt;Ronin is the blockchain behind Axie Infinity. Its bridge to Ethereum has two parts that matter here&lt;sup&gt;[2]&lt;/sup&gt;&lt;sup&gt;[3]&lt;/sup&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The gateway&lt;/strong&gt; &lt;a href="https://etherscan.io/address/0x64192819Ac13Ef72bF6b5AE239AC672B43a9AF08" rel="noopener noreferrer"&gt;&lt;code&gt;0x6419…AF08&lt;/code&gt;&lt;/a&gt; on Ethereum holds the bridged ETH, USDC and other tokens. It is a proxy: the address stays, the code behind it can be replaced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridge operators&lt;/strong&gt; watch Ronin. When a user withdraws to Ethereum, operators sign the withdrawal. Each operator has a vote weight.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gateway pays out only when the signers' total weight reaches a threshold. The view function &lt;code&gt;minimumVoteWeight()&lt;/code&gt; returns it. Before August 6 it returned &lt;strong&gt;1540&lt;/strong&gt;, 70% of the operators' total weight&lt;sup&gt;[3]&lt;/sup&gt;&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The gateway also limits how much one withdrawal may take, about 4,000 ETH or 2M USDC&lt;sup&gt;[2]&lt;/sup&gt;. Keep that in mind. It capped the size of each withdrawal.&lt;/p&gt;

&lt;p&gt;Changes to the gateway go through &lt;strong&gt;MainchainBridgeManager&lt;/strong&gt; &lt;a href="https://etherscan.io/address/0xa71456fA88a5f6a4696D0446E690Db4a5913fab0" rel="noopener noreferrer"&gt;&lt;code&gt;0xa714…fab0&lt;/code&gt;&lt;/a&gt;: a proposal needs operator signatures, then anyone can relay it on-chain. For emergencies there is a separate &lt;strong&gt;PauseEnforcer&lt;/strong&gt; &lt;a href="https://etherscan.io/address/0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4" rel="noopener noreferrer"&gt;&lt;code&gt;0xe514…bcd4&lt;/code&gt;&lt;/a&gt; that can stop the gateway in one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong in the upgrade
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;_totalOperatorWeight&lt;/code&gt;, the total weight the threshold is computed from. Version 4 set a new helper contract for WETH&lt;sup&gt;[2]&lt;/sup&gt;&lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The upgrade on August 6 jumped straight to version 4. The proxy had never run version 3. So &lt;code&gt;_totalOperatorWeight&lt;/code&gt; stayed 0, and 70% of 0 is 0&lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The upgrade logs show this. They contain &lt;code&gt;Upgraded&lt;/code&gt; and &lt;code&gt;Initialized(4)&lt;/code&gt;, but no &lt;code&gt;Initialized(3)&lt;/code&gt;&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack, step by step
&lt;/h2&gt;

&lt;p&gt;All times UTC, block times from Ethereum. We read every transaction below on-chain ourselves&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  08:48:47 — the planned upgrade
&lt;/h3&gt;

&lt;p&gt;Address &lt;code&gt;0xe880…f059&lt;/code&gt; relayed proposal round 5 to MainchainBridgeManager with 16 operator signatures (&lt;a href="https://etherscan.io/tx/0x855dd3b1194e3b889f4667b6a0996220e350e034d35d3eab29b4f23bc205767e" rel="noopener noreferrer"&gt;&lt;code&gt;0x855d…767e&lt;/code&gt;&lt;/a&gt;, block 20468438). The proposal made six calls to the gateway. Among them: add WBTC, switch the code to &lt;a href="https://etherscan.io/address/0xfc274EC92bBb1A1472884558d1B5CaaC6F8220Ee" rel="noopener noreferrer"&gt;&lt;code&gt;0xfc27…20Ee&lt;/code&gt;&lt;/a&gt; and run &lt;code&gt;initializeV4&lt;/code&gt;, then change the proxy admin. The last two calls failed, the first four succeeded&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;We read &lt;code&gt;minimumVoteWeight()&lt;/code&gt; with &lt;code&gt;eth_call&lt;/code&gt; on archive blocks&lt;sup&gt;[7]&lt;/sup&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;&lt;code&gt;minimumVoteWeight()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20468437&lt;/td&gt;
&lt;td&gt;the block before the upgrade&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1540&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20468438&lt;/td&gt;
&lt;td&gt;08:48:47, the upgrade&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20468678&lt;/td&gt;
&lt;td&gt;the block before the first withdrawal&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔔 SmartAlert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; "Contract state" on the gateway: call &lt;code&gt;minimumVoteWeight()&lt;/code&gt; every minute, fire when the answer is below 1. It is the same rule as the &lt;a href="https://smart-alert.io/recipes/contract-state-paused" rel="noopener noreferrer"&gt;paused() recipe&lt;/a&gt;, with a number instead of true/false.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert:&lt;/strong&gt; about 08:51–08:53. The rule reads the block 12 confirmations behind the head (about 144 seconds), so the first call that sees 0 lands 2.5–4 minutes after the upgrade&lt;sup&gt;[8]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time left:&lt;/strong&gt; about 45 minutes before the first withdrawal (09:37:23) and 79 minutes before the second (10:11:47).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the team could do:&lt;/strong&gt; the PauseEnforcer was there. One &lt;code&gt;triggerPause()&lt;/code&gt; stops the gateway. The team made exactly that call later, at 10:15:23.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ What it misses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An &lt;a href="https://smart-alert.io/recipes/proxy-upgraded" rel="noopener noreferrer"&gt;&lt;code&gt;Upgraded&lt;/code&gt;&lt;/a&gt; rule would fire here too, and it would not help.&lt;/strong&gt; The team was waiting for this event. An alert that says "the gateway was upgraded" reads as "yes, that is ours". Nothing in the event stream says the threshold is now 0. Only the contract knows that, and only if someone asks it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  09:37:23 — 3,996 ETH
&lt;/h3&gt;

&lt;p&gt;Address &lt;code&gt;0x1a56…3cc1&lt;/code&gt; called its MEV contract &lt;a href="https://etherscan.io/address/0x4Ab12E7CE31857Ee022f273e8580F73335a73c0B" rel="noopener noreferrer"&gt;&lt;code&gt;0x4Ab1…3c0B&lt;/code&gt;&lt;/a&gt;, which called &lt;code&gt;submitWithdrawal&lt;/code&gt; on the gateway. The gateway paid out 3,996.09 ETH (&lt;a href="https://etherscan.io/tx/0x2619570088683e6cc3a38d93c3d98899e5783864e15525d5f5810c11189ba6cb" rel="noopener noreferrer"&gt;&lt;code&gt;0x2619…a6cb&lt;/code&gt;&lt;/a&gt;, block 20468679)&lt;sup&gt;[7]&lt;/sup&gt;. This was 48 minutes after the upgrade.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔔 SmartAlert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; &lt;a href="https://smart-alert.io/recipes/treasury-below-usd" rel="noopener noreferrer"&gt;"Total address balance in $ below threshold"&lt;/a&gt; on the gateway, with a floor a little under the normal total. Native ETH does not emit &lt;code&gt;Transfer&lt;/code&gt;, so a balance rule is how you see it leave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert:&lt;/strong&gt; about 09:40–09:41.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time left:&lt;/strong&gt; about 31 minutes before the 2M USDC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest part:&lt;/strong&gt; this is the alert that confirms the first one. On its own it comes after $10M has left. With the state alert in front of it, it says "it is happening now".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  10:11:47 — 2M USDC
&lt;/h3&gt;

&lt;p&gt;Address &lt;code&gt;0xFDe0…455A&lt;/code&gt; withdrew 1,998,046.88 USDC to &lt;code&gt;0x6980…BDD0&lt;/code&gt; (&lt;a href="https://etherscan.io/tx/0xbce5b8548db486c561948e8a177c8ccaa72810f972cee3909ea50af015a60ad8" rel="noopener noreferrer"&gt;&lt;code&gt;0xbce5…0ad8&lt;/code&gt;&lt;/a&gt;, block 20468848)&lt;sup&gt;[7]&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔔 SmartAlert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; &lt;a href="https://smart-alert.io/recipes/large-transfer-usd" rel="noopener noreferrer"&gt;Transfer above an amount in $&lt;/a&gt; on USDC, &lt;code&gt;from&lt;/code&gt; = the gateway, threshold $1,000,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert:&lt;/strong&gt; about 10:14–10:15:30, about when the team paused the bridge. This one tells you the size of the loss, not the time left.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  10:15:23 — the pause
&lt;/h3&gt;

&lt;p&gt;Address &lt;code&gt;0x8B35…8f57&lt;/code&gt; called &lt;code&gt;triggerPause()&lt;/code&gt; on the PauseEnforcer (&lt;a href="https://etherscan.io/tx/0x90a1a9011dc2c246631da893f5f9f17ea4e27dbd9a334052a1d53359dfe090c9" rel="noopener noreferrer"&gt;&lt;code&gt;0x90a1…c090&lt;/code&gt;&lt;/a&gt;, block 20468866)&lt;sup&gt;[7]&lt;/sup&gt;. 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 withdrawal&lt;sup&gt;[1]&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  After — the money came back
&lt;/h3&gt;

&lt;p&gt;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 time&lt;sup&gt;[1]&lt;/sup&gt;&lt;sup&gt;[4]&lt;/sup&gt;. The per-withdrawal limit capped each transaction at about 4,000 ETH or 2M USDC&lt;sup&gt;[2]&lt;/sup&gt;. SEAL 911 helped with the response&lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no rule would have changed
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ What it misses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule created after the upgrade.&lt;/strong&gt; A contract-state rule starts recording answers when you create it. It does not read past blocks, so you cannot check it on history the way you can an event rule. To show the moment of the change, it had to exist before 08:48:47 for Ronin. A "below 1" rule created later would still fire, on its first call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ What it misses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing which number matters.&lt;/strong&gt; Here it is &lt;code&gt;minimumVoteWeight()&lt;/code&gt;. For a lending market it may be a price from an oracle, for a token &lt;code&gt;paused()&lt;/code&gt; or &lt;code&gt;owner()&lt;/code&gt;. A monitor checks the numbers you point it at. It does not guess your invariants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ What it misses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attacks in one transaction.&lt;/strong&gt; If the bad value and the withdrawal land in the same transaction, a state check sees the result, not the warning. At &lt;a href="https://www.certik.com/resources/blog/bybit-incident-technical-analysis" rel="noopener noreferrer"&gt;Bybit&lt;/a&gt; in February 2025, a signed Safe transaction emptied about $1.46B two and a half minutes after the first sign&lt;sup&gt;[5]&lt;/sup&gt;. At &lt;a href="https://medium.com/@KiloEx/kiloex-security-incident-root-cause-analysis-post-mortem-3d899caac08c" rel="noopener noreferrer"&gt;KiloEx&lt;/a&gt; in April 2025, pools were drained in the same second&lt;sup&gt;[6]&lt;/sup&gt;. Ronin had 48 minutes. Not every incident does.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to set up this rule in SmartAlert
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add the contract&lt;/strong&gt; and open it. Contract-state rules are on Pro and above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a "Contract state" rule.&lt;/strong&gt; Pick the function, for example &lt;code&gt;minimumVoteWeight()&lt;/code&gt;, and press "Check function" to see the current answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the condition&lt;/strong&gt; "Below threshold" with a threshold under the normal value, for example 1. For a bridge, call every minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create it before your next upgrade.&lt;/strong&gt; Then the first call after the upgrade shows whether the number survived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the event rules too.&lt;/strong&gt; &lt;a href="https://smart-alert.io/recipes/proxy-upgraded" rel="noopener noreferrer"&gt;&lt;code&gt;Upgraded&lt;/code&gt;&lt;/a&gt; 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 &lt;a href="https://smart-alert.io/recipes/treasury-below-usd" rel="noopener noreferrer"&gt;"Total address balance in $ below threshold"&lt;/a&gt; rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose where alerts go.&lt;/strong&gt; Telegram is free.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;See all &lt;a href="https://smart-alert.io/guides/alert-recipes" rel="noopener noreferrer"&gt;alert recipes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ronin Network on X, &lt;a href="https://x.com/Ronin_Network/status/1820804772917588339" rel="noopener noreferrer"&gt;incident statement&lt;/a&gt;: pause, about 40 minutes, the whitehat.&lt;/li&gt;
&lt;li&gt;Rekt, &lt;a href="https://rekt.news/roninnetwork-rektII" rel="noopener noreferrer"&gt;Ronin Network — Rekt II&lt;/a&gt;: the skipped initializer, the per-withdrawal limit, the amounts.&lt;/li&gt;
&lt;li&gt;Three Sigma, &lt;a href="https://threesigma.xyz/blog/ronin-network-12m-exploit-analysis" rel="noopener noreferrer"&gt;analysis of the Ronin Network exploit&lt;/a&gt;: &lt;code&gt;initializeV3&lt;/code&gt; and &lt;code&gt;_totalOperatorWeight&lt;/code&gt;, the 70% threshold, SEAL 911.&lt;/li&gt;
&lt;li&gt;The Block, &lt;a href="https://www.theblock.co/news/regulation/2024-08-06-ronin-bridge-pauses-309707" rel="noopener noreferrer"&gt;report on the Ronin bridge pause&lt;/a&gt;: returned funds, the $500K bounty, more than $850M in the bridge.&lt;/li&gt;
&lt;li&gt;CertiK, &lt;a href="https://www.certik.com/resources/blog/bybit-incident-technical-analysis" rel="noopener noreferrer"&gt;Bybit incident technical analysis&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;KiloEx, &lt;a href="https://medium.com/@KiloEx/kiloex-security-incident-root-cause-analysis-post-mortem-3d899caac08c" rel="noopener noreferrer"&gt;Security incident root cause analysis&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checked by SmartAlert on-chain:&lt;/strong&gt; &lt;code&gt;minimumVoteWeight()&lt;/code&gt; with archive &lt;code&gt;eth_call&lt;/code&gt; at blocks 20468437, 20468438 and 20468678; the upgrade transaction, its six calls, their results and its logs (&lt;code&gt;Upgraded&lt;/code&gt;, &lt;code&gt;Initialized(4)&lt;/code&gt;, no &lt;code&gt;Initialized(3)&lt;/code&gt;); the two withdrawals and the pause, with block times, senders and amounts, through the Blockscout API.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://smart-alert.io/blog/ronin-bridge-vote-weight" rel="noopener noreferrer"&gt;SmartAlert blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>security</category>
      <category>ethereum</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
