@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 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/audit/PendingReserveSnapshotBypass.t.sol`
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/audit/PendingReserveSnapshotBypass.t.sol`
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 audit test coverage around governance, fee accounting, attestations, and lifecycle edge cases.
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 audit remediations
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 treasury balance required for the game to proceed to scoring. The `currentGamePhaseOf()` function checks the treasury balance against this threshold before returning SCORING. If the balance is below the threshold, it returns NO_CONTEST.
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 refund enough tokens during the refund phase to push the balance below the threshold. Mitigation: set the threshold conservatively low relative to expected participation (e.g., 10% of the maximum expected pot).
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` (balance too low) or `scorecardTimeout` (time elapsed) — whichever condition is met first.
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 finding 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.
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 audit coverage
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/audit/PendingReserveDilution.t.sol`](./test/audit/PendingReserveDilution.t.sol)
145
- - [`test/audit/AttestationDoubleCount.t.sol`](./test/audit/AttestationDoubleCount.t.sol)
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 audit scope.
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 audit and regression tests before trusting a clean happy-path result.
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
- **slither.yml** (repos with `src/` contracts only):
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
- **Variations:**
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.28",
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.39",
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",
@@ -54,18 +54,14 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
54
54
  // --------------------------- custom errors ------------------------- //
55
55
  //*********************************************************************//
56
56
 
57
- error DefifaDeployer_CantFulfillYet();
58
- error DefifaDeployer_GameOver();
59
- error DefifaDeployer_InvalidFeePercent();
60
- error DefifaDeployer_InvalidGameConfiguration();
61
- error DefifaDeployer_IncorrectDecimalAmount();
62
- error DefifaDeployer_InvalidCurrency();
63
- error DefifaDeployer_NotNoContest();
64
- error DefifaDeployer_NoContestAlreadyTriggered();
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: if the treasury balance is below the threshold, the game is
258
- // NO_CONTEST.
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
- IJBTerminal terminal = CONTROLLER.DIRECTORY().primaryTerminalOf({projectId: gameId, token: ops.token});
261
- uint256 balance = IJBMultiTerminal(address(terminal)).STORE()
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) revert DefifaDeployer_InvalidGameConfiguration();
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
- ) revert DefifaDeployer_InvalidGameConfiguration();
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) revert DefifaDeployer_InvalidGameConfiguration();
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 grace period + timelock duration.
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
- launchProjectData.scorecardTimeout > 0
434
- && launchProjectData.scorecardTimeout
435
- <= launchProjectData.attestationGracePeriod + launchProjectData.timelockDuration
436
- ) revert DefifaDeployer_InvalidGameConfiguration();
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
- // slither-disable-next-line reentrancy-events
600
- emit LaunchGame(gameId, hook, GOVERNOR, uriResolver, msg.sender);
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
- if (currentGamePhaseOf(gameId) != DefifaGamePhase.NO_CONTEST) {
616
- revert DefifaDeployer_NotNoContest();
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
- useTotalSurplusForCashOuts: false,
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
- // slither-disable-next-line reentrancy-events
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) revert DefifaDeployer_SplitsDontAddUp();
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
- useTotalSurplusForCashOuts: false,
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
- useTotalSurplusForCashOuts: false,
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
- useTotalSurplusForCashOuts: false,
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
- useTotalSurplusForCashOuts: false,
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
  });