-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from GenerationSoftware/gen-1772-114-weth-has…
…-automatic-yield-accrual-on-blast-that-is-not Add blast-compatible prize pool extension
- Loading branch information
Showing
5 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import { PrizePool, ConstructorParams } from "../PrizePool.sol"; | ||
|
||
// The rebasing WETH token on Blast | ||
IERC20Rebasing constant WETH = IERC20Rebasing(0x4300000000000000000000000000000000000004); | ||
|
||
/// @notice The Blast yield modes for WETH | ||
enum YieldMode { | ||
AUTOMATIC, | ||
VOID, | ||
CLAIMABLE | ||
} | ||
|
||
/// @notice The relevant interface for rebasing WETH on Blast | ||
interface IERC20Rebasing { | ||
function configure(YieldMode) external returns (uint256); | ||
function claim(address recipient, uint256 amount) external returns (uint256); | ||
function getClaimableAmount(address account) external view returns (uint256); | ||
} | ||
|
||
/// @notice Thrown if the prize token is not the expected token on Blast. | ||
/// @param prizeToken The prize token address | ||
/// @param expectedToken The expected token address | ||
error PrizeTokenNotExpectedToken(address prizeToken, address expectedToken); | ||
|
||
/// @notice Thrown if a yield donation is triggered when there is no claimable balance. | ||
error NoClaimableBalance(); | ||
|
||
/// @title PoolTogether V5 Blast Prize Pool | ||
/// @author G9 Software Inc. | ||
/// @notice A modified prize pool that opts in to claimable WETH yield on Blast and allows anyone to trigger | ||
/// a donation of the accrued yield to the prize pool. | ||
contract BlastPrizePool is PrizePool { | ||
|
||
/* ============ Constructor ============ */ | ||
|
||
/// @notice Constructs a new Blast Prize Pool. | ||
/// @dev Reverts if the prize token is not the expected WETH token on Blast. | ||
/// @param params A struct of constructor parameters | ||
constructor(ConstructorParams memory params) PrizePool(params) { | ||
if (address(params.prizeToken) != address(WETH)) { | ||
revert PrizeTokenNotExpectedToken(address(params.prizeToken), address(WETH)); | ||
} | ||
|
||
// Opt-in to claimable yield | ||
WETH.configure(YieldMode.CLAIMABLE); | ||
} | ||
|
||
/* ============ External Functions ============ */ | ||
|
||
/// @notice Returns the claimable WETH yield balance for this contract | ||
function claimableYieldBalance() external view returns (uint256) { | ||
return WETH.getClaimableAmount(address(this)); | ||
} | ||
|
||
/// @notice Claims the available WETH yield balance and donates it to the prize pool. | ||
/// @return The amount claimed and donated. | ||
function donateClaimableYield() external returns (uint256) { | ||
uint256 _claimableYieldBalance = WETH.getClaimableAmount(address(this)); | ||
if (_claimableYieldBalance == 0) { | ||
revert NoClaimableBalance(); | ||
} | ||
WETH.claim(address(this), _claimableYieldBalance); | ||
contributePrizeTokens(DONATOR, _claimableYieldBalance); | ||
return _claimableYieldBalance; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "forge-std/Test.sol"; | ||
|
||
import { TwabController } from "pt-v5-twab-controller/TwabController.sol"; | ||
import { BlastPrizePool, ConstructorParams, WETH, PrizeTokenNotExpectedToken, NoClaimableBalance } from "../../src/extensions/BlastPrizePool.sol"; | ||
import { IERC20 } from "../../src/PrizePool.sol"; | ||
|
||
contract BlastPrizePoolTest is Test { | ||
BlastPrizePool prizePool; | ||
|
||
address bob = makeAddr("bob"); | ||
address alice = makeAddr("alice"); | ||
|
||
address wethWhale = address(0x66714DB8F3397c767d0A602458B5b4E3C0FE7dd1); | ||
|
||
TwabController twabController; | ||
IERC20 prizeToken; | ||
address drawManager; | ||
|
||
uint256 TIER_SHARES = 100; | ||
uint256 CANARY_SHARES = 5; | ||
uint256 RESERVE_SHARES = 10; | ||
|
||
uint24 grandPrizePeriodDraws = 365; | ||
uint48 drawPeriodSeconds = 1 days; | ||
uint24 drawTimeout; | ||
uint48 firstDrawOpensAt; | ||
uint8 initialNumberOfTiers = 4; | ||
uint256 winningRandomNumber = 123456; | ||
uint256 tierLiquidityUtilizationRate = 1e18; | ||
|
||
uint256 blockNumber = 5213491; | ||
uint256 blockTimestamp = 1719236797; | ||
|
||
ConstructorParams params; | ||
|
||
function setUp() public { | ||
drawTimeout = 30; | ||
|
||
vm.createSelectFork("blast", blockNumber); | ||
vm.warp(blockTimestamp); | ||
|
||
prizeToken = IERC20(address(WETH)); | ||
twabController = new TwabController(uint32(drawPeriodSeconds), uint32(blockTimestamp - 1 days)); | ||
|
||
firstDrawOpensAt = uint48(blockTimestamp + 1 days); // set draw start 1 day into future | ||
|
||
drawManager = address(this); | ||
|
||
params = ConstructorParams( | ||
prizeToken, | ||
twabController, | ||
drawManager, | ||
tierLiquidityUtilizationRate, | ||
drawPeriodSeconds, | ||
firstDrawOpensAt, | ||
grandPrizePeriodDraws, | ||
initialNumberOfTiers, | ||
uint8(TIER_SHARES), | ||
uint8(CANARY_SHARES), | ||
uint8(RESERVE_SHARES), | ||
drawTimeout | ||
); | ||
|
||
prizePool = new BlastPrizePool(params); | ||
prizePool.setDrawManager(address(this)); | ||
} | ||
|
||
function testWrongPrizeToken() public { | ||
params.prizeToken = IERC20(address(1)); | ||
vm.expectRevert(abi.encodeWithSelector(PrizeTokenNotExpectedToken.selector, address(1), address(WETH))); | ||
prizePool = new BlastPrizePool(params); | ||
} | ||
|
||
function testClaimableYield() public { | ||
assertEq(IERC20(address(WETH)).balanceOf(address(prizePool)), 0); | ||
|
||
// check balance | ||
assertEq(prizePool.claimableYieldBalance(), 0); | ||
|
||
// donate some tokens to the prize pool | ||
vm.startPrank(wethWhale); | ||
IERC20(address(WETH)).approve(address(prizePool), 1e18); | ||
prizePool.donatePrizeTokens(1e18); | ||
vm.stopPrank(); | ||
assertEq(prizePool.getDonatedBetween(1, 1), 1e18); | ||
|
||
// deal some ETH to the WETH contract and call addValue | ||
deal(address(WETH), 1e18 + address(WETH).balance); | ||
vm.startPrank(address(0x4300000000000000000000000000000000000000)); // REPORTER | ||
(bool success,) = address(WETH).call(abi.encodeWithSignature("addValue(uint256)", 0)); | ||
vm.stopPrank(); | ||
require(success, "addValue failed"); | ||
|
||
// check balance non-zero | ||
uint256 claimable = prizePool.claimableYieldBalance(); | ||
assertGt(claimable, 0); | ||
|
||
// trigger donation | ||
vm.startPrank(alice); | ||
uint256 donated = prizePool.donateClaimableYield(); | ||
vm.stopPrank(); | ||
|
||
assertEq(donated, claimable); | ||
assertEq(prizePool.getDonatedBetween(1, 1), 1e18 + donated); | ||
assertEq(prizePool.claimableYieldBalance(), 0); | ||
|
||
// reverts on donation of zero balance | ||
vm.expectRevert(abi.encodeWithSelector(NoClaimableBalance.selector)); | ||
prizePool.donateClaimableYield(); | ||
} | ||
|
||
} |