Skip to content

Commit

Permalink
Merge pull request #26 from GenerationSoftware/gen-1157-decrease-cana…
Browse files Browse the repository at this point in the history
…ry-liquidity-to-increase-daily-prize-sizes

Eliminated fee limit for canary tiers
  • Loading branch information
asselstine committed Feb 29, 2024
2 parents 56dc13f + 364b4c5 commit 9719308
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 43 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ In PoolTogether V5 prizes are awarded every "Draw". When a user wins a prize a t
- Prizes come from the Prize Pool.
- All depositors in Vaults that have contributed yield through a Liquidator recently are elgigible to win a prize.

Anyone may capture fees by claiming prizes through the Claimer contract. The claimer contract will price fees according to a VRGDA algorithm. The fees for the two canary tiers are always set to the prize size; meaning that the claimers can capture the whole prize.

## Development

### Installation
Expand Down
71 changes: 37 additions & 34 deletions src/Claimer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.19;

import { SD59x18 } from "prb-math/SD59x18.sol";
import { UD2x18 } from "prb-math/UD2x18.sol";
import { UD60x18 } from "prb-math/UD60x18.sol";
import { UD60x18, convert } from "prb-math/UD60x18.sol";
import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";
import { SafeCast } from "openzeppelin/utils/math/SafeCast.sol";

Expand Down Expand Up @@ -33,8 +33,9 @@ error FeeRecipientZeroAddress();

