@ballkidz/defifa 0.0.12 → 0.0.14

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.
Files changed (44) hide show
  1. package/ADMINISTRATION.md +3 -3
  2. package/ARCHITECTURE.md +3 -2
  3. package/AUDIT_INSTRUCTIONS.md +5 -5
  4. package/CHANGE_LOG.md +62 -5
  5. package/CRYPTO_ECON.md +506 -271
  6. package/CRYPTO_ECON.pdf +0 -0
  7. package/CRYPTO_ECON.tex +438 -241
  8. package/RISKS.md +13 -1
  9. package/SKILLS.md +5 -3
  10. package/USER_JOURNEYS.md +4 -3
  11. package/package.json +6 -6
  12. package/src/DefifaDeployer.sol +128 -130
  13. package/src/DefifaGovernor.sol +304 -83
  14. package/src/DefifaHook.sol +184 -171
  15. package/src/enums/DefifaScorecardState.sol +1 -0
  16. package/src/interfaces/IDefifaGovernor.sol +42 -2
  17. package/src/libraries/DefifaHookLib.sol +69 -62
  18. package/src/structs/DefifaAttestations.sol +3 -3
  19. package/src/structs/DefifaLaunchProjectData.sol +1 -0
  20. package/src/structs/DefifaScorecard.sol +2 -0
  21. package/test/BWAFunctionComparison.t.sol +1320 -0
  22. package/test/DefifaAdversarialQuorum.t.sol +52 -37
  23. package/test/DefifaAuditLowGuards.t.sol +9 -5
  24. package/test/DefifaFeeAccounting.t.sol +2 -1
  25. package/test/DefifaGovernanceHardening.t.sol +1315 -0
  26. package/test/DefifaGovernor.t.sol +8 -4
  27. package/test/DefifaHookRegressions.t.sol +2 -1
  28. package/test/DefifaMintCostInvariant.t.sol +2 -1
  29. package/test/DefifaNoContest.t.sol +3 -2
  30. package/test/DefifaSecurity.t.sol +55 -47
  31. package/test/DefifaUSDC.t.sol +3 -2
  32. package/test/Fork.t.sol +37 -32
  33. package/test/TestAuditGaps.sol +6 -4
  34. package/test/TestQALastMile.t.sol +6 -3
  35. package/test/audit/{CodexAttestationDoubleCount.t.sol → AttestationDoubleCount.t.sol} +3 -2
  36. package/test/audit/FixPendingReserveDilution.t.sol +366 -0
  37. package/test/audit/PendingReserveDilution.t.sol +298 -0
  38. package/test/audit/PendingReserveQuorumGrief.t.sol +355 -0
  39. package/test/audit/PendingReserveSnapshotBypass.t.sol +279 -0
  40. package/test/regression/AttestationDelegateBeneficiary.t.sol +2 -1
  41. package/test/regression/FulfillmentBlocksRatification.t.sol +2 -1
  42. package/test/regression/GracePeriodBypass.t.sol +2 -1
  43. package/test/SVG.t.sol +0 -164
  44. package/test/deployScript.t.sol +0 -144
package/ADMINISTRATION.md CHANGED
@@ -31,8 +31,8 @@ Admin privileges and their scope in defifa-collection-deployer-v6.
31
31
  | Function | Required Role | Permission Check | What It Does |
32
32
  |----------|--------------|-----------------|-------------|
33
33
  | `initializeGame()` | DefifaDeployer (owner) | `onlyOwner` | Sets attestation start time and grace period for a game. Enforces minimum 1-day grace period. Called automatically during `launchGameWith()`. |
