@ballkidz/defifa 0.0.7 → 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/USER_JOURNEYS.md +691 -0
- package/package.json +7 -7
- package/script/Deploy.s.sol +14 -3
- package/script/helpers/DefifaDeploymentLib.sol +13 -15
- package/src/DefifaDeployer.sol +221 -192
- package/src/DefifaGovernor.sol +286 -276
- package/src/DefifaHook.sol +65 -32
- package/src/DefifaProjectOwner.sol +27 -4
- package/src/DefifaTokenUriResolver.sol +136 -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 +66 -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,12 +307,19 @@ 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
325
|
uint256 _feeAmount =
|
|
@@ -331,59 +330,26 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
331
330
|
fulfilledCommitmentsOf[gameId] = _feeAmount > 0 ? _feeAmount : 1;
|
|
332
331
|
|
|
333
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.
|
|
334
334
|
// slither-disable-next-line unused-return
|
|
335
|
-
_terminal.sendPayoutsOf({
|
|
335
|
+
try _terminal.sendPayoutsOf({
|
|
336
336
|
projectId: gameId,
|
|
337
337
|
token: _token,
|
|
338
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)
|
|
339
341
|
currency: _token == JBConstants.NATIVE_TOKEN ? _metadata.baseCurrency : uint32(uint160(_token)),
|
|
340
|
-
minTokensPaidOut:
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
weight: 0,
|
|
349
|
-
weightCutPercent: 0,
|
|
350
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
351
|
-
metadata: JBRulesetMetadata({
|
|
352
|
-
reservedPercent: 0,
|
|
353
|
-
cashOutTaxRate: 0,
|
|
354
|
-
baseCurrency: _metadata.baseCurrency,
|
|
355
|
-
pausePay: true,
|
|
356
|
-
pauseCreditTransfers: false,
|
|
357
|
-
allowOwnerMinting: false,
|
|
358
|
-
allowSetCustomToken: false,
|
|
359
|
-
allowTerminalMigration: false,
|
|
360
|
-
allowSetTerminals: false,
|
|
361
|
-
allowSetController: false,
|
|
362
|
-
allowAddAccountingContext: false,
|
|
363
|
-
allowAddPriceFeed: false,
|
|
364
|
-
// Set this to true so only the deployer can fulfill the commitments.
|
|
365
|
-
ownerMustSendPayouts: true,
|
|
366
|
-
holdFees: false,
|
|
367
|
-
useTotalSurplusForCashOuts: false,
|
|
368
|
-
useDataHookForPay: true,
|
|
369
|
-
useDataHookForCashOut: true,
|
|
370
|
-
dataHook: _metadata.dataHook,
|
|
371
|
-
metadata: uint16(
|
|
372
|
-
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
373
|
-
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
374
|
-
)
|
|
375
|
-
)
|
|
376
|
-
}),
|
|
377
|
-
// No more payouts.
|
|
378
|
-
splitGroups: new JBSplitGroup[](0),
|
|
379
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
380
|
-
});
|
|
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
|
+
}
|
|
381
350
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
controller.queueRulesetsOf({
|
|
385
|
-
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
386
|
-
});
|
|
351
|
+
// Queue the final ruleset and emit.
|
|
352
|
+
_queueFinalRuleset({gameId: gameId, metadata: _metadata});
|
|
387
353
|
|
|
388
354
|
emit FulfilledCommitments({gameId: gameId, pot: _pot, caller: msg.sender});
|
|
389
355
|
}
|
|
@@ -420,7 +386,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
420
386
|
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
421
387
|
|
|
422
388
|
// Get the game ID, optimistically knowing it will be one greater than the current count.
|
|
423
|
-
|
|
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;
|
|
424
393
|
|
|
425
394
|
{
|
|
426
395
|
// Store the timestamps that'll define the game phases.
|
|
@@ -453,8 +422,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
453
422
|
// Add a split for the fee.
|
|
454
423
|
_splits[_numberOfSplits] = JBSplit({
|
|
455
424
|
preferAddToBalance: false,
|
|
425
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
456
426
|
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT / DEFIFA_FEE_DIVISOR),
|
|
457
|
-
|
|
427
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
428
|
+
projectId: uint64(DEFIFA_PROJECT_ID),
|
|
458
429
|
beneficiary: payable(address(this)),
|
|
459
430
|
lockedUntil: 0,
|
|
460
431
|
hook: IJBSplitHook(address(0))
|
|
@@ -462,11 +433,11 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
462
433
|
|
|
463
434
|
// Store the splits.
|
|
464
435
|
JBSplitGroup[] memory _groupedSplits = new JBSplitGroup[](1);
|
|
465
|
-
_groupedSplits[0] = JBSplitGroup({groupId:
|
|
436
|
+
_groupedSplits[0] = JBSplitGroup({groupId: SPLIT_GROUP, splits: _splits});
|
|
466
437
|
|
|
467
438
|
// This contract must have SET_SPLIT_GROUPS permission from the defifa project owner.
|
|
468
|
-
|
|
469
|
-
projectId:
|
|
439
|
+
CONTROLLER.setSplitGroupsOf({
|
|
440
|
+
projectId: DEFIFA_PROJECT_ID, rulesetId: gameId, splitGroups: _groupedSplits
|
|
470
441
|
});
|
|
471
442
|
}
|
|
472
443
|
}
|
|
@@ -525,7 +496,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
525
496
|
// caller produces a different address.
|
|
526
497
|
DefifaHook _hook = DefifaHook(
|
|
527
498
|
Clones.cloneDeterministic({
|
|
528
|
-
implementation:
|
|
499
|
+
implementation: HOOK_CODE_ORIGIN, salt: keccak256(abi.encodePacked(msg.sender, _currentNonce))
|
|
529
500
|
})
|
|
530
501
|
);
|
|
531
502
|
|
|
@@ -533,13 +504,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
533
504
|
IJB721TokenUriResolver _uriResolver = launchProjectData.defaultTokenUriResolver
|
|
534
505
|
!= IJB721TokenUriResolver(address(0))
|
|
535
506
|
? launchProjectData.defaultTokenUriResolver
|
|
536
|
-
:
|
|
507
|
+
: TOKEN_URI_RESOLVER;
|
|
537
508
|
|
|
538
509
|
_hook.initialize({
|
|
539
510
|
_gameId: gameId,
|
|
540
511
|
_name: launchProjectData.name,
|
|
541
512
|
_symbol: string.concat("DEFIFA #", gameId.toString()),
|
|
542
|
-
_rulesets:
|
|
513
|
+
_rulesets: CONTROLLER.RULESETS(),
|
|
543
514
|
_baseUri: launchProjectData.baseUri,
|
|
544
515
|
_tokenUriResolver: _uriResolver,
|
|
545
516
|
_contractUri: launchProjectData.contractUri,
|
|
@@ -560,19 +531,19 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
560
531
|
if (gameId != _actualGameId) revert DefifaDeployer_InvalidGameConfiguration();
|
|
561
532
|
|
|
562
533
|
// Clone and initialize the new governor.
|
|
563
|
-
|
|
534
|
+
GOVERNOR.initializeGame({
|
|
564
535
|
gameId: gameId,
|
|
565
536
|
attestationStartTime: uint256(launchProjectData.attestationStartTime),
|
|
566
537
|
attestationGracePeriod: uint256(launchProjectData.attestationGracePeriod)
|
|
567
538
|
});
|
|
568
539
|
|
|
569
540
|
// Transfer ownership to the specified owner.
|
|
570
|
-
_hook.transferOwnership(address(
|
|
541
|
+
_hook.transferOwnership(address(GOVERNOR));
|
|
571
542
|
|
|
572
543
|
// Add the hook to the registry, contract nonce starts at 1
|
|
573
|
-
|
|
544
|
+
REGISTRY.registerAddress({deployer: address(this), nonce: _currentNonce});
|
|
574
545
|
|
|
575
|
-
emit LaunchGame(gameId, _hook,
|
|
546
|
+
emit LaunchGame(gameId, _hook, GOVERNOR, _uriResolver, msg.sender);
|
|
576
547
|
}
|
|
577
548
|
|
|
578
549
|
/// @notice Allows this contract to receive 721s.
|
|
@@ -595,11 +566,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
595
566
|
if (noContestTriggeredFor[gameId]) revert DefifaDeployer_NoContestAlreadyTriggered();
|
|
596
567
|
|
|
597
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.
|
|
598
573
|
noContestTriggeredFor[gameId] = true;
|
|
599
574
|
|
|
600
575
|
// Get the game's current ruleset metadata for the data hook address.
|
|
601
576
|
// slither-disable-next-line unused-return
|
|
602
|
-
(, JBRulesetMetadata memory _metadata) =
|
|
577
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
603
578
|
|
|
604
579
|
// Queue a new ruleset without payout limits so surplus = balance, enabling refunds.
|
|
605
580
|
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
@@ -640,7 +615,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
640
615
|
|
|
641
616
|
// Queue the no-contest refund ruleset.
|
|
642
617
|
// slither-disable-next-line unused-return
|
|
643
|
-
|
|
618
|
+
CONTROLLER.queueRulesetsOf({
|
|
644
619
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game: no contest."
|
|
645
620
|
});
|
|
646
621
|
|
|
@@ -651,6 +626,90 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
651
626
|
// ------------------------ internal functions ----------------------- //
|
|
652
627
|
//*********************************************************************//
|
|
653
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
|
+
|
|
654
713
|
function _launchGame(
|
|
655
714
|
DefifaLaunchProjectData memory launchProjectData,
|
|
656
715
|
uint256 _gameId,
|
|
@@ -816,7 +875,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
816
875
|
});
|
|
817
876
|
|
|
818
877
|
// launch the project.
|
|
819
|
-
return
|
|
878
|
+
return CONTROLLER.launchProjectFor({
|
|
820
879
|
owner: address(this),
|
|
821
880
|
projectUri: launchProjectData.projectUri,
|
|
822
881
|
rulesetConfigurations: rulesetConfigs,
|
|
@@ -825,80 +884,50 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
825
884
|
});
|
|
826
885
|
}
|
|
827
886
|
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
denominator: _totalAbsolutePercent
|
|
868
|
-
})
|
|
869
|
-
);
|
|
870
|
-
_normalizedTotal += _splits[_i].percent;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Add Defifa fee split (normalized).
|
|
874
|
-
uint256 _defifaNormalized = mulDiv({
|
|
875
|
-
x: _defifaAbsolutePercent, y: JBConstants.SPLITS_TOTAL_PERCENT, denominator: _totalAbsolutePercent
|
|
876
|
-
});
|
|
877
|
-
_splits[_numberOfUserSplits] = JBSplit({
|
|
878
|
-
preferAddToBalance: false,
|
|
879
|
-
percent: uint32(_defifaNormalized),
|
|
880
|
-
projectId: uint64(defifaProjectId),
|
|
881
|
-
beneficiary: payable(address(_dataHook)),
|
|
882
|
-
lockedUntil: 0,
|
|
883
|
-
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)
|
|
884
926
|
});
|
|
885
|
-
_normalizedTotal += _defifaNormalized;
|
|
886
927
|
|
|
887
|
-
//
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
preferAddToBalance: false,
|
|
891
|
-
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT - _normalizedTotal),
|
|
892
|
-
projectId: uint64(baseProtocolProjectId),
|
|
893
|
-
beneficiary: payable(address(_dataHook)),
|
|
894
|
-
lockedUntil: 0,
|
|
895
|
-
hook: IJBSplitHook(address(0))
|
|
928
|
+
// slither-disable-next-line unused-return
|
|
929
|
+
CONTROLLER.queueRulesetsOf({
|
|
930
|
+
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
896
931
|
});
|
|
897
|
-
|
|
898
|
-
// Build the grouped split for the payment of the game token.
|
|
899
|
-
JBSplitGroup[] memory _groupedSplits = new JBSplitGroup[](1);
|
|
900
|
-
_groupedSplits[0] = JBSplitGroup({groupId: uint256(uint160(_token)), splits: _splits});
|
|
901
|
-
|
|
902
|
-
return _groupedSplits;
|
|
903
932
|
}
|
|
904
933
|
}
|