DEV Community

ohmygod
ohmygod

Posted on

The Resolv USR Exploit: How a Missing Max-Mint Check Let an Attacker Print $25M From $100K

The Resolv USR Exploit: How a Missing Max-Mint Check Let an Attacker Print $25M From $100K

A deep dive into the March 22 Resolv Labs hack — the anatomy of a two-step minting flaw, compromised key infrastructure, and why on-chain guardrails are non-negotiable for stablecoin protocols.


TL;DR

On March 22, 2026, an attacker exploited Resolv Labs' USR stablecoin minting system by compromising a privileged signing key stored in AWS KMS. Using the requestSwap()completeSwap() flow, they deposited ~$100K–$200K in USDC and minted 80 million unbacked USR tokens — a 400–500x over-mint. The attacker extracted ~$25M in ETH within 17 minutes, crashing USR from $1.00 to $0.025. The root cause: zero on-chain validation between collateral deposited and tokens minted.


The Protocol Design

Resolv is a delta-neutral stablecoin protocol. USR maintains its dollar peg through a collateral pool of ETH, staked ETH, and Bitcoin, hedged with perpetual futures. A second token, RLP (Resolv Liquidity Pool), acts as a junior tranche absorbing losses before USR holders take any hit.

At peak, Resolv held over $500M in TVL and had raised $10M from Coinbase Ventures, Maven 11, and Animoca Brands. The protocol was integrated into Morpho, Aave, Euler, and Curve — deep tentacles across DeFi's lending and DEX infrastructure.

But TVL had been hemorrhaging before the exploit. USR's market cap dropped from ~$400M in early February to ~$100M by mid-March — a 75% contraction in weeks.

The Minting Architecture (And Its Fatal Flaw)

USR minting uses a two-step off-chain approval pattern:

Step 1: requestSwap()

User deposits USDC into the USR Counter contract. This creates a pending minting request on-chain.

Step 2: completeSwap()

An off-chain service holding a privileged key (the SERVICE_ROLE) calls back to finalize the mint, specifying how much USR to issue.

The critical problem: The smart contract enforced a minimum USR output but no maximum. There was:

  • ❌ No on-chain ratio check between collateral deposited and USR minted
  • ❌ No price oracle validation
  • ❌ No hard-coded maximum mint cap
  • ❌ No rate limiting on minting volume

The SERVICE_ROLE was a plain EOA (externally owned address) — not a multisig. Whatever amount it signed off on, the contract would mint. The admin role was a multisig, but the service role — the one with actual minting power — was not.

The Trust Model Was Inverted

What the contract checked:
  ✅ Valid signature from SERVICE_ROLE
  ✅ Minimum output amount met

What the contract didn't check:
  ❌ Deposited amount vs. minted amount ratio
  ❌ Maximum mint per transaction
  ❌ Maximum mint per time window
  ❌ Total supply cap
Enter fullscreen mode Exit fullscreen mode

The entire security model rested on a single assumption: the SERVICE_ROLE key would never be compromised. That's not defense in depth — it's a single point of failure.

The Attack: Step by Step

Phase 1: Infrastructure Compromise

The attacker gained access to Resolv's AWS Key Management Service (KMS) environment where the privileged signing key was stored. With control over the KMS environment, they could use Resolv's own minting key to authorize arbitrary operations.

Phase 2: The Over-Mint

Armed with the signing key, the attacker executed two primary minting transactions:

  1. 50M USR mint — depositing minimal USDC, minting 50 million USR
  2. 30M USR mint — another 30 million USR shortly after

Total: 80 million unbacked USR from ~$100K–$200K in deposits.

Phase 3: The Cashout

The attacker converted USR → wstUSR (wrapped staked USR) to move into a less immediately liquid but more fungible derivative, then:

  1. Dumped across Curve, KyberSwap, and Velodrome
  2. Swapped USR → USDC/USDT → ETH
  3. Extracted ~$23–$25M in ETH

The entire cashout took 17 minutes. By the time USR hit $0.025 on Curve's USR/USDC pool, the attacker was already in ETH.

Phase 4: Failed Follow-Up

On-chain records show the attacker attempted additional mints after the initial 80M, but Resolv paused the protocol before more damage could be done. This underscores why automated circuit breakers are essential.

The Blast Radius

The damage extended far beyond Resolv:

Protocol Exposure Impact
Gauntlet (Morpho) ~$7.5M across USDC Core/Frontier vaults Deposits paused, high utilization
Steakhouse Financial Dedicated Resolv USR vaults Confirmed no direct exposure in main vaults
Euler Labs Under investigation Measures taken to isolate risk
Aave Zero exposure Resolv participated as LP only
Curve pools USR/USDC liquidity drained Severe impermanent loss for LPs

The contagion spread through composability — the exact property that makes DeFi powerful also amplifies exploit damage.

Root Cause Analysis: Three Layers of Failure

Layer 1: Missing On-Chain Invariants

The smart contract was a blank check. Any valid SERVICE_ROLE signature could mint any amount. This is the most fundamental failure — the contract should have enforced:

// What should have existed
require(
    mintAmount <= depositAmount * MAX_MINT_RATIO,
    "Mint exceeds collateral ratio"
);

require(
    totalSupply() + mintAmount <= SUPPLY_CAP,
    "Supply cap exceeded"
);

require(
    recentMints[block.timestamp / WINDOW] + mintAmount <= WINDOW_CAP,
    "Rate limit exceeded"
);
Enter fullscreen mode Exit fullscreen mode

Layer 2: Single-Key Infrastructure

The SERVICE_ROLE was a single EOA, not a multisig. A protocol with $500M TVL had a single private key controlling unlimited minting. Even if the key were a multisig, the lack of on-chain bounds would still be dangerous — but at least it would require compromising multiple signers.

