@ballkidz/defifa 0.0.11 → 0.0.13
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/AUDIT_INSTRUCTIONS.md +2 -2
- package/CHANGE_LOG.md +60 -5
- package/CRYPTO_ECON.md +505 -270
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +438 -241
- package/RISKS.md +9 -1
- package/SKILLS.md +3 -2
- package/STYLE_GUIDE.md +2 -2
- package/foundry.toml +1 -1
- package/package.json +7 -7
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/DefifaDeploymentLib.sol +1 -1
- package/src/DefifaDeployer.sol +129 -131
- package/src/DefifaGovernor.sol +279 -84
- package/src/DefifaHook.sol +159 -172
- package/src/DefifaProjectOwner.sol +1 -1
- package/src/DefifaTokenUriResolver.sol +1 -1
- package/src/enums/DefifaScorecardState.sol +1 -0
- package/src/interfaces/IDefifaGovernor.sol +41 -2
- package/src/libraries/DefifaFontImporter.sol +1 -1
- package/src/libraries/DefifaHookLib.sol +70 -63
- package/src/structs/DefifaAttestations.sol +3 -3
- package/src/structs/DefifaLaunchProjectData.sol +1 -0
- package/src/structs/DefifaScorecard.sol +2 -0
- package/test/BWAFunctionComparison.t.sol +1320 -0
- package/test/DefifaAdversarialQuorum.t.sol +53 -38
- package/test/DefifaAuditLowGuards.t.sol +10 -6
- package/test/DefifaFeeAccounting.t.sol +3 -2
- package/test/DefifaGovernanceHardening.t.sol +1311 -0
- package/test/DefifaGovernor.t.sol +5 -3
- package/test/DefifaHookRegressions.t.sol +3 -2
- package/test/DefifaMintCostInvariant.t.sol +3 -2
- package/test/DefifaNoContest.t.sol +4 -3
- package/test/DefifaSecurity.t.sol +55 -42
- package/test/DefifaUSDC.t.sol +4 -3
- package/test/Fork.t.sol +12 -13
- package/test/SVG.t.sol +1 -1
- package/test/TestAuditGaps.sol +7 -5
- package/test/TestQALastMile.t.sol +5 -3
- package/test/audit/{CodexAttestationDoubleCount.t.sol → AttestationDoubleCount.t.sol} +4 -3
- package/test/audit/FixPendingReserveDilution.t.sol +366 -0
- package/test/audit/PendingReserveDilution.t.sol +298 -0
- package/test/audit/PendingReserveQuorumGrief.t.sol +355 -0
- package/test/deployScript.t.sol +1 -1
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -2
- package/test/regression/FulfillmentBlocksRatification.t.sol +3 -2
- package/test/regression/GracePeriodBypass.t.sol +3 -2
package/src/DefifaDeployer.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
5
5
|
import {
|
|
@@ -143,22 +143,22 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
143
143
|
returns (uint256, address, uint256)
|
|
144
144
|
{
|
|
145
145
|
// Get a reference to the token being used by the project.
|
|
146
|
-
address
|
|
146
|
+
address token = _opsOf[gameId].token;
|
|
147
147
|
|
|
148
148
|
// Get a reference to the terminal.
|
|
149
|
-
IJBTerminal
|
|
149
|
+
IJBTerminal terminal = CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: token});
|
|
150
150
|
|
|
151
151
|
// Get the accounting context for the project.
|
|
152
|
-
JBAccountingContext memory
|
|
152
|
+
JBAccountingContext memory context = terminal.accountingContextForTokenOf({projectId: gameId, token: token});
|
|
153
153
|
|
|
154
154
|
// Get the current balance.
|
|
155
|
-
uint256
|
|
156
|
-
.balanceOf({terminal: address(
|
|
155
|
+
uint256 pot = IJBMultiTerminal(address(terminal)).STORE()
|
|
156
|
+
.balanceOf({terminal: address(terminal), projectId: gameId, token: token});
|
|
157
157
|
|
|
158
158
|
// Add any fulfilled commitments.
|
|
159
|
-
if (includeCommitments)
|
|
159
|
+
if (includeCommitments) pot += fulfilledCommitmentsOf[gameId];
|
|
160
160
|
|
|
161
|
-
return (
|
|
161
|
+
return (pot, token, context.decimals);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/// @notice Whether or not the next phase still needs queuing.
|
|
@@ -166,13 +166,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
166
166
|
/// @return Whether or not the next phase still needs queuing.
|
|
167
167
|
function nextPhaseNeedsQueueing(uint256 gameId) external view override returns (bool) {
|
|
168
168
|
// Get the game's current funding cycle along with its metadata.
|
|
169
|
-
JBRuleset memory
|
|
169
|
+
JBRuleset memory currentRuleset = CONTROLLER.RULESETS().currentOf(gameId);
|
|
170
170
|
// Get the game's queued funding cycle along with its metadata.
|
|
171
171
|
// slither-disable-next-line unused-return
|
|
172
|
-
(JBRuleset memory
|
|
172
|
+
(JBRuleset memory queuedRuleset,) = CONTROLLER.RULESETS().latestQueuedOf(gameId);
|
|
173
173
|
|
|
174
174
|
// If the configurations are the same and the game hasn't ended, queueing is still needed.
|
|
175
|
-
return
|
|
175
|
+
return currentRuleset.duration != 0 && currentRuleset.id == queuedRuleset.id;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/// @notice The safety mechanism parameters of a game.
|
|
@@ -185,8 +185,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
185
185
|
override
|
|
186
186
|
returns (uint256 minParticipation, uint32 scorecardTimeout)
|
|
187
187
|
{
|
|
188
|
-
DefifaOpsData memory
|
|
189
|
-
return (
|
|
188
|
+
DefifaOpsData memory ops = _opsOf[gameId];
|
|
189
|
+
return (ops.minParticipation, ops.scorecardTimeout);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/// @notice The game times.
|
|
@@ -195,8 +195,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
195
195
|
/// @return The game's minting period duration, in seconds.
|
|
196
196
|
/// @return The game's refund period duration, in seconds.
|
|
197
197
|
function timesFor(uint256 gameId) external view override returns (uint48, uint24, uint24) {
|
|
198
|
-
DefifaOpsData memory
|
|
199
|
-
return (
|
|
198
|
+
DefifaOpsData memory ops = _opsOf[gameId];
|
|
199
|
+
return (ops.start, ops.mintPeriodDuration, ops.refundPeriodDuration);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/// @notice The token of a game.
|
|
@@ -218,36 +218,36 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
218
218
|
/// @return The game phase.
|
|
219
219
|
function currentGamePhaseOf(uint256 gameId) public view override returns (DefifaGamePhase) {
|
|
220
220
|
// Get the game's current funding cycle along with its metadata.
|
|
221
|
-
(JBRuleset memory
|
|
221
|
+
(JBRuleset memory currentRuleset, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
222
222
|
|
|
223
|
-
if (
|
|
224
|
-
if (
|
|
225
|
-
if (
|
|
223
|
+
if (currentRuleset.cycleNumber == 0) return DefifaGamePhase.COUNTDOWN;
|
|
224
|
+
if (currentRuleset.cycleNumber == 1) return DefifaGamePhase.MINT;
|
|
225
|
+
if (currentRuleset.cycleNumber == 2 && _opsOf[gameId].refundPeriodDuration != 0) {
|
|
226
226
|
return DefifaGamePhase.REFUND;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Check if the scorecard has been ratified (game is COMPLETE).
|
|
230
230
|
// This takes priority over all NO_CONTEST checks — a ratified scorecard is final.
|
|
231
|
-
if (IDefifaHook(
|
|
231
|
+
if (IDefifaHook(metadata.dataHook).cashOutWeightIsSet()) return DefifaGamePhase.COMPLETE;
|
|
232
232
|
|
|
233
233
|
// If no-contest has already been triggered, stay in NO_CONTEST.
|
|
234
234
|
if (noContestTriggeredFor[gameId]) return DefifaGamePhase.NO_CONTEST;
|
|
235
235
|
|
|
236
236
|
// Get the game's ops data for the safety mechanism checks.
|
|
237
|
-
DefifaOpsData memory
|
|
237
|
+
DefifaOpsData memory ops = _opsOf[gameId];
|
|
238
238
|
|
|
239
239
|
// Check minimum participation threshold: if the treasury balance is below the threshold, the game is
|
|
240
240
|
// NO_CONTEST.
|
|
241
|
-
if (
|
|
242
|
-
IJBTerminal
|
|
243
|
-
uint256
|
|
244
|
-
.balanceOf({terminal: address(
|
|
245
|
-
if (
|
|
241
|
+
if (ops.minParticipation > 0) {
|
|
242
|
+
IJBTerminal terminal = CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: ops.token});
|
|
243
|
+
uint256 balance = IJBMultiTerminal(address(terminal)).STORE()
|
|
244
|
+
.balanceOf({terminal: address(terminal), projectId: gameId, token: ops.token});
|
|
245
|
+
if (balance < ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// Check scorecard ratification timeout: if enough time has passed without a ratified scorecard, the game is
|
|
249
249
|
// NO_CONTEST.
|
|
250
|
-
if (
|
|
250
|
+
if (ops.scorecardTimeout > 0 && block.timestamp > currentRuleset.start + ops.scorecardTimeout) {
|
|
251
251
|
return DefifaGamePhase.NO_CONTEST;
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -297,48 +297,48 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
297
297
|
|
|
298
298
|
// Get the game's current funding cycle along with its metadata.
|
|
299
299
|
// slither-disable-next-line unused-return
|
|
300
|
-
(, JBRulesetMetadata memory
|
|
300
|
+
(, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
301
301
|
|
|
302
302
|
// Make sure the game's commitments can be fulfilled.
|
|
303
|
-
if (!IDefifaHook(
|
|
303
|
+
if (!IDefifaHook(metadata.dataHook).cashOutWeightIsSet()) {
|
|
304
304
|
revert DefifaDeployer_CantFulfillYet();
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// Get the game token and the terminal.
|
|
308
|
-
address
|
|
309
|
-
IJBMultiTerminal
|
|
310
|
-
IJBMultiTerminal(address(CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token:
|
|
308
|
+
address token = _opsOf[gameId].token;
|
|
309
|
+
IJBMultiTerminal terminal =
|
|
310
|
+
IJBMultiTerminal(address(CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: token})));
|
|
311
311
|
|
|
312
312
|
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
313
|
-
uint256
|
|
313
|
+
uint256 pot = terminal.STORE().balanceOf({terminal: address(terminal), projectId: gameId, token: token});
|
|
314
314
|
|
|
315
315
|
// If the pot is empty, set the sentinel and queue the final ruleset without attempting payouts.
|
|
316
316
|
// slither-disable-next-line incorrect-equality
|
|
317
|
-
if (
|
|
317
|
+
if (pot == 0) {
|
|
318
318
|
fulfilledCommitmentsOf[gameId] = 1;
|
|
319
|
-
_queueFinalRuleset({gameId: gameId, metadata:
|
|
319
|
+
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
320
320
|
emit FulfilledCommitments({gameId: gameId, pot: 0, caller: msg.sender});
|
|
321
321
|
return;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
// Compute the fee amount based on the total absolute split percent stored at game creation.
|
|
325
|
-
uint256
|
|
326
|
-
mulDiv({x:
|
|
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
|
-
fulfilledCommitmentsOf[gameId] =
|
|
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
333
|
// Wrapped in try-catch so the final ruleset is always queued even if payout fails.
|
|
334
334
|
// slither-disable-next-line unused-return,reentrancy-no-eth
|
|
335
|
-
try
|
|
335
|
+
try terminal.sendPayoutsOf({
|
|
336
336
|
projectId: gameId,
|
|
337
|
-
token:
|
|
338
|
-
amount:
|
|
337
|
+
token: token,
|
|
338
|
+
amount: feeAmount,
|
|
339
339
|
// Casting address to uint32 via uint160 is the standard Juicebox token-to-currency conversion.
|
|
340
340
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
341
|
-
currency:
|
|
341
|
+
currency: token == JBConstants.NATIVE_TOKEN ? metadata.baseCurrency : uint32(uint160(token)),
|
|
342
342
|
minTokensPaidOut: 0
|
|
343
343
|
}) {}
|
|
344
344
|
catch (bytes memory reason) {
|
|
@@ -346,14 +346,14 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
346
346
|
// doesn't double-count the fee, while preserving the reentrancy guard.
|
|
347
347
|
fulfilledCommitmentsOf[gameId] = 1;
|
|
348
348
|
// slither-disable-next-line reentrancy-events
|
|
349
|
-
emit CommitmentPayoutFailed({gameId: gameId, amount:
|
|
349
|
+
emit CommitmentPayoutFailed({gameId: gameId, amount: feeAmount, reason: reason});
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
// Queue the final ruleset and emit.
|
|
353
|
-
_queueFinalRuleset({gameId: gameId, metadata:
|
|
353
|
+
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
354
354
|
|
|
355
355
|
// slither-disable-next-line reentrancy-events
|
|
356
|
-
emit FulfilledCommitments({gameId: gameId, pot:
|
|
356
|
+
emit FulfilledCommitments({gameId: gameId, pot: pot, caller: msg.sender});
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
/// @notice Launches a new game owned by this contract with a DefifaHook attached.
|
|
@@ -390,7 +390,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
390
390
|
// Get the game ID, optimistically knowing it will be one greater than the current count.
|
|
391
391
|
// Note: this prediction can race with other concurrent project deployments. If another project is
|
|
392
392
|
// created between reading count() and launchProjectFor(), the actual ID will differ. This is
|
|
393
|
-
// caught by the equality check after launch (gameId !=
|
|
393
|
+
// caught by the equality check after launch (gameId != actualGameId reverts).
|
|
394
394
|
gameId = CONTROLLER.PROJECTS().count() + 1;
|
|
395
395
|
|
|
396
396
|
{
|
|
@@ -405,24 +405,24 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
405
405
|
});
|
|
406
406
|
|
|
407
407
|
// Keep a reference to the number of splits.
|
|
408
|
-
uint256
|
|
408
|
+
uint256 numberOfSplits = launchProjectData.splits.length;
|
|
409
409
|
|
|
410
410
|
// If there are splits being added, store the fee alongside. The fee will otherwise be added later.
|
|
411
|
-
if (
|
|
411
|
+
if (numberOfSplits != 0) {
|
|
412
412
|
// Make a new splits where fees will be added to.
|
|
413
|
-
JBSplit[] memory
|
|
413
|
+
JBSplit[] memory splits = new JBSplit[](launchProjectData.splits.length + 1);
|
|
414
414
|
|
|
415
415
|
// Copy the splits over.
|
|
416
|
-
for (uint256
|
|
416
|
+
for (uint256 i; i < numberOfSplits;) {
|
|
417
417
|
// Copy the split over.
|
|
418
|
-
|
|
418
|
+
splits[i] = launchProjectData.splits[i];
|
|
419
419
|
unchecked {
|
|
420
|
-
++
|
|
420
|
+
++i;
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
// Add a split for the fee.
|
|
425
|
-
|
|
425
|
+
splits[numberOfSplits] = JBSplit({
|
|
426
426
|
preferAddToBalance: false,
|
|
427
427
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
428
428
|
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT / DEFIFA_FEE_DIVISOR),
|
|
@@ -434,44 +434,44 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
434
434
|
});
|
|
435
435
|
|
|
436
436
|
// Store the splits.
|
|
437
|
-
JBSplitGroup[] memory
|
|
438
|
-
|
|
437
|
+
JBSplitGroup[] memory groupedSplits = new JBSplitGroup[](1);
|
|
438
|
+
groupedSplits[0] = JBSplitGroup({groupId: SPLIT_GROUP, splits: splits});
|
|
439
439
|
|
|
440
440
|
// This contract must have SET_SPLIT_GROUPS permission from the defifa project owner.
|
|
441
441
|
CONTROLLER.setSplitGroupsOf({
|
|
442
|
-
projectId: DEFIFA_PROJECT_ID, rulesetId: gameId, splitGroups:
|
|
442
|
+
projectId: DEFIFA_PROJECT_ID, rulesetId: gameId, splitGroups: groupedSplits
|
|
443
443
|
});
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
// Keep track of the number of tiers.
|
|
448
|
-
uint256
|
|
448
|
+
uint256 numberOfTiers = launchProjectData.tiers.length;
|
|
449
449
|
|
|
450
450
|
// Create the standard tiers struct that will be populated from the defifa tiers.
|
|
451
|
-
JB721TierConfig[] memory
|
|
451
|
+
JB721TierConfig[] memory hookTiers = new JB721TierConfig[](launchProjectData.tiers.length);
|
|
452
452
|
|
|
453
453
|
// Group all the tier names together.
|
|
454
|
-
string[] memory
|
|
454
|
+
string[] memory tierNames = new string[](launchProjectData.tiers.length);
|
|
455
455
|
|
|
456
456
|
// Keep a reference to the tier being iterated on.
|
|
457
|
-
DefifaTierParams memory
|
|
457
|
+
DefifaTierParams memory defifaTier;
|
|
458
458
|
|
|
459
459
|
// Create the hook tiers from the Defifa tiers.
|
|
460
|
-
for (uint256
|
|
461
|
-
|
|
460
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
461
|
+
defifaTier = launchProjectData.tiers[i];
|
|
462
462
|
|
|
463
463
|
// Set the tier. All tiers use the same price so that price-based voting power is equal.
|
|
464
|
-
|
|
464
|
+
hookTiers[i] = JB721TierConfig({
|
|
465
465
|
price: launchProjectData.tierPrice,
|
|
466
466
|
initialSupply: 999_999_999, // Uncapped minting — max value allowed by the 721 store.
|
|
467
467
|
votingUnits: 0,
|
|
468
|
-
reserveFrequency:
|
|
469
|
-
reserveBeneficiary:
|
|
470
|
-
encodedIPFSUri:
|
|
468
|
+
reserveFrequency: defifaTier.reservedRate,
|
|
469
|
+
reserveBeneficiary: defifaTier.reservedTokenBeneficiary,
|
|
470
|
+
encodedIPFSUri: defifaTier.encodedIPFSUri,
|
|
471
471
|
category: 0,
|
|
472
472
|
discountPercent: 0,
|
|
473
473
|
allowOwnerMint: false,
|
|
474
|
-
useReserveBeneficiaryAsDefault:
|
|
474
|
+
useReserveBeneficiaryAsDefault: defifaTier.shouldUseReservedTokenBeneficiaryAsDefault,
|
|
475
475
|
transfersPausable: false,
|
|
476
476
|
useVotingUnits: false,
|
|
477
477
|
cannotBeRemoved: true,
|
|
@@ -481,72 +481,73 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
481
481
|
});
|
|
482
482
|
|
|
483
483
|
// Set the name.
|
|
484
|
-
|
|
484
|
+
tierNames[i] = defifaTier.name;
|
|
485
485
|
|
|
486
486
|
unchecked {
|
|
487
|
-
++
|
|
487
|
+
++i;
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
// Increment the nonce for this deployment.
|
|
492
|
-
uint256
|
|
492
|
+
uint256 currentNonce = ++_nonce;
|
|
493
493
|
|
|
494
494
|
// Clone deterministically using sender and nonce to prevent front-running.
|
|
495
495
|
// Clones.clone() creates the proxy before initialize() is called, allowing an
|
|
496
496
|
// attacker to front-run initialization and DOS the game deployment. Using
|
|
497
497
|
// cloneDeterministic with msg.sender in the salt prevents this since a different
|
|
498
498
|
// caller produces a different address.
|
|
499
|
-
DefifaHook
|
|
499
|
+
DefifaHook hook = DefifaHook(
|
|
500
500
|
Clones.cloneDeterministic({
|
|
501
|
-
implementation: HOOK_CODE_ORIGIN, salt: keccak256(abi.encodePacked(msg.sender,
|
|
501
|
+
implementation: HOOK_CODE_ORIGIN, salt: keccak256(abi.encodePacked(msg.sender, currentNonce))
|
|
502
502
|
})
|
|
503
503
|
);
|
|
504
504
|
|
|
505
505
|
// Use the default uri resolver if provided, else use the hardcoded generic default.
|
|
506
|
-
IJB721TokenUriResolver
|
|
506
|
+
IJB721TokenUriResolver uriResolver = launchProjectData.defaultTokenUriResolver
|
|
507
507
|
!= IJB721TokenUriResolver(address(0))
|
|
508
508
|
? launchProjectData.defaultTokenUriResolver
|
|
509
509
|
: TOKEN_URI_RESOLVER;
|
|
510
510
|
|
|
511
|
-
|
|
511
|
+
hook.initialize({
|
|
512
512
|
_gameId: gameId,
|
|
513
513
|
_name: launchProjectData.name,
|
|
514
514
|
_symbol: string.concat("DEFIFA #", gameId.toString()),
|
|
515
515
|
_rulesets: CONTROLLER.RULESETS(),
|
|
516
516
|
_baseUri: launchProjectData.baseUri,
|
|
517
|
-
_tokenUriResolver:
|
|
517
|
+
_tokenUriResolver: uriResolver,
|
|
518
518
|
_contractUri: launchProjectData.contractUri,
|
|
519
|
-
_tiers:
|
|
519
|
+
_tiers: hookTiers,
|
|
520
520
|
_currency: launchProjectData.token.currency,
|
|
521
521
|
_store: launchProjectData.store,
|
|
522
522
|
_gamePhaseReporter: this,
|
|
523
523
|
_gamePotReporter: this,
|
|
524
524
|
_defaultAttestationDelegate: launchProjectData.defaultAttestationDelegate,
|
|
525
|
-
_tierNames:
|
|
525
|
+
_tierNames: tierNames
|
|
526
526
|
});
|
|
527
527
|
|
|
528
528
|
// Launch the Juicebox project.
|
|
529
|
-
uint256
|
|
530
|
-
_launchGame({launchProjectData: launchProjectData,
|
|
529
|
+
uint256 actualGameId =
|
|
530
|
+
_launchGame({launchProjectData: launchProjectData, gameId: gameId, dataHook: address(hook)});
|
|
531
531
|
|
|
532
532
|
// Revert if the game ID does not match (e.g. front-run by another project creation).
|
|
533
|
-
if (gameId !=
|
|
533
|
+
if (gameId != actualGameId) revert DefifaDeployer_InvalidGameConfiguration();
|
|
534
534
|
|
|
535
535
|
// Clone and initialize the new governor.
|
|
536
536
|
GOVERNOR.initializeGame({
|
|
537
537
|
gameId: gameId,
|
|
538
538
|
attestationStartTime: uint256(launchProjectData.attestationStartTime),
|
|
539
|
-
attestationGracePeriod: uint256(launchProjectData.attestationGracePeriod)
|
|
539
|
+
attestationGracePeriod: uint256(launchProjectData.attestationGracePeriod),
|
|
540
|
+
timelockDuration: launchProjectData.timelockDuration
|
|
540
541
|
});
|
|
541
542
|
|
|
542
543
|
// Transfer ownership to the specified owner.
|
|
543
|
-
|
|
544
|
+
hook.transferOwnership(address(GOVERNOR));
|
|
544
545
|
|
|
545
546
|
// Add the hook to the registry, contract nonce starts at 1
|
|
546
|
-
REGISTRY.registerAddress({deployer: address(this), nonce:
|
|
547
|
+
REGISTRY.registerAddress({deployer: address(this), nonce: currentNonce});
|
|
547
548
|
|
|
548
549
|
// slither-disable-next-line reentrancy-events
|
|
549
|
-
emit LaunchGame(gameId,
|
|
550
|
+
emit LaunchGame(gameId, hook, GOVERNOR, uriResolver, msg.sender);
|
|
550
551
|
}
|
|
551
552
|
|
|
552
553
|
/// @notice Allows this contract to receive 721s.
|
|
@@ -577,7 +578,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
577
578
|
|
|
578
579
|
// Get the game's current ruleset metadata for the data hook address.
|
|
579
580
|
// slither-disable-next-line unused-return
|
|
580
|
-
(, JBRulesetMetadata memory
|
|
581
|
+
(, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
581
582
|
|
|
582
583
|
// Queue a new ruleset without payout limits so surplus = balance, enabling refunds.
|
|
583
584
|
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
@@ -590,7 +591,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
590
591
|
metadata: JBRulesetMetadata({
|
|
591
592
|
reservedPercent: 0,
|
|
592
593
|
cashOutTaxRate: 0,
|
|
593
|
-
baseCurrency:
|
|
594
|
+
baseCurrency: metadata.baseCurrency,
|
|
594
595
|
pausePay: true,
|
|
595
596
|
pauseCreditTransfers: false,
|
|
596
597
|
allowOwnerMinting: false,
|
|
@@ -605,7 +606,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
605
606
|
useTotalSurplusForCashOuts: false,
|
|
606
607
|
useDataHookForPay: true,
|
|
607
608
|
useDataHookForCashOut: true,
|
|
608
|
-
dataHook:
|
|
609
|
+
dataHook: metadata.dataHook,
|
|
609
610
|
metadata: uint16(
|
|
610
611
|
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
611
612
|
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
@@ -631,93 +632,90 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
631
632
|
//*********************************************************************//
|
|
632
633
|
|
|
633
634
|
function _buildSplits(
|
|
634
|
-
uint256
|
|
635
|
-
address
|
|
636
|
-
address
|
|
637
|
-
JBSplit[] memory
|
|
635
|
+
uint256 gameId,
|
|
636
|
+
address dataHook,
|
|
637
|
+
address token,
|
|
638
|
+
JBSplit[] memory initialSplits
|
|
638
639
|
)
|
|
639
640
|
internal
|
|
640
641
|
returns (JBSplitGroup[] memory)
|
|
641
642
|
{
|
|
642
|
-
uint256
|
|
643
|
+
uint256 numberOfUserSplits = initialSplits.length;
|
|
643
644
|
|
|
644
645
|
// Compute absolute percents for protocol fees.
|
|
645
|
-
uint256
|
|
646
|
-
uint256
|
|
646
|
+
uint256 nanaAbsolutePercent = JBConstants.SPLITS_TOTAL_PERCENT / BASE_PROTOCOL_FEE_DIVISOR;
|
|
647
|
+
uint256 defifaAbsolutePercent = JBConstants.SPLITS_TOTAL_PERCENT / DEFIFA_FEE_DIVISOR;
|
|
647
648
|
|
|
648
649
|
// Sum all absolute percents.
|
|
649
|
-
uint256
|
|
650
|
-
for (uint256
|
|
651
|
-
|
|
650
|
+
uint256 totalAbsolutePercent = nanaAbsolutePercent + defifaAbsolutePercent;
|
|
651
|
+
for (uint256 i; i < numberOfUserSplits; i++) {
|
|
652
|
+
totalAbsolutePercent += initialSplits[i].percent;
|
|
652
653
|
}
|
|
653
654
|
|
|
654
655
|
// Validate that total fee splits don't exceed 100%.
|
|
655
|
-
if (
|
|
656
|
+
if (totalAbsolutePercent > JBConstants.SPLITS_TOTAL_PERCENT) revert DefifaDeployer_SplitsDontAddUp();
|
|
656
657
|
|
|
657
658
|
// Store the total absolute percent for use in fulfillCommitmentsOf.
|
|
658
|
-
_commitmentPercentOf[
|
|
659
|
+
_commitmentPercentOf[gameId] = totalAbsolutePercent;
|
|
659
660
|
|
|
660
661
|
// Build the splits array: user splits + Defifa + NANA (NANA last to absorb rounding).
|
|
661
|
-
uint256
|
|
662
|
-
JBSplit[] memory
|
|
662
|
+
uint256 splitCount = numberOfUserSplits + 2;
|
|
663
|
+
JBSplit[] memory splits = new JBSplit[](splitCount);
|
|
663
664
|
|
|
664
665
|
// Normalize user splits and copy them over.
|
|
665
|
-
uint256
|
|
666
|
-
for (uint256
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
uint256 normalizedTotal;
|
|
667
|
+
for (uint256 i; i < numberOfUserSplits; i++) {
|
|
668
|
+
splits[i] = initialSplits[i];
|
|
669
|
+
splits[i].percent = uint32(
|
|
669
670
|
mulDiv({
|
|
670
|
-
x:
|
|
671
|
-
y: JBConstants.SPLITS_TOTAL_PERCENT,
|
|
672
|
-
denominator: _totalAbsolutePercent
|
|
671
|
+
x: initialSplits[i].percent, y: JBConstants.SPLITS_TOTAL_PERCENT, denominator: totalAbsolutePercent
|
|
673
672
|
})
|
|
674
673
|
);
|
|
675
|
-
|
|
674
|
+
normalizedTotal += splits[i].percent;
|
|
676
675
|
}
|
|
677
676
|
|
|
678
677
|
// Add Defifa fee split (normalized).
|
|
679
|
-
uint256
|
|
680
|
-
x:
|
|
681
|
-
|
|
682
|
-
_splits[_numberOfUserSplits] = JBSplit({
|
|
678
|
+
uint256 defifaNormalized =
|
|
679
|
+
mulDiv({x: defifaAbsolutePercent, y: JBConstants.SPLITS_TOTAL_PERCENT, denominator: totalAbsolutePercent});
|
|
680
|
+
splits[numberOfUserSplits] = JBSplit({
|
|
683
681
|
preferAddToBalance: false,
|
|
684
682
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
685
|
-
percent: uint32(
|
|
683
|
+
percent: uint32(defifaNormalized),
|
|
686
684
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
687
685
|
projectId: uint64(DEFIFA_PROJECT_ID),
|
|
688
|
-
beneficiary: payable(address(
|
|
686
|
+
beneficiary: payable(address(dataHook)),
|
|
689
687
|
lockedUntil: 0,
|
|
690
688
|
hook: IJBSplitHook(address(0))
|
|
691
689
|
});
|
|
692
|
-
|
|
690
|
+
normalizedTotal += defifaNormalized;
|
|
693
691
|
|
|
694
692
|
// Add NANA protocol fee split last — absorbs rounding remainder from normalization.
|
|
695
693
|
// Because mulDiv rounds down, the sum of normalized percents can be slightly less than SPLITS_TOTAL_PERCENT.
|
|
696
694
|
// The NANA split receives the difference, so its effective percent may be a few basis points above its
|
|
697
695
|
// proportional share. This is economically negligible (< 1 bps at typical split counts).
|
|
698
696
|
// Beneficiary is the data hook so the hook receives NANA tokens for distribution during cash-outs.
|
|
699
|
-
|
|
697
|
+
splits[splitCount - 1] = JBSplit({
|
|
700
698
|
preferAddToBalance: false,
|
|
701
699
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
702
|
-
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT -
|
|
700
|
+
percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT - normalizedTotal),
|
|
703
701
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
704
702
|
projectId: uint64(BASE_PROTOCOL_PROJECT_ID),
|
|
705
|
-
beneficiary: payable(address(
|
|
703
|
+
beneficiary: payable(address(dataHook)),
|
|
706
704
|
lockedUntil: 0,
|
|
707
705
|
hook: IJBSplitHook(address(0))
|
|
708
706
|
});
|
|
709
707
|
|
|
710
708
|
// Build the grouped split for the payment of the game token.
|
|
711
|
-
JBSplitGroup[] memory
|
|
712
|
-
|
|
709
|
+
JBSplitGroup[] memory groupedSplits = new JBSplitGroup[](1);
|
|
710
|
+
groupedSplits[0] = JBSplitGroup({groupId: uint256(uint160(token)), splits: splits});
|
|
713
711
|
|
|
714
|
-
return
|
|
712
|
+
return groupedSplits;
|
|
715
713
|
}
|
|
716
714
|
|
|
717
715
|
function _launchGame(
|
|
718
716
|
DefifaLaunchProjectData memory launchProjectData,
|
|
719
|
-
uint256
|
|
720
|
-
address
|
|
717
|
+
uint256 gameId,
|
|
718
|
+
address dataHook
|
|
721
719
|
)
|
|
722
720
|
internal
|
|
723
721
|
returns (uint256 projectId)
|
|
@@ -761,7 +759,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
761
759
|
useTotalSurplusForCashOuts: false,
|
|
762
760
|
useDataHookForPay: true,
|
|
763
761
|
useDataHookForCashOut: true,
|
|
764
|
-
dataHook:
|
|
762
|
+
dataHook: dataHook,
|
|
765
763
|
metadata: uint16(
|
|
766
764
|
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
767
765
|
JB721TiersRulesetMetadata({
|
|
@@ -804,7 +802,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
804
802
|
useTotalSurplusForCashOuts: false,
|
|
805
803
|
useDataHookForPay: true,
|
|
806
804
|
useDataHookForCashOut: true,
|
|
807
|
-
dataHook:
|
|
805
|
+
dataHook: dataHook,
|
|
808
806
|
metadata: uint16(
|
|
809
807
|
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
810
808
|
JB721TiersRulesetMetadata({
|
|
@@ -862,7 +860,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
862
860
|
useTotalSurplusForCashOuts: false,
|
|
863
861
|
useDataHookForPay: true,
|
|
864
862
|
useDataHookForCashOut: true,
|
|
865
|
-
dataHook:
|
|
863
|
+
dataHook: dataHook,
|
|
866
864
|
metadata: uint16(
|
|
867
865
|
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
868
866
|
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
@@ -870,10 +868,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
870
868
|
)
|
|
871
869
|
}),
|
|
872
870
|
splitGroups: _buildSplits({
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
871
|
+
gameId: gameId,
|
|
872
|
+
dataHook: dataHook,
|
|
873
|
+
token: launchProjectData.token.token,
|
|
874
|
+
initialSplits: launchProjectData.splits
|
|
877
875
|
}),
|
|
878
876
|
fundAccessLimitGroups: fundAccessConstraints
|
|
879
877
|
});
|