Bedrock uniBTC Native-Asset Mint Conversion Omission / Unbacked Mint Drain (2024-09-26)

  • Name: Bedrock uniBTC native-asset mint conversion omission / unbacked mint drain
  • Date: 2024-09-26
  • Protocol: Bedrock uniBTC
  • Chain: Ethereum exploit path with the same vulnerable vault design deployed across at least Ethereum, BNB Chain, Arbitrum, Optimism, Mantle, Mode, BOB, and ZetaChain
  • Category: issuance-redemption solvency asymmetry / asset-conversion omission / unbacked mint / deployment-config mismatch
  • Loss: about $1.8M to $2M drained from live liquidity, with much larger multi-chain exposure temporarily at risk
  • Summary: On 2024-09-26, Bedrock’s uniBTC vault exposed a payable native-asset mint path on non-BTC chains. In the live Vault implementation, mint() accepted msg.value and forwarded it to _mint(msg.sender, msg.value), while _amounts(uint256) converted the deposit into uniBTC by decimal scaling alone. That logic makes sense only if the native asset itself is actually BTC-like. On Ethereum, however, it meant ETH deposits were effectively treated as though they were native BTC: about 1 ETH could mint about 1 uniBTC despite the huge ETH/BTC price gap. Because the vault was an authorized uniBTC minter and the deployed supply / cap controls did not stop repeated use of this path, attackers minted unbacked uniBTC and dumped it into real exit liquidity. Dedaub disclosed the bug before exploitation, but the first public exploit transaction still landed at 2024-09-26 18:28 UTC, and Bedrock plus dependent protocols then raced to pause affected integrations before the multi-chain exposure turned into a much larger insolvency event.
  • Trigger / entry point:
    • The vulnerable entrypoint was the payable Vault.mint() path intended for NATIVE_BTC environments.
    • In the onchain source highlighted by Dedaub, the function was simply:
      • require(allowedTokenList[NATIVE_BTC] && !paused[NATIVE_BTC], "SYS002");
      • _mint(msg.sender, msg.value);
    • The internal native-asset amount helper then did only decimal normalization:
      • uint256 uniBTCAmt = _amount / EXCHANGE_RATE_BASE;
      • return (uniBTCAmt * EXCHANGE_RATE_BASE, uniBTCAmt);
    • No ETH/BTC conversion, price feed, or reserve-side value check corrected that assumption on non-BTC chains.
    • Dedaub identified the Ethereum implementation behind proxy 0x047d41f2544b7f63a8e991af2068a363d210d6da as implementation 0x702696b2aa47fd1d4feaaf03ce273009dc47d901.
  • Root cause:
    • Bedrock exposed a mint primitive whose economic assumptions were chain-specific, but the deployed contract surface did not keep those assumptions aligned with the actual native asset of the chain.
    • The payable mint() branch treated the chain’s native token as if it were BTC-denominated collateral and only reconciled decimals, not value.
    • That turned a dimensional mistake on the issuance side into unbacked uniBTC creation: ETH or other non-BTC native assets entered on one side, while a BTC-priced claim came out on the other.
    • The codebase itself preserved a contrasting VaultWithoutNative variant that omits the payable native-mint entrypoint and explicitly rejects NATIVE_BTC, which helps show the real design boundary Bedrock needed to preserve on non-native-BTC chains.
    • Public postmortem reporting also said the supply-tracking / cap path for NATIVE_BTC failed to register correctly in the deployed setup, leaving the cap mechanism ineffective against repeated minting on the vulnerable path.
  • Exploit path:
    1. Bedrock deployed or activated a Vault implementation on non-BTC chains with a live payable mint() entrypoint.
    2. An attacker sent the native gas asset of the chain, such as ETH, into mint().
    3. The vault converted the deposit into uniBTC using only decimal scaling, effectively treating 1 ETH as though it were worth 1 BTC for issuance purposes.
    4. Because the contract was a permissioned uniBTC minter and the relevant cap / supply controls did not stop the path in time, the attacker could repeat minting far beyond legitimate backing.
    5. The attacker then sold the freshly minted uniBTC into secondary liquidity such as Uniswap and other exposed DeFi venues that still honored uniBTC as a BTC-like asset.
    6. Bedrock, Dedaub, SEAL 911, and downstream protocols such as Pendle moved to disable affected routes, which contained losses before the full market cap across chains could be cashed out.
  • What the attacker needed to be true:
    • A non-BTC chain had to expose the payable mint() path anyway.
    • The mint path had to use only decimal normalization instead of a real native-asset-to-BTC conversion.
    • The contract needed authority to mint uniBTC directly.
    • There had to be external liquidity or protocol integrations still willing to accept and price the newly minted uniBTC as if it were properly backed.
    • Operators needed to react more slowly than attackers, because once the bad issuance path was public the profitable trade was mechanically obvious.
  • Technical anatomy worth remembering:
    • This was not an oracle-manipulation incident. Nobody had to move the ETH/BTC market. Bedrock itself defined the wrong exchange rule on the mint boundary.
    • The key mistake was not subtle math but omitted math: the native-asset branch applied decimal conversion where it needed economic conversion.
    • The existence of both Vault and VaultWithoutNative in the codebase is a useful forensic clue. Bedrock did not just have a generic accounting bug; it had a deployment / surface-selection failure that left the wrong economic entrypoint live on the wrong chains.
    • Dedaub’s timeline matters operationally: the issue was discovered around 16:00 UTC, reported at 16:27 UTC, and first exploited on Ethereum at 18:28 UTC, showing how little time teams get once a trivially monetizable issuance path is reachable.
    • Dedaub also noted that the vulnerable contract had been deployed for under two days, which makes this a good reminder that freshly shipped mint surfaces are often the easiest places for bots and whitehats alike to find catastrophic value-conversion mistakes.
  • Representative contracts and transactions:
    • Ethereum proxy vault: 0x047d41f2544b7f63a8e991af2068a363d210d6da
    • Ethereum implementation identified by Dedaub: 0x702696b2aa47fd1d4feaaf03ce273009dc47d901
    • Representative exploit transaction: 0x725f0d65340c859e0f64e72ca8260220c526c3e0ccde530004160809f6177940
    • Relevant source surfaces: contracts/contracts/Vault.sol and contracts/contracts/VaultWithoutNative.sol in Bedrock’s public uniBTC repository
  • Response / mitigation:
    • Dedaub disclosed the issue privately to Bedrock and escalated through SEAL 911 when immediate response lagged.
    • Attackers still exploited the path before Bedrock fully reacted, but downstream protocols helped limit contagion by disabling or pausing uniBTC exposure.
    • Bedrock later paused the relevant vaults and publicly moved toward Proof-of-Reserve-style controls to harden minting.
    • The lasting mitigation lesson is stronger than add reserve checks: issuance paths that depend on chain-specific asset identity should be segregated by contract surface, not merely by configuration assumptions.
  • Key transaction hashes:
    • Representative Ethereum exploit transaction: 0x725f0d65340c859e0f64e72ca8260220c526c3e0ccde530004160809f6177940

