@ballkidz/defifa 0.0.17 → 0.0.18

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/SKILLS.md CHANGED
@@ -1,294 +1,42 @@
1
1
  # Defifa
2
2
 
3
- ## Purpose
4
-
5
- On-chain prediction game framework built on Juicebox V6. Players mint NFT game pieces representing teams/outcomes, a governor-based scorecard system determines tier payouts, and winners burn NFTs to claim proportional shares of the pot plus accumulated fee tokens ($DEFIFA/$NANA).
6
-
7
- ## Game Lifecycle
8
-
9
- ```
10
- start time reached
11
- COUNTDOWN ──────────────────────────────► MINT
12
-
13
- mintPeriodDuration expires
14
-
15
- ┌───────────────────────────┤
16
- │ ▼
17
- │ (refundPeriodDuration=0) REFUND
18
- │ │
19
- │ refundPeriodDuration expires
20
- │ │
21
- ├◄──────────────────────────┘
22
-
23
- SCORING
24
-
25
- ├── scorecard ratified + commitments fulfilled ──► COMPLETE
26
-
27
- └── safety trigger (minParticipation not met
28
- OR scorecardTimeout elapsed) ──────────────► NO_CONTEST
29
- (full refunds)
30
- ```
31
-
32
- - **COUNTDOWN**: Before `start`. No minting.
33
- - **MINT**: Players mint NFTs and delegate attestation power. Delegation changes only allowed here.
34
- - **REFUND**: Optional. Players can burn NFTs for full refund at mint price. Skipped if `refundPeriodDuration=0`.
35
- - **SCORING**: Scorecards are submitted, attested, and ratified. Cash outs blocked until scorecard is set.
36
- - **COMPLETE**: Commitments fulfilled. Players burn NFTs for weighted pot share + fee tokens.
37
- - **NO_CONTEST**: Safety exit. Full refunds enabled. Irreversible once triggered.
38
-
39
- ## Contracts
40
-
41
- | Contract | Role |
42
- |----------|------|
43
- | `DefifaDeployer` | Factory that creates games as Juicebox projects with phased rulesets, cloned hooks, and governor initialization. Manages post-game commitment fulfillment. Implements `IDefifaGamePhaseReporter` and `IDefifaGamePotReporter`. |
44
- | `DefifaHook` | ERC-721 hook (extends `JB721Hook`) managing cash-out weights, attestation delegation with checkpointed voting, and proportional pot distribution. Deployed as minimal proxy clones. |
45
- | `DefifaGovernor` | Shared singleton for scorecard submission, attestation, and ratification. |
46
- | `DefifaHookLib` | External library: scorecard validation, cash-out weight calculation, fee token distribution, attestation aggregation. |
47
- | `DefifaTokenUriResolver` | On-chain SVG renderer for game card metadata with phase-aware display. Uses embedded Capsules typeface. |
48
- | `DefifaFontImporter` | Loads Capsules typeface font for SVG renderer. |
49
- | `DefifaProjectOwner` | Receives Defifa fee project's ownership NFT and permanently grants the deployer `SET_SPLIT_GROUPS` permission. |
50
-
51
- ## Key Functions
52
-
53
- | Function | Contract | What it does |
54
- |----------|----------|--------------|
55
- | `launchGameWith(data)` | `DefifaDeployer` | Creates a game: clones the hook, launches a Juicebox project with phased rulesets, initializes the governor, transfers hook ownership to the governor. Returns the game ID. |
56
- | `fulfillCommitmentsOf(gameId)` | `DefifaDeployer` | After ratification, sends fee payouts via `sendPayoutsOf`, then queues a final ruleset enabling cash outs. |
57
- | `triggerNoContestFor(gameId)` | `DefifaDeployer` | Checks safety conditions and queues a NO_CONTEST ruleset enabling full refunds. Once per game. |
58
- | `currentGamePhaseOf(gameId)` | `DefifaDeployer` | Returns the current `DefifaGamePhase`. |
59
- | `currentGamePotOf(gameId, includeCommitments)` | `DefifaDeployer` | Returns pot size, token address, and decimals. |
60
- | `timesFor(gameId)` | `DefifaDeployer` | Returns `(start, mintPeriodDuration, refundPeriodDuration)`. |
61
- | `safetyParamsOf(gameId)` | `DefifaDeployer` | Returns `(minParticipation, scorecardTimeout)`. |
62
- | `nextPhaseNeedsQueueing(gameId)` | `DefifaDeployer` | True if the next phase ruleset hasn't been queued yet. |
63
- | `submitScorecardFor(gameId, tierWeights)` | `DefifaGovernor` | Submits a scorecard proposal. Only during SCORING. |
64
- | `attestToScorecardFrom(gameId, scorecardId)` | `DefifaGovernor` | Attests to a scorecard using tier-delegated voting power. One attestation per address per scorecard. |
65
- | `ratifyScorecardFrom(gameId, tierWeights)` | `DefifaGovernor` | Ratifies a `SUCCEEDED` scorecard (50% quorum met + grace period elapsed). Executes weights on the hook, then fulfills commitments. |
66
- | `initializeGame(gameId, ...)` | `DefifaGovernor` | Sets attestation start time and grace period. Called by deployer during launch. |
67
- | `quorum(gameId)` | `DefifaGovernor` | Returns the quorum threshold. See Attestation & Governance for formula. |
68
- | `getAttestationWeight(gameId, account, timestamp)` | `DefifaGovernor` | Returns an account's attestation power. See Attestation & Governance for formula. |
69
- | `stateOf(gameId, scorecardId)` | `DefifaGovernor` | Returns scorecard state: `RATIFIED`, `PENDING`, `SUCCEEDED`, `ACTIVE`, or `DEFEATED`. |
70
- | `setTierCashOutWeightsTo(tierWeights)` | `DefifaHook` | Sets cash-out weights. Weights must sum to exactly `TOTAL_CASHOUT_WEIGHT` (1e18). Owner-only (governor), SCORING phase only, one-time. |
71
- | `afterPayRecordedWith(context)` | `DefifaHook` | Processes payments. Adds `msg.value != 0` check over base `JB721Hook`. |
72
- | `beforeCashOutRecordedWith(context)` | `DefifaHook` | Returns cash-out parameters based on game phase. Always returns `noop: false`. |
73
- | `afterCashOutRecordedWith(context)` | `DefifaHook` | Burns NFTs and tracks redemptions. During COMPLETE, also distributes fee tokens. |
74
- | `cashOutWeightOf(tokenIds)` | `DefifaHook` | Cumulative cash-out weight for token IDs: `tierWeight / (minted - burned)` per token. |
75
- | `totalCashOutWeight()` | `DefifaHook` | Returns `TOTAL_CASHOUT_WEIGHT` (1e18). |
76
- | `setTierDelegateTo(delegatee, tierId)` | `DefifaHook` | Delegates attestation power for a tier. MINT phase only. |
77
- | `setTierDelegatesTo(delegations)` | `DefifaHook` | Batch delegation. MINT phase only. |
78
- | `mintReservesFor(tierId, count)` | `DefifaHook` | Mints reserved tokens. Increments `_totalMintCost` so reserved recipients share fee tokens. |
79
- | `initialize(gameId, ...)` | `DefifaHook` | One-time init for cloned hook. Sets project ID, store, reporters, tiers, and default attestation delegate. |
80
-
81
- ## Integration Points
82
-
83
- | Dependency | Import | Used For |
84
- |------------|--------|----------|
85
- | `@bananapus/core-v6` | `IJBController`, `IJBDirectory`, `IJBRulesets`, `IJBTerminal`, `IJBMultiTerminal`, `JBRulesetConfig`, `JBSplit`, `JBConstants`, `JBMetadataResolver` | Project creation, ruleset management, terminal interactions, payout distribution, metadata encoding. |
86
- | `@bananapus/721-hook-v6` | `JB721Hook`, `IJB721TiersHookStore`, `JB721TierConfig`, `JB721Tier`, `ERC721`, `JB721TiersRulesetMetadataResolver` | Hook base class, NFT tier management, tier storage, transfer pause checking. |
87
- | `@bananapus/address-registry-v6` | `IJBAddressRegistry` | Hook address registration for discoverability. |
88
- | `@bananapus/permission-ids-v6` | `JBPermissionIds` | Permission constants (`SET_SPLIT_GROUPS`). |
89
- | `@openzeppelin/contracts` | `Ownable`, `Clones`, `IERC721Receiver`, `SafeERC20`, `Checkpoints`, `Strings`, `IERC20` | Access control, minimal proxy cloning, safe token handling, checkpointed voting, string formatting, fee token transfers. |
90
- | `@prb/math` | `mulDiv` | Fixed-point arithmetic for attestation weight and pot distribution. |
91
-
92
- ## Key Types
93
-
94
- | Struct/Enum | Key Fields | Used In |
95
- |-------------|------------|---------|
96
- | `DefifaLaunchProjectData` | `name`, `projectUri`, `contractUri`, `baseUri`, `tiers` (DefifaTierParams[]), `tierPrice` (uint104), `token` (JBAccountingContext), `mintPeriodDuration` (uint24), `refundPeriodDuration` (uint24), `start` (uint48), `splits` (JBSplit[]), `attestationStartTime`, `attestationGracePeriod`, `defaultAttestationDelegate`, `defaultTokenUriResolver` (IJB721TokenUriResolver), `terminal`, `store`, `minParticipation` (uint256), `scorecardTimeout` (uint32) | `DefifaDeployer.launchGameWith` |
97
- | `DefifaTierParams` | `name` (string), `reservedRate` (uint16), `reservedTokenBeneficiary` (address), `encodedIPFSUri` (bytes32), `shouldUseReservedTokenBeneficiaryAsDefault` (bool) | `DefifaLaunchProjectData.tiers` |
98
- | `DefifaTierCashOutWeight` | `id` (uint256), `cashOutWeight` (uint256) | Scorecard proposals, `DefifaHook.setTierCashOutWeightsTo` |
99
- | `DefifaOpsData` | `token` (address), `start` (uint48), `mintPeriodDuration` (uint24), `refundPeriodDuration` (uint24), `minParticipation` (uint256), `scorecardTimeout` (uint32) | Internal game state in `DefifaDeployer` |
100
- | `DefifaDelegation` | `delegatee` (address), `tierId` (uint256) | `DefifaHook.setTierDelegatesTo` |
101
- | `DefifaGamePhase` | `COUNTDOWN`, `MINT`, `REFUND`, `SCORING`, `COMPLETE`, `NO_CONTEST` | Phase reporting throughout |
102
- | `DefifaScorecard` | `attestationsBegin` (uint48), `gracePeriodEnds` (uint48) — set at submission time | `DefifaGovernor._scorecardOf` |
103
- | `DefifaAttestations` | `count` (uint256), `hasAttested` (mapping(address => bool)) | `DefifaGovernor._scorecardAttestationsOf` |
104
- | `DefifaScorecardState` | `PENDING`, `ACTIVE`, `DEFEATED`, `SUCCEEDED`, `RATIFIED` | `DefifaGovernor.stateOf` |
105
-
106
- ## Events
3
+ ## Use This File For
107
4
 