/// @title Variable Rate Gradual Dutch Auction (VRGDA) Claimer
/// @author G9 Software Inc.
/// @notice This contract uses a variable rate gradual dutch auction to incentivize prize claims on behalf of others
/// @notice This contract uses a variable rate gradual dutch auction to incentivize prize claims on behalf of others. Fees for each canary tier is set to the respective tier's prize size.
contract Claimer {

/// @notice Emitted when a prize has already been claimed
/// @param winner The winner of the prize
/// @param tier The prize tier
Expand Down Expand Up @@ -102,24 +103,24 @@ contract Claimer {

/// @notice Allows the caller to claim prizes on behalf of others or for themself.
/// @dev If you are claiming for yourself or don't want to take a fee, set the `_feeRecipient` and
/// `_minVrgdaFeePerClaim` to zero. This will save some gas on fee calculation.
/// `_minFeePerClaim` to zero. This will save some gas on fee calculation.
/// @param _vault The vault to claim from
/// @param _tier The tier to claim for
/// @param _winners The array of winners to claim for
/// @param _prizeIndices The array of prize indices to claim for each winner (length should match winners)
/// @param _feeRecipient The address to receive the claim fees
/// @param _minVrgdaFeePerClaim The minimum fee for each claim
/// @param _minFeePerClaim The minimum fee for each claim
/// @return totalFees The total fees collected across all successful claims
function claimPrizes(
IClaimable _vault,
uint8 _tier,
address[] calldata _winners,
uint32[][] calldata _prizeIndices,
address _feeRecipient,
uint256 _minVrgdaFeePerClaim
uint256 _minFeePerClaim
) external returns (uint256 totalFees) {
bool feeRecipientZeroAddress = address(0) == _feeRecipient;
if (feeRecipientZeroAddress && _minVrgdaFeePerClaim != 0) {
if (feeRecipientZeroAddress && _minFeePerClaim != 0) {
revert FeeRecipientZeroAddress();
}
if (_winners.length != _prizeIndices.length) {
Expand All @@ -133,22 +134,20 @@ contract Claimer {
* expect a fee and save them some gas on the calculation.
*/
if (!feeRecipientZeroAddress) {
feePerClaim = SafeCast.toUint96(_computeFeePerClaimForBatch(_tier, _winners, _prizeIndices));
if (feePerClaim < _minVrgdaFeePerClaim) {
revert VrgdaClaimFeeBelowMin(_minVrgdaFeePerClaim, feePerClaim);
feePerClaim = SafeCast.toUint96(_computeFeePerClaim(_tier, _countClaims(_winners, _prizeIndices), prizePool.claimCount()));
if (feePerClaim < _minFeePerClaim) {
revert VrgdaClaimFeeBelowMin(_minFeePerClaim, feePerClaim);
}
}

return feePerClaim * _claim(_vault, _tier, _winners, _prizeIndices, _feeRecipient, feePerClaim);
}

/// @notice Computes the fee per claim given a batch of winners and prize indices
/// @param _tier The tier the claims are for
/// @notice Computes the number of claims that will be made
/// @param _winners The array of winners to claim for
/// @param _prizeIndices The array of prize indices to claim for each winner (length should match winners)
/// @return The fee per claim
function _computeFeePerClaimForBatch(
uint8 _tier,
/// @return The number of claims
function _countClaims(
address[] calldata _winners,
uint32[][] calldata _prizeIndices
) internal view returns (uint256) {
Expand All @@ -157,8 +156,7 @@ contract Claimer {
for (uint256 i = 0; i < length; i++) {
claimCount += _prizeIndices[i].length;
}

return _computeFeePerClaim(_computeMaxFee(_tier), claimCount, prizePool.claimCount());
return claimCount;
}

/// @notice Claims prizes for a batch of winners and prize indices
Expand Down Expand Up @@ -206,8 +204,7 @@ contract Claimer {
/// @param _claimCount The number of claims
/// @return The total fees for those claims
function computeTotalFees(uint8 _tier, uint256 _claimCount) external view returns (uint256) {
return
_computeFeePerClaim(_computeMaxFee(_tier), _claimCount, prizePool.claimCount()) * _claimCount;
return computeTotalFees(_tier, _claimCount, prizePool.claimCount());
}

/// @notice Computes the total fees for the given number of claims if a number of claims have already been made.
Expand All @@ -219,34 +216,35 @@ contract Claimer {
uint8 _tier,
uint256 _claimCount,
uint256 _claimedCount
) external view returns (uint256) {
return _computeFeePerClaim(_computeMaxFee(_tier), _claimCount, _claimedCount) * _claimCount;
) public view returns (uint256) {
return _computeFeePerClaim(_tier, _claimCount, _claimedCount) * _claimCount;
}

/// @notice Computes the fees for several claims.
/// @param _maxFee the maximum fee that can be charged
/// @param _claimCount the number of claims to check
/// @return The fees for the claims
function computeFeePerClaim(
uint256 _maxFee,
uint256 _claimCount
) external view returns (uint256) {
return _computeFeePerClaim(_maxFee, _claimCount, prizePool.claimCount());
/// @notice Computes the fee per claim for the given tier and number of claims
/// @param _tier The tier to claim prizes from
/// @param _claimCount The number of claims
/// @return The fee that will be taken per claim
function computeFeePerClaim(uint8 _tier, uint256 _claimCount) external view returns (uint256) {
return _computeFeePerClaim(_tier, _claimCount, prizePool.claimCount());
}

/// @notice Computes the total fees for the given number of claims.
/// @param _maxFee The maximum fee
/// @param _tier The tier
/// @param _claimCount The number of claims to check
/// @param _claimedCount The number of prizes already claimed
/// @return The total fees for the claims
function _computeFeePerClaim(
uint256 _maxFee,
uint8 _tier,
uint256 _claimCount,
uint256 _claimedCount
) internal view returns (uint256) {
if (_claimCount == 0) {
return 0;
}
if (prizePool.isCanaryTier(_tier)) {
return prizePool.getTierPrizeSize(_tier);
}
uint256 _maxFee = _computeMaxFee(_tier);
SD59x18 perTimeUnit = LinearVRGDALib.getPerTimeUnit(
prizePool.estimatedPrizeCount(),
timeToReachMaxFee
Expand All @@ -272,16 +270,21 @@ contract Claimer {
/// @param _tier The tier to compute the max fee for
/// @return The maximum fee that can be charged
function computeMaxFee(uint8 _tier) public view returns (uint256) {
return _computeMaxFee(_tier);
if (prizePool.isCanaryTier(_tier)) {
return prizePool.getTierPrizeSize(_tier);
} else {
return _computeMaxFee(_tier);
}
}

/// @notice Computes the max fee given the tier
/// @param _tier The tier to compute the max fee for
/// @return The maximum fee that will be charged for a prize claim for the given tier
function _computeMaxFee(uint8 _tier) internal view returns (uint256) {
uint256 prizeSize = prizePool.getTierPrizeSize(_tier);
return
UD60x18.unwrap(
maxFeePortionOfPrize.intoUD60x18().mul(UD60x18.wrap(prizePool.getTierPrizeSize(_tier)))
convert(
maxFeePortionOfPrize.intoUD60x18().mul(convert(prizeSize))
);
}

Expand Down
45 changes: 37 additions & 8 deletions test/Claimer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ contract ClaimerTest is Test {
uint64 public constant MAX_FEE_PERCENTAGE_OF_PRIZE = 0.5e18;

Claimer public claimer;
PrizePool public prizePool = PrizePool(address(0x1234));
IClaimable public vault;
PrizePool public prizePool = PrizePool(makeAddr("prizePool"));
IClaimable public vault = IClaimable(makeAddr("vault"));

SD59x18 public decayConstant;
uint256 public ahead1_fee; // = 0.000090909090909090e18;
Expand All @@ -52,7 +52,6 @@ contract ClaimerTest is Test {
function setUp() public {
vm.warp(TIME_TO_REACH_MAX * 100);
vm.etch(address(prizePool), "prizePool");
vault = IClaimable(address(0x99));
vm.etch(address(vault), "fakecode");
claimer = new Claimer(
prizePool,
Expand All @@ -71,6 +70,10 @@ contract ClaimerTest is Test {
LinearVRGDALib.getPerTimeUnit(ESTIMATED_PRIZES, TIME_TO_REACH_MAX),
decayConstant
);
mockIsCanaryTier(0, false);
mockIsCanaryTier(1, false);
mockIsCanaryTier(2, true);
mockIsCanaryTier(3, true);
}

function testConstructor() public {
Expand Down Expand Up @@ -149,7 +152,7 @@ contract ClaimerTest is Test {
assertEq(totalNoFeeFees, 0, "Total fees");

// Check gas
console2.log("no fee claim gas savings: ", feeClaimGasUsed - noFeeClaimGasUsed);
// console2.log("no fee claim gas savings: ", feeClaimGasUsed - noFeeClaimGasUsed);
assertGt(feeClaimGasUsed, noFeeClaimGasUsed, "Fee / No Fee Gas Difference");
}

Expand Down Expand Up @@ -272,6 +275,17 @@ contract ClaimerTest is Test {
assertEq(claimer.computeTotalFees(1, 2, 10), 169295709060728);
}

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);
}

function testComputeMaxFee_normalPrizes() public {
mockGetTierPrizeSize(0, 10e18);
mockGetTierPrizeSize(1, SMALLEST_PRIZE_SIZE);
Expand All @@ -281,7 +295,9 @@ contract ClaimerTest is Test {

function testComputeMaxFee_canaryPrizes() public {
mockGetTierPrizeSize(2, 0.5e18);
assertEq(claimer.computeMaxFee(2), 0.25e18);
mockGetTierPrizeSize(3, 1e18);
assertEq(claimer.computeMaxFee(2), 0.5e18); // full size
assertEq(claimer.computeMaxFee(3), 1e18); // full size
}

function testComputeFeePerClaim_minFee() public {
Expand All @@ -302,10 +318,12 @@ contract ClaimerTest is Test {
abi.encodeWithSelector(prizePool.claimCount.selector),
abi.encode(0)
);
mockIsCanaryTier(0, false);
mockGetTierPrizeSize(0, 100e18);

uint firstSaleTime = TIME_TO_REACH_MAX / prizeCount;
vm.warp(startTime + firstSaleTime);
assertApproxEqAbs(claimer.computeFeePerClaim(100e18, 1), MINIMUM_FEE, 4);
assertApproxEqAbs(claimer.computeFeePerClaim(0, 1), MINIMUM_FEE, 4);
}

function testComputeFeePerClaim_maxFee() public {
Expand All @@ -327,10 +345,13 @@ contract ClaimerTest is Test {
abi.encode(0)
);

mockIsCanaryTier(0, false);
mockGetTierPrizeSize(0, MAXIMUM_FEE*2);

uint firstSaleTime = TIME_TO_REACH_MAX / prizeCount;

vm.warp(startTime + firstSaleTime + TIME_TO_REACH_MAX + 1);
assertApproxEqAbs(claimer.computeFeePerClaim(MAXIMUM_FEE, 1), MAXIMUM_FEE, 4);
assertApproxEqAbs(claimer.computeFeePerClaim(0, 1), MAXIMUM_FEE, 4);
}

function mockPrizePool(uint256 drawId, int256 drawEndedRelativeToNow, uint256 claimCount) public {
Expand Down Expand Up @@ -413,11 +434,19 @@ contract ClaimerTest is Test {
);
}

function mockIsCanaryTier(uint8 _tier, bool isCanary) internal {
vm.mockCall(
address(prizePool),
abi.encodeWithSelector(prizePool.isCanaryTier.selector, _tier),
abi.encode(isCanary)
);
}

function mockGetTierPrizeSize(uint8 _tier, uint256 prizeSize) internal {
vm.mockCall(
address(prizePool),
abi.encodeWithSelector(prizePool.getTierPrizeSize.selector, _tier),
abi.encodePacked(prizeSize)
abi.encode(prizeSize)
);
}
}

0 comments on commit 9719308

Please sign in to comment.