DeFi Strategies
Last updated
Was this helpful?
Last updated
Was this helpful?
The Maple Strategies repository contains three secondary yield-generating strategies that can be added to a Maple Pool. These secondary strategies will be used to park capital before it is deployed to loans (Maple's primary yield-generating source) or to act as instant liquidity that can be called upon to facilitate withdrawal requests.
Each strategy has the ability to deposit pool assets into, and withdraw pool assets from, the underlying protocol.
Pool assets are pulled into the strategy contracts via requestFunds()
in the PoolManager
contract, following the same flow that both open-term and fixed-term Loan Managers do.
Note that a decision was made not to support redeem-style flows that allow 100% of the shares to be redeemed; as a consequence, some dust may be left in the contracts.
There is also a known issue due when attempting withdraw the full assetsUnderManagement()
amount of a strategy in the same block. This is due to 'fees' being withdrawn first leading to a 1 wei
round up in the shares
or aTokens
required resulting in remaining shares
or aTokens
being insufficient by 1 wei
to complete the full withdrawal. This in practice isn't a concern as the txn to withdraw the full amount won't atomically land on chain as the assetsUnderManagement()
is read with the expectation that the yield from a strategy continues to accrue. We can also send 1 wei
less then the max withdrawal amount.
A performance fee is charged on the yield generated from each strategy. A snapshot is taken of the strategy's total assets during deposit()
, withdraw()
, and setStrategyFeeRate()
function calls as lastRecordedTotalAssets
. This snapshot is then compared at the next interaction, and if yield has been generated, the fee is charged and sent to the Maple Treasury.
The strategy fee is calculated using the formula:
Where:
$\text{yieldAccrued}$ is the total yield accrued since the last deposit()
, withdraw()
, or setStrategyFeeRate()
function call.
$\text{strategyFeeRate}$ is the fee rate for the strategy, which can be no greater than $1 \times 10^6$.
$1 \times 10^6$ represents the scaling factor for 100%, declared as the constant HUNDRED_PERCENT
in the contracts.
The Assets Under Management (AUM) is reported at a given block as the value of the strategy in terms of the pool's underlying asset, net of fees. This calculation is unique to each strategy depending on the underlying protocol.
Where:
$\text{currentTotalAssets}$ is the gross value of the strategy at that block, which may increase if yield is generated or decrease if there is a loss in the strategy.
$\text{currentAccruedFees}$ is the calculated fee at that block, which is positive if the strategy has gained value since the last snapshot (lastRecordedTotalAssets
), otherwise 0.
A strategy contract can be in one of the following states:
Refers to the default state of a strategy where it can be used to access all external functions, and it is expected that assetsUnderManagement
is being correctly reported. This can include situations where the strategy is earning yield or even at a loss, as long as it's correctly reported and the underlying assets can be withdrawn.
Please note that when a strategy is reactivated, the protocol admins reserve the ability to update the accounting of lastRecordedTotalAssets
. For example, if lastRecordedTotalAssets
is being over reported, updating this variable upon reactivation ensures that performance fees can be correctly accrued. Conversely, if lastRecordedTotalAssets
is correct, there is no need to update this variable during reactivation.
The Impaired state is expected to be enabled when the assetsUnderManagement
is being correctly reported by the underlying protocol, but there is an issue when withdrawing—for example, if you're unable to get back what is reported in assetsUnderManagement
, or the withdrawal reverts.
While a strategy is impaired, unrealizedLosses
is equal to assetsUnderManagement
. As a result, when a user goes to withdraw from a Maple Pool, the unrealizedLosses
are reduced from totalAssets
in the pool when calculating the value of a share. This is the same mechanism used when loans are impaired.
The benefit here is that users can continue to withdraw if they wish, with the reduced share price ensuring no LP is treated unfairly. However, a strategy can be reactivated, which would cause a jump in totalAssets
equal to the assetsUnderManagement
being reported (by reducing unrealizedLosses
to 0
). Therefore, communication with LPs is important to gauge if a strategy will be reactivated for example, if hacked funds are returned to the external protocol or the external protocol tops up via an insurance fund.
Note:
Calling withdrawFromStrategy()
whilst impaired
skips any fee logic.
If the protocol is impaired
and there is a gain in yield resulting in fees we will set the strategy state back to active
before withdrawing. Withdrawing when impaired
will be reserved for when there is a loss in the strategy. This is cause if there are any fees to be accrued and the admins withdraw in an impaired
state the fees are lost.
The Inactive state is to be used as a last resort, as it detaches the assetsUnderManagement
external calls to the external protocol and returns 0. The main use case of this is if the underlying protocol starts reverting on the external calls; this would block LPs from depositing and withdrawing from a Maple Pool, as the totalAssets
calculation in the PoolManager
relies on the strategy reporting assetsUnderManagement
without reverting.
A secondary use case of Inactive is also to mark a strategy to 0 if it's confirmed that funds cannot be recovered, which is akin to a default.
Note:
Calling withdrawFromStrategy()
whilst inactive
skips any fee logic.
It's a known issue that if the strategy is set back to inactive
-> active
there will be a jump in assetsUnderManagement()
which a new depositor can arbitrage by getting shares for a lower amount of pool assets. To combat this when the inactive
state is used on a strategy the Pool Liquidity Cap will be used to stop new deposits whilst we assess the situation.
The Aave Strategy is designed for a Maple Pool to deploy into a respective Aave V3 Pool with the same underlying asset as the Maple Pool. The strategy uses the balanceOf(aTokenAmount)
of the rebasing aToken held by the strategy contract to report assetsUnderManagement
.
Notes:
The Aave Strategy could potentially accrue additional rewards from Aave which can be claimed using the claimRewards()
function.
The Sky Strategy is designed to make use of the Sky Savings Rate (SSR) by using the Peg Stability Module (PSM). This strategy is specifically made for Maple Pools where the underlying asset is USDC, i.e., the gem
asset on the PSM.
Deposit Flow:
USDC is pulled from the Maple Pool and swapped into USDS via the PSM.
USDS is deposited into sUSDS to earn the SSR.
Withdraw Flow:
sUSDS is burned to redeem USDS.
USDS is swapped via the PSM into USDC and returned back to the Maple Pool.
Notes:
The PSM can have a fee when swapping USDC into USDS (tin
) or when swapping USDS into USDC (tout
).
It's assumed that USDS and USDC are 1:1 in USD value due to the PSM allowing zero-slippage swaps (when tin
and tout
are 0).
When calculating assetsUnderManagement
, alongside netting out performance fees, any tout
is also netted out. It is known that a deposit will result in the assetsUnderManagement
being incremented slightly less than the deposit amount due to PSM fees if set.
The protocol retains the ability to set a new PSM
address in case Sky upgrades their contracts.
It is known that if the PSM
doesn't have enough USDC
liquidity withdrawing can fail. This will be managed by monitoring liquidity.
It is known that if there is a positive tin
that the pools totalAssets()
will drop by the fee amount, whilst unlikely to use this strategy when there is a positive tin
, it is possible for a LP to withdraw prior to the strategy being funded to then redeposit not having their shares "pay" the tin
like all other LPs. This will be managed by monitoring the withdrawal queue which we process as user's can't unilaterally exit.
It is also noted that is the DaiJoin
contract is caged or if the PSM
has halted this can cause the assetsUnderManagement()
to revert in which case the strategy can be either set to Inactive
or if a new PSM
is available we can swap the PSM
. This will be dealt on a case by case basis.
_gemForUsds()
can have a rounding error up to 1e12 USDS
leaving some dust in the contract, this is acceptable given the conversion needed from 1e18 -> 1e6
Each ERC-4626 compliant vault that is being considered for integration will be tested for compatibility first before being onboarded, similar to how ERC-20 tokens are vetted.
Notes:
fundStrategy()
has added slippage protection when depositing assets, where the admin can specify the minSharesOut
for the shares minted by the ERC-4626 vault. Slippage isn't required on the withdrawFromStrategy()
function due to the EIP spec specifying that the assets out must equal the amount requested or revert. (Note: As Aave and Sky contracts are known ahead of time and have adequate liquidity, slippage isn't a concern on the respective strategy contracts.)
Currently, this strategy is only tested to work with sUSDS
, which is compliant with the ERC-4626 standard.
Each ERC-4626 vault will need to be tested to ensure previewRedeem()
does not revert before integrating.
Each ERC-4626 vault will need to be tested to ensure upon withdrawal the amount of shares required to withdraw the known assets doesn't drastically change e.g a slashing event as in this case slippage controls would be required. These controls can be added in a future upgrade if needed or a bespoke strategy contract can be implemented.
All strategies will be deployed as unique proxy instances per pool via the MapleStrategyFactory
contract. The MapleStrategyFactory
contract is a redeployment of a previously audited factory. The proxy pattern is further explained in the Maple Protocol Gitbook linked .
The factories and how they fit into the Maple Protocol can further be visualized in the Updated Protocol Architecture diagram .
To illustrate, please refer to the integration tests , where the Aave V3 USDC Pool is used as the yield-generating strategy.
Addresses on Ethereum Mainnet that will be used for Aave Pools can be found in the address registry .
Addresses on Ethereum Mainnet that will be used for Sky contracts can be found in the address registry .
The Basic Strategy is a generic strategy to be used with any fully ERC-4626 compliant vault. This includes any fees being included as part of the previewRedeem()
call, as per the EIP spec .