@ballkidz/defifa 0.0.32 → 0.0.35

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.35 — Bump v6 deps to nana-core-v6 0.0.53 cohort
4
+
5
+ - `@bananapus/core-v6`: `^0.0.48 → ^0.0.53` ([PR #145](https://github.com/Bananapus/nana-core-v6/pull/145)).
6
+ - `@bananapus/721-hook-v6`: `^0.0.46 → ^0.0.50`.
7
+ - `@bananapus/permission-ids-v6`: `^0.0.22 → ^0.0.25`.
8
+ - All `JBRulesetMetadata` literals (src + test) patched to include `pauseCrossProjectFeeFreeInflows: false`.
9
+
3
10
  ## Scope
4
11
 
5
12
  This repo was not part of the deployed v5 ecosystem that the top-level changelog measures, so it is excluded from the ecosystem delta.
@@ -23,6 +30,9 @@ This file instead describes the current v6 repo at a high level and the broad mi
23
30
  ## Local review remediations
24
31
 
25
32
  - 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.
33
+ - Reserve minting now caps `count` by `adjustedPendingReservesFor(tierId)` inside `DefifaHook.mintReservesFor`. Without the cap, a caller could request more reserves than the refund-adjusted pending balance, inflating `totalMintCost` from already-refunded mints and breaking the supply-vs-pending-reserves invariant that fee-token claims rely on.
34
+ - One-tier games now revert at launch with `DefifaDeployer_InvalidGameConfiguration` if `scorecardTimeout == 0`. A one-tier game cannot reach quorum (the BWA multiplier reduces the sole tier's power to zero), so a zero timeout would leave funds permanently locked with no exit path. Enforcement moves this from a launcher-side responsibility (previously documented in `RISKS.md §8.6`) to a contract-level guarantee.
35
+ - `DefifaHook.mintReservesFor` now reverts with `DefifaHook_ReservedTokenMintingBlockedInNoContest` while the game is in `NO_CONTEST`. Reserve mints inflate `totalMintCost` so reserved recipients can claim fee tokens; without the block, a game that failed `minParticipation` could be revived back to SCORING via free notional face value before `triggerNoContestFor` latches the failure.
26
36
 
27
37
  ## Migration notes
28
38
 
package/RISKS.md CHANGED
@@ -93,4 +93,16 @@ This is conservative, but it prevents users from front-running reserve dilution
93
93
 
94
94
  ### 8.6 One-tier games always resolve via no-contest
95
95
 
96
- A single-tier game cannot complete normal governance because the governance attestation model gives zero weight to holders of a tier that receives 100% of the scorecard, making quorum unreachable. This is expected: the game falls through to `NO_CONTEST` once `scorecardTimeout` elapses, and players recover their mint price via the permissionless `triggerNoContestFor()` refund path that queues a refund ruleset. This only works when `scorecardTimeout > 0`. A one-tier game launched with `scorecardTimeout = 0` disables the timeout path entirely, and funds become permanently locked with no exit. Game deployers must ensure `scorecardTimeout > 0` for single-tier configurations.
96
+ A single-tier game cannot complete normal governance because the governance attestation model gives zero weight to holders of a tier that receives 100% of the scorecard, making quorum unreachable. This is expected: the game falls through to `NO_CONTEST` once `scorecardTimeout` elapses, and players recover their mint price via the permissionless `triggerNoContestFor()` refund path that queues a refund ruleset. This only works when `scorecardTimeout > 0` a one-tier game launched with `scorecardTimeout = 0` would disable the timeout path entirely and leave funds permanently locked. `DefifaDeployer.launchGameWith` now enforces this at the contract level: a one-tier game with `scorecardTimeout == 0` reverts with `DefifaDeployer_InvalidGameConfiguration`. Two-tier games are still rounding-fragile in the same direction and should also be launched with a nonzero `scorecardTimeout`, but this is not enforced because some two-tier configurations can still reach quorum.
97
+
98
+ ### 8.7 Commitment splits are responsible for never reverting
99
+
100
+ `DefifaDeployer.fulfillCommitmentsOf` calls `terminal.sendPayoutsOf` to distribute the commitment portion of the pot. Core processes splits inside a try/catch — when an individual split reverts (rejecting split hook, recipient terminal that does not accept the game token, fee project with a missing currency feed) the failed amount is silently re-credited to the game's terminal balance and the outer call succeeds. The unpaid commitment funds then remain available to game players via the cash-out path.
101
+
102
+ This means **a single bad split cannot block the others from being paid**, and the unpaid funds are never lost — but it also means the recipient configured behind the failing split does not get paid through `fulfillCommitmentsOf` and must be settled separately if the game launcher cares. Game launchers are responsible for configuring commitment splits that will not revert under the game's terminal/currency setup. Recipients of commitment splits should treat split delivery as best-effort; the canonical post-game pot accounting still holds.
103
+
104
+ `fulfilledCommitmentsOf[gameId]` always equals the requested commitment amount whenever any portion was attempted, regardless of whether every split succeeded. `currentGamePotOf(gameId, true)` adds this back to the remaining balance so the displayed pot represents the original pre-commitment value, but the actual on-terminal balance may be higher than the displayed pot when splits silently failed — by exactly the failed split amounts, which remain redeemable by game players. The bound therefore stays one-sided: real balance ≥ reported pot.
105
+
106
+ ### 8.8 Reserve minting is blocked during NO_CONTEST
107
+
108
+ `DefifaHook.mintReservesFor` reverts with `DefifaHook_ReservedTokenMintingBlockedInNoContest` once `currentGamePhaseOf` reports `NO_CONTEST`. Reserve mints inflate `totalMintCost` so reserved recipients can claim a proportional share of fee tokens. Without this block, a game that failed `minParticipation` could be revived back into a SCORING-eligible state via free notional face value (reserves bump `totalMintCost` over the threshold) before `triggerNoContestFor()` latches the failure into a refund ruleset. The block tightens the §8.4 trust assumption that pending reserves are folded into governance and fee accounting: that remains true while the game is live, but once a game has already failed participation the reserve channel is closed so the no-contest outcome is final.
package/foundry.toml CHANGED
@@ -1,5 +1,6 @@
1
1
  [profile.default]
2
2
  solc = '0.8.28'
3
+ bytecode_hash = "none"
3
4
  evm_version = 'cancun'
4
5
  via_ir = true
5
6
  optimizer_runs = 200
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ballkidz/defifa",
3
- "version": "0.0.32",
3
+ "version": "0.0.35",
4
4
  "license": "MIT",
5
5
  "engines": {
6
6
  "node": "25.9.0"
@@ -31,10 +31,10 @@
31
31
  "remappings.txt"
32
32
  ],
33
33
  "dependencies": {
34
- "@bananapus/721-hook-v6": "0.0.46",
35
- "@bananapus/address-registry-v6": "0.0.25",
36
- "@bananapus/core-v6": "0.0.44",
37
- "@bananapus/permission-ids-v6": "0.0.22",
34
+ "@bananapus/721-hook-v6": "^0.0.50",
35
+ "@bananapus/address-registry-v6": "^0.0.25",
36
+ "@bananapus/core-v6": "^0.0.53",
37
+ "@bananapus/permission-ids-v6": "^0.0.25",
38
38
  "@openzeppelin/contracts": "5.6.1",
39
39
  "@prb/math": "4.1.1",
40
40
  "scripty.sol": "2.1.1"
@@ -425,6 +425,20 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
425
425
  });
426
426
  }
427
427
 
428
+ // One-tier games cannot reach quorum (the BWA multiplier reduces the sole beneficiary tier's power to zero),
429
+ // so they must have a nonzero scorecardTimeout to resolve via NO_CONTEST. Without a timeout they would lock
430
+ // forever with no exit. Two-tier games are also rounding-fragile (see RISKS.md §8.6) and should set a
431
+ // scorecardTimeout, but this is not enforced at the contract level because some configurations can still
432
+ // reach quorum with luck-of-the-draw holder distributions.
433
+ if (launchProjectData.tiers.length == 1 && launchProjectData.scorecardTimeout == 0) {
434
+ revert DefifaDeployer_InvalidGameConfiguration({
435
+ start: launchProjectData.start,
436
+ mintPeriodDuration: launchProjectData.mintPeriodDuration,
437
+ refundPeriodDuration: launchProjectData.refundPeriodDuration,
438
+ tierCount: launchProjectData.tiers.length
439
+ });
440
+ }
441
+
428
442
  // Reject ERC-20 games with a zero currency. A zero baseCurrency would cause payout limit lookups
429
443
  // in fulfillCommitmentsOf to silently fail, skipping all commitment payouts.
430
444
  if (launchProjectData.token.token != JBConstants.NATIVE_TOKEN && launchProjectData.token.currency == 0) {
@@ -673,6 +687,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
673
687
  ownerMustSendPayouts: true,
674
688
  holdFees: false,
675
689
  scopeCashOutsToLocalBalances: true,
690
+ pauseCrossProjectFeeFreeInflows: false,
676
691
  useDataHookForPay: true,
677
692
  useDataHookForCashOut: true,
678
693
  dataHook: metadata.dataHook,
@@ -836,6 +851,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
836
851
  ownerMustSendPayouts: false,
837
852
  holdFees: false,
838
853
  scopeCashOutsToLocalBalances: true,
854
+ pauseCrossProjectFeeFreeInflows: false,
839
855
  useDataHookForPay: true,
840
856
  useDataHookForCashOut: true,
841
857
  dataHook: dataHook,
@@ -879,6 +895,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
879
895
  ownerMustSendPayouts: false,
880
896
  holdFees: false,
881
897
  scopeCashOutsToLocalBalances: true,
898
+ pauseCrossProjectFeeFreeInflows: false,
882
899
  useDataHookForPay: true,
883
900
  useDataHookForCashOut: true,
884
901
  dataHook: dataHook,
@@ -939,6 +956,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
939
956
  ownerMustSendPayouts: true,
940
957
  holdFees: false,
941
958
  scopeCashOutsToLocalBalances: true,
959
+ pauseCrossProjectFeeFreeInflows: false,
942
960
  useDataHookForPay: true,
943
961
  useDataHookForCashOut: true,
944
962
  dataHook: dataHook,
@@ -994,6 +1012,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
994
1012
  ownerMustSendPayouts: false,
995
1013
  holdFees: false,
996
1014
  scopeCashOutsToLocalBalances: true,
1015
+ pauseCrossProjectFeeFreeInflows: false,
997
1016
  useDataHookForPay: true,
998
1017
  useDataHookForCashOut: true,
999
1018
  dataHook: metadata.dataHook,
@@ -57,6 +57,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
57
57
  error DefifaHook_Overspending(uint256 leftoverAmount);
58
58
  error DefifaHook_CashoutWeightsAlreadySet(uint256 projectId);
59
59
  error DefifaHook_ReservedTokenMintingPaused(uint256 projectId, uint256 tierId);
60
+ error DefifaHook_ReservedTokenMintingBlockedInNoContest(uint256 projectId, uint256 tierId);
60
61
  error DefifaHook_TransfersPaused(uint256 projectId, uint256 tokenId, address from, address to);
61
62
  error DefifaHook_Unauthorized(uint256 tokenId, address owner, address caller);
62
63
 
@@ -568,6 +569,13 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
568
569
  revert DefifaHook_ReservedTokenMintingPaused({projectId: PROJECT_ID, tierId: tierId});
569
570
  }
570
571
 
572
+ // Block reserve minting while the game is in NO_CONTEST. Reserve mints inflate `totalMintCost` (so reserved
573
+ // recipients can claim fee tokens), which would otherwise let a game that failed `minParticipation` revive
574
+ // back to SCORING via free notional face value before `triggerNoContestFor` latches the failure.
575
+ if (_currentGamePhaseOf(PROJECT_ID) == DefifaGamePhase.NO_CONTEST) {
576
+ revert DefifaHook_ReservedTokenMintingBlockedInNoContest({projectId: PROJECT_ID, tierId: tierId});
577
+ }
578
+
571
579
  // Cache the store reference in a local variable to avoid repeated SLOAD.
572
580
  IJB721TiersHookStore hookStore = store;
573
581
 
@@ -588,6 +596,11 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
588
596
  });
589
597
  }
590
598
 
599
+ // Cap count by the refund-adjusted pending reserves to prevent inflating totalMintCost
600
+ // from already-refunded mints whose reserve liability no longer exists.
601
+ uint256 adjusted = adjustedPendingReservesFor(tierId);
602
+ if (count > adjusted) count = adjusted;
603
+
591
604
  // Record the minted reserves for the tier.
592
605
  uint256[] memory tokenIds = hookStore.recordMintReservesFor({tierId: tierId, count: count});
593
606