AaveV3Strategy
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:
- Deposits assets into Aave V3 pool
- Receives aTokens that automatically accrue interest
- 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
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 addresssweepAirdrop(token)- Keeper sweeps non-critical ERC-20 balances to the donation addressavailableDepositLimit- Returns 0 for paused, inactive, or frozen reserves and respects supply capsavailableWithdrawLimit- 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
| Name | Type | Description |
|---|---|---|
_addressesProvider | address | Address of Aave V3 addresses provider |
_rewardsController | address | Address of Aave V3 RewardsController (may be address(0) to disable) |
_asset | address | Address of the underlying asset (must be supported by Aave pool) |
_name | string | Strategy display name (e.g., "Octant Aave V3 USDC Strategy") |
_symbol | string | Strategy share token symbol (e.g., "osAAVE") |
_management | address | Address with management permissions |
_keeper | address | Address authorized to call report() and tend() |
_emergencyAdmin | address | Address authorized for emergency shutdown |
_donationAddress | address | Address receiving minted profit shares |
_enableBurning | bool | True to enable loss protection via share burning |
_tokenizedStrategyAddress | address | Address 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
| Name | Type | Description |
|---|---|---|
rewardsList | address[] | Reward token addresses claimed (may be empty) |
amounts | uint256[] | 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
| Name | Type | Description |
|---|---|---|
_token | address | Address 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
| Name | Type | Description |
|---|---|---|
<none> | IPoolDataProvider | Current 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
| Name | Type | Description |
|---|---|---|
<none> | uint256 | limit 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
| Name | Type | Description |
|---|---|---|
<none> | uint256 | limit 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
| Name | Type | Description |
|---|---|---|
_amount | uint256 | Amount of assets to deploy in asset base units |
_freeFunds
Withdraws assets from Aave V3 pool
function _freeFunds(uint256 _amount) internal override;
Parameters
| Name | Type | Description |
|---|---|---|
_amount | uint256 | Amount 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:
- Call
shutdownStrategy()first -- this marks the strategy shutdown for new deposits independently of Aave state. - Call
emergencyWithdraw(amount)withamountcapped at the Aave-backed portion ofavailableWithdrawLimit(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. - 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
| Name | Type | Description |
|---|---|---|
_amount | uint256 | Amount of assets to withdraw in asset base units |
_harvestAndReport
Reports current total assets under management
function _harvestAndReport() internal view override returns (uint256 _totalAssets);
Returns
| Name | Type | Description |
|---|---|---|
_totalAssets | uint256 | Sum 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);