PoolTogether - volodya'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: 10/111

Findings: 3

Award: $2,014.75

QA:
grade-a

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: dirk_y

Also found by: bin2chen, volodya, xuwinnie

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor confirmed
edited-by-warden
duplicate-332

Awards

230.4735 USDC - $230.47

External Links

Lines of code

https://github.com/generationsoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L781

Vulnerability details

Impact

Detailed description of the impact of this finding. Number of tiers calculations doesn't work like supposed to.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. According to docs Number of tiers only increased when both

the number of claimed standard prizes exceeds the claim threshold % of the expected number of standard prizes. the number of claimed canary prizes exceeds the expected number of canary prizes

And decreases when

The number of tiers is decreased if the highest tier claimed is less than n-2

But from the code number of tiers will increase when largestTierClaimed >= _numTiers - 1 If is suppose to work like that than what should happen if largestTierClaimed + 2 > _numTiers + 1 there should be maximum

  function _computeNextNumberOfTiers(uint8 _numTiers) internal view returns (uint8) {
    UD2x18 _claimExpansionThreshold = claimExpansionThreshold;

    uint8 _nextNumberOfTiers = largestTierClaimed + 2; // canary tier, then length
    _nextNumberOfTiers = _nextNumberOfTiers > MINIMUM_NUMBER_OF_TIERS
      ? _nextNumberOfTiers
      : MINIMUM_NUMBER_OF_TIERS;

    if (_nextNumberOfTiers >= MAXIMUM_NUMBER_OF_TIERS) {
      return MAXIMUM_NUMBER_OF_TIERS;
    }

    // check to see if we need to expand the number of tiers
    if (
      _nextNumberOfTiers >= _numTiers &&
      canaryClaimCount >=
      fromUD60x18(
        intoUD60x18(_claimExpansionThreshold).mul(_canaryPrizeCountFractional(_numTiers).floor())
      ) &&
      claimCount >=
      fromUD60x18(
        intoUD60x18(_claimExpansionThreshold).mul(toUD60x18(_estimatedPrizeCount(_numTiers)))
      )
    ) {
      // increase the number of tiers to include a new tier
      _nextNumberOfTiers = _numTiers + 1; //@audit what if largestTierClaimed + 2 >= _numTiers + 1
    }
// @audit will just return largestTierClaimed + 2
    return _nextNumberOfTiers;
  }

src/PrizePool.sol#L781

Tools Used

Rewrite whole function according to docs and video so it will increases only on meeting threshold and decreasing when The number of tiers is decreased if the highest tier claimed is less than

  function _computeNextNumberOfTiers(uint8 _numTiers) internal view returns (uint8) {
    UD2x18 _claimExpansionThreshold = claimExpansionThreshold;

    uint8 _nextNumberOfTiers = largestTierClaimed + 2; // canary tier, then length
    _nextNumberOfTiers = _nextNumberOfTiers > MINIMUM_NUMBER_OF_TIERS
      ? _nextNumberOfTiers
      : MINIMUM_NUMBER_OF_TIERS;

-    if (_nextNumberOfTiers >= MAXIMUM_NUMBER_OF_TIERS) {
-      return MAXIMUM_NUMBER_OF_TIERS;
-    }
+    if (_nextNumberOfTiers >= _numTiers) {
+      _nextNumberOfTiers = _numTiers;
+    }

    // check to see if we need to expand the number of tiers
    if (
      _nextNumberOfTiers >= _numTiers &&
      canaryClaimCount >=
      fromUD60x18(
        intoUD60x18(_claimExpansionThreshold).mul(_canaryPrizeCountFractional(_numTiers).floor())
      ) &&
      claimCount >=
      fromUD60x18(
        intoUD60x18(_claimExpansionThreshold).mul(toUD60x18(_estimatedPrizeCount(_numTiers)))
      )
    ) {
      // increase the number of tiers to include a new tier
      _nextNumberOfTiers = _numTiers + 1;
    }

    return _nextNumberOfTiers;
  }

Assessed type

Error

#0 - c4-sponsor

2023-07-18T22:35:43Z

asselstine marked the issue as sponsor confirmed

#1 - c4-judge

2023-08-06T21:44:15Z

Picodes marked the issue as duplicate of #145

#2 - c4-judge

2023-08-06T21:44:19Z

Picodes marked the issue as satisfactory

#3 - c4-judge

2023-08-08T14:39:05Z

Picodes changed the severity to 2 (Med Risk)

Findings Information

🌟 Selected for report: volodya

Labels

bug
2 (Med Risk)
satisfactory
selected for report
sponsor confirmed
edited-by-warden
M-27

Awards

1643.9812 USDC - $1,643.98

External Links

Lines of code

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L76

Vulnerability details

Impact

