@ballkidz/defifa 0.0.17 → 0.0.19
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/ADMINISTRATION.md +34 -6
- package/ARCHITECTURE.md +54 -102
- package/AUDIT_INSTRUCTIONS.md +96 -504
- package/CHANGELOG.md +26 -0
- package/README.md +61 -207
- package/RISKS.md +19 -2
- package/SKILLS.md +29 -281
- package/STYLE_GUIDE.md +57 -18
- package/USER_JOURNEYS.md +45 -1011
- package/foundry.lock +17 -0
- package/package.json +5 -6
- package/references/operations.md +27 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +5 -3
- package/src/DefifaDeployer.sol +33 -9
- package/src/DefifaGovernor.sol +63 -30
- package/src/DefifaHook.sol +33 -13
- package/src/interfaces/IDefifaDeployer.sol +28 -1
- package/src/interfaces/IDefifaGovernor.sol +26 -0
- package/src/interfaces/IDefifaHook.sol +30 -1
- package/src/libraries/DefifaHookLib.sol +8 -0
- package/test/DefifaGovernor.t.sol +55 -32
- package/test/DefifaSecurity.t.sol +1 -3
- package/test/Fork.t.sol +3 -9
- package/test/audit/CodexRegistryMismatch.t.sol +191 -0
- package/test/audit/PendingReserveSnapshotBypass.t.sol +40 -0
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -5
- package/CHANGE_LOG.md +0 -164
package/ADMINISTRATION.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Admin privileges and their scope in defifa-collection-deployer-v6.
|
|
4
4
|
|
|
5
|
+
## At A Glance
|
|
6
|
+
|
|
7
|
+
| Item | Details |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| Scope | Defifa game launch, scorecard ratification, lifecycle progression, and fee-project ownership locking. |
|
|
10
|
+
| Operators | Permissionless game creators and scorecard participants, plus the protocol contracts `DefifaDeployer`, `DefifaGovernor`, `DefifaHook`, and optional `DefifaProjectOwner`. |
|
|
11
|
+
| Highest-risk actions | Launching a game with bad immutable parameters, ratifying the winning scorecard, or transferring a fee-project NFT into `DefifaProjectOwner`. |
|
|
12
|
+
| Recovery posture | Game parameters are intentionally immutable after launch. A bad game setup usually means launching a new game or falling back to the no-contest path if available. |
|
|
13
|
+
|
|
14
|
+
## Routine Operations
|
|
15
|
+
|
|
16
|
+
- Validate game timings, tier setup, fee routing, and attestation settings before calling `launchGameWith()`.
|
|
17
|
+
- During scoring, rely on the documented submission, attestation, and ratification flow rather than looking for discretionary admin overrides that do not exist.
|
|
18
|
+
- Use `triggerNoContestFor()` when the game has entered the documented no-contest condition and refunds need to be unlocked.
|
|
19
|
+
- Treat the fee-project ownership proxy as a burn-lock mechanism, not a recoverable custody tool.
|
|
20
|
+
|
|
21
|
+
## One-Way Or High-Risk Actions
|
|
22
|
+
|
|
23
|
+
- `launchGameWith()` fixes the game's core configuration.
|
|
24
|
+
- `setTierCashOutWeightsTo()` is irreversible once weights are set for a game.
|
|
25
|
+
- `DefifaProjectOwner` permanently locks the project NFT it receives.
|
|
26
|
+
|
|
27
|
+
## Recovery Notes
|
|
28
|
+
|
|
29
|
+
- If a scorecard never reaches a valid ratification path and timeout conditions are met, use the documented no-contest recovery flow.
|
|
30
|
+
- If the game launched with the wrong immutable economics or timing, recovery is a replacement game deployment rather than an admin patch.
|
|
31
|
+
|
|
5
32
|
## Roles
|
|
6
33
|
|
|
7
34
|
| Role | Who | How Assigned |
|
|
@@ -30,10 +57,10 @@ Admin privileges and their scope in defifa-collection-deployer-v6.
|
|
|
30
57
|
|
|
31
58
|
| Function | Required Role | Permission Check | What It Does |
|
|
32
59
|
|----------|--------------|-----------------|-------------|
|
|
33
|
-
| `initializeGame()` | DefifaDeployer (owner) | `onlyOwner` | Sets attestation start time
|
|
60
|
+
| `initializeGame()` | DefifaDeployer (owner) | `onlyOwner` | Sets the attestation start time, attestation grace period, and optional post-quorum `timelockDuration` for a game. Enforces a minimum 1-day grace period. Called automatically during `launchGameWith()`. |
|
|
34
61
|
| `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
|
|
36
|
-
| `ratifyScorecardFrom()` | Anyone | Scorecard must be in SUCCEEDED state (quorum met
|
|
62
|
+
| `attestToScorecardFrom()` | Any NFT holder | Must be in SCORING phase; scorecard must be `ACTIVE`, `QUEUED`, 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. |
|
|
63
|
+
| `ratifyScorecardFrom()` | Anyone | Scorecard must be in SUCCEEDED state (quorum met, grace period elapsed, and any configured `timelockDuration` elapsed); no scorecard already ratified | Executes the scorecard via low-level call to `setTierCashOutWeightsTo` on the hook, then calls `fulfillCommitmentsOf`. |
|
|
37
64
|
|
|
38
65
|
### DefifaHook
|
|
39
66
|
|
|
@@ -73,7 +100,7 @@ COUNTDOWN --> MINT --> REFUND (optional) --> SCORING --> COMPLETE or NO_CONTEST
|
|
|
73
100
|
**Who controls scoring:**
|
|
74
101
|
1. Anyone submits a scorecard during SCORING (`submitScorecardFor`)
|
|
75
102
|
2. NFT holders attest based on their per-tier voting weight (`attestToScorecardFrom`)
|
|
76
|
-
3. Once quorum (50% of minted tiers' attestation power) is met
|
|
103
|
+
3. Once quorum (50% of minted tiers' attestation power) is met, the grace period has elapsed, and any configured `timelockDuration` has elapsed, the scorecard reaches `SUCCEEDED` and anyone can ratify it with `ratifyScorecardFrom`
|
|
77
104
|
4. The governor calls `setTierCashOutWeightsTo` on the hook via low-level call
|
|
78
105
|
5. `fulfillCommitmentsOf` sends fee payouts (try-catch) and queues the final ruleset
|
|
79
106
|
|
|
@@ -84,8 +111,8 @@ The quorum threshold is 50% of the total attestation power across all tiers with
|
|
|
84
111
|
**Edge cases:**
|
|
85
112
|
- **Tiers with zero mints:** Tiers with `currentSupplyOfTier(tierId) == 0` are excluded from the quorum calculation. They have no attestation power and cannot influence scoring.
|
|
86
113
|
- **All mints in a single tier:** If all participation concentrates in one tier, that tier's holders control the quorum. The 50% threshold still applies -- holders of 50% of that tier's supply can ratify a scorecard.
|
|
87
|
-
- **Grace period:** After a scorecard
|
|
88
|
-
- **Competing scorecards:** Multiple scorecards can be submitted. Each tracks attestations independently. Only the first to be ratified
|
|
114
|
+
- **Grace period and timelock:** After submission, a scorecard stays `ACTIVE` until its grace period ends. If it has quorum at that point, it becomes `QUEUED` when `timelockDuration > 0`, or `SUCCEEDED` immediately when `timelockDuration == 0`. Attestations remain allowed while the scorecard is `ACTIVE`, `QUEUED`, or `SUCCEEDED`, but revocations are only allowed while it is `ACTIVE`. Ratification is only allowed from `SUCCEEDED`.
|
|
115
|
+
- **Competing scorecards:** Multiple scorecards can be submitted. Each tracks attestations independently. Only the first to be ratified after meeting quorum, clearing the grace period, and clearing any configured timelock takes effect. Once ratified, no other scorecard can be ratified for the same game.
|
|
89
116
|
- **Scorecard timeout:** If `scorecardTimeout` is nonzero and elapses without ratification, the game enters NO_CONTEST state, enabling full refunds via `triggerNoContestFor()`.
|
|
90
117
|
|
|
91
118
|
**No single entity controls scoring.** The process requires collective attestation from NFT holders across tiers.
|
|
@@ -103,6 +130,7 @@ The following are set at game creation and cannot be changed:
|
|
|
103
130
|
| Fee structure | Constructor constants | `DEFIFA_FEE_DIVISOR = 20` (5%), `BASE_PROTOCOL_FEE_DIVISOR = 40` (2.5%) |
|
|
104
131
|
| Attestation start time | `launchGameWith()` | Stored in governor via `initializeGame()` |
|
|
105
132
|
| Attestation grace period | `launchGameWith()` | Minimum 1 day enforced in `initializeGame()` |
|
|
133
|
+
| Timelock duration | `launchGameWith()` | Optional cooling period after quorum before a scorecard becomes ratifiable |
|
|
106
134
|
| Default attestation delegate | `launchGameWith()` | Stored in hook |
|
|
107
135
|
| `minParticipation` threshold | `launchGameWith()` | 0 = disabled |
|
|
108
136
|
| `scorecardTimeout` | `launchGameWith()` | 0 = disabled |
|
package/ARCHITECTURE.md
CHANGED
|
@@ -1,115 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Architecture
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
SCORING Phase:
|
|
41
|
-
→ Anyone → DefifaGovernor.submitScorecard(weights[])
|
|
42
|
-
→ Tier holders attest to scorecards
|
|
43
|
-
→ Scorecard reaches quorum → ratified
|
|
44
|
-
→ DefifaHook receives final cash-out weights per tier
|
|
45
|
-
|
|
46
|
-
COMPLETE Phase:
|
|
47
|
-
→ Deployer → fulfillCommitmentsOf() sends fee payouts and queues the final ruleset
|
|
48
|
-
→ Winners → cash out NFTs at scored weights (see "Scored Weight Redemption" below)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Governance Flow
|
|
52
|
-
```
|
|
53
|
-
Scorer → DefifaGovernor.submitScorecard(tierWeights[])
|
|
54
|
-
→ Validate: correct phase, valid tier order, weights sum correctly
|
|
55
|
-
→ Create proposal hash
|
|
56
|
-
→ Snapshot pending reserves per tier for BWA computation
|
|
57
|
-
|
|
58
|
-
Attestor → DefifaGovernor.attestToScorecard(proposalId)
|
|
59
|
-
→ Must hold NFT tier tokens at attestationsBegin - 1 checkpoint
|
|
60
|
-
→ Attestation weight = voting power from held tiers (diluted by snapshotted pending reserves)
|
|
61
|
-
→ When quorum reached → scorecard ratified
|
|
62
|
-
→ DefifaHook.setScorecard() called
|
|
5
|
+
`defifa-collection-deployer-v6` builds prediction games on top of Juicebox and the 721 tiers hook. Each game is a project with phased rulesets, a hook that mints and cashes out game-piece NFTs, and a governor that ratifies scorecards which decide how the prize pool is distributed.
|
|
6
|
+
|
|
7
|
+
## Boundaries
|
|
8
|
+
|
|
9
|
+
- `DefifaHook` owns game-piece behavior, delegation, and cash-out math.
|
|
10
|
+
- `DefifaGovernor` owns scorecard submission, attestation, quorum, and ratification.
|
|
11
|
+
- `DefifaDeployer` owns game launch and post-ratification commitment fulfillment.
|
|
12
|
+
- Generic tier storage and terminal accounting stay in `nana-721-hook-v6` and `nana-core-v6`.
|
|
13
|
+
|
|
14
|
+
## Main Components
|
|
15
|
+
|
|
16
|
+
| Component | Responsibility |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `DefifaDeployer` | Launches phased projects, clones hooks, initializes the governor, and fulfills commitments |
|
|
19
|
+
| `DefifaHook` | ERC-721 game pieces, attestation delegation, custom cash-out weighting, and game-state aware behavior |
|
|
20
|
+
| `DefifaGovernor` | Scorecard proposals, attestations, quorum checks, grace periods, and ratification |
|
|
21
|
+
| `DefifaHookLib` | Shared validation and weight math extracted to keep the hook within size limits |
|
|
22
|
+
| `DefifaTokenUriResolver` | Dynamic token metadata and SVG rendering |
|
|
23
|
+
| `DefifaProjectOwner` | Lock helper for the fee project |
|
|
24
|
+
|
|
25
|
+
## Runtime Model
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
creator
|
|
29
|
+
-> DefifaDeployer launches a project with staged rulesets
|
|
30
|
+
players
|
|
31
|
+
-> mint tiered NFTs during the mint window
|
|
32
|
+
holders
|
|
33
|
+
-> optionally refund during the refund window
|
|
34
|
+
attesters
|
|
35
|
+
-> submit and attest to scorecards during scoring
|
|
36
|
+
governor
|
|
37
|
+
-> ratifies the winning scorecard after quorum and grace-period checks
|
|
38
|
+
holders
|
|
39
|
+
-> cash out NFTs for their share of the prize pool during completion
|
|
63
40
|
```
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
## Critical Invariants
|
|
66
43
|
|
|
67
|
-
|
|
44
|
+
- Delegation and attestation power must stay aligned with tier semantics; a scorecard system that can be inflated by supply alone breaks the game.
|
|
45
|
+
- Ratification is the only path that should install final cash-out weights.
|
|
46
|
+
- Refund, scoring, complete, and no-contest phases must remain mutually coherent. If phase transitions drift, funds get stuck.
|
|
47
|
+
- The deployer's commitment-fulfillment logic is part of game completion, not optional bookkeeping.
|
|
68
48
|
|
|
69
|
-
|
|
49
|
+
## Where Complexity Lives
|
|
70
50
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Example: 100 ETH pot, 4 tiers, winning tier gets weight 500000000000000000 (50%). If that tier had 10 minted tokens, each token redeems for `100 * (500000000000000000 / 10) / 1e18 = 5 ETH`.
|
|
76
|
-
|
|
77
|
-
### fulfillCommitmentsOf
|
|
78
|
-
|
|
79
|
-
Called automatically when a scorecard is ratified (by `ratifyScorecardFrom`). It performs two actions:
|
|
80
|
-
|
|
81
|
-
1. **Sends fee payouts**: Computes the fee portion of the pot based on the split percentages configured at game creation, then calls `sendPayoutsOf` to distribute fees to the protocol. If the payout fails, the fee stays in the pot and the function continues (try-catch ensures the final ruleset is always queued).
|
|
82
|
-
2. **Queues the final ruleset**: Queues a new Juicebox ruleset with `pausePay: true` (no new payments), `cashOutTaxRate: 0` (no tax on cash-outs), and the data hook still active so scored weights are enforced. This transitions the game to its terminal state where only cash-outs remain.
|
|
83
|
-
|
|
84
|
-
## Extension Points
|
|
85
|
-
|
|
86
|
-
| Point | Interface | Purpose |
|
|
87
|
-
|-------|-----------|---------|
|
|
88
|
-
| Data hook | `IJBRulesetDataHook` | Phase-aware pay/cashout behavior |
|
|
89
|
-
| Pay hook | `IJBPayHook` | NFT minting during MINT phase |
|
|
90
|
-
| Cash out hook | `IJBCashOutHook` | Scored weight redemptions |
|
|
91
|
-
| Token URI resolver | `IJB721TokenUriResolver` | On-chain SVG generation |
|
|
92
|
-
| Governor | `IDefifaGovernor` | Scorecard governance |
|
|
51
|
+
- The game is split across hook, governor, and deployer contracts, but users experience it as one state machine.
|
|
52
|
+
- Scorecard weighting, attestation accounting, and phase transitions are tightly coupled.
|
|
53
|
+
- Completion logic is economically sensitive because it combines prize distribution with fee fulfillment.
|
|
93
54
|
|
|
94
55
|
## Dependencies
|
|
95
|
-
- `@bananapus/core-v6` — Core protocol
|
|
96
|
-
- `@bananapus/721-hook-v6` — NFT tier system
|
|
97
|
-
- `@bananapus/address-registry-v6` — Deterministic deploys
|
|
98
|
-
- `@bananapus/permission-ids-v6` — Permission constants
|
|
99
|
-
- `@croptop/core-v6` — Croptop integration
|
|
100
|
-
- `@rev-net/core-v6` — Revnet integration
|
|
101
|
-
- `@openzeppelin/contracts` — Checkpoints, Ownable, Clones
|
|
102
|
-
- `@prb/math` — mulDiv
|
|
103
|
-
- `scripty.sol` — On-chain scripting for SVG
|
|
104
|
-
|
|
105
|
-
## Design Decisions
|
|
106
|
-
|
|
107
|
-
**Attestation-based governance over token-weighted voting.** Each tier gets equal max attestation power (`MAX_ATTESTATION_POWER_TIER = 1e9`) regardless of how many tokens it sold. A holder's power within a tier is proportional to their share of that tier's supply. This prevents a popular outcome (e.g., the favorite team) from dominating the scorecard simply by having more buyers. Every outcome's community has equal say in ratification.
|
|
108
|
-
|
|
109
|
-
**Six distinct game phases.** The MINT, REFUND, SCORING, and COMPLETE phases enforce a strict lifecycle where each action is only valid in its phase. MINT allows buying in, REFUND provides a grace period for full refunds, SCORING locks the treasury while governance resolves, and COMPLETE enables scored cash-outs. The COUNTDOWN phase gates minting before the game starts, and NO_CONTEST acts as a fallback if no scorecard is ratified within the timeout. This phased approach prevents timing exploits (e.g., buying in after seeing results) and ensures the treasury is never drained during scoring.
|
|
110
56
|
|
|
111
|
-
|
|
57
|
+
- `nana-721-hook-v6` for tiered NFT infrastructure
|
|
58
|
+
- `nana-core-v6` for phased rulesets, terminals, and payout mechanics
|
|
59
|
+
- Juicebox governance and permission surfaces for project ownership and split updates
|
|
112
60
|
|
|
113
|
-
|
|
61
|
+
## Safe Change Guide
|
|
114
62
|
|
|
115
|
-
|
|
63
|
+
- Treat phase logic, governor logic, and hook cash-out logic as one system.
|
|
64
|
+
- If you change scorecard validation, also inspect attestation weighting and ratification thresholds.
|
|
65
|
+
- Keep `DefifaHookLib` and hook storage assumptions in sync; library extraction is for size, not for a separate trust boundary.
|
|
66
|
+
- Do not move generic 721 behavior into Defifa just because the game uses it heavily.
|
|
67
|
+
- When in doubt, trace an end-to-end game lifecycle rather than auditing one contract in isolation.
|