Three issuance-authority promotions this incident collapsed

  • A native-asset receipt reached a live mint path. On Ethereum and other non-BTC deployments, an ordinary msg.value deposit could still enter a payable vault entrypoint that only made economic sense when the chain’s native asset really was BTC-like backing.
  • That receipt was over-promoted into canonical BTC-backing truth. _amounts(uint256) performed decimal normalization only, so the protocol treated the native-asset deposit as if it had already crossed the harder boundary from some gas-token amount arrived to this exact amount now proves BTC-domain reserve value.
  • That accepted backing truth still inherited live claim-creation and cash-out authority. Because the vault was an authorized uniBTC minter and downstream venues still honored uniBTC as BTC-like inventory, the bad publication immediately became market-spendable unbacked issuance rather than a quarantined accounting anomaly.

Pattern tags:

Why the exploit worked

  • Bedrock preserved a mint verb whose safety depended on native asset == BTC-like collateral, but it did not bind that assumption to the deployed chain surface. On Ethereum the same function still accepted ETH and treated it as if it were economically commensurate with BTC.
  • The vulnerable path did not perform a bad conversion; it performed no economic conversion at all. _amounts(uint256) only normalized decimals, so the issuance boundary never asked whether the asset entering the vault could actually back a BTC-priced claim.
  • Because the contract was an authorized uniBTC minter, the accounting mistake directly printed canonical supply rather than a secondary wrapper or soft claim that needed later ratification.
  • External liquidity completed the theft path. The exploit was profitable because Uniswap and other venues still priced uniBTC as a BTC-like asset even after the mint boundary had accepted the wrong underlying asset.
  • Operationally, the short disclosure-to-exploit window mattered because once the market sees send 1 ETH, receive roughly 1 uniBTC, the trade no longer requires subtle strategy — only speed.

Detection and audit pivots

  • Enumerate every asset-entry verb for reserve-backed claims and ask what exact asset identity is admissible here? If the answer changes by chain, encode it in the contract surface rather than in offchain assumptions.
  • Review helper functions like _amounts, previewMint, and conversion utilities for dimensional drift. A function that only rescales decimals may still be catastrophically wrong if it is standing in for value conversion.
  • Diff deployment variants against live chain topology. If a repository contains both Vault and VaultWithoutNative, auditors should ask why each chain received one variant and what would break if the wrong one were deployed.
  • Treat cap and supply-limit logic as secondary containment, not primary correctness. A mint path that prices the wrong asset can still be existentially unsafe even if operators expect caps to catch abuse later.
  • During incident review, map downstream liquidity and integration dependence immediately. Once unbacked supply is live, every honest venue still accepting the claim becomes part of the solvency blast radius.