Detailed description of the impact of this finding. Inconsistent behavior for canary claims in claimer

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. Whenever claimer claims prizes he computes fees per claim without considering canary claims

  function claimPrizes(
    Vault vault,
    uint8 tier,
    address[] calldata winners,
    uint32[][] calldata prizeIndices,
    address _feeRecipient
  ) external returns (uint256 totalFees) {
    uint256 claimCount;
    for (uint i = 0; i < winners.length; i++) {
      claimCount += prizeIndices[i].length;
    }

    uint96 feePerClaim = uint96(
      _computeFeePerClaim(
        _computeMaxFee(tier, prizePool.numberOfTiers()),
        claimCount,
        prizePool.claimCount()
      )
    );

    vault.claimPrizes(tier, winners, prizeIndices, feePerClaim, _feeRecipient);

    return feePerClaim * claimCount;
  }

src/Claimer.sol#L76

Tools Used

Consider canary as claim

    uint96 feePerClaim = uint96(
      _computeFeePerClaim(
        _computeMaxFee(tier, prizePool.numberOfTiers()),
        claimCount,
-        prizePool.claimCount()
+        prizePool.claimCount() + prizePool.canaryClaimCount()
      )
    );

Assessed type

Error

#0 - c4-sponsor

2023-07-18T22:57:19Z

asselstine marked the issue as sponsor confirmed

#1 - c4-judge

2023-08-08T13:29:57Z

Picodes marked the issue as satisfactory

#2 - Picodes

2023-08-08T13:30:09Z

Keeping Med severity as fees amounts are affected

Awards

140.2996 USDC - $140.30

Labels

bug
downgraded by judge
grade-a
QA (Quality Assurance)
sponsor confirmed
edited-by-warden
Q-34

External Links

Lines of code

https://github.com/generationsoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L26

Vulnerability details

Impact

Detailed description of the impact of this finding. Tiers odds compuations is not accurate

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. If you will go to wolfram link than equation for tiers can be simplified to this image

So code for ods will become like this

  function getTierOdds(
    uint8 _tier,
    uint8 _numberOfTiers,
    uint16 _grandPrizePeriod
  ) internal pure returns (SD59x18) {
    SD59x18 _k = sd(1).div(sd(int16(_grandPrizePeriod))).ln().div(
      sd((-1 * int8(_numberOfTiers) + 1))
    );

    return E.pow(_k.mul(sd(int8(_tier) - (int8(_numberOfTiers) - 1))));
  }
// new equation
  function getTierOdds2(
    uint8 _tier,
    uint8 _numberOfTiers,
    uint16 _grandPrizePeriod
  ) internal pure returns (SD59x18) {

    return sd(1).div(sd(int16(_grandPrizePeriod))).pow((sd(int8(_tier) - (int8(_numberOfTiers) - 1))).div(sd((-1 * int8(_numberOfTiers) + 1))));
  }

blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L26

Here is an example before and after, its not a huge difference. But seems like real tier odds are slightly less than current this means that protocol will give out prize a little bit more than it should. Above is current formula, below for new formula.

  SD59x18 internal constant TIER_ODDS_1_4 = SD59x18.wrap(19579642462506911);
  SD59x18 internal constant TIER_ODDS_1_4 = SD59x18.wrap(19579642462506911);
  ----------
  SD59x18 internal constant TIER_ODDS_2_4 = SD59x18.wrap(139927275620255366);
  SD59x18 internal constant TIER_ODDS_2_4 = SD59x18.wrap(139927275620255364);
  ----------
  SD59x18 internal constant TIER_ODDS_3_4 = SD59x18.wrap(1000000000000000000);
  SD59x18 internal constant TIER_ODDS_3_4 = SD59x18.wrap(1000000000000000000);
  ----------
  SD59x18 internal constant TIER_ODDS_0_5 = SD59x18.wrap(2739726027397260);
  SD59x18 internal constant TIER_ODDS_0_5 = SD59x18.wrap(2739726027397260);
  ----------
  SD59x18 internal constant TIER_ODDS_1_5 = SD59x18.wrap(11975133168707466);
  SD59x18 internal constant TIER_ODDS_1_5 = SD59x18.wrap(11975133168707465);
  ----------
  SD59x18 internal constant TIER_ODDS_2_5 = SD59x18.wrap(52342392259021369);
  SD59x18 internal constant TIER_ODDS_2_5 = SD59x18.wrap(52342392259021367);
  ----------
  SD59x18 internal constant TIER_ODDS_3_5 = SD59x18.wrap(228784597949733865);
  SD59x18 internal constant TIER_ODDS_3_5 = SD59x18.wrap(228784597949733862);

Tools Used

Fix a tier odds

Assessed type

Error

#0 - c4-sponsor

2023-07-18T23:11:11Z

asselstine marked the issue as sponsor confirmed

#1 - Picodes

2023-08-08T13:28:52Z

Considering the rounding issue is of a few wei, I'll downgrade this to low under the assumption that the error is not significant.

#2 - c4-judge

2023-08-08T13:28:58Z

Picodes changed the severity to QA (Quality Assurance)

#3 - c4-judge

2023-08-08T14:36:54Z

Picodes marked the issue as grade-a

#4 - asselstine

2023-08-17T21:34:37Z

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