@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 +10 -0
- package/RISKS.md +13 -1
- package/foundry.toml +1 -0
- package/package.json +5 -5
- package/src/DefifaDeployer.sol +19 -0
- package/src/DefifaHook.sol +13 -0
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
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
35
|
-
"@bananapus/address-registry-v6": "0.0.25",
|
|
36
|
-
"@bananapus/core-v6": "0.0.
|
|
37
|
-
"@bananapus/permission-ids-v6": "0.0.
|
|
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"
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -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,
|
package/src/DefifaHook.sol
CHANGED
|
@@ -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
|
|