Reproduced Exploit

BarnBridge SMART Yield — DAO Controller Swap → CompoundProvider Approval Drain

1. BarnBridge SMART Yield’s CompoundProvider trusts its controller for privileged pulls: _takeUnderlying is gated only by onlySmartYieldOrController and will transferFrom any from_ that still approves the provider (CompoundProvider.sol:132-143).

Jul 2026Ethereumgovernance6 min read

Loss

776,575.547933 USDC (~$776K) across two drain batches

Chain

Ethereum

Category

governance

Date

Jul 2026

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Original research reproduction (not from DeFiHackLabs). Standalone Foundry PoC, offline anvil_state.json, and full write-up: 2026-07-BarnBridgeSmartYield_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/governance/proposal-manipulation · vuln/dependency/upgradeable-contract · vuln/access-control/broken-logic · vuln/logic/missing-allowance

Reproduction: the PoC compiles & runs in an isolated Foundry project at this project folder. The fork is served offline from the bundled anvil_state.json (local anvil replays Ethereum state at block 25535119), so no public RPC is required. Full verbose trace: output.txt. Verified on-chain sources: CompoundProvider.sol (BarnBridge SMART Yield cUSDC provider at 0xDAA0…8310not Compound Protocol core).


Key info#

Loss776,575.547933 USDC (~$776K) across two drain batches
PoC scopeFull USDC path: batch1 774,943.379409 + batch2 1,632.168524 USDC
ProtocolBarnBridge SMART Yield (bb_cUSDC) — not Compound Protocol
CompoundProvider0xDAA037F99d168b552c0c61B7Fb64cF7819D78310
SmartYield0x4B8d90D68F26DEF303Dcb6CFc9b63A1aAEC15840 (bb_cUSDC)
Old controller0x41Ab25709e0C3EDf027F6099963fE9AD3EBaB3A3
Attacker controller proxy0x66c6f3b4B4b458e6d764759Ecf122484ebEf7580
Malicious impl0x769a9fa1e2414db14b35c46e4095d6e8f1694565 (unverified)
DAO Governor0x4cae362d7f227e3d306f70ce4878e245563f3069
Attacker EOA0xF908610E9174c7cd6e9dfD371e238be4511297A1
Drain #10xd191fead…5afb (block 25535120)
Drain #20x7d722637…d238 (block 25535160)
Chain / fork / dateEthereum mainnet / fork 25535119 (post-upgradeTo, pre-drain) / ~2026-07-15
CompilerSolidity 0.7.6 (CompoundProvider)
Bug classDAO-authorized setController → attacker proxy → malicious upgradeTo → privileged _takeUnderlying sweeps standing USDC approvals

TL;DR#

  1. BarnBridge SMART Yield’s CompoundProvider trusts its controller for privileged pulls: _takeUnderlying is gated only by onlySmartYieldOrController and will transferFrom any from_ that still approves the provider (CompoundProvider.sol:132-143).

  2. setController is callable by the current controller or the DAO (CompoundProvider.sol:106-118). The attacker used BarnBridge governance to point the provider at an attacker-deployed proxy whose admin is the attacker EOA.

  3. After the DAO swap landed, the attacker upgradeTo’d a malicious implementation that batch-calls _takeUnderlying(user, amount) for ~50+ residual USDC approvers, then transferFees() which pays the entire provider USDC balance to controller.feesOwner() (hard-coded attacker) (CompoundProvider.sol:208-221).

  4. Clarification (ExVul): this is BarnBridge infrastructure, not a Compound Protocol core bug. The name CompoundProvider only means the provider integrates Compound’s cUSDC market as a yield venue.

  5. This PoC forks post-upgrade (block 25535119) and replays both historical drain calldatas for the exact on-chain profit 776,575.547933 USDC.


Background#

Protocol model#

flowchart TB subgraph users [Users with standing USDC approvals] U1[User 1] U2[User N] end subgraph bb [BarnBridge SMART Yield cUSDC] SY[SmartYield bb_cUSDC] CP[CompoundProvider] CTL[Controller] end subgraph compound [Compound Protocol - not compromised] cUSDC[cUSDC market] end DAO[BarnBridge DAO Governor] U1 -->|USDC approve| CP U2 -->|USDC approve| CP SY --> CP CTL -->|_takeUnderlying / transferFees| CP CP -->|mint/redeem| cUSDC DAO -->|setController| CP