34
- | `submitScorecardFor()` | Anyone | Must be in SCORING phase; no ratified scorecard yet; no duplicate scorecard hash; weighted tiers must have nonzero supply | Submits a scorecard for attestation. Sets `attestationsBegin` and `gracePeriodEnds` timestamps. |
35
- | `attestToScorecardFrom()` | Any NFT holder | Must be in SCORING phase; scorecard must be ACTIVE or SUCCEEDED; caller cannot have already attested | Records attestation weight based on tier holdings at the scorecard's `attestationsBegin` timestamp. |
34
+ | `submitScorecardFor()` | Anyone | Must be in SCORING phase; no ratified scorecard yet; no duplicate scorecard hash; weighted tiers must have nonzero supply | Submits a scorecard for attestation. Sets `attestationsBegin` and `gracePeriodEnds` timestamps. Snapshots pending reserves per tier for BWA computation. |
35
+ | `attestToScorecardFrom()` | Any NFT holder | Must be in SCORING phase; scorecard must be ACTIVE or SUCCEEDED; caller cannot have already attested | Records attestation weight based on tier holdings at the `attestationsBegin - 1` checkpoint timestamp. Uses pending reserve snapshot from submission time. |
36
36
  | `ratifyScorecardFrom()` | Anyone | Scorecard must be in SUCCEEDED state (quorum met + grace period elapsed); no scorecard already ratified | Executes the scorecard via low-level call to `setTierCashOutWeightsTo` on the hook, then calls `fulfillCommitmentsOf`. |
37
37
 
38
38
  ### DefifaHook
@@ -79,7 +79,7 @@ COUNTDOWN --> MINT --> REFUND (optional) --> SCORING --> COMPLETE or NO_CONTEST
79
79
 
80
80
  ### Attestation Quorum Details
81
81
 
82
- The quorum threshold is 50% of the total attestation power across all tiers with nonzero mint supply. Attestation power per tier is proportional to the tier's minted supply at the `attestationsBegin` snapshot timestamp.
82
+ The quorum threshold is 50% of the total attestation power across all tiers with nonzero mint supply. Attestation power per tier is proportional to the tier's minted supply at the `attestationsBegin - 1` checkpoint timestamp, with pending reserves snapshotted at scorecard submission time to prevent reserve minting from inflating attestation power.
83
83
 
84
84
  **Edge cases:**
85
85
  - **Tiers with zero mints:** Tiers with `currentSupplyOfTier(tierId) == 0` are excluded from the quorum calculation. They have no attestation power and cannot influence scoring.
package/ARCHITECTURE.md CHANGED
@@ -53,10 +53,11 @@ COMPLETE Phase:
53
53
  Scorer → DefifaGovernor.submitScorecard(tierWeights[])
54
54
  → Validate: correct phase, valid tier order, weights sum correctly
55
55
  → Create proposal hash
56
+ → Snapshot pending reserves per tier for BWA computation
56
57
 
57
58
  Attestor → DefifaGovernor.attestToScorecard(proposalId)
58
- → Must hold NFT tier tokens
59
- → Attestation weight = voting power from held tiers
59
+ → Must hold NFT tier tokens at attestationsBegin - 1 checkpoint
60
+ → Attestation weight = voting power from held tiers (diluted by snapshotted pending reserves)
60
61
  → When quorum reached → scorecard ratified
61
62
  → DefifaHook.setScorecard() called
