The Incident
On March 2026, Solv Protocol — a Bitcoin reserve protocol that lets users wrap BTC into SolvBTC for cross-chain DeFi — lost approximately $2.7 million through a smart contract exploit targeting its BitcoinReserveOffering (BRO) vault.
The attacker didn't need a flashloan. They didn't exploit an oracle. They found something more subtle: a cross-standard reentrancy hidden in the interaction between ERC-3525 and ERC-721.
Understanding the Attack Vector
ERC-3525: The Semi-Fungible Token Standard
ERC-3525 defines semi-fungible tokens (SFTs) — tokens that have both an ID (like NFTs) and a value (like ERC-20). The critical design decision: ERC-3525 inherits from ERC-721.
This means every ERC-3525 token is also an ERC-721 token. And ERC-721 mandates calling onERC721Received during safe transfers.
The Double-Mint Flaw
Here's the vulnerable flow in the BRO vault:
1. User deposits ERC-3525 token via doSafeTransferIn()
2. doSafeTransferIn() triggers token transfer
3. Transfer triggers onERC721Received() callback ← REENTRANCY POINT
4. Callback mints BRO tokens (Mint #2 — completes first)
5. Original deposit handler mints BRO tokens (Mint #1 — completes second)
The vault's doSafeTransferIn ingests the ERC-3525 token and triggers a mint. But because of the ERC-721 inheritance, the safe transfer also calls onERC721Received on the receiver — which triggers a second mint. Since mint #2 completes before mint #1, the contract's state hasn't been updated to reflect the first operation.
This is classic reentrancy, but hidden behind cross-standard inheritance.
The Exploit in Practice
The attacker:
- Triggered the double-mint vulnerability 22 times
- Converted 135 BRO into ~567 million BRO tokens
- Swapped the inflated BRO for ~38 SolvBTC
- Walked away with ~$2.7M (SolvBTC trades 1:1 with BTC)
Why Traditional Audits Missed This
CEI Isn't Enough
The Check-Effects-Interactions (CEI) pattern is the standard reentrancy defense. But in this case:
- The check was correct — the vault verified the deposit
- The effects were applied — state was updated after the mint
- The interaction was the problem — but it was hidden inside the token standard itself
The reentrancy didn't come from an external call to an untrusted contract. It came from a mandatory callback defined by the token standard. The vault was following best practices for handling ERC-3525 tokens — it just didn't account for the ERC-721 callback that comes along for the ride.
Cross-Standard Inheritance is a Blind Spot
Most auditors check reentrancy within a single standard. But when Token Standard A inherits from Token Standard B, you get two sets of callbacks, two sets of hooks, and two sets of assumptions that can conflict.
The ERC-3525 → ERC-721 inheritance means:
-
safeTransferFromtriggers ERC-3525's value transfer logic AND ERC-721'sonERC721Received - If both paths can trigger state changes, you have a reentrancy window
Detection Patterns
What to Look For in Audits
1. Token standard inheritance chains
ERC-3525 → ERC-721 → ERC-165
ERC-4626 → ERC-20
ERC-1155 → (multi-token callbacks)
Any time a token standard inherits from another, check if both levels have callbacks or hooks.
2. Safe transfer functions with state changes
// RED FLAG: State change after safe transfer
function deposit(uint256 tokenId) external {
token.safeTransferFrom(msg.sender, address(this), tokenId);
// ↑ This triggers onERC721Received before reaching ↓
_mint(msg.sender, calculateAmount(tokenId));
}
3. Multiple mint/burn paths in a single transaction
If a function can trigger minting through more than one code path, verify each path is aware of the others.
Foundry Test Pattern
contract ReentrancyTest is Test {
BROVault vault;
MaliciousReceiver attacker;
function testDoubleMinReentrancy() public {
attacker = new MaliciousReceiver(address(vault));
uint256 tokenId = erc3525.mint(address(attacker), 1, 100);
uint256 balBefore = bro.balanceOf(address(attacker));
attacker.attack(tokenId);
uint256 balAfter = bro.balanceOf(address(attacker));
assertEq(balAfter - balBefore, expectedSingleMint);
}
}
contract MaliciousReceiver is IERC721Receiver {
BROVault vault;
bool attacked;
function onERC721Received(
address, address, uint256, bytes calldata
) external returns (bytes4) {
if (!attacked) {
attacked = true;
vault.deposit(savedTokenId);
}
return this.onERC721Received.selector;
}
}
Mitigation Strategies
For Protocol Developers
1. Use reentrancy guards on all state-changing functions
function deposit(uint256 tokenId) external nonReentrant {
token.safeTransferFrom(msg.sender, address(this), tokenId);
_mint(msg.sender, calculateAmount(tokenId));
}
2. Apply CEI at the cross-standard level
Update state before any transfer that could trigger callbacks:
function deposit(uint256 tokenId) external nonReentrant {
uint256 amount = calculateAmount(tokenId);
_mint(msg.sender, amount); // Effects first
token.safeTransferFrom(msg.sender, address(this), tokenId); // Interaction last
}
3. Audit all token standards for inherited callbacks
If you're building a vault that accepts ERC-3525, you must also handle:
-
onERC721Received(from ERC-721 inheritance) -
supportsInterfacefor both ERC-3525 and ERC-721 - Potential
onERC3525Received(if defined by the implementation)
For Auditors
Add this to your checklist:
- [ ] Map all token standard inheritance chains in the codebase
- [ ] Identify every callback hook in each standard layer
- [ ] Test reentrancy through each callback independently
- [ ] Verify
nonReentrantguards cover cross-standard paths - [ ] Check if state mutations happen between transfer initiation and callback completion
The Bigger Picture
The Solv hack is a case study in composability risk. DeFi's power comes from composing standards and protocols. But each layer of composition adds potential interaction paths that may not be obvious:
| Risk Layer | Example |
|---|---|
| Single-contract reentrancy | Classic CEI violation |
| Cross-function reentrancy | Read-only reentrancy via view functions |
| Cross-contract reentrancy | Callback to another protocol |
| Cross-standard reentrancy | ERC-3525 → ERC-721 callback (Solv) |
| Cross-chain reentrancy | Bridge message replay |
As DeFi matures and more complex token standards emerge (ERC-3525, ERC-4337, ERC-6551), we'll see more of these cross-standard vulnerabilities. The attack surface isn't just in the code you write — it's in the standards you inherit.
DreamWork Security specializes in smart contract auditing across Solana and EVM ecosystems. Follow for weekly security research and vulnerability analysis.
Top comments (0)