Users historically approved CompoundProvider so senior/junior bond flows could pull USDC. Those approvals remained after the product went quiet — latent surface for any entity that becomes controller.

Privileged entry points#

FunctionGuardEffect
_takeUnderlying(from, amt)onlySmartYieldOrControllerUSDC.transferFrom(from, provider, amt)no check that from is a legitimate depositor
transferFees()none (public)redeem fee cTokens if any; USDC.transfer(controller.feesOwner(), balance)
setController(new)onlyControllerOrDaorewires trust root; updates COMP allowance to new controller

Root cause#

1. Governance can rewire the controller#

SOLIDITY
// CompoundProvider.sol
function setController(address newController_)
  external override
  onlyControllerOrDao
{
  // ... revoke COMP allowance on old controller ...
  controller = newController_;
  updateAllowances();
}

Whoever controls the DAO (or the current controller) can install an arbitrary address as controller. There is no allowlist, timelock-on-provider, or two-step acceptance by the new controller.

2. Controller is fully trusted for user pulls#

SOLIDITY
function _takeUnderlying(address from_, uint256 underlyingAmount_)
  external override
  onlySmartYieldOrController
{
    IERC20(uToken).safeTransferFrom(from_, address(this), underlyingAmount_);
    // balance delta check only
}

Once the attacker’s proxy is controller, it can pull any residual USDC allowance to the provider — the original product intent (deposit plumbing) becomes an approval drain.

3. transferFees is a permissionless forwarder to feesOwner#

SOLIDITY
function transferFees() external override {
  _withdrawProviderInternal(underlyingFees, 0);
  underlyingFees = 0;
  uint256 fees = IERC20(uToken).balanceOf(address(this));
  address to = CompoundController(controller).feesOwner();
  IERC20(uToken).safeTransfer(to, fees);
}

After _takeUnderlying aggregates USDC on the provider, a single transferFees() ships the balance to whatever feesOwner() the malicious controller returns (attacker EOA).


Attack path (on-chain chronology)#

BlockAction
25472222Attacker deploys controller proxy 0x66c6… with initial impl = old legitimate controller 0x41ab…, admin = attacker
25472231propose(...) on BarnBridge DAO to setController(0x66c6…) (and related wiring)
25507914castVote
25508121queue (+ abrogation proposal started — did not stop execution)
25535097DAO execution: CompoundProvider.controller flips 0x41ab…0x66c6…
25535106Attacker deploys malicious impl 0x769a…
25535107upgradeTo(0x769a…) on the proxy (admin = attacker)
25535120Drain batch 1 — 774,943.379409 USDC (~50 victims)
25535160Drain batch 2 — 1,632.168524 USDC (~42 victims)

Malicious entrypoint (selector 0xe321fa05) roughly:

TEXT
drain(provider, address[] users, uint256[] amounts):
  for i in users:
    if amounts[i] != 0:
      provider._takeUnderlying(users[i], amounts[i])
  provider.transferFees()   // → feesOwner = attacker

PoC notes#

  • Scope: post-upgrade fork at 25535119; historical calldata replay of both drain transactions (governance + upgradeTo already mined — heavy to re-simulate full DAO voting, and the economic impact is entirely in the pull phase).
  • Assertion: attacker USDC delta == 776_575_547_933 (6 decimals).
  • Offline: anvil_state.json + createSelectFork("http://127.0.0.1:8545", 25535119).
BASH
# offline (after warm)
bash _shared/run-poc/run_poc.sh 2026-07-BarnBridgeSmartYield_exp -vv

Mitigations (retrospective)#

  1. Two-step controller rotation with explicit accept by the new controller, plus a delay / guardian veto on setController.
  2. Expire or scope user approvals — pull only during an active deposit session; do not leave unlimited USDC allowances on a product that is winding down.
  3. Bound _takeUnderlying to known bond/deposit accounting (msg.sender path must match a live position), not arbitrary (from, amount).
  4. Gate transferFees to controller/DAO and separate fee accrual from arbitrary provider balances created by _takeUnderlying.
  5. DAO hygiene — low-activity governance on legacy products is an attack surface; renounce or freeze controller changes when TVL is residual approvals only.

References#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.