62
63
  ```
@@ -150,7 +150,7 @@ NO_CONTEST (safety mechanism triggered)
150
150
  **Attest (DefifaGovernor.attestToScorecardFrom):**
151
151
  1. Require SCORING phase, scorecard ACTIVE or SUCCEEDED.
152
152
  2. Prevent double attestation per account per scorecard.
153
- 3. Compute weight via `getAttestationWeight()` at `attestationsBegin` timestamp.
153
+ 3. Compute weight via `getBWAAttestationWeight()` at `attestationsBegin - 1` timestamp, using pending reserve snapshot from submission time.
154
154
  4. Increment `_scorecardAttestationsOf[gameId][scorecardId].count += weight`.
155
155
 
156
156
  **Ratify (DefifaGovernor.ratifyScorecardFrom):**
@@ -188,7 +188,7 @@ tierPower = MAX_ATTESTATION_POWER_TIER * (account's attestation units / tier's t
188
188
 
189
189
  Where:
190
190
  - `MAX_ATTESTATION_POWER_TIER = 1,000,000,000` (1e9)
191
- - Account's units come from `getPastTierAttestationUnitsOf()` (checkpoint at `attestationsBegin` timestamp)
191
+ - Account's units come from `getPastTierAttestationUnitsOf()` (checkpoint at `attestationsBegin - 1` timestamp)
192
192
  - Total tier units from `getPastTierTotalAttestationUnitsOf()`
193
193
 
194
194
  Total attestation power = sum of per-tier powers across all tiers.
@@ -296,9 +296,9 @@ Start with the money: follow ETH from payment to cash-out.
296
296
 
297
297
  6. **Quorum manipulation via live supply**: `quorum()` reads `currentSupplyOfTier()` at call time (not snapshotted). Verify that burning tokens during SCORING is prevented by `DefifaHook_NothingToClaim` (cash-out weights not set yet). Check if any other burn path exists that could reduce quorum after attestations have begun.
298
298
 
299
- 7. **Attestation snapshotting**: Attestation weight is computed at the `attestationsBegin` timestamp via `getPastTierAttestationUnitsOf()`. Verify that the `Checkpoints.Trace208.upperLookup()` correctly captures the state at that exact timestamp, and that minting or transferring NFTs after `attestationsBegin` does not retroactively affect attestation power.
299
+ 7. **Attestation snapshotting**: Attestation weight is computed at the `attestationsBegin - 1` timestamp via `getPastTierAttestationUnitsOf()`. Pending reserves are snapshotted at submission time (`_pendingReservesSnapshotOf`). Verify that the `Checkpoints.Trace208.upperLookup()` correctly captures the state at that timestamp, that minting or transferring NFTs after `attestationsBegin - 1` does not retroactively affect attestation power, and that minting reserves after submission does not change the snapshotted pending reserve counts.
300
300
 
301
- 8. **Double attestation prevention**: `_attestations.hasAttested[msg.sender]` prevents double voting. But verify that an attacker cannot attest, transfer NFTs to another address, and have that address attest with the same attestation power (the snapshot at `attestationsBegin` should prevent this, but verify the checkpoint resolution).
301
+ 8. **Double attestation prevention**: `_attestations.attestedWeightOf[msg.sender]` prevents double voting. Verify that an attacker cannot attest, transfer NFTs to another address, and have that address attest with the same attestation power (the checkpoint at `attestationsBegin - 1` should prevent this because same-block transfers are invisible at `attestationsBegin - 1`).
302
302
 
303
303
  9. **Grace period anchoring**: `gracePeriodEnds = attestationsBegin + attestationGracePeriod`. Verify that early scorecard submission (before `attestationStartTime`) correctly delays the grace period start, preventing instant ratification.
304
304
 
@@ -344,7 +344,7 @@ These properties should hold for all games in all states. The test suite validat
344
344
 
345
345
  ### Governance
346
346
  - Each account can attest to a given scorecard at most once
347
- - Attestation power is snapshotted at `attestationsBegin` (not live)
347
+ - Attestation power is checkpointed at `attestationsBegin - 1` (not live); pending reserves snapshotted at submission time
348
348
  - Quorum threshold: 50% of minted tiers' total max attestation power (live at call time)
349
349
  - Only one scorecard can be ratified per game
350
350
  - Minimum grace period: 1 day (enforced in `initializeGame`)
package/CHANGE_LOG.md CHANGED
@@ -8,7 +8,20 @@ This document describes the changes between `defifa-collection-deployer` (v5) an
8
8
  - **Dependency modernization**: Core, permission IDs, address registry, and ownable dependencies all updated to v6. New ecosystem dependencies on `croptop-core-v6` and `revnet-core-v6`.
9
9
  - **Error naming standardized**: Error names changed from bare names (e.g., `InvalidCashoutWeights`) to contract-prefixed names (e.g., `DefifaHook_InvalidCashoutWeights`).
10
10
  - **Cash out hook spec gains `noop` field**: `beforeCashOutRecordedWith` now returns `noop=false` in all specifications, ensuring the terminal always calls the hook callback.
11
- - **Game lifecycle unchanged**: The core game phases (COUNTDOWN MINT REFUND SCORING COMPLETE) and governance model (50% quorum, scorecard ratification) remain identical.
11
+ - **Compiler/tooling updated**: The v6 repo now builds and tests on Solidity `0.8.28`, matching the rest of the V6 ecosystem.
12
+ - **Game lifecycle preserved**: The core game phases (COUNTDOWN → MINT → REFUND → SCORING → COMPLETE) and governance model (50% quorum, scorecard ratification) remain the same at the product level even though the underlying hook/controller integrations changed.
13
+
14
+ ## ABI Status
15
+
16
+ This repo does have meaningful ABI migration surface. The main ABI-facing contracts for integrators are:
17
+ - `IDefifaDeployer`
18
+ - `IDefifaHook`
19
+ - `IDefifaGovernor`
20
+
21
+ The largest ABI risks are:
22
+ - inherited 721-hook/core-v6 struct and return-shape changes flowing through Defifa interfaces;
23
+ - event families now living on v6 interfaces/contracts with prefixed errors and updated dependent types;
24
+ - hook return values that now include v6 `noop`-aware hook-spec structures.
12
25
 
13
26
  ---
14
27
 
@@ -42,7 +55,8 @@ The v6 `JBCashOutHookSpecification` struct has a `noop` boolean field. `DefifaHo
42
55
 
