Skip to content

Commit

Permalink
Merge pull request #34 from pooltogether/pool-2870-update-prizepool-v…
Browse files Browse the repository at this point in the history
…ault-to-be-able-to-redistribute-prizes

Updated claim prize to decouple eligiblity from recipient
  • Loading branch information
dylandesrosier authored Jun 29, 2023
2 parents e4a5d1b + e040da0 commit 978ba4c
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 106 deletions.
116 changes: 41 additions & 75 deletions src/PrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ error DrawManagerAlreadySet();
/// @notice Emitted when someone tries to claim a prize that was already claimed
/// @param winner The winner of the prize
/// @param tier The prize tier
error AlreadyClaimedPrize(address winner, uint8 tier);
error AlreadyClaimedPrize(address vault, address winner, uint8 tier, uint32 prizeIndex, address recipient);

/// @notice Emitted when someone tries to withdraw too many rewards
/// @param requested The requested reward amount to withdraw
/// @param available The total reward amount available for the caller to withdraw
error InsufficientRewardsError(uint256 requested, uint256 available);

/// @notice Emitted when an address did not win the specified prize on a vault
/// @param _address The address checked for the prize
/// @param winner The address checked for the prize
/// @param vault The vault address
/// @param tier The prize tier
/// @param prizeIndex The prize index
error DidNotWin(address _address, address vault, uint8 tier, uint32 prizeIndex);
error DidNotWin(address vault, address winner, uint8 tier, uint32 prizeIndex);

/// @notice Emitted when the fee being claimed is larger than the max allowed fee
/// @param fee The fee being claimed
Expand Down Expand Up @@ -61,11 +61,6 @@ error RandomNumberIsZero();
/// @param drawEndsAt The timestamp in seconds at which the draw ends
error DrawNotFinished(uint64 drawEndsAt);

/// @notice Emitted when the number of winners and number of prize lists do not match while claiming prizes
/// @param numWinners The number of winner addresses provided
/// @param numPrizeLists The number of prize lists provided
error WinnerPrizeMismatch(uint128 numWinners, uint128 numPrizeLists);

