DEV Community

ohmygod
ohmygod

Posted on

The Resolv USR Exploit: How a $100K Deposit Minted 80 Million Unbacked Stablecoins and Crashed USR 75%

TL;DR

On March 22, 2026, an attacker deposited ~$100,000 USDC into Resolv's USR stablecoin protocol and minted approximately 80 million unbacked USR tokens — a 500x amplification. The attacker drained ~$25 million by swapping through DEXs, crashing USR to $0.25. The root cause: the completeSwap function blindly trusted a _mintAmount parameter from an off-chain service without on-chain validation.

This is a textbook case of the trusted oracle anti-pattern — and it happened in production, today.


The Attack Flow

Phase 1: Minimal Collateral Deposit

The attacker address 0x04A288a7789DD6Ade935361a4fB1Ec5db513caEd deposited approximately 100,000 USDC into the USR Counter contract via the requestSwap function. This is a standard entry point — nothing unusual here.

Phase 2: The Amplified Mint

Here's where the vulnerability kicks in. The completeSwap function processed the request and authorized minting of 49.95 million USR tokens — roughly 500x the deposited collateral. A second transaction minted an additional 30 million USR.

The total: ~80 million unbacked USR from a $100K deposit.

Phase 3: Liquidation Cascade

The attacker converted USR → wstUSR → USDC/USDT → ETH across multiple DEXs (Kyber Network, Uniswap). The selling pressure crashed USR from $1.00 to as low as $0.257. Approximately 5,500 ETH (~$25M) was extracted and distributed across multiple addresses:

  • 0x6Db6006c38468CDc0fD7d1c251018b1B696232Ed (5,500 ETH)
  • 0xb945ec1be1f42777f3aa7d683562800b4cdd3890 (additional swapped funds)

Resolv Labs paused all protocol functions shortly after.


Root Cause: The Blind Trust Pattern

The vulnerability is architecturally simple and devastating. Resolv's minting flow works in two phases:

User → requestSwap(collateral) → Off-chain SERVICE_ROLE → completeSwap(_mintAmount)
Enter fullscreen mode Exit fullscreen mode

The completeMint/completeSwap function completely trusts the _mintAmount provided by SERVICE_ROLE — an off-chain service that monitors requests and queries an oracle (Pyth) to determine asset values.

Three possible attack vectors exist for this design:

1. Oracle Manipulation

If the Pyth oracle feed was manipulated to report an inflated USDC price, the off-chain service would calculate an amplified mint amount. However, manipulating Pyth for a major asset like USDC is non-trivial.

2. Compromised Off-Chain Validator

The SERVICE_ROLE private key could have been compromised, allowing the attacker to directly call completeSwap with an arbitrary _mintAmount. This is the most likely vector given the 500x amplification.

3. Missing Amount Validation (The Real Bug)

The smart contract had no on-chain sanity check between the collateral deposited in requestSwap and the amount minted in completeSwap. No upper bound. No ratio check. No independent oracle verification on-chain.

// Pseudocode of the vulnerable pattern
function completeSwap(
    bytes32 requestId,
    uint256 _mintAmount  // ← Blindly trusted from SERVICE_ROLE
) external onlyRole(SERVICE_ROLE) {
    // No check: _mintAmount <= collateral * maxRatio
    // No check: _mintAmount <= totalCollateral * supplyCapRatio
    // Just... mint it.
    _mint(recipient, _mintAmount);
}
Enter fullscreen mode Exit fullscreen mode

This is the trusted intermediary anti-pattern: the contract outsources a critical security decision to an off-chain component and provides no fallback validation.


Why This Matters: The Recurring Pattern

This isn't a novel attack. It's a well-known anti-pattern that keeps killing protocols:

Incident Year Root Cause Loss
Wormhole Bridge 2022 Missing signature validation $320M
Euler Finance 2023 Missing health check $197M
Mango Markets 2022 Oracle price manipulation $114M
Resolv USR 2026 Blind trust in off-chain mint amount ~$25M

The common thread: security-critical validation deferred to a component that can be manipulated.


Defensive Patterns: How to Prevent This

1. On-Chain Invariant Checks

Every minting function should enforce on-chain bounds, regardless of who calls it:

function completeSwap(
    bytes32 requestId,
    uint256 _mintAmount
) external onlyRole(SERVICE_ROLE) {
    SwapRequest memory req = requests[requestId];

    // Hard ceiling: mint cannot exceed collateral * maxRatio
    uint256 maxMint = req.collateralAmount * MAX_MINT_RATIO / PRECISION;
    require(_mintAmount <= maxMint, "Mint exceeds collateral bound");

    // Global supply cap check
    require(totalSupply() + _mintAmount <= SUPPLY_CAP, "Supply cap exceeded");

    _mint(req.recipient, _mintAmount);
}
Enter fullscreen mode Exit fullscreen mode

2. Dual Oracle Verification

Don't rely on a single price source for minting decisions:

function validateMintAmount(
    uint256 collateral,
    uint256 mintAmount
) internal view {
    uint256 pythPrice = pythOracle.getPrice(collateralAsset);
    uint256 chainlinkPrice = chainlinkOracle.getPrice(collateralAsset);

    // Prices must agree within 2% tolerance
    require(
        _withinTolerance(pythPrice, chainlinkPrice, 200),
        "Oracle divergence"
    );

    uint256 expectedMint = collateral * pythPrice / 1e18;
    require(
        mintAmount <= expectedMint * 105 / 100,  // 5% max slippage
        "Mint amount exceeds oracle valuation"
    );
}
Enter fullscreen mode Exit fullscreen mode

3. Rate Limiting and Circuit Breakers

uint256 public constant MAX_MINT_PER_BLOCK = 1_000_000e18; // 1M per block
uint256 public mintedThisBlock;

modifier rateLimited(uint256 amount) {
    if (block.number > lastMintBlock) {
        mintedThisBlock = 0;
        lastMintBlock = block.number;
    }
    mintedThisBlock += amount;
    require(mintedThisBlock <= MAX_MINT_PER_BLOCK, "Rate limit exceeded");
    _;
}
Enter fullscreen mode Exit fullscreen mode

4. Time-Locked Large Mints

For amounts above a threshold, enforce a time delay:

if (_mintAmount > LARGE_MINT_THRESHOLD) {
    pendingMints[requestId] = PendingMint({
        amount: _mintAmount,
        recipient: req.recipient,
        executeAfter: block.timestamp + TIMELOCK_DELAY
    });
    emit LargeMintQueued(requestId, _mintAmount);
    return; // Don't mint immediately
}
Enter fullscreen mode Exit fullscreen mode

Audit Checklist: Finding This Before Attackers Do

When reviewing stablecoin or token minting contracts, check for:

  • [ ] Unbounded mint authority: Can any role mint arbitrary amounts without on-chain validation?
  • [ ] Missing collateral-to-mint ratio checks: Is the relationship between deposited collateral and minted tokens enforced on-chain?
  • [ ] Single point of oracle failure: Does the contract rely on one oracle or one off-chain service for pricing?
  • [ ] No supply caps or rate limits: Can the total supply be inflated without bounds in a single transaction?
  • [ ] Two-phase operations without validation: Does complete* blindly trust parameters from request* without re-validating?
  • [ ] Privileged role = single key: Is SERVICE_ROLE a single EOA, or a multisig/MPC?

Timeline

Time (UTC) Event
~12:00 Mar 22 Attacker deposits 100K USDC, mints 49.95M USR
~12:05 Second mint: 30M additional USR
~12:10-12:30 USR → wstUSR → USDC/USDT → ETH swaps across DEXs
~12:15 USR depegs to $0.40, continues falling
~12:30 USR hits $0.257 on some pools
~12:45 Resolv Labs pauses protocol
~13:00 D2 security researchers publish initial analysis

Key Takeaways

  1. Never blindly trust off-chain services for mint amounts. On-chain invariant checks are your last line of defense.

  2. The 500x amplification should have been caught by any reasonable bound check. A require(_mintAmount <= collateral * 2) would have stopped this cold.

  3. Stablecoin protocols need circuit breakers. If 50 million tokens are minted in one transaction when total supply is orders of magnitude lower, something is wrong.

  4. Two-phase commit patterns (request/complete) need the complete step to re-validate, not just execute. The separation of concerns should add security, not remove it.

  5. This exploit cost $100K to execute. The ROI for the attacker was 250x. That's the asymmetry defenders are up against.


Follow for more DeFi security research. Previous analyses: Aave CAPO Oracle Incident, Solana Noisy Neighbor Attack.


Disclaimer: This analysis is based on publicly available information as of March 22, 2026. The investigation is ongoing. Technical details may be refined as more on-chain data becomes available.

Top comments (0)