Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how →
F-2026-0010·denial-of-service

Inactive Aave target supply can self-seal a full rebalance recovery

Acknowledgedvaultcrosschainepoch-accountinggithub.com/contractlevel/yield-v2
TL;DR

Anyone can supply to Aave on behalf of the still-inactive target adapter and consume the reserve's supply-cap headroom. The rebalance then fails into a recovery that retries the same over-cap supply, sealing the vault in recovery.

Severity
LOW
Impact
LOW
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

Description

Aave V3 lets any caller supply an asset for any onBehalfOf address, and separately rejects a supply that would push reserve supply above the reserve's cap. A caller can therefore mint aTokens directly to a registered Yieldcoin adapter without touching the adapter or the vault, consuming capacity that a pending rebalance needs.

With P the position in the source adapter, C the target reserve's available headroom and U an unsolicited supply credited to the still-inactive target adapter, the honest rebalance fits when P <= C. An attacker chooses U > C - P.

executeRebalance then withdraws the full source position, activates the target, and attempts to deposit P. The deposit fails on the cap. The source withdrawal and the adapter change are already committed and are not rolled back, so the Child stores REBALANCE_DEPOSIT(P) and returns successfully:

code
source adapter = 0
target adapter = U
Child loose balance = P
recovery obligation = REBALANCE_DEPOSIT(P)
Parent rebalance = REBALANCING (source -> target)

Recovery retries exactly P, which still exceeds the remaining headroom, so every retry fails. The distinguishing property is that the blocking U belongs to the Yieldcoin target adapter itself: no unrelated Aave supplier can exit to release it, and no Yieldcoin path can withdraw it either. AaveV3Adapter.withdraw is vault-only, and both vault paths that withdraw — the epoch withdrawal and a further rebalance — are excluded while recovery is pending. Recovery itself only deposits.

The retry is therefore self-sealing: its own precondition is destroyed by a balance the recovering vault holds and cannot release. Parent cannot close an epoch or start a different rebalance while its first rebalance remains in progress, so the position and all outstanding shares are immobilised. Repair requires an external Aave-governance cap increase, or a purpose-built upgrade that can unwind the target, restore the principal, clear Child recovery and reconcile Parent's pending rebalance.

An unprivileged account can immobilise the vault's entire strategy principal and every outstanding share indefinitely, repairable only from outside the protocol, using capital only slightly greater than the target reserve's headroom above the position. That overshoot is forfeited rather than recoverable — against the smallest headroom currently targeted for Aave v3 USDC, roughly 16.5m USDC against a low initial position, an attacker would irrecoverably commit several orders of magnitude more than the value they immobilise, which is what confines the trigger to targets whose headroom sits close to the position. No trusted actor misbehaves: rebalance selection and the TVL observation are truthful throughout, and the only adversarial entry point is Aave's public supply.

Vulnerable scenario

  1. The position P is deployed in Child protocol A. Protocol B's Aave V3 adapter is registered but inactive, and P <= C.
  2. A truthful observation reports the active source TVL. ChildVault.getTVL() reads only the active adapter, and rebalance selection checks APY, not target reserve capacity.
  3. An unprivileged account calls Aave supply(asset, U, targetAdapter, 0) before the selected rebalance executes, with P + U > C.
  4. Parent commits REBALANCING from A to B.
  5. Child clears A, activates B, fails to deposit P, and stores REBALANCE_DEPOSIT(P).
  6. Every retry reproduces the same failure, and no Child or Parent transition can reroute.

Recovery attempts that do not resolve it

Pausing every component, force-cancelling the open deposit, unpausing in dependency order, cancelling the open withdrawal intent, and retrying gives BaseVault__DepositFailed. The authorised Child reroute gives BaseVault__RecoveryAlreadyPending. Parent close and Parent reroute both give ParentVault__RebalanceInProgress. Custody and obligations are unchanged throughout. Calling completeRebalance is not a safe escape either: it would make Parent authoritative for a target that never received the principal.

03Section · Recommendation

Recommendation

Add an authenticated, state-bound abort for a failed asynchronous rebalance deposit. The Child half is straightforward; the Parent half is a new lifecycle transition. Both are needed.

Child side — roll back rather than latch an unsatisfiable retry. On a failed target deposit, withdraw whatever the target holds, restore the previous adapter as active, and redeposit the combined balance there:

solidity
bool success = _executeDeposit(tvlToRebalance, false, newAdapter);
if (success) {
emit RebalanceDepositSuccess(rebalanceNonce, tvlToRebalance);
} else {
(, uint256 reclaimed) = _executeWithdraw(type(uint256).max, false, newAdapter);
_baseVaultStorage().s_activeProtocolAdapter = oldAdapter;
_executeDeposit(tvlToRebalance + reclaimed, true, oldAdapter);
emit RebalanceDepositFailure(rebalanceNonce, tvlToRebalance);
}

Applied to the audited commit this prevents the terminal state above: the principal returns to a yielding position, no recovery is latched, and the unsolicited supply is absorbed into vault accounting instead of sealing the retry. It is a control rather than a shipping patch — it writes the active adapter directly instead of re-resolving it, does not distinguish unsolicited surplus from vault-originated principal, and is not bound to the stored rebalance nonce, source, target and amounts. A production version should do all three.

Parent side. Even with the rollback, Parent remains REBALANCING with no way to stand down, which continues to block epoch settlement. Closing that requires an authenticated failure acknowledgement letting Parent clear the pending rebalance without marking the failed target active. This has not been prototyped here, because it is a new transition across both vaults whose authentication needs designing rather than a local change.

Reverting the failed deposit instead of storing recovery is not sufficient on its own. It preserves the source position, but the blocking supply remains and Parent is still REBALANCING, so it trades an immobilised principal for an immobilised lifecycle without providing an abort.

Selection-time capacity check. Accounting for reserve caps, utilisation and available headroom before initiating a rebalance keeps the required overshoot large enough that the attack stays uneconomical, and is a change to target selection rather than to vault logic. Because the rating above depends on that margin holding, this is the mitigation the severity is conditioned on.

Interim measure. Until both halves exist, do not schedule asynchronous rebalances into adapters whose underlying protocol lets third parties create positions for the adapter address. An off-chain capacity preflight is not sufficient on its own, because the observation can go stale between selection and Child execution.

When to re-rate. The cost ratio moves against the protocol as it grows, because the attacker's cost tracks the reserve's remaining headroom while the damage tracks the position. Below position = headroom / 2 the attacker pays more than the value frozen; above it they pay less, and against the tightest currently targeted headroom that crossover sits near 8m USDC of position on that chain. Evaluate against live headroom at selection time rather than against the cap, since ordinary third-party supply consumes headroom with no attacker involvement, and treat any candidate target whose headroom is close to the position as in scope irrespective of absolute size.

04Section · Resolution

Resolution

Acknowledged and documented as KI-027 in 7b97b61 and 3a8d0c0, which records the margin between the position and live headroom as the condition the rating rests on; nothing on-chain enforces that margin.

05Section · Affected files

Affected files

  • evm/src/vaults/ChildVault.sol:296-337 (executeRebalance, _rebalanceToNewStrategy)
  • evm/src/vaults/ChildVault.sol:364-379 (executeRecovery)
  • evm/src/vaults/BaseVault.sol:359-399 (recovery store and retry)
  • evm/src/modules/adapters/AaveV3Adapter.sol:49-98
F-2026-0010