43
56
  ### 1.4 Solidity Version
44
57
 
45
- Compiler remains at `pragma solidity 0.8.23` (not bumped to 0.8.26 unlike most other v6 repos).
58
+ - **v5:** `pragma solidity 0.8.23`
59
+ - **v6:** `pragma solidity 0.8.28`
46
60
 
47
61
  ### 1.5 721 Hook API Changes
48
62
 
@@ -52,6 +66,14 @@ Inherited from `nana-721-hook-v6`:
52
66
  - `JB721TierConfig` gained `splitPercent` and `splits` fields
53
67
  - `JB721TiersHookFlags` gained `issueTokensForSplits` field
54
68
 
69
+ ### 1.6 Function-Level Integration Changes
70
+
71
+ These are the main V6 surface changes integrators should care about:
72
+ - `beforeCashOutRecordedWith(...)` now returns a `JBCashOutHookSpecification` that includes the new `noop` field from core-v6.
73
+ - Any integration constructing or decoding `JB721TierConfig` must account for the added `splitPercent` and `splits` fields.
74
+ - Any integration reading pricing context from the inherited 721 hook must expect 2 return values, not 3.
75
+ - Defifa deployer integrations should re-check `launchGameWith(...)`, `fulfillCommitmentsOf(...)`, `triggerNoContestFor(...)`, `nextPhaseNeedsQueueing(...)`, `safetyParamsOf(...)`, and `timesFor(...)` against the v6 ABIs and dependent core-v6 structs.
76
+
55
77
  ---
56
78
 
57
79
  ## 2. Game Lifecycle (Unchanged)
@@ -89,9 +111,22 @@ DefifaHook instances are deployed as minimal proxy clones via `Clones.cloneDeter
89
111
  - One-time `initialize()` call per clone
90
112
  - Owned by DefifaGovernor for scorecard weight setting
91
113
 
114
+ ## 4. Events and Errors
115
+
116
+ The most important integration-facing patterns are:
117
+ - Errors are now consistently contract-prefixed (`DefifaHook_*`, `DefifaDeployer_*`, `DefifaGovernor_*`, `DefifaProjectOwner_*`) instead of using the older unprefixed style.
118
+ - Defifa-specific events remain centered around game launch, phase transitions, fulfillment, scoring, minting, claims, and delegation, but they now live against the V6 hook/controller stack and should be indexed using the V6 ABIs.
119
+ - `CommitmentPayoutFailed`, `LaunchGame`, `QueuedRefundPhase`, `QueuedScoringPhase`, `QueuedNoContest`, `GameInitialized`, `ScorecardSubmitted`, `ScorecardAttested`, `ScorecardRatified`, `Mint`, `MintReservedToken`, `ClaimedTokens`, and `TierCashOutWeightsSet` are the key integration events to watch across the deployer, governor, and hook.
120
+ - Other hook-level events worth indexing for governance clients are `TierDelegateAttestationsChanged` and `DelegateChanged`.
121
+
122
+ Key runtime errors now exposed by the v6 contracts include:
123
+ - `DefifaDeployer_*` errors such as `DefifaDeployer_InvalidGameConfiguration`, `DefifaDeployer_TerminalNotFound`, `DefifaDeployer_SplitsDontAddUp`, `DefifaDeployer_CantFulfillYet`, and `DefifaDeployer_NoContestAlreadyTriggered`.
124
+ - `DefifaHook_*` errors such as `DefifaHook_InvalidCashoutWeights`, `DefifaHook_InvalidTierId`, `DefifaHook_ReservedTokenMintingPaused`, `DefifaHook_TransfersPaused`, and `DefifaHook_Unauthorized(...)`.
125
+ - `DefifaGovernor_*` errors such as `DefifaGovernor_AlreadyAttested`, `DefifaGovernor_AlreadyRatified`, `DefifaGovernor_DuplicateScorecard`, `DefifaGovernor_NotAllowed`, and `DefifaGovernor_UnknownProposal`.
126
+
92
127
  ---
93
128
 
94
- ## 4. Migration Table
129
+ ## 5. Migration Table
95
130
 
96
131
  | Aspect | v5 | v6 |
97
132
  |--------|----|----|
@@ -100,8 +135,30 @@ DefifaHook instances are deployed as minimal proxy clones via `Clones.cloneDeter
100
135
  | Permission IDs | Not a direct dependency | `@bananapus/permission-ids-v6` |
