150K SUI from Code Nobody Called
The protocol was audited. The old version wasn't.
Hey, it’s Arsen.
In today’s menu:
• How a 17-month-old Sui package drained a live rewards pool
• Why Litecoin needed a 13-block reorg to recover
• The Solana event your relayer fills that never happened
• And more…
🏴☠️ Hacks
Scallop drained for 150K SUI via deprecated V2 package
You scope the Scallop protocol.
You check the live deployment, the active SDK path, the current package.
Everything looks clean.
Here’s what you didn’t check:
What can the old package still do?
On Sui, deployed packages are immutable. Every version you ever shipped stays on-chain — and callable. The attacker didn’t touch the live code. They found a V2 spool package from November 2023. Nobody had called it in months.
The bug was in spool_account creation. In the deprecated package, last_index never initializes. It stays at zero.
Points earned = stake × (current_index − last_index).
With last_index at zero, every historical reward counts. Staking credits you for everything accumulated since the spool launched in August 2023.
The spool index had grown to 1.19 billion over 20 months. The attacker staked 136K sSUI. The pool ran a 1:1 exchange rate — numerator and denominator both set to 1. 162 trillion points converted directly to 162K SUI worth of rewards. The pool held 150K SUI. They drained it.
The bug sat dormant for 17 months. Legitimate users hit the new package via SDK, which initializes last_index correctly. The old path was invisible to anyone watching the live code.
The fix: version fields on shared objects. assert!(version == CURRENT_VERSION) in every function entry. Without that, every prior package version stays live. Not legacy. Not retired. Live.
KelpDAO, Litecoin, Aethir, Scallop. Most April exploits weren’t in core protocol code.
The audit perimeter isn’t the deployed contract.
It’s everything you ever shipped.
🗞️ News
Litecoin zero-day disrupts mining pools, 13-block reorg follows
A zero-day in Litecoin’s MWEB privacy layer hit last week. Major mining pools went down.
Mining nodes running outdated versions accepted an invalid MWEB transaction. That transaction pegged out coins to third-party DEXs — movement the protocol should have rejected. Nodes on the patched version rejected it outright.
What does it mean when 13 blocks have to be undone to fix it?
The network ran a 13-block reorg. The invalid chain segment reversed. All valid transactions from that window stayed in the main chain. Funds unaffected. Bug fully patched.
A 13-block reorg sounds alarming. In Bitcoin, it would be a crisis. Here it was the right call — letting invalid pegs stand was the worse outcome.
But 13 blocks is a number worth sitting with.
The fix existed before this happened. The patched version was already live. The disruption wasn’t a zero-day in the strictest sense — it was a deployment gap. Mining pools running unpatched nodes opened the window. The attacker used it.
Between a patch going out and every operator running it, there’s a gap. In this case, that gap was big enough to matter.
Patching the bug is step one.
Getting every operator to actually ship it is the harder problem.
13 blocks of drift is the evidence. “Patched” doesn’t mean “deployed.”
📚 Education
Missing status check lets Across relayers fill fake Solana deposits
You’re building off-chain infrastructure for a Solana bridge.
Your indexer catches FundsDeposited events. Each one triggers a relayer fill on the destination chain. Across is intent-based — the relayer takes on the capital burden, fills the order, gets reimbursed from HubPool on Ethereum later.
In EVM, events are first-class. A failed transaction doesn’t emit them. In Solana, there’s no canonical events API. Off-chain infra reconstructs events from instruction data — specifically from self-CPIs, where a program calls back into itself.
The check: find inner instructions from the target program. Verify the PDA event authority is a signer. Deserialize the data. Both conditions met — fire the event.
Almost right.
Did the transaction actually succeed?
The Across indexer parsed meta.InnerInstructions for matching program address and event authority. It never checked transaction status. A failed Solana transaction still has instruction data. The event fires. The relayer sees a FundsDeposited and fills the order on the destination chain. The source-chain deposit reverted. The attacker keeps the fill.
The attack: flash loan for maximum relayer capacity. Submit a deposit on Solana. Force a revert after the deposit instruction runs — the token transfer reverts, but the instruction data stays. The relayer sees the event and fills the destination chain. You keep the fill. Flash loan repaid. Up the full fill amount.
The fix was one check: verify the transaction succeeded before processing events. Shipped within hours. 0xAlphaRush flagged the same class in a Sherlock contest for Zetachain. It’s not isolated to Across.
Solana gives you the event data regardless of outcome.
Outcome is your job to check.
If your indexer doesn’t, someone else will notice first.
That’s it for this week.
Reply with the Solana bug, tool, or pattern you want me to cover next — I read every one.
If a working Solana auditor in your circle would find this useful, forward it their way.
— Arsen, working Solana auditor




