BRO-SOLV Callback Double-Mint Value Amplification
Exploit Transactions
0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958dVictim Addresses
0x014e6f6ba7a9f4c9a51a0aa3189b5c0a21006869Ethereum0x982d50f8557d57b748733a3fc3d55aef40c46756Ethereum0x1e6101728fd9920465dfa1562c5e371850103da2EthereumLoss Breakdown
Similar Incidents
Orion Pool Double-Count Exploit
32%WETH Drain via Unprotected 0xfa461e33 Callback on 0x03f9-62c0
32%Unlimited-Mint Collateral Used to Over-Mint Debt Token
30%CivTrade Fake-Pool Callback Drain
30%GoodDollar Fake-Interest Mint
30%Unauthorized WETH drain via unprotected Uniswap V3 callback
30%Root Cause Analysis
BRO-SOLV Callback Double-Mint Value Amplification
1. Incident Overview TL;DR
A single adversary-crafted Ethereum transaction (0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d, block 24592074) exploited BitcoinReserveOffering (BRO, 0x014e6f6ba7a9f4c9a51a0aa3189b5c0a21006869) by repeatedly minting unbacked BRO shares and redeeming them into real value. The sender EOA (0xa407fe273db74184898cb56d2cb685615e1c0d6e) deployed helper contracts (0xb32d389901f963e7c87168724fbdcc3a9db20dc9, 0x6aa78a9b245cc56377b21401b517ec8c03a40f03) and completed bootstrap, amplification, and profit realization in one transaction.
The root cause is a deterministic double-credit path in BRO minting: mint() credits shares once after moving GOEFS value, while ERC3525/ERC721 receive callbacks also credit shares for the same movement. This violates the one-inflow-one-credit invariant and enables repeated over-mint/burn amplification. The result was net attacker profit of 1211.030278988153120904 ETH (after gas), with in-transaction conversion path through SolvBTC and AMM swaps.
2. Key Background
BRO is an ERC20 wrapper over GOEFS ERC3525 value (GOEFS, 0x982d50f8557d57b748733a3fc3d55aef40c46756) with conversion controlled by exchangeRate. In the validated pre-state (block 24592073), BRO points to GOEFS and uses initialized holding token id and non-zero exchange rate.
Relevant design facts:
- GOEFS value transfers call ERC3525 receiver callback on destination owner contracts.
- BRO implements both
onERC3525ReceivedandonERC721Received, each minting BRO shares. - BRO
burn()converts shares back into GOEFS value (integer conversion), so over-minted shares can be redeemed into extra underlying value.
Validated pre-state evidence:
block=24592073
wrappedSftAddress=0x982D50f8557D57B748733a3fC3d55AeF40C46756
holdingValueSftId=4488
exchangeRate=4352256450000000000000000
proxy implementation=0x15F7c1Ac69f0C102e4f390e45306BD917f21cFCf
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is an accounting logic flaw (ATTACK), not MEV-only pricing behavior. The intended invariant is: for mint input x, BRO shares should increase exactly once by floor(x * exchangeRate / 1e18) against actual underlying inflow. In practice, the same GOEFS movement can trigger callback minting first and then post-transfer minting in mint(), resulting in double credit. This duplicated credit compounds through mint/burn loops, growing attacker-controlled GOEFS value deterministically and allowing conversion into external assets. On-chain logs confirm repeated transfer-value cycles and final realization into WETH/ETH. The exploit is ACT: no privileged keys or privileged protocol role were required.
Security principles violated:
- Single-accounting principle (one inflow should map to one credit).
- Callback safety isolation (callback-side state changes were not reconciled against local mint accounting).
- Post-transfer invariant enforcement (no guard preventing duplicate mint credit).
Exploit preconditions satisfied in this incident:
- Unprivileged attacker could submit custom contracts/calldata.
- Attacker could control a GOEFS token id in wrapped slot and grant approvals.
- BRO
mintpath reached both callback mint and post-transfer mint for the same amount.
4. Detailed Root Cause Analysis
4.1 Code-Level Breakpoint
BRO callback handlers mint shares for incoming GOEFS value:
function onERC3525Received(...) external onlyWrappedSft returns (bytes4) {
...
uint256 value = sftValue_ * exchangeRate / (10 ** decimals());
_mint(fromSftOwner, value);
return IERC3525Receiver.onERC3525Received.selector;
}
function onERC721Received(...) external onlyWrappedSft returns (bytes4) {
...
uint256 value = sftValue * exchangeRate / (10 ** decimals());
_mint(from_, value);
return IERC721Receiver.onERC721Received.selector;
}
BRO mint() also mints after transfer:
function mint(uint256 sftId_, uint256 amount_) external nonReentrant {
...
// transfer/doTransfer occurs first
uint256 value = amount_ * exchangeRate / (10 ** decimals());
_mint(msg.sender, value);
}
GOEFS transfer path guarantees callback execution after TransferValue:
function _transferValue(...) internal {
...
emit TransferValue(fromTokenId_, toTokenId_, value_);
...
require(
_checkOnERC3525Received(fromTokenId_, toTokenId_, value_, ""),
"ERC3525: transfer rejected by ERC3525Receiver"
);
}
Therefore, one attacker mint(sftId, amount) can mint twice for one underlying movement.
4.2 On-Chain Deterministic Evidence
Receipt-level evidence for tx 0x44e637...:
status=0x1
gasUsed=0x727ff9
effectiveGasPrice=0xbf6a353e
from=0xa407fe273db74184898cb56d2cb685615e1c0d6e
contractAddress=0xb32d389901f963e7c87168724fbdcc3a9db20dc9
logs_count=237
TransferValue events=44
TransferValue progression (token ids 0x1188 <-> 0x1344) shows deterministic growth from 31102085070226 to 65225799909192499201 (factor 2^21 across loop progression), consistent with repeated double-credit amplification.
Balance-diff evidence confirms realized net profit on sender EOA:
{
"address": "0xa407fe273db74184898cb56d2cb685615e1c0d6e",
"before_wei": "191427924249947920",
"after_wei": "1211221706912403068824",
"delta_wei": "1211030278988153120904"
}
Final realization logs show path from inflated BRO to SolvBTC, then swaps to WBTC/WETH, then WETH withdrawal:
- BRO transfer to exchange path (
log 0x110) - SolvBTC transfer to attacker contract (
log 0x111) - Exchange event (
log 0x112) - WBTC/WETH swap events (
log 0x114to0x119) - WETH withdrawal event (
log 0x11a)
5. Adversary Flow Analysis
Adversary cluster identified:
- EOA sender/profit recipient:
0xa407fe273db74184898cb56d2cb685615e1c0d6e - Deployed exploit contract:
0xb32d389901f963e7c87168724fbdcc3a9db20dc9 - Helper contract in loop path:
0x6aa78a9b245cc56377b21401b517ec8c03a40f03
End-to-end sequence (single tx):
- Bootstrap and deploy helper contracts from an unprivileged EOA.
- Prepare approvals and token-id control for GOEFS interactions.
- Execute repeated BRO mint/burn loop where each mint receives duplicated share credit via callback + post-transfer mint.
- Convert inflated BRO to SolvBTC through exchange proxy path (
0x1e6101728fd9920465dfa1562c5e371850103da2). - Swap into WBTC/WETH through public routers/pools and withdraw WETH to ETH.
- End with net positive ETH on originating EOA.
ACT framing:
- All required data and actions were public and permissionless.
- No private keys, privileged governance, or non-public infra assumptions were needed.
- Inclusion feasibility is standard type-2 transaction submission by any unprivileged actor.
6. Impact & Losses
Measured impact in this incident:
- Net sender EOA gain:
1211.030278988153120904 ETH(1211030278988153120904 wei). - WETH withdrawn by attacker contract:
1211.054376965512754134 WETH(1211054376965512754134 wei). - SolvBTC transferred to attacker contract in realization stage:
38.047405138108322251 SolvBTC(38047405138108322251units at 18 decimals).
Impact summary: the callback double-mint bug created unbacked BRO issuance and deterministic invariant drift; attacker monetized that drift through exchange plus AMM routes in one transaction.
7. References
- Exploit transaction:
0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d - BRO proxy (victim):
0x014e6f6ba7a9f4c9a51a0aa3189b5c0a21006869 - BRO implementation:
0x15f7c1ac69f0c102e4f390e45306bd917f21cfcf - GOEFS:
0x982d50f8557d57b748733a3fc3d55aef40c46756 - Exchange proxy in realization stage:
0x1e6101728fd9920465dfa1562c5e371850103da2 - Seed tx metadata:
/workspace/session/artifacts/collector/seed/1/0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d/metadata.json - Seed full trace:
/workspace/session/artifacts/collector/seed/1/0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d/trace.cast.log - Seed balance diff:
/workspace/session/artifacts/collector/seed/1/0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d/balance_diff.json - Receipt/log summary:
/workspace/session/artifacts/auditor/iter_0/receipt_summary.txt - BRO source:
/workspace/session/artifacts/collector/seed/1/0x15f7c1ac69f0c102e4f390e45306bd917f21cfcf/src/BitcoinReserveOffering.sol - GOEFS ERC3525 callback code:
/workspace/session/.tmp/source_fetch/0x2be4500c50d99a81c8b4cf8da10c5edbae6a234a/OpenFundShareConcrete/@solvprotocol/erc-3525/ERC3525Upgradeable.sol - Pre-state calls:
/workspace/session/artifacts/auditor/iter_0/prestate_calls.txt