Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

user's funds lock and incorrect code behavior because users withdrawal amount won't get reset for all users in each userPeriodLength in WithdrawHook contract #325

Closed
code423n4 opened this issue Dec 12, 2022 · 4 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-116 satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/WithdrawHook.sol#L53-L79

Vulnerability details

Impact

according to the comments in code: "Every time userPeriodLength seconds passes, the amount withdrawn for all users will be reset to 0" (https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/interfaces/IWithdrawHook.sol#L64-L76). but in current implementation only one of the users userToAmountWithdrawnThisPeriod[] value gets reset and this will cause users to not be able to withdraw their tokens even if they follow the limitation. users which has high withdrawal amount only can withdraw when they reset the value of userPeriodLength and in each period only one user can do that because in each userPeriodLength only one user withdraw amounts gets reset. so over time in each period only one user could withdraw. for example if userPeriodLength was 1 day, in each day 1 user can withdraw and if protocol had 100K user some users would never withdraw in their life time.

Proof of Concept

This is hook() code in WithdrawHook:

  function hook(
    address _sender,
    uint256 _amountBeforeFee,
    uint256 _amountAfterFee
  ) external override onlyCollateral {
    require(withdrawalsAllowed, "withdrawals not allowed");
    if (lastGlobalPeriodReset + globalPeriodLength < block.timestamp) {
      lastGlobalPeriodReset = block.timestamp;
      globalAmountWithdrawnThisPeriod = _amountBeforeFee;
    } else {
      require(globalAmountWithdrawnThisPeriod + _amountBeforeFee <= globalWithdrawLimitPerPeriod, "global withdraw limit exceeded");
      globalAmountWithdrawnThisPeriod += _amountBeforeFee;
    }
    if (lastUserPeriodReset + userPeriodLength < block.timestamp) {
      lastUserPeriodReset = block.timestamp;
      userToAmountWithdrawnThisPeriod[_sender] = _amountBeforeFee;
    } else {
      require(userToAmountWithdrawnThisPeriod[_sender] + _amountBeforeFee <= userWithdrawLimitPerPeriod, "user withdraw limit exceeded");
      userToAmountWithdrawnThisPeriod[_sender] += _amountBeforeFee;
    }
    depositRecord.recordWithdrawal(_amountBeforeFee);
    uint256 _fee = _amountBeforeFee - _amountAfterFee;
    if (_fee > 0) {
      collateral.getBaseToken().transferFrom(address(collateral), _treasury, _fee);
      _tokenSender.send(_sender, _fee);
    }
  }

As you can see when the lastUserPeriodReset + userPeriodLength < block.timestamp happens(which means last period finished) only the callers userToAmountWithdrawnThisPeriod[] value changes and other don't get changed. and in the else case (when period is not finished) if user's userToAmountWithdrawnThisPeriod[user] + amount succeed userWithdrawLimitPerPeriod code would revert. so during periods users with high withdraw amount can't withdraw and in each period only one user withdraw amount gets new value so overtime all users get high withdraw amounts and in each period 1 user cant get his withdraws and users tokens would stuck in the contract literally forever. POC:

  1. user1 withdraw 90 token and userToAmountWithdrawnThisPeriod[user1] would be 90. max allowed withdraw in each period for users is 100 token.
  2. some time passes and we are still in current period, and user1 wants to withdraw 20 tokens but userToAmountWithdrawnThisPeriod[user1] is still 90 and 90+20 is bigger than 100 and user1 can't be able to withdraw.
  3. this would happen to all users over time and only one user can withdraw in each period.

Tools Used

VIM

Recommended Mitigation Steps

reset all users withdrawal amounts in each period start time.

@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Dec 12, 2022
code423n4 added a commit that referenced this issue Dec 12, 2022
@c4-judge c4-judge added the primary issue Highest quality submission among a set of duplicates label Dec 13, 2022
@c4-judge
Copy link
Contributor

Picodes marked the issue as primary issue

@c4-sponsor
Copy link

ramenforbreakfast marked the issue as sponsor confirmed

@c4-judge
Copy link
Contributor

c4-judge commented Jan 1, 2023

Picodes marked the issue as satisfactory

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Jan 1, 2023
@C4-Staff
Copy link
Contributor

captainmangoC4 marked issue 116 as selected for report. Updating associated duplicate and primary issues.

@C4-Staff C4-Staff added duplicate-116 and removed primary issue Highest quality submission among a set of duplicates labels Jan 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-116 satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Projects
None yet
Development

No branches or pull requests

4 participants