@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,228 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
6
+ import {IJB721Hook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Hook.sol";
7
+ import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
8
+ import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
9
+ import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
10
+ import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
11
+ import {DefifaTierCashOutWeight} from "./../structs/DefifaTierCashOutWeight.sol";
12
+ import {DefifaDelegation} from "./../structs/DefifaDelegation.sol";
13
+ import {IDefifaGamePhaseReporter} from "./IDefifaGamePhaseReporter.sol";
14
+ import {IDefifaGamePotReporter} from "./IDefifaGamePotReporter.sol";
15
+
16
+ /// @notice The hook interface for Defifa games, extending the 721 hook with game-specific attestation delegation,
17
+ /// scorecard-based cash out weights, and token claiming.
18
+ interface IDefifaHook is IJB721Hook {
19
+ event Mint(
20
+ uint256 indexed tokenId,
21
+ uint256 indexed tierId,
22
+ address indexed beneficiary,
23
+ uint256 totalAmountContributed,
24
+ address caller
25
+ );
26
+
27
+ event MintReservedToken(
28
+ uint256 indexed tokenId, uint256 indexed tierId, address indexed beneficiary, address caller
29
+ );
30
+
31
+ event TierDelegateAttestationsChanged(
32
+ address indexed delegate, uint256 indexed tierId, uint256 previousBalance, uint256 newBalance, address caller
33
+ );
34
+
35
+ event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
36
+
37
+ event ClaimedTokens(
38
+ address indexed beneficiary, uint256 defifaTokenAmount, uint256 baseProtocolTokenAmount, address caller
39
+ );
40
+
41
+ event TierCashOutWeightsSet(DefifaTierCashOutWeight[] tierWeights, address caller);
42
+
43
+ /// @notice The total cash out weight used to normalize tier cash out weights.
44
+ /// @return The total cash out weight.
45
+ function TOTAL_CASHOUT_WEIGHT() external view returns (uint256);
46
+
47
+ /// @notice The Defifa project token used for token allocations.
48
+ /// @return The Defifa ERC-20 token.
49
+ function defifaToken() external view returns (IERC20);
50
+
51
+ /// @notice The base protocol token used for token allocations.
52
+ /// @return The base protocol ERC-20 token.
53
+ function baseProtocolToken() external view returns (IERC20);
54
+
55
+ /// @notice The cash out weight of a specific token based on its tier's scorecard weight.
56
+ /// @param tokenId The token ID to look up.
57
+ /// @return The cash out weight.
58
+ function cashOutWeightOf(uint256 tokenId) external view returns (uint256);
59
+
60
+ /// @notice The cash out weights for all tiers (up to 128).
61
+ /// @return The array of tier cash out weights.
62
+ function tierCashOutWeights() external view returns (uint256[128] memory);
63
+
64
+ /// @notice The address of the code origin contract used as an implementation for clones.
65
+ /// @return The code origin address.
66
+ function codeOrigin() external view returns (address);
67
+
68
+ /// @notice Whether the cash out weights have been set by the game's governor.
69
+ /// @return True if cash out weights are set.
70
+ function cashOutWeightIsSet() external view returns (bool);
71
+
72
+ /// @notice The current supply of a specific tier.
73
+ /// @param tierId The ID of the tier.
74
+ /// @return The current supply.
75
+ function currentSupplyOfTier(uint256 tierId) external view returns (uint256);
76
+
77
+ /// @notice The 721 tiers hook store used by this hook.
78
+ /// @return The store contract.
79
+ function store() external view returns (IJB721TiersHookStore);
80
+
81
+ /// @notice The rulesets contract used by this hook.
82
+ /// @return The rulesets contract.
83
+ function rulesets() external view returns (IJBRulesets);
84
+
85
+ /// @notice The game phase reporter for this hook.
86
+ /// @return The game phase reporter contract.
87
+ function gamePhaseReporter() external view returns (IDefifaGamePhaseReporter);
88
+
89
+ /// @notice The game pot reporter for this hook.
90
+ /// @return The game pot reporter contract.
91
+ function gamePotReporter() external view returns (IDefifaGamePotReporter);
92
+
93
+ /// @notice The total amount redeemed from this game (refunds not counted).
94
+ /// @return The total redeemed amount.
95
+ function amountRedeemed() external view returns (uint256);
96
+
97
+ /// @notice The token allocations (Defifa token amount, base protocol token amount).
98
+ /// @return The Defifa token allocation and the base protocol token allocation.
99
+ function tokenAllocations() external view returns (uint256, uint256);
100
+
101
+ /// @notice The name of a specific tier.
102
+ /// @param tierId The ID of the tier.
103
+ /// @return The tier name.
104
+ function tierNameOf(uint256 tierId) external view returns (string memory);
105
+
106
+ /// @notice The number of tokens redeemed from a specific tier.
107
+ /// @param tierId The tier ID.
108
+ /// @return The number of tokens redeemed.
109
+ function tokensRedeemedFrom(uint256 tierId) external view returns (uint256);
110
+
111
+ /// @notice The pricing currency used by this hook.
112
+ /// @return The currency identifier.
113
+ function pricingCurrency() external view returns (uint256);
114
+
115
+ /// @notice The first owner of a given token ID.
116
+ /// @param tokenId The token ID.
117
+ /// @return The address of the first owner.
118
+ function firstOwnerOf(uint256 tokenId) external view returns (address);
119
+
120
+ /// @notice The base URI for token metadata.
121
+ /// @return The base URI string.
122
+ function baseURI() external view returns (string memory);
123
+
124
+ /// @notice The contract-level metadata URI.
125
+ /// @return The contract URI string.
126
+ function contractURI() external view returns (string memory);
127
+
128
+ /// @notice The default attestation delegate for new token holders.
129
+ /// @return The default delegate address.
130
+ function defaultAttestationDelegate() external view returns (address);
131
+
132
+ /// @notice Get the delegate for a specific account and tier.
133
+ /// @param account The account to look up.
134
+ /// @param tier The tier ID.
135
+ /// @return The delegate address.
136
+ function getTierDelegateOf(address account, uint256 tier) external view returns (address);
137
+
138
+ /// @notice Get the attestation units for a specific account and tier.
139
+ /// @param account The account to look up.
140
+ /// @param tier The tier ID.
141
+ /// @return The number of attestation units.
142
+ function getTierAttestationUnitsOf(address account, uint256 tier) external view returns (uint256);
143
+
144
+ /// @notice Get the attestation units for a specific account and tier at a past timestamp.
145
+ /// @param account The account to look up.
146
+ /// @param tier The tier ID.
147
+ /// @param timestamp The historical timestamp.
148
+ /// @return The number of attestation units at that time.
149
+ function getPastTierAttestationUnitsOf(
150
+ address account,
151
+ uint256 tier,
152
+ uint48 timestamp
153
+ )
154
+ external
155
+ view
156
+ returns (uint256);
157
+
158
+ /// @notice Get the total attestation units for a specific tier.
159
+ /// @param tier The tier ID.
160
+ /// @return The total attestation units.
161
+ function getTierTotalAttestationUnitsOf(uint256 tier) external view returns (uint256);
162
+
163
+ /// @notice Get the total attestation units for a tier at a past timestamp.
164
+ /// @param tier The tier ID.
165
+ /// @param timestamp The historical timestamp.
166
+ /// @return The total attestation units at that time.
167
+ function getPastTierTotalAttestationUnitsOf(uint256 tier, uint48 timestamp) external view returns (uint256);
168
+
169
+ /// @notice Get the claimable Defifa and base protocol tokens for a set of token IDs.
170
+ /// @param tokenIds The token IDs to check.
171
+ /// @return The claimable Defifa token amount and base protocol token amount.
172
+ function tokensClaimableFor(uint256[] memory tokenIds) external view returns (uint256, uint256);
173
+
174
+ /// @notice Set the attestation delegate for a specific tier.
175
+ /// @param delegatee The address to delegate to.
176
+ /// @param tierId The tier ID.
177
+ function setTierDelegateTo(address delegatee, uint256 tierId) external;
178
+
179
+ /// @notice Set attestation delegates for multiple tiers at once.
180
+ /// @param delegations The delegation assignments.
181
+ function setTierDelegatesTo(DefifaDelegation[] memory delegations) external;
182
+
183
+ /// @notice Set the cash out weights for tiers. Only callable by the game's governor (owner).
184
+ /// @param tierWeights The tier cash out weights to set.
185
+ function setTierCashOutWeightsTo(DefifaTierCashOutWeight[] memory tierWeights) external;
186
+
187
+ /// @notice Mint reserved tokens for multiple tiers.
188
+ /// @param mintReservesForTiersData The configuration for which tiers to mint reserves for.
189
+ function mintReservesFor(JB721TiersMintReservesConfig[] memory mintReservesForTiersData) external;
190
+
191
+ /// @notice Mint reserved tokens for a specific tier.
192
+ /// @param tierId The tier ID to mint reserves for.
193
+ /// @param count The number of reserved tokens to mint.
194
+ function mintReservesFor(uint256 tierId, uint256 count) external;
195
+
196
+ /// @notice Initialize the hook with game-specific configuration.
197
+ /// @param gameId The ID of the game.
198
+ /// @param name The name of the NFT collection.
199
+ /// @param symbol The symbol of the NFT collection.
200
+ /// @param rulesets The rulesets contract.
201
+ /// @param baseUri The base URI for token metadata.
202
+ /// @param tokenUriResolver The token URI resolver.
203
+ /// @param contractUri The contract-level metadata URI.
204
+ /// @param tiers The initial tier configurations.
205
+ /// @param currency The pricing currency.
206
+ /// @param store The 721 tiers hook store.
207
+ /// @param gamePhaseReporter The game phase reporter.
208
+ /// @param gamePotReporter The game pot reporter.
209
+ /// @param defaultAttestationDelegate The default attestation delegate.
210
+ /// @param tierNames The names for each tier.
211
+ function initialize(
212
+ uint256 gameId,
213
+ string memory name,
214
+ string memory symbol,
215
+ IJBRulesets rulesets,
216
+ string memory baseUri,
217
+ IJB721TokenUriResolver tokenUriResolver,
218
+ string memory contractUri,
219
+ JB721TierConfig[] memory tiers,
220
+ uint48 currency,
221
+ IJB721TiersHookStore store,
222
+ IDefifaGamePhaseReporter gamePhaseReporter,
223
+ IDefifaGamePotReporter gamePotReporter,
224
+ address defaultAttestationDelegate,
225
+ string[] memory tierNames
226
+ )
227
+ external;
228
+ }
@@ -0,0 +1,10 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
5
+ import {IDefifaHook} from "./IDefifaHook.sol";
6
+ import {IDefifaGamePhaseReporter} from "./IDefifaGamePhaseReporter.sol";
7
+
8
+ interface IDefifaTokenUriResolver {
9
+ function typeface() external view returns (ITypeface);
10
+ }
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {ITypeface, Font} from "lib/typeface/contracts/interfaces/ITypeface.sol";
5
+
6
+ /// @notice Summon fonts.
7
+ library DefifaFontImporter {
8
+ // @notice Gets the Base64 encoded Capsules-500.otf typeface
9
+ /// @return The Base64 encoded font file
10
+ function getSkinnyFontSource(ITypeface _typeface) internal view returns (bytes memory) {
11
+ return _typeface.sourceOf(Font(500, "normal")); // Capsules font source
12
+ }
13
+
14
+ // @notice Gets the Base64 encoded Capsules-500.otf typeface
15
+ /// @return The Base64 encoded font file
16
+ function getBeefyFontSource(ITypeface _typeface) internal view returns (bytes memory) {
17
+ return _typeface.sourceOf(Font(700, "normal")); // Capsules font source
18
+ }
19
+ }
@@ -0,0 +1,358 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {mulDiv} from "@prb/math/src/Common.sol";
5
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
8
+ import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
9
+ import {DefifaTierCashOutWeight} from "../structs/DefifaTierCashOutWeight.sol";
10
+ import {DefifaGamePhase} from "../enums/DefifaGamePhase.sol";
11
+
12
+ /// @title DefifaHookLib
13
+ /// @notice Pure/view helper functions extracted from DefifaHook to reduce contract bytecode size.
14
+ /// @dev Public library functions are deployed separately and called via delegatecall, so their bytecode does not count
15
+ /// toward the calling contract's EIP-170 size limit.
16
+ library DefifaHookLib {
17
+ using SafeERC20 for IERC20;
18
+
19
+ error DefifaHook_BadTierOrder();
20
+ error DefifaHook_InvalidTierId();
21
+ error DefifaHook_InvalidCashoutWeights();
22
+
23
+ event ClaimedTokens(
24
+ address indexed beneficiary, uint256 defifaTokenAmount, uint256 baseProtocolTokenAmount, address caller
25
+ );
26
+
27
+ /// @notice The total cashOut weight that can be divided among tiers.
28
+ uint256 internal constant TOTAL_CASHOUT_WEIGHT = 1_000_000_000_000_000_000;
29
+
30
+ /// @notice Validates tier cash out weights and returns the weight array to store.
31
+ /// @param tierWeights The tier weights to validate and set.
32
+ /// @param _store The 721 tiers hook store.
33
+ /// @param hook The hook address.
34
+ /// @return weights The 128-element array of validated weights.
35
+ function validateAndBuildWeights(
36
+ DefifaTierCashOutWeight[] memory tierWeights,
37
+ IJB721TiersHookStore _store,
38
+ address hook
39
+ )
40
+ public
41
+ view
42
+ returns (uint256[128] memory weights)
43
+ {
44
+ // Keep a reference to the max tier ID.
45
+ uint256 _maxTierId = _store.maxTierIdOf(hook);
46
+
47
+ // Keep a reference to the cumulative amounts.
48
+ uint256 _cumulativeCashOutWeight;
49
+
50
+ // Keep a reference to the number of tier weights.
51
+ uint256 _numberOfTierWeights = tierWeights.length;
52
+
53
+ // Keep a reference to the tier being iterated on.
54
+ JB721Tier memory _tier;
55
+
56
+ // Keep a reference to the last tier ID to enforce ascending order (no duplicates).
57
+ uint256 _lastTierId;
58
+
59
+ for (uint256 _i; _i < _numberOfTierWeights;) {
60
+ // Enforce strict ascending order to prevent duplicate tier IDs.
61
+ if (tierWeights[_i].id <= _lastTierId && _i != 0) revert DefifaHook_BadTierOrder();
62
+ _lastTierId = tierWeights[_i].id;
63
+
64
+ // Get the tier.
65
+ _tier = _store.tierOf({hook: hook, id: tierWeights[_i].id, includeResolvedUri: false});
66
+
67
+ // Can't set a cashOut weight for tiers not in category 0.
68
+ if (_tier.category != 0) revert DefifaHook_InvalidTierId();
69
+
70
+ // Attempting to set the cashOut weight for a tier that does not exist (yet) reverts.
71
+ if (_tier.id > _maxTierId) revert DefifaHook_InvalidTierId();
72
+
73
+ // Save the tier weight. Tiers are 1 indexed and should be stored 0 indexed.
74
+ weights[_tier.id - 1] = tierWeights[_i].cashOutWeight;
75
+
76
+ // Increment the cumulative amount.
77
+ _cumulativeCashOutWeight += tierWeights[_i].cashOutWeight;
78
+
79
+ unchecked {
80
+ ++_i;
81
+ }
82
+ }
83
+
84
+ // Make sure the cumulative amount is exactly the total cashOut weight.
85
+ if (_cumulativeCashOutWeight != TOTAL_CASHOUT_WEIGHT) revert DefifaHook_InvalidCashoutWeights();
86
+ }
87
+
88
+ /// @notice Compute the cash out weight for a single token.
89
+ /// @param tokenId The token ID.
90
+ /// @param _store The 721 tiers hook store.
91
+ /// @param hook The hook address.
92
+ /// @param tierCashOutWeights The tier cash out weights array.
93
+ /// @param tokensRedeemedFrom The mapping of tokens redeemed per tier (passed as a function that returns the value).
94
+ /// @return The cash out weight.
95
+ function computeCashOutWeight(
96
+ uint256 tokenId,
97
+ IJB721TiersHookStore _store,
98
+ address hook,
99
+ uint256[128] storage tierCashOutWeights,
100
+ mapping(uint256 => uint256) storage tokensRedeemedFrom
101
+ )
102
+ public
103
+ view
104
+ returns (uint256)
105
+ {
106
+ // Keep a reference to the token's tier ID.
107
+ uint256 _tierId = _store.tierIdOfToken(tokenId);
108
+
109
+ // Keep a reference to the tier.
110
+ JB721Tier memory _tier = _store.tierOf({hook: hook, id: _tierId, includeResolvedUri: false});
111
+
112
+ // Get the tier's weight.
113
+ uint256 _weight = tierCashOutWeights[_tierId - 1];
114
+
115
+ // If there's no weight there's nothing to redeem.
116
+ if (_weight == 0) return 0;
117
+
118
+ // Get the amount of tokens that have already been burned.
119
+ uint256 _burnedTokens = _store.numberOfBurnedFor({hook: hook, tierId: _tierId});
120
+
121
+ // If no tiers were minted, nothing to redeem.
122
+ if (_tier.initialSupply - (_tier.remainingSupply + _burnedTokens) == 0) return 0;
123
+
124
+ // Calculate the amount of tokens that existed at the start of the last phase.
125
+ uint256 _totalTokensForCashoutInTier =
126
+ _tier.initialSupply - _tier.remainingSupply - (_burnedTokens - tokensRedeemedFrom[_tierId]);
127
+
128
+ // Calculate the percentage of the tier cashOut amount a single token counts for.
129
+ return _weight / _totalTokensForCashoutInTier;
130
+ }
131
+
132
+ /// @notice Compute the cumulative cash out weight for multiple tokens.
133
+ /// @param tokenIds The token IDs.
134
+ /// @param _store The 721 tiers hook store.
135
+ /// @param hook The hook address.
136
+ /// @param tierCashOutWeights The tier cash out weights array.
137
+ /// @param tokensRedeemedFrom The mapping of tokens redeemed per tier.
138
+ /// @return cumulativeWeight The cumulative weight.
139
+ function computeCashOutWeightBatch(
140
+ uint256[] memory tokenIds,
141
+ IJB721TiersHookStore _store,
142
+ address hook,
143
+ uint256[128] storage tierCashOutWeights,
144
+ mapping(uint256 => uint256) storage tokensRedeemedFrom
145
+ )
146
+ public
147
+ view
148
+ returns (uint256 cumulativeWeight)
149
+ {
150
+ uint256 _tokenCount = tokenIds.length;
151
+ for (uint256 _i; _i < _tokenCount;) {
152
+ cumulativeWeight += computeCashOutWeight({
153
+ tokenId: tokenIds[_i],
154
+ _store: _store,
155
+ hook: hook,
156
+ tierCashOutWeights: tierCashOutWeights,
157
+ tokensRedeemedFrom: tokensRedeemedFrom
158
+ });
159
+ unchecked {
160
+ ++_i;
161
+ }
162
+ }
163
+ }
164
+
165
+ /// @notice Compute the claimable token amounts for a set of token IDs.
166
+ /// @param tokenIds The token IDs.
167
+ /// @param _store The 721 tiers hook store.
168
+ /// @param hook The hook address.
169
+ /// @param totalMintCost The cumulative mint cost.
170
+ /// @param defifaBalance The current $DEFIFA balance.
171
+ /// @param baseProtocolBalance The current $BASE_PROTOCOL balance.
172
+ /// @return defifaTokenAmount The claimable $DEFIFA amount.
173
+ /// @return baseProtocolTokenAmount The claimable $BASE_PROTOCOL amount.
174
+ function computeTokensClaim(
175
+ uint256[] memory tokenIds,
176
+ IJB721TiersHookStore _store,
177
+ address hook,
178
+ uint256 totalMintCost,
179
+ uint256 defifaBalance,
180
+ uint256 baseProtocolBalance
181
+ )
182
+ public
183
+ view
184
+ returns (uint256 defifaTokenAmount, uint256 baseProtocolTokenAmount)
185
+ {
186
+ // If nothing was paid to mint, no fee tokens can be claimed.
187
+ if (totalMintCost == 0) return (0, 0);
188
+
189
+ // Keep a reference to the number of tokens being used for claims.
190
+ uint256 _numberOfTokens = tokenIds.length;
191
+
192
+ // Calculate the amount paid to mint the tokens that are being burned.
193
+ uint256 _cumulativeMintPrice;
194
+ for (uint256 _i; _i < _numberOfTokens; _i++) {
195
+ _cumulativeMintPrice += _store.tierOfTokenId({hook: hook, tokenId: tokenIds[_i], includeResolvedUri: false})
196
+ .price;
197
+ }
198
+
199
+ // Calculate the user's claimable amount proportional to what they paid.
200
+ defifaTokenAmount = defifaBalance * _cumulativeMintPrice / totalMintCost;
201
+ baseProtocolTokenAmount = baseProtocolBalance * _cumulativeMintPrice / totalMintCost;
202
+ }
203
+
204
+ /// @notice Compute the cumulative mint price for a set of token IDs.
205
+ /// @param tokenIds The token IDs.
206
+ /// @param _store The 721 tiers hook store.
207
+ /// @param hook The hook address.
208
+ /// @return cumulativeMintPrice The total mint price.
209
+ function computeCumulativeMintPrice(
210
+ uint256[] memory tokenIds,
211
+ IJB721TiersHookStore _store,
212
+ address hook
213
+ )
214
+ public
215
+ view
216
+ returns (uint256 cumulativeMintPrice)
217
+ {
218
+ uint256 _numberOfTokenIds = tokenIds.length;
219
+ for (uint256 _i; _i < _numberOfTokenIds; _i++) {
220
+ cumulativeMintPrice += _store.tierOfTokenId({hook: hook, tokenId: tokenIds[_i], includeResolvedUri: false})
221
+ .price;
222
+ }
223
+ }
224
+
225
+ /// @notice Compute the cash out count for the beforeCashOutRecorded hook.
226
+ /// @param gamePhase The current game phase.
227
+ /// @param cumulativeMintPrice The cumulative mint price of the tokens being cashed out.
228
+ /// @param surplusValue The surplus value from the context.
229
+ /// @param _amountRedeemed The amount already redeemed.
230
+ /// @param cumulativeCashOutWeight The cumulative cash out weight of the tokens.
231
+ /// @return cashOutCount The computed cash out count.
232
+ function computeCashOutCount(
233
+ DefifaGamePhase gamePhase,
234
+ uint256 cumulativeMintPrice,
235
+ uint256 surplusValue,
236
+ uint256 _amountRedeemed,
237
+ uint256 cumulativeCashOutWeight
238
+ )
239
+ public
240
+ pure
241
+ returns (uint256 cashOutCount)
242
+ {
243
+ // If the game is in its minting, refund, or no-contest phase, reclaim amount is the same as it cost to mint.
244
+ if (
245
+ gamePhase == DefifaGamePhase.MINT || gamePhase == DefifaGamePhase.REFUND
246
+ || gamePhase == DefifaGamePhase.NO_CONTEST
247
+ ) {
248
+ cashOutCount = cumulativeMintPrice;
249
+ } else {
250
+ // If the game is in its scoring or complete phase, reclaim amount is based on the tier weights.
251
+ cashOutCount = mulDiv(surplusValue + _amountRedeemed, cumulativeCashOutWeight, TOTAL_CASHOUT_WEIGHT);
252
+ }
253
+ }
254
+
255
+ /// @notice Compute the current supply of a tier (minted - burned).
256
+ /// @param _store The 721 tiers hook store.
257
+ /// @param hook The hook address.
258
+ /// @param tierId The ID of the tier.
259
+ /// @return The current supply.
260
+ function computeCurrentSupply(
261
+ IJB721TiersHookStore _store,
262
+ address hook,
263
+ uint256 tierId
264
+ )
265
+ public
266
+ view
267
+ returns (uint256)
268
+ {
269
+ JB721Tier memory _tier = _store.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
270
+ return _tier.initialSupply - (_tier.remainingSupply + _store.numberOfBurnedFor({hook: hook, tierId: tierId}));
271
+ }
272
+
273
+ /// @notice Computes the attestation units for tiers during payment processing.
274
+ /// @dev Returns parallel arrays: tier IDs, cumulative attestation units per tier, and whether to switch delegate.
275
+ /// @param _tierIdsToMint The tier IDs being minted (must be in ascending order).
276
+ /// @param _store The 721 tiers hook store.
277
+ /// @param hook The hook address.
278
+ /// @return tierIds The unique tier IDs.
279
+ /// @return attestationAmounts The cumulative attestation units for each unique tier.
280
+ /// @return count The number of unique tiers.
281
+ function computeAttestationUnits(
282
+ uint16[] memory _tierIdsToMint,
283
+ IJB721TiersHookStore _store,
284
+ address hook
285
+ )
286
+ public
287
+ view
288
+ returns (uint256[] memory tierIds, uint256[] memory attestationAmounts, uint256 count)
289
+ {
290
+ uint256 _numberOfTiers = _tierIdsToMint.length;
291
+ tierIds = new uint256[](_numberOfTiers);
292
+ attestationAmounts = new uint256[](_numberOfTiers);
293
+
294
+ if (_numberOfTiers == 0) return (tierIds, attestationAmounts, 0);
295
+
296
+ uint256 _currentTierId;
297
+ uint256 _attestationUnits;
298
+ uint256 _accumulated;
299
+
300
+ for (uint256 _i; _i < _numberOfTiers;) {
301
+ if (_currentTierId != _tierIdsToMint[_i]) {
302
+ // Flush accumulated units for previous tier.
303
+ if (_currentTierId != 0) {
304
+ tierIds[count] = _currentTierId;
305
+ attestationAmounts[count] = _accumulated;
306
+ count++;
307
+ }
308
+ if (_tierIdsToMint[_i] < _currentTierId) revert DefifaHook_BadTierOrder();
309
+ _currentTierId = _tierIdsToMint[_i];
310
+ _attestationUnits =
311
+ _store.tierOf({hook: hook, id: _currentTierId, includeResolvedUri: false}).votingUnits;
312
+ _accumulated = _attestationUnits;
313
+ } else {
314
+ _accumulated += _attestationUnits;
315
+ }
316
+ unchecked {
317
+ ++_i;
318
+ }
319
+ }
320
+ // Flush the last tier.
321
+ if (_currentTierId != 0) {
322
+ tierIds[count] = _currentTierId;
323
+ attestationAmounts[count] = _accumulated;
324
+ count++;
325
+ }
326
+ }
327
+
328
+ /// @notice Claims the defifa and base protocol tokens for a beneficiary.
329
+ /// @dev Executes via delegatecall, so `address(this)` is the calling contract. Transfers are from the hook's
330
+ /// balance. @param _beneficiary The address to claim tokens for.
331
+ /// @param shareToBeneficiary The share relative to the `outOfTotal` to send the user.
332
+ /// @param outOfTotal The total share that the `shareToBeneficiary` is relative to.
333
+ /// @param _defifaToken The $DEFIFA token.
334
+ /// @param _baseProtocolToken The $BASE_PROTOCOL token.
335
+ /// @return beneficiaryReceivedTokens A flag indicating if the beneficiary received any tokens.
336
+ function claimTokensFor(
337
+ address _beneficiary,
338
+ uint256 shareToBeneficiary,
339
+ uint256 outOfTotal,
340
+ IERC20 _defifaToken,
341
+ IERC20 _baseProtocolToken
342
+ )
343
+ public
344
+ returns (bool beneficiaryReceivedTokens)
345
+ {
346
+ // Calculate the share of $DEFIFA and $BASE_PROTOCOL tokens to send.
347
+ uint256 baseProtocolAmount = _baseProtocolToken.balanceOf(address(this)) * shareToBeneficiary / outOfTotal;
348
+ uint256 defifaAmount = _defifaToken.balanceOf(address(this)) * shareToBeneficiary / outOfTotal;
349
+
350
+ // If there is an amount we should send, send it.
351
+ if (defifaAmount != 0) _defifaToken.safeTransfer(_beneficiary, defifaAmount);
352
+ if (baseProtocolAmount != 0) _baseProtocolToken.safeTransfer(_beneficiary, baseProtocolAmount);
353
+
354
+ emit ClaimedTokens(_beneficiary, defifaAmount, baseProtocolAmount, msg.sender);
355
+
356
+ return (defifaAmount != 0 || baseProtocolAmount != 0);
357
+ }
358
+ }
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ /// @custom:param A count of attestations.
5
+ /// @custom:param A mapping of which accounts have attested.
6
+ struct DefifaAttestations {
7
+ uint256 count;
8
+ mapping(address => bool) hasAttested;
9
+ }
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ /// @custom:member delegatee The account to delegate tier voting units to.
5
+ /// @custom:member tierId The ID of the tier to delegate voting units for.
6
+ struct DefifaDelegation {
7
+ address delegatee;
8
+ uint256 tierId;
9
+ }