Solari Systems — Smart Contract Security
EXPLOIT ANALYSIS $2.73M LOST

Solv BRO Vault Exploit: How Callback Reentrancy Drained $2.73 Million

March 11, 2026 • 6 min read • Solari Systems Research

On March 5, 2026, an attacker exploited a callback reentrancy vulnerability in Solv Protocol's BitcoinReserveOffering contract, turning 135 BRO tokens into 567 million and draining 38 SolvBTC (~$2.73M). The root cause? A missing reentrancy guard on a mint function that processed ERC-721 callbacks.

Key Takeaway: This is a textbook checks-effects-interactions violation. The contract performed an external call (NFT transfer with callback) before updating its internal state, allowing the same deposit to be processed twice per iteration.

The Vulnerability

Solv's BitcoinReserveOffering contract used ERC-3525 semi-fungible tokens. When a user called mint(), the contract:

1
Calls doSafeTransferIn() to transfer the user's NFT collateral
2
The NFT transfer triggers onERC721Received callback on the recipient
3
Inside the callback, BRO tokens are minted (first mint)
4
Execution returns to mint(), which mints BRO again (second mint)
5
Same exchange rate applied both times — deposit counted twice

The Vulnerable Pattern

The core issue is a function that makes an external call before completing its state updates:

// VULNERABLE PATTERN - Do NOT use
function mint(uint256 nftId) external {
    // 1. Transfer NFT (triggers onERC721Received callback)
    doSafeTransferIn(msg.sender, nftId);  // External call HERE

    // 2. Mint BRO tokens based on NFT value
    uint256 broAmount = calculateMintAmount(nftId);
    _mint(msg.sender, broAmount);  // State update AFTER call
}

// Attacker's contract:
function onERC721Received(...) external returns (bytes4) {
    // Re-enters mint() during the callback
    // BRO is minted AGAIN for the same deposit
    target.mint(nftId);  // Reentrancy!
    return this.onERC721Received.selector;
}

The Safe Pattern

// SAFE - Checks-Effects-Interactions + ReentrancyGuard
function mint(uint256 nftId) external nonReentrant {
    // 1. Checks
    require(!processed[nftId], "Already processed");

    // 2. Effects (update state BEFORE external call)
    processed[nftId] = true;
    uint256 broAmount = calculateMintAmount(nftId);
    _mint(msg.sender, broAmount);

    // 3. Interactions (external call LAST)
    doSafeTransferIn(msg.sender, nftId);
}

The Attack in Numbers

MetricValue
Starting capital135 BRO tokens
Iterations22 loops in a single transaction
Tokens minted567,000,000 BRO
Value extracted38.05 SolvBTC (~$2.73M)
Exit pathBRO → SolvBTC → WBTC → WETH → ETH
Laundering1,211 ETH via Tornado Cash

Why This Keeps Happening

Callback reentrancy is the #1 smart contract vulnerability, responsible for billions in losses since The DAO hack in 2016. Yet it remains the most common exploit because:

Audit Gap: Solv claimed five auditors (Quantstamp, Salus, OpenZeppelin, Offside, Paladin), but none reviewed the BitcoinReserveOffering contract. The exploited code shipped without audit. Their HackenProof bounty only covered Web2 and Solana contracts.

How to Protect Your Contracts

1. Use ReentrancyGuard

OpenZeppelin's nonReentrant modifier is the simplest defense. Apply it to any function that makes external calls.

2. Follow Checks-Effects-Interactions

Update all state variables before making any external calls. Mark deposits as processed before transferring tokens.

3. Automated Scanning

Pattern-based scanners can catch reentrancy vulnerabilities in seconds. Every contract should be scanned before deployment.

Scan Your Contracts Now

Our scanner detects reentrancy, callback vulnerabilities, missing guards, and 20+ other vulnerability patterns. Free for basic scans.

Free Vulnerability Scan

No signup required. Results in seconds.

On-Chain Evidence

ComponentAddress
Victim Contract (BRO)0x014e6F6ba7a9f4C9a51a0Aa3189B5c0a21006869
Attack Tx0x44e637c7...a97a958d
BRO-SolvBTC Exchange0x1E6101728fD9920465dfA1562c5e371850103da2

Timeline