PoolTogether - Brenzee's results

A protocol for no-loss prize savings

General Information

Platform: Code4rena

Start Date: 07/07/2023

Pot Size: $121,650 USDC

Total HM: 36

Participants: 111

Period: 7 days

Judge: Picodes

Total Solo HM: 13

Id: 258

League: ETH

PoolTogether

Findings Distribution

Researcher Performance

Rank: 12/111

Findings: 1

Award: $1,896.90

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: rvierdiiev

Also found by: Brenzee

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
upgraded by judge
duplicate-79

Awards

1896.9014 USDC - $1,896.90

External Links

Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/main/src/Vault.sol#L1176

Vulnerability details

Impact

The vault exchange rate is directly dependent on the _yieldVault.maxWithdraw() value.

The issue occurs if _yieldVault withdrawals are paused. Per ERC-4626 standard rules - maxWithdraw "MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0." Link to ERC-4626 documentation

This means that if _yieldVault withdrawals are paused, maxWithdraw value will be 0 and it is going deflate/inflate the exchange rate of the Vault, which may cause loss of funds for protocol or for user.

Proof of Concept

Vault._getExchangeRate function calls _yieldVault.maxWithdraw function. Value from it is used in the logic that happens next

    uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this)); // @audit - Can return 0

    if (_withdrawableAssets > _totalSupplyToAssets) { // @audit - Will never be true if _withdrawableAssets is 0, go to next step
      _withdrawableAssets = _withdrawableAssets - (_withdrawableAssets - _totalSupplyToAssets);
    }

    if (_totalSupplyAmount != 0 && _withdrawableAssets != 0) { // @audit - Will never be true if _withdrawableAssets is 0, go to next step
      return _withdrawableAssets.mulDiv(_assetUnit, _totalSupplyAmount, Math.Rounding.Down); 
    }

    return _assetUnit; // @audit - Return _assetUnit as exchange rate

Example

Users interact with the Vault - they deposit and withdraw tokens, which means that the exchange rate has shifted from _assetUnit (which is the exchange rate when nothing is deposited in the _yieldVault)

All of a sudden _yieldVault owner has paused the withdrawals, which makes the maxWithdraw function return 0.

Now instead of having the exchange rate based on deposited/maxWithdraw values, the exchange rate will be 1, because the maxWithdraw function returns 0.

As a result - the exchange rate is either inflated or deflated, which may cause:

  • Loss of funds for protocol (when exchange rate is inflated)
  • Loss of funds for user (when exchange rate is deflated)

Tools Used

Manual Review

Consider that maxWithdraw can return 0 even after the assets are deposited in the _yieldVault

Assessed type

Invalid Validation

#0 - c4-sponsor

2023-07-18T23:26:15Z

asselstine marked the issue as sponsor confirmed

#1 - c4-judge

2023-08-08T13:21:19Z

Picodes marked the issue as duplicate of #79

#2 - Picodes

2023-08-08T13:22:12Z

I consider this as another instance of #79, where users can lose funds because of maxWithdraw of the underlying not returning the value of the shares but what can actually be redeemed.

#3 - c4-judge

2023-08-08T13:22:19Z

Picodes marked the issue as satisfactory

#4 - c4-judge

2023-08-08T14:39:51Z

Picodes changed the severity to 3 (High Risk)

#5 - PierrickGT

2023-08-14T22:30:32Z

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter