@ballkidz/defifa 0.0.6 → 0.0.8
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.
- package/ADMINISTRATION.md +3 -3
- package/AUDIT_INSTRUCTIONS.md +422 -0
- package/CRYPTO_ECON.md +5 -5
- package/RISKS.md +38 -335
- package/SKILLS.md +1 -1
- package/STYLE_GUIDE.md +14 -1
- package/USER_JOURNEYS.md +691 -0
- package/package.json +7 -5
- package/script/Deploy.s.sol +26 -13
- package/script/helpers/DefifaDeploymentLib.sol +30 -14
- package/src/DefifaDeployer.sol +225 -187
- package/src/DefifaGovernor.sol +291 -281
- package/src/DefifaHook.sol +81 -42
- package/src/DefifaProjectOwner.sol +27 -4
- package/src/DefifaTokenUriResolver.sol +137 -134
- package/src/enums/DefifaGamePhase.sol +1 -1
- package/src/enums/DefifaScorecardState.sol +1 -1
- package/src/interfaces/IDefifaDeployer.sol +52 -50
- package/src/interfaces/IDefifaGamePhaseReporter.sol +2 -2
- package/src/interfaces/IDefifaGamePotReporter.sol +1 -1
- package/src/interfaces/IDefifaGovernor.sol +53 -54
- package/src/interfaces/IDefifaHook.sol +104 -103
- package/src/interfaces/IDefifaTokenUriResolver.sol +2 -2
- package/src/libraries/DefifaFontImporter.sol +11 -9
- package/src/libraries/DefifaHookLib.sol +68 -53
- package/src/structs/DefifaAttestations.sol +1 -1
- package/src/structs/DefifaDelegation.sol +1 -1
- package/src/structs/DefifaLaunchProjectData.sol +4 -4
- package/src/structs/DefifaOpsData.sol +1 -1
- package/src/structs/DefifaScorecard.sol +1 -1
- package/src/structs/DefifaTierCashOutWeight.sol +1 -1
- package/src/structs/DefifaTierParams.sol +2 -1
- package/test/DefifaAdversarialQuorum.t.sol +602 -0
- package/test/DefifaAuditLowGuards.t.sol +304 -0
- package/test/DefifaFeeAccounting.t.sol +37 -16
- package/test/DefifaGovernor.t.sol +37 -11
- package/test/DefifaHookRegressions.t.sol +14 -12
- package/test/DefifaMintCostInvariant.t.sol +31 -12
- package/test/DefifaNoContest.t.sol +33 -13
- package/test/DefifaSecurity.t.sol +45 -25
- package/test/DefifaUSDC.t.sol +44 -34
- package/test/Fork.t.sol +42 -40
- package/test/SVG.t.sol +2 -2
- package/test/TestAuditGaps.sol +982 -0
- package/test/TestQALastMile.t.sol +511 -0
- package/test/regression/FulfillmentBlocksRatification.t.sol +36 -30
- package/test/regression/GracePeriodBypass.t.sol +15 -10
package/src/DefifaDeployer.sol
CHANGED
|
@@ -1,50 +1,43 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
5
|
+
import {
|
|
6
|
+
JB721TiersRulesetMetadata,
|
|
7
|
+
JB721TiersRulesetMetadataResolver
|
|
8
|
+
} from "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
9
|
+
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
10
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
11
|
+
import {IJBController, JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
12
|
+
import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
|
|
13
|
+
import {IJBRulesetApprovalHook, JBRuleset} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
14
|
+
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
15
|
+
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
16
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
17
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
18
|
+
import {JBCurrencyAmount} from "@bananapus/core-v6/src/structs/JBCurrencyAmount.sol";
|
|
19
|
+
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
20
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
21
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
22
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
6
23
|
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
|
7
|
-
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
8
|
-
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
9
24
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
25
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
26
|
+
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
10
27
|
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|
28
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
29
|
+
|
|
30
|
+
import {DefifaHook} from "./DefifaHook.sol";
|
|
11
31
|
import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
|
|
12
32
|
import {IDefifaDeployer} from "./interfaces/IDefifaDeployer.sol";
|
|
13
|
-
import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
|
|
14
33
|
import {IDefifaGamePhaseReporter} from "./interfaces/IDefifaGamePhaseReporter.sol";
|
|
15
34
|
import {IDefifaGamePotReporter} from "./interfaces/IDefifaGamePotReporter.sol";
|
|
16
35
|
import {IDefifaGovernor} from "./interfaces/IDefifaGovernor.sol";
|
|
36
|
+
import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
|
|
17
37
|
import {DefifaLaunchProjectData} from "./structs/DefifaLaunchProjectData.sol";
|
|
18
|
-
import {DefifaTierParams} from "./structs/DefifaTierParams.sol";
|
|
19
38
|
import {DefifaOpsData} from "./structs/DefifaOpsData.sol";
|
|
20
|
-
import {
|
|
21
|
-
import {DefifaTokenUriResolver} from "./DefifaTokenUriResolver.sol";
|
|
22
|
-
|
|
23
|
-
import {IJBToken} from "@bananapus/core-v6/src/interfaces/IJBToken.sol";
|
|
24
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
25
|
-
import {IJBController, JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
26
|
-
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
27
|
-
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
28
|
-
import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
|
|
29
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
30
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
31
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
32
|
-
import {JBCurrencyAmount} from "@bananapus/core-v6/src/structs/JBCurrencyAmount.sol";
|
|
33
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
34
|
-
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
35
|
-
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
36
|
-
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
37
|
-
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
38
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
39
|
-
import {JBSplitHookContext} from "@bananapus/core-v6/src/structs/JBSplitHookContext.sol";
|
|
40
|
-
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
41
|
-
import {
|
|
42
|
-
JB721TiersRulesetMetadata,
|
|
43
|
-
JB721TiersRulesetMetadataResolver
|
|
44
|
-
} from "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
45
|
-
import {IJBRulesets, IJBRulesetApprovalHook, JBRuleset} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
39
|
+
import {DefifaTierParams} from "./structs/DefifaTierParams.sol";
|
|
46
40
|
|
|
47
|
-
/// @title DefifaDeployer
|
|
48
41
|
/// @notice Deploys and manages Defifa games.
|
|
49
42
|
contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGamePotReporter, IERC721Receiver {
|
|
50
43
|
using Strings for uint256;
|
|
@@ -55,7 +48,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
55
48
|
//*********************************************************************//
|
|
56
49
|
|
|
57
50
|
error DefifaDeployer_CantFulfillYet();
|
|
58
|
-
error DefifaDeployer_NothingToFulfill();
|
|
59
51
|
error DefifaDeployer_GameOver();
|
|
60
52
|
error DefifaDeployer_InvalidFeePercent();
|
|
61
53
|
error DefifaDeployer_InvalidGameConfiguration();
|
|
@@ -84,29 +76,29 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
84
76
|
|
|
85
77
|
/// @notice The group relative to which splits are stored.
|
|
86
78
|
/// @dev This could be any fixed number.
|
|
87
|
-
uint256 public immutable override
|
|
79
|
+
uint256 public immutable override SPLIT_GROUP;
|
|
88
80
|
|
|
89
81
|
/// @notice The project ID that'll receive game fees, and relative to which splits are stored.
|
|
90
82
|
/// @dev The owner of this project ID must give this contract operator permissions over the SET_SPLITS operation.
|
|
91
|
-
uint256 public immutable override
|
|
83
|
+
uint256 public immutable override DEFIFA_PROJECT_ID;
|
|
92
84
|
|
|
93
85
|
/// @notice The project ID that'll receive protocol fees as commitments are fulfilled.
|
|
94
|
-
uint256 public immutable override
|
|
86
|
+
uint256 public immutable override BASE_PROTOCOL_PROJECT_ID;
|
|
95
87
|
|
|
96
88
|
/// @notice The original code for the Defifa hook to base subsequent instances on.
|
|
97
|
-
address public immutable override
|
|
89
|
+
address public immutable override HOOK_CODE_ORIGIN;
|
|
98
90
|
|
|
99
91
|
/// @notice The default Defifa token URI resolver.
|
|
100
|
-
IJB721TokenUriResolver public immutable override
|
|
92
|
+
IJB721TokenUriResolver public immutable override TOKEN_URI_RESOLVER;
|
|
101
93
|
|
|
102
94
|
/// @notice The Defifa governor.
|
|
103
|
-
IDefifaGovernor public immutable override
|
|
95
|
+
IDefifaGovernor public immutable override GOVERNOR;
|
|
104
96
|
|
|
105
97
|
/// @notice The controller with which new projects should be deployed.
|
|
106
|
-
IJBController public immutable override
|
|
98
|
+
IJBController public immutable override CONTROLLER;
|
|
107
99
|
|
|
108
100
|
/// @notice The hooks registry.
|
|
109
|
-
IJBAddressRegistry public immutable
|
|
101
|
+
IJBAddressRegistry public immutable REGISTRY;
|
|
110
102
|
|
|
111
103
|
/// @notice The divisor that describes the protocol fee that should be taken.
|
|
112
104
|
/// @dev This is equal to 100 divided by the fee percent (e.g. 40 = 2.5% fee).
|
|
@@ -154,7 +146,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
154
146
|
address _token = _opsOf[gameId].token;
|
|
155
147
|
|
|
156
148
|
// Get a reference to the terminal.
|
|
157
|
-
IJBTerminal _terminal =
|
|
149
|
+
IJBTerminal _terminal = CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: _token});
|
|
158
150
|
|
|
159
151
|
// Get the accounting context for the project.
|
|
160
152
|
JBAccountingContext memory _context = _terminal.accountingContextForTokenOf({projectId: gameId, token: _token});
|
|
@@ -174,10 +166,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
174
166
|
/// @return Whether or not the next phase still needs queuing.
|
|
175
167
|
function nextPhaseNeedsQueueing(uint256 gameId) external view override returns (bool) {
|
|
176
168
|
// Get the game's current funding cycle along with its metadata.
|
|
177
|
-
JBRuleset memory _currentRuleset =
|
|
169
|
+
JBRuleset memory _currentRuleset = CONTROLLER.RULESETS().currentOf(gameId);
|
|
178
170
|
// Get the game's queued funding cycle along with its metadata.
|
|
179
171
|
// slither-disable-next-line unused-return
|
|
180
|
-
(JBRuleset memory _queuedRuleset,) =
|
|
172
|
+
(JBRuleset memory _queuedRuleset,) = CONTROLLER.RULESETS().latestQueuedOf(gameId);
|
|
181
173
|
|
|
182
174
|
// If the configurations are the same and the game hasn't ended, queueing is still needed.
|
|
183
175
|
return _currentRuleset.duration != 0 && _currentRuleset.id == _queuedRuleset.id;
|
|
@@ -226,7 +218,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
226
218
|
/// @return The game phase.
|
|
227
219
|
function currentGamePhaseOf(uint256 gameId) public view override returns (DefifaGamePhase) {
|
|
228
220
|
// Get the game's current funding cycle along with its metadata.
|
|
229
|
-
(JBRuleset memory _currentRuleset, JBRulesetMetadata memory _metadata) =
|
|
221
|
+
(JBRuleset memory _currentRuleset, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
230
222
|
|
|
231
223
|
if (_currentRuleset.cycleNumber == 0) return DefifaGamePhase.COUNTDOWN;
|
|
232
224
|
if (_currentRuleset.cycleNumber == 1) return DefifaGamePhase.MINT;
|
|
@@ -247,7 +239,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
247
239
|
// Check minimum participation threshold: if the treasury balance is below the threshold, the game is
|
|
248
240
|
// NO_CONTEST.
|
|
249
241
|
if (_ops.minParticipation > 0) {
|
|
250
|
-
IJBTerminal _terminal =
|
|
242
|
+
IJBTerminal _terminal = CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: _ops.token});
|
|
251
243
|
uint256 _balance = IJBMultiTerminal(address(_terminal)).STORE()
|
|
252
244
|
.balanceOf({terminal: address(_terminal), projectId: gameId, token: _ops.token});
|
|
253
245
|
if (_balance < _ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
@@ -282,15 +274,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
282
274
|
uint256 _defifaProjectId,
|
|
283
275
|
uint256 _baseProtocolProjectId
|
|
284
276
|
) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
277
|
+
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
278
|
+
TOKEN_URI_RESOLVER = _tokenUriResolver;
|
|
279
|
+
GOVERNOR = _governor;
|
|
280
|
+
CONTROLLER = _controller;
|
|
281
|
+
REGISTRY = _registry;
|
|
282
|
+
DEFIFA_PROJECT_ID = _defifaProjectId;
|
|
283
|
+
BASE_PROTOCOL_PROJECT_ID = _baseProtocolProjectId;
|
|
292
284
|
/// @dev Uses the deployer address as group ID. Game scoring rulesets use uint160(token) as group ID.
|
|
293
|
-
|
|
285
|
+
SPLIT_GROUP = uint256(uint160(address(this)));
|
|
294
286
|
}
|
|
295
287
|
|
|
296
288
|
//*********************************************************************//
|
|
@@ -305,7 +297,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
305
297
|
|
|
306
298
|
// Get the game's current funding cycle along with its metadata.
|
|
307
299
|
// slither-disable-next-line unused-return
|
|
308
|
-
(, JBRulesetMetadata memory _metadata) =
|
|
300
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
309
301
|
|
|
310
302
|
// Make sure the game's commitments can be fulfilled.
|
|
311
303
|
if (!IDefifaHook(_metadata.dataHook).cashOutWeightIsSet()) {
|
|
@@ -315,74 +307,49 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
315
307
|
// Get the game token and the terminal.
|
|
316
308
|
address _token = _opsOf[gameId].token;
|
|
317
309
|
IJBMultiTerminal _terminal =
|
|
318
|
-
IJBMultiTerminal(address(
|
|
310
|
+
IJBMultiTerminal(address(CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: _token})));
|
|
319
311
|
|
|
320
312
|
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
321
313
|
uint256 _pot = _terminal.STORE().balanceOf({terminal: address(_terminal), projectId: gameId, token: _token});
|
|
314
|
+
|
|
315
|
+
// If the pot is empty, set the sentinel and queue the final ruleset without attempting payouts.
|
|
322
316
|
// slither-disable-next-line incorrect-equality
|
|
323
|
-
if (_pot == 0)
|
|
317
|
+
if (_pot == 0) {
|
|
318
|
+
fulfilledCommitmentsOf[gameId] = 1;
|
|
319
|
+
_queueFinalRuleset({gameId: gameId, metadata: _metadata});
|
|
320
|
+
emit FulfilledCommitments({gameId: gameId, pot: 0, caller: msg.sender});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
324
323
|
|
|
325
324
|
// Compute the fee amount based on the total absolute split percent stored at game creation.
|
|
326
|
-
uint256 _feeAmount =
|
|
325
|
+
uint256 _feeAmount =
|
|
326
|
+
mulDiv({x: _pot, y: _commitmentPercentOf[gameId], denominator: JBConstants.SPLITS_TOTAL_PERCENT});
|
|
327
327
|
|
|
328
328
|
// Store the actual fee amount for accurate currentGamePotOf reporting.
|
|
329
329
|
// Use max(feeAmount, 1) to preserve the reentrancy guard when pot is 0.
|
|
330
330
|
fulfilledCommitmentsOf[gameId] = _feeAmount > 0 ? _feeAmount : 1;
|
|
331
331
|
|
|
332
332
|
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
333
|
+
// Wrapped in try-catch so the final ruleset is always queued even if payout fails.
|
|
333
334
|
// slither-disable-next-line unused-return
|
|
334
|
-
_terminal.sendPayoutsOf({
|
|
335
|
+
try _terminal.sendPayoutsOf({
|
|
335
336
|
projectId: gameId,
|
|
336
337
|
token: _token,
|
|
337
338
|
amount: _feeAmount,
|
|
339
|
+
// Casting address to uint32 via uint160 is the standard Juicebox token-to-currency conversion.
|
|
340
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
338
341
|
currency: _token == JBConstants.NATIVE_TOKEN ? _metadata.baseCurrency : uint32(uint160(_token)),
|
|
339
|
-
minTokensPaidOut:
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
weight: 0,
|
|
348
|
-
weightCutPercent: 0,
|
|
349
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
350
|
-
metadata: JBRulesetMetadata({
|
|
351
|
-
reservedPercent: 0,
|
|
352
|
-
cashOutTaxRate: 0,
|
|
353
|
-
baseCurrency: _metadata.baseCurrency,
|
|
354
|
-
pausePay: true,
|
|
355
|
-
pauseCreditTransfers: false,
|
|
356
|
-
allowOwnerMinting: false,
|
|
357
|
-
allowSetCustomToken: false,
|
|
358
|
-
allowTerminalMigration: false,
|
|
359
|
-
allowSetTerminals: false,
|
|
360
|
-
allowSetController: false,
|
|
361
|
-
allowAddAccountingContext: false,
|
|
362
|
-
allowAddPriceFeed: false,
|
|
363
|
-
// Set this to true so only the deployer can fulfill the commitments.
|
|
364
|
-
ownerMustSendPayouts: true,
|
|
365
|
-
holdFees: false,
|
|
366
|
-
useTotalSurplusForCashOuts: false,
|
|
367
|
-
useDataHookForPay: true,
|
|
368
|
-
useDataHookForCashOut: true,
|
|
369
|
-
dataHook: _metadata.dataHook,
|
|
370
|
-
metadata: uint16(
|
|
371
|
-
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
372
|
-
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
373
|
-
)
|
|
374
|
-
)
|
|
375
|
-
}),
|
|
376
|
-
// No more payouts.
|
|
377
|
-
splitGroups: new JBSplitGroup[](0),
|
|
378
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
379
|
-
});
|
|
342
|
+
minTokensPaidOut: 0
|
|
343
|
+
}) {}
|
|
344
|
+
catch (bytes memory reason) {
|
|
345
|
+
// Payout failed — fee stays in pot. Reset to sentinel (1) so currentGamePotOf
|
|
346
|
+
// doesn't double-count the fee, while preserving the reentrancy guard.
|
|
347
|
+
fulfilledCommitmentsOf[gameId] = 1;
|
|
348
|
+
emit CommitmentPayoutFailed({gameId: gameId, amount: _feeAmount, reason: reason});
|
|
349
|
+
}
|
|
380
350
|
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
controller.queueRulesetsOf({
|
|
384
|
-
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
385
|
-
});
|
|
351
|
+
// Queue the final ruleset and emit.
|
|
352
|
+
_queueFinalRuleset({gameId: gameId, metadata: _metadata});
|
|
386
353
|
|
|
387
354
|
emit FulfilledCommitments({gameId: gameId, pot: _pot, caller: msg.sender});
|
|
388
355
|
}
|
|
@@ -419,7 +386,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
419
386
|
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
420
387
|
|
|
421
388
|
// Get the game ID, optimistically knowing it will be one greater than the current count.
|
|
422
|
-
|
|
389
|
+
// Note: this prediction can race with other concurrent project deployments. If another project is
|
|
390
|
+
// created between reading count() and launchProjectFor(), the actual ID will differ. This is
|
|
391
|
+
// caught by the equality check after launch (gameId != _actualGameId reverts).
|
|
392
|
+
gameId = CONTROLLER.PROJECTS().count() + 1;
|
|
423
393
|
|
|
424
394
|
{
|
|
425
395
|
// Store the timestamps that'll define the game phases.
|
|
@@ -452,8 +422,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
452
422
|
// Add a split for the fee.
|
|
453
423
|
_splits[_numberOfSplits] = JBSplit({
|
|
454
424
|
preferAddToBalance: false,
|
|
425
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
455
426
|
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT / DEFIFA_FEE_DIVISOR),
|
|
456
|
-
|
|
427
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
428
|
+
projectId: uint64(DEFIFA_PROJECT_ID),
|
|
457
429
|
beneficiary: payable(address(this)),
|
|
458
430
|
lockedUntil: 0,
|
|
459
431
|
hook: IJBSplitHook(address(0))
|
|
@@ -461,11 +433,11 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
461
433
|
|
|
462
434
|
// Store the splits.
|
|
463
435
|
JBSplitGroup[] memory _groupedSplits = new JBSplitGroup[](1);
|
|
464
|
-
_groupedSplits[0] = JBSplitGroup({groupId:
|
|
436
|
+
_groupedSplits[0] = JBSplitGroup({groupId: SPLIT_GROUP, splits: _splits});
|
|
465
437
|
|
|
466
438
|
// This contract must have SET_SPLIT_GROUPS permission from the defifa project owner.
|
|
467
|
-
|
|
468
|
-
projectId:
|
|
439
|
+
CONTROLLER.setSplitGroupsOf({
|
|
440
|
+
projectId: DEFIFA_PROJECT_ID, rulesetId: gameId, splitGroups: _groupedSplits
|
|
469
441
|
});
|
|
470
442
|
}
|
|
471
443
|
}
|
|
@@ -523,20 +495,22 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
523
495
|
// cloneDeterministic with msg.sender in the salt prevents this since a different
|
|
524
496
|
// caller produces a different address.
|
|
525
497
|
DefifaHook _hook = DefifaHook(
|
|
526
|
-
Clones.cloneDeterministic(
|
|
498
|
+
Clones.cloneDeterministic({
|
|
499
|
+
implementation: HOOK_CODE_ORIGIN, salt: keccak256(abi.encodePacked(msg.sender, _currentNonce))
|
|
500
|
+
})
|
|
527
501
|
);
|
|
528
502
|
|
|
529
503
|
// Use the default uri resolver if provided, else use the hardcoded generic default.
|
|
530
504
|
IJB721TokenUriResolver _uriResolver = launchProjectData.defaultTokenUriResolver
|
|
531
505
|
!= IJB721TokenUriResolver(address(0))
|
|
532
506
|
? launchProjectData.defaultTokenUriResolver
|
|
533
|
-
:
|
|
507
|
+
: TOKEN_URI_RESOLVER;
|
|
534
508
|
|
|
535
509
|
_hook.initialize({
|
|
536
510
|
_gameId: gameId,
|
|
537
511
|
_name: launchProjectData.name,
|
|
538
512
|
_symbol: string.concat("DEFIFA #", gameId.toString()),
|
|
539
|
-
_rulesets:
|
|
513
|
+
_rulesets: CONTROLLER.RULESETS(),
|
|
540
514
|
_baseUri: launchProjectData.baseUri,
|
|
541
515
|
_tokenUriResolver: _uriResolver,
|
|
542
516
|
_contractUri: launchProjectData.contractUri,
|
|
@@ -557,19 +531,19 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
557
531
|
if (gameId != _actualGameId) revert DefifaDeployer_InvalidGameConfiguration();
|
|
558
532
|
|
|
559
533
|
// Clone and initialize the new governor.
|
|
560
|
-
|
|
534
|
+
GOVERNOR.initializeGame({
|
|
561
535
|
gameId: gameId,
|
|
562
536
|
attestationStartTime: uint256(launchProjectData.attestationStartTime),
|
|
563
537
|
attestationGracePeriod: uint256(launchProjectData.attestationGracePeriod)
|
|
564
538
|
});
|
|
565
539
|
|
|
566
540
|
// Transfer ownership to the specified owner.
|
|
567
|
-
_hook.transferOwnership(address(
|
|
541
|
+
_hook.transferOwnership(address(GOVERNOR));
|
|
568
542
|
|
|
569
543
|
// Add the hook to the registry, contract nonce starts at 1
|
|
570
|
-
|
|
544
|
+
REGISTRY.registerAddress({deployer: address(this), nonce: _currentNonce});
|
|
571
545
|
|
|
572
|
-
emit LaunchGame(gameId, _hook,
|
|
546
|
+
emit LaunchGame(gameId, _hook, GOVERNOR, _uriResolver, msg.sender);
|
|
573
547
|
}
|
|
574
548
|
|
|
575
549
|
/// @notice Allows this contract to receive 721s.
|
|
@@ -592,11 +566,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
592
566
|
if (noContestTriggeredFor[gameId]) revert DefifaDeployer_NoContestAlreadyTriggered();
|
|
593
567
|
|
|
594
568
|
// Mark as triggered.
|
|
569
|
+
// Note: the queued ruleset does not take effect until the current ruleset's cycle ends (or immediately
|
|
570
|
+
// if duration is 0). During this gap, the game reports NO_CONTEST but the on-chain ruleset still has
|
|
571
|
+
// payout limits, so cash-out reclaim values may differ from the full-refund expectation. Callers
|
|
572
|
+
// should verify the active ruleset before cashing out.
|
|
595
573
|
noContestTriggeredFor[gameId] = true;
|
|
596
574
|
|
|
597
575
|
// Get the game's current ruleset metadata for the data hook address.
|
|
598
576
|
// slither-disable-next-line unused-return
|
|
599
|
-
(, JBRulesetMetadata memory _metadata) =
|
|
577
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
600
578
|
|
|
601
579
|
// Queue a new ruleset without payout limits so surplus = balance, enabling refunds.
|
|
602
580
|
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
@@ -637,7 +615,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
637
615
|
|
|
638
616
|
// Queue the no-contest refund ruleset.
|
|
639
617
|
// slither-disable-next-line unused-return
|
|
640
|
-
|
|
618
|
+
CONTROLLER.queueRulesetsOf({
|
|
641
619
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game: no contest."
|
|
642
620
|
});
|
|
643
621
|
|
|
@@ -648,6 +626,90 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
648
626
|
// ------------------------ internal functions ----------------------- //
|
|
649
627
|
//*********************************************************************//
|
|
650
628
|
|
|
629
|
+
function _buildSplits(
|
|
630
|
+
uint256 _gameId,
|
|
631
|
+
address _dataHook,
|
|
632
|
+
address _token,
|
|
633
|
+
JBSplit[] memory _initialSplits
|
|
634
|
+
)
|
|
635
|
+
internal
|
|
636
|
+
returns (JBSplitGroup[] memory)
|
|
637
|
+
{
|
|
638
|
+
uint256 _numberOfUserSplits = _initialSplits.length;
|
|
639
|
+
|
|
640
|
+
// Compute absolute percents for protocol fees.
|
|
641
|
+
uint256 _nanaAbsolutePercent = JBConstants.SPLITS_TOTAL_PERCENT / BASE_PROTOCOL_FEE_DIVISOR;
|
|
642
|
+
uint256 _defifaAbsolutePercent = JBConstants.SPLITS_TOTAL_PERCENT / DEFIFA_FEE_DIVISOR;
|
|
643
|
+
|
|
644
|
+
// Sum all absolute percents.
|
|
645
|
+
uint256 _totalAbsolutePercent = _nanaAbsolutePercent + _defifaAbsolutePercent;
|
|
646
|
+
for (uint256 _i; _i < _numberOfUserSplits; _i++) {
|
|
647
|
+
_totalAbsolutePercent += _initialSplits[_i].percent;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Validate that total fee splits don't exceed 100%.
|
|
651
|
+
if (_totalAbsolutePercent > JBConstants.SPLITS_TOTAL_PERCENT) revert DefifaDeployer_SplitsDontAddUp();
|
|
652
|
+
|
|
653
|
+
// Store the total absolute percent for use in fulfillCommitmentsOf.
|
|
654
|
+
_commitmentPercentOf[_gameId] = _totalAbsolutePercent;
|
|
655
|
+
|
|
656
|
+
// Build the splits array: user splits + Defifa + NANA (NANA last to absorb rounding).
|
|
657
|
+
uint256 _splitCount = _numberOfUserSplits + 2;
|
|
658
|
+
JBSplit[] memory _splits = new JBSplit[](_splitCount);
|
|
659
|
+
|
|
660
|
+
// Normalize user splits and copy them over.
|
|
661
|
+
uint256 _normalizedTotal;
|
|
662
|
+
for (uint256 _i; _i < _numberOfUserSplits; _i++) {
|
|
663
|
+
_splits[_i] = _initialSplits[_i];
|
|
664
|
+
_splits[_i].percent = uint32(
|
|
665
|
+
mulDiv({
|
|
666
|
+
x: _initialSplits[_i].percent,
|
|
667
|
+
y: JBConstants.SPLITS_TOTAL_PERCENT,
|
|
668
|
+
denominator: _totalAbsolutePercent
|
|
669
|
+
})
|
|
670
|
+
);
|
|
671
|
+
_normalizedTotal += _splits[_i].percent;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Add Defifa fee split (normalized).
|
|
675
|
+
uint256 _defifaNormalized = mulDiv({
|
|
676
|
+
x: _defifaAbsolutePercent, y: JBConstants.SPLITS_TOTAL_PERCENT, denominator: _totalAbsolutePercent
|
|
677
|
+
});
|
|
678
|
+
_splits[_numberOfUserSplits] = JBSplit({
|
|
679
|
+
preferAddToBalance: false,
|
|
680
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
681
|
+
percent: uint32(_defifaNormalized),
|
|
682
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
683
|
+
projectId: uint64(DEFIFA_PROJECT_ID),
|
|
684
|
+
beneficiary: payable(address(_dataHook)),
|
|
685
|
+
lockedUntil: 0,
|
|
686
|
+
hook: IJBSplitHook(address(0))
|
|
687
|
+
});
|
|
688
|
+
_normalizedTotal += _defifaNormalized;
|
|
689
|
+
|
|
690
|
+
// Add NANA protocol fee split last — absorbs rounding remainder from normalization.
|
|
691
|
+
// Because mulDiv rounds down, the sum of normalized percents can be slightly less than SPLITS_TOTAL_PERCENT.
|
|
692
|
+
// The NANA split receives the difference, so its effective percent may be a few basis points above its
|
|
693
|
+
// proportional share. This is economically negligible (< 1 bps at typical split counts).
|
|
694
|
+
// Beneficiary is the data hook so the hook receives NANA tokens for distribution during cash-outs.
|
|
695
|
+
_splits[_splitCount - 1] = JBSplit({
|
|
696
|
+
preferAddToBalance: false,
|
|
697
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
698
|
+
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT - _normalizedTotal),
|
|
699
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
700
|
+
projectId: uint64(BASE_PROTOCOL_PROJECT_ID),
|
|
701
|
+
beneficiary: payable(address(_dataHook)),
|
|
702
|
+
lockedUntil: 0,
|
|
703
|
+
hook: IJBSplitHook(address(0))
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// Build the grouped split for the payment of the game token.
|
|
707
|
+
JBSplitGroup[] memory _groupedSplits = new JBSplitGroup[](1);
|
|
708
|
+
_groupedSplits[0] = JBSplitGroup({groupId: uint256(uint160(_token)), splits: _splits});
|
|
709
|
+
|
|
710
|
+
return _groupedSplits;
|
|
711
|
+
}
|
|
712
|
+
|
|
651
713
|
function _launchGame(
|
|
652
714
|
DefifaLaunchProjectData memory launchProjectData,
|
|
653
715
|
uint256 _gameId,
|
|
@@ -813,7 +875,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
813
875
|
});
|
|
814
876
|
|
|
815
877
|
// launch the project.
|
|
816
|
-
return
|
|
878
|
+
return CONTROLLER.launchProjectFor({
|
|
817
879
|
owner: address(this),
|
|
818
880
|
projectUri: launchProjectData.projectUri,
|
|
819
881
|
rulesetConfigurations: rulesetConfigs,
|
|
@@ -822,74 +884,50 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
822
884
|
});
|
|
823
885
|
}
|
|
824
886
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
// Add Defifa fee split (normalized).
|
|
866
|
-
uint256 _defifaNormalized =
|
|
867
|
-
mulDiv(_defifaAbsolutePercent, JBConstants.SPLITS_TOTAL_PERCENT, _totalAbsolutePercent);
|
|
868
|
-
_splits[_numberOfUserSplits] = JBSplit({
|
|
869
|
-
preferAddToBalance: false,
|
|
870
|
-
percent: uint32(_defifaNormalized),
|
|
871
|
-
projectId: uint64(defifaProjectId),
|
|
872
|
-
beneficiary: payable(address(_dataHook)),
|
|
873
|
-
lockedUntil: 0,
|
|
874
|
-
hook: IJBSplitHook(address(0))
|
|
887
|
+
/// @notice Queues the final ruleset for a game: no payouts, no fund access limits, surplus = entire balance.
|
|
888
|
+
/// @param gameId The ID of the game.
|
|
889
|
+
/// @param metadata The current ruleset metadata (used to carry forward baseCurrency and dataHook).
|
|
890
|
+
function _queueFinalRuleset(uint256 gameId, JBRulesetMetadata memory metadata) internal {
|
|
891
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
892
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
893
|
+
mustStartAtOrAfter: 0,
|
|
894
|
+
duration: 0,
|
|
895
|
+
weight: 0,
|
|
896
|
+
weightCutPercent: 0,
|
|
897
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
898
|
+
metadata: JBRulesetMetadata({
|
|
899
|
+
reservedPercent: 0,
|
|
900
|
+
cashOutTaxRate: 0,
|
|
901
|
+
baseCurrency: metadata.baseCurrency,
|
|
902
|
+
pausePay: true,
|
|
903
|
+
pauseCreditTransfers: false,
|
|
904
|
+
allowOwnerMinting: false,
|
|
905
|
+
allowSetCustomToken: false,
|
|
906
|
+
allowTerminalMigration: false,
|
|
907
|
+
allowSetTerminals: false,
|
|
908
|
+
allowSetController: false,
|
|
909
|
+
allowAddAccountingContext: false,
|
|
910
|
+
allowAddPriceFeed: false,
|
|
911
|
+
ownerMustSendPayouts: false,
|
|
912
|
+
holdFees: false,
|
|
913
|
+
useTotalSurplusForCashOuts: false,
|
|
914
|
+
useDataHookForPay: true,
|
|
915
|
+
useDataHookForCashOut: true,
|
|
916
|
+
dataHook: metadata.dataHook,
|
|
917
|
+
metadata: uint16(
|
|
918
|
+
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
919
|
+
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
}),
|
|
923
|
+
// No more payouts.
|
|
924
|
+
splitGroups: new JBSplitGroup[](0),
|
|
925
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
875
926
|
});
|
|
876
|
-
_normalizedTotal += _defifaNormalized;
|
|
877
927
|
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
preferAddToBalance: false,
|
|
882
|
-
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT - _normalizedTotal),
|
|
883
|
-
projectId: uint64(baseProtocolProjectId),
|
|
884
|
-
beneficiary: payable(address(_dataHook)),
|
|
885
|
-
lockedUntil: 0,
|
|
886
|
-
hook: IJBSplitHook(address(0))
|
|
928
|
+
// slither-disable-next-line unused-return
|
|
929
|
+
CONTROLLER.queueRulesetsOf({
|
|
930
|
+
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
887
931
|
});
|
|
888
|
-
|
|
889
|
-
// Build the grouped split for the payment of the game token.
|
|
890
|
-
JBSplitGroup[] memory _groupedSplits = new JBSplitGroup[](1);
|
|
891
|
-
_groupedSplits[0] = JBSplitGroup({groupId: uint256(uint160(_token)), splits: _splits});
|
|
892
|
-
|
|
893
|
-
return _groupedSplits;
|
|
894
932
|
}
|
|
895
933
|
}
|