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

Permanent freeze of yield when TokenSender rewards bank is depleted and deposit or withdraw is called. #311

Closed
code423n4 opened this issue Dec 12, 2022 · 7 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-257 satisfactory satisfies C4 submission criteria; eligible for awards sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/TokenSender.sol#L41

Vulnerability details

Description

In collateral deposit() and withdraw() flow, a fee is calculated as a percentage of user's requested amount. It is passed to the DepositHook and WithdrawHook, for example in deposit():

uint256 _amountAfterFee = _amount - _fee;
if (address(depositHook) != address(0)) {
  baseToken.approve(address(depositHook), _fee);
  // hook will calculate fee = _amount - _amountAfterFee
  depositHook.hook(_recipient, _amount, _amountAfterFee);
  baseToken.approve(address(depositHook), 0);
}

In the DepositHook, if there is a fee two actions take place:

  1. Collect the fee to the treasury from Collateral contract
  2. Send rewards relative to fee amount, to the depositor
uint256 _fee = _amountBeforeFee - _amountAfterFee;
if (_fee > 0) {
  collateral.getBaseToken().transferFrom(address(collateral), _treasury, _fee);
  _tokenSender.send(_sender, _fee);
}

The issue is that TokenSender has an early exit problem:

function send(address recipient, uint256 unconvertedAmount) external override onlyAllowedMsgSenders {
  uint256 scaledPrice = (_price.get() * _priceMultiplier) / MULTIPLIER_DENOMINATOR;
  if (scaledPrice <= _scaledPriceLowerBound) return;
  uint256 outputAmount = (unconvertedAmount * _outputTokenDecimalsFactor) / scaledPrice;
  if (outputAmount == 0) return;
  // EARLY EXIT PROBLEM
  if (outputAmount > _outputToken.balanceOf(address(this))) return;
  _outputToken.transfer(recipient, outputAmount);
}

In the code above, note that if the TokenSender does not currently have enough reward tokens to hand out, it will simply return successfully from the call. Therefore, user which assumed they will be getting cash back rewards from fees when depositing or withdrawing, are actually paying the fees with no compensation.

Impact

Permanent freeze of yield when TokenSender rewards bank is depleted and deposit or withdraw is called.

Proof of Concept

  1. User has $1000 USDC
  2. User deposits it to Collateral.sol, which charges 10% deposit fee
  3. Meanwhile, TokenSender offers PPO rewards, but has just ran out.
  4. DepositHook calls TokenSender.send(), which should send $100 fee rewards in PPO to user. send() returns without paying.
  5. User is unhappy having assumed they will receive PPO from the fee.

Tools Used

Manual audit

Recommended Mitigation Steps

The root cause is that there is no differentiation between user's request to mint expecting rewards, and user's request to mint in any case. This lack of acknoledgement means user may be left under-compensated.

It is very advisable to add "forfeitRewards" flag, which is required to be true when TokenSender is not able to satisfy the reward owings to user.

Judging note

Permanent freezing of unclaimed yield is always rated as High severity on Immunefi bounties.

@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
@Picodes
Copy link

Picodes commented Dec 13, 2022

Indeed, it would be good to add a flag or some parameter called minimumCompensationExpected.

However, I do not agree that this falls in the "Permanent freezing of unclaimed yield" category. From my perspective, this part of the code does not promise users anything. Instead, there is eventually a fee rebate if there are still PPO to send. Therefore, the additional parameter would only be an additional safety check for the user.

@c4-judge c4-judge added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue and removed 3 (High Risk) Assets can be stolen/lost/compromised directly labels Dec 13, 2022
@c4-judge
Copy link
Contributor

Picodes changed the severity to 2 (Med Risk)

@c4-judge
Copy link
Contributor

Picodes marked the issue as primary issue

@c4-sponsor
Copy link

ramenforbreakfast marked the issue as sponsor disputed

@c4-sponsor c4-sponsor added the sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue label Dec 19, 2022
@ramenforbreakfast
Copy link

ramenforbreakfast commented Dec 19, 2022

Yes, i agree that to categorize this under "Permanent freezing of unclaimed yield", since this is just an additional rebate reward given to the user. It is within the contract documentation that this is expected behavior and in our front end we will be very clear on the lack of a rebate if the TokenSender contract does not have funds to issue one.

We do not want a situation where the rebate contract has exhausted its PPO and the platform is halted because of it.

@c4-judge
Copy link
Contributor

c4-judge commented Jan 7, 2023

Picodes marked the issue as duplicate of #257

@c4-judge c4-judge added duplicate-257 satisfactory satisfies C4 submission criteria; eligible for awards and removed primary issue Highest quality submission among a set of duplicates labels Jan 7, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Jan 7, 2023

Picodes marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-257 satisfactory satisfies C4 submission criteria; eligible for awards sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue
Projects
None yet
Development

No branches or pull requests

5 participants