Skip to main content

AaveV3Strategy

Git Source

Inherits: BaseHealthCheck

Title: AaveV3Strategy

Author: Golem Foundation

Yield-donating strategy that earns yield from Aave V3

Deposits assets into Aave V3 lending pool to earn interest WARNING: THIS CONTRACT IS UNAUDITED AND NOT INTENDED FOR PRODUCTION USE. USE AT YOUR OWN RISK. YIELD FLOW:

  1. Deposits assets into Aave V3 pool
  2. Receives aTokens that automatically accrue interest
  3. On report, profit is minted as shares to donation address DEPOSIT/WITHDRAW LIMITS:
  • Aave V3 has supply caps per asset that limit total deposits
  • Strategy checks available capacity before deposits
  • Withdrawals limited by available liquidity in the pool

Notes:

  • security-contact: [email protected]

  • security: Aave pool must be trusted and not manipulatable

.6 behavior

The .6 strategy wires a RewardsController at construction, reads the Aave data provider dynamically from the addresses provider, accounts for accrued-to-treasury supply-cap headroom, and clears pool approvals after each supply.

Quick Start - What Matters Most

Use this as the Aave V3 yield-donating adapter reference.

Key functions to understand:

  • claimAaveRewards() - Keeper claims Aave supply-side incentives to the donation address
  • sweepAirdrop(token) - Keeper sweeps non-critical ERC-20 balances to the donation address
  • availableDepositLimit - Returns 0 for paused, inactive, or frozen reserves and respects supply caps
  • availableWithdrawLimit - Idle funds remain withdrawable even when Aave exits are constrained

State Variables

addressesProvider

Address of the Aave V3 addresses provider

IPoolAddressesProvider public immutable addressesProvider

pool

Address of the Aave V3 pool

IPool public immutable pool

aToken

Address of the aToken for the underlying asset

address public immutable aToken

rewardsController

Address of Aave V3's RewardsController for liquidity-mining incentives.

Wired at construction so the strategy can claim supply-side emissions if/when Aave governance enables them on a market we use. May be address(0) for chains/markets where no RewardsController exists; in that case claimAaveRewards reverts with a clear message instead of silently no-op'ing on a zero target.

IRewardsController public immutable rewardsController

Functions

constructor

Initializes the Aave V3 strategy

Sets up connections to Aave V3 pool, derives aToken from pool registry, and wires the optional RewardsController for incentive claims.

constructor(
address _addressesProvider,
address _rewardsController,
address _asset,
string memory _name,
string memory _symbol,
address _management,
address _keeper,
address _emergencyAdmin,
address _donationAddress,
bool _enableBurning,
address _tokenizedStrategyAddress
)
BaseHealthCheck(
_asset,
_name,
_symbol,
_management,
_keeper,
_emergencyAdmin,
_donationAddress,
_enableBurning,
_tokenizedStrategyAddress
);

Parameters

NameTypeDescription
_addressesProvideraddressAddress of Aave V3 addresses provider
_rewardsControlleraddressAddress of Aave V3 RewardsController (may be address(0) to disable)
_assetaddressAddress of the underlying asset (must be supported by Aave pool)
_namestringStrategy display name (e.g., "Octant Aave V3 USDC Strategy")
_symbolstringStrategy share token symbol (e.g., "osAAVE")
_managementaddressAddress with management permissions
_keeperaddressAddress authorized to call report() and tend()
_emergencyAdminaddressAddress authorized for emergency shutdown
_donationAddressaddressAddress receiving minted profit shares
_enableBurningboolTrue to enable loss protection via share burning
_tokenizedStrategyAddressaddressAddress of TokenizedStrategy implementation contract

claimAaveRewards

Claims all accrued Aave V3 supply-side rewards on this strategy's aToken position and forwards them directly to the dragon router.

The hook is keeper-callable so it can be folded into the same cron that calls report(). When no emissions are configured for the aToken the call is a no-op (returns empty arrays). Off-chain Merit/Merkl claims are out of scope -- those rely on Merkle proofs against an external curator and must be handled by the Octant multisig, not the strategy. Reverts on address(0) rewardsController so a misconfigured deployment is surfaced loudly instead of silently swallowing claim attempts. The dragon router used as to MUST be capable of accepting arbitrary ERC-20s. EOA / splitter routers are fine; an immutable single-asset router would lose any non-asset reward token.

function claimAaveRewards() external onlyKeepers returns (address[] memory rewardsList, uint256[] memory amounts);

Returns

NameTypeDescription
rewardsListaddress[]Reward token addresses claimed (may be empty)
amountsuint256[]Reward amounts transferred to the dragon router

sweepAirdrop

Sweeps non-critical ERC-20 balances on this strategy address to the dragon router. Mirrors SparkStrategy.sweepAirdrop so the operational interface is consistent across strategies.

Used to forward airdrops, off-chain reward distributions (Merkl/Merit settled by an off-chain curator), and any stray ERC-20 that lands on the strategy address. Excludes the strategy asset and the aToken to protect the deployed position.

function sweepAirdrop(address _token) external onlyKeepers;

Parameters

NameTypeDescription
_tokenaddressAddress of the token to sweep (must NOT be asset or aToken)

dataProvider

Returns the current Aave V3 pool data provider.