101
136
  | Error naming | Bare names | Contract-prefixed names |
102
137
  | `JBCashOutHookSpecification` | No `noop` field | `noop=false` on all specs |
103
- | Solidity version | `0.8.23` | `0.8.23` (unchanged) |
138
+ | Solidity version | `0.8.23` | `0.8.28` |
104
139
  | Game lifecycle | COUNTDOWN->MINT->REFUND->SCORING->COMPLETE | Identical |
105
140
  | Governance model | 50% quorum, tier-delegated | Identical |
106
141
 
107
- > **Cross-repo impact**: Uses `nana-721-hook-v6` for all tier management and cashout weight distribution. The `nana-permission-ids-v6` ID shifts affect any hardcoded permission checks. Not yet included in `deploy-all-v6` deployment phases (awaiting source updates).
142
+ > **Cross-repo impact**: Uses `nana-721-hook-v6` for all tier management and cashout weight distribution. The `nana-permission-ids-v6` ID shifts affect any hardcoded permission checks. `deploy-all-v6` now includes Defifa as Phase 10, so canonical deployments can source Defifa addresses from the top-level rollout.
143
+
144
+ ---
145
+
146
+ ## 6. Post-Audit Fixes (Codex R2)
147
+
148
+ ### 6.1 H-1: Prevent same-block double attestation via checkpoint snapshot
149
+
150
+ **File:** `DefifaGovernor.sol` -- `attestToScorecardFrom()`
151
+
152
+ Previously, attestation weight was snapshot at `_scorecard.attestationsBegin`, which could equal `block.timestamp` during the same block as a transfer. This allowed a holder to attest, transfer the NFT, and have the recipient also attest in the same block -- both counting because `upperLookup(block.timestamp)` includes same-block checkpoints.
153
+
154
+ **Fix:** Changed the checkpoint timestamp to `attestationsBegin - 1`, ensuring only state from before the attestation window opens is visible. Same-block transfer recipients now receive zero attestation weight. Note: NFTs minted in the same block as `attestationsBegin` have zero weight for attestation -- a negligible trade-off since attestation typically happens well after minting.
155
+
156
+ ### 6.2 H-2: Snapshot pending reserves at scorecard submission and include in denominators
157
+
158
+ **Files:** `DefifaGovernor.sol` -- `submitScorecardFor()`, `getBWAAttestationWeight()`; `DefifaHook.sol` -- `afterCashOutRecordedWith()`
159
+
160
+ Two related fixes to prevent gaming via pending reserve manipulation:
161
+
162
+ **Governance (attestation weight):** Previously, `getBWAAttestationWeight()` read pending reserve counts live from the store. Minting reserves between scorecard submission and attestation could inflate a holder's BWA power by removing the pending-reserve dilution. **Fix:** `submitScorecardFor()` now snapshots `numberOfPendingReservesFor()` for every tier into `_pendingReservesSnapshotOf[gameId][scorecardId][tierId]`. `getBWAAttestationWeight()` reads from this snapshot instead of live state, locking the dilution in place regardless of subsequent reserve minting.
163
+
164
+ **Cash-out (fee token distribution):** Previously, the fee token claim denominator (`_totalMintCost`) only counted minted tokens. Pending (unminted) reserve NFTs were excluded, allowing paid holders to cash out before reserves were minted and claim a disproportionate share of fee tokens ($DEFIFA/$NANA). **Fix:** `afterCashOutRecordedWith()` now passes `_totalMintCost + _pendingReserveMintCost()` as the denominator when distributing fee tokens. The new `_pendingReserveMintCost()` function iterates all tiers and sums `pendingReserves * tier.price`, ensuring pending reserves are accounted for in fee token distribution.