At this stage, we are not checking if the balance left after withdrawal is sufficient to honor any collateral withdrawals by current LPs.
In the extreme case where minimumReserve is set to a low number OR global/user withdrawal cap per period is set to a high number OR there is a case of sudden spike in withdrawals, right after a manager withdrawal (or combination of all the above), we could end up in a scenario where
minimum Reserve after withdrawal is less than 'withdrawal request' of users in a single period.
In such cases, users will face a denial of service because of insufficient balance. Ofcourse the edge case here is dependent on userWithdrawalCapPerPeriod and minimumReserve parameters set by the protocol. But such a scenario is conceptually possible.
Since the event can potentially stop the normal functioning of protocol & considering it to be an edge case, I've marked it as MEDIUM risk.
Proof of Concept
Suppose vault had 10 depositors of 100 USDC each. userWithdrawLimitPerPeriod is 80 USDC in a single period and minimumReserve is 5%. A user with MANAGER_WITHDRAW_ROLE withdraws 95% of base tokens from vault leaving just 50 USDC in collateral vault. Bob, a depositor, tries to withdraw maximum of 80 USDC, and gets rejected because of insufficient balance in the vault.
Tools Used
Recommended Mitigation Steps
Just like globalNetDepositAmount being tracked in DepositRecord contract in line 11 of DepositRecord.sol, a globalUserDepositAmount should be tracked that is a cumulative sum of all user deposits (excluding deposits made by protocol itself).
In the managerFunction on Line 82 in Collateral.sol, following code block can be changed from
function managerWithdraw(uint256 _amount) external override onlyRole(MANAGER_WITHDRAW_ROLE) nonReentrant {
if (address(managerWithdrawHook) != address(0)) managerWithdrawHook.hook(msg.sender, _amount, _amount);
baseToken.transfer(manager, _amount);
}
to
function managerWithdraw(uint256 _amount) external override onlyRole(MANAGER_WITHDRAW_ROLE) nonReentrant {
if (address(managerWithdrawHook) != address(0)) managerWithdrawHook.hook(msg.sender, _amount, _amount);
if(minimumReserve * depositRecord. getGlobalNetDepositAmount() >= depositRecord.getGlobalUserDepositAmount()){
baseToken.transfer(manager, _amount); // assumed that `getGlobalUserDepositAmount()` getter would exist in DepositRecord
}
}.
By checking if minimum reserves are sufficient to meet all user withdrawal requests, we can mitigate the risk stated above.
Lines of code
https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L80 https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/ManagerWithdrawHook.sol#L17
Vulnerability details
Impact
In the
managerWithdraw
function on Line80 ofCollateral.sol
, user withMANAGER_WITHDRAW_ROLE
can withdraw funds from Collateral vault subject to a minimum reserve requirement condition on Line 17 ofManagerWithdrawHook.sol
At this stage, we are not checking if the balance left after withdrawal is sufficient to honor any collateral withdrawals by current LPs.
In the extreme case where
minimumReserve
is set to a low number OR global/user withdrawal cap per period is set to a high number OR there is a case of sudden spike in withdrawals, right after a manager withdrawal (or combination of all the above), we could end up in a scenario where minimum Reserve after withdrawal is less than 'withdrawal request' of users in a single period.In such cases, users will face a denial of service because of insufficient balance. Ofcourse the edge case here is dependent on
userWithdrawalCapPerPeriod
andminimumReserve
parameters set by the protocol. But such a scenario is conceptually possible.Since the event can potentially stop the normal functioning of protocol & considering it to be an edge case, I've marked it as MEDIUM risk.
Proof of Concept
Suppose vault had 10 depositors of 100 USDC each.
userWithdrawLimitPerPeriod
is 80 USDC in a single period andminimumReserve
is 5%. A user withMANAGER_WITHDRAW_ROLE
withdraws 95% of base tokens from vault leaving just 50 USDC in collateral vault. Bob, a depositor, tries to withdraw maximum of 80 USDC, and gets rejected because of insufficient balance in the vault.Tools Used
Recommended Mitigation Steps
Just like
globalNetDepositAmount
being tracked inDepositRecord
contract in line 11 of DepositRecord.sol, aglobalUserDepositAmount
should be tracked that is a cumulative sum of all user deposits (excluding deposits made by protocol itself).In the
managerFunction
on Line 82 in Collateral.sol, following code block can be changed fromto
By checking if minimum reserves are sufficient to meet all user withdrawal requests, we can mitigate the risk stated above.