diff --git a/lib/forge-std b/lib/forge-std index f73c73d..bb4ceea 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 9329cfa..dc44c9f 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 9329cfacd4c7d20bcb43d772d947ff9e39b65df9 +Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 diff --git a/lib/prb-math b/lib/prb-math index 77fa88e..9dc0651 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 77fa88eda4a4a91b3f3e9431df291292c26b6c71 +Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c diff --git a/lib/pt-v5-claimable-interface b/lib/pt-v5-claimable-interface index d98557e..bcdb174 160000 --- a/lib/pt-v5-claimable-interface +++ b/lib/pt-v5-claimable-interface @@ -1 +1 @@ -Subproject commit d98557ed43321ccbd357d0c5d2464911f145a7dd +Subproject commit bcdb17442c4d7f4c268301190e12106a0e57eae2 diff --git a/lib/pt-v5-prize-pool b/lib/pt-v5-prize-pool index 19ffd4d..cce5555 160000 --- a/lib/pt-v5-prize-pool +++ b/lib/pt-v5-prize-pool @@ -1 +1 @@ -Subproject commit 19ffd4df1598ee2bfd259b502d1fb4369274e810 +Subproject commit cce55551219adad79f4e0b041a067c67d3057732 diff --git a/lib/solmate b/lib/solmate index 0384dba..c892309 160000 --- a/lib/solmate +++ b/lib/solmate @@ -1 +1 @@ -Subproject commit 0384dbaaa4fcb5715738a9254a7c0a4cb62cf458 +Subproject commit c892309933b25c03d32b1b0d674df7ae292ba925 diff --git a/src/Claimer.sol b/src/Claimer.sol index f43e4b0..093d01f 100644 --- a/src/Claimer.sol +++ b/src/Claimer.sol @@ -216,8 +216,9 @@ contract Claimer { if (prizePool.isCanaryTier(_tier)) { return prizePool.getTierPrizeSize(_tier); } - uint256 targetFee = _computeFeeTarget(); - SD59x18 decayConstant = _computeDecayConstant(targetFee); + uint8 numberOfTiers = prizePool.numberOfTiers(); + uint256 targetFee = _computeFeeTarget(numberOfTiers); + SD59x18 decayConstant = _computeDecayConstant(targetFee, numberOfTiers); uint256 _maxFee = _computeMaxFee(_tier); SD59x18 perTimeUnit = LinearVRGDALib.getPerTimeUnit( prizePool.estimatedPrizeCountWithBothCanaries(), @@ -252,19 +253,23 @@ contract Claimer { } /// @notice Compute the target fee for prize claims + /// @param _numberOfTiers The current number of tiers for the prize pool /// @return The target fee for prize claims - function _computeFeeTarget() internal view returns (uint256) { - uint8 numberOfTiers = prizePool.numberOfTiers(); - // we expect the fee to match the second canary tier - return prizePool.getTierPrizeSize(numberOfTiers - 2); + function _computeFeeTarget(uint8 _numberOfTiers) internal view returns (uint256) { + // we expect the fee to be somewhere between the first and second canary tier prize sizes, + // so we set it to the lower of the two. + return prizePool.getTierPrizeSize(_numberOfTiers - 1); } /// @notice Computes the decay constant for the VRGDA. /// @dev This is a decay constant that ensures the fee will grow from the target to the max fee within the time frame - /// @param _targetFee The target fee + /// @param _targetFee The target fee + /// @param _numberOfTiers The current number of tiers for the prize pool /// @return The decay constant - function _computeDecayConstant(uint256 _targetFee) internal view returns (SD59x18) { - uint maximumFee = _computeMaxFee(0); + function _computeDecayConstant(uint256 _targetFee, uint8 _numberOfTiers) internal view returns (SD59x18) { + // the max fee should never need to go beyond the full daily prize size under normal operating + // conditions. + uint maximumFee = prizePool.getTierPrizeSize(_numberOfTiers - 3); return LinearVRGDALib.getDecayConstant( LinearVRGDALib.getMaximumPriceDeltaScale( _targetFee, diff --git a/test/Claimer.t.sol b/test/Claimer.t.sol index 242829a..d9f3276 100644 --- a/test/Claimer.t.sol +++ b/test/Claimer.t.sol @@ -29,12 +29,12 @@ contract ClaimerTest is Test { bytes reason ); - uint256 public MINIMUM_FEE = 0.0001e18; - uint256 public MAXIMUM_FEE = 1000e18; + uint256 public PRIZE_SIZE_GP = 1000000e18; + uint256 public PRIZE_SIZE_DAILY = 500e18; + uint256 public PRIZE_SIZE_C1 = 0.01e18; + uint256 public PRIZE_SIZE_C2 = 0.0001e18; uint256 public TIME_TO_REACH_MAX = 86400; uint256 public ESTIMATED_PRIZES = 1000; - uint256 public SMALLEST_PRIZE_SIZE = 1e18; - uint256 public CANARY_PRIZE_SIZE = 0.4e18; uint256 public NO_SALES_100_SECONDS_BEHIND_SCHEDULE_FEE = 100243095112994; uint256 public SOLD_ONE_100_SECONDS_IN_FEE = 98708714827462; uint64 public MAX_FEE_PERCENTAGE_OF_PRIZE = 0.5e18; @@ -63,6 +63,11 @@ contract ClaimerTest is Test { mockIsCanaryTier(1, false); mockIsCanaryTier(2, true); mockIsCanaryTier(3, true); + + mockGetTierPrizeSize(0, PRIZE_SIZE_GP); + mockGetTierPrizeSize(1, PRIZE_SIZE_DAILY); + mockGetTierPrizeSize(2, PRIZE_SIZE_C1); + mockGetTierPrizeSize(3, PRIZE_SIZE_C2); } function testConstructor() public { @@ -204,21 +209,19 @@ contract ClaimerTest is Test { function testClaimPrizes_maxFee() public { address[] memory winners = newWinners(winner1); uint32[][] memory prizeIndices = newPrizeIndices(1, 1); - mockPrizePool(1, -1, 0); - mockLastClosedDrawAwardedAt(-80000); // much time has passed, meaning the fee is large - mockClaimPrize(1, winner1, 0, uint96(0.5e18), address(this), 100); + mockPrizePool(1, -1 * int256((99 * TIME_TO_REACH_MAX) / 100), 0); // much time has passed, meaning the fee is large + mockClaimPrize(1, winner1, 0, uint96(PRIZE_SIZE_DAILY / 2), address(this), PRIZE_SIZE_DAILY); uint256 totalFees = claimer.claimPrizes(vault, 1, winners, prizeIndices, address(this), 0); - assertEq(totalFees, 0.5e18, "Total fees"); + assertEq(totalFees, PRIZE_SIZE_DAILY / 2, "Total fees"); } function testClaimPrizes_veryLongElapsedTime() public { address[] memory winners = newWinners(winner1); uint32[][] memory prizeIndices = newPrizeIndices(1, 1); - mockPrizePool(1, -1, 0); - mockLastClosedDrawAwardedAt(-1_000_000); // a long time has passed, meaning the fee should be capped (and there should be no EXP_OVERFLOW!) - mockClaimPrize(1, winner1, 0, uint96(0.5e18), address(this), 100); + mockPrizePool(1, -1_000_000, 0);// a long time has passed, meaning the fee should be capped (and there should be no EXP_OVERFLOW!) + mockClaimPrize(1, winner1, 0, uint96(PRIZE_SIZE_DAILY / 2), address(this), PRIZE_SIZE_DAILY); uint256 totalFees = claimer.claimPrizes(vault, 1, winners, prizeIndices, address(this), 0); - assertEq(totalFees, 0.5e18, "Total fees"); + assertEq(totalFees, PRIZE_SIZE_DAILY / 2, "Total fees"); } function testClaimPrizes_arrayMismatchGt() public { @@ -277,26 +280,20 @@ contract ClaimerTest is Test { } function testComputeTotalFees_canary() public { - mockIsCanaryTier(1, true); - mockGetTierPrizeSize(1, 100e18); vm.mockCall( address(prizePool), abi.encodeWithSelector(prizePool.claimCount.selector), abi.encode(0) ); - assertEq(claimer.computeTotalFees(1, 1), 100e18); + assertEq(claimer.computeTotalFees(2, 1), PRIZE_SIZE_C1); } function testComputeMaxFee_normalPrizes() public { - mockGetTierPrizeSize(0, 10e18); - mockGetTierPrizeSize(1, SMALLEST_PRIZE_SIZE); - assertEq(claimer.computeMaxFee(0), 5e18); - assertEq(claimer.computeMaxFee(1), 0.5e18); + assertEq(claimer.computeMaxFee(0), PRIZE_SIZE_GP / 2); + assertEq(claimer.computeMaxFee(1), PRIZE_SIZE_DAILY / 2); } function testComputeMaxFee_canaryPrizes() public { - mockGetTierPrizeSize(2, 0.5e18); - mockGetTierPrizeSize(3, 1e18); assertEq(claimer.computeMaxFee(2), type(uint256).max); // full size assertEq(claimer.computeMaxFee(3), type(uint256).max); // full size } @@ -312,7 +309,7 @@ contract ClaimerTest is Test { ud2x18(MAX_FEE_PERCENTAGE_OF_PRIZE) ); mockPrizePool(1, -int(firstSaleTime), 0); - assertApproxEqAbs(claimer.computeFeePerClaim(0, 1), MINIMUM_FEE, 4); + assertApproxEqAbs(claimer.computeFeePerClaim(0, 1), PRIZE_SIZE_C2, 4); } function testComputeFeePerClaim_maxFee() public { @@ -322,11 +319,10 @@ contract ClaimerTest is Test { uint firstSaleTime = TIME_TO_REACH_MAX / ESTIMATED_PRIZES; vm.warp(startTime + firstSaleTime + TIME_TO_REACH_MAX + 1); - assertApproxEqAbs(claimer.computeFeePerClaim(0, 1), MAXIMUM_FEE/2 /* 50% */, 4); + assertApproxEqRel(claimer.computeFeePerClaim(0, 1), PRIZE_SIZE_DAILY, 0.02e18); } function mockPrizePool(uint256 drawId, int256 drawEndedRelativeToNow, uint256 claimCount) public { - uint numberOfTiers = 3; vm.mockCall( address(prizePool), abi.encodeWithSignature("getLastClosedDrawId()"), @@ -342,9 +338,6 @@ contract ClaimerTest is Test { abi.encodeWithSignature("estimatedPrizeCountWithBothCanaries()"), abi.encodePacked(ESTIMATED_PRIZES) ); - mockGetTierPrizeSize(1, SMALLEST_PRIZE_SIZE); - mockGetTierPrizeSize(2, MINIMUM_FEE); - mockGetTierPrizeSize(0, MAXIMUM_FEE); mockLastClosedDrawAwardedAt(drawEndedRelativeToNow); vm.mockCall( address(prizePool),