Resolved from addressesProvider on every call so the strategy picks up governance-driven PoolDataProvider rotations without redeployment. The few hundred extra gas per limit query is the cost of not silently reading stale supply caps / pause flags.

function dataProvider() public view returns (IPoolDataProvider);

Returns

NameTypeDescription
<none>IPoolDataProviderCurrent IPoolDataProvider instance reported by the addresses provider.

availableDepositLimit

Returns maximum additional assets that can be deposited

Checks Aave V3 supply cap and subtracts current supply. DUST CAVEAT: Aave mints aTokens proportional to amount / liquidityIndex. For sub-unit deposits on a high-index reserve, the scaled mint amount can round to zero and pool.supply reverts with INVALID_MINT_AMOUNT. This view does NOT pre-filter such dust; integrators relying on maxDeposit should be prepared for Aave reverts on amounts below the per-reserve scaled-unit floor. A pre-flight check is intentionally omitted to keep the hot path cheap for the typical case where deposits are far above the dust threshold.

function availableDepositLimit(
address /*_owner*/
)
public
view
override
returns (uint256);

Returns

NameTypeDescription
<none>uint256limit Maximum additional deposit amount in asset base units

_cappedTotalSupply

Returns the cap-denominator view of total supplied assets, rayMul-scaled into underlying units to match Aave's validateSupply: totalAToken + rayMul(accruedToTreasuryScaled, nextLiquidityIndex), ceiling-rounded so headroom stays conservative. liquidityIndex is read via IPool.getReserveNormalizedIncome — this is the projected nextLiquidityIndex Aave uses, avoiding the few-seconds-of- accrual gap vs the stored liquidityIndex on the data provider. Uses IPoolDataProviderSlim (3-return view of getReserveData) instead of the full 12-return interface: the narrower ABI decoder keeps the call site within the EVM stack budget under the --no-via-ir + --no-optimizer forge coverage build. Identical on-chain behavior — Solidity decodes the first three 32-byte slots of the return data and stops.

function _cappedTotalSupply() internal view returns (uint256);

availableWithdrawLimit

Returns maximum assets withdrawable without expected loss

Checks pool liquidity to ensure withdrawals won't fail due to high utilization. DUST CAVEAT: mirrors availableDepositLimit. Aave burns aTokens proportional to amount / liquidityIndex; sub-unit withdrawals can round to a zero scaled burn amount and revert with INVALID_BURN_AMOUNT. This view does NOT pre-filter dust amounts; callers must handle Aave reverts on amounts below the per-reserve scaled-unit floor.

function availableWithdrawLimit(
address /*_owner*/
)
public
view
override
returns (uint256);

Returns

NameTypeDescription
<none>uint256limit Maximum withdrawal amount in asset base units

_deployFunds

Deposits idle assets into Aave V3 pool. We used to grant type(uint256).max allowance to the pool in the constructor. The Aave pool is an upgradeable proxy controlled by external governance, so a standing max-approval would let a hostile upgrade drain the strategy's idle balance at any time. We now approve exactly _amount for the call and clear the allowance afterward, even if a broken counterparty returns after pulling less than _amount. forceApprove handles non-standard tokens that require clearing an existing non-zero allowance first.

function _deployFunds(uint256 _amount) internal override;

Parameters

NameTypeDescription
_amountuint256Amount of assets to deploy in asset base units

_freeFunds

Withdraws assets from Aave V3 pool

function _freeFunds(uint256 _amount) internal override;

Parameters

NameTypeDescription
_amountuint256Amount of assets to withdraw in asset base units

_emergencyWithdraw

Emergency withdrawal after strategy shutdown. AAVE DEPENDENCY: this path delegates to _freeFunds, which calls pool.withdraw(asset, amount, this) with no fallback. Emergency withdrawal therefore depends on Aave pool state -- if Aave has paused the reserve, marked it inactive, or utilization is too high for the requested amount, the call reverts and no funds are pulled. OPERATIONAL RUNBOOK when Aave blocks an emergency exit:

  1. Call shutdownStrategy() first -- this marks the strategy shutdown for new deposits independently of Aave state.
  2. Call emergencyWithdraw(amount) with amount capped at the Aave-backed portion of availableWithdrawLimit(address(0)). The view already accounts for pool liquidity; on a paused or inactive reserve it surfaces only idle assets, which are already outside Aave and remain withdrawable through regular user exits.
  3. Repeat step 2 as Aave liquidity returns or pauses lift. A strategy stuck on a paused reserve recovers automatically once Aave governance resumes the pool; no contract-level rescue is possible without trusting the Aave counterparty.
function _emergencyWithdraw(uint256 _amount) internal override;

Parameters

NameTypeDescription
_amountuint256Amount of assets to withdraw in asset base units

_harvestAndReport

Reports current total assets under management

function _harvestAndReport() internal view override returns (uint256 _totalAssets);

Returns

NameTypeDescription
_totalAssetsuint256Sum of aToken balance and idle assets in asset base units

Events

AaveRewardsClaimed

Emitted on a successful claimAaveRewards call.

event AaveRewardsClaimed(address indexed to, address[] rewardsList, uint256[] amounts);

TokenSwept

Emitted on a successful sweepAirdrop call.

event TokenSwept(address indexed token, uint256 amount, address indexed recipient);