Open code423n4 opened 1 year ago
Picodes marked the issue as duplicate of #325
Picodes marked the issue as selected for report
Picodes marked the issue as satisfactory
Picodes changed the severity to 3 (High Risk)
captainmangoC4 marked the issue as selected for report
Lines of code
https://github.com/prepo-io/prepo-monorepo/blob/feat/2022-12-prepo/apps/smart-contracts/core/contracts/WithdrawHook.sol#L66-L72
Vulnerability details
griefing / blocking / delaying users to withdraw
To withdraw, a user needs to convert his collateral for the base token. This is done in the withdraw function in Collateral.
The WithdrawHook has some security mechanics that can be activated like a global max withdraw in a specific timeframe, also for users to have a withdraw limit for them in a specific timeframe. It also collects the fees.
The check for the user withdraw is wrongly implemented and can lead to an unepexted delay for a user with a position > userWithdrawLimitPerPeriod. To withdraw all his funds he needs to be the first in every first new epoch (lastUserPeriodReset + userPeriodLength) to get his amount out. If he is not the first transaction in the new epoch, he needs to wait for a complete new epoch and depending on the timeframe from lastUserPeriodReset + userPeriodLength this can get a long delay to get his funds out.
The documentation says, that after every epoch all the user withdraws will be reset and they can withdraw the next set.
But the implementation only resets the amount for the first user that interacts with the contract in the new epoch and leaves all other users with their old limit. This can lead to a delay for every user that is on his limit from a previous epoch until they manage to be the first to interact with the contract in the new epoch.
Proof of Concept
https://github.com/prepo-io/prepo-monorepo/blob/feat/2022-12-prepo/apps/smart-contracts/core/contracts/WithdrawHook.sol#L66-L72
The following test shows how a user is locked out to withdraw if he's at his limit from a previous epoch and another withdraw is done before him.
apps/smart-contracts/core/test/WithdrawHook.test.ts
To get the test running you need to add let user2: SignerWithAddress and the user2 in await ethers.getSigners()
Recommended Mitigation Steps
The check how the user periods are handled need to be changed. One possible way is to change the lastUserPeriodReset to a mapping like mapping(address => uint256) private lastUserPeriodReset to track the time for every user seperatly.
With a mapping you can change the condition to
With this change, we can change the test to how we would normaly expect the contract to work and see that it is correct.