@ballkidz/defifa 0.0.28 → 0.0.30
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/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +2 -2
- package/CRYPTO_ECON.md +4 -4
- package/README.md +3 -3
- package/RISKS.md +1 -1
- package/SKILLS.md +1 -1
- package/STYLE_GUIDE.md +2 -50
- package/package.json +2 -2
- package/src/DefifaDeployer.sol +78 -61
- package/src/DefifaGovernor.sol +74 -63
- package/src/DefifaHook.sol +127 -183
- package/src/DefifaProjectOwner.sol +4 -2
- package/src/DefifaTokenUriResolver.sol +28 -16
- package/src/interfaces/IDefifaDeployer.sol +0 -16
- package/src/interfaces/IDefifaHook.sol +0 -4
- package/src/libraries/DefifaHookLib.sol +261 -151
package/ARCHITECTURE.md
CHANGED
|
@@ -115,7 +115,7 @@ The hook's completion math is intentionally phase-sensitive:
|
|
|
115
115
|
- fulfillment and ratification coupling:
|
|
116
116
|
`test/regression/FulfillmentBlocksRatification.t.sol`
|
|
117
117
|
- pending reserve effects on completion fairness:
|
|
118
|
-
`test/
|
|
118
|
+
`test/regression/PendingReserveSnapshotBypass.t.sol`
|
|
119
119
|
|
|
120
120
|
## Source Map
|
|
121
121
|
|
|
@@ -128,4 +128,4 @@ The hook's completion math is intentionally phase-sensitive:
|
|
|
128
128
|
- `test/DefifaHookRegressions.t.sol`
|
|
129
129
|
- `test/DefifaFeeAccounting.t.sol`
|
|
130
130
|
- `test/regression/FulfillmentBlocksRatification.t.sol`
|
|
131
|
-
- `test/
|
|
131
|
+
- `test/regression/PendingReserveSnapshotBypass.t.sol`
|
package/CHANGELOG.md
CHANGED
|
@@ -17,10 +17,10 @@ This file instead describes the current v6 repo at a high level and the broad mi
|
|
|
17
17
|
## Summary
|
|
18
18
|
|
|
19
19
|
- The repo is now built directly on the v6 Juicebox stack, including the v6 core and 721-hook packages.
|
|
20
|
-
- The v6 surface is split across dedicated deployer, hook, governor, project-owner, and token-uri contracts, with dedicated regression and
|
|
20
|
+
- The v6 surface is split across dedicated deployer, hook, governor, project-owner, and token-uri contracts, with dedicated regression and review test coverage around governance, fee accounting, attestations, and lifecycle edge cases.
|
|
21
21
|
- Solidity and tooling were upgraded to the v6 baseline around `0.8.28`.
|
|
22
22
|
|
|
23
|
-
## Local
|
|
23
|
+
## Local review remediations
|
|
24
24
|
|
|
25
25
|
- Reserve-minted NFTs are now excluded from refund calculations during MINT, REFUND, and NO_CONTEST phases. A public `isReserveMint` mapping tracks which tokens were created via tier reserve frequency rather than paid for. `beforeCashOutRecordedWith` subtracts their tier price from `cumulativeMintPrice`, preventing reserve beneficiaries from withdrawing funds they never contributed.
|
|
26
26
|
|
package/CRYPTO_ECON.md
CHANGED
|
@@ -777,11 +777,11 @@ Defifa includes a comprehensive safety system — the **NO_CONTEST** mechanism
|
|
|
777
777
|
|
|
778
778
|
#### 9.1.1 Trigger 1: Minimum Participation Threshold
|
|
779
779
|
|
|
780
|
-
**Mechanism.** At game creation, the organizer sets `minParticipation` — a minimum
|
|
780
|
+
**Mechanism.** At game creation, the organizer sets `minParticipation` — a minimum token supply required for the game to proceed to scoring. The `currentGamePhaseOf()` function checks the total token supply (via `CONTROLLER.TOKENS().totalSupplyOf(gameId)`) against this threshold before returning SCORING. If the supply is below the threshold, it returns NO_CONTEST.
|
|
781
781
|
|
|
782
782
|
**What it solves.** Ghost games with negligible participation skip directly to refundability without requiring any governance action. A 32-team World Cup game with `minParticipation = 1 ETH` won't enter scoring if only 50 people mint (0.5 ETH pot).
|
|
783
783
|
|
|
784
|
-
**Attack surface.** An adversary who wants to force no-contest can
|
|
784
|
+
**Attack surface.** An adversary who wants to force no-contest can cash out enough tokens during the refund phase to push the supply below the threshold. Note that direct balance top-ups (via `addToBalanceOf`) cannot inflate participation since the check uses token supply, not treasury balance. Mitigation: set the threshold conservatively low relative to expected participation.
|
|
785
785
|
|
|
786
786
|
**Configuration.** Set to 0 to disable. The threshold is set at launch before any minting occurs, so calibration depends on organizer judgment.
|
|
787
787
|
|
|
@@ -820,7 +820,7 @@ The phase resolution follows strict priority:
|
|
|
820
820
|
|
|
821
821
|
2. **Explicit trigger is sticky.** Once `noContestTriggeredFor[gameId]` is set, the game stays in NO_CONTEST permanently (cannot transition to SCORING even if conditions change).
|
|
822
822
|
|
|
823
|
-
3. **Both thresholds are checked independently.** A game can enter NO_CONTEST from either `minParticipation` (
|
|
823
|
+
3. **Both thresholds are checked independently.** A game can enter NO_CONTEST from either `minParticipation` (token supply too low) or `scorecardTimeout` (time elapsed) — whichever condition is met first.
|
|
824
824
|
|
|
825
825
|
#### 9.1.5 The Default Attestation Delegate
|
|
826
826
|
|
|
@@ -1187,4 +1187,4 @@ Defifa implements a rigorous approach to prediction gaming through the compositi
|
|
|
1187
1187
|
|
|
1188
1188
|
The elegance of Defifa resides in its architectural composability: prediction games with arbitrary outcomes, arbitrary tier structures, and arbitrary payout distributions emerge from the same set of twelve parameters (Eq. 1), executed deterministically by immutable smart contracts with a single, time-bounded governance input. From a 4-team presidential election to a 32-team World Cup, the same protocol handles it all — and the safety mechanisms ensure that every game resolves, one way or another.
|
|
1189
1189
|
|
|
1190
|
-
The most significant
|
|
1190
|
+
The most significant result is the Uniform Participation Theorem: **a game with uniform tier supply is provably impervious to profitable governance attacks regardless of attacker capital.** This transforms game design from an art into an engineering discipline — the designer's job is to choose events and tier structures that naturally produce uniform participation, and the cryptoeconomics handle the rest.
|
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ src/
|
|
|
117
117
|
libraries/
|
|
118
118
|
structs/
|
|
119
119
|
test/
|
|
120
|
-
governance, fee-accounting, no-contest, adversarial, regression, fork, and
|
|
120
|
+
governance, fee-accounting, no-contest, adversarial, regression, fork, and review coverage
|
|
121
121
|
script/
|
|
122
122
|
Deploy.s.sol
|
|
123
123
|
helpers/
|
|
@@ -141,8 +141,8 @@ references/
|
|
|
141
141
|
- [`test/DefifaFeeAccounting.t.sol`](./test/DefifaFeeAccounting.t.sol)
|
|
142
142
|
- [`test/DefifaNoContest.t.sol`](./test/DefifaNoContest.t.sol)
|
|
143
143
|
- [`test/DefifaAdversarialQuorum.t.sol`](./test/DefifaAdversarialQuorum.t.sol)
|
|
144
|
-
- [`test/
|
|
145
|
-
- [`test/
|
|
144
|
+
- [`test/regression/PendingReserveDilution.t.sol`](./test/regression/PendingReserveDilution.t.sol)
|
|
145
|
+
- [`test/regression/AttestationDoubleCount.t.sol`](./test/regression/AttestationDoubleCount.t.sol)
|
|
146
146
|
- [`test/regression/GracePeriodBypass.t.sol`](./test/regression/GracePeriodBypass.t.sol)
|
|
147
147
|
|
|
148
148
|
## Deployment Notes
|
package/RISKS.md
CHANGED
|
@@ -6,7 +6,7 @@ This file focuses on the game-theoretic, governance, and settlement risks in Def
|
|
|
6
6
|
|
|
7
7
|
- Read `Priority risks` first.
|
|
8
8
|
- Use the detailed sections below to reason about governor power, live supply assumptions, and downstream hook dependencies.
|
|
9
|
-
- Treat `Accepted Behaviors` and `Invariants to Verify` as explicit boundaries for
|
|
9
|
+
- Treat `Accepted Behaviors` and `Invariants to Verify` as explicit boundaries for review scope.
|
|
10
10
|
|
|
11
11
|
## Priority risks
|
|
12
12
|
|
package/SKILLS.md
CHANGED
|
@@ -43,4 +43,4 @@ Defifa is an onchain prediction game system built on Juicebox. This repo package
|
|
|
43
43
|
- Defifa-specific cash-out weights and governance thresholds sit on top of `nana-721-hook-v6` and `nana-core-v6`.
|
|
44
44
|
- When a task mentions NFT rendering or metadata, confirm whether it belongs in [`src/DefifaTokenUriResolver.sol`](./src/DefifaTokenUriResolver.sol).
|
|
45
45
|
- If you edit phase transitions, check lifecycle, governance, and fee-accounting tests together.
|
|
46
|
-
- If ratification, attestation, or quorum behavior changes, re-read the
|
|
46
|
+
- If ratification, attestation, or quorum behavior changes, re-read the review and regression tests before trusting a clean happy-path result.
|
package/STYLE_GUIDE.md
CHANGED
|
@@ -451,57 +451,9 @@ jobs:
|
|
|
451
451
|
run: forge fmt --check
|
|
452
452
|
```
|
|
453
453
|
|
|
454
|
-
**
|
|
455
|
-
```yaml
|
|
456
|
-
name: slither
|
|
457
|
-
on:
|
|
458
|
-
pull_request:
|
|
459
|
-
branches:
|
|
460
|
-
- main
|
|
461
|
-
push:
|
|
462
|
-
branches:
|
|
463
|
-
- main
|
|
464
|
-
jobs:
|
|
465
|
-
analyze:
|
|
466
|
-
runs-on: ubuntu-latest
|
|
467
|
-
steps:
|
|
468
|
-
- uses: actions/checkout@v4
|
|
469
|
-
with:
|
|
470
|
-
submodules: recursive
|
|
471
|
-
- uses: actions/setup-node@v4
|
|
472
|
-
with:
|
|
473
|
-
node-version: 25.9.0
|
|
474
|
-
- name: Install npm dependencies
|
|
475
|
-
run: npm install --omit=dev
|
|
476
|
-
- name: Install Foundry
|
|
477
|
-
uses: foundry-rs/foundry-toolchain@v1
|
|
478
|
-
- name: Prebuild contracts
|
|
479
|
-
run: forge build --deny notes --build-info --skip "*/test/**" --skip "*/script/**"
|
|
480
|
-
- name: Run slither
|
|
481
|
-
uses: crytic/slither-action@v0.4.1
|
|
482
|
-
with:
|
|
483
|
-
slither-config: slither-ci.config.json
|
|
484
|
-
fail-on: medium
|
|
485
|
-
ignore-compile: true
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
**slither-ci.config.json:**
|
|
489
|
-
```json
|
|
490
|
-
{
|
|
491
|
-
"detectors_to_exclude": "timestamp,uninitialized-local,naming-convention,solc-version,shadowing-local",
|
|
492
|
-
"exclude_informational": true,
|
|
493
|
-
"exclude_low": false,
|
|
494
|
-
"exclude_medium": false,
|
|
495
|
-
"exclude_high": false,
|
|
496
|
-
"disable_color": false,
|
|
497
|
-
"filter_paths": "(mocks/|test/|node_modules/|lib/)",
|
|
498
|
-
"legacy_ast": false
|
|
499
|
-
}
|
|
500
|
-
```
|
|
454
|
+
**Static review workflow** (repos with `src/` contracts only):
|
|
501
455
|
|
|
502
|
-
|
|
503
|
-
- Deployer-only repos (no `src/`, only `script/`) skip slither entirely — the action's internal `forge build` skips `test/` and `script/` by default, leaving nothing to compile.
|
|
504
|
-
- Use inline `// slither-disable-next-line <detector>` to suppress known false positives rather than adding to `detectors_to_exclude` in the config. The comment must be on the line immediately before the flagged expression.
|
|
456
|
+
Keep repo-local static review automation current with the package's runtime surface. At minimum, CI should run formatting, linting, and build checks with `--deny notes`. Repos that only contain deployment scripts can rely on the shared formatting and lint jobs unless they add runtime contracts.
|
|
505
457
|
|
|
506
458
|
### package.json
|
|
507
459
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "25.9.0"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@bananapus/721-hook-v6": "0.0.43",
|
|
35
35
|
"@bananapus/address-registry-v6": "0.0.25",
|
|
36
|
-
"@bananapus/core-v6": "0.0.
|
|
36
|
+
"@bananapus/core-v6": "0.0.44",
|
|
37
37
|
"@bananapus/permission-ids-v6": "0.0.22",
|
|
38
38
|
"@openzeppelin/contracts": "5.6.1",
|
|
39
39
|
"@prb/math": "4.1.1",
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -54,18 +54,14 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
54
54
|
// --------------------------- custom errors ------------------------- //
|
|
55
55
|
//*********************************************************************//
|
|
56
56
|
|
|
57
|
-
error DefifaDeployer_CantFulfillYet();
|
|
58
|
-
error
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
error
|
|
62
|
-
error
|
|
63
|
-
error
|
|
64
|
-
error
|
|
65
|
-
error DefifaDeployer_TerminalNotFound();
|
|
66
|
-
error DefifaDeployer_PhaseAlreadyQueued();
|
|
67
|
-
error DefifaDeployer_SplitsDontAddUp();
|
|
68
|
-
error DefifaDeployer_UnexpectedTerminalCurrency();
|
|
57
|
+
error DefifaDeployer_CantFulfillYet(uint256 gameId);
|
|
58
|
+
error DefifaDeployer_InvalidGameConfiguration(
|
|
59
|
+
uint256 start, uint256 mintPeriodDuration, uint256 refundPeriodDuration, uint256 tierCount
|
|
60
|
+
);
|
|
61
|
+
error DefifaDeployer_InvalidCurrency(address token, uint256 currency);
|
|
62
|
+
error DefifaDeployer_NotNoContest(uint256 gameId, DefifaGamePhase phase);
|
|
63
|
+
error DefifaDeployer_NoContestAlreadyTriggered(uint256 gameId);
|
|
64
|
+
error DefifaDeployer_SplitsDontAddUp(uint256 totalPercent, uint256 maxPercent);
|
|
69
65
|
|
|
70
66
|
//*********************************************************************//
|
|
71
67
|
// ----------------------- internal properties ----------------------- //
|
|
@@ -182,7 +178,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
182
178
|
// Get the game's current funding cycle along with its metadata.
|
|
183
179
|
JBRuleset memory currentRuleset = CONTROLLER.RULESETS().currentOf(gameId);
|
|
184
180
|
// Get the game's queued funding cycle along with its metadata.
|
|
185
|
-
// slither-disable-next-line unused-return
|
|
186
181
|
(JBRuleset memory queuedRuleset,) = CONTROLLER.RULESETS().latestQueuedOf(gameId);
|
|
187
182
|
|
|
188
183
|
// If the configurations are the same and the game hasn't ended, queueing is still needed.
|
|
@@ -254,13 +249,12 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
254
249
|
// Get the game's ops data for the safety mechanism checks. Cache to avoid repeated SLOAD.
|
|
255
250
|
DefifaOpsData memory ops = _opsOf[gameId];
|
|
256
251
|
|
|
257
|
-
// Check minimum participation threshold
|
|
258
|
-
//
|
|
252
|
+
// Check minimum participation threshold using token supply (not terminal balance).
|
|
253
|
+
// Token supply reflects actual minted participation — direct `addToBalanceOf` top-ups
|
|
254
|
+
// don't mint tokens and therefore can't bypass this check.
|
|
259
255
|
if (ops.minParticipation > 0) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.balanceOf({terminal: address(terminal), projectId: gameId, token: ops.token});
|
|
263
|
-
if (balance < ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
256
|
+
uint256 totalTokenSupply = CONTROLLER.TOKENS().totalSupplyOf(gameId);
|
|
257
|
+
if (totalTokenSupply < ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
264
258
|
}
|
|
265
259
|
|
|
266
260
|
// Check scorecard ratification timeout: if enough time has passed without a ratified scorecard, the game is
|
|
@@ -295,7 +289,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
295
289
|
uint256 _baseProtocolProjectId,
|
|
296
290
|
IJB721TiersHookStore _hookStore
|
|
297
291
|
) {
|
|
298
|
-
// slither-disable-next-line missing-zero-check
|
|
299
292
|
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
300
293
|
TOKEN_URI_RESOLVER = _tokenUriResolver;
|
|
301
294
|
GOVERNOR = _governor;
|
|
@@ -314,19 +307,17 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
314
307
|
|
|
315
308
|
/// @notice Fulfill split amounts between all splits for a game.
|
|
316
309
|
/// @param gameId The ID of the game to fulfill splits for.
|
|
317
|
-
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
318
310
|
function fulfillCommitmentsOf(uint256 gameId) external virtual override {
|
|
319
311
|
// Make sure commitments haven't already been fulfilled.
|
|
320
312
|
if (commitmentsFulfilledFor[gameId]) return;
|
|
321
313
|
commitmentsFulfilledFor[gameId] = true;
|
|
322
314
|
|
|
323
315
|
// Get the game's current funding cycle along with its metadata.
|
|
324
|
-
// slither-disable-next-line unused-return
|
|
325
316
|
(, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
326
317
|
|
|
327
318
|
// Make sure the game's commitments can be fulfilled.
|
|
328
319
|
if (!IDefifaHook(metadata.dataHook).cashOutWeightIsSet()) {
|
|
329
|
-
revert DefifaDeployer_CantFulfillYet();
|
|
320
|
+
revert DefifaDeployer_CantFulfillYet({gameId: gameId});
|
|
330
321
|
}
|
|
331
322
|
|
|
332
323
|
// Get the game token and the terminal.
|
|
@@ -338,10 +329,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
338
329
|
uint256 pot = terminal.STORE().balanceOf({terminal: address(terminal), projectId: gameId, token: token});
|
|
339
330
|
|
|
340
331
|
// If the pot is empty, queue the final ruleset without attempting payouts.
|
|
341
|
-
// slither-disable-next-line incorrect-equality
|
|
342
332
|
if (pot == 0) {
|
|
343
333
|
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
344
|
-
// slither-disable-next-line reentrancy-events
|
|
345
334
|
emit FulfilledCommitments({gameId: gameId, pot: 0, caller: msg.sender});
|
|
346
335
|
return;
|
|
347
336
|
}
|
|
@@ -357,7 +346,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
357
346
|
// Use the ruleset's baseCurrency — this matches the currency under which payout limits were stored
|
|
358
347
|
// at launch time, regardless of whether the token is native ETH or an ERC-20.
|
|
359
348
|
// Wrapped in try-catch so the final ruleset is always queued even if payout fails.
|
|
360
|
-
// slither-disable-next-line unused-return,reentrancy-no-eth
|
|
361
349
|
try terminal.sendPayoutsOf({
|
|
362
350
|
projectId: gameId, token: token, amount: feeAmount, currency: metadata.baseCurrency, minTokensPaidOut: 0
|
|
363
351
|
}) {}
|
|
@@ -365,21 +353,18 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
365
353
|
// Payout failed — fee stays in pot. Reset to 0 so currentGamePotOf
|
|
366
354
|
// doesn't double-count the fee.
|
|
367
355
|
fulfilledCommitmentsOf[gameId] = 0;
|
|
368
|
-
// slither-disable-next-line reentrancy-events
|
|
369
356
|
emit CommitmentPayoutFailed({gameId: gameId, amount: feeAmount, reason: reason});
|
|
370
357
|
}
|
|
371
358
|
|
|
372
359
|
// Queue the final ruleset and emit.
|
|
373
360
|
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
374
361
|
|
|
375
|
-
// slither-disable-next-line reentrancy-events
|
|
376
362
|
emit FulfilledCommitments({gameId: gameId, pot: pot, caller: msg.sender});
|
|
377
363
|
}
|
|
378
364
|
|
|
379
365
|
/// @notice Launches a new game owned by this contract with a DefifaHook attached.
|
|
380
366
|
/// @param launchProjectData Data necessary to fulfill the transaction to launch a game.
|
|
381
367
|
/// @return gameId The ID of the newly configured game.
|
|
382
|
-
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
383
368
|
function launchGameWith(DefifaLaunchProjectData memory launchProjectData)
|
|
384
369
|
external
|
|
385
370
|
override
|
|
@@ -392,7 +377,14 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
392
377
|
if (launchProjectData.start == 0) {
|
|
393
378
|
uint256 start =
|
|
394
379
|
currentTimestamp + launchProjectData.mintPeriodDuration + launchProjectData.refundPeriodDuration;
|
|
395
|
-
if (start > type(uint48).max)
|
|
380
|
+
if (start > type(uint48).max) {
|
|
381
|
+
revert DefifaDeployer_InvalidGameConfiguration({
|
|
382
|
+
start: start,
|
|
383
|
+
mintPeriodDuration: launchProjectData.mintPeriodDuration,
|
|
384
|
+
refundPeriodDuration: launchProjectData.refundPeriodDuration,
|
|
385
|
+
tierCount: launchProjectData.tiers.length
|
|
386
|
+
});
|
|
387
|
+
}
|
|
396
388
|
|
|
397
389
|
// Casting to uint48 is safe because `start` was bounded above.
|
|
398
390
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
@@ -400,7 +392,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
400
392
|
}
|
|
401
393
|
// If callers provide a future start with no mint duration, derive a mint window that begins now and ends
|
|
402
394
|
// before the refund window. This preserves the requested start while keeping minting immediately available.
|
|
403
|
-
// slither-disable-next-line incorrect-equality
|
|
404
395
|
else if (
|
|
405
396
|
launchProjectData.mintPeriodDuration == 0
|
|
406
397
|
&& launchProjectData.start > currentTimestamp + launchProjectData.refundPeriodDuration
|
|
@@ -411,29 +402,56 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
411
402
|
|
|
412
403
|
// Make sure the provided gameplay timestamps are sequential and that there is a mint duration.
|
|
413
404
|
if (
|
|
414
|
-
// slither-disable-next-line incorrect-equality
|
|
415
405
|
launchProjectData.mintPeriodDuration == 0
|
|
416
406
|
|| launchProjectData.start
|
|
417
407
|
< currentTimestamp + launchProjectData.refundPeriodDuration + launchProjectData.mintPeriodDuration
|
|
418
|
-
)
|
|
408
|
+
) {
|
|
409
|
+
revert DefifaDeployer_InvalidGameConfiguration({
|
|
410
|
+
start: launchProjectData.start,
|
|
411
|
+
mintPeriodDuration: launchProjectData.mintPeriodDuration,
|
|
412
|
+
refundPeriodDuration: launchProjectData.refundPeriodDuration,
|
|
413
|
+
tierCount: launchProjectData.tiers.length
|
|
414
|
+
});
|
|
415
|
+
}
|
|
419
416
|
|
|
420
417
|
// The hook and governor hardcode uint256[128] tier-weight tables, so reject games with more than 128 tiers.
|
|
421
|
-
if (launchProjectData.tiers.length > 128)
|
|
418
|
+
if (launchProjectData.tiers.length > 128) {
|
|
419
|
+
revert DefifaDeployer_InvalidGameConfiguration({
|
|
420
|
+
start: launchProjectData.start,
|
|
421
|
+
mintPeriodDuration: launchProjectData.mintPeriodDuration,
|
|
422
|
+
refundPeriodDuration: launchProjectData.refundPeriodDuration,
|
|
423
|
+
tierCount: launchProjectData.tiers.length
|
|
424
|
+
});
|
|
425
|
+
}
|
|
422
426
|
|
|
423
427
|
// Reject ERC-20 games with a zero currency. A zero baseCurrency would cause payout limit lookups
|
|
424
428
|
// in fulfillCommitmentsOf to silently fail, skipping all commitment payouts.
|
|
425
|
-
// slither-disable-next-line incorrect-equality
|
|
426
429
|
if (launchProjectData.token.token != JBConstants.NATIVE_TOKEN && launchProjectData.token.currency == 0) {
|
|
427
|
-
revert DefifaDeployer_InvalidCurrency(
|
|
430
|
+
revert DefifaDeployer_InvalidCurrency({
|
|
431
|
+
token: launchProjectData.token.token, currency: launchProjectData.token.currency
|
|
432
|
+
});
|
|
428
433
|
}
|
|
429
434
|
|
|
430
|
-
// If a scorecard timeout is set, it must exceed the
|
|
435
|
+
// If a scorecard timeout is set, it must exceed the full ratification window:
|
|
436
|
+
// attestation delay (time from scoring start to attestation start) + grace period + timelock.
|
|
431
437
|
// Otherwise the game would enter NO_CONTEST before a scorecard could ever reach SUCCEEDED.
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
if (launchProjectData.scorecardTimeout > 0) {
|
|
439
|
+
// Attestation delay: how long after scoring starts before attestations can begin.
|
|
440
|
+
uint256 attestationDelay = launchProjectData.attestationStartTime > launchProjectData.start
|
|
441
|
+
? launchProjectData.attestationStartTime - launchProjectData.start
|
|
442
|
+
: 0;
|
|
443
|
+
if (
|
|
444
|
+
launchProjectData.scorecardTimeout
|
|
445
|
+
<= attestationDelay + launchProjectData.attestationGracePeriod + launchProjectData.timelockDuration
|
|
446
|
+
) {
|
|
447
|
+
revert DefifaDeployer_InvalidGameConfiguration({
|
|
448
|
+
start: launchProjectData.start,
|
|
449
|
+
mintPeriodDuration: launchProjectData.mintPeriodDuration,
|
|
450
|
+
refundPeriodDuration: launchProjectData.refundPeriodDuration,
|
|
451
|
+
tierCount: launchProjectData.tiers.length
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
437
455
|
|
|
438
456
|
// Reserve the game ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
439
457
|
gameId = CONTROLLER.PROJECTS().createFor(address(this));
|
|
@@ -537,7 +555,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
537
555
|
}
|
|
538
556
|
|
|
539
557
|
// Increment the nonce for this deployment.
|
|
540
|
-
// slither-disable-next-line reentrancy-benign
|
|
541
558
|
uint256 currentNonce = ++_nonce;
|
|
542
559
|
|
|
543
560
|
// Clone deterministically using sender and nonce to prevent front-running.
|
|
@@ -596,8 +613,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
596
613
|
bytecode: _cloneCreationCodeFor(address(HOOK_CODE_ORIGIN))
|
|
597
614
|
});
|
|
598
615
|
|
|
599
|
-
|
|
600
|
-
|
|
616
|
+
emit LaunchGame({
|
|
617
|
+
gameId: gameId, hook: hook, governor: GOVERNOR, tokenUriResolver: uriResolver, caller: msg.sender
|
|
618
|
+
});
|
|
601
619
|
}
|
|
602
620
|
|
|
603
621
|
/// @notice Allows this contract to receive 721s.
|
|
@@ -612,12 +630,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
612
630
|
/// @param gameId The ID of the game to trigger no-contest for.
|
|
613
631
|
function triggerNoContestFor(uint256 gameId) external override {
|
|
614
632
|
// Make sure the game is currently in NO_CONTEST phase.
|
|
615
|
-
|
|
616
|
-
|
|
633
|
+
DefifaGamePhase phase = currentGamePhaseOf(gameId);
|
|
634
|
+
if (phase != DefifaGamePhase.NO_CONTEST) {
|
|
635
|
+
revert DefifaDeployer_NotNoContest({gameId: gameId, phase: phase});
|
|
617
636
|
}
|
|
618
637
|
|
|
619
638
|
// Make sure no-contest hasn't already been triggered.
|
|
620
|
-
if (noContestTriggeredFor[gameId]) revert DefifaDeployer_NoContestAlreadyTriggered();
|
|
639
|
+
if (noContestTriggeredFor[gameId]) revert DefifaDeployer_NoContestAlreadyTriggered({gameId: gameId});
|
|
621
640
|
|
|
622
641
|
// Mark as triggered.
|
|
623
642
|
// Note: the queued ruleset does not take effect until the current ruleset's cycle ends (or immediately
|
|
@@ -627,7 +646,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
627
646
|
noContestTriggeredFor[gameId] = true;
|
|
628
647
|
|
|
629
648
|
// Get the game's current ruleset metadata for the data hook address.
|
|
630
|
-
// slither-disable-next-line unused-return
|
|
631
649
|
(, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
632
650
|
|
|
633
651
|
// Queue a new ruleset without payout limits so surplus = balance, enabling refunds.
|
|
@@ -653,7 +671,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
653
671
|
allowAddPriceFeed: false,
|
|
654
672
|
ownerMustSendPayouts: true,
|
|
655
673
|
holdFees: false,
|
|
656
|
-
|
|
674
|
+
scopeCashOutsToLocalBalances: true,
|
|
657
675
|
useDataHookForPay: true,
|
|
658
676
|
useDataHookForCashOut: true,
|
|
659
677
|
dataHook: metadata.dataHook,
|
|
@@ -668,13 +686,11 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
668
686
|
});
|
|
669
687
|
|
|
670
688
|
// Queue the no-contest refund ruleset.
|
|
671
|
-
// slither-disable-next-line unused-return
|
|
672
689
|
CONTROLLER.queueRulesetsOf({
|
|
673
690
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game: no contest."
|
|
674
691
|
});
|
|
675
692
|
|
|
676
|
-
|
|
677
|
-
emit QueuedNoContest(gameId, msg.sender);
|
|
693
|
+
emit QueuedNoContest({gameId: gameId, caller: msg.sender});
|
|
678
694
|
}
|
|
679
695
|
|
|
680
696
|
//*********************************************************************//
|
|
@@ -706,10 +722,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
706
722
|
}
|
|
707
723
|
|
|
708
724
|
// Validate that total fee splits don't exceed 100%.
|
|
709
|
-
if (totalAbsolutePercent > JBConstants.SPLITS_TOTAL_PERCENT)
|
|
725
|
+
if (totalAbsolutePercent > JBConstants.SPLITS_TOTAL_PERCENT) {
|
|
726
|
+
revert DefifaDeployer_SplitsDontAddUp({
|
|
727
|
+
totalPercent: totalAbsolutePercent, maxPercent: JBConstants.SPLITS_TOTAL_PERCENT
|
|
728
|
+
});
|
|
729
|
+
}
|
|
710
730
|
|
|
711
731
|
// Store the total absolute percent for use in fulfillCommitmentsOf.
|
|
712
|
-
// slither-disable-next-line reentrancy-benign
|
|
713
732
|
_commitmentPercentOf[gameId] = totalAbsolutePercent;
|
|
714
733
|
|
|
715
734
|
// Build the splits array: user splits + Defifa + NANA (NANA last to absorb rounding).
|
|
@@ -815,7 +834,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
815
834
|
allowAddPriceFeed: false,
|
|
816
835
|
ownerMustSendPayouts: false,
|
|
817
836
|
holdFees: false,
|
|
818
|
-
|
|
837
|
+
scopeCashOutsToLocalBalances: true,
|
|
819
838
|
useDataHookForPay: true,
|
|
820
839
|
useDataHookForCashOut: true,
|
|
821
840
|
dataHook: dataHook,
|
|
@@ -858,7 +877,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
858
877
|
allowAddPriceFeed: false,
|
|
859
878
|
ownerMustSendPayouts: false,
|
|
860
879
|
holdFees: false,
|
|
861
|
-
|
|
880
|
+
scopeCashOutsToLocalBalances: true,
|
|
862
881
|
useDataHookForPay: true,
|
|
863
882
|
useDataHookForCashOut: true,
|
|
864
883
|
dataHook: dataHook,
|
|
@@ -918,7 +937,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
918
937
|
// Set this to true so only the deployer can fulfill the commitments.
|
|
919
938
|
ownerMustSendPayouts: true,
|
|
920
939
|
holdFees: false,
|
|
921
|
-
|
|
940
|
+
scopeCashOutsToLocalBalances: true,
|
|
922
941
|
useDataHookForPay: true,
|
|
923
942
|
useDataHookForCashOut: true,
|
|
924
943
|
dataHook: dataHook,
|
|
@@ -938,7 +957,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
938
957
|
});
|
|
939
958
|
|
|
940
959
|
// Launch the rulesets for the reserved project and set the project URI in the same controller call.
|
|
941
|
-
// slither-disable-next-line unused-return
|
|
942
960
|
CONTROLLER.launchRulesetsFor({
|
|
943
961
|
projectId: gameId,
|
|
944
962
|
projectUri: launchProjectData.projectUri,
|
|
@@ -974,7 +992,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
974
992
|
allowAddPriceFeed: false,
|
|
975
993
|
ownerMustSendPayouts: false,
|
|
976
994
|
holdFees: false,
|
|
977
|
-
|
|
995
|
+
scopeCashOutsToLocalBalances: true,
|
|
978
996
|
useDataHookForPay: true,
|
|
979
997
|
useDataHookForCashOut: true,
|
|
980
998
|
dataHook: metadata.dataHook,
|
|
@@ -989,7 +1007,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
989
1007
|
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
990
1008
|
});
|
|
991
1009
|
|
|
992
|
-
// slither-disable-next-line unused-return
|
|
993
1010
|
CONTROLLER.queueRulesetsOf({
|
|
994
1011
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
995
1012
|
});
|