@@ -3,7 +3,7 @@ pragma solidity ^0.8.24;
3
3
4
4
import { IPrizeHooks } from "pt-v5-vault/interfaces/IPrizeHooks.sol " ;
5
5
import { IERC721 } from "openzeppelin-v5/token/ERC721/IERC721.sol " ;
6
- import { PrizePool, TwabController } from "pt-v5-prize-pool/PrizePool.sol " ;
6
+ import { PrizePool, TwabController, SafeERC20, IERC20 } from "pt-v5-prize-pool/PrizePool.sol " ;
7
7
import { UniformRandomNumber } from "uniform-random-number/UniformRandomNumber.sol " ;
8
8
9
9
uint256 constant PICK_GAS_ESTIMATE = 60_000 ; // probably lower, but we set it higher to avoid a reversion
@@ -30,12 +30,23 @@ error InvalidTokenIdBounds(uint256 tokenIdLowerBound, uint256 tokenIdUpperBound)
30
30
/// @dev This contract works best with NFTs that have iterating IDs ex. IDs: (1,2,3,4,5,...)
31
31
/// @author G9 Software Inc.
32
32
contract NftChanceBoosterHook is IPrizeHooks {
33
+ using SafeERC20 for IERC20 ;
33
34
34
35
/// @notice Emitted when a vault is boosted with a prize re-contribution.
35
36
/// @param prizePool The prize pool the vault was boosted on
36
37
/// @param boostedVault The boosted vault
37
38
/// @param prizeAmount The amount of prize tokens contributed
38
- event BoostedVaultWithPrize (address indexed prizePool , address indexed boostedVault , uint256 prizeAmount );
39
+ /// @param pickAttempts The number of times the hook tried to pick a winner
40
+ event BoostedVaultWithPrize (address indexed prizePool , address indexed boostedVault , uint256 prizeAmount , uint256 pickAttempts );
41
+
42
+ /// @notice Emitted when an NFT holder wins a prize.
43
+ /// @param nftWinner The NFT holder that won the prize
44
+ /// @param vault The vault that the prize was won through
45
+ /// @param donor The original winner of the prize before it was redirected
46
+ /// @param tier The prize tier
47
+ /// @param prizeIndex The prize index
48
+ /// @param prizeAmount The amount of prize tokens won
49
+ event PrizeWonByNftHolder (address indexed nftWinner , address indexed vault , address indexed donor , uint8 tier , uint32 prizeIndex , uint256 prizeAmount );
39
50
40
51
/// @notice The ERC721 token whose holders will have a chance to win prizes
41
52
IERC721 public immutable nftCollection;
@@ -63,7 +74,7 @@ contract NftChanceBoosterHook is IPrizeHooks {
63
74
/// @param prizePool_ The prize pool that is awarding prizes
64
75
/// @param boostedVault_ The The vault that is being boosted
65
76
/// @param minTwabOverPrizePeriod_ The minimum TWAB that the selected winner must have over the prize
66
- /// period to win the prize; if set to zero, no balance is needed.
77
+ /// period to win the prize; if set to zero, no balance is needed.
67
78
/// @param tokenIdLowerBound_ The lower bound of eligible NFT IDs (inclusive)
68
79
/// @param tokenIdUpperBound_ The upper bound of eligible NFT IDs (inclusive)
69
80
constructor (
@@ -90,14 +101,15 @@ contract NftChanceBoosterHook is IPrizeHooks {
90
101
91
102
/// @inheritdoc IPrizeHooks
92
103
/// @dev This prize hook uses the random number from the last awarded prize pool draw to randomly select
93
- /// the receiver of the prize from a list of current NFT holders. The prize tier and prize index are also
94
- /// used to provide variance in the entropy for each prize so there can be multiple winners per draw.
104
+ /// the receiver of the prize from a list of current NFT holders. The prize data is also used to provide
105
+ /// variance in the entropy for each prize so there can be multiple winners per draw.
95
106
/// @dev Tries to select a winner until the call runs out of gas before reverting to the backup action of
96
107
/// contributing the prize on behalf of the boosted vault.
108
+ /// @dev Returns the winning token holder in data if successful or the number of pick attempts if not successful.
97
109
function beforeClaimPrize (address _winner , uint8 _tier , uint32 _prizeIndex , uint96 , address ) external view returns (address , bytes memory ) {
98
110
uint256 _tierStartTime;
99
111
uint256 _tierEndTime;
100
- uint256 _winningRandomNumber = prizePool.getWinningRandomNumber ();
112
+ bytes32 _entropy = keccak256 ( abi.encode ( prizePool.getWinningRandomNumber (), msg . sender , _winner, _tier, _prizeIndex) );
101
113
{
102
114
uint24 _tierEndDrawId = prizePool.getLastAwardedDrawId ();
103
115
uint24 _tierStartDrawId = prizePool.computeRangeStartDrawIdInclusive (
@@ -117,7 +129,7 @@ contract NftChanceBoosterHook is IPrizeHooks {
117
129
_randomTokenId = tokenIdLowerBound;
118
130
} else {
119
131
_randomTokenId = tokenIdLowerBound + UniformRandomNumber.uniform (
120
- uint256 (keccak256 (abi.encode (_winningRandomNumber, _winner, _tier, _prizeIndex , _pickAttempt))),
132
+ uint256 (keccak256 (abi.encode (_entropy , _pickAttempt))),
121
133
_numTokens
122
134
);
123
135
}
@@ -131,8 +143,8 @@ contract NftChanceBoosterHook is IPrizeHooks {
131
143
_recipientTwab = twabController.getTwabBetween (boostedVault, _ownerOfToken, _tierStartTime, _tierEndTime);
132
144
}
133
145
if (_recipientTwab >= minTwabOverPrizePeriod) {
134
- // The owner of the selected NFT wins the prize!
135
- return (_ownerOfToken , abi.encode (_pickAttempt ));
146
+ // The owner of the selected NFT will be awarded the prize in the `afterClaimPrize` callback.
147
+ return (address ( this ) , abi.encode (_ownerOfToken ));
136
148
}
137
149
}
138
150
}
@@ -143,12 +155,19 @@ contract NftChanceBoosterHook is IPrizeHooks {
143
155
144
156
/// @inheritdoc IPrizeHooks
145
157
/// @dev If the recipient is set to the prize pool, the prize will be contributed on behalf of the vault
146
- /// that is being boosted. Otherwise, it will do nothing (the prize will have already been sent to the
147
- /// randomly selected NFT winner).
148
- function afterClaimPrize (address , uint8 , uint32 , uint256 _prizeAmount , address _prizeRecipient , bytes memory ) external {
149
- if (_prizeRecipient == address (prizePool) && _prizeAmount > 0 ) {
150
- prizePool.contributePrizeTokens (boostedVault, _prizeAmount);
151
- emit BoostedVaultWithPrize (address (prizePool), boostedVault, _prizeAmount);
158
+ /// that is being boosted. Otherwise if this contract received the prize, it will transfer it to the
159
+ /// winner passed in through the extra hook data.
160
+ function afterClaimPrize (address _winner , uint8 _tier , uint32 _prizeIndex , uint256 _prizeAmount , address _prizeRecipient , bytes memory _data ) external {
161
+ if (_prizeAmount > 0 ) {
162
+ if (_prizeRecipient == address (prizePool)) {
163
+ uint256 _pickAttempts = abi.decode (_data, (uint256 ));
164
+ prizePool.contributePrizeTokens (boostedVault, _prizeAmount);
165
+ emit BoostedVaultWithPrize (address (prizePool), boostedVault, _prizeAmount, _pickAttempts);
166
+ } else if (_prizeRecipient == address (this )) {
167
+ address _nftWinner = abi.decode (_data, (address ));
168
+ prizePool.prizeToken ().safeTransfer (_nftWinner, _prizeAmount);
169
+ emit PrizeWonByNftHolder (_nftWinner, msg .sender , _winner, _tier, _prizeIndex, _prizeAmount);
170
+ }
152
171
}
153
172
}
154
173
}
0 commit comments