Closed sherlock-admin closed 1 year ago
minPurchaseAmount is validated in this require statement, it is not possible to set to zero unless the slippage limit is also set to zero: https://github.com/notional-finance/leveraged-vaults/blob/91100c0d2c81ea67de7f060a42d6b5fab3fc26e9/contracts/vaults/CrossCurrencyfCashVault.sol#L135-L137
I'd consider this report invalid.
xiaoming90
high
Permissionless
settleVault
Function WithinCrossCurrencyfCashVault
Vault Can Be Exploited To Steal ProfitSummary
The permissionless
settleVault
function withinCrossCurrencyfCashVault
vault can be exploited to steal the profit of the vaults.Vulnerability Detail
The
CrossCurrencyfCashVault.settleVault
function is permissionless and can be called by anyone.https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/CrossCurrencyfCashVault.sol#L121
Following is an example of a
CrossCurrencyfCashVault.settleVault
call taken from the test scripts for reference.https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/tests/test_cross_currency.py#L265
When calling the
CrossCurrencyfCashVault.settleVault
function, the caller can specify the redemption configuration by configuring theRedeemParams
. The caller can instruct the leverage vault on how the trading of the lending underlying assets (e.g. DAI) back to borrowing underlying assets (e.g. USDC) should occur. Currently, the Notional Trade Module supports trading with slippage limits across multiple DEX protocols (Curve, Balancer V2, Uniswap V2 & V3 and 0x).https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/CrossCurrencyfCashVault.sol#L52
The
minPurchaseAmount
within theRedeemParams
specifies the slippage limit of the trade. If it is set to zero, there is no slippage limit.An attacker can call the
CrossCurrencyfCashVault.settleVault
function with no slippage limit by configuringminPurchaseAmount
to0
, thus giving the attacker the ability to force the vault to trade with a DEX/pool and cause it to suffer huge slippage.During vault settlement, the
VaultAccount._redeemStrategyTokensToCashInternal
function is triggered. Within the function, it will attempt to redeem the strategy tokens without any debt repayment by calling theVaultConfiguration.redeemWithoutDebtRepayment
function.https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/external/actions/VaultAction.sol#L132
The
VaultConfiguration.redeemWithoutDebtRepayment
function will in turn call the internal_redeem
function. Notice that since no debt repayment is required, theRedeemParams.assetInternalToRepayDebt
is set to0
in the parameters.https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L521
https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L562
Within the
_redeem
function, sinceparams.assetInternalToRepayDebt
is set to0
,underlyingExternalToRepay
is set to zero, which means that no debt repayment is required. The entire code block from Line 588 to 598 will be skipped.At Line 619, it calls the
IStrategyVault(vaultConfig.vault).redeemFromNotional
function to instruct the vault to perform the following steps:RedeemParams.minPurchaseAmount
to zero when callingCrossCurrencyfCashVault.settleVault
function, the trade will occur with no slippage control. In the worst-case scenario, it is possible to swap 1,000,000 DAI (lending underlying token) for only 10 USDC (borrowing underlying token) in an extremely imbalanced Uniswap pool manipulated by a flash-loan attack.At Line 623, the
amountTransferred
will be the amount of underlying borrowing token (USDC) received by the vault during the DEX's trade. In the previous worst-case scenario, if the vault only received 10 USDC, theamountTransferred
will be set to10
.Since
amountTransferred=10
andunderlyingExternalToRepay=0
:The entire code block from Line 626 to 649 will be skipped since
(amountTransferred < underlyingExternalToRepay)
evaluate tofalse
The require statement at Line 651 will pass.
At Line 659,
10 USDC
worth of asset cash (cUSDC) will be minted to the vault. After the vault settlement, the vault hold0
strategy token and10 USDC
worth of asset cash (cUSDC) assuming the worst-case scenario described above. Notice that this is a significant deviation from the 1,000,000 DAI valuation of the vault before the settlement.https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L578
What happens if there is a cash shortfall within the vault after the settlement?
Per the code and comment on Line 406-411, Notional does not allow the
CrossCurrencyfCashVault
vault to have a cash shortfall. If there is a cash shortfall after all the strategy tokens have been redeemed to asset cash (cUSDC), it will resolve the shortfall using the protocol reserve by calling theVaultConfiguration.resolveShortfallWithReserve
function. Since protocol reserves are being used to fill up the cash shortfall within the vault, it entails the loss of assets for the protocol if such an incident occurs.https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/external/actions/VaultAction.sol#L375
Proof-of-Concept
Following are the possible attack paths against the
CrossCurrencyfCashVault
vaultAttack 1
CrossCurrencyfCashVault
USDC/DAI vault is ready to be settled.settleVault
function withminPurchaseAmount
set to0
, which means there is no slippage control. This makes the vault trade with the imbalanced Uniswap pool at an unfavorable exchange rating, resulting in huge slippage.settleVault
function. Also, the attacker can always choose the Protocol and/or Pool with the least liquidity out of the many options to imbalance it to facilitate the attack.Side Note 1: This issue reassembles some attacks seen in the past where the attacker takes advantage of the permissionless Yearn's
earn()
function. Refer to https://github.com/yearn/yearn-security/blob/master/disclosures/2020-10-30.md andhttps://github.com/yearn/yearn-security/blob/master/disclosures/2021-02-04.md.
Side Note 2: This attack can be carried out without a flash-loan if the attacker has enough funds to obtain majority of the vault shares.
Attack 2
Assume there is a protocol called XYZ that offers fixed-rate, fixed-term crypto asset lending and borrowing. XYZ sees Notional as its only direct competitor. XYZ has strong financial backing and is willing to do whatever it takes to sabotage Notional. The cost of attack is not so much of a concern for XYZ as the main goal is to damage the community and reputation of Notional, so that XYZ will benefit in the long run.
settleVault
function withminPurchaseAmount
set to0
, which means there is no slippage control. This makes the vault to trade with the imbalanced Uniswap pool at an unfavorable exchange rating, resulting in huge slippage.Impact
Loss of assets for protocol and its users
Code Snippet
https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/CrossCurrencyfCashVault.sol#L121 https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/tests/test_cross_currency.py#L265 https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/CrossCurrencyfCashVault.sol#L52 https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/external/actions/VaultAction.sol#L132 https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L521 https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L562 https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/internal/vaults/VaultConfiguration.sol#L578 https://github.com/sherlock-audit/2022-09-notional/blob/main/contracts-v2/contracts/external/actions/VaultAction.sol#L375
Tool used
Manual Review
Recommendation
Consider making the
settleVault
function permissioned. Additionally, the security risks of allowing thesettleVault
function to be called by anyone outweigh the benefits of this function being permissionless.CrossCurrencyfCashVault
vault relies on theTradeModule.executeTrade
to execute its trade. Alternatively, thesettleVault
function can be kept permissionless, but consider using theTrade.executeTradeWithDynamicSlippage
function with hardcodeddynamicSlippageLimit
to reduce the impact.