@ballkidz/defifa 0.0.1

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.
Files changed (59) hide show
  1. package/.gas-snapshot +2 -0
  2. package/CRYPTO_ECON.md +955 -0
  3. package/CRYPTO_ECON.pdf +0 -0
  4. package/CRYPTO_ECON.tex +800 -0
  5. package/README.md +119 -0
  6. package/SKILLS.md +177 -0
  7. package/deployments/defifa-v5/arbitrum_sepolia/DefifaDelegate.json +4867 -0
  8. package/deployments/defifa-v5/arbitrum_sepolia/DefifaDeployer.json +1719 -0
  9. package/deployments/defifa-v5/arbitrum_sepolia/DefifaGovernor.json +1535 -0
  10. package/deployments/defifa-v5/arbitrum_sepolia/DefifaTokenUriResolver.json +295 -0
  11. package/deployments/defifa-v5/base_sepolia/DefifaDelegate.json +4875 -0
  12. package/deployments/defifa-v5/base_sepolia/DefifaDeployer.json +1725 -0
  13. package/deployments/defifa-v5/base_sepolia/DefifaGovernor.json +1543 -0
  14. package/deployments/defifa-v5/base_sepolia/DefifaTokenUriResolver.json +301 -0
  15. package/deployments/defifa-v5/optimism_sepolia/DefifaDelegate.json +4875 -0
  16. package/deployments/defifa-v5/optimism_sepolia/DefifaDeployer.json +1725 -0
  17. package/deployments/defifa-v5/optimism_sepolia/DefifaGovernor.json +1543 -0
  18. package/deployments/defifa-v5/optimism_sepolia/DefifaTokenUriResolver.json +301 -0
  19. package/deployments/defifa-v5/sepolia/DefifaDelegate.json +4875 -0
  20. package/deployments/defifa-v5/sepolia/DefifaDeployer.json +1725 -0
  21. package/deployments/defifa-v5/sepolia/DefifaGovernor.json +1543 -0
  22. package/deployments/defifa-v5/sepolia/DefifaTokenUriResolver.json +301 -0
  23. package/foundry.lock +17 -0
  24. package/foundry.toml +35 -0
  25. package/package.json +33 -0
  26. package/remappings.txt +6 -0
  27. package/script/Deploy.s.sol +109 -0
  28. package/script/helpers/DefifaDeploymentLib.sol +83 -0
  29. package/slither-ci.config.json +10 -0
  30. package/sphinx.lock +521 -0
  31. package/src/DefifaDeployer.sol +894 -0
  32. package/src/DefifaGovernor.sol +490 -0
  33. package/src/DefifaHook.sol +1056 -0
  34. package/src/DefifaProjectOwner.sol +63 -0
  35. package/src/DefifaTokenUriResolver.sol +312 -0
  36. package/src/enums/DefifaGamePhase.sol +11 -0
  37. package/src/enums/DefifaScorecardState.sol +10 -0
  38. package/src/interfaces/IDefifaDeployer.sol +108 -0
  39. package/src/interfaces/IDefifaGamePhaseReporter.sol +8 -0
  40. package/src/interfaces/IDefifaGamePotReporter.sol +8 -0
  41. package/src/interfaces/IDefifaGovernor.sol +132 -0
  42. package/src/interfaces/IDefifaHook.sol +228 -0
  43. package/src/interfaces/IDefifaTokenUriResolver.sol +10 -0
  44. package/src/libraries/DefifaFontImporter.sol +19 -0
  45. package/src/libraries/DefifaHookLib.sol +358 -0
  46. package/src/structs/DefifaAttestations.sol +9 -0
  47. package/src/structs/DefifaDelegation.sol +9 -0
  48. package/src/structs/DefifaLaunchProjectData.sol +59 -0
  49. package/src/structs/DefifaOpsData.sol +20 -0
  50. package/src/structs/DefifaScorecard.sol +9 -0
  51. package/src/structs/DefifaTierCashOutWeight.sol +9 -0
  52. package/src/structs/DefifaTierParams.sol +16 -0
  53. package/test/DefifaFeeAccounting.t.sol +559 -0
  54. package/test/DefifaGovernor.t.sol +1333 -0
  55. package/test/DefifaMintCostInvariant.t.sol +299 -0
  56. package/test/DefifaNoContest.t.sol +922 -0
  57. package/test/DefifaSecurity.t.sol +717 -0
  58. package/test/SVG.t.sol +164 -0
  59. package/test/deployScript.t.sol +144 -0
