Given that the AAcA (Aave Asset class Allowlist) is currently a work-in-progress and the interest of the community in getting our technical analysis on the asset, we present it to the community.
syrupUSDC technical analysis
Summary
This is a technical analysis of all the smart contracts of the syrupUSDC asset and its main dependencies.
Disclosure: This is not an exhaustive security review of the asset, as performed by the Maple Team, but rather an analysis from an Aave technical service provider on various aspects we consider critical to review before a new type of listing.
Consequently, like with any security review, this is not an absolute statement about whether the asset is flawless/not. Only our opinion in what concerns problems with is integration with Aave and/or additional technical risks.
Analysis
The syrupUSDC is a yield-bearing, strategy-based asset that primarily generates yield through the allocation of funds to the Maple Lending Protocol. Additionally, it earns variable yields by deploying funds across various DeFi protocols. Users can deposit USDC to receive syrupUSDC, which accrues a fixed yield from these strategies. Institutional loans can access liquidity after passing Maple’s KYC requirements.
For the context of this analysis, our focus has been on the following aspects, critical for the correct and secure integration with Aave:
- Mechanism to update the exchange rate of the asset for the underlying, in this case, USDC.
- A recommendation of pricing strategy to be used in the integration asset <> Aave.
- Any miscellaneous aspect of the code we can consider important.
- Analysis of the access control (ownerships, admin roles) and the nature of the entities involved in the system. Regarding the table permissions’ holders and their criticality/risk, it is done following these guidelines:
| Criticality | Description |
|---|---|
| CRITICAL | Usually super-admin functionality: it can compromise the system by completely changing its fundamentals, leading to loss of funds if misused or exploited. E.g. proxy admin, default admin |
| HIGH | It can control several parts of the system with some risk of losing funds. E.g., general owners or admin roles involved in the flow of funds |
| MEDIUM | It can cause malfunction and/or minor financial losses if misused or exploited. E.g., fee setter, fee recipient addresses |
| LOW | It can cause system malfunctions but on non-critical parts without meaningful/direct financial losses. E.g., updating descriptions or certain non-critical parameters. |
| Risk | Description |
|---|---|
| The role is controlled via a mechanism we consider safe, such as on-chain governance, a timelock contract, or setups involving multi-sigs under certain circumstances. | |
| The role is controlled in a way that could expose the system and users to some risk depending on the actions it can control. | |
| The role is controlled via a clearly non-secure method, representing risks for the system and users. |
General points
- The system is divided into upgradable and non-upgradable contracts.
- For proxies, the system uses the Proxy standard from the MapleProxyFactory.
- The MapleProxyFactory is governed by the Maple Governor, a 4-of-7 multisig held by founders and partners of the protocol.
Contracts
The following is a non-exhaustive overview of the main smart contracts involved with syrupUSDC.
syrupUSDC
The so-called syrupUSDC is the system’s core contract, implementing the ERC4626 standard that allows users to exchange USDC for the yield-bearing syrupUSDC token. It is a non-upgradable contract that has all permission operations controlled by the PoolManager.
- Access Control
- All functions in syrupUSDC are validated against the
PoolManager.canCall()method, which evaluates whether the operations can be performed.
- All functions in syrupUSDC are validated against the
- Deposits and Redemptions
- Users can deposit USDC and receive syrupUSDC by calling the
deposit(assets)function. Internally it calculates the amount of shares viapreviewDeposit(assets)getter. Alternatively, users can call themint(shares)or use thedepositWithPermitandmintWithPermit. - Users can initiate a redemption via the
requestRedeem(shares)function. The shares will then be moved to the WithdrawalManagerQueue contract by the PoolManager via theWithdrawalManagerQueue.addShares(shares, owner)that creates the request into a FIFO queue. - After creating the request and the conditions are met (e.g, enough time has passed for users who locked their shares), the user can call the
redeem(shares), which will invoke the Pool Manager and WithdrawalManagerQueue to calculate the amount of assets via the internal_calculateRedemption()method in the WithdrawalManagerQueue. - Users can cancel the request via the
removeShares(amount)function and reclaim their syrupUSDC shares.
- Users can deposit USDC and receive syrupUSDC by calling the
- Exchange Rate
- The system utilizes two distinct exchange rates: one for processing new deposits and the other for finalizing redemptions.
- The deposit exchange rate relies on the
previewDeposit(assets), which is calculated using the standard pattern of ERC4626shares * totalSupply() / totalAssets(). ThetotalAssets()is fetched fromPoolManager.totalAssets(), which returns the number of USDC held by the pool viaUSDC.balanceOf(syrupUSDC)plus the sum of USDC managed by all syrupUSDC strategies. - The redemption exchange rate is calculated in the internal
_calculateRedemption(sharesToRedeem)getter of the WithdrawalManagerQueue contract using the formulatotalAssetsWithLosses * sharesToRedeem / totalSupply(). ThetotalAssetsWithLossesvariable is thetotalAssets()minus the sum of unrealized losses by all syrupUSDC strategies. - The
_calculateRedemption()also also verifies theavailableLiquidityin the pool viaUSDC.balanceOf(syrupUSDC), and if not enough liquidity, the final redemption amount will be adjusted and calculated asavailableLiquidity * sharesToRedeem / totalSupply(). - It is important to mention that the system utilizes
asset.balanceOf(pool)as part of its accounting, which may result in donations of USDC to the system, potentially manipulating the exchange rate.
- The deposit exchange rate relies on the
- The system utilizes two distinct exchange rates: one for processing new deposits and the other for finalizing redemptions.
PoolManager
The Pool Manager is a master contract and core admin of the system. It holds the assets’ accounting of the pool and the admin functions to configure the system. It is an upgradable Proxy contract.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| PoolDelegate - MPC (call time-locked) | upgrade | CRITICAL | |
| SecurityAdmin - Safe 3-of-6 | upgrade | CRITICAL | |
| factory (time-locked) | setImplementation, migrate | CRITICAL | |
| ProtocolAdmins: Governor, PoolDelegate, OperationalAdmin (Safe 3-of-5) | setPendingPoolDelegate, setIsStrategy, finishCollateralLiquidation, triggerDefault, addStrategy, setDelegateManagementFeeRate, setLiquidityCap, setPoolPermissionManager | CRITICAL | |
| PoolDelegate - MPC: Syrup Deployer | withdrawCover | HIGH | |
| syrupUSDC | requestRedeem, processRedeem, removeShares, | HIGH | |
| Strategies | requestFunds | HIGH |
- Access Control
- ProtocolAdmins
- A New pool delegate (which is a strategy manager and funds allocator of the system) can be set in a two-step process via the
setPendingPoolDelegate(address)function. To finalize, the new delegate must confirm the transfer by calling theacceptPoolDelegate()function. - The pool permission manager contract can be set via the
setPoolPermissionManager(address). Internally, it verifies if the contract is an of Maple’s contracts. - A strategy can be created via the
addStrategy(address strategyFactory, bytes extraDeploymentData)function. It verifies if thestrategyFactoryis an instance of Maple contracts and callsstrategyFactory.createInstance(extraDeploymentData), which deploys the new strategy contract. It finishes by enabling theisStrategymapping and adding to thestrategyList. - Strategies can be toggled as valid via the
setIsStrategy(strategy, isStrategy)function It requires that the strategy address was previously included in theStrategyList. - Liquidations can be executed against the borrower’s collateral in the event of non-compliance with payment obligations within the specified period via the
triggerDefault(address loan, address liquidatorFactory)method.
Internally, it gets the lender contract via the loan address and calls thelender.triggerDefault(loan, liquidatorFactory)which returns whether the liquidation is finalized, the totallosses, and the platformfees. If the liquidation is finalized, it triggers the internalhandleCover(losses, fees)function, which calculates the amount that the PoolDelegateCover can pay asfeesto the treasury and thelossesto the pool. Otherwise, the Admin or delegate must callfinishCollateralLiquidation(address loan).
It is important to note that liquidations are only applied to strategies that allocate funds to the Maple Lending Protocol, not to DeFi strategies. - A liquidation that is not finalized can be terminated by calling the function
finishCollateralLiquidation(address loan). Internally, it gets thelendercontract via the loan address and callslender.finishCollateralLiquidation(loan)which returns the totallossesand the platformfees. It finishes by calling the internalhandleCover(losses, fees). - The liquidity cap is configured via the
setLiquidityCap(amount)method. - The delegate fee management can be set via the
setDelegateManagementFeeRate(fee)method.
- A New pool delegate (which is a strategy manager and funds allocator of the system) can be set in a two-step process via the
- PoolDelegate
- The funds from a liquidation (known as the cover amount ) can be withdrawn from the PoolDelegateCover and sent to an arbitrary address via the
withdrawCover(amount, recipient)function. It invokes thePoolDelegateCover.moveFunds(amount, recipient)and ensures thePoolDelegateCoverbalance doesn’t fall below the minimum set for the PoolManager contract. Currently, the minimum is set to zero. - It is important to highlight that The Pool Delegate Cover is not actively in use across the protocol.
- The funds from a liquidation (known as the cover amount ) can be withdrawn from the PoolDelegateCover and sent to an arbitrary address via the
- Pool
- The
requestRedeem(),processRedeem(), andremoveShares()functions are initiated by users via the syrupUSDC Pool and forwarded to the WithdrawalManagerQueue.
- The
- ProtocolAdmins
- Fund Strategies
- Strategies can request funds from the pool via the
requestFunds(address destination, amount)function. The call must be initiated by a valid strategy instance of Maple contracts and registered in the Pool Manager. This function also ensures, internally, that the pool balance doesn’t fall below the liquidity reserved for withdrawals.
- Strategies can request funds from the pool via the
PoolDelegateCover
The PoolDelegateCover is a contract that facilitates funds transfer and is the recipient of funds from liquidations. It’s a non-upgradable contract.
It is important to mention that the team has confirmed that the Pool Delegate Cover is not actively in use across the protocol. Maple previously supported external Pool Delegates to align economically with lenders.
- Access Control
- The PoolManager can transfer funds from this contract to any address via the
moveFunds(amount, recipient)function.
- The PoolManager can transfer funds from this contract to any address via the
WithdrawalManagerQueue
This contract allows users to submit withdrawal requests and holds custody of their shares until the request is processed. Once the withdrawal request is processed the shares in custody will be exchanged for assets using the current exchange rate and then transferred to the owner of the shares.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| PoolDelegate - MPC (call time-locked) | upgrade | CRITICAL | |
| SecurityAdmin - Safe 3-of-6 | upgrade | CRITICAL | |
| factory | setImplementation, migrate | CRITICAL | |
| Redeemers: PoolDelegate, Governor, OperationalAdmin (Safe 3-of-5) | processRedemptions | HIGH | |
| ProtocolAdmins: PoolDelegate, Governor, OperationalAdmin (Safe 3-of-5) | removeRequest, setManualWithdrawal | HIGH | |
| PoolManager | addShares, processExit, removeShares | HIGH |
- Access Control
- Redeemers
- The
processRedemptions(shares)function finalizes the max number of redemption requests possible given the amount of shares. It calls the internalcalculateRedemption(shares)function to obtain theredeemable sharesamount and iterates through the maximum requests that theredeemable sharescan support. Every request is processed via the internal_processRequest(requestId, amount)function, which checks whether the user opted to manually withdraw later via theredeem()function in the Pool contract or to automatically receive the underlying asset.
- The
- ProtocolAdmins or PoolDelegate
- A redemption request from any user can be cancelled via the
removeRequest(user)function. The shares are sent back to theuser, and the request is deleted. - The manual withdrawal can be toggled from any user request via the
setManualWithdrawal(user, isManual)function.
- A redemption request from any user can be cancelled via the
- PoolManager
- During the redemption request by a user in the Pool, the PoolManager takes the user shares and transfers them to the WithdrawalManager contract via the
addShares(shares, owner)method, which creates the request in the queue. - When the user finalizes the redemption in the Pool, the PoolManager calls the
processExit(shares, owner)to calculate the correct amount of shares that the user will receive. - If a user cancels their redemption request in the Pool, the PoolManager forwards it by calling the
removeShares(amount, owner)function, which decreases the shares or deletes the request completely, depending on theamount. Then, it finalizes by transferring the syrupUSDC shares back to theowner.
- During the redemption request by a user in the Pool, the PoolManager takes the user shares and transfers them to the WithdrawalManager contract via the
- Redeemers
Strategies
SyrupUSDC employs various strategies to generate yield for its lenders. There are two main strategies:
- Loan Managers, who act as the principal yield source for the protocol, operate with fixed and open terms.
- DeFi Strategies (for example, Aave and Sky) serve as a secondary yield source for the protocol, used to park funds that will be later utilized by Loan Managers or to fulfill withdrawal requests.
LoanManagers
The Loan Managers (Open and Fixed) are responsible for interacting with and managing their specific loan types on behalf of the Pool Manager, meaning they handle the flow of funds and, most importantly, provide the total assets under management to the Pool Manager’s account. They’re upgradable Proxy contracts.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| PoolDelegate - MPC (call time-locked) | upgrade | CRITICAL | |
| SecurityAdmin - Safe 3-of-6 | upgrade | CRITICAL | |
| factory | setImplementation, migrate | CRITICAL | |
| PoolDelegate - MPC: Syrup Deployer | fund, proposeNewTerms, rejectNewTerms, acceptNewTerms | HIGH | |
| PoolDelegate or Governor | setAllowedSlippage, impairLoan | HIGH | |
| Governor | removeLoanImpairment | HIGH | |
| PoolManager | triggerDefault, finishCollateralLiquidation | HIGH |
- It is outside of the scope of this analysis to evaluate the lending protocol/overcollateralization dynamics (e.g., lending terms), so there can be additional risk.
- Access Control
- Pool Delegate
- Assets are borrowed from the pool via the
fund(address loan). This function gets the funds viapoolManager.requestFunds(loanManager, amount)and for Open-Term loans are pulled by the loan contract via theloan.fund()method, while for the Fixed-term, the assets are transferred directly to the loan contract. - New terms between the Borrower and Lender can be changed via the
proposeNewTerms()function. This function allows to change the principal amount borrowed, periods, interest rate, and collateral required. For Open-Term loans, the new terms are proposed by the Lender and accepted via theclaim()method. For Fixed-term loans, they are proposed by the borrower and accepted by the Lender (PoolDelegate) via theacceptNewTerms()method, and later the borrower can call theclaim()method. - For the Fixed-Term loans, the new terms can be rejected by the Lender via the
rejectNewTerms()function.
- Assets are borrowed from the pool via the
- PoolDelegate or Governor
- For Fixed-Term loans, the maximum slippage to liquidate the collateral asset can be set via the
setAllowedSlippage(slippage)function. - When the borrower is unable to repay the loan, the PoolDelegate or the Governor can call the
impairLoan()function, which allows thetotalAssets()to account for theassetsUnderManagement()asunrealizedLosses(). This has a direct effect on the exchange rate syrupUSDC. - Only the Governor can remove a loan from an impaired state by calling the
removeLoanImpairment()function.
- For Fixed-Term loans, the maximum slippage to liquidate the collateral asset can be set via the
- PoolManager
- When borrowers fail to repay their loans on the defined date, following a grace period, the PoolManager can liquidate the collateral via the
triggerDefault()method. ThetotalAssets()will be adjusted accordingly to reflect the recovered assets from the liquidation.
- When borrowers fail to repay their loans on the defined date, following a grace period, the PoolManager can liquidate the collateral via the
- Pool Delegate
DeFi Strategies
Inheriting the base functionality from the MapleAbstractStrategyContract, each strategy contains the logic to interact with a specific DeFi protocol. The strategy has 3 different states: Active, Inactive and Impaired (when it’s not possible to withdraw funds from the DeFi protocol). It’s an upgradable Proxy contract.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| PoolDelegate - MPC (call time-locked) | upgrade | CRITICAL | |
| SecurityAdmin - Safe 3-of-6 | upgrade | CRITICAL | |
| factory | setImplementation, migrate | CRITICAL | |
| StrategyManager: PoolDelegate - MPC: Syrup Deployer | fundStrategy, withdrawFromStrategy | HIGH | |
| ProtocolAdmins: PoolDelegate - MPC: Syrup Deployer, Governor, OperationalAdmin (Safe 3-of-5) | deactivateStrategy, impairStrategy, reactivateStrategy, setStrategyFeeRate | HIGH |
- It is outside of the scope of this analysis to evaluate the lending protocol/overcollateralization dynamics (e.g., lending terms), so there can be additional risk.
- Access Control
- StrategyManager
- Assets are deposited into the specific DeFi Strategy via the
fundStrategy(amount)method. This function gets funds via thepoolManager.requestFunds(strategy, amount)and then interacts with the DeFi protocol by depositing funds (e.g, supply on Aave). - Later, assets can be withdrawn via the
withdrawFromStrategy(amount)function. A fee is charged on the yield accrued during the period the strategy was active and sent to the treasury. The amount is then sent directly to the pool.
- Assets are deposited into the specific DeFi Strategy via the
- ProtocolAdmins
- A fee Rate can be set via the
setStrategyFeeRate(fee)method. - The strategy’s state via the
deactivateStrategy(),reactivateStrategy(), andimpairStrategy()methods. It is important to mention that the strategy’s state directly affects thetotalAssets()used for redemptions, where:- Active state increases
totalAssets()with the strategy’sassetsUnderManagement(). - Impaired state decreases
totalAssets()with the strategy’sassetsUnderManagement()asunrealizedLosses() - Inactive state returns zero;
- Active state increases
- A fee Rate can be set via the
- StrategyManager
MapleProxyFactory
The MapleProxyFactory is a generic Beacon Proxy Factory contract that handles the deployment of the system’s upgradable contracts, such as PoolManager, Strategies, and WithdrawalManagerQueue. Since the factory is contract-specific, it holds a default implementation that is used by the new proxy when no implementation is provided. Upgrades are triggered in the instance by the SecurityAdmin or PoolDelegate. It is a non-upgradable contract controlled by the Governor’s multisig.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| Governor | createInstance, enableUpgradePath, disableUpgradePath, registerImplementation, setDefaultVersion | CRITICAL |
- Access Control
- New Proxy contracts can be deployed via the
createInstance(bytes arguments, bytes32 salt). Internally, it deploys the Proxy usingcreate2, validates the implementation version, and finalizes by initializing the contract with the inputarguments. - Upgrades are made in a 3-step process:
- First, the implementation contract and its version and its initializer contract must be registered in the factory storage via the
registerImplementation(uint256 version, address implementation, address initializer). - The Governor must authorize the upgrade from the current version to the new version and whether needs a migrator contract via the
enableUpgradePath(uint256 fromVersion, uint256 toVersion, address migrator). It can also be cancelled via thedisableUpgradePath(uint256 fromVersion, uint256 toVersion). - Finally, it is upgraded via the
upgradeInstance(uint256 toVersion, bytes calldata arguments), which is called from the instance contract by the Pool Delegate (scheduled calls) or by the SecurityAdmin (instant upgrade). Internally, it verifies whether the upgrade was authorized and queries the toVersion implementation. Then, it callsproxy.setImplementation(implementation), and it migrates whether the migrator andargumentswere provided via theproxy.migrate(migrator, arguments). - For upgrades invoked by the PoolDelegate, an additional timelock validation is checked in the
proxy.setImplementation(implementation)function, which checks if the upgrade was previously scheduled using theglobals.isValidScheduledCall()function. In contrast, when the upgrade is initiated by the SecurityAdmin, the timelock validation is bypassed. - The default implementation version for the factory’s proxies can be set via the
setDefaultVersion(version).
- First, the implementation contract and its version and its initializer contract must be registered in the factory storage via the
- New Proxy contracts can be deployed via the
Maple Globals
Globals is a central Maple contract for storing Maple system parameters. It includes a pauser and a timelock for upgrades for certain core contracts of the system. It’s an upgradable NonTransparentProxy contract owned by the Maple Governor.
| Permission Owner | functions | Criticality | Risk |
|---|---|---|---|
| Upgradable Admin: Governor | setImplementation | CRITICAL | |
| Governor | setPendingGovernor, setDefaultTimelockParameters, setTimelockWindows, unscheduleCall, setMapleTreasury, setMigrationAdmin, setOperationalAdmin, setSecurityAdmin, setPriceOracle, setValidCollateralAsset, setValidPoolAsset, setValidPoolDeployer, setManualOverridePrice | CRITICAL | |
| Governor or OperationalAdmin - Safe 3-of-5 | activatePoolManager, setBootstrapMint, setCanDeployFrom, setValidBorrower, setValidInstanceOf, setValidPoolDelegate, setMaxCoverLiquidationPercent, setMinCoverAmount, setPlatformManagementFeeRate, setPlatformOriginationFeeRate, setPlatformServiceFeeRate | HIGH | |
| Governor or SecurityAdmin - Safe 3-of-6 | setContractPause, setFunctionUnpause, setProtocolPause | HIGH |
- Access Control
- Governor
- The default timelock delay period and its maximum duration to be executed after the delay period can be set by calling the
setDefaultTimelockParameters(delay, duration)method. Currently, the default delay for scheduled calls is7 dayswith a maximum execution-window duration of2 days. - The delay period to scheduling calls for specific contract actions (e.g, upgradeability) can be set through the
setTimelockWindow(contract, bytes32 functionId, delay, duration). It is important to mention that this function can bypass the default 7-day delay by entering a shorter period. - The
scheduleCall(contract, functionId, calldata)function is permissionless, but e.g, upgrades must be called by the PoolDelegate or SecurityAdmin. - The Governor can remove scheduled calls through the
unscheduleCall(caller, contract, functionId, callData)function. - The addresses of the access control admins, treasury, collateral assets and oracles can be set by the Governor.
- The default timelock delay period and its maximum duration to be executed after the delay period can be set by calling the
- Governor or OperationalAdmin
- Different service fees can be set for a specific poolManager through the
setPlatformManagementFeeRate(poolManager, fee),setPlatformOriginationFeeRate(poolManager, fee),setPlatformServiceFeeRate(poolManager, fee)methods. - New contracts into the system are registered, validated and activated by the Governor or OperationalAdmin.
- Different service fees can be set for a specific poolManager through the
- Governor or SecurityAdmin
- Specific contracts can be paused/unpaused via the
setContractPause(address, bool). - The system can be paused/unpaused via the
setProtocolPause(bool)function. - Specific functions can be unpaused by calling
setFunctionUnpause(address)function.
- Specific contracts can be paused/unpaused via the
- Governor
Pricing strategy
SyrupUSDC can be characterized as a Lending Protocol, which means a new category of asset being onboarded to Aave. We agree that even if the more straightforward solution to price syrupUSDC with Capo adapter + CL USDC/USD Price feed, as suggested by the risk service provides, we still see this asset as a complex set of pricing, given that the collateral is not only USDC but also composed of n different abstract strategies, some of which can be straightforward to price, while at the same time, others require a more complex evaluation to reach a conclusion.
It’s also important to highlight that the exchange rate can be manipulated in both directions by increasing it through direct USDC donations to the pool or strategies, and decreasing it by impairing or disabling strategies.
Given this fragility of the rate, we highly recommend that if the DAO chooses to list syrupUSDC, it must not be borrowable.
Miscellaneous
- The system has undergone several security reviews by Trail of Bits, Spearbit (Cantina), Three Sigma, and 0xMacro. They can be found in the Maple documentation here.
- SyrupUSDC requires several trust assumptions, given that the entities that control the administrative functions, such as the Governor, Pool Delegate, Operational and Security Admins, are controlled by the Maple Team. The Team has provided a list of Assumptions, which highlights that all roles mentioned above are KYC-processed actors and are incentivized to act in the best interest of the protocol.
- The Maple Team has confirmed that the address managing the Pool Delegate role is an MPC wallet.
- Even taking those into account, it is important to highlight that the system has multiple moving parts with its custom strategies that will directly affect Aave.
Conclusion
We believe syrupUSDC does not face any infrastructural barriers to listing; however, we identify technical blockers that directly affect the security users’ funds on the Aave Protocol as follows:
- The Globals singleton contract upgrades that are not time-locked have enough rights to modify critical parts of the system in a scenario where the upgradable admin (currently controlled by the Governor) has its signers’ keys exploited by a malicious actor.
- The Governor + SecurityAdmin pattern can bypass the timelock, which is only enforced when the Pool Delegate initiates the upgradable calls of critical contracts within the system. This could break the system, similar to the scenario above, if a malicious actor gains complete control.
- The impairment/disabling strategies action is not time-locked, which could lead to a rapid rate drop, resulting in multiple liquidations and potential bad debt on Aave. A similar scenario could occur if a malicious actor gains control over one of the addresses responsible for this role. We have recommended the Maple team to re-evaluate this flow, to be more defensive, even if keeping the impairment/disabling levers required for the protocol to work.
We discussed the previous points with the Maple Team, and they agreed to evaluate improvements, which, if applied, would remove the blockers from our side (after we re-check the system).
Additionally, it is outside of our scope to evaluate in depth each of the underlying strategies of the asset (both lending systems and DeFi allocation), each one of them involving additional risk, and dependency on the for example, the underlying venues where the funds are allocated.