Failure-prevention checklist

  • Do not expose a native-asset mint verb unless the chain’s native asset is actually one of the protocol’s admissible backing assets. If the allowed asset set changes by chain, use a different contract surface or immutable deployment mode instead of one generic payable path plus operator convention.
  • Bind asset identity and value conversion in the same place. A mint path that accepts msg.value should prove both what asset just entered? and what claim quantity may that exact asset authorize?, rather than letting decimal normalization masquerade as economic conversion.
  • Encode chain-surface restrictions directly in code. If non-BTC chains should never accept NATIVE_BTC, reject that branch at deployment or initialization time rather than relying on allowlists and pause flags to stay perfectly configured forever.
  • Treat conversion helpers like _amounts, previewMint, and quote utilities as solvency code. If one helper decides how much uniBTC a deposit mints, it belongs inside the same review scope as minter permissions and reserve accounting.
  • Make caps and supply tracking true secondary brakes. They should limit loss if issuance logic is wrong, but the protocol should still be correct when those brakes are late, misconfigured, or partially unregistered.
  • Separate deployment variants aggressively when economics differ. Vault and VaultWithoutNative should not be swappable operational preferences if one of them can print BTC-denominated claims from the wrong native gas asset.
  • Quarantine fresh mint surfaces before downstream integrations honor them as money-good collateral or swap inventory. New vault deployments, new chain routes, and newly enabled minter paths deserve staged activation and immediate kill switches.
  • Monitor for impossible issuance ratios and cross-asset domain drift. 1 ETH -> ~1 uniBTC should be caught as a production-impossible state long before attackers recycle the claim into external liquidity.

Audit prompts

  • What exact asset identities may enter through each mint verb on this chain, and where is that admissibility rule enforced onchain rather than in deployment runbooks or docs?
  • Does the function that turns deposit quantity into minted claim quantity perform true economic conversion, or is it only preserving decimals while silently assuming asset equivalence?
  • If the repository includes multiple vault variants or deployment modes, what exact invariant decides which one is safe on each chain, and can the wrong variant still be initialized or upgraded into production?
  • Which immutable, initializer, or runtime checks prove that a msg.value path is only live on chains where the native asset really belongs in the reserve domain of the claim being minted?
  • If cap accounting, supply registration, or minter quota logic failed open for one hour, would the core mint path still remain economically correct?
  • What downstream AMMs, lending markets, wrappers, or yield venues would immediately treat newly minted supply as BTC-like truth, and how quickly can that acceptance be paused if issuance is compromised?
  • Are there production alerts for domain-impossible minting outcomes, such as native-asset deposits producing a claim whose market value should be many times larger than the asset that entered?
  • If a payable mint path exists only for one deployment family, why is it present in shared code at all, and what prevents future operators from enabling it in the wrong environment again?

Lessons that should already be standard

  • Decimal normalization is not value conversion. Preserving units while skipping the asset-identity or price-domain question is enough to turn reserve-backed minting into counterfeit issuance.
  • Asset admissibility is part of solvency, not deployment decoration. If a claim is BTC-denominated, did BTC-like backing enter? is the first security question, not an afterthought behind one generic payable function.
  • A codebase that already contains a safe variant and an unsafe variant is still unsafe if the deployment process can put the wrong one behind a live proxy.
  • Cap logic is not a substitute for correctness. A mint path that prices the wrong asset can become fatal before secondary containment notices that supply is growing strangely.
  • Freshly deployed mint surfaces are among the easiest catastrophic bugs to monetize. Once the market sees deposit cheaper asset, receive expensive claim, the attack becomes a race, not a puzzle.
  • External liquidity is often the real redemption rail. A protocol can die from unbacked issuance even without an explicit internal redeem function if the market still honors the printed claim.

Why this incident matters

  • It is a crisp example of issuance-side solvency failure caused by treating asset type as a deployment detail instead of a hard economic invariant.
  • It preserves a recurring lesson that decimal normalization is not value normalization. If a mint path crosses assets, the conversion rule is part of solvency, not a cosmetic implementation detail.
  • It also shows how unbacked-mint incidents often cash out through external liquidity rather than an explicit protocol-native redemption function. Once the protocol has printed a BTC-like claim under false premises, every still-honest downstream venue becomes a redemption rail.

Internal linkages

  • Primary recurring family: issuance-redemption-solvency-asymmetry.
  • Healthy-side issuance companion: reserve-backed-claim-truth-security-object — the stronger positive-design note when the next question is what exact asset-intake proof, claim-creation rule, reserve-liability bucket, and deployment-surface boundary should have stayed fused.
  • Useful dimensional cousin: unit-normalization-failure — the same quantity is correct in its own domain but wrong for solvency instinct appears here, even though Bedrock’s failure lived in issuance math rather than oracle composition.
  • Adjacent standards / surface-selection lesson: shared-standard-entrypoint-invariant-drift — not because Bedrock inherited a stale standard entrypoint, but because the important design error was likewise the wrong public issuance surface stayed live after the real economic boundary moved.
  • Healthy-side asset-admission companion: actual-receipt-accounting-and-token-semantics-admission-discipline — useful when the sharper question is how one reserve-backed mint surface should have distinguished a native asset arrived from this native asset belongs in the same economic domain as the claim being issued.

Control surface