@@ -0,0 +1,1056 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
5
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
6
+ import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
7
+ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
8
+ import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
9
+ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
10
+ import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
11
+ import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
12
+ import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
13
+ import {JBAfterCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
14
+ import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
15
+ import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
16
+ import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
17
+ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
18
+ import {JB721Hook} from "@bananapus/721-hook-v6/src/abstract/JB721Hook.sol";
19
+ import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
20
+ import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
21
+ import {
22
+ JB721TiersRulesetMetadataResolver
23
+ } from "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
24
+ import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
25
+ import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
26
+ import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
27
+
28
+ import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
29
+ import {DefifaDelegation} from "./structs/DefifaDelegation.sol";
30
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
31
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
32
+ import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
33
+ import {IDefifaGamePhaseReporter} from "./interfaces/IDefifaGamePhaseReporter.sol";
34
+ import {IDefifaGamePotReporter} from "./interfaces/IDefifaGamePotReporter.sol";
35
+ import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
36
+ import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
37
+ import {DefifaHookLib} from "./libraries/DefifaHookLib.sol";
38
+
39
+ /// @title DefifaHook
40
+ /// @notice A hook that transforms Juicebox treasury interactions into a Defifa game.
41
+ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
42
+ using Checkpoints for Checkpoints.Trace208;
43
+
44
+ //*********************************************************************//
45
+ // --------------------------- custom errors ------------------------- //
46
+ //*********************************************************************//
47
+
48
+ error DefifaHook_BadTierOrder();
49
+ error DefifaHook_DelegateAddressZero();
50
+ error DefifaHook_DelegateChangesUnavailableInThisPhase();
51
+ error DefifaHook_GameIsntScoringYet();
52
+ error DefifaHook_InvalidTierId();
53
+ error DefifaHook_InvalidCashoutWeights();
54
+ error DefifaHook_NothingToClaim();
55
+ error DefifaHook_NothingToMint();
56
+ error DefifaHook_WrongCurrency();
57
+ error DefifaHook_NoContest();
58
+ error DefifaHook_Overspending();
59
+ error DefifaHook_CashoutWeightsAlreadySet();
60
+ error DefifaHook_ReservedTokenMintingPaused();
61
+ error DefifaHook_TransfersPaused();
62
+ error DefifaHook_Unauthorized(uint256 tokenId, address owner, address caller);
63
+
64
+ //*********************************************************************//
65
+ // --------------------- public constant properties ------------------ //
66
+ //*********************************************************************//
67
+
68
+ /// @notice The total cashOut weight that can be divided among tiers.
69
+ uint256 public constant override TOTAL_CASHOUT_WEIGHT = 1_000_000_000_000_000_000;
70
+
71
+ //*********************************************************************//
72
+ // -------------------- internal stored properties ------------------- //
73
+ //*********************************************************************//
74
+
75
+ /// @notice The cashOut weight for each tier.
76
+ /// @dev Tiers are limited to ID 128
77
+ uint256[128] internal _tierCashOutWeights;
78
+
79
+ /// @notice The delegation status for each address and for each tier.
80
+ /// _delegator The delegator.
81
+ /// _tierId The ID of the tier being delegated.
82
+ mapping(address => mapping(uint256 => address)) internal _tierDelegation;
83
+
84
+ /// @notice The delegation checkpoints for each address and for each tier.
85
+ /// _delegator The delegator.
86
+ /// _tierId The ID of the tier being checked.
87
+ mapping(address => mapping(uint256 => Checkpoints.Trace208)) internal _delegateTierCheckpoints;
88
+
89
+ /// @notice The total delegation status for each tier.
90
+ /// _tierId The ID of the tier being checked.
91
+ mapping(uint256 => Checkpoints.Trace208) internal _totalTierCheckpoints;
92
+
93
+ /// @notice The first owner of each token ID, stored on first transfer out.
94
+ /// _tokenId The ID of the token to get the stored first owner of.
95
+ mapping(uint256 => address) internal _firstOwnerOf;
96
+
97
+ /// @notice The names of each tier.
98
+ /// @dev _tierId The ID of the tier to get a name for.
99
+ mapping(uint256 => string) internal _tierNameOf;
100
+
101
+ /// @notice The cumulative mint price of all tokens (paid and reserved). Used as the denominator for fee token
102
+ /// ($DEFIFA/$NANA) distribution.
103
+ uint256 internal _totalMintCost;
104
+
105
+ //*********************************************************************//
106
+ // ---------------- public immutable stored properties --------------- //
107
+ //*********************************************************************//
108
+
109
+ /// @notice The $DEFIFA token that is expected to be issued from paying fees.
110
+ IERC20 public immutable override defifaToken;
111
+
112
+ /// @notice The $BASE_PROTOCOL token that is expected to be issued from paying fees.
113
+ IERC20 public immutable override baseProtocolToken;
114
+
115
+ //*********************************************************************//
116
+ // --------------------- public stored properties -------------------- //
117
+ //*********************************************************************//
118
+
119
+ /// @notice The address of the origin 'DefifaHook', used to check in the init if the contract is the original or not
120
+ address public immutable override codeOrigin;
121
+
122
+ /// @notice The contract that stores and manages the NFT's data.
123
+ IJB721TiersHookStore public override store;
124
+
125
+ /// @notice The contract storing all funding cycle configurations.
126
+ IJBRulesets public override rulesets;
127
+
128
+ /// @notice The contract reporting game phases.
129
+ IDefifaGamePhaseReporter public override gamePhaseReporter;
130
+
131
+ /// @notice The contract reporting the game pot.
132
+ IDefifaGamePotReporter public override gamePotReporter;
133
+
134
+ /// @notice The currency that is accepted when minting tier NFTs.
135
+ uint256 public override pricingCurrency;
136
+
137
+ /// @notice A flag indicating if the cashout weights has been set.
138
+ bool public override cashOutWeightIsSet;
139
+
140
+ /// @notice The common base for the tokenUri's
141
+ string public override baseURI;
142
+
143
+ /// @notice Contract metadata uri.
144
+ string public override contractURI;
145
+
146
+ /// @notice The address that'll be set as the attestation delegate by default.
147
+ address public override defaultAttestationDelegate;
148
+
149
+ /// @notice The amount that has been redeemed from this game, refunds are not counted.
150
+ uint256 public override amountRedeemed;
151
+
152
+ /// @notice The amount of tokens that have been redeemed from a tier, refunds are not counted.
153
+ /// @custom:param The tier from which tokens have been redeemed.
154
+ mapping(uint256 => uint256) public override tokensRedeemedFrom;
155
+
156
+ //*********************************************************************//
157
+ // ------------------------- external views -------------------------- //
158
+ //*********************************************************************//
159
+
160
+ /// @notice The cashOut weight for each tier.
161
+ /// @return The array of weights, indexed by tier.
162
+ function tierCashOutWeights() external view override returns (uint256[128] memory) {
163
+ return _tierCashOutWeights;
164
+ }
165
+
166
+ /// @notice Returns the delegate of an account for specific tier.
167
+ /// @param account The account to check for a delegate of.
168
+ /// @param tier The tier to check within.
169
+ function getTierDelegateOf(address account, uint256 tier) external view override returns (address) {
170
+ return _tierDelegation[account][tier];
171
+ }
172
+
173
+ /// @notice Returns the current attestation power of an address for a specific tier.
174
+ /// @param account The address to check.
175
+ /// @param tier The tier to check within.
176
+ function getTierAttestationUnitsOf(address account, uint256 tier) external view override returns (uint256) {
177
+ return _delegateTierCheckpoints[account][tier].latest();
178
+ }
179
+
180
+ /// @notice Returns the past attestation units of a specific address for a specific tier.
181
+ /// @param account The address to check.
182
+ /// @param tier The tier to check within.
183
+ /// @param timestamp The timestamp to check the attestation power at.
184
+ function getPastTierAttestationUnitsOf(
185
+ address account,
186
+ uint256 tier,
187
+ uint48 timestamp
188
+ )
189
+ external
190
+ view
191
+ override
192
+ returns (uint256)
193
+ {
194
+ return _delegateTierCheckpoints[account][tier].upperLookup(timestamp);
195
+ }
196
+
197
+ /// @notice Returns the total amount of attestation units that exists for a tier.
198
+ /// @param tier The tier to check.
199
+ function getTierTotalAttestationUnitsOf(uint256 tier) external view override returns (uint256) {
200
+ return _totalTierCheckpoints[tier].latest();
201
+ }
202
+
203
+ /// @notice Returns the total amount of attestation units that has existed for a tier.
204
+ /// @param tier The tier to check.
205
+ /// @param timestamp The timestamp to check the total attestation units at.
206
+ function getPastTierTotalAttestationUnitsOf(
207
+ uint256 tier,
208
+ uint48 timestamp
209
+ )
210
+ external
211
+ view
212
+ override
213
+ returns (uint256)
214
+ {
215
+ return _totalTierCheckpoints[tier].upperLookup(timestamp);
216
+ }
217
+
218
+ /// @notice The first owner of each token ID, which corresponds to the address that originally contributed to the
219
+ /// project to receive the NFT.
220
+ /// @param tokenId The ID of the token to get the first owner of.
221
+ /// @return The first owner of the token.
222
+ function firstOwnerOf(uint256 tokenId) external view override returns (address) {
223
+ // Get a reference to the first owner.
224
+ address _storedFirstOwner = _firstOwnerOf[tokenId];
225
+
226
+ // If the stored first owner is set, return it.
227
+ if (_storedFirstOwner != address(0)) return _storedFirstOwner;
228
+
229
+ // Otherwise, the first owner must be the current owner.
230
+ return _owners[tokenId];
231
+ }
232
+
233
+ /// @notice The name of the tier with the specified ID.
234
+ /// @param tierId The ID of the tier.
235
+ function tierNameOf(uint256 tierId) external view override returns (string memory) {
236
+ return _tierNameOf[tierId];
237
+ }
238
+
239
+ //*********************************************************************//
240
+ // -------------------------- public views --------------------------- //
241
+ //*********************************************************************//
242
+
243
+ /// @notice The metadata URI of the provided token ID.
244
+ /// @dev Defer to the tokenUriResolver if set, otherwise, use the tokenUri set with the token's tier.
245
+ /// @param tokenId The ID of the token to get the tier URI for.
246
+ /// @return The token URI corresponding with the tier or the tokenUriResolver URI.
247
+ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
248
+ // Use the resolver.
249
+ return store.tokenUriResolverOf(address(this)).tokenUriOf(address(this), tokenId);
250
+ }
251
+
252
+ /// @notice The cumulative weight the given token IDs have in cashOuts compared to the `totalCashOutWeight`.
253
+ /// @param tokenIds The IDs of the tokens to get the cumulative cashOut weight of.
254
+ /// @return cumulativeWeight The weight.
255
+ function cashOutWeightOf(uint256[] memory tokenIds)
256
+ public
257
+ view
258
+ virtual
259
+ override
260
+ returns (uint256 cumulativeWeight)
261
+ {
262
+ cumulativeWeight = DefifaHookLib.computeCashOutWeightBatch({
263
+ tokenIds: tokenIds,
264
+ _store: store,
265
+ hook: address(this),
266
+ tierCashOutWeights: _tierCashOutWeights,
267
+ tokensRedeemedFrom: tokensRedeemedFrom
268
+ });
269
+ }
270
+
271
+ /// @notice The weight the given token ID has in cashOuts.
272
+ /// @param tokenId The ID of the token to get the cashOut weight of.
273
+ /// @return The weight.
274
+ function cashOutWeightOf(uint256 tokenId) public view override returns (uint256) {
275
+ return DefifaHookLib.computeCashOutWeight({
276
+ tokenId: tokenId,
277
+ _store: store,
278
+ hook: address(this),
279
+ tierCashOutWeights: _tierCashOutWeights,
280
+ tokensRedeemedFrom: tokensRedeemedFrom
281
+ });
282
+ }
283
+
284
+ /// @notice The amount of tokens of a tier that are currently in circulation.
285
+ /// @param tierId The ID of the tier to get the current supply of.
286
+ function currentSupplyOfTier(uint256 tierId) public view returns (uint256) {
287
+ return DefifaHookLib.computeCurrentSupply({_store: store, hook: address(this), tierId: tierId});
288
+ }
289
+
290
+ /// @notice The combined cash out weight of all outstanding NFTs.
291
+ /// @dev An NFT's cash out weight is its price.
292
+ /// @return The total cash out weight.
293
+ function totalCashOutWeight() public view virtual override returns (uint256) {
294
+ return TOTAL_CASHOUT_WEIGHT;
295
+ }
296
+
297
+ /// @notice The amount of $DEFIFA and $BASE_PROTOCOL tokens this game was allocated from paying the network fee.
298
+ /// @return defifaTokenAllocation The $DEFIFA token allocation.
299
+ /// @return baseProtocolTokenAllocation The $BASE_PROTOCOL token allocation.
300
+ function tokenAllocations()
301
+ public
302
+ view
303
+ returns (uint256 defifaTokenAllocation, uint256 baseProtocolTokenAllocation)
304
+ {
305
+ defifaTokenAllocation = defifaToken.balanceOf(address(this));
306
+ baseProtocolTokenAllocation = baseProtocolToken.balanceOf(address(this));
307
+ }
308
+
309
+ /// @notice The data calculated before a cash out is recorded in the terminal store. This data is provided to the
310
+ /// terminal's `cashOutTokensOf(...)` transaction.
311
+ /// @dev Sets this contract as the cash out hook. Part of `IJBRulesetDataHook`.
312
+ /// @dev This function is used for NFT cash outs, and will only be called if the project's ruleset has
313
+ /// `useDataHookForCashOut` set to `true`.
314
+ /// @param context The cash out context passed to this contract by the `cashOutTokensOf(...)` function.
315
+ /// @return cashOutTaxRate The cash out tax rate influencing the reclaim amount.
316
+ /// @return cashOutCount The amount of tokens that should be considered cashed out.
317
+ /// @return totalSupply The total amount of tokens that are considered to be existing.
318
+ /// @return hookSpecifications The amount and data to send to cash out hooks (this contract) instead of returning to
319
+ /// the beneficiary.
320
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
321
+ public
322
+ view
323
+ virtual
324
+ override(IJBRulesetDataHook, JB721Hook)
325
+ returns (
326
+ uint256 cashOutTaxRate,
327
+ uint256 cashOutCount,
328
+ uint256 totalSupply,
329
+ JBCashOutHookSpecification[] memory hookSpecifications
330
+ )
331
+ {
332
+ // Make sure (fungible) project tokens aren't also being cashed out.
333
+ if (context.cashOutCount > 0) revert JB721Hook_UnexpectedTokenCashedOut();
334
+
335
+ // Fetch the cash out hook metadata using the corresponding metadata ID.
336
+ (bool metadataExists, bytes memory metadata) =
337
+ JBMetadataResolver.getDataFor(JBMetadataResolver.getId("cashOut", codeOrigin), context.metadata);
338
+
339
+ uint256[] memory decodedTokenIds;
340
+
341
+ // Decode the metadata.
342
+ if (metadataExists) decodedTokenIds = abi.decode(metadata, (uint256[]));
343
+
344
+ // Get the current game phase.
345
+ DefifaGamePhase _gamePhase = gamePhaseReporter.currentGamePhaseOf(context.projectId);
346
+
347
+ // Calculate the amount paid to mint the tokens that are being burned.
348
+ uint256 _cumulativeMintPrice =
349
+ DefifaHookLib.computeCumulativeMintPrice({tokenIds: decodedTokenIds, _store: store, hook: address(this)});
350
+
351
+ // Use this contract as the only cash out hook.
352
+ hookSpecifications = new JBCashOutHookSpecification[](1);
353
+ hookSpecifications[0] = JBCashOutHookSpecification(this, 0, abi.encode(_cumulativeMintPrice));
354
+
355
+ // Compute the cash out count based on the game phase.
356
+ cashOutCount = DefifaHookLib.computeCashOutCount({
357
+ gamePhase: _gamePhase,
358
+ cumulativeMintPrice: _cumulativeMintPrice,
359
+ surplusValue: context.surplus.value,
360
+ _amountRedeemed: amountRedeemed,
361
+ cumulativeCashOutWeight: cashOutWeightOf(decodedTokenIds)
362
+ });
363
+
364
+ // Use the surplus as the total supply.
365
+ totalSupply = context.surplus.value;
366
+
367
+ // Use the cash out tax rate from the context.
368
+ cashOutTaxRate = context.cashOutTaxRate;
369
+ }
370
+
371
+ /// @notice The amount of $DEFIFA and $BASE_PROTOCOL tokens claimable for a set of token IDs.
372
+ /// @param tokenIds The IDs of the tokens that justify a $DEFIFA claim.
373
+ /// @return defifaTokenAmount The amount of $DEFIFA that can be claimed.
374
+ /// @return baseProtocolTokenAmount The amount of $BASE_PROTOCOL that can be claimed.
375
+ function tokensClaimableFor(uint256[] memory tokenIds)
376
+ public
377
+ view
378
+ returns (uint256 defifaTokenAmount, uint256 baseProtocolTokenAmount)
379
+ {
380
+ // If the game isn't complete, we do not have any tokens to claim.
381
+ if (gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) != DefifaGamePhase.COMPLETE) return (0, 0);
382
+
383
+ // slither-disable-next-line unused-return
384
+ return DefifaHookLib.computeTokensClaim({
385
+ tokenIds: tokenIds,
386
+ _store: store,
387
+ hook: address(this),
388
+ totalMintCost: _totalMintCost,
389
+ defifaBalance: defifaToken.balanceOf(address(this)),
390
+ baseProtocolBalance: baseProtocolToken.balanceOf(address(this))
391
+ });
392
+ }
393
+
394
+ /// @notice Indicates if this contract adheres to the specified interface.
395
+ /// @dev See {IERC165-supportsInterface}.
396
+ /// @param interfaceId The ID of the interface to check for adherence to.
397
+ function supportsInterface(bytes4 interfaceId) public view override(JB721Hook, IERC165) returns (bool) {
398
+ return interfaceId == type(IDefifaHook).interfaceId || super.supportsInterface(interfaceId);
399
+ }
400
+
401
+ //*********************************************************************//
402
+ // -------------------------- constructor ---------------------------- //
403
+ //*********************************************************************//
404
+
405
+ /// @dev The initial owner is msg.sender; ownership is transferred to the governor after initialization.
406
+ constructor(
407
+ IJBDirectory _directory,
408
+ IERC20 _defifaToken,
409
+ IERC20 _baseProtocolToken
410
+ )
411
+ JB721Hook(_directory)
412
+ Ownable(msg.sender)
413
+ {
414
+ codeOrigin = address(this);
415
+ defifaToken = _defifaToken;
416
+ baseProtocolToken = _baseProtocolToken;
417
+ }
418
+
419
+ //*********************************************************************//
420
+ // ---------------------- external transactions ---------------------- //
421
+ //*********************************************************************//
422
+
423
+ /// @notice Mints one or more NFTs to the `context.beneficiary` upon payment if conditions are met.
424
+ /// @dev Reverts if the calling contract is not one of the project's terminals.
425
+ /// @param context The payment context passed in by the terminal.
426
+ // slither-disable-next-line locked-ether
427
+ function afterPayRecordedWith(JBAfterPayRecordedContext calldata context)
428
+ external
429
+ payable
430
+ virtual
431
+ override(IJBPayHook, JB721Hook)
432
+ {
433
+ uint256 projectId = PROJECT_ID;
434
+
435
+ // Make sure the caller is a terminal of the project, and that the call is being made on behalf of an
436
+ // interaction with the correct project.
437
+ if (
438
+ msg.value != 0 || !DIRECTORY.isTerminalOf({projectId: projectId, terminal: IJBTerminal(msg.sender)})
439
+ || context.projectId != projectId
440
+ ) revert JB721Hook_InvalidPay();
441
+
442
+ // Process the payment.
443
+ _processPayment(context);
444
+ }
445
+
446
+ //*********************************************************************//
447
+ // ----------------------- public transactions ----------------------- //
448
+ //*********************************************************************//
449
+
450
+ /// @notice Initialize a clone of this contract.
451
+ /// @param _gameId The ID of the project this contract's functionality applies to.
452
+ /// @param _name The name of the token.
453
+ /// @param _symbol The symbol that the token should be represented by.
454
+ /// @param _rulesets A contract storing all ruleset configurations.
455
+ /// @param _baseUri A URI to use as a base for full token URIs.
456
+ /// @param _tokenUriResolver A contract responsible for resolving the token URI for each token ID.
457
+ /// @param _contractUri A URI where contract metadata can be found.
458
+ /// @param _tiers The tiers to set.
459
+ /// @param _currency The currency that the tier contribution floors are denoted in.
460
+ /// @param _store A contract that stores the NFT's data.
461
+ /// @param _gamePhaseReporter The contract that reports the game phase.
462
+ /// @param _gamePotReporter The contract that reports the game's pot.
463
+ /// @param _defaultAttestationDelegate The address that'll be set as the attestation delegate by default.
464
+ /// @param _tierNames The names of each tier.
465
+ function initialize(
466
+ uint256 _gameId,
467
+ string memory _name,
468
+ string memory _symbol,
469
+ IJBRulesets _rulesets,
470
+ string memory _baseUri,
471
+ IJB721TokenUriResolver _tokenUriResolver,
472
+ string memory _contractUri,
473
+ JB721TierConfig[] memory _tiers,
474
+ uint48 _currency,
475
+ IJB721TiersHookStore _store,
476
+ IDefifaGamePhaseReporter _gamePhaseReporter,
477
+ IDefifaGamePotReporter _gamePotReporter,
478
+ address _defaultAttestationDelegate,
479
+ string[] memory _tierNames
480
+ )
481
+ public
482
+ override
483
+ {
484
+ // Make the original un-initializable.
485
+ if (address(this) == codeOrigin) revert();
486
+
487
+ // Stop re-initialization.
488
+ if (address(store) != address(0)) revert();
489
+
490
+ // Initialize the superclass.
491
+ _initialize({projectId: _gameId, name: _name, symbol: _symbol});
492
+
493
+ // Store stuff.
494
+ rulesets = _rulesets;
495
+ store = _store;
496
+ pricingCurrency = _currency;
497
+ gamePhaseReporter = _gamePhaseReporter;
498
+ gamePotReporter = _gamePotReporter;
499
+ defaultAttestationDelegate = _defaultAttestationDelegate;
500
+
501
+ // Store the base URI if provided.
502
+ if (bytes(_baseUri).length != 0) baseURI = _baseUri;
503
+
504
+ // Set the contract URI if provided.
505
+ if (bytes(_contractUri).length != 0) contractURI = _contractUri;
506
+
507
+ // Set the token URI resolver if provided.
508
+ if (_tokenUriResolver != IJB721TokenUriResolver(address(0))) {
509
+ _store.recordSetTokenUriResolver(_tokenUriResolver);
510
+ }
511
+
512
+ // Record the provided tiers.
513
+ // slither-disable-next-line unused-return
514
+ _store.recordAddTiers(_tiers);
515
+
516
+ // Keep a reference to the number of tier names.
517
+ uint256 _numberOfTierNames = _tierNames.length;
518
+
519
+ // Set the name for each tier.
520
+ for (uint256 _i; _i < _numberOfTierNames;) {
521
+ // Set the tier name.
522
+ _tierNameOf[_i + 1] = _tierNames[_i];
523
+
524
+ unchecked {
525
+ ++_i;
526
+ }
527
+ }
528
+
529
+ // Transfer ownership to the initializer.
530
+ _transferOwnership(msg.sender);
531
+ }
532
+
533
+ /// @notice Mint reserved tokens within the tier for the provided value.
534
+ /// @param tierId The ID of the tier to mint within.
535
+ /// @param count The number of reserved tokens to mint.
536
+ function mintReservesFor(uint256 tierId, uint256 count) public override {
537
+ // Minting reserves must not be paused.
538
+ if (JB721TiersRulesetMetadataResolver.mintPendingReservesPaused(
539
+ (JBRulesetMetadataResolver.metadata(rulesets.currentOf(PROJECT_ID)))
540
+ )) revert DefifaHook_ReservedTokenMintingPaused();
541
+
542
+ // Keep a reference to the reserved token beneficiary.
543
+ address _reservedTokenBeneficiary = store.reserveBeneficiaryOf({hook: address(this), tierId: tierId});
544
+
545
+ // Get a reference to the old delegate.
546
+ address _oldDelegate = _tierDelegation[_reservedTokenBeneficiary][tierId];
547
+
548
+ // Set the delegate as the beneficiary if the beneficiary hasn't already set a delegate.
549
+ if (_oldDelegate == address(0)) {
550
+ _delegateTier({
551
+ _account: _reservedTokenBeneficiary,
552
+ _delegatee: defaultAttestationDelegate != address(0)
553
+ ? defaultAttestationDelegate
554
+ : _reservedTokenBeneficiary,
555
+ _tierId: tierId
556
+ });
557
+ }
558
+
559
+ // Record the minted reserves for the tier.
560
+ uint256[] memory _tokenIds = store.recordMintReservesFor({tierId: tierId, count: count});
561
+
562
+ // Keep a reference to the token ID being iterated on.
563
+ uint256 _tokenId;
564
+
565
+ // Fetch the tier details (needed for votingUnits below).
566
+ JB721Tier memory _tier = store.tierOf({hook: address(this), id: tierId, includeResolvedUri: false});
567
+
568
+ // Increment _totalMintCost so reserved recipients can claim their share of fee tokens ($DEFIFA/$NANA).
569
+ _totalMintCost += _tier.price * count;
570
+
571
+ for (uint256 _i; _i < count;) {
572
+ // Set the token ID.
573
+ _tokenId = _tokenIds[_i];
574
+
575
+ // Mint the token.
576
+ _mint(_reservedTokenBeneficiary, _tokenId);
577
+
578
+ emit MintReservedToken(_tokenId, tierId, _reservedTokenBeneficiary, msg.sender);
579
+
580
+ unchecked {
581
+ ++_i;
582
+ }
583
+ }
584
+
585
+ // Transfer the attestation units to the delegate.
586
+ _transferTierAttestationUnits({
587
+ _from: address(0),
588
+ _to: _reservedTokenBeneficiary,
589
+ _tierId: tierId,
590
+ _amount: _tier.votingUnits * _tokenIds.length
591
+ });
592
+ }
593
+
594
+ //*********************************************************************//
595
+ // ---------------------- external transactions ---------------------- //
596
+ //*********************************************************************//
597
+
598
+ /// @notice Stores the cashOut weights that should be used in the end game phase.
599
+ /// @dev Only this contract's owner can set tier cashOut weights.
600
+ /// @param tierWeights The tier weights to set.
601
+ function setTierCashOutWeightsTo(DefifaTierCashOutWeight[] memory tierWeights) external override onlyOwner {
602
+ // Get a reference to the game phase.
603
+ DefifaGamePhase _gamePhase = gamePhaseReporter.currentGamePhaseOf(PROJECT_ID);
604
+
605
+ // Make sure the game has ended.
606
+ if (_gamePhase != DefifaGamePhase.SCORING) {
607
+ revert DefifaHook_GameIsntScoringYet();
608
+ }
609
+
610
+ // Make sure the cashOut weights haven't already been set.
611
+ if (cashOutWeightIsSet) revert DefifaHook_CashoutWeightsAlreadySet();
612
+
613
+ // Make sure the game is not in no contest.
614
+ if (_gamePhase == DefifaGamePhase.NO_CONTEST) {
615
+ revert DefifaHook_NoContest();
616
+ }
617
+
618
+ // Validate weights and build the array. Reverts on invalid input.
619
+ _tierCashOutWeights =
620
+ DefifaHookLib.validateAndBuildWeights({tierWeights: tierWeights, _store: store, hook: address(this)});
621
+
622
+ // Mark the cashOut weight as set.
623
+ cashOutWeightIsSet = true;
624
+
625
+ emit TierCashOutWeightsSet(tierWeights, msg.sender);
626
+ }
627
+
628
+ /// @notice Burns the specified NFTs upon token holder cash out, reclaiming funds from the project's balance for
629
+ /// `context.beneficiary`. Part of `IJBCashOutHook`.
630
+ /// @dev Reverts if the calling contract is not one of the project's terminals.
631
+ /// @param context The cash out context passed in by the terminal.
632
+ // slither-disable-next-line locked-ether,reentrancy-no-eth
633
+ function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context)
634
+ external
635
+ payable
636
+ virtual
637
+ override(IJBCashOutHook, JB721Hook)
638
+ {
639
+ // Make sure the caller is a terminal of the project, and that the call is being made on behalf of an
640
+ // interaction with the correct project.
641
+ if (
642
+ msg.value != 0 || !DIRECTORY.isTerminalOf({projectId: PROJECT_ID, terminal: IJBTerminal(msg.sender)})
643
+ || context.projectId != PROJECT_ID
644
+ ) revert JB721Hook_InvalidCashOut();
645
+
646
+ // Fetch the cash out hook metadata using the corresponding metadata ID.
647
+ (bool metadataExists, bytes memory metadata) = JBMetadataResolver.getDataFor(
648
+ JBMetadataResolver.getId("cashOut", METADATA_ID_TARGET), context.cashOutMetadata
649
+ );
650
+
651
+ if (!metadataExists) {
652
+ revert();
653
+ }
654
+
655
+ // Decode the CashOut metadata.
656
+ (uint256[] memory _decodedTokenIds) = abi.decode(metadata, (uint256[]));
657
+
658
+ // Get a reference to the number of token IDs being checked.
659
+ uint256 _numberOfTokenIds = _decodedTokenIds.length;
660
+
661
+ // Keep a reference to the token ID being iterated on.
662
+ uint256 _tokenId;
663
+
664
+ // Keep track of whether the cashOut is happening during the complete phase.
665
+ bool _isComplete = gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) == DefifaGamePhase.COMPLETE;
666
+
667
+ // Iterate through all tokens, burning them if the owner is correct.
668
+ for (uint256 _i; _i < _numberOfTokenIds; _i++) {
669
+ // Set the token's ID.
670
+ _tokenId = _decodedTokenIds[_i];
671
+
672
+ // Make sure the token's owner is correct.
673
+ address _tokenOwner = _ownerOf(_tokenId);
674
+ if (_tokenOwner != context.holder) {
675
+ revert DefifaHook_Unauthorized(_tokenId, _tokenOwner, context.holder);
676
+ }
677
+
678
+ // Burn the token.
679
+ _burn(_tokenId);
680
+
681
+ if (_isComplete) {
682
+ unchecked {
683
+ ++tokensRedeemedFrom[store.tierIdOfToken(_tokenId)];
684
+ }
685
+ }
686
+ }
687
+
688
+ // Call the hook.
689
+ _didBurn(_decodedTokenIds);
690
+
691
+ // Decode the metadata passed by the hook.
692
+ (uint256 _cumulativeMintPrice) = abi.decode(context.hookMetadata, (uint256));
693
+
694
+ // Increment the amount redeemed if this is the complete phase.
695
+ bool _beneficiaryReceivedTokens;
696
+ if (_isComplete) {
697
+ amountRedeemed += context.reclaimedAmount.value;
698
+
699
+ // Claim the $DEFIFA and $NANA tokens for the user.
700
+ _beneficiaryReceivedTokens = _claimTokensFor({
701
+ _beneficiary: context.holder, shareToBeneficiary: _cumulativeMintPrice, outOfTotal: _totalMintCost
702
+ });
703
+ }
704
+
705
+ // If there's nothing being claimed and we did not distribute fee tokens, revert to prevent burning for nothing.
706
+ if (context.reclaimedAmount.value == 0 && !_beneficiaryReceivedTokens) revert DefifaHook_NothingToClaim();
707
+
708
+ // Decrement the paid mint cost by the cumulative mint price of the tokens being burned.
709
+ _totalMintCost -= _cumulativeMintPrice;
710
+ }
711
+
712
+ /// @notice Mint reserved tokens within the tier for the provided value.
713
+ /// @param mintReservesForTiersData Contains information about how many reserved tokens to mint for each tier.
714
+ function mintReservesFor(JB721TiersMintReservesConfig[] calldata mintReservesForTiersData) external override {
715
+ // Keep a reference to the number of tiers there are to mint reserves for.
716
+ uint256 _numberOfTiers = mintReservesForTiersData.length;
717
+
718
+ for (uint256 _i; _i < _numberOfTiers;) {
719
+ // Get a reference to the data being iterated on.
720
+ JB721TiersMintReservesConfig memory _data = mintReservesForTiersData[_i];
721
+
722
+ // Mint for the tier.
723
+ mintReservesFor(_data.tierId, _data.count);
724
+
725
+ unchecked {
726
+ ++_i;
727
+ }
728
+ }
729
+ }
730
+
731
+ /// @notice Delegate attestations.
732
+ /// @param delegations An array of tiers to set delegates for.
733
+ function setTierDelegatesTo(DefifaDelegation[] memory delegations) external virtual override {
734
+ // Make sure the current game phase is the minting phase.
735
+ if (gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) != DefifaGamePhase.MINT) {
736
+ revert DefifaHook_DelegateChangesUnavailableInThisPhase();
737
+ }
738
+
739
+ // Keep a reference to the number of tier delegates.
740
+ uint256 _numberOfTierDelegates = delegations.length;
741
+
742
+ // Keep a reference to the data being iterated on.
743
+ DefifaDelegation memory _data;
744
+
745
+ for (uint256 _i; _i < _numberOfTierDelegates;) {
746
+ // Reference the data being iterated on.
747
+ _data = delegations[_i];
748
+
749
+ // Make sure a delegate is specified.
750
+ if (_data.delegatee == address(0)) revert DefifaHook_DelegateAddressZero();
751
+
752
+ _delegateTier({_account: msg.sender, _delegatee: _data.delegatee, _tierId: _data.tierId});
753
+
754
+ unchecked {
755
+ ++_i;
756
+ }
757
+ }
758
+ }
759
+
760
+ /// @notice Delegate attestations.
761
+ /// @param delegatee The account to delegate tier attestation units to.
762
+ /// @param tierId The ID of the tier to delegate attestation units for.
763
+ function setTierDelegateTo(address delegatee, uint256 tierId) public virtual override {
764
+ // Make sure the current game phase is the minting phase.
765
+ if (gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) != DefifaGamePhase.MINT) {
766
+ revert DefifaHook_DelegateChangesUnavailableInThisPhase();
767
+ }
768
+
769
+ _delegateTier({_account: msg.sender, _delegatee: delegatee, _tierId: tierId});
770
+ }
771
+
772
+ //*********************************************************************//
773
+ // ------------------------ internal functions ----------------------- //
774
+ //*********************************************************************//
775
+
776
+ /// @notice Process an incoming payment.
777
+ /// @param context The Juicebox standard project payment data.
778
+ function _processPayment(JBAfterPayRecordedContext calldata context) internal override {
779
+ // Make sure the game is being played in the correct currency.
780
+ if (context.amount.currency != pricingCurrency) revert DefifaHook_WrongCurrency();
781
+
782
+ // Resolve the metadata.
783
+ (bool found, bytes memory metadata) =
784
+ JBMetadataResolver.getDataFor(JBMetadataResolver.getId("pay", codeOrigin), context.payerMetadata);
785
+
786
+ if (!found) revert DefifaHook_NothingToMint();
787
+
788
+ // Decode the metadata.
789
+ (address _attestationDelegate, uint16[] memory _tierIdsToMint) = abi.decode(metadata, (address, uint16[]));
790
+
791
+ // Set the payer as the attestation delegate by default.
792
+ if (_attestationDelegate == address(0)) {
793
+ _attestationDelegate = defaultAttestationDelegate != address(0) ? defaultAttestationDelegate : context.payer;
794
+ }
795
+
796
+ // Make sure something is being minted.
797
+ if (_tierIdsToMint.length == 0) revert DefifaHook_NothingToMint();
798
+
799
+ // Compute attestation units per unique tier (validates ascending order, reverts on bad order).
800
+ (uint256[] memory _tierIds, uint256[] memory _attestationAmounts, uint256 _uniqueTierCount) =
801
+ DefifaHookLib.computeAttestationUnits({_tierIdsToMint: _tierIdsToMint, _store: store, hook: address(this)});
802
+
803
+ // Apply attestation units for each unique tier.
804
+ for (uint256 _i; _i < _uniqueTierCount;) {
805
+ uint256 _tierId = _tierIds[_i];
806
+
807
+ // Get a reference to the old delegate.
808
+ address _oldDelegate = _tierDelegation[context.payer][_tierId];
809
+
810
+ // If there's either a new delegate or old delegate, set delegation and transfer units.
811
+ if (_attestationDelegate != address(0) || _oldDelegate != address(0)) {
812
+ // Switch delegates if needed.
813
+ if (_attestationDelegate != address(0) && _attestationDelegate != _oldDelegate) {
814
+ _delegateTier({_account: context.payer, _delegatee: _attestationDelegate, _tierId: _tierId});
815
+ }
816
+
817
+ // Transfer the attestation units.
818
+ _transferTierAttestationUnits({
819
+ _from: address(0), _to: context.payer, _tierId: _tierId, _amount: _attestationAmounts[_i]
820
+ });
821
+ }
822
+
823
+ unchecked {
824
+ ++_i;
825
+ }
826
+ }
827
+
828
+ // Mint tiers if they were specified.
829
+ uint256 _leftoverAmount =
830
+ _mintAll({_amount: context.amount.value, _mintTierIds: _tierIdsToMint, _beneficiary: context.beneficiary});
831
+
832
+ // Make sure the buyer isn't overspending.
833
+ if (_leftoverAmount != 0) revert DefifaHook_Overspending();
834
+ }
835
+
836
+ /// @notice Gets the amount of attestation units an address has for a particular tier.
837
+ /// @param _account The account to get attestation units for.
838
+ /// @param _tierId The ID of the tier to get attestation units for.
839
+ /// @return The attestation units.
840
+ function _getTierAttestationUnits(address _account, uint256 _tierId) internal view virtual returns (uint256) {
841
+ return store.tierVotingUnitsOf({hook: address(this), account: _account, tierId: _tierId});
842
+ }
843
+
844
+ /// @notice Delegate all attestation units for the specified tier.
845
+ /// @param _account The account delegating tier attestation units.
846
+ /// @param _delegatee The account to delegate tier attestation units to.
847
+ /// @param _tierId The ID of the tier for which attestation units are being transferred.
848
+ function _delegateTier(address _account, address _delegatee, uint256 _tierId) internal virtual {
849
+ // Get the current delegatee
850
+ address _oldDelegate = _tierDelegation[_account][_tierId];
851
+
852
+ // Store the new delegatee
853
+ _tierDelegation[_account][_tierId] = _delegatee;
854
+
855
+ emit DelegateChanged(_account, _oldDelegate, _delegatee);
856
+
857
+ // Move the attestations.
858
+ _moveTierDelegateAttestations({
859
+ _from: _oldDelegate,
860
+ _to: _delegatee,
861
+ _tierId: _tierId,
862
+ _amount: _getTierAttestationUnits({_account: _account, _tierId: _tierId})
863
+ });
864
+ }
865
+
866
+ /// @notice Transfers, mints, or burns tier attestation units. To register a mint, `_from` should be zero. To
867
+ /// register a burn, `_to` should be zero. Total supply of attestation units will be adjusted with mints and burns.
868
+ /// @param _from The account to transfer tier attestation units from.
869
+ /// @param _to The account to transfer tier attestation units to.
870
+ /// @param _tierId The ID of the tier for which attestation units are being transferred.
871
+ /// @param _amount The amount of attestation units to delegate.
872
+ function _transferTierAttestationUnits(
873
+ address _from,
874
+ address _to,
875
+ uint256 _tierId,
876
+ uint256 _amount
877
+ )
878
+ internal
879
+ virtual
880
+ {
881
+ if (_from == address(0) || _to == address(0)) {
882
+ // Get the current total for the tier.
883
+ uint208 _current = _totalTierCheckpoints[_tierId].latest();
884
+
885
+ // If minting, add to the total tier checkpoints.
886
+ if (_from == address(0)) {
887
+ // slither-disable-next-line unused-return
888
+ _totalTierCheckpoints[_tierId].push(uint48(block.timestamp), _current + uint208(_amount));
889
+ }
890
+
891
+ // If burning, subtract from the total tier checkpoints.
892
+ if (_to == address(0)) {
893
+ // slither-disable-next-line unused-return
894
+ _totalTierCheckpoints[_tierId].push(uint48(block.timestamp), _current - uint208(_amount));
895
+ }
896
+ }
897
+
898
+ // Move delegated attestations.
899
+ _moveTierDelegateAttestations({
900
+ _from: _tierDelegation[_from][_tierId],
901
+ _to: _tierDelegation[_to][_tierId],
902
+ _tierId: _tierId,
903
+ _amount: _amount
904
+ });
905
+ }
906
+
907
+ /// @notice Moves delegated tier attestations from one delegate to another.
908
+ /// @param _from The account to transfer tier attestation units from.
909
+ /// @param _to The account to transfer tier attestation units to.
910
+ /// @param _tierId The ID of the tier for which attestation units are being transferred.
911
+ /// @param _amount The amount of attestation units to delegate.
912
+ function _moveTierDelegateAttestations(address _from, address _to, uint256 _tierId, uint256 _amount) internal {
913
+ // Nothing to do if moving to the same account, or no amount is being moved.
914
+ if (_from == _to || _amount == 0) return;
915
+
916
+ // If not moving from the zero address, update the checkpoints to subtract the amount.
917
+ if (_from != address(0)) {
918
+ // Get the current amount for the sending delegate.
919
+ uint208 _current = _delegateTierCheckpoints[_from][_tierId].latest();
920
+ // Set the new amount for the sending delegate.
921
+ (uint256 _oldValue, uint256 _newValue) =
922
+ _delegateTierCheckpoints[_from][_tierId].push(uint48(block.timestamp), _current - uint208(_amount));
923
+ emit TierDelegateAttestationsChanged(_from, _tierId, _oldValue, _newValue, msg.sender);
924
+ }
925
+
926
+ // If not moving to the zero address, update the checkpoints to add the amount.
927
+ if (_to != address(0)) {
928
+ // Get the current amount for the receiving delegate.
929
+ uint208 _current = _delegateTierCheckpoints[_to][_tierId].latest();
930
+ // Set the new amount for the receiving delegate.
931
+ (uint256 _oldValue, uint256 _newValue) =
932
+ _delegateTierCheckpoints[_to][_tierId].push(uint48(block.timestamp), _current + uint208(_amount));
933
+ emit TierDelegateAttestationsChanged(_to, _tierId, _oldValue, _newValue, msg.sender);
934
+ }
935
+ }
936
+
937
+ /// @notice A function that will run when tokens are burned via cashOut.
938
+ /// @param _tokenIds The IDs of the tokens that were burned.
939
+ function _didBurn(uint256[] memory _tokenIds) internal virtual override {
940
+ // Add to burned counter.
941
+ store.recordBurn(_tokenIds);
942
+ }
943
+
944
+ /// @notice Mints a token in all provided tiers.
945
+ /// @param _amount The amount to base the mints on. All mints' price floors must fit in this amount.
946
+ /// @param _mintTierIds An array of tier IDs that are intended to be minted.
947
+ /// @param _beneficiary The address to mint for.
948
+ /// @return leftoverAmount The amount leftover after the mint.
949
+ function _mintAll(
950
+ uint256 _amount,
951
+ uint16[] memory _mintTierIds,
952
+ address _beneficiary
953
+ )
954
+ internal
955
+ returns (uint256 leftoverAmount)
956
+ {
957
+ // Keep a reference to the token ID.
958
+ uint256[] memory _tokenIds;
959
+
960
+ // Record the mint. The returned token IDs correspond to the tiers passed in.
961
+ (_tokenIds, leftoverAmount) = store.recordMint({
962
+ amount: _amount,
963
+ tierIds: _mintTierIds,
964
+ isOwnerMint: false // Not a manual mint
965
+ });
966
+
967
+ // Get a reference to the number of mints.
968
+ uint256 _mintsLength = _tokenIds.length;
969
+
970
+ // Keep a reference to the token ID being iterated on.
971
+ uint256 _tokenId;
972
+
973
+ // Increment the paid mint cost.
974
+ _totalMintCost += _amount;
975
+
976
+ // Loop through each token ID and mint.
977
+ for (uint256 _i; _i < _mintsLength;) {
978
+ // Get a reference to the tier being iterated on.
979
+ _tokenId = _tokenIds[_i];
980
+
981
+ // Mint the tokens.
982
+ _mint(_beneficiary, _tokenId);
983
+
984
+ emit Mint(_tokenId, _mintTierIds[_i], _beneficiary, _amount, msg.sender);
985
+
986
+ unchecked {
987
+ ++_i;
988
+ }
989
+ }
990
+ }
991
+
992
+ /// @notice Claims the defifa and base protocol tokens for a beneficiary.
993
+ /// @param _beneficiary The address to claim tokens for.
994
+ /// @param shareToBeneficiary The share relative to the `outOfTotal` to send the user.
995
+ /// @param outOfTotal The total share that the `shareToBeneficiary` is relative to.
996
+ /// @return beneficiaryReceivedTokens A flag indicating if the beneficiary received any tokens.
997
+ function _claimTokensFor(
998
+ address _beneficiary,
999
+ uint256 shareToBeneficiary,
1000
+ uint256 outOfTotal
1001
+ )
1002
+ internal
1003
+ returns (bool beneficiaryReceivedTokens)
1004
+ {
1005
+ return DefifaHookLib.claimTokensFor({
1006
+ _beneficiary: _beneficiary,
1007
+ shareToBeneficiary: shareToBeneficiary,
1008
+ outOfTotal: outOfTotal,
1009
+ _defifaToken: defifaToken,
1010
+ _baseProtocolToken: baseProtocolToken
1011
+ });
1012
+ }
1013
+
1014
+ /// @notice Before transferring an NFT, register its first owner (if necessary).
1015
+ /// @param to The address the NFT is being transferred to.
1016
+ /// @param tokenId The token ID of the NFT being transferred.
1017
+ function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address from) {
1018
+ // Get a reference to the tier.
1019
+ // slither-disable-next-line calls-loop
1020
+ JB721Tier memory tier = store.tierOfTokenId({hook: address(this), tokenId: tokenId, includeResolvedUri: false});
1021
+
1022
+ // Record the transfers and keep a reference to where the token is coming from.
1023
+ from = super._update(to, tokenId, auth);
1024
+
1025
+ // Transfers must not be paused (when not minting or burning).
1026
+ if (from != address(0)) {
1027
+ // If transfers are pausable, check if they're paused.
1028
+ if (tier.transfersPausable) {
1029
+ // Get a reference to the project's current ruleset.
1030
+ JBRuleset memory ruleset = rulesets.currentOf(PROJECT_ID);
1031
+
1032
+ // If transfers are paused and the NFT isn't being transferred to the zero address, revert.
1033
+ if (
1034
+ to != address(0)
1035
+ && JB721TiersRulesetMetadataResolver.transfersPaused(
1036
+ (JBRulesetMetadataResolver.metadata(ruleset))
1037
+ )
1038
+ ) revert DefifaHook_TransfersPaused();
1039
+ }
1040
+
1041
+ // If the token isn't already associated with a first owner, store the sender as the first owner.
1042
+ // slither-disable-next-line calls-loop
1043
+ if (_firstOwnerOf[tokenId] == address(0)) _firstOwnerOf[tokenId] = from;
1044
+ }
1045
+
1046
+ // Record the transfer.
1047
+ // slither-disable-next-line reentrency-events,calls-loop
1048
+ store.recordTransferForTier({tierId: tier.id, from: from, to: to});
1049
+
1050
+ // Dont transfer on mint since the delegation will be transferred more efficiently in _processPayment.
1051
+ if (from == address(0)) return from;
1052
+
1053
+ // Transfer the attestation units.
1054
+ _transferTierAttestationUnits({_from: from, _to: to, _tierId: tier.id, _amount: tier.votingUnits});
1055
+ }
1056
+ }