DEV Community

Saravana kumar for Cryip

Posted on • Originally published at cryip.co

How a Double-Mint Smart Contract Bug Led to the Solv Protocol BRO Vault Exploit

Smart contract vulnerabilities in DeFi rarely come from complex cryptography. Most exploits happen because of subtle accounting mistakes in token minting or collateral tracking.
A recent exploit in Solv Protocol’s BRO Vault demonstrates how a small oversight in token processing logic can lead to multi-million dollar losses.
In this post, we’ll break down:

  • What the BRO vault does
  • Where the vulnerability existed
  • How the attacker exploited it
  • How developers can prevent similar bugs

What is the BRO Vault?

Solv Protocol uses ERC-3525 Semi-Fungible Tokens (SFTs) to represent value-bearing assets.
The BRO vault allows users to deposit these SFTs and receive wrapped tokens based on their value.
Simplified architecture
User deposits SFT

Vault reads SFT value

Vault mints BRO tokens

The vault automatically mints tokens when it receives an SFT.
This happens through the ERC721 receiver callback function.

The Vulnerable Code

The critical logic is inside the onERC721Received function.
function onERC721Received(
address,
address from_,
uint256 sftId_,
bytes calldata
)
external override returns (bytes4)
{
uint256 sftValue = IERC3525(wrappedSftAddress).balanceOf(sftId_);
require(sftValue > 0, "mint zero not allowed");

uint256 value = sftValue * exchangeRate / (10 ** decimals());

_mint(from_, value);

return IERC721Receiver.onERC721Received.selector;
Enter fullscreen mode Exit fullscreen mode

}
What this function does

  1. Receives an SFT token
  2. Reads the token’s value
  3. Calculates the amount of wrapped tokens
  4. Mints tokens to the user At first glance, the logic appears straightforward. However, the problem lies in how the contract tracks processed tokens.

The Root Cause of the Vulnerability

The contract does not track whether an SFT has already been processed.
This means the same token ID can potentially trigger the mint logic multiple times.
The only validation present is:
require(sftValue > 0)

But there is no protection against processing the same collateral twice.
As a result, the contract may mint tokens multiple times using the same underlying asset value.
This leads to a double-mint vulnerability.

Why the Callback Design Was Risky

The minting logic is executed inside:
onERC721Received()

This function is automatically triggered when an NFT or SFT is transferred to the contract.
Using callbacks for financial logic introduces several risks.
1. Implicit execution
Developers might assume the function runs once per deposit.
But attackers can manipulate token transfers to trigger it multiple times.
2. Complex token flows
The contract internally transfers SFT balances between token IDs using a helper:
ERC3525TransferHelper.doTransfer(...)

These internal movements can create unexpected state changes that allow the mint logic to run again.
Possible Attack Flow
A simplified version of the exploit may look like this:
Step 1 — Attacker deposits an SFT
The attacker sends an ERC-3525 token to the vault.
Attacker → Vault

The onERC721Received callback triggers.
The vault mints wrapped tokens.
Step 2 — Internal SFT transfer occurs
The vault moves value between SFT IDs using an internal transfer helper.
doTransfer(sftId → holdingValueSftId)

This rearranges token balances.
Step 3 — Mint logic executes again
Because the contract does not mark SFTs as processed, the mint logic can run again using the same token value.
Mint again
Step 4 — Repeating the process
The attacker loops the process:
transfer → mint
transfer → mint
transfer → mint

Eventually, this inflates the supply of wrapped tokens and allows the attacker to drain assets from the vault.

Why Double-Mint Bugs Are Dangerous

DeFi protocols rely on strict collateral accounting.
A basic invariant should always hold:
Total Minted Tokens ≤ Total Collateral Deposited

With a double-mint vulnerability, this rule breaks:
Total Minted Tokens > Actual Collateral

Once this happens, attackers can withdraw more assets than they deposited.
How Developers Can Prevent This
Track Processed Assets

Always ensure a token can only be processed once.
Example:
mapping(uint256 => bool) public processedSFT;

require(!processedSFT[sftId_], "SFT already processed");

processedSFT[sftId_] = true;

This prevents double minting from the same collateral.
2. Avoid Minting in Callback Functions

Callbacks should only validate transfers.
Bad design:
transfer → automatic mint

Better design:
transfer → record deposit
user calls claim() → mint tokens

Separating these steps makes logic easier to verify.
3. Track Value Deltas
Instead of reading raw balances:
balanceOf(sftId)

Track how much value actually changed:
delta = newBalance - previousBalance

Mint tokens based only on new value deposited.
4. Use Reentrancy Protection
Adding protection helps defend against complex execution flows.
Example:
function onERC721Received(...) external nonReentrant

Key Takeaways for Smart Contract Developers

The Solv Protocol exploit highlights several common DeFi security pitfalls.
Avoid implicit financial operations
Critical actions like minting should require explicit user interaction.
Track every collateral asset
Assets must never be counted more than once.
Keep accounting logic simple
Complex token transfers increase the chance of accounting errors.
Enforce invariants
Protocols should constantly ensure:
mintedSupply ≤ collateralValue

If this rule breaks, the system becomes vulnerable to inflation attacks.

Conclusion

The Solv Protocol BRO vault exploit is a reminder that smart contract security often comes down to careful state management and correct accounting.
A missing validation check allowed the same collateral to be processed multiple times, leading to token inflation and fund loss.
For developers building DeFi protocols, the key lesson is simple:
Always assume that every external interaction — including token transfers — can be manipulated by attackers.
Design contracts so that each deposit is processed exactly once and every mint is fully backed by collateral.

Top comments (0)