From 1b4b5cdb93aa5c1e3b5cca294a576a898ad3f368 Mon Sep 17 00:00:00 2001 From: Brendan Asselstine Date: Tue, 30 Jan 2024 09:04:55 -0800 Subject: [PATCH] Limited tier changes to one per draw --- src/PrizePool.sol | 10 ++++- src/abstract/TieredLiquidityDistributor.sol | 26 ++++++----- test/PrizePool.t.sol | 40 ++++++++++++----- .../abstract/TieredLiquidityDistributor.t.sol | 44 ++++++++++++++++++ .../TieredLiquidityDistributorWrapper.sol | 45 ++++++++++++++----- .../TieredLiquidityDistributorFuzzHarness.sol | 3 -- 6 files changed, 129 insertions(+), 39 deletions(-) diff --git a/src/PrizePool.sol b/src/PrizePool.sol index 0d807f5..ee2a63d 100644 --- a/src/PrizePool.sol +++ b/src/PrizePool.sol @@ -838,7 +838,15 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { /// @return The estimated number of tiers + the canary tier function _computeNextNumberOfTiers(uint32 _claimCount) internal view returns (uint8) { // claimCount is expected to be the estimated number of claims for the current prize tier. - return _estimateNumberOfTiersUsingPrizeCountPerDraw(_claimCount) + 1; + uint8 nextNumberOfTiers = _estimateNumberOfTiersUsingPrizeCountPerDraw(_claimCount) + 1; + // limit change to 1 tier + uint8 _numTiers = numberOfTiers; + if (nextNumberOfTiers > _numTiers) { + nextNumberOfTiers = _numTiers + 1; + } else if (nextNumberOfTiers < _numTiers) { + nextNumberOfTiers = _numTiers - 1; + } + return nextNumberOfTiers; } /// @notice Calculates the number of tiers given the number of prize claims diff --git a/src/abstract/TieredLiquidityDistributor.sol b/src/abstract/TieredLiquidityDistributor.sol index 85783c7..7fb479c 100644 --- a/src/abstract/TieredLiquidityDistributor.sol +++ b/src/abstract/TieredLiquidityDistributor.sol @@ -250,7 +250,6 @@ contract TieredLiquidityDistributor { // need to redistribute to the canary tier and any new tiers (if expanding) uint8 start; uint8 end; - uint8 shares = tierShares; // if we are shrinking, we need to reclaim including the new canary tier if (_nextNumberOfTiers < _numberOfTiers) { start = _nextNumberOfTiers - 1; @@ -263,7 +262,6 @@ contract TieredLiquidityDistributor { for (uint8 i = start; i < end; i++) { reclaimedLiquidity = reclaimedLiquidity.add( _getTierRemainingLiquidity( - shares, fromUD34x4toUD60x18(_tiers[i].prizeTokenPerShare), _currentPrizeTokenPerShare ) @@ -350,7 +348,6 @@ contract TieredLiquidityDistributor { uint104 remainingLiquidity = SafeCast.toUint104( convert( _getTierRemainingLiquidity( - _shares, fromUD34x4toUD60x18(_tierStruct.prizeTokenPerShare), fromUD34x4toUD60x18(prizeTokenPerShare) ) @@ -443,35 +440,42 @@ contract TieredLiquidityDistributor { /// @notice Computes the remaining liquidity available to a tier. /// @param _tier The tier to compute the liquidity for /// @return The remaining liquidity - function getTierRemainingLiquidity(uint8 _tier) external view returns (uint256) { - uint8 _numTiers = numberOfTiers; + function getTierRemainingLiquidity(uint8 _tier) public view returns (uint256) { + return _getTierRemainingLiquidity(_tier, fromUD34x4toUD60x18(prizeTokenPerShare)); + } + /// @notice Computes the remaining liquidity available to a tier. + /// @param _tier The tier to compute the liquidity for + /// @param _prizeTokenPerShare The global prizeTokenPerShare + /// @return The remaining liquidity + function _getTierRemainingLiquidity( + uint8 _tier, + UD60x18 _prizeTokenPerShare + ) internal view returns (uint256) { + uint8 _numTiers = numberOfTiers; return !TierCalculationLib.isValidTier(_tier, _numTiers) ? 0 : convert( _getTierRemainingLiquidity( - tierShares, fromUD34x4toUD60x18(_getTier(_tier, _numTiers).prizeTokenPerShare), - fromUD34x4toUD60x18(prizeTokenPerShare) + _prizeTokenPerShare ) ); } /// @notice Computes the remaining tier liquidity. - /// @param _shares The number of shares that the tier has (can be tierShares or canaryShares) /// @param _tierPrizeTokenPerShare The prizeTokenPerShare of the Tier struct /// @param _prizeTokenPerShare The global prizeTokenPerShare /// @return The remaining available liquidity function _getTierRemainingLiquidity( - uint256 _shares, UD60x18 _tierPrizeTokenPerShare, UD60x18 _prizeTokenPerShare - ) internal pure returns (UD60x18) { + ) internal view returns (UD60x18) { return _tierPrizeTokenPerShare.gte(_prizeTokenPerShare) ? ud(0) - : _prizeTokenPerShare.sub(_tierPrizeTokenPerShare).mul(convert(_shares)); + : _prizeTokenPerShare.sub(_tierPrizeTokenPerShare).mul(convert(tierShares)); } /// @notice Estimates the number of prizes for the current number of tiers, including the canary tier diff --git a/test/PrizePool.t.sol b/test/PrizePool.t.sol index f681fcc..66b1ac1 100644 --- a/test/PrizePool.t.sol +++ b/test/PrizePool.t.sol @@ -625,8 +625,13 @@ contract PrizePoolTest is Test { contribute(510e18); - assertEq(prizePool.estimateNextNumberOfTiers(), 3, "will reduce to 3"); + assertEq(prizePool.estimateNextNumberOfTiers(), 4, "will reduce to 4"); + // first draw (same num tiers) + // total for first draw = 0.1 * 510e18 = 51e18 + // 5th tier (canary tier) = (100/510)*51e18 = 10e18 + // 4th tier (daily tier) = (100/510)*51.18 = 10e18 + // reserve = 51e18 * (10 / 510) = 1e18 awardDraw(1234); assertEq(prizePool.reserve(), 1e18, "reserve after first draw"); @@ -637,16 +642,20 @@ contract PrizePoolTest is Test { 2, 4567, startingTiers, - 3, - 3448387096774193470 /*reserve from output*/, - UD34x4.wrap(3448387096774193330000) /*prize tokens per share from output*/, + 4, // change is limited to 1 tier + 2607317073170731770 /*reserve from output*/, + UD34x4.wrap(2607317073170731540000) /*prize tokens per share from output*/, firstDrawOpensAt + drawPeriodSeconds ); // award second draw + // reclaimed tiers: 10e18 + 10e18 = 20e18 + // liquidity for second draw = 0.1 * (510e18 - 51e18) + 20e18 = 65.9e18 + // reserve for second draw = 65.9e18 * (10 / 410) = 1607317073170731800 + // total reserve = 1e18 + 1607317073170731800 = 2607317073170732000 awardDraw(4567); - assertEq(prizePool.numberOfTiers(), 3, "number of tiers"); + assertEq(prizePool.numberOfTiers(), 4, "number of tiers"); // two tiers + canary tier = 30e18 // total liquidity for second draw is 45.9e18 @@ -654,7 +663,7 @@ contract PrizePoolTest is Test { // reserve for second draw = (10/310)*75.9e18 = 2.445e18 // total reserve = 3.445e18 - assertEq(prizePool.reserve(), 3.44838709677419347e18, "size of reserve"); + assertEq(prizePool.reserve(), 2607317073170731770, "size of reserve"); } function testAwardDraw_expandingTiers() public { @@ -1003,19 +1012,26 @@ contract PrizePoolTest is Test { assertEq(prizePool.computeNextNumberOfTiers(24), 4); } - function testComputeNextNumberOfTiers_beyondMinimum() public { + function testComputeNextNumberOfTiers_beyondMinimum_maxIncreaseBy1() public { // canary prizes were taken for tier 4! - assertEq(prizePool.computeNextNumberOfTiers(80), 5); + // should crank up to 5, but limit increasee to 1 + assertEq(prizePool.computeNextNumberOfTiers(80), 4); } - function testComputeNextNumberOfTiers_beyondMinimum_bigDeviation() public { + function testComputeNextNumberOfTiers_beyondMinimum_bigDeviation_maxIncreaseBy1() public { // half the canary prizes were taken - assertEq(prizePool.computeNextNumberOfTiers(150), 5); + assertEq(prizePool.computeNextNumberOfTiers(150), 4); } - function testComputeNextNumberOfTiers_beyondMinimum_nextLevelUp() public { + function testComputeNextNumberOfTiers_beyondMinimum_nextLevelUp_maxIncreaseBy1() public { // half the canary prizes were taken - assertEq(prizePool.computeNextNumberOfTiers(200), 6); + assertEq(prizePool.computeNextNumberOfTiers(200), 4); + } + + function testComputeNextNumberOfTiers_drop_maxDecreaseBy1() public { + params.numberOfTiers = 5; + prizePool = new PrizePool(params); + assertEq(prizePool.computeNextNumberOfTiers(0), 4); } function testClaimPrize_secondTier_claimTwice() public { diff --git a/test/abstract/TieredLiquidityDistributor.t.sol b/test/abstract/TieredLiquidityDistributor.t.sol index 082eb25..1f2c2fa 100644 --- a/test/abstract/TieredLiquidityDistributor.t.sol +++ b/test/abstract/TieredLiquidityDistributor.t.sol @@ -34,6 +34,36 @@ contract TieredLiquidityDistributorTest is Test { distributor.awardDraw(1, 100); } + function testAwardDraw_liquidity_shrinkTiers1() public { + distributor.awardDraw(5, 100e18); + distributor.awardDraw(4, 100e18); + assertEq(_computeLiquidity(), 200e18); + } + + function testAwardDraw_liquidity_shrinkTiers2() public { + distributor.awardDraw(5, 100e18); + distributor.awardDraw(3, 100e18); + assertEq(_computeLiquidity(), 200e18); + } + + function testAwardDraw_liquidity_sameTiers() public { + distributor.awardDraw(5, 100e18); + distributor.awardDraw(5, 100e18); + assertEq(_computeLiquidity(), 200e18); + } + + function testAwardDraw_liquidity_growTiers1() public { + distributor.awardDraw(5, 100e18); + distributor.awardDraw(6, 100e18); + assertEq(_computeLiquidity(), 200e18); + } + + function testAwardDraw_liquidity_growTiers2() public { + distributor.awardDraw(5, 100e18); + distributor.awardDraw(7, 100e18); + assertEq(_computeLiquidity(), 200e18); + } + function testConstructor_numberOfTiersTooLarge() public { vm.expectRevert(abi.encodeWithSelector(NumberOfTiersGreaterThanMaximum.selector, 16)); new TieredLiquidityDistributorWrapper(16, tierShares, reserveShares, 365); @@ -295,4 +325,18 @@ contract TieredLiquidityDistributorTest is Test { summed += distributor.reserve(); return summed; } + + function _computeLiquidity() internal view returns (uint256) { + uint256 liquidity = _getTierLiquidity(distributor.numberOfTiers(), fromUD34x4toUD60x18(distributor.prizeTokenPerShare())); + liquidity += distributor.reserve(); + return liquidity; + } + + function _getTierLiquidity(uint8 _numberOfTiers, UD60x18 _prizeTokenPerShare) internal view returns (uint256) { + uint256 liquidity = 0; + for (uint8 i = 0; i < _numberOfTiers; i++) { + liquidity += distributor.getTierRemainingLiquidity(i, _prizeTokenPerShare); + } + return liquidity; + } } diff --git a/test/abstract/helper/TieredLiquidityDistributorWrapper.sol b/test/abstract/helper/TieredLiquidityDistributorWrapper.sol index 56b3fdb..2ea7d06 100644 --- a/test/abstract/helper/TieredLiquidityDistributorWrapper.sol +++ b/test/abstract/helper/TieredLiquidityDistributorWrapper.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; import "forge-std/console2.sol"; import { TieredLiquidityDistributor, Tier, fromUD34x4toUD60x18, convert } from "../../../src/abstract/TieredLiquidityDistributor.sol"; +import { UD60x18 } from "prb-math/UD60x18.sol"; contract TieredLiquidityDistributorWrapper is TieredLiquidityDistributor { constructor( @@ -25,18 +26,33 @@ contract TieredLiquidityDistributorWrapper is TieredLiquidityDistributor { } function remainingTierLiquidity(uint8 _tier) external view returns (uint112) { - uint8 shares = tierShares; - Tier memory tier = _getTier(_tier, numberOfTiers); - return - uint112( - convert( - _getTierRemainingLiquidity( - shares, - fromUD34x4toUD60x18(tier.prizeTokenPerShare), - fromUD34x4toUD60x18(prizeTokenPerShare) - ) - ) - ); + return uint112(getTierRemainingLiquidity(_tier)); + // uint8 shares = tierShares; + // Tier memory tier = _getTier(_tier, numberOfTiers); + // return + // uint112( + // convert( + // _getTierRemainingLiquidity( + // fromUD34x4toUD60x18(tier.prizeTokenPerShare), + // fromUD34x4toUD60x18(prizeTokenPerShare) + // ) + // ) + // ); + } + + function computeNewDistributions( + uint8 _numberOfTiers, + uint8 _nextNumberOfTiers, + UD60x18 _currentPrizeTokenPerShare, + uint256 _prizeTokenLiquidity + ) external view returns (uint96, UD60x18) { + (uint96 newReserve, UD60x18 newPrizeTokenPerShare) = _computeNewDistributions( + _numberOfTiers, + _nextNumberOfTiers, + _currentPrizeTokenPerShare, + _prizeTokenLiquidity + ); + return (newReserve, newPrizeTokenPerShare); } function estimateNumberOfTiersUsingPrizeCountPerDraw( @@ -50,4 +66,9 @@ contract TieredLiquidityDistributorWrapper is TieredLiquidityDistributor { uint32 result = _sumTierPrizeCounts(_numTiers); return result; } + + function getTierRemainingLiquidity(uint8 _tier, UD60x18 _prizeTokenPerShare) external view returns (uint256) { + uint256 result = _getTierRemainingLiquidity(_tier, _prizeTokenPerShare); + return result; + } } diff --git a/test/invariants/helpers/TieredLiquidityDistributorFuzzHarness.sol b/test/invariants/helpers/TieredLiquidityDistributorFuzzHarness.sol index 57cb524..3b09234 100644 --- a/test/invariants/helpers/TieredLiquidityDistributorFuzzHarness.sol +++ b/test/invariants/helpers/TieredLiquidityDistributorFuzzHarness.sol @@ -27,7 +27,6 @@ contract TieredLiquidityDistributorFuzzHarness is TieredLiquidityDistributor { Tier memory tier = _getTier(i, numberOfTiers); availableLiquidity += convert( _getTierRemainingLiquidity( - tierShares, fromUD34x4toUD60x18(tier.prizeTokenPerShare), fromUD34x4toUD60x18(prizeTokenPerShare) ) @@ -43,11 +42,9 @@ contract TieredLiquidityDistributorFuzzHarness is TieredLiquidityDistributor { uint8 tier = _tier % numberOfTiers; Tier memory tier_ = _getTier(tier, numberOfTiers); - uint8 shares = tierShares; uint104 liq = uint104( convert( _getTierRemainingLiquidity( - shares, fromUD34x4toUD60x18(tier_.prizeTokenPerShare), fromUD34x4toUD60x18(prizeTokenPerShare) )