108
- | Event | Contract | Parameters |
109
- |-------|----------|------------|
110
- | `LaunchGame` | `DefifaDeployer` | `gameId` (indexed), `hook` (indexed), `governor` (indexed), `tokenUriResolver`, `caller` |
111
- | `FulfilledCommitments` | `DefifaDeployer` | `gameId` (indexed), `pot`, `caller` |
112
- | `CommitmentPayoutFailed` | `DefifaDeployer` | `gameId` (indexed), `amount`, `reason` (bytes) |
113
- | `DistributeToSplit` | `DefifaDeployer` | `split` (JBSplit), `amount`, `caller` |
114
- | `QueuedNoContest` | `DefifaDeployer` | `gameId` (indexed), `caller` |
115
- | `QueuedRefundPhase` | `DefifaDeployer` | `gameId` (indexed), `caller` |
116
- | `QueuedScoringPhase` | `DefifaDeployer` | `gameId` (indexed), `caller` |
117
- | `Mint` | `DefifaHook` | `tokenId` (indexed), `tierId` (indexed), `beneficiary` (indexed), `totalAmountContributed`, `caller` |
118
- | `MintReservedToken` | `DefifaHook` | `tokenId` (indexed), `tierId` (indexed), `beneficiary` (indexed), `caller` |
119
- | `TierDelegateAttestationsChanged` | `DefifaHook` | `delegate` (indexed), `tierId` (indexed), `previousBalance`, `newBalance`, `caller` |
120
- | `DelegateChanged` | `DefifaHook` | `delegator` (indexed), `fromDelegate` (indexed), `toDelegate` (indexed) |
121
- | `ClaimedTokens` | `DefifaHook` | `beneficiary` (indexed), `defifaTokenAmount`, `baseProtocolTokenAmount`, `caller` |
122
- | `TierCashOutWeightsSet` | `DefifaHook` | `tierWeights` (DefifaTierCashOutWeight[]), `caller` |
123
- | `GameInitialized` | `DefifaGovernor` | `gameId` (indexed), `attestationStartTime`, `attestationGracePeriod`, `caller` |
124
- | `ScorecardSubmitted` | `DefifaGovernor` | `gameId` (indexed), `scorecardId` (indexed), `tierWeights` (DefifaTierCashOutWeight[]), `isDefaultAttestationDelegate`, `caller` |
125
- | `ScorecardAttested` | `DefifaGovernor` | `gameId` (indexed), `scorecardId` (indexed), `weight`, `caller` |
126
- | `ScorecardRatified` | `DefifaGovernor` | `gameId` (indexed), `scorecardId` (indexed), `caller` |
5
+ - Use this file when the task involves Defifa game deployment, phase transitions, scorecards, attestations, governance thresholds, fee accounting, or Defifa token URI behavior.
6
+ - Start here, then open the deployer, hook, governor, resolver, or tests based on which phase or subsystem is relevant.
127
7
 