/// @notice Emitted when prize index is greater or equal to the max prize count for the tier
/// @param invalidPrizeIndex The invalid prize index
/// @param prizeCount The prize count for the tier
Expand Down Expand Up @@ -124,18 +119,21 @@ contract PrizePool is TieredLiquidityDistributor {
using SafeERC20 for IERC20;

/// @notice Emitted when a prize is claimed.
/// @param drawId The draw ID of the draw that was claimed.
/// @param vault The address of the vault that claimed the prize.
/// @param winner The address of the winner
/// @param recipient The address of the prize recipient
/// @param drawId The draw ID of the draw that was claimed.
/// @param tier The prize tier that was claimed.
/// @param payout The amount of prize tokens that were paid out to the winner
/// @param fee The amount of prize tokens that were paid to the claimer
/// @param feeRecipient The address that the claim fee was sent to
event ClaimedPrize(
uint16 indexed drawId,
address indexed vault,
address indexed winner,
address indexed recipient,
uint16 drawId,
uint8 tier,
uint32 prizeIndex,
uint152 payout,
uint96 fee,
address feeRecipient
Expand Down Expand Up @@ -510,96 +508,64 @@ contract PrizePool is TieredLiquidityDistributor {
tier claimed and the claim count, and transferring prize tokens. The function is marked as external which
means that it can be called from outside the contract.
@param _tier The tier of the prize to be claimed.
@param _winners The address of the winners to claim the prize for.
@param _prizeIndices The array of prizes to claim for each winner.
@param _feePerPrizeClaim The fee associated with claiming the prize.
@param _winner The address of the eligible winner
@param _prizeIndex The prize to claim for the winner. Must be less than the prize count for the tier.
@param _prizeRecipient The recipient of the prize
@param _fee The fee associated with claiming the prize.
@param _feeRecipient The address to receive the fee.
@return Total prize amount claimed (payout and fees combined).
*/
function claimPrizes(
function claimPrize(
address _winner,
uint8 _tier,
address[] calldata _winners,
uint32[][] calldata _prizeIndices,
uint96 _feePerPrizeClaim,
uint32 _prizeIndex,
address _prizeRecipient,
uint96 _fee,
address _feeRecipient
) external returns (uint256) {
if (_winners.length != _prizeIndices.length) {
revert WinnerPrizeMismatch(uint128(_winners.length), uint128(_prizeIndices.length));
Tier memory tierLiquidity = _getTier(_tier, numberOfTiers);

if (_fee > tierLiquidity.prizeSize) {
revert FeeTooLarge(_fee, tierLiquidity.prizeSize);
}

Tier memory tierLiquidity = _getTier(_tier, numberOfTiers);
if (_feePerPrizeClaim > tierLiquidity.prizeSize) {
revert FeeTooLarge(_feePerPrizeClaim, tierLiquidity.prizeSize);
(SD59x18 _vaultPortion, SD59x18 _tierOdds, uint16 _drawDuration) = _computeVaultTierDetails(msg.sender, _tier, numberOfTiers, lastCompletedDrawId);


if (!_isWinner(msg.sender, _winner, _tier, _prizeIndex, _vaultPortion, _tierOdds, _drawDuration)) {
revert DidNotWin(msg.sender, _winner, _tier, _prizeIndex);
}

uint32 prizeClaimCount = _claimPrizes(msg.sender, _tier, _winners, _prizeIndices, tierLiquidity.prizeSize - _feePerPrizeClaim, _feePerPrizeClaim, _feeRecipient);

if (claimedPrizes[_winner][lastCompletedDrawId][_tier][_prizeIndex]) {
revert AlreadyClaimedPrize(msg.sender, _winner, _tier, _prizeIndex, _prizeRecipient);
}

claimedPrizes[_winner][lastCompletedDrawId][_tier][_prizeIndex] = true;

if (_isCanaryTier(_tier, numberOfTiers)) {
canaryClaimCount += prizeClaimCount;
canaryClaimCount++;
} else {
claimCount += prizeClaimCount;
claimCount++;
}

if (largestTierClaimed < _tier) {
largestTierClaimed = _tier;
}

_consumeLiquidity(tierLiquidity, _tier, tierLiquidity.prizeSize * prizeClaimCount);
_consumeLiquidity(tierLiquidity, _tier, tierLiquidity.prizeSize);

if (_feePerPrizeClaim != 0 && prizeClaimCount != 0) {
claimerRewards[_feeRecipient] += _feePerPrizeClaim * prizeClaimCount;
if (_fee != 0) {
claimerRewards[_feeRecipient] += _fee;
}

return tierLiquidity.prizeSize * prizeClaimCount;
}
uint256 payout = tierLiquidity.prizeSize - _fee;

function _claimPrizes(
address _vault,
uint8 _tier,
address[] calldata _winners,
uint32[][] calldata _prizeIndices,
uint96 _payout,
uint96 _feePerPrizeClaim,
address _feeRecipient
) internal returns (uint32) {
uint32 _prizeClaimCount = 0;
(SD59x18 _vaultPortion, SD59x18 _tierOdds, uint16 _drawDuration) = _computeVaultTierDetails(_vault, _tier, numberOfTiers, lastCompletedDrawId);
emit ClaimedPrize(msg.sender, _winner, _prizeRecipient, lastCompletedDrawId, _tier, _prizeIndex, uint152(payout), _fee, _feeRecipient);

for (uint winnerIndex = 0; winnerIndex < _winners.length; winnerIndex++) {
_prizeClaimCount += _claimWinnerPrizes(_vault, _tier, _winners[winnerIndex], _prizeIndices[winnerIndex], _payout, _feePerPrizeClaim, _feeRecipient, _vaultPortion, _tierOdds, _drawDuration);
}

return _prizeClaimCount;
}

function _claimWinnerPrizes(
address _vault,
uint8 _tier,
address _winner,
uint32[] calldata _prizeIndices,
uint96 _payout,
uint96 _feePerPrizeClaim,
address _feeRecipient,
SD59x18 _vaultPortion,
SD59x18 _tierOdds,
uint16 _drawDuration
) internal returns (uint32) {
for (uint256 _prizeArrayIndex = 0; _prizeArrayIndex < _prizeIndices.length; _prizeArrayIndex++) {
if (!_isWinner(_vault, _winner, _tier, _prizeIndices[_prizeArrayIndex], _vaultPortion, _tierOdds, _drawDuration)) {
revert DidNotWin(_winner, _vault, _tier, _prizeIndices[_prizeArrayIndex]);
}

if (claimedPrizes[_winner][lastCompletedDrawId][_tier][_prizeIndices[_prizeArrayIndex]]) {
revert AlreadyClaimedPrize(_winner, _tier);
}

claimedPrizes[_winner][lastCompletedDrawId][_tier][_prizeIndices[_prizeArrayIndex]] = true;
_transfer(_winner, _payout);

emit ClaimedPrize(lastCompletedDrawId, _vault, _winner, _tier, uint152(_payout), _feePerPrizeClaim, _feeRecipient);
}
_transfer(_prizeRecipient, payout);

return uint32(_prizeIndices.length);
return tierLiquidity.prizeSize;
}

function _transfer(address _to, uint256 _amount) internal {
Expand Down
62 changes: 37 additions & 25 deletions test/PrizePool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
InsufficientReserve,
RandomNumberIsZero,
DrawNotFinished,
WinnerPrizeMismatch,
InvalidPrizeIndex,
NoCompletedDraw,
InvalidTier,
Expand Down Expand Up @@ -104,6 +103,18 @@ contract PrizePoolTest is Test {
address indexed drawManager
);

event ClaimedPrize(
address indexed vault,
address indexed winner,
address indexed recipient,
uint16 drawId,
uint8 tier,
uint32 prizeIndex,
uint152 payout,
uint96 fee,
address feeRecipient
);

/**********************************************************************************/

ConstructorParams params;
Expand Down Expand Up @@ -622,10 +633,28 @@ contract PrizePoolTest is Test {
function testClaimPrize_single() public {
contribute(100e18);
completeAndStartNextDraw(winningRandomNumber);
mockTwab(msg.sender, 0);
claimPrize(msg.sender, 0, 0);
address winner = makeAddr("winner");
address recipient = makeAddr("recipient");
mockTwab(winner, 1);

console2.log("winner", winner);
console2.log("recipient", recipient);

vm.expectEmit();
emit ClaimedPrize(
address(this),
winner,
recipient,
1,
1,
0,
1.13636363636363635e18,
0,
address(0)
);
prizePool.claimPrize(winner, 1, 0, recipient, 0, address(0));
// grand prize is (100/220) * 0.1 * 100e18 = 4.5454...e18
assertEq(prizeToken.balanceOf(msg.sender), 4.5454545454545454e18);
assertEq(prizeToken.balanceOf(recipient), 1.13636363636363635e18, "recipient balance is good");
assertEq(prizePool.claimCount(), 1);
}

Expand All @@ -644,7 +673,7 @@ contract PrizePoolTest is Test {
function testClaimPrize_notWinner() public {
contribute(100e18);
completeAndStartNextDraw(winningRandomNumber);
vm.expectRevert(abi.encodeWithSelector(DidNotWin.selector, msg.sender, address(this), 0, 0));
vm.expectRevert(abi.encodeWithSelector(DidNotWin.selector, address(this), msg.sender, 0, 0));
claimPrize(msg.sender, 0, 0);
}

Expand All @@ -662,7 +691,7 @@ contract PrizePoolTest is Test {
mockTwab(msg.sender, 0);
assertEq(claimPrize(msg.sender, 0, 0), 4.5454545454545454e18, "prize size");
// second claim is zero
vm.expectRevert(abi.encodeWithSelector(AlreadyClaimedPrize.selector, msg.sender, 0));
vm.expectRevert(abi.encodeWithSelector(AlreadyClaimedPrize.selector, address(this), msg.sender, 0, 0, msg.sender));
claimPrize(msg.sender, 0, 0);
}

Expand Down Expand Up @@ -762,25 +791,13 @@ contract PrizePoolTest is Test {
completeAndStartNextDraw(winningRandomNumber);
mockTwab(msg.sender, 0);

address[] memory winners = new address[](1);
winners[0] = msg.sender;
uint32[][] memory prizeIndices = new uint32[][](1);
prizeIndices[0] = new uint32[](1);
prizeIndices[0][0] = 0;
prizePool.claimPrizes(0, winners, prizeIndices, 1e18, address(this));
prizePool.claimPrize(msg.sender, 0, 0, msg.sender, 1e18, address(this));

vm.expectEmit();
emit WithdrawClaimRewards(address(this), 5e17, 1e18);
prizePool.withdrawClaimRewards(address(this), 5e17);
}

function testClaimPrizes_winnerPrizeMismatch() public {
vm.expectRevert(abi.encodeWithSelector(WinnerPrizeMismatch.selector, 2, 3));
address[] memory winners = new address[](2);
uint32[][] memory prizeIndices = new uint32[][](3);
prizePool.claimPrizes(0, winners, prizeIndices, 0, address(this));
}

function testNextDrawStartsAt_zeroDraw() public {
// current time *is* lastCompletedDrawStartedAt
assertEq(prizePool.nextDrawStartsAt(), lastCompletedDrawStartedAt);
Expand Down Expand Up @@ -956,12 +973,7 @@ contract PrizePoolTest is Test {
}

function claimPrize(address sender, uint8 tier, uint32 prizeIndex, uint96 fee, address feeRecipient) public returns (uint256) {
address[] memory winners = new address[](1);
winners[0] = sender;
uint32[][] memory prizeIndices = new uint32[][](1);
prizeIndices[0] = new uint32[](1);
prizeIndices[0][0] = prizeIndex;
return prizePool.claimPrizes(tier, winners, prizeIndices, fee, feeRecipient);
return prizePool.claimPrize(sender, tier, prizeIndex, sender, fee, feeRecipient);
}

function mockTwab(address _account, uint256 startTime, uint256 endTime) public {
Expand Down
7 changes: 1 addition & 6 deletions test/invariants/helpers/PrizePoolFuzzHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,7 @@ contract PrizePoolFuzzHarness is CommonBase {
for (uint8 i = 0; i < prizePool.numberOfTiers(); i++) {
for (uint32 p = 0; p < prizePool.getTierPrizeCount(i); i++) {
if (prizePool.isWinner(address(this), address(this), i, p) && !prizePool.wasClaimed(address(this), i, p)) {
address[] memory winners = new address[](1);
winners[0] = address(this);
uint32[][] memory prizeIndices = new uint32[][](1);
prizeIndices[0] = new uint32[](1);
prizeIndices[0][0] = p;
claimed += prizePool.claimPrizes(i, winners, prizeIndices, 0, address(0));
claimed += prizePool.claimPrize(address(this), i, p, address(this), 0, address(0));
}
}
}
Expand Down

0 comments on commit 978ba4c

Please sign in to comment.