PoolTogether - 0xhunter20's results

General Information

Platform: Code4rena

Start Date: 04/03/2024

Pot Size: $36,500 USDC

Total HM: 9

Participants: 80

Period: 7 days

Judge: hansfriese

Total Solo HM: 2

Id: 332

League: ETH

PoolTogether

Findings Distribution

Researcher Performance

Rank: 2/80

Findings: 2

Award: $5,314.56

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: 0xhunter20

Labels

bug
2 (Med Risk)
insufficient quality report
satisfactory
selected for report
sponsor confirmed
:robot:_188_group
M-05

Awards

4400.588 USDC - $4,400.59

External Links

Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L692

Vulnerability details

Impact

yieldFeeBalance wouldn't be claimed after calling transferTokensOut() due to the twab supply limit.

Proof of Concept

When _tokenOut == address(this), liquidatableBalanceOf() mints shares to the receiver and accumulates yieldFeeBalance accordingly.

But when it checks the maximum liquidatable amount in liquidatableBalanceOf(), it validates the twap supply limit with the liquidYield only and it might meet the supply limit while minting yieldFeeBalance like the below.

  • We assume totalSupply = 6e28, yieldFeeBalance = 0, twabSupplyLimit = 2^96 - 1 = 7.9e28 and the vault has enough available yield.
  • Then liquidatableBalanceOf(_tokenOut = address(this)) will return _maxAmountOut = 7.9e28 - 6e28 = 1.9e28 when _liquidYield > _maxAmountOut.
  • After calling transferTokensOut() with _amountOut = 1.9e28, _yieldFee will be added to yieldFeeBalance but it can't be claimed as we met the twap supply limit already.

Tools Used

Manual Review

liquidatableBalanceOf() shouldn't apply yieldFeePercentage to compare with _maxAmountOut when _tokenOut == address(this).

    function liquidatableBalanceOf(address _tokenOut) public view returns (uint256) {
        uint256 _totalSupply = totalSupply();
        uint256 _maxAmountOut;
        if (_tokenOut == address(this)) {
            // Liquidation of vault shares is capped to the TWAB supply limit.
            _maxAmountOut = _twabSupplyLimit(_totalSupply);
        } else if (_tokenOut == address(_asset)) {
            // Liquidation of yield assets is capped at the max yield vault withdraw plus any latent balance.
            _maxAmountOut = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this));
        } else {
            return 0;
        }

        // The liquid yield is computed by taking the available yield balance and multiplying it
        // by (1 - yieldFeePercentage), rounding down, to ensure that enough yield is left for the
        // yield fee.
        uint256 _liquidYield = _availableYieldBalance(totalAssets(), _totalDebt(_totalSupply));

        if (_tokenOut == address(this)) {
            if (_liquidYield >= _maxAmountOut) { //compare before applying yieldFeePercentage 
                _liquidYield = _maxAmountOut;
            }
            _liquidYield = _liquidYield.mulDiv(FEE_PRECISION - yieldFeePercentage, FEE_PRECISION);
        } else {
            _liquidYield = _liquidYield.mulDiv(FEE_PRECISION - yieldFeePercentage, FEE_PRECISION);

            if (_liquidYield >= _maxAmountOut) { //same as before
                _liquidYield = _maxAmountOut;
            }
        }

        return _liquidYield;
    }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-03-13T01:50:55Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-03-13T01:56:07Z

raymondfam marked the issue as duplicate of #319

#2 - raymondfam

2024-03-13T01:58:58Z

On top of the comments on #319, _tokenOut is just a specifier whether to mint shares or to withdraw assets to the receiver. And the yieldFee added to yieldFeeBalance will be incorporated into totalDebt. The yield fee recipient will be able to claim it anytime unrestricted via the pull method.

#3 - c4-judge

2024-03-15T15:38:39Z

hansfriese marked the issue as not a duplicate

#4 - hansfriese

2024-03-18T14:51:06Z

The impact is the same as #91 but the flaw still exists after mitigating #91 because liquidatableBalanceOf() doesn't use the newly accumulated yield fees while checking _twabSupplyLimit.

#5 - c4-judge

2024-03-18T14:56:40Z

hansfriese marked the issue as satisfactory

#6 - trmid

2024-03-18T15:09:20Z

Similar to #91, this issue outlines the need for the TWAB supply limit checks to account for the yield fee balance so that the entire yield fee balance is always available to be realized as shares.

#7 - c4-sponsor

2024-03-18T15:09:25Z

trmid (sponsor) confirmed

#8 - c4-judge

2024-03-18T15:14:04Z

hansfriese marked the issue as selected for report

#9 - trmid

2024-03-20T02:14:54Z

Findings Information

🌟 Selected for report: pa6kuda

Also found by: 0xhunter20, Afriauditor

Labels

bug
2 (Med Risk)
downgraded by judge
insufficient quality report
satisfactory
:robot:_188_group
duplicate-91

Awards

913.9683 USDC - $913.97

External Links

Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L800

Vulnerability details

Impact

The yield fee recipient couldn't claim his yield fee shares due to the twap supply limit.

Proof of Concept

The yield fee recipient can claim already earned yield fee shares using claimYieldFeeShares().

Due to the TwabController's limits, we check the available supply using _twabSupplyLimit() but we don't check yieldFeeBalance that can be claimed any time.

So the below case would be possible.

  • We assume totalSupply = 6e28, yieldFeeBalance = 1e28, twabSupplyLimit = 2^96 - 1 = 7.9e28.
  • maxDeposit() will return twabSupplyLimit - totalSupply = 1.9e28.
  • A user calls deposit() with the above maximum amounts and totalSupply = twabSupplyLimit now.
  • Then claimYieldFeeShares() will revert due to the twap supply limit.

Tools Used

Manual Review

_twabSupplyLimit() should calculate the remaining supply using totalSupply + yieldFeeBalance and deposit() should validate the limit as well.

    function _twabSupplyLimit(uint256 _totalSupply) internal pure returns (uint256) {
        unchecked {
            return type(uint96).max - _totalSupply - yieldFeeBalance;
        }
    }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-03-13T01:49:24Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-03-13T01:49:39Z

raymondfam marked the issue as duplicate of #64

#2 - c4-judge

2024-03-18T01:56:26Z

hansfriese marked the issue as not a duplicate

#3 - c4-judge

2024-03-18T01:57:57Z

hansfriese changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-03-18T01:58:06Z

hansfriese marked the issue as duplicate of #91

#5 - c4-judge

2024-03-18T01:58:25Z

hansfriese marked the issue as unsatisfactory: Invalid

#6 - c4-judge

2024-03-18T14:34:21Z

hansfriese marked the issue as satisfactory

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