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;