128
- ## Errors
8
+ ## Read This Next
129
9
 
130
- | Error | Contract | When |
131
- |-------|----------|------|
132
- | `DefifaDeployer_CantFulfillYet` | `DefifaDeployer` | `fulfillCommitmentsOf` called before scorecard is ratified. |
133
- | `DefifaDeployer_GameOver` | `DefifaDeployer` | Attempting to queue a phase after the game is already complete. |
134
- | `DefifaDeployer_InvalidFeePercent` | `DefifaDeployer` | Fee configuration is invalid. |
135
- | `DefifaDeployer_InvalidGameConfiguration` | `DefifaDeployer` | Launch data fails validation (e.g., missing tiers, bad durations). |
136
- | `DefifaDeployer_IncorrectDecimalAmount` | `DefifaDeployer` | Token accounting context has wrong decimal count. |
137
- | `DefifaDeployer_NotNoContest` | `DefifaDeployer` | Safety conditions for no-contest are not met. |
138
- | `DefifaDeployer_NoContestAlreadyTriggered` | `DefifaDeployer` | `triggerNoContestFor` called more than once for the same game. |
139
- | `DefifaDeployer_TerminalNotFound` | `DefifaDeployer` | No terminal found for the game's project. |
140
- | `DefifaDeployer_PhaseAlreadyQueued` | `DefifaDeployer` | The next phase ruleset has already been queued. |
141
- | `DefifaDeployer_SplitsDontAddUp` | `DefifaDeployer` | Split percentages don't sum correctly. |
142
- | `DefifaDeployer_UnexpectedTerminalCurrency` | `DefifaDeployer` | Terminal's accounting currency doesn't match expected currency. |
143
- | `DefifaHook_BadTierOrder` | `DefifaHook` | Scorecard tier IDs are not in strict ascending order. |
144
- | `DefifaHook_DelegateAddressZero` | `DefifaHook` | Attempting to delegate to the zero address. |
145
- | `DefifaHook_DelegateChangesUnavailableInThisPhase` | `DefifaHook` | Delegation change attempted outside MINT phase. |
146
- | `DefifaHook_GameIsntScoringYet` | `DefifaHook` | `setTierCashOutWeightsTo` called before SCORING phase. |
147
- | `DefifaHook_InvalidTierId` | `DefifaHook` | Tier ID in scorecard doesn't exist. |
148
- | `DefifaHook_InvalidCashoutWeights` | `DefifaHook` | Scorecard tier weights don't sum to exactly `TOTAL_CASHOUT_WEIGHT` (1e18). |
149
- | `DefifaHook_NothingToClaim` | `DefifaHook` | Cash out during COMPLETE yields no ETH and no fee tokens. |
150
- | `DefifaHook_NothingToMint` | `DefifaHook` | Reserved mint attempted with zero count or no available reserves. |
151
- | `DefifaHook_WrongCurrency` | `DefifaHook` | Payment currency doesn't match the hook's pricing currency. |
152
- | `DefifaHook_Overspending` | `DefifaHook` | Payment exceeds allowed amount for the tier. |
153
- | `DefifaHook_CashoutWeightsAlreadySet` | `DefifaHook` | `setTierCashOutWeightsTo` called after weights were already set. |
154
- | `DefifaHook_ReservedTokenMintingPaused` | `DefifaHook` | Reserved token minting is paused in the current ruleset. |
155
- | `DefifaHook_TransfersPaused` | `DefifaHook` | Token transfers are paused in the current ruleset. |
156
- | `DefifaHook_Unauthorized(tokenId, owner, caller)` | `DefifaHook` | Caller doesn't own the token being operated on. |
157
- | `DefifaGovernor_AlreadyAttested` | `DefifaGovernor` | Account already attested to this scorecard. |
158
- | `DefifaGovernor_AlreadyInitialized` | `DefifaGovernor` | `initializeGame` called for a game that's already initialized. |
159
- | `DefifaGovernor_AlreadyRatified` | `DefifaGovernor` | Attempting to submit or ratify when a scorecard is already ratified. |
160
- | `DefifaGovernor_DuplicateScorecard` | `DefifaGovernor` | Submitting a scorecard that produces the same hash as an existing one. |
161
- | `DefifaGovernor_GameNotFound` | `DefifaGovernor` | Game has not been initialized (`_packedScorecardInfoOf` is 0). |
162
- | `DefifaGovernor_IncorrectTierOrder` | `DefifaGovernor` | Tier weights not in ascending order. |
163
- | `DefifaGovernor_NotAllowed` | `DefifaGovernor` | Operation not permitted in the current game phase or scorecard state. |
164
- | `DefifaGovernor_Uint48Overflow` | `DefifaGovernor` | `attestationStartTime` or `attestationGracePeriod` exceeds uint48 max. |
165
- | `DefifaGovernor_UnknownProposal` | `DefifaGovernor` | `stateOf` called with a scorecard ID that hasn't been submitted. |
166
- | `DefifaGovernor_UnownedProposedCashoutValue` | `DefifaGovernor` | Scorecard assigns non-zero weight to a tier with zero minted supply. |
10
+ | If you need... | Open this next |
11
+ |---|---|
12
+ | Repo overview and lifecycle framing | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
+ | Deployment and phase scheduling | [`src/DefifaDeployer.sol`](./src/DefifaDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
14
+ | Cash-out and game-phase behavior | [`src/DefifaHook.sol`](./src/DefifaHook.sol), [`src/libraries/`](./src/libraries/) |
15
+ | Governance and scorecards | [`src/DefifaGovernor.sol`](./src/DefifaGovernor.sol) |
16
+ | Project-owner or token URI behavior | [`src/DefifaProjectOwner.sol`](./src/DefifaProjectOwner.sol), [`src/DefifaTokenUriResolver.sol`](./src/DefifaTokenUriResolver.sol) |
17
+ | Security, lifecycle, and regressions | [`test/DefifaGovernor.t.sol`](./test/DefifaGovernor.t.sol), [`test/DefifaNoContest.t.sol`](./test/DefifaNoContest.t.sol), [`test/DefifaFeeAccounting.t.sol`](./test/DefifaFeeAccounting.t.sol), [`test/regression/`](./test/regression/) |
167
18
 
168
- ## Storage
19
+ ## Repo Map
169
20
 
170
- | Variable | Type | Contract | Description |
171
- |----------|------|----------|-------------|
172
- | `_tierCashOutWeights` | `uint256[128]` | `DefifaHook` | Fixed-size array of cash-out weights per tier, set once by the governor. |
173
- | `cashOutWeightIsSet` | `bool` | `DefifaHook` | Flag preventing re-setting of cash-out weights. |
174
- | `amountRedeemed` | `uint256` | `DefifaHook` | Cumulative ETH redeemed from the pot (refunds not counted). |
175
- | `_totalMintCost` | `uint256` | `DefifaHook` | Cumulative mint price of all live tokens. Denominator for fee token distribution. |
176
- | `tokensRedeemedFrom` | `mapping(uint256 => uint256)` | `DefifaHook` | Number of tokens redeemed per tier. |
177
- | `ratifiedScorecardIdOf` | `mapping(uint256 => uint256)` | `DefifaGovernor` | Maps game ID to the ratified scorecard ID (0 if none). |
178
- | `_packedScorecardInfoOf` | `mapping(uint256 => uint256)` | `DefifaGovernor` | Bit-packed: attestation start time (bits 0-47), grace period (bits 48-95). |
179
- | `_scorecardOf` | `mapping(uint256 => mapping(uint256 => DefifaScorecard))` | `DefifaGovernor` | Maps (gameId, scorecardId) to scorecard data. |
180
- | `_scorecardAttestationsOf` | `mapping(uint256 => mapping(uint256 => DefifaAttestations))` | `DefifaGovernor` | Maps (gameId, scorecardId) to attestation data. |
181
- | `defaultAttestationDelegateProposalOf` | `mapping(uint256 => uint256)` | `DefifaGovernor` | Maps game ID to the scorecard ID submitted by the default attestation delegate. |
182
- | `fulfilledCommitmentsOf` | `mapping(uint256 => uint256)` | `DefifaDeployer` | Non-zero means commitments fulfilled; value of 1 is a sentinel for reentrancy guard. |
183
- | `noContestTriggeredFor` | `mapping(uint256 => bool)` | `DefifaDeployer` | Whether no-contest has been triggered. Can only be set once. |
184
- | `_opsOf` | `mapping(uint256 => DefifaOpsData)` | `DefifaDeployer` | Operational data (token, start, durations, safety params). |
185
- | `_commitmentPercentOf` | `mapping(uint256 => uint256)` | `DefifaDeployer` | Total commitment percentage (fees + splits). |
21
+ | Area | Where to look |
22
+ |---|---|
23
+ | Main contracts | [`src/`](./src/) |
24
+ | Libraries, enums, interfaces, and structs | [`src/libraries/`](./src/libraries/), [`src/enums/`](./src/enums/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
25
+ | Scripts | [`script/`](./script/) |
26
+ | Tests | [`test/`](./test/) |
186
27
 
187
- ## Constants
188
-
189
- | Constant | Value | Location | Meaning |
190
- |----------|-------|----------|---------|
191
- | `TOTAL_CASHOUT_WEIGHT` | `1_000_000_000_000_000_000` (1e18) | `DefifaHook` | Total weight that scorecard tier weights must sum to exactly. |
192
- | `MAX_ATTESTATION_POWER_TIER` | `1_000_000_000` | `DefifaGovernor` | Per-tier attestation power cap. Each minted tier contributes this amount to quorum regardless of supply. |
193
- | `BASE_PROTOCOL_FEE_DIVISOR` | `40` | `DefifaDeployer` | 2.5% fee to the base protocol project. |
194
- | `DEFIFA_FEE_DIVISOR` | `20` | `DefifaDeployer` | 5% fee to the Defifa project. |
195
- | Max tiers | `128` | `DefifaHook` | `_tierCashOutWeights` is a fixed `uint256[128]` array. |
196
- | Grace period minimum | `1 day` | `DefifaGovernor` | Minimum attestation grace period enforced during `initializeGame`. |
197
-
198
- ## Cash-Out Logic by Phase
199
-
200
- | Phase | `cashOutCount` | `totalSupply` | Effect |
201
- |-------|---------------|---------------|--------|
202
- | `MINT` / `REFUND` / `NO_CONTEST` | Cumulative mint price of tokens | Surplus | Full refund at mint price |
203
- | `SCORING` (no scorecard) | 0 | Surplus | Reverts (nothing to claim) |
204
- | `SCORING` / `COMPLETE` (scorecard set) | Weighted share of surplus minus amount already redeemed | Surplus | Proportional pot distribution based on tier weights |
205
-
206
- During COMPLETE phase cash outs, players also receive proportional $DEFIFA and $NANA tokens based on their tokens' cumulative mint price relative to `_totalMintCost`.
207
-
208
- ## Attestation & Governance
209
-
210
- - **Per-tier power**: `mulDiv(MAX_ATTESTATION_POWER_TIER, accountTierUnits, totalTierUnits)`. Each tier contributes equal weight regardless of supply -- a tier with 1 NFT has the same governance weight as a tier with 100.
211
- - **Quorum**: `50% of (MAX_ATTESTATION_POWER_TIER * numberOfMintedTiers)`. Only tiers with at least one minted token count.
212
- - Attestation weight checkpointed at `attestationsBegin - 1` (one second before the attestation window opens), preventing same-block transfer manipulation. Pending reserve counts are snapshotted at submission time to prevent reserve minting from inflating attestation power.
213
- - Each address can only attest once per scorecard.
214
- - Grace period (minimum 1 day) prevents instant ratification after quorum is reached.
215
-
216
- ## Gotchas
217
-
218
- - `TOTAL_CASHOUT_WEIGHT` is 1e18. Submitted scorecard tier weights must sum to **exactly** this value or `setTierCashOutWeightsTo` reverts. No tolerance.
219
- - Tier IDs in a scorecard must be in **strict ascending order** with no duplicates.
220
- - Max 128 tiers (`uint256[128] _tierCashOutWeights`).
221
- - `DefifaHook` is a **minimal proxy clone** (`Clones.cloneDeterministic`). `initialize` can only be called once.
222
- - All tiers share the same price (`tierPrice` on `DefifaLaunchProjectData`).
223
- - **Delegation only during MINT phase**. Other phases revert with `DefifaHook_DelegateChangesUnavailableInThisPhase`.
224
- - If `totalTierUnits` is 0 for a tier (no delegations), that tier contributes no attestation power.
225
- - **Quorum stability via pending reserves**: `quorum()` counts tiers with either minted supply (`currentSupplyOfTier > 0`) OR pending reserves (`numberOfPendingReservesFor > 0`). No snapshot is needed because during SCORING, supply is frozen (no new paid mints, no burns) and reserve minting doesn't change which tiers are counted — tiers with pending reserves are already included. The pending reserves check matters when all paid tokens in a tier were burned during REFUND: `currentSupplyOfTier` drops to 0 but pending reserves persist.
226
- - **Pending reserves dilute attestation power (snapshotted)**: `getBWAAttestationWeight` includes pending reserves in the denominator (total attestation units) but NOT the numerator (individual account units). These pending reserve counts are **snapshotted at scorecard submission time** (`_pendingReservesSnapshotOf`), so minting reserves after submission does not inflate attestation power. The live `getAttestationWeight` still reads live pending reserves. Consistent with the cash-out path (`computeCashOutWeight`) which also includes pending reserves in `totalTokensForCashoutInTier`.
227
- - **Fee token claim includes pending reserve cost**: During COMPLETE cash-outs, fee token distribution uses `_totalMintCost + _pendingReserveMintCost()` as the denominator. This prevents paid holders from claiming a disproportionate share of $DEFIFA/$NANA tokens before reserves are minted.
228
- - `ratifyScorecardFrom` uses **low-level `.call`** to execute the scorecard on the hook (necessary because `setTierCashOutWeightsTo` is `onlyOwner`).
229
- - `fulfillCommitmentsOf` uses `max(amount, 1)` as a reentrancy sentinel. `sendPayoutsOf` is wrapped in try-catch: on failure, resets to sentinel (1) and emits `CommitmentPayoutFailed`.
230
- - `_buildSplits` normalizes split percentages. Rounding remainder absorbed by the protocol fee split (last in array).
231
- - `_totalMintCost` tracks cumulative mint prices (paid + reserved). Incremented on pay/reserve, decremented on cash out. Denominator for fee token distribution.
232
- - Cash outs during COMPLETE revert with `DefifaHook_NothingToClaim` if **both** reclaimed ETH is 0 **and** no fee tokens transferred.
233
- - `minParticipation` is compared against terminal surplus. Value of 0 disables the check.
234
- - `scorecardTimeout` counts seconds from SCORING start. Value of 0 disables. Both enable `triggerNoContestFor` when exceeded.
235
- - `triggerNoContestFor` can only be called once per game and is irreversible.
236
- - Token IDs follow `JB721TiersHookStore` encoding: `tierId * 1_000_000_000 + tokenNumber`.
237
- - Metadata IDs use the **code origin address** (uncloned implementation), not the clone: `JBMetadataResolver.getId("pay", codeOrigin)`.
238
-
239
- ## Example Integration
240
-
241
- ```solidity
242
- import {IDefifaDeployer} from "./interfaces/IDefifaDeployer.sol";
243
- import {DefifaLaunchProjectData} from "./structs/DefifaLaunchProjectData.sol";
244
- import {DefifaTierParams} from "./structs/DefifaTierParams.sol";
245
- import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
246
-
247
- // 1. Launch a game with 2 teams
248
- DefifaTierParams[] memory tiers = new DefifaTierParams[](2);
249
- tiers[0] = DefifaTierParams({
250
- name: "Team A",
251
- // reservedRate maps to JB721's `reserveFrequency`: 1 reserved mint per N paid mints.
252
- // 1001 means "1 reserve per 1001 mints" -- effectively no reserves for normal game sizes.
253
- reservedRate: 1001,
254
- reservedTokenBeneficiary: address(0),
255
- encodedIPFSUri: bytes32(0),
256
- shouldUseReservedTokenBeneficiaryAsDefault: false
257
- });
258
- tiers[1] = DefifaTierParams({
259
- name: "Team B",
260
- reservedRate: 1001, // effectively no reserves (see above)
261
- reservedTokenBeneficiary: address(0),
262
- encodedIPFSUri: bytes32(0),
263
- shouldUseReservedTokenBeneficiaryAsDefault: false
264
- });
265
-
266
- uint256 gameId = deployer.launchGameWith(DefifaLaunchProjectData({
267
- name: "Championship",
268
- tierPrice: 0.01 ether,
269
- tiers: tiers,
270
- start: uint48(block.timestamp + 7 days),
271
- mintPeriodDuration: 3 days,
272
- refundPeriodDuration: 1 days,
273
- minParticipation: 0, // no minimum
274
- scorecardTimeout: 7 days, // 7-day timeout
275
- // ... other fields
276
- }));
28
+ ## Purpose
277
29
 
278
- // 2. Submit a scorecard (Team A wins 70%, Team B gets 30%)
279
- DefifaTierCashOutWeight[] memory weights = new DefifaTierCashOutWeight[](2);
280
- weights[0] = DefifaTierCashOutWeight({id: 1, cashOutWeight: 7e17});
281
- weights[1] = DefifaTierCashOutWeight({id: 2, cashOutWeight: 3e17});
282
- // Total must equal 1e18
30
+ Defifa is an on-chain prediction game system built on Juicebox. This repo packages game launch, phased lifecycle control, scorecard governance, and NFT-based settlement into a single game-specific deployment surface.
283
31
 
284
- uint256 scorecardId = governor.submitScorecardFor(gameId, weights);
32
+ ## Reference Files
285
33
 
286
- // 3. Attest to the scorecard (weight based on tier delegation)
287
- governor.attestToScorecardFrom(gameId, scorecardId);
34
+ - Open [`references/runtime.md`](./references/runtime.md) when you need the game lifecycle, contract roles, settlement path, or the main economic and governance invariants.
35
+ - Open [`references/operations.md`](./references/operations.md) when you need deployment and phase-queueing behavior, test breadcrumbs, or the common sources of stale operational assumptions.
288
36
 
289
- // 4. Ratify once quorum is reached and grace period elapsed
290
- governor.ratifyScorecardFrom(gameId, weights);
37
+ ## Working Rules
291
38
 
292
- // 5. Players burn NFTs via terminal cash-out to claim their share
293
- // They receive proportional ETH + proportional $DEFIFA/$NANA tokens
294
- ```
39
+ - Start in [`src/DefifaDeployer.sol`](./src/DefifaDeployer.sol) for lifecycle and queueing behavior, but verify hook and governor assumptions before treating a game-state issue as deployer-only.
40
+ - Treat scorecard ratification, no-contest behavior, and fee accounting as treasury-sensitive. Small changes there can alter settlement outcomes materially.
41
+ - When a task mentions NFT rendering or metadata, confirm whether it belongs in [`src/DefifaTokenUriResolver.sol`](./src/DefifaTokenUriResolver.sol) instead of the hook or deployer.
42
+ - If you edit phase transitions, check both lifecycle tests and fee/governance tests. Defifa behavior is cross-coupled.
package/STYLE_GUIDE.md CHANGED
@@ -26,8 +26,8 @@ pragma solidity 0.8.28;
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
28
28
 
29
- // Libraries — caret, may use newer features
30
- pragma solidity ^0.8.17;
29
+ // Libraries — pin to exact version like contracts
30
+ pragma solidity 0.8.28;
31
31
  ```
32
32
 
33
33
  ## Imports
@@ -86,12 +86,20 @@ contract JBExample is JBPermissioned, IJBExample {
86
86
 
87
87
  uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
88
88
 
89
+ //*********************************************************************//
90
+ // ------------------------ private constants ------------------------ //
91
+ //*********************************************************************//
92
+
89
93
  //*********************************************************************//
90
94
  // --------------- public immutable stored properties ---------------- //
91
95
  //*********************************************************************//
92
96
 
93
97
  IJBDirectory public immutable override DIRECTORY;
94
98
 
99
+ //*********************************************************************//
100
+ // -------------- internal immutable stored properties -------------- //
101
+ //*********************************************************************//
102
+
95
103
  //*********************************************************************//
96
104
  // --------------------- public stored properties -------------------- //
97
105
  //*********************************************************************//
@@ -100,10 +108,26 @@ contract JBExample is JBPermissioned, IJBExample {
100
108
  // -------------------- internal stored properties ------------------- //
101
109
  //*********************************************************************//
102
110
 
111
+ //*********************************************************************//
112
+ // -------------------- private stored properties -------------------- //
113
+ //*********************************************************************//
114
+
115
+ //*********************************************************************//
116
+ // ------------------- transient stored properties ------------------- //
117
+ //*********************************************************************//
118
+
103
119
  //*********************************************************************//
104
120
  // -------------------------- constructor ---------------------------- //
105
121
  //*********************************************************************//
106
122
 
123
+ //*********************************************************************//
124
+ // ---------------------------- modifiers ---------------------------- //
125
+ //*********************************************************************//
126
+
127
+ //*********************************************************************//
128
+ // ------------------------- receive / fallback ---------------------- //
129
+ //*********************************************************************//
130
+
107
131
  //*********************************************************************//
108
132
  // ---------------------- external transactions ---------------------- //
109
133
  //*********************************************************************//
@@ -112,10 +136,18 @@ contract JBExample is JBPermissioned, IJBExample {
112
136
  // ----------------------- external views ---------------------------- //
113
137
  //*********************************************************************//
114
138
 
139
+ //*********************************************************************//
140
+ // -------------------------- public views --------------------------- //
141
+ //*********************************************************************//
142
+
115
143
  //*********************************************************************//
116
144
  // ----------------------- public transactions ----------------------- //
117
145
  //*********************************************************************//
118
146
 
147
+ //*********************************************************************//
148
+ // ---------------------- internal transactions ---------------------- //
149
+ //*********************************************************************//
150
+
119
151
  //*********************************************************************//
120
152
  // ----------------------- internal helpers -------------------------- //
121
153
  //*********************************************************************//
@@ -134,17 +166,28 @@ contract JBExample is JBPermissioned, IJBExample {
134
166
  1. Custom errors
135
167
  2. Public constants
136
168
  3. Internal constants
137
- 4. Public immutable stored properties
138
- 5. Internal immutable stored properties
139
- 6. Public stored properties
140
- 7. Internal stored properties
141
- 8. Constructor
142
- 9. External transactions
143
- 10. External views
144
- 11. Public transactions
145
- 12. Internal helpers
146
- 13. Internal views
147
- 14. Private helpers
169
+ 4. Private constants
170
+ 5. Public immutable stored properties
171
+ 6. Internal immutable stored properties
172
+ 7. Public stored properties
173
+ 8. Internal stored properties
174
+ 9. Private stored properties
175
+ 10. Transient stored properties
176
+ 11. Constructor
177
+ 12. Modifiers
178
+ 13. Receive / fallback
179
+ 14. External transactions
180
+ 15. External views
181
+ 16. Public views
182
+ 17. Public transactions
183
+ 18. Internal transactions
184
+ 19. Internal helpers
185
+ 20. Internal views
186
+ 21. Private helpers
187
+
188
+ Use these additional section labels where they better match the contents of the block:
189
+ - `internal functions` is accepted as equivalent to `internal helpers`
190
+ - `events` and `structs` are acceptable in specialized contracts that define them explicitly
148
191
 
149
192
  Functions are alphabetized within each section.
150
193
 
@@ -197,7 +240,7 @@ interface IJBExample is IJBBase {
197
240
  | Public/external function | `camelCase` | `cashOutTokensOf` |
198
241
  | Internal/private function | `_camelCase` | `_processFee` |
199
242
  | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
200
- | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
243
+ | Function parameter | `camelCase` (no underscores) | `projectId`, `cashOutCount` |
201
244
 
202
245
  ## NatSpec
203
246
 
@@ -565,7 +608,3 @@ CI checks formatting via `forge fmt --check`.
565
608
  ### Contract Size Checks
566
609
 
567
610
  CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
568
-
569
- ## Repo-Specific Deviations
570
-
571
- None. This repo follows the standard configuration exactly.