@ballkidz/defifa 0.0.1
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/.gas-snapshot +2 -0
- package/CRYPTO_ECON.md +955 -0
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +800 -0
- package/README.md +119 -0
- package/SKILLS.md +177 -0
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaDelegate.json +4867 -0
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaDeployer.json +1719 -0
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaGovernor.json +1535 -0
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaTokenUriResolver.json +295 -0
- package/deployments/defifa-v5/base_sepolia/DefifaDelegate.json +4875 -0
- package/deployments/defifa-v5/base_sepolia/DefifaDeployer.json +1725 -0
- package/deployments/defifa-v5/base_sepolia/DefifaGovernor.json +1543 -0
- package/deployments/defifa-v5/base_sepolia/DefifaTokenUriResolver.json +301 -0
- package/deployments/defifa-v5/optimism_sepolia/DefifaDelegate.json +4875 -0
- package/deployments/defifa-v5/optimism_sepolia/DefifaDeployer.json +1725 -0
- package/deployments/defifa-v5/optimism_sepolia/DefifaGovernor.json +1543 -0
- package/deployments/defifa-v5/optimism_sepolia/DefifaTokenUriResolver.json +301 -0
- package/deployments/defifa-v5/sepolia/DefifaDelegate.json +4875 -0
- package/deployments/defifa-v5/sepolia/DefifaDeployer.json +1725 -0
- package/deployments/defifa-v5/sepolia/DefifaGovernor.json +1543 -0
- package/deployments/defifa-v5/sepolia/DefifaTokenUriResolver.json +301 -0
- package/foundry.lock +17 -0
- package/foundry.toml +35 -0
- package/package.json +33 -0
- package/remappings.txt +6 -0
- package/script/Deploy.s.sol +109 -0
- package/script/helpers/DefifaDeploymentLib.sol +83 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +521 -0
- package/src/DefifaDeployer.sol +894 -0
- package/src/DefifaGovernor.sol +490 -0
- package/src/DefifaHook.sol +1056 -0
- package/src/DefifaProjectOwner.sol +63 -0
- package/src/DefifaTokenUriResolver.sol +312 -0
- package/src/enums/DefifaGamePhase.sol +11 -0
- package/src/enums/DefifaScorecardState.sol +10 -0
- package/src/interfaces/IDefifaDeployer.sol +108 -0
- package/src/interfaces/IDefifaGamePhaseReporter.sol +8 -0
- package/src/interfaces/IDefifaGamePotReporter.sol +8 -0
- package/src/interfaces/IDefifaGovernor.sol +132 -0
- package/src/interfaces/IDefifaHook.sol +228 -0
- package/src/interfaces/IDefifaTokenUriResolver.sol +10 -0
- package/src/libraries/DefifaFontImporter.sol +19 -0
- package/src/libraries/DefifaHookLib.sol +358 -0
- package/src/structs/DefifaAttestations.sol +9 -0
- package/src/structs/DefifaDelegation.sol +9 -0
- package/src/structs/DefifaLaunchProjectData.sol +59 -0
- package/src/structs/DefifaOpsData.sol +20 -0
- package/src/structs/DefifaScorecard.sol +9 -0
- package/src/structs/DefifaTierCashOutWeight.sol +9 -0
- package/src/structs/DefifaTierParams.sol +16 -0
- package/test/DefifaFeeAccounting.t.sol +559 -0
- package/test/DefifaGovernor.t.sol +1333 -0
- package/test/DefifaMintCostInvariant.t.sol +299 -0
- package/test/DefifaNoContest.t.sol +922 -0
- package/test/DefifaSecurity.t.sol +717 -0
- package/test/SVG.t.sol +164 -0
- package/test/deployScript.t.sol +144 -0
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Defifa
|
|
2
|
+
|
|
3
|
+
On-chain prediction games built on Juicebox. Players mint NFT game pieces representing teams or outcomes, a governor-based scorecard system determines payouts, and winners burn their NFTs to claim proportional shares of the pot.
|
|
4
|
+
|
|
5
|
+
Each game is a Juicebox project with phased rulesets that move through Countdown, Mint, Refund, Scoring, and Complete. The game's pot is the project's surplus. Anyone can launch a game by calling the deployer.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
### Minting
|
|
10
|
+
|
|
11
|
+
During the Mint phase, players pay the project's terminal to mint ERC-721 game pieces. Each tier represents a team or outcome -- all tiers share the same price. Minting delegates attestation power to the payer (or a specified delegate), which is used later during scorecard governance.
|
|
12
|
+
|
|
13
|
+
### Refunds
|
|
14
|
+
|
|
15
|
+
If an optional Refund phase is configured, players can burn their NFTs after minting closes to reclaim the full mint price. This gives players an exit window before the game locks in.
|
|
16
|
+
|
|
17
|
+
### Scoring
|
|
18
|
+
|
|
19
|
+
Once the game starts, anyone can submit a **scorecard** -- a proposed distribution of the pot across tiers. Scorecards are attested to by NFT holders, weighted by their tier-delegated voting power. Each tier contributes equal attestation power regardless of supply (capped at `MAX_ATTESTATION_POWER_TIER`), so a tier with 1 NFT has the same governance weight as a tier with 100 NFTs.
|
|
20
|
+
|
|
21
|
+
Once a scorecard reaches 50% quorum and its grace period elapses, it can be ratified. Ratification sets the cash-out weights on the hook and fulfills fee commitments (Defifa fee + protocol fee + user splits).
|
|
22
|
+
|
|
23
|
+
### Cash Outs
|
|
24
|
+
|
|
25
|
+
After ratification, the game enters the Complete phase. Players burn their NFTs via the terminal's `cashOutTokensOf` to claim their proportional share of the remaining pot. Each NFT's share is its tier's scorecard weight divided by the tier's minted supply. Players also receive a proportional share of any fee tokens ($DEFIFA / $NANA) that accumulated from the game's fee payments.
|
|
26
|
+
|
|
27
|
+
### Safety Mechanisms
|
|
28
|
+
|
|
29
|
+
Two configurable safety mechanisms prevent games from getting stuck:
|
|
30
|
+
|
|
31
|
+
- **Minimum participation** (`minParticipation`) -- if the pot never reaches a threshold, the game enters No Contest and all players can refund at mint price.
|
|
32
|
+
- **Scorecard timeout** (`scorecardTimeout`) -- if no scorecard is ratified within a time limit after scoring begins, the game enters No Contest.
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
DefifaDeployer ──── launches ────► Juicebox Project (phased rulesets)
|
|
38
|
+
│ │
|
|
39
|
+
├── clones ──► DefifaHook (ERC-721 game pieces, cash-out weights)
|
|
40
|
+
│ │
|
|
41
|
+
│ └── uses ──► JB721TiersHookStore (tier storage)
|
|
42
|
+
│
|
|
43
|
+
└── initializes ──► DefifaGovernor (scorecard governance)
|
|
44
|
+
│
|
|
45
|
+
└── owns ──► DefifaHook (sets weights on ratification)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Contracts
|
|
49
|
+
|
|
50
|
+
| Contract | Description |
|
|
51
|
+
|----------|-------------|
|
|
52
|
+
| `DefifaDeployer` | Game factory. Clones the hook, launches a Juicebox project with phased rulesets (Mint → Refund → Scoring), initializes the governor, and manages post-game commitment fulfillment (fee payouts). Also serves as the game phase and pot reporter. |
|
|
53
|
+
| `DefifaHook` | ERC-721 game piece hook (extends `JB721Hook`). Manages tier-based cash-out weights, per-tier attestation delegation with checkpointed voting power (`Checkpoints.Trace208`), and custom cash-out logic that distributes the pot proportionally based on the ratified scorecard. Deployed as minimal proxy clones. |
|
|
54
|
+
| `DefifaGovernor` | Scorecard governance. Accepts scorecard submissions (tier weight proposals), collects attestations weighted by tier-delegated voting power, and ratifies scorecards that reach 50% quorum. Executes the winning scorecard on the hook via low-level call. |
|
|
55
|
+
| `DefifaHookLib` | Library with pure/view helpers: scorecard validation, cash-out weight calculation, fee token distribution, attestation unit aggregation. Extracted to stay within EIP-170 contract size limits. |
|
|
56
|
+
| `DefifaTokenUriResolver` | On-chain SVG token URI resolver. Renders dynamic game cards showing phase, pot size, rarity, team name, and current value using embedded Capsules typeface. |
|
|
57
|
+
| `DefifaProjectOwner` | Receives the Defifa fee project's ownership NFT and permanently grants the deployer `SET_SPLIT_GROUPS` permission. |
|
|
58
|
+
|
|
59
|
+
### Game Lifecycle
|
|
60
|
+
|
|
61
|
+
| Phase | Ruleset Cycle | What Happens |
|
|
62
|
+
|-------|---------------|--------------|
|
|
63
|
+
| `COUNTDOWN` | 0 | Project launched, minting not yet open. |
|
|
64
|
+
| `MINT` | 1 | Players pay to mint NFT game pieces. Cash outs return full mint price (refund). Delegation can be set. |
|
|
65
|
+
| `REFUND` | 2 (optional) | Minting closed, refunds still allowed at face value. Delegation can still change. |
|
|
66
|
+
| `SCORING` | 3+ | Game started. Scorecards submitted, attested to, and ratified via governor. Delegation frozen. |
|
|
67
|
+
| `COMPLETE` | -- | Scorecard ratified. Commitments fulfilled. Players burn NFTs to claim pot share + fee tokens. |
|
|
68
|
+
| `NO_CONTEST` | -- | Safety mechanism triggered. All players can refund at mint price. |
|
|
69
|
+
|
|
70
|
+
### Fee Structure
|
|
71
|
+
|
|
72
|
+
| Fee | Rate | Recipient |
|
|
73
|
+
|-----|------|-----------|
|
|
74
|
+
| Protocol fee | 2.5% (`BASE_PROTOCOL_FEE_DIVISOR = 40`) | Base protocol project |
|
|
75
|
+
| Defifa fee | 5% (`DEFIFA_FEE_DIVISOR = 20`) | Defifa project |
|
|
76
|
+
| User splits | Configurable | Custom split recipients |
|
|
77
|
+
|
|
78
|
+
Fees are taken as payouts during commitment fulfillment. The remaining surplus is available for player cash outs. Fee payments generate $NANA and $DEFIFA tokens, which accumulate in the hook and are distributed proportionally to players when they cash out.
|
|
79
|
+
|
|
80
|
+
### Structs
|
|
81
|
+
|
|
82
|
+
| Struct | Purpose |
|
|
83
|
+
|--------|---------|
|
|
84
|
+
| `DefifaLaunchProjectData` | Full game configuration: name, tiers, token, durations, splits, attestation params, safety params, terminal, store. |
|
|
85
|
+
| `DefifaTierParams` | Per-tier config: name, reserved rate, beneficiary, encoded IPFS URI. Price is set uniformly via `tierPrice` on the launch data. |
|
|
86
|
+
| `DefifaTierCashOutWeight` | Scorecard entry: tier ID and its cash-out weight. Weights must sum to `TOTAL_CASHOUT_WEIGHT` (1e18). |
|
|
87
|
+
| `DefifaOpsData` | Packed game timing and safety params: token, start, mint duration, refund duration, min participation, scorecard timeout. |
|
|
88
|
+
| `DefifaDelegation` | Delegation assignment: delegatee address and tier ID. |
|
|
89
|
+
|
|
90
|
+
### Enums
|
|
91
|
+
|
|
92
|
+
| Enum | Values |
|
|
93
|
+
|------|--------|
|
|
94
|
+
| `DefifaGamePhase` | `COUNTDOWN`, `MINT`, `REFUND`, `SCORING`, `COMPLETE`, `NO_CONTEST` |
|
|
95
|
+
| `DefifaScorecardState` | `PENDING`, `ACTIVE`, `DEFEATED`, `SUCCEEDED`, `RATIFIED` |
|
|
96
|
+
|
|
97
|
+
## Install
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install @ballkidz/defifa-collection-deployer
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Develop
|
|
104
|
+
|
|
105
|
+
Uses [npm](https://www.npmjs.com/) for package management and [Foundry](https://github.com/foundry-rs/foundry) for builds, tests, and deployments. Requires `via-ir = true` in foundry.toml.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
curl -L https://foundry.paradigm.xyz | sh
|
|
109
|
+
npm install && forge install
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Command | Description |
|
|
113
|
+
|---------|-------------|
|
|
114
|
+
| `forge build` | Compile contracts and write artifacts to `out`. |
|
|
115
|
+
| `forge test` | Run the test suite (53 tests: unit, fuzz, invariant). |
|
|
116
|
+
| `forge test -vvvv` | Run tests with full traces. |
|
|
117
|
+
| `forge fmt` | Format Solidity files. |
|
|
118
|
+
| `forge build --sizes` | Get contract sizes. |
|
|
119
|
+
| `forge clean` | Remove build artifacts and cache. |
|
package/SKILLS.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Defifa
|
|
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
|
+
## Contracts
|
|
8
|
+
|
|
9
|
+
| Contract | Role |
|
|
10
|
+
|----------|------|
|
|
11
|
+
| `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`. |
|
|
12
|
+
| `DefifaHook` | ERC-721 hook (extends `JB721Hook`) that manages cash-out weights per tier, attestation delegation with checkpointed voting power, and proportional pot distribution on burn. Deployed as minimal proxy clones via `Clones.cloneDeterministic`. |
|
|
13
|
+
| `DefifaGovernor` | Governance contract for scorecard submission, attestation, and ratification with 50% quorum requirement. Shared singleton across all games. |
|
|
14
|
+
| `DefifaHookLib` | External library with pure/view helpers: scorecard validation, cash-out weight calculation, fee token distribution, attestation unit aggregation, supply computation. |
|
|
15
|
+
| `DefifaTokenUriResolver` | On-chain SVG renderer for game card metadata with phase-aware display, pot size, rarity, and current value. Uses embedded Capsules typeface. |
|
|
16
|
+
| `DefifaProjectOwner` | Receives Defifa fee project's ownership NFT and permanently grants the deployer `SET_SPLIT_GROUPS` permission. |
|
|
17
|
+
|
|
18
|
+
## Key Functions
|
|
19
|
+
|
|
20
|
+
| Function | Contract | What it does |
|
|
21
|
+
|----------|----------|--------------|
|
|
22
|
+
| `launchGameWith(data)` | `DefifaDeployer` | Creates a new game: clones the hook via `Clones.cloneDeterministic`, initializes it with tiers and reporters, launches a Juicebox project with phased rulesets (Mint → optional Refund → Scoring), initializes the governor, transfers hook ownership to the governor. Returns the game ID (Juicebox project ID). |
|
|
23
|
+
| `fulfillCommitmentsOf(gameId)` | `DefifaDeployer` | After scorecard ratification, sends the fee portion (Defifa fee + protocol fee + user splits) as payouts via `sendPayoutsOf`, then queues a final ruleset with `pausePay=true` and zero payout limits so the remaining pot is available for cash outs. Uses `max(amount, 1)` as reentrancy guard. |
|
|
24
|
+
| `triggerNoContestFor(gameId)` | `DefifaDeployer` | Checks safety conditions (min participation or scorecard timeout) and queues a NO_CONTEST ruleset enabling full refunds. Can only be called once per game. |
|
|
25
|
+
| `currentGamePhaseOf(gameId)` | `DefifaDeployer` | Returns the current game phase based on ruleset cycle number, cash-out weight state, and no-contest status. Implements `IDefifaGamePhaseReporter`. |
|
|
26
|
+
| `currentGamePotOf(gameId, includeCommitments)` | `DefifaDeployer` | Returns pot size, token address, and decimals. If `includeCommitments` is false, subtracts already-fulfilled commitment amount. |
|
|
27
|
+
| `timesFor(gameId)` | `DefifaDeployer` | Returns `(start, mintPeriodDuration, refundPeriodDuration)` for a game. |
|
|
28
|
+
| `safetyParamsOf(gameId)` | `DefifaDeployer` | Returns `(minParticipation, scorecardTimeout)` for a game. |
|
|
29
|
+
| `nextPhaseNeedsQueueing(gameId)` | `DefifaDeployer` | Returns true if the current ruleset has a duration > 0 and the latest queued ruleset is the same as the current one (meaning no new ruleset has been queued yet). |
|
|
30
|
+
| `submitScorecardFor(gameId, tierWeights)` | `DefifaGovernor` | Submits a proposed scorecard (array of tier cash-out weights). Hashes the encoded calldata to produce a scorecard ID. Stores attestation begin and grace period end timestamps. Only during SCORING phase. If `defaultAttestationDelegateProposalOf[gameId]` is 0, the first proposal from the default delegate auto-sets it. |
|
|
31
|
+
| `attestToScorecardFrom(gameId, scorecardId)` | `DefifaGovernor` | Attests to a scorecard. Weight is proportional to the caller's tier-delegated voting power at the attestation begin timestamp. Each address can only attest once per scorecard. Returns the attestation weight. |
|
|
32
|
+
| `ratifyScorecardFrom(gameId, tierWeights)` | `DefifaGovernor` | Ratifies a scorecard that has reached `SUCCEEDED` state (50% quorum). Executes `setTierCashOutWeightsTo` on the hook via low-level `.call`, then calls `fulfillCommitmentsOf`. Scorecard is immutable once ratified. |
|
|
33
|
+
| `initializeGame(gameId, startTime, gracePeriod)` | `DefifaGovernor` | Sets attestation start time and grace period for a game. Grace period minimum is 1 day. Called by the deployer during game launch. |
|
|
34
|
+
| `quorum(gameId)` | `DefifaGovernor` | Returns `50% of (MAX_ATTESTATION_POWER_TIER * numberOfMintedTiers)`. Only tiers with non-zero minted supply count toward quorum. |
|
|
35
|
+
| `getAttestationWeight(gameId, account, timestamp)` | `DefifaGovernor` | Calculates an account's attestation power across all tiers (up to 128) using checkpointed delegation snapshots at `timestamp`. Per-tier power: `mulDiv(MAX_ATTESTATION_POWER_TIER, accountTierUnits, totalTierUnits)`. |
|
|
36
|
+
| `stateOf(gameId, scorecardId)` | `DefifaGovernor` | Returns scorecard state: `RATIFIED` if matches ratified ID, `PENDING` if before attestation begin, `SUCCEEDED` if quorum reached + grace period elapsed, `ACTIVE` if attestation in progress, `DEFEATED` otherwise. |
|
|
37
|
+
| `setTierCashOutWeightsTo(tierWeights)` | `DefifaHook` | Sets cash-out weights for each tier. Validates weights sum to exactly `TOTAL_CASHOUT_WEIGHT` (1e18), tiers are in ascending order, and all tiers exist. Only callable by owner (the governor). Only callable during SCORING phase. Once set, cannot be changed (`cashOutWeightIsSet` flag). |
|
|
38
|
+
| `afterPayRecordedWith(context)` | `DefifaHook` | Processes payments: validates caller is a project terminal and `msg.value == 0`, then delegates to `_processPayment`. Overrides `JB721Hook` to add the `msg.value != 0` check. |
|
|
39
|
+
| `beforeCashOutRecordedWith(context)` | `DefifaHook` | Returns cash-out parameters based on game phase. During MINT/REFUND/NO_CONTEST: returns cumulative mint price as `cashOutCount` (full refund). During SCORING/COMPLETE: returns weighted share based on tier scorecard weights. Uses surplus as `totalSupply`. |
|
|
40
|
+
| `afterCashOutRecordedWith(context)` | `DefifaHook` | Burns NFTs, validates ownership, tracks redemptions per tier. During COMPLETE phase: increments `amountRedeemed`, distributes proportional $DEFIFA/$NANA tokens to the holder based on `_totalMintCost` share. Reverts with `NothingToClaim` if no ETH and no fee tokens received. Decrements `_totalMintCost` by the burned tokens' cumulative mint price. |
|
|
41
|
+
| `cashOutWeightOf(tokenIds)` | `DefifaHook` | Returns the cumulative cash-out weight for an array of token IDs. Each token's weight: `tierWeight / (minted - burned)`, accounting for already-redeemed tokens. Overrides `JB721Hook`. |
|
|
42
|
+
| `cashOutWeightOf(tokenId)` | `DefifaHook` | Returns the cash-out weight for a single token ID. |
|
|
43
|
+
| `totalCashOutWeight()` | `DefifaHook` | Returns `TOTAL_CASHOUT_WEIGHT` (1e18). Overrides `JB721Hook`. |
|
|
44
|
+
| `setTierDelegateTo(delegatee, tierId)` | `DefifaHook` | Delegates attestation voting power for a specific tier to another address. Only during MINT phase. Reverts during other phases. |
|
|
45
|
+
| `setTierDelegatesTo(delegations)` | `DefifaHook` | Batch variant. Sets delegates for multiple tiers at once. Only during MINT phase. |
|
|
46
|
+
| `mintReservesFor(tierId, count)` | `DefifaHook` | Mints reserved tokens for a tier. Auto-delegates to default attestation delegate if no delegate set. Increments `_totalMintCost` by `tier.price * count` so reserved recipients get their share of fee tokens. |
|
|
47
|
+
| `initialize(gameId, name, symbol, ...)` | `DefifaHook` | One-time initialization for a cloned hook. Sets project ID, name, symbol, store, rulesets, reporters, tiers, tier names, and default attestation delegate. Reverts if called on code origin or if already initialized. |
|
|
48
|
+
|
|
49
|
+
## Integration Points
|
|
50
|
+
|
|
51
|
+
| Dependency | Import | Used For |
|
|
52
|
+
|------------|--------|----------|
|
|
53
|
+
| `@bananapus/core-v6` | `IJBController`, `IJBDirectory`, `IJBRulesets`, `IJBTerminal`, `IJBMultiTerminal`, `JBRulesetConfig`, `JBSplit`, `JBConstants`, `JBMetadataResolver` | Project creation, ruleset management, terminal interactions, payout distribution, metadata encoding. |
|
|
54
|
+
| `@bananapus/721-hook-v6` | `JB721Hook`, `IJB721TiersHookStore`, `JB721TierConfig`, `JB721Tier`, `ERC721`, `JB721TiersRulesetMetadataResolver` | Hook base class, NFT tier management, tier storage, transfer pause checking. |
|
|
55
|
+
| `@bananapus/address-registry-v6` | `IJBAddressRegistry` | Hook address registration for discoverability. |
|
|
56
|
+
| `@bananapus/permission-ids-v6` | `JBPermissionIds` | Permission constants for split management (`SET_SPLIT_GROUPS`). |
|
|
57
|
+
| `@openzeppelin/contracts` | `Ownable`, `Clones`, `IERC721Receiver`, `SafeERC20`, `Checkpoints`, `Strings`, `IERC20` | Access control, minimal proxy cloning, safe token handling, checkpointed voting, string formatting, fee token transfers. |
|
|
58
|
+
| `@prb/math` | `mulDiv` | Precise fixed-point arithmetic for attestation weight and pot distribution calculations. |
|
|
59
|
+
|
|
60
|
+
## Key Types
|
|
61
|
+
|
|
62
|
+
| Struct/Enum | Key Fields | Used In |
|
|
63
|
+
|-------------|------------|---------|
|
|
64
|
+
| `DefifaLaunchProjectData` | `name`, `tiers` (DefifaTierParams[]), `tierPrice` (uint104), `token` (JBAccountingContext), `mintPeriodDuration` (uint24), `refundPeriodDuration` (uint24), `start` (uint48), `splits` (JBSplit[]), `attestationStartTime`, `attestationGracePeriod`, `defaultAttestationDelegate`, `terminal`, `store`, `minParticipation` (uint256), `scorecardTimeout` (uint32) | `DefifaDeployer.launchGameWith` |
|
|
65
|
+
| `DefifaTierParams` | `name` (string), `reservedRate` (uint16), `reservedTokenBeneficiary` (address), `encodedIPFSUri` (bytes32), `shouldUseReservedTokenBeneficiaryAsDefault` (bool) | `DefifaLaunchProjectData.tiers` |
|
|
66
|
+
| `DefifaTierCashOutWeight` | `id` (uint256), `cashOutWeight` (uint256) | Scorecard proposals, `DefifaHook.setTierCashOutWeightsTo` |
|
|
67
|
+
| `DefifaOpsData` | `token` (address), `start` (uint48), `mintPeriodDuration` (uint24), `refundPeriodDuration` (uint24), `minParticipation` (uint256), `scorecardTimeout` (uint32) | Internal game state in `DefifaDeployer` |
|
|
68
|
+
| `DefifaDelegation` | `delegatee` (address), `tierId` (uint256) | `DefifaHook.setTierDelegatesTo` |
|
|
69
|
+
| `DefifaGamePhase` | `COUNTDOWN`, `MINT`, `REFUND`, `SCORING`, `COMPLETE`, `NO_CONTEST` | Phase reporting throughout |
|
|
70
|
+
| `DefifaScorecardState` | `PENDING`, `ACTIVE`, `DEFEATED`, `SUCCEEDED`, `RATIFIED` | `DefifaGovernor.stateOf` |
|
|
71
|
+
|
|
72
|
+
## Constants
|
|
73
|
+
|
|
74
|
+
| Constant | Value | Location | Meaning |
|
|
75
|
+
|----------|-------|----------|---------|
|
|
76
|
+
| `TOTAL_CASHOUT_WEIGHT` | `1_000_000_000_000_000_000` (1e18) | `DefifaHook` | Total weight that scorecard tier weights must sum to exactly. |
|
|
77
|
+
| `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. |
|
|
78
|
+
| `BASE_PROTOCOL_FEE_DIVISOR` | `40` | `DefifaDeployer` | 2.5% fee to the base protocol project. |
|
|
79
|
+
| `DEFIFA_FEE_DIVISOR` | `20` | `DefifaDeployer` | 5% fee to the Defifa project. |
|
|
80
|
+
| Max tiers | `128` | `DefifaHook` | `_tierCashOutWeights` is a fixed `uint256[128]` array. |
|
|
81
|
+
| Grace period minimum | `1 day` | `DefifaGovernor` | Minimum attestation grace period enforced during `initializeGame`. |
|
|
82
|
+
|
|
83
|
+
## Cash-Out Logic by Phase
|
|
84
|
+
|
|
85
|
+
| Phase | `cashOutCount` | `totalSupply` | Effect |
|
|
86
|
+
|-------|---------------|---------------|--------|
|
|
87
|
+
| `MINT` / `REFUND` / `NO_CONTEST` | Cumulative mint price of tokens | Surplus | Full refund at mint price |
|
|
88
|
+
| `SCORING` (no scorecard) | 0 | Surplus | Reverts (nothing to claim) |
|
|
89
|
+
| `SCORING` / `COMPLETE` (scorecard set) | Weighted share of surplus minus amount already redeemed | Surplus | Proportional pot distribution based on tier weights |
|
|
90
|
+
|
|
91
|
+
During COMPLETE phase cash outs, players also receive proportional $DEFIFA and $NANA tokens based on their tokens' cumulative mint price relative to `_totalMintCost`.
|
|
92
|
+
|
|
93
|
+
## Attestation & Governance
|
|
94
|
+
|
|
95
|
+
- Each tier contributes equal `MAX_ATTESTATION_POWER_TIER` to quorum regardless of supply -- a tier with 1 NFT has the same governance weight as a tier with 100.
|
|
96
|
+
- Attestation power per account per tier: `mulDiv(MAX_ATTESTATION_POWER_TIER, accountTierUnits, totalTierUnits)`.
|
|
97
|
+
- Quorum: `50% of (MAX_ATTESTATION_POWER_TIER * numberOfMintedTiers)`. Only tiers with at least one minted token count.
|
|
98
|
+
- Attestation snapshots are taken at the scorecard's `attestationsBegin` timestamp, locking voting power to prevent post-submission manipulation.
|
|
99
|
+
- Each address can only attest once per scorecard.
|
|
100
|
+
- The grace period (minimum 1 day) prevents instant ratification after quorum is reached.
|
|
101
|
+
|
|
102
|
+
## Gotchas
|
|
103
|
+
|
|
104
|
+
- `TOTAL_CASHOUT_WEIGHT` is 1e18. Submitted scorecard tier weights must sum to **exactly** this value or `setTierCashOutWeightsTo` reverts with `DefifaHook_InvalidCashoutWeights`. No tolerance.
|
|
105
|
+
- Tier IDs in a scorecard must be in **strict ascending order** with no duplicates, or validation reverts with `DefifaHook_BadTierOrder`.
|
|
106
|
+
- Tier IDs are limited to 128 (`uint256[128] _tierCashOutWeights`). Games with more than 128 tiers are not supported.
|
|
107
|
+
- `DefifaHook` is deployed as a **minimal proxy clone** (`Clones.cloneDeterministic`). The `initialize` function can only be called once -- the code origin reverts (has `store != address(0)` after its own construction prevents re-init).
|
|
108
|
+
- All tiers share the same price (`tierPrice` on `DefifaLaunchProjectData`). The hook enforces this uniformity.
|
|
109
|
+
- Delegation changes are **only allowed during MINT phase**. During REFUND, SCORING, and COMPLETE, attestation delegation is frozen to prevent manipulation. Calling `setTierDelegateTo` outside MINT reverts with `DefifaHook_DelegateChangesUnavailableInThisPhase`.
|
|
110
|
+
- Scorecard attestation weight uses `mulDiv(MAX_ATTESTATION_POWER_TIER, userTierUnits, totalTierUnits)` per tier. If `totalTierUnits` is 0 for a tier (no delegations), that tier contributes no attestation power.
|
|
111
|
+
- The governor's `quorum` is **dynamic**: it only counts tiers that have at least one minted token. Adding minted tiers changes the quorum retroactively for all active proposals.
|
|
112
|
+
- `ratifyScorecardFrom` executes the scorecard via a **low-level `.call`** to the hook address. This is necessary because the hook's `setTierCashOutWeightsTo` is `onlyOwner` and the governor is the hook's owner.
|
|
113
|
+
- `fulfillCommitmentsOf` uses `max(amount, 1)` as a reentrancy guard. If called when the pot is 0, it stores 1 as the fulfilled amount to prevent re-entry.
|
|
114
|
+
- `_buildSplits` normalizes all split percentages relative to the total absolute percent. Rounding remainder is absorbed by the protocol fee split (last in the array).
|
|
115
|
+
- `_totalMintCost` tracks cumulative mint prices of all live tokens (paid + reserved). It's incremented on pay and reserve mint, decremented on cash out. This is the denominator for fee token ($DEFIFA/$NANA) distribution.
|
|
116
|
+
- Cash outs during COMPLETE phase revert with `DefifaHook_NothingToClaim` if **both** the reclaimed ETH amount is 0 **and** no fee tokens were transferred. This prevents burning NFTs for nothing.
|
|
117
|
+
- `minParticipation` is compared against the terminal's surplus. If surplus never reaches this value, `triggerNoContestFor` can be called to enter NO_CONTEST. A value of 0 disables this check.
|
|
118
|
+
- `scorecardTimeout` counts seconds from when SCORING begins. If no scorecard is ratified within this window, `triggerNoContestFor` can be called. A value of 0 disables this check.
|
|
119
|
+
- `triggerNoContestFor` can only be called once per game. It queues a new ruleset enabling full refunds and is irreversible.
|
|
120
|
+
- `afterPayRecordedWith` overrides `JB721Hook`'s version to add a `msg.value != 0` check. The base `JB721Hook` does not include this check.
|
|
121
|
+
- Token IDs follow the `JB721TiersHookStore` encoding: `tierId * 1_000_000_000 + tokenNumber`.
|
|
122
|
+
- Metadata IDs for pay and cashout use the **code origin address** (the uncloned implementation), not the clone address: `JBMetadataResolver.getId("pay", codeOrigin)`.
|
|
123
|
+
|
|
124
|
+
## Example Integration
|
|
125
|
+
|
|
126
|
+
```solidity
|
|
127
|
+
import {IDefifaDeployer} from "./interfaces/IDefifaDeployer.sol";
|
|
128
|
+
import {DefifaLaunchProjectData} from "./structs/DefifaLaunchProjectData.sol";
|
|
129
|
+
import {DefifaTierParams} from "./structs/DefifaTierParams.sol";
|
|
130
|
+
import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
|
|
131
|
+
|
|
132
|
+
// 1. Launch a game with 2 teams
|
|
133
|
+
DefifaTierParams[] memory tiers = new DefifaTierParams[](2);
|
|
134
|
+
tiers[0] = DefifaTierParams({
|
|
135
|
+
name: "Team A",
|
|
136
|
+
reservedRate: 1001, // no reserves
|
|
137
|
+
reservedTokenBeneficiary: address(0),
|
|
138
|
+
encodedIPFSUri: bytes32(0),
|
|
139
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false
|
|
140
|
+
});
|
|
141
|
+
tiers[1] = DefifaTierParams({
|
|
142
|
+
name: "Team B",
|
|
143
|
+
reservedRate: 1001,
|
|
144
|
+
reservedTokenBeneficiary: address(0),
|
|
145
|
+
encodedIPFSUri: bytes32(0),
|
|
146
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
uint256 gameId = deployer.launchGameWith(DefifaLaunchProjectData({
|
|
150
|
+
name: "Championship",
|
|
151
|
+
tierPrice: 0.01 ether,
|
|
152
|
+
tiers: tiers,
|
|
153
|
+
start: uint48(block.timestamp + 7 days),
|
|
154
|
+
mintPeriodDuration: 3 days,
|
|
155
|
+
refundPeriodDuration: 1 days,
|
|
156
|
+
minParticipation: 0, // no minimum
|
|
157
|
+
scorecardTimeout: 7 days, // 7-day timeout
|
|
158
|
+
// ... other fields
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
// 2. Submit a scorecard (Team A wins 70%, Team B gets 30%)
|
|
162
|
+
DefifaTierCashOutWeight[] memory weights = new DefifaTierCashOutWeight[](2);
|
|
163
|
+
weights[0] = DefifaTierCashOutWeight({id: 1, cashOutWeight: 7e17});
|
|
164
|
+
weights[1] = DefifaTierCashOutWeight({id: 2, cashOutWeight: 3e17});
|
|
165
|
+
// Total must equal 1e18
|
|
166
|
+
|
|
167
|
+
uint256 scorecardId = governor.submitScorecardFor(gameId, weights);
|
|
168
|
+
|
|
169
|
+
// 3. Attest to the scorecard (weight based on tier delegation)
|
|
170
|
+
governor.attestToScorecardFrom(gameId, scorecardId);
|
|
171
|
+
|
|
172
|
+
// 4. Ratify once quorum is reached and grace period elapsed
|
|
173
|
+
governor.ratifyScorecardFrom(gameId, weights);
|
|
174
|
+
|
|
175
|
+
// 5. Players burn NFTs via terminal cash-out to claim their share
|
|
176
|
+
// They receive proportional ETH + proportional $DEFIFA/$NANA tokens
|
|
177
|
+
```
|