@bananapus/distributor-v6 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,563 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.28;
3
+
4
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
5
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import {mulDiv} from "@prb/math/src/Common.sol";
8
+
9
+ import {IJBDistributor} from "./interfaces/IJBDistributor.sol";
10
+ import {JBTokenSnapshotData} from "./structs/JBTokenSnapshotData.sol";
11
+ import {JBVestingData} from "./structs/JBVestingData.sol";
12
+
13
+ /// @notice A contract managing distributions of tokens to be claimed and vested by stakers of any other token.
14
+ abstract contract JBDistributor is IJBDistributor {
15
+ using SafeERC20 for IERC20;
16
+ //*********************************************************************//
17
+ // --------------------------- custom errors ------------------------- //
18
+ //*********************************************************************//
19
+
20
+ /// @notice Thrown when a token has already begun vesting for a given round.
21
+ error JBDistributor_AlreadyVesting();
22
+
23
+ /// @notice Thrown when a native ETH transfer fails.
24
+ error JBDistributor_NativeTransferFailed();
25
+
26
+ /// @notice Thrown when there is nothing to distribute for a token in the current round.
27
+ error JBDistributor_NothingToDistribute();
28
+
29
+ /// @notice Thrown when the caller does not have access to the token.
30
+ error JBDistributor_NoAccess();
31
+
32
+ //*********************************************************************//
33
+ // ---------------- public immutable stored properties --------------- //
34
+ //*********************************************************************//
35
+
36
+ /// @notice The starting block of the distributor.
37
+ uint256 public immutable startingBlock;
38
+
39
+ /// @notice The minimum amount of time stakers have to claim rewards, specified in blocks.
40
+ uint256 public immutable override roundDuration;
41
+
42
+ /// @notice The number of rounds until tokens are fully vested.
43
+ uint256 public immutable override vestingRounds;
44
+
45
+ //*********************************************************************//
46
+ // --------------------- public constant properties ----------------- //
47
+ //*********************************************************************//
48
+
49
+ /// @notice The number of shares that represent 100%.
50
+ uint256 public constant MAX_SHARE = 100_000;
51
+
52
+ //*********************************************************************//
53
+ // --------------------- public stored properties -------------------- //
54
+ //*********************************************************************//
55
+
56
+ /// @notice The amount of a token that is currently vesting for a hook's stakers.
57
+ /// @custom:param hook The hook whose stakers are vesting.
58
+ /// @custom:param token The address of the token that is vesting.
59
+ mapping(address hook => mapping(IERC20 token => uint256 amount)) public override totalVestingAmountOf;
60
+
61
+ /// @notice All vesting data of a tokenId for any number of vesting tokens.
62
+ /// @custom:param hook The hook the tokenId belongs to.
63
+ /// @custom:param tokenId The ID of the token to which the vests belong.
64
+ /// @custom:param token The address of the token being vested.
65
+ // slither-disable-next-line uninitialized-state
66
+ mapping(address hook => mapping(uint256 tokenId => mapping(IERC20 token => JBVestingData[]))) public vestingDataOf;
67
+
68
+ /// @notice The index within `vestingDataOf` of the latest vest.
69
+ /// @custom:param hook The hook the tokenId belongs to.
70
+ /// @custom:param tokenId The ID of the token to which the vests belong.
71
+ /// @custom:param token The address of the token being vested.
72
+ mapping(address hook => mapping(uint256 tokenId => mapping(IERC20 token => uint256))) public latestVestedIndexOf;
73
+
74
+ //*********************************************************************//
75
+ // ------------------------ internal properties ---------------------- //
76
+ //*********************************************************************//
77
+
78
+ /// @notice The balance of a token held for a specific hook's stakers.
79
+ /// @custom:param hook The hook whose balance to check.
80
+ /// @custom:param token The token to check the balance of.
81
+ mapping(address hook => mapping(IERC20 token => uint256)) internal _balanceOf;
82
+
83
+ /// @notice The snapshot data of the token information for each round.
84
+ /// @custom:param hook The hook the snapshot is for.
85
+ /// @custom:param token The address of the token being claimed and vested.
86
+ /// @custom:param round The round to which the data applies.
87
+ mapping(address hook => mapping(IERC20 token => mapping(uint256 round => JBTokenSnapshotData snapshot))) internal
88
+ _snapshotAtRoundOf;
89
+
90
+ //*********************************************************************//
91
+ // -------------------------- public views --------------------------- //
92
+ //*********************************************************************//
93
+
94
+ /// @notice The number of the current round.
95
+ function currentRound() public view override returns (uint256) {
96
+ return (block.number - startingBlock) / roundDuration;
97
+ }
98
+
99
+ /// @notice The block at which a round started.
100
+ /// @param round The round to get the start block of.
101
+ function roundStartBlock(uint256 round) public view override returns (uint256) {
102
+ return startingBlock + roundDuration * round;
103
+ }
104
+
105
+ /// @notice The balance of a token held for a specific hook's stakers.
106
+ /// @param hook The hook whose balance to check.
107
+ /// @param token The token to check the balance of.
108
+ function balanceOf(address hook, IERC20 token) external view override returns (uint256) {
109
+ return _balanceOf[hook][token];
110
+ }
111
+
112
+ /// @notice The snapshot data of the token information for each round.
113
+ /// @param hook The hook the snapshot is for.
114
+ /// @param token The address of the token being claimed and vested.
115
+ /// @param round The round to which the data applies.
116
+ function snapshotAtRoundOf(
117
+ address hook,
118
+ IERC20 token,
119
+ uint256 round
120
+ )
121
+ external
122
+ view
123
+ override
124
+ returns (JBTokenSnapshotData memory)
125
+ {
126
+ return _snapshotAtRoundOf[hook][token][round];
127
+ }
128
+
129
+ /// @notice Calculate how much of the token has been claimed for the given tokenId.
130
+ /// @param hook The hook the tokenId belongs to.
131
+ /// @param tokenId The ID of the token to calculate the token amount for.
132
+ /// @param token The address of the token being claimed.
133
+ /// @return tokenAmount The amount of tokens that can be claimed once they have vested.
134
+ function claimedFor(
135
+ address hook,
136
+ uint256 tokenId,
137
+ IERC20 token
138
+ )
139
+ external
140
+ view
141
+ override
142
+ returns (uint256 tokenAmount)
143
+ {
144
+ // Keep a reference to the latest vested index.
145
+ uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
146
+
147
+ // Keep a reference to the number of vesting rounds for the tokenId and token.
148
+ uint256 numberOfVestingRounds = vestingDataOf[hook][tokenId][token].length;
149
+
150
+ while (vestedIndex < numberOfVestingRounds) {
151
+ // Keep a reference to the vested data being iterated on.
152
+ JBVestingData memory vesting = vestingDataOf[hook][tokenId][token][vestedIndex];
153
+
154
+ tokenAmount += mulDiv(vesting.amount, MAX_SHARE - vesting.shareClaimed, MAX_SHARE);
155
+
156
+ unchecked {
157
+ ++vestedIndex;
158
+ }
159
+ }
160
+ }
161
+
162
+ /// @notice Calculate how much of the token is currently ready to be collected for the given tokenId.
163
+ /// @param hook The hook the tokenId belongs to.
164
+ /// @param tokenId The ID of the token to calculate the token amount for.
165
+ /// @param token The address of the token being claimed.
166
+ /// @return tokenAmount The amount of tokens that can be claimed right now.
167
+ function collectableFor(
168
+ address hook,
169
+ uint256 tokenId,
170
+ IERC20 token
171
+ )
172
+ external
173
+ view
174
+ override
175
+ returns (uint256 tokenAmount)
176
+ {
177
+ // The round that we are in right now.
178
+ uint256 round = currentRound();
179
+
180
+ // Keep a reference to the latest vested index.
181
+ uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
182
+
183
+ // Keep a reference to the number of vesting rounds for the tokenId and token.
184
+ uint256 numberOfVestingRounds = vestingDataOf[hook][tokenId][token].length;
185
+
186
+ while (vestedIndex < numberOfVestingRounds) {
187
+ uint256 lockedShare;
188
+
189
+ // Keep a reference to the vested data being iterated on.
190
+ JBVestingData memory vesting = vestingDataOf[hook][tokenId][token][vestedIndex];
191
+
192
+ // Calculate the share amount that is locked.
193
+ if (vesting.releaseRound > round) {
194
+ lockedShare = (vesting.releaseRound - round) * MAX_SHARE / vestingRounds;
195
+ }
196
+
197
+ tokenAmount += mulDiv(vesting.amount, MAX_SHARE - vesting.shareClaimed - lockedShare, MAX_SHARE);
198
+
199
+ unchecked {
200
+ ++vestedIndex;
201
+ }
202
+ }
203
+ }
204
+
205
+ //*********************************************************************//
206
+ // -------------------------- constructor ---------------------------- //
207
+ //*********************************************************************//
208
+
209
+ /// @param roundDuration_ The minimum amount of time stakers have to claim rewards, specified in blocks. Make sure
210
+ /// this is correct for each blockchain/rollup this gets deployed to.
211
+ /// @param vestingRounds_ The number of rounds until tokens are fully vested.
212
+ constructor(uint256 roundDuration_, uint256 vestingRounds_) {
213
+ startingBlock = block.number;
214
+ roundDuration = roundDuration_;
215
+ vestingRounds = vestingRounds_;
216
+ }
217
+
218
+ //*********************************************************************//
219
+ // ---------------------- external transactions ---------------------- //
220
+ //*********************************************************************//
221
+
222
+ /// @notice Fund the distributor for a specific hook by pulling tokens from the caller.
223
+ /// @dev For native ETH, send `msg.value` and pass `IERC20(JBConstants.NATIVE_TOKEN)` as the token.
224
+ /// @param hook The hook to fund.
225
+ /// @param token The token to fund with.
226
+ /// @param amount The amount to fund.
227
+ function fund(address hook, IERC20 token, uint256 amount) external payable override {
228
+ if (address(token) == JBConstants.NATIVE_TOKEN) {
229
+ amount = msg.value;
230
+ } else {
231
+ // Use balance delta to handle fee-on-transfer tokens correctly.
232
+ uint256 balanceBefore = token.balanceOf(address(this));
233
+ token.safeTransferFrom(msg.sender, address(this), amount);
234
+ amount = token.balanceOf(address(this)) - balanceBefore;
235
+ }
236
+ _balanceOf[hook][token] += amount;
237
+ }
238
+
239
+ /// @notice Claims tokens and begins vesting.
240
+ /// @param hook The hook whose stakers are vesting.
241
+ /// @param tokenIds The IDs to claim rewards for.
242
+ /// @param tokens The tokens to claim.
243
+ function beginVesting(address hook, uint256[] calldata tokenIds, IERC20[] calldata tokens) external override {
244
+ // Keep a reference to the current round.
245
+ uint256 round = currentRound();
246
+
247
+ // Keep a reference to the total staked amount at the current round.
248
+ uint256 totalStakeAmount = _totalStake(hook, roundStartBlock(round));
249
+
250
+ // Loop through each token for which vesting is beginning.
251
+ for (uint256 i; i < tokens.length;) {
252
+ IERC20 token = tokens[i];
253
+
254
+ // Take a snapshot of the token balance if it hasn't been taken already.
255
+ JBTokenSnapshotData memory snapshot = _takeSnapshotOf(hook, token);
256
+ uint256 distributable = snapshot.balance - snapshot.vestingAmount;
257
+
258
+ // Revert if there is nothing to distribute for this token.
259
+ if (distributable == 0) revert JBDistributor_NothingToDistribute();
260
+
261
+ // Vest each token ID and get the total amount vested.
262
+ uint256 totalVestingAmount =
263
+ _vestTokenIds(hook, tokenIds, token, distributable, totalStakeAmount, round + vestingRounds);
264
+
265
+ unchecked {
266
+ // Store the updated total claimed amount now vesting.
267
+ totalVestingAmountOf[hook][token] += totalVestingAmount;
268
+
269
+ ++i;
270
+ }
271
+ }
272
+ }
273
+
274
+ /// @notice Vests each token ID for a given reward token and returns the total amount vested.
275
+ /// @param hook The hook whose stakers are vesting.
276
+ /// @param tokenIds The IDs to claim rewards for.
277
+ /// @param token The reward token.
278
+ /// @param distributable The distributable amount for this round.
279
+ /// @param totalStakeAmount The total stake amount.
280
+ /// @param vestingReleaseRound The round at which vesting will be released.
281
+ /// @return totalVestingAmount The total amount that began vesting.
282
+ function _vestTokenIds(
283
+ address hook,
284
+ uint256[] calldata tokenIds,
285
+ IERC20 token,
286
+ uint256 distributable,
287
+ uint256 totalStakeAmount,
288
+ uint256 vestingReleaseRound
289
+ )
290
+ internal
291
+ returns (uint256 totalVestingAmount)
292
+ {
293
+ for (uint256 j; j < tokenIds.length;) {
294
+ uint256 tokenId = tokenIds[j];
295
+
296
+ // Skip burned tokens — they are excluded from _totalStake, so including them would overbook vesting.
297
+ if (_tokenBurned(hook, tokenId)) {
298
+ unchecked {
299
+ ++j;
300
+ }
301
+ continue;
302
+ }
303
+
304
+ // Keep a reference to the vesting data for this hook/tokenId/token.
305
+ JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
306
+
307
+ // Make sure this token hasn't already been claimed by checking if the last item is the current round.
308
+ uint256 numVesting = vestings.length;
309
+ // slither-disable-next-line incorrect-equality
310
+ if (numVesting != 0 && vestings[numVesting - 1].releaseRound == vestingReleaseRound) {
311
+ revert JBDistributor_AlreadyVesting();
312
+ }
313
+
314
+ // Keep a reference to the amount of tokens being claimed.
315
+ uint256 tokenAmount = mulDiv(distributable, _tokenStake(hook, tokenId), totalStakeAmount);
316
+
317
+ // Add to the list of vesting data.
318
+ vestings.push(JBVestingData({releaseRound: vestingReleaseRound, amount: tokenAmount, shareClaimed: 0}));
319
+
320
+ emit Claimed(hook, tokenId, token, tokenAmount, vestingReleaseRound);
321
+
322
+ unchecked {
323
+ totalVestingAmount += tokenAmount;
324
+ ++j;
325
+ }
326
+ }
327
+ }
328
+
329
+ /// @notice Release vested rewards in the case that a token was burned.
330
+ /// @param hook The hook whose tokens were burned.
331
+ /// @param tokenIds The IDs of the burned tokens.
332
+ /// @param tokens The address of the tokens being released.
333
+ /// @param beneficiary The recipient of the released tokens.
334
+ function releaseForfeitedRewards(
335
+ address hook,
336
+ uint256[] calldata tokenIds,
337
+ IERC20[] calldata tokens,
338
+ address beneficiary
339
+ )
340
+ external
341
+ override
342
+ {
343
+ // Make sure that all tokens are burned.
344
+ for (uint256 i; i < tokenIds.length;) {
345
+ if (!_tokenBurned(hook, tokenIds[i])) revert JBDistributor_NoAccess();
346
+ unchecked {
347
+ ++i;
348
+ }
349
+ }
350
+
351
+ // Unlock the rewards and send them to the beneficiary.
352
+ _unlockRewards(hook, tokenIds, tokens, beneficiary, false);
353
+ }
354
+
355
+ //*********************************************************************//
356
+ // ----------------------- public transactions ----------------------- //
357
+ //*********************************************************************//
358
+
359
+ /// @notice Collect vested tokens.
360
+ /// @param hook The hook whose stakers are collecting.
361
+ /// @param tokenIds The IDs of the tokens to collect for.
362
+ /// @param tokens The address of the tokens being claimed.
363
+ /// @param beneficiary The recipient of the collected tokens.
364
+ function collectVestedRewards(
365
+ address hook,
366
+ uint256[] calldata tokenIds,
367
+ IERC20[] calldata tokens,
368
+ address beneficiary
369
+ )
370
+ public
371
+ override
372
+ {
373
+ // Make sure that all tokens can be claimed by this sender.
374
+ for (uint256 i; i < tokenIds.length;) {
375
+ if (!_canClaim(hook, tokenIds[i], msg.sender)) revert JBDistributor_NoAccess();
376
+ unchecked {
377
+ ++i;
378
+ }
379
+ }
380
+
381
+ // Unlock the rewards and send them to the beneficiary.
382
+ _unlockRewards(hook, tokenIds, tokens, beneficiary, true);
383
+ }
384
+
385
+ //*********************************************************************//
386
+ // ---------------------- internal transactions ---------------------- //
387
+ //*********************************************************************//
388
+
389
+ /// @notice Unlocks rewards for the given token IDs and tokens, either for collection or forfeiture.
390
+ /// @param hook The hook the tokens belong to.
391
+ /// @param tokenIds The IDs of the tokens to unlock rewards for.
392
+ /// @param tokens The address of the tokens being unlocked.
393
+ /// @param beneficiary The recipient of the unlocked tokens.
394
+ /// @param ownerClaim Whether this is a claim by the owner (true) or a forfeiture release (false).
395
+ function _unlockRewards(
396
+ address hook,
397
+ uint256[] calldata tokenIds,
398
+ IERC20[] calldata tokens,
399
+ address beneficiary,
400
+ bool ownerClaim
401
+ )
402
+ internal
403
+ {
404
+ uint256 round = currentRound();
405
+
406
+ // Loop through each token for which vested rewards are being collected.
407
+ for (uint256 i; i < tokens.length;) {
408
+ IERC20 token = tokens[i];
409
+
410
+ // Process all token IDs for this reward token.
411
+ uint256 totalTokenAmount = _unlockTokenIds(hook, tokenIds, token, round);
412
+
413
+ // Perform the transfer.
414
+ if (totalTokenAmount != 0) {
415
+ unchecked {
416
+ // Update the amount that is left vesting.
417
+ totalVestingAmountOf[hook][token] -= totalTokenAmount;
418
+ }
419
+
420
+ // If this claim is from the owner (or on behalf of the owner).
421
+ if (ownerClaim) {
422
+ // Decrement the hook's balance and transfer tokens out.
423
+ _balanceOf[hook][token] -= totalTokenAmount;
424
+
425
+ if (address(token) == JBConstants.NATIVE_TOKEN) {
426
+ // slither-disable-next-line arbitrary-send-eth,reentrancy-eth
427
+ (bool success,) = beneficiary.call{value: totalTokenAmount}("");
428
+ if (!success) revert JBDistributor_NativeTransferFailed();
429
+ } else {
430
+ token.safeTransfer(beneficiary, totalTokenAmount);
431
+ }
432
+ }
433
+ // If forfeiture: _balanceOf is NOT decremented so the forfeited tokens
434
+ // return to the hook's distributable pool for future rounds.
435
+ }
436
+
437
+ unchecked {
438
+ ++i;
439
+ }
440
+ }
441
+ }
442
+
443
+ /// @notice Unlocks rewards for a set of token IDs for a single reward token.
444
+ /// @param hook The hook the tokens belong to.
445
+ /// @param tokenIds The IDs of the tokens to unlock rewards for.
446
+ /// @param token The reward token being unlocked.
447
+ /// @param round The current round.
448
+ /// @return totalTokenAmount The total amount of reward tokens unlocked.
449
+ function _unlockTokenIds(
450
+ address hook,
451
+ uint256[] calldata tokenIds,
452
+ IERC20 token,
453
+ uint256 round
454
+ )
455
+ internal
456
+ returns (uint256 totalTokenAmount)
457
+ {
458
+ for (uint256 j; j < tokenIds.length;) {
459
+ uint256 tokenId = tokenIds[j];
460
+
461
+ // Keep a reference to the latest vested index.
462
+ uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
463
+
464
+ // Keep a reference to the vesting data array.
465
+ JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
466
+ uint256 numberOfVestingRounds = vestings.length;
467
+
468
+ // Keep a reference to a vested index that will be incremented.
469
+ uint256 newLatestVestedIndex = vestedIndex;
470
+
471
+ while (vestedIndex < numberOfVestingRounds) {
472
+ uint256 lockedShare;
473
+
474
+ // Keep a reference to the vested data being iterated on.
475
+ JBVestingData memory vesting = vestings[vestedIndex];
476
+
477
+ // Calculate the share amount that is locked.
478
+ if (vesting.releaseRound > round) {
479
+ lockedShare = (vesting.releaseRound - round) * MAX_SHARE / vestingRounds;
480
+ }
481
+
482
+ uint256 claimAmount = mulDiv(vesting.amount, MAX_SHARE - vesting.shareClaimed - lockedShare, MAX_SHARE);
483
+
484
+ // Update to reflect the amount claimed.
485
+ vestings[vestedIndex].shareClaimed = MAX_SHARE - lockedShare;
486
+
487
+ if (claimAmount != 0) {
488
+ totalTokenAmount += claimAmount;
489
+ emit Collected(hook, tokenId, token, claimAmount, vesting.releaseRound);
490
+ }
491
+
492
+ unchecked {
493
+ ++vestedIndex;
494
+
495
+ // Only advance the latest-vested index contiguously past fully exhausted entries.
496
+ // slither-disable-next-line incorrect-equality
497
+ if (lockedShare == 0 && vestedIndex == newLatestVestedIndex + 1) {
498
+ ++newLatestVestedIndex;
499
+ }
500
+ }
501
+ }
502
+
503
+ latestVestedIndexOf[hook][tokenId][token] = newLatestVestedIndex;
504
+
505
+ unchecked {
506
+ ++j;
507
+ }
508
+ }
509
+ }
510
+
511
+ /// @notice Takes a snapshot of the token balance and vesting amount for the current round.
512
+ /// @param hook The hook to take the snapshot for.
513
+ /// @param token The token address to take a snapshot of.
514
+ /// @return snapshot The snapshot data.
515
+ function _takeSnapshotOf(address hook, IERC20 token) internal returns (JBTokenSnapshotData memory snapshot) {
516
+ // Keep a reference to the current round.
517
+ uint256 round = currentRound();
518
+
519
+ // Keep a reference to the token's snapshot.
520
+ snapshot = _snapshotAtRoundOf[hook][token][round];
521
+
522
+ // If a snapshot was already taken at this cycle, do not take a new one.
523
+ if (snapshot.balance != 0) return snapshot;
524
+
525
+ // Take a snapshot using the hook's tracked balance.
526
+ snapshot =
527
+ JBTokenSnapshotData({balance: _balanceOf[hook][token], vestingAmount: totalVestingAmountOf[hook][token]});
528
+
529
+ // Store the snapshot.
530
+ _snapshotAtRoundOf[hook][token][round] = snapshot;
531
+
532
+ emit SnapshotCreated(hook, round, token, snapshot.balance, snapshot.vestingAmount);
533
+ }
534
+
535
+ //*********************************************************************//
536
+ // ----------------------- virtual transactions ---------------------- //
537
+ //*********************************************************************//
538
+
539
+ /// @notice A flag indicating if an account can currently claim their tokens.
540
+ /// @param hook The hook the token belongs to.
541
+ /// @param tokenId The ID of the token to check.
542
+ /// @param account The account to check if it can claim.
543
+ /// @return canClaim A flag indicating if claiming is allowed.
544
+ function _canClaim(address hook, uint256 tokenId, address account) internal view virtual returns (bool canClaim);
545
+
546
+ /// @notice The total amount staked at the given block.
547
+ /// @param hook The hook to get the total stake for.
548
+ /// @param blockNumber The block number to get the total staked amount at.
549
+ /// @return totalStakedAmount The total amount staked at a block number.
550
+ function _totalStake(address hook, uint256 blockNumber) internal view virtual returns (uint256 totalStakedAmount);
551
+
552
+ /// @notice The amount of tokens staked for the given token ID.
553
+ /// @param hook The hook the token belongs to.
554
+ /// @param tokenId The ID of the token to get the staked value of.
555
+ /// @return tokenStakeAmount The amount of staked tokens that is being represented by the token.
556
+ function _tokenStake(address hook, uint256 tokenId) internal view virtual returns (uint256 tokenStakeAmount);
557
+
558
+ /// @notice Checks if the given token was burned or not.
559
+ /// @param hook The hook the token belongs to.
560
+ /// @param tokenId The tokenId to check.
561
+ /// @return tokenWasBurned A boolean that is true if the token was burned.
562
+ function _tokenBurned(address hook, uint256 tokenId) internal view virtual returns (bool tokenWasBurned);
563
+ }