-
Notifications
You must be signed in to change notification settings - Fork 12
/
Claimer.sol
312 lines (279 loc) · 12 KB
/
Claimer.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { SD59x18 } from "prb-math/SD59x18.sol";
import { UD2x18 } from "prb-math/UD2x18.sol";
import { UD60x18, convert } from "prb-math/UD60x18.sol";
import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";
import { SafeCast } from "openzeppelin/utils/math/SafeCast.sol";
import { LinearVRGDALib } from "./libraries/LinearVRGDALib.sol";
import { IClaimable } from "pt-v5-claimable-interface/interfaces/IClaimable.sol";
/// @notice Thrown when the length of the winners array does not match the length of the prize indices array while claiming.
/// @param winnersLength Length of the winners array
/// @param prizeIndicesLength Length of the prize indices array
error ClaimArraySizeMismatch(uint256 winnersLength, uint256 prizeIndicesLength);
/// @notice Thrown when the minimum fee is greater than or equal to the max fee
/// @param minFee The minimum fee passed
/// @param maxFee The maximum fee passed
error MinFeeGeMax(uint256 minFee, uint256 maxFee);
/// @notice Thrown when the VRGDA fee is below the minimum fee
/// @param minFee The minimum fee requested by the user
/// @param fee The actual VRGDA fee
error VrgdaClaimFeeBelowMin(uint256 minFee, uint256 fee);
/// @notice Thrown when the prize pool is set the the zero address
error PrizePoolZeroAddress();
/// @notice Thrown when someone tries to claim a prizes with a fee, but sets the fee recipient address to the zero address.
error FeeRecipientZeroAddress();
/// @notice Thrown when the time to reach the max fee is zero
error TimeToReachMaxFeeZero();
/// @title Variable Rate Gradual Dutch Auction (VRGDA) Claimer
/// @author G9 Software Inc.
/// @notice This contract uses a variable rate gradual dutch auction to incentivize prize claims on behalf of others. Fees for each canary tier is set to the respective tier's prize size.
contract Claimer {
/// @notice Emitted when a claim reverts
/// @param vault The vault for which the claim failed
/// @param tier The tier for which the claim failed
/// @param winner The winner for which the claim failed
/// @param prizeIndex The prize index for which the claim failed
/// @param reason The revert reason
event ClaimError(
IClaimable indexed vault,
uint8 indexed tier,
address indexed winner,
uint32 prizeIndex,
bytes reason
);
/// @notice The Prize Pool that this Claimer is claiming prizes for
PrizePool public immutable prizePool;
/// @notice The maximum fee that can be charged as a portion of the prize size. Fixed point 18 number
UD2x18 public immutable maxFeePortionOfPrize;
/// @notice The VRGDA decay constant computed in the constructor
SD59x18 public immutable decayConstant;
/// @notice The minimum fee that should be charged per claim (used to calculate the VRGDA decay constant that controls the pricing curve)
uint256 public immutable minimumFee;
/// @notice The time in seconds to reach the max auction fee
uint256 public immutable timeToReachMaxFee;
/// @notice Constructs a new Claimer
/// @param _prizePool The prize pool to claim for
/// @param _minimumFee The minimum fee that should be charged (used to calculate the VRGDA decay constant). Fees will start
/// at this price during an auction, so it is recommended to set it very low (must be greater than zero).
/// @param _maximumFee The maximum fee that should be charged (used to calculate the VRGDA decay constant). Fees will never
/// exceed this amount per claim.
/// @param _timeToReachMaxFee The time it should take to reach the maximum fee
/// @param _maxFeePortionOfPrize The maximum fee that can be charged as a portion of the prize size. Fixed point 18 number
constructor(
PrizePool _prizePool,
uint256 _minimumFee,
uint256 _maximumFee,
uint256 _timeToReachMaxFee,
UD2x18 _maxFeePortionOfPrize
) {
if (address(0) == address(_prizePool)) {
revert PrizePoolZeroAddress();
}
if (_minimumFee >= _maximumFee) {
revert MinFeeGeMax(_minimumFee, _maximumFee);
}
if (_timeToReachMaxFee == 0) {
revert TimeToReachMaxFeeZero();
}
prizePool = _prizePool;
maxFeePortionOfPrize = _maxFeePortionOfPrize;
decayConstant = LinearVRGDALib.getDecayConstant(
LinearVRGDALib.getMaximumPriceDeltaScale(_minimumFee, _maximumFee, _timeToReachMaxFee)
);
minimumFee = _minimumFee;
timeToReachMaxFee = _timeToReachMaxFee;
}
/// @notice Allows the caller to claim prizes on behalf of others or for themself.
/// @dev If you are claiming for yourself or don't want to take a fee, set the `_feeRecipient` and
/// `_minFeePerClaim` to zero. This will save some gas on fee calculation.
/// @param _vault The vault to claim from
/// @param _tier The tier to claim for
/// @param _winners The array of winners to claim for
/// @param _prizeIndices The array of prize indices to claim for each winner (length should match winners)
/// @param _feeRecipient The address to receive the claim fees
/// @param _minFeePerClaim The minimum fee for each claim
/// @return totalFees The total fees collected across all successful claims
function claimPrizes(
IClaimable _vault,
uint8 _tier,
address[] calldata _winners,
uint32[][] calldata _prizeIndices,
address _feeRecipient,
uint256 _minFeePerClaim
) external returns (uint256 totalFees) {
bool feeRecipientZeroAddress = address(0) == _feeRecipient;
if (feeRecipientZeroAddress && _minFeePerClaim != 0) {
revert FeeRecipientZeroAddress();
}
if (_winners.length != _prizeIndices.length) {
revert ClaimArraySizeMismatch(_winners.length, _prizeIndices.length);
}
uint96 feePerClaim;
/**
* If the claimer hasn't specified both a min fee and a fee recipient, we assume that they don't
* expect a fee and save them some gas on the calculation.
*/
if (!feeRecipientZeroAddress) {
feePerClaim = SafeCast.toUint96(_computeFeePerClaim(_tier, _countClaims(_winners, _prizeIndices), prizePool.claimCount()));
if (feePerClaim < _minFeePerClaim) {
revert VrgdaClaimFeeBelowMin(_minFeePerClaim, feePerClaim);
}
}
return feePerClaim * _claim(_vault, _tier, _winners, _prizeIndices, _feeRecipient, feePerClaim);
}
/// @notice Computes the number of claims that will be made
/// @param _winners The array of winners to claim for
/// @param _prizeIndices The array of prize indices to claim for each winner (length should match winners)
/// @return The number of claims
function _countClaims(
address[] calldata _winners,
uint32[][] calldata _prizeIndices
) internal view returns (uint256) {
uint256 claimCount;
uint256 length = _winners.length;
for (uint256 i = 0; i < length; i++) {
claimCount += _prizeIndices[i].length;
}
return claimCount;
}
/// @notice Claims prizes for a batch of winners and prize indices
/// @param _vault The vault to claim from
/// @param _tier The tier to claim for
/// @param _winners The array of winners to claim for
/// @param _prizeIndices The array of prize indices to claim for each winner (length should match winners)
/// @param _feeRecipient The address to receive the claim fees
/// @param _feePerClaim The fee to charge for each claim
/// @return The number of claims that were successful
function _claim(
IClaimable _vault,
uint8 _tier,
address[] calldata _winners,
uint32[][] calldata _prizeIndices,
address _feeRecipient,
uint96 _feePerClaim
) internal returns (uint256) {
uint256 actualClaimCount;
uint256 prizeIndicesLength;
// `_winners.length` is not cached cause via-ir would need to be used
for (uint256 w = 0; w < _winners.length; w++) {
prizeIndicesLength = _prizeIndices[w].length;
for (uint256 p = 0; p < prizeIndicesLength; p++) {
try
_vault.claimPrize(_winners[w], _tier, _prizeIndices[w][p], _feePerClaim, _feeRecipient)
returns (uint256 /* prizeSize */) {
actualClaimCount++;
} catch (bytes memory reason) {
emit ClaimError(_vault, _tier, _winners[w], _prizeIndices[w][p], reason);
}
}
}
return actualClaimCount;
}
/// @notice Computes the total fees for the given number of claims.
/// @param _tier The tier to claim prizes from
/// @param _claimCount The number of claims
/// @return The total fees for those claims
function computeTotalFees(uint8 _tier, uint256 _claimCount) external view returns (uint256) {
return computeTotalFees(_tier, _claimCount, prizePool.claimCount());
}
/// @notice Computes the total fees for the given number of claims if a number of claims have already been made.
/// @param _tier The tier to claim prizes from
/// @param _claimCount The number of claims
/// @param _claimedCount The number of prizes already claimed
/// @return The total fees for those claims
function computeTotalFees(
uint8 _tier,
uint256 _claimCount,
uint256 _claimedCount
) public view returns (uint256) {
return _computeFeePerClaim(_tier, _claimCount, _claimedCount) * _claimCount;
}
/// @notice Computes the fee per claim for the given tier and number of claims
/// @param _tier The tier to claim prizes from
/// @param _claimCount The number of claims
/// @return The fee that will be taken per claim
function computeFeePerClaim(uint8 _tier, uint256 _claimCount) external view returns (uint256) {
return _computeFeePerClaim(_tier, _claimCount, prizePool.claimCount());
}
/// @notice Computes the total fees for the given number of claims.
/// @param _tier The tier
/// @param _claimCount The number of claims to check
/// @param _claimedCount The number of prizes already claimed
/// @return The total fees for the claims
function _computeFeePerClaim(
uint8 _tier,
uint256 _claimCount,
uint256 _claimedCount
) internal view returns (uint256) {
if (_claimCount == 0) {
return 0;
}
if (prizePool.isCanaryTier(_tier)) {
return prizePool.getTierPrizeSize(_tier);
}
uint256 _maxFee = _computeMaxFee(_tier);
SD59x18 perTimeUnit = LinearVRGDALib.getPerTimeUnit(
prizePool.estimatedPrizeCount(),
timeToReachMaxFee
);
uint256 elapsed = block.timestamp - (prizePool.lastAwardedDrawAwardedAt());
uint256 fee;
for (uint256 i = 0; i < _claimCount; i++) {
fee += _computeFeeForNextClaim(
minimumFee,
decayConstant,
perTimeUnit,
elapsed,
_claimedCount + i,
_maxFee
);
}
return fee / _claimCount;
}
/// @notice Computes the maximum fee that can be charged.
/// @param _tier The tier to compute the max fee for
/// @return The maximum fee that can be charged
function computeMaxFee(uint8 _tier) public view returns (uint256) {
if (prizePool.isCanaryTier(_tier)) {
return prizePool.getTierPrizeSize(_tier);
} else {
return _computeMaxFee(_tier);
}
}
/// @notice Computes the max fee given the tier
/// @param _tier The tier to compute the max fee for
/// @return The maximum fee that will be charged for a prize claim for the given tier
function _computeMaxFee(uint8 _tier) internal view returns (uint256) {
uint256 prizeSize = prizePool.getTierPrizeSize(_tier);
return
convert(
maxFeePortionOfPrize.intoUD60x18().mul(convert(prizeSize))
);
}
/// @notice Computes the fee for the next claim.
/// @param _minimumFee The minimum fee that should be charged
/// @param _decayConstant The VRGDA decay constant
/// @param _perTimeUnit The num to be claimed per second
/// @param _elapsed The number of seconds that have elapsed
/// @param _sold The number of prizes that were claimed
/// @param _maxFee The maximum fee that can be charged
/// @return The fee to charge for the next claim
function _computeFeeForNextClaim(
uint256 _minimumFee,
SD59x18 _decayConstant,
SD59x18 _perTimeUnit,
uint256 _elapsed,
uint256 _sold,
uint256 _maxFee
) internal pure returns (uint256) {
uint256 fee = LinearVRGDALib.getVRGDAPrice(
_minimumFee,
_elapsed,
_sold,
_perTimeUnit,
_decayConstant
);
return fee > _maxFee ? _maxFee : fee;
}
}