DeFi Strategies
Overview
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.
Common Functionality
Funding & Withdrawing
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.
Known Issues
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 a1 wei
round up in theshares
oraTokens
required resulting in remainingshares
oraTokens
being insufficient by1 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 theassetsUnderManagement()
is read with the expectation that the yield from a strategy continues to accrue. We can also send1 wei
less then the max withdrawal amount.
Strategy Fees
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()
, orsetStrategyFeeRate()
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.
Assets Under Management
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.
Strategy States
A strategy contract can be in one of the following states:
Active
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.
Impaired
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()
whilstimpaired
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 toactive
before withdrawing. Withdrawing whenimpaired
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 animpaired
state the fees are lost.
Inactive
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()
whilstinactive
skips any fee logic.It's a known issue that if the strategy is set back to
inactive
->active
there will be a jump inassetsUnderManagement()
which a new depositor can arbitrage by getting shares for a lower amount of pool assets. To combat this when theinactive
state is used on a strategy the Pool Liquidity Cap will be used to stop new deposits whilst we assess the situation.
Deployments & Proxy Pattern
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 here.
The factories and how they fit into the Maple Protocol can further be visualized in the Updated Protocol Architecture diagram here.
Aave Strategy
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
.
To illustrate, please refer to the integration tests here, where the Aave V3 USDC Pool is used as the yield-generating strategy.
Notes:
The Aave Strategy could potentially accrue additional rewards from Aave which can be claimed using the
claimRewards()
function.
Addresses on Ethereum Mainnet that will be used for Aave Pools can be found in the address registry here.
Sky Strategy
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
andtout
are 0).When calculating
assetsUnderManagement
, alongside netting out performance fees, anytout
is also netted out. It is known that a deposit will result in theassetsUnderManagement
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.
Known Issues
It is known that if the
PSM
doesn't have enoughUSDC
liquidity withdrawing can fail. This will be managed by monitoring liquidity.It is known that if there is a positive
tin
that the poolstotalAssets()
will drop by the fee amount, whilst unlikely to use this strategy when there is a positivetin
, it is possible for a LP to withdraw prior to the strategy being funded to then redeposit not having their shares "pay" thetin
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 thePSM
has halted this can cause theassetsUnderManagement()
to revert in which case the strategy can be either set toInactive
or if a newPSM
is available we can swap thePSM
. This will be dealt on a case by case basis._gemForUsds()
can have a rounding error up to1e12 USDS
leaving some dust in the contract, this is acceptable given the conversion needed from1e18 -> 1e6
Addresses on Ethereum Mainnet that will be used for Sky contracts can be found in the address registry here.
Basic Strategy
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 here.
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 theminSharesOut
for the shares minted by the ERC-4626 vault. Slippage isn't required on thewithdrawFromStrategy()
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.
Last updated
Was this helpful?