@ballkidz/defifa 0.0.32 → 0.0.34
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 +2 -0
- package/RISKS.md +13 -1
- package/package.json +5 -5
- package/src/DefifaDeployer.sol +14 -0
- package/src/DefifaHook.sol +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,8 @@ This file instead describes the current v6 repo at a high level and the broad mi
|
|
|
23
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
|
+
- 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.
|
|
27
|
+
- `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
28
|
|
|
27
29
|
## Migration notes
|
|
28
30
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
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.
|
|
37
|
-
"@bananapus/permission-ids-v6": "0.0.22",
|
|
34
|
+
"@bananapus/721-hook-v6": "^0.0.46",
|
|
35
|
+
"@bananapus/address-registry-v6": "^0.0.25",
|
|
36
|
+
"@bananapus/core-v6": "^0.0.48",
|
|
37
|
+
"@bananapus/permission-ids-v6": "^0.0.22",
|
|
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) {
|
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
|
|