Layer 3: Cloud Key Management

Storing the minting key in AWS KMS is standard practice, but it created a blast radius where cloud infrastructure compromise = unlimited token minting. There was no:

  • Hardware Security Module (HSM) isolation
  • Geographic key splitting
  • Time-locked minting operations
  • Multi-party computation (MPC) for minting signatures

Defensive Patterns: Building Mint-Safe Stablecoins

Pattern 1: On-Chain Mint Bounds

Every minting function should enforce hard limits regardless of who calls it:

uint256 public constant MAX_MINT_PER_TX = 1_000_000e18;     // 1M per tx
uint256 public constant MAX_MINT_PER_HOUR = 10_000_000e18;   // 10M per hour
uint256 public constant MAX_MINT_RATIO = 105;                 // 105% of deposit (5% tolerance)

mapping(uint256 => uint256) public hourlyMints;

function completeSwap(
    uint256 requestId,
    uint256 mintAmount
) external onlyServiceRole {
    SwapRequest memory req = requests[requestId];
    uint256 hour = block.timestamp / 1 hours;

    require(mintAmount <= MAX_MINT_PER_TX, "TX_LIMIT");
    require(hourlyMints[hour] + mintAmount <= MAX_MINT_PER_HOUR, "HOURLY_LIMIT");
    require(mintAmount * 100 <= req.depositAmount * MAX_MINT_RATIO, "RATIO_EXCEEDED");

    hourlyMints[hour] += mintAmount;
    // ... proceed with mint
}
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Timelock + Circuit Breaker

Large mints should be time-delayed with a cancellation window:

function completeSwap(...) external onlyServiceRole {
    if (mintAmount > INSTANT_MINT_THRESHOLD) {
        // Queue for 1-hour delay, allowing guardian cancellation
        pendingMints[requestId] = PendingMint({
            amount: mintAmount,
            executeAfter: block.timestamp + 1 hours,
            cancelled: false
        });
        emit LargeMintQueued(requestId, mintAmount);
        return;
    }
    // Instant mint for small amounts
    _executeMint(requestId, mintAmount);
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Oracle-Backed Ratio Validation

Even with off-chain signing, the contract should validate against an independent price oracle:

function completeSwap(uint256 requestId, uint256 mintAmount) external {
    uint256 depositValue = getOraclePrice(USDC) * req.depositAmount;
    uint256 mintValue = mintAmount; // USR is pegged 1:1

    require(
        mintValue <= depositValue * 110 / 100, // 10% tolerance
        "Mint exceeds oracle-validated deposit value"
    );
}
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Multi-Sig + MPC for Minting Roles

Critical protocol operations should require multiple independent signers:

  • Minting < $100K: 2-of-3 multisig
  • Minting $100K–$1M: 3-of-5 multisig with 30-minute timelock
  • Minting > $1M: 4-of-7 multisig with 6-hour timelock + governance notification

Pattern 5: Real-Time Anomaly Detection

Deploy on-chain monitoring that triggers automatic pauses:

  • Mint-to-deposit ratio > 2x → pause minting
  • Hourly mint volume > 5x rolling average → pause minting
  • Any single mint > 1% of total supply → require additional confirmation

Lessons for Protocol Developers

1. Never Trust Off-Chain Alone

If your smart contract's security depends entirely on an off-chain component behaving correctly, your smart contract is not secure. On-chain invariants are your final line of defense.

2. The "It Can't Be Compromised" Key Will Be Compromised

Every key management system has been compromised at some point — AWS KMS, HSMs, hardware wallets. Design your contracts to limit damage even when keys are stolen.

3. Composability Amplifies Everything

When your token is integrated into lending markets, yield vaults, and DEX pools, an exploit doesn't just affect your protocol — it cascades through DeFi. Build with the assumption that your worst-case scenario affects everyone who touches your token.

4. TVL Decline Is a Signal

Resolv's TVL dropped 75% in the weeks before the exploit. While this may be coincidental, rapid TVL outflows can indicate insiders aware of vulnerabilities, or simply a protocol losing confidence. Monitor TVL trends as a risk indicator.

5. Audit the Trust Boundaries, Not Just the Code

The Resolv contracts were audited. The code worked exactly as designed. The vulnerability was in the trust architecture — the assumption that a single key stored in cloud infrastructure would never be compromised. Audits must evaluate the full system, including off-chain components and key management.

Timeline

Time (UTC) Event
~02:21 AM, Mar 22 First 50M USR minted
~02:25 AM Second 30M USR minted
~02:30 AM USR begins dumping on Curve, KyberSwap
~02:38 AM USR hits $0.025 on Curve USR/USDC pool
~02:45 AM Attacker begins ETH conversion
~03:00 AM Additional mint attempts detected (failed)
~04:00 AM Resolv pauses all protocol functions
Later Mar 22 Resolv issues public statement, offers 10% bounty

Conclusion

The Resolv exploit is a masterclass in what happens when protocol security depends on a single off-chain assumption. The smart contracts were audited and worked as designed — but "as designed" included zero on-chain safeguards against the most obvious attack vector: a compromised minting key.

For every stablecoin protocol builder reading this: your minting function is your protocol's nuclear launch code. Treat it accordingly. On-chain bounds, multi-sig requirements, rate limits, timelocks, and anomaly detection are not optional — they're the difference between a protocol that survives a key compromise and one that prints $25M for an attacker in 17 minutes.


Follow for more DeFi security research, vulnerability analysis, and smart contract auditing insights.

Tags: #defi #security #smartcontracts #solidity #web3 #blockchain #ethereum #stablecoin

Top comments (0)