From 8a7552a5bb6558ee6053d46f05c394c5938dee1f Mon Sep 17 00:00:00 2001 From: Brendan Asselstine Date: Wed, 14 Feb 2024 12:45:20 -0800 Subject: [PATCH 1/2] Added ability to donate to the prizes --- src/PrizePool.sol | 31 ++++++++++++++-- test/PrizePool.t.sol | 37 +++++++++++++++++++ .../helpers/PrizePoolFuzzHarness.sol | 11 ++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/PrizePool.sol b/src/PrizePool.sol index 408283e..4880d22 100644 --- a/src/PrizePool.sol +++ b/src/PrizePool.sol @@ -230,6 +230,8 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { /// @notice Records the last shutdown withdrawal for an account mapping(address vault => mapping(address user => uint24 drawId)) internal _lastShutdownWithdrawal; + address public constant DONATOR = 0x000000000000000000000000000000000000F2EE; + /// @notice The token that is being contributed and awarded as prizes. IERC20 public immutable prizeToken; @@ -343,7 +345,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { /// @param _prizeVault The address of the vault to contribute to /// @param _amount The amount of prize tokens to contribute /// @return The amount of available prize tokens prior to the contribution. - function contributePrizeTokens(address _prizeVault, uint256 _amount) external returns (uint256) { + function contributePrizeTokens(address _prizeVault, uint256 _amount) public returns (uint256) { uint256 _deltaBalance = prizeToken.balanceOf(address(this)) - accountedBalance(); if (_deltaBalance < _amount) { revert ContributionGTDeltaBalance(_amount, _deltaBalance); @@ -355,6 +357,11 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { return _deltaBalance; } + function donatePrizeTokens(uint256 _amount) external { + prizeToken.transferFrom(msg.sender, address(this), _amount); + contributePrizeTokens(DONATOR, _amount); + } + /// @notice Allows the Manager to allocate a reward from the reserve to a recipient. /// @param _to The address to allocate the rewards to /// @param _amount The amount of tokens for the reward @@ -579,7 +586,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { address _vault, uint24 _startDrawIdInclusive, uint24 _endDrawIdInclusive - ) external view returns (uint256) { + ) public view returns (uint256) { return DrawAccumulatorLib.getDisbursedBetween( _vaultAccumulator[_vault], @@ -588,6 +595,22 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { ); } + /// @notice Returns the total prize tokens donated to the prize pool + /// @param _startDrawIdInclusive Start draw id inclusive + /// @param _endDrawIdInclusive End draw id inclusive + /// @return The total prize tokens donated to the prize pool + function getDonatedBetween( + uint24 _startDrawIdInclusive, + uint24 _endDrawIdInclusive + ) public view returns (uint256) { + return + DrawAccumulatorLib.getDisbursedBetween( + _vaultAccumulator[DONATOR], + _startDrawIdInclusive, + _endDrawIdInclusive + ); + } + /// @notice Computes the expected duration prize accrual for a tier. /// @param _tier The tier to check /// @return The number of draws @@ -960,6 +983,8 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { _endDrawIdInclusive ); + uint256 totalDonated = DrawAccumulatorLib.getDisbursedBetween(_vaultAccumulator[DONATOR], _startDrawIdInclusive, _endDrawIdInclusive); + // vaultContributed / totalContributed return totalContributed != 0 @@ -971,7 +996,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { _endDrawIdInclusive ) ) - ).div(sd(SafeCast.toInt256(totalContributed))) + ).div(sd(SafeCast.toInt256(totalContributed - totalDonated))) : sd(0); } diff --git a/test/PrizePool.t.sol b/test/PrizePool.t.sol index 96efc7a..e5fb53e 100644 --- a/test/PrizePool.t.sol +++ b/test/PrizePool.t.sol @@ -377,6 +377,26 @@ contract PrizePoolTest is Test { prizePool.contributePrizeTokens(address(this), 100); } + function testDonatePrizeTokens() public { + prizeToken.mint(address(this), 100); + prizeToken.approve(address(prizePool), 100); + prizePool.donatePrizeTokens(100); + assertEq(prizeToken.balanceOf(address(prizePool)), 100); + assertEq(prizePool.getTotalContributedBetween(1, 1), 100); + assertEq(prizePool.getDonatedBetween(1,1), 100); + } + + function testDonatePrizeTokens_twice() public { + prizeToken.mint(address(this), 100); + prizeToken.approve(address(prizePool), 100); + prizePool.donatePrizeTokens(50); + awardDraw(winningRandomNumber); + prizePool.donatePrizeTokens(50); + assertEq(prizeToken.balanceOf(address(prizePool)), 100); + assertEq(prizePool.getTotalContributedBetween(1, 2), 100); + assertEq(prizePool.getDonatedBetween(1, 2), 100); + } + function testAccountedBalance_withdrawnReserve() public { contribute(100e18); awardDraw(1); @@ -477,6 +497,23 @@ contract PrizePoolTest is Test { assertEq(SD59x18.unwrap(prizePool.getVaultPortion(address(this), 0, 2)), 1e18); } + function testGetVaultPortion_ignoresDonations() public { + // contribute to vault1 + prizeToken.mint(address(prizePool), 100); + prizePool.contributePrizeTokens(address(vault), 100); + + // contribute to vault2 + prizeToken.mint(address(prizePool), 100); + prizePool.contributePrizeTokens(address(vault2), 100); + + prizeToken.mint(address(this), 100); + prizeToken.approve(address(prizePool), 100); + prizePool.donatePrizeTokens(100); + + assertEq(prizePool.getVaultPortion(address(vault), 1, 1).unwrap(), 0.5e18); + assertEq(prizePool.getVaultPortion(address(vault2), 1, 1).unwrap(), 0.5e18); + } + function testGetOpenDrawId() public { uint256 openDrawId = prizePool.getOpenDrawId(); assertEq(openDrawId, 1); diff --git a/test/invariants/helpers/PrizePoolFuzzHarness.sol b/test/invariants/helpers/PrizePoolFuzzHarness.sol index 6862bba..e55ade2 100644 --- a/test/invariants/helpers/PrizePoolFuzzHarness.sol +++ b/test/invariants/helpers/PrizePoolFuzzHarness.sol @@ -96,6 +96,17 @@ contract PrizePoolFuzzHarness is CommonBase, StdCheats, StdUtils, CurrentTimeCon prizePool.contributePrizeTokens(_actor(actorSeed), _amount); } + function donatePrizeTokens(uint88 _amount, uint256 actorSeed) public increaseCurrentTime(_timeIncrease()) prankActor(actorSeed) { + // console2.log("contributePrizeTokens"); + address actor = _actor(actorSeed); + token.mint(address(actor), _amount); + vm.startPrank(actor); + token.approve(address(prizePool), _amount); + contributed += _amount; + prizePool.donatePrizeTokens(_amount); + vm.stopPrank(); + } + function contributeReserve(uint88 _amount, uint256 actorSeed) public increaseCurrentTime(_timeIncrease()) prankActor(actorSeed) { if (prizePool.isShutdown()) { return; From 53efe9500ad56fb4c830dc1adf77ba4ed2afc110 Mon Sep 17 00:00:00 2001 From: Brendan Asselstine Date: Mon, 19 Feb 2024 13:55:01 -0800 Subject: [PATCH 2/2] PR feedback --- src/PrizePool.sol | 11 +++++++++-- test/PrizePool.t.sol | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/PrizePool.sol b/src/PrizePool.sol index 4880d22..3df1479 100644 --- a/src/PrizePool.sol +++ b/src/PrizePool.sol @@ -230,6 +230,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { /// @notice Records the last shutdown withdrawal for an account mapping(address vault => mapping(address user => uint24 drawId)) internal _lastShutdownWithdrawal; + /// @notice The special value for the donator address. Contributions from this address are excluded from the total odds. F2EE because it's free money! address public constant DONATOR = 0x000000000000000000000000000000000000F2EE; /// @notice The token that is being contributed and awarded as prizes. @@ -357,6 +358,8 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { return _deltaBalance; } + /// @notice Allows a user to donate prize tokens to the prize pool. + /// @param _amount The amount of tokens to donate. The amount should already be approved for transfer. function donatePrizeTokens(uint256 _amount) external { prizeToken.transferFrom(msg.sender, address(this), _amount); contributePrizeTokens(DONATOR, _amount); @@ -586,7 +589,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { address _vault, uint24 _startDrawIdInclusive, uint24 _endDrawIdInclusive - ) public view returns (uint256) { + ) external view returns (uint256) { return DrawAccumulatorLib.getDisbursedBetween( _vaultAccumulator[_vault], @@ -602,7 +605,7 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { function getDonatedBetween( uint24 _startDrawIdInclusive, uint24 _endDrawIdInclusive - ) public view returns (uint256) { + ) external view returns (uint256) { return DrawAccumulatorLib.getDisbursedBetween( _vaultAccumulator[DONATOR], @@ -977,6 +980,10 @@ contract PrizePool is TieredLiquidityDistributor, Ownable { uint24 _startDrawIdInclusive, uint24 _endDrawIdInclusive ) public view returns (SD59x18) { + if (_vault == DONATOR) { + return sd(0); + } + uint256 totalContributed = DrawAccumulatorLib.getDisbursedBetween( _totalAccumulator, _startDrawIdInclusive, diff --git a/test/PrizePool.t.sol b/test/PrizePool.t.sol index e5fb53e..f072e5d 100644 --- a/test/PrizePool.t.sol +++ b/test/PrizePool.t.sol @@ -446,6 +446,11 @@ contract PrizePoolTest is Test { assertEq(prizePool.accountedBalance(), 100e18 - prize - prize2, "accounted balance"); } + function testGetVaultPortion_fromDonator() public { + contribute(100e18, prizePool.DONATOR()); // available draw 1 + assertEq(SD59x18.unwrap(prizePool.getVaultPortion(prizePool.DONATOR(), 1, 1)), 0); + } + function testGetVaultPortion_WhenEmpty() public { assertEq(SD59x18.unwrap(prizePool.getVaultPortion(address(this), 0, 0)), 0); }