@ballkidz/defifa 0.0.13 → 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.
- package/ADMINISTRATION.md +3 -3
- package/ARCHITECTURE.md +3 -2
- package/AUDIT_INSTRUCTIONS.md +5 -5
- package/CHANGE_LOG.md +8 -6
- package/CRYPTO_ECON.md +4 -4
- package/RISKS.md +8 -4
- package/SKILLS.md +3 -2
- package/USER_JOURNEYS.md +4 -3
- package/package.json +2 -2
- package/src/DefifaGovernor.sol +30 -4
- package/src/DefifaHook.sol +27 -1
- package/src/interfaces/IDefifaGovernor.sol +1 -0
- package/test/DefifaGovernanceHardening.t.sol +6 -2
- package/test/DefifaGovernor.t.sol +4 -2
- package/test/DefifaSecurity.t.sol +5 -10
- package/test/Fork.t.sol +26 -20
- package/test/TestQALastMile.t.sol +2 -1
- package/test/audit/PendingReserveSnapshotBypass.t.sol +279 -0
- package/test/SVG.t.sol +0 -164
- 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
|
|
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`
|
|
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
|
```
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -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 `
|
|
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
|
|
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.
|
|
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
|
|
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
|
@@ -145,18 +145,20 @@ Key runtime errors now exposed by the v6 contracts include:
|
|
|
145
145
|
|
|
146
146
|
## 6. Post-Audit Fixes (Codex R2)
|
|
147
147
|
|
|
148
|
-
### 6.1 H-1: Prevent same-block double attestation via
|
|
148
|
+
### 6.1 H-1: Prevent same-block double attestation via checkpoint snapshot
|
|
149
149
|
|
|
150
150
|
**File:** `DefifaGovernor.sol` -- `attestToScorecardFrom()`
|
|
151
151
|
|
|
152
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
153
|
|
|
154
|
-
**Fix:** Changed the
|
|
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
155
|
|
|
156
|
-
### 6.2 H-2:
|
|
156
|
+
### 6.2 H-2: Snapshot pending reserves at scorecard submission and include in denominators
|
|
157
157
|
|
|
158
|
-
**
|
|
158
|
+
**Files:** `DefifaGovernor.sol` -- `submitScorecardFor()`, `getBWAAttestationWeight()`; `DefifaHook.sol` -- `afterCashOutRecordedWith()`
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
Two related fixes to prevent gaming via pending reserve manipulation:
|
|
161
161
|
|
|
162
|
-
**
|
|
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.
|
package/CRYPTO_ECON.md
CHANGED
|
@@ -379,11 +379,11 @@ where $n_i^{\text{holder}}$ is the number of tier-$i$ tokens delegated to (or he
|
|
|
379
379
|
|
|
380
380
|
$$v^{\text{holder}} = \sum_{i : n_i^{\text{holder}} > 0} V_{\text{max}} \cdot \frac{n_i^{\text{holder}}}{n_i^{\text{total}}} \tag{25}$$
|
|
381
381
|
|
|
382
|
-
**Checkpoint-based snapshots.** Attestation power is measured at the scorecard's `attestationsBegin` timestamp
|
|
382
|
+
**Checkpoint-based snapshots.** Attestation power is measured at the scorecard's `attestationsBegin - 1` timestamp (one second before the attestation window opens). This prevents same-block transfer manipulation: acquiring tokens at or after `attestationsBegin` provides zero additional voting power. Additionally, pending reserve counts are snapshotted per tier at submission time (`_pendingReservesSnapshotOf`), so minting reserves after submission cannot inflate attestation power by removing the pending-reserve dilution. All attestors' weights are measured at the same point in time with a fixed pending reserve baseline, ensuring fairness.
|
|
383
383
|
|
|
384
384
|
**Delegation.** During the mint phase only, holders may delegate their attestation units to a chosen delegate address per tier. Delegation is:
|
|
385
385
|
- Per-tier (a holder can delegate different tiers to different delegates),
|
|
386
|
-
- Snapshot-locked (only the delegation state at `attestationsBegin` counts),
|
|
386
|
+
- Snapshot-locked (only the delegation state at `attestationsBegin - 1` counts),
|
|
387
387
|
- Mint-phase-only (no delegation changes after minting closes — the `_update` function enforces `DELEGATE_CHANGES_UNAVAILABLE_IN_THIS_PHASE`).
|
|
388
388
|
|
|
389
389
|
**World Cup example.** Argentina (Tier 1) has 2,000 NFTs. A fan holding 100 Argentina NFTs has attestation power: $10^9 \times 100/2{,}000 = 50{,}000{,}000$ from Tier 1. If they also hold 50 France NFTs (out of 1,800): $10^9 \times 50/1{,}800 \approx 27{,}778{,}000$ from Tier 2. Total: $\sim 77.8$ million attestation units. Note that despite Argentina having more total mints, each *tier* contributes equally to governance weight — the per-tier cap ensures that Argentina's 2,000 holders collectively have the same maximum power ($10^9$) as Saudi Arabia's 10 holders.
|
|
@@ -431,7 +431,7 @@ The attestation model incorporates several defenses against strategic manipulati
|
|
|
431
431
|
|
|
432
432
|
**Defense 1: Per-tier cap.** No single tier's holders can contribute more than $V_{\text{max}}$ attestation units, regardless of how many tokens they hold. A whale who buys the entire supply of one tier has exactly $V_{\text{max}}$ power — the same as if any single holder held the tier.
|
|
433
433
|
|
|
434
|
-
**Defense 2: Checkpoint snapshots.** Attestation power is computed at a fixed historical timestamp (`attestationsBegin`). Acquiring tokens after the snapshot provides zero additional voting power for that scorecard.
|
|
434
|
+
**Defense 2: Checkpoint snapshots.** Attestation power is computed at a fixed historical timestamp (`attestationsBegin - 1`). Acquiring tokens after the snapshot provides zero additional voting power for that scorecard. Pending reserve counts are snapshotted at submission time, preventing reserve minting from inflating attestation power.
|
|
435
435
|
|
|
436
436
|
**Defense 3: Mint-phase-only delegation.** Delegation is locked after the mint phase, preventing last-minute delegation changes during the scoring phase.
|
|
437
437
|
|
|
@@ -1147,7 +1147,7 @@ The scorecard weight system ($\sum w_i = 10^{18}$) provides a flexible framework
|
|
|
1147
1147
|
|
|
1148
1148
|
### Governance Security
|
|
1149
1149
|
|
|
1150
|
-
The attestation model (Section 3) achieves a balance between decentralization and efficiency. The per-tier cap on attestation power ($V_{\text{max}} = 10^9$) prevents any single tier from dominating governance, while the 50% quorum across minted tiers ensures broad participation. The checkpoint-based snapshot (at `attestationsBegin
|
|
1150
|
+
The attestation model (Section 3) achieves a balance between decentralization and efficiency. The per-tier cap on attestation power ($V_{\text{max}} = 10^9$) prevents any single tier from dominating governance, while the 50% quorum across minted tiers ensures broad participation. The checkpoint-based snapshot (at `attestationsBegin - 1`, with pending reserves snapshotted at submission time) prevents vote-buying and reserve-minting manipulation, mint-phase-only delegation prevents last-minute manipulation, and scoring-phase-only submission prevents pre-accumulation of attestations.
|
|
1151
1151
|
|
|
1152
1152
|
Section 9.2 introduces **benefit-weighted attestation** (BWA): the "perfect proportion" where a tier's governance power for a given scorecard equals $V_{\text{max}} \times (1 - w_i / W_{\text{total}})$. This structural mechanism makes self-serving scorecards unratifiable regardless of attacker capital — the beneficiaries of a scorecard cannot be the coalition that pushes it through. The dead token economics prove that even attacks overcoming BWA are unprofitable: tokens purchased for governance power in non-winning tiers return \$0 under the fraudulent scorecard, creating a guaranteed loss when combined with fee extraction. Section 9.3 formalizes the Uniform Participation Theorem, proving that games with equal tier supply are impervious to profitable governance attacks.
|
|
1153
1153
|
|
package/RISKS.md
CHANGED
|
@@ -68,10 +68,14 @@ Cash-out weights set via `ratifyScorecardFrom` cannot be updated or corrected. T
|
|
|
68
68
|
|
|
69
69
|
`ratifyScorecardFrom` executes arbitrary calldata on the hook via low-level call. The hook's `setTierCashOutWeightsTo` has an `onlyOwner` guard and a `cashOutWeightIsSet` check preventing double-set. Both guards prevent reentrancy exploitation.
|
|
70
70
|
|
|
71
|
-
### 8.5 Attestation snapshot uses
|
|
71
|
+
### 8.5 Attestation snapshot uses attestationsBegin - 1 (Codex R2 fix)
|
|
72
72
|
|
|
73
|
-
`attestToScorecardFrom` snapshots attestation weight at `
|
|
73
|
+
`attestToScorecardFrom` snapshots attestation weight at `attestationsBegin - 1` instead of `attestationsBegin`. This prevents same-block transfer manipulation where a holder attests, transfers the NFT, and the recipient also attests in the same block. The trade-off is that NFTs minted in the same block as `attestationsBegin` have zero weight for that attestation call. This is acceptable because attestation typically happens well after minting, and the delay is negligible.
|
|
74
74
|
|
|
75
|
-
### 8.6 Pending reserves
|
|
75
|
+
### 8.6 Pending reserves snapshotted at submission and included in denominators (Codex R2 fix)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Two related protections:
|
|
78
|
+
|
|
79
|
+
**Governance:** `submitScorecardFor` snapshots `numberOfPendingReservesFor()` per tier into `_pendingReservesSnapshotOf`. `getBWAAttestationWeight` reads from this snapshot instead of live state. This prevents reserve minting between submission and attestation from inflating a holder's voting power by removing pending-reserve dilution. The trade-off is that if reserves are minted after submission, the dilution persists even though the reserves are no longer pending. This is conservative but correct -- it locks governance power at submission time.
|
|
80
|
+
|
|
81
|
+
**Cash-out (fee tokens):** `afterCashOutRecordedWith` includes `_pendingReserveMintCost()` (sum of `pendingReserves * tier.price` across all tiers) in the fee token claim denominator. This prevents paid holders from claiming a disproportionate share of $DEFIFA/$NANA tokens before reserves are minted. The trade-off is that if reserve NFTs are never minted (e.g., the reserve beneficiary is set to address(0) and minting reverts), those shares of fee tokens remain locked in the contract. This is acceptable because: (1) it prevents paid holders from front-running reserve minting to extract the reserves' share, and (2) reserve beneficiaries are set at deployment and should always be valid.
|
package/SKILLS.md
CHANGED
|
@@ -209,7 +209,7 @@ During COMPLETE phase cash outs, players also receive proportional $DEFIFA and $
|
|
|
209
209
|
|
|
210
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
211
|
- **Quorum**: `50% of (MAX_ATTESTATION_POWER_TIER * numberOfMintedTiers)`. Only tiers with at least one minted token count.
|
|
212
|
-
-
|
|
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
213
|
- Each address can only attest once per scorecard.
|
|
214
214
|
- Grace period (minimum 1 day) prevents instant ratification after quorum is reached.
|
|
215
215
|
|
|
@@ -223,7 +223,8 @@ During COMPLETE phase cash outs, players also receive proportional $DEFIFA and $
|
|
|
223
223
|
- **Delegation only during MINT phase**. Other phases revert with `DefifaHook_DelegateChangesUnavailableInThisPhase`.
|
|
224
224
|
- If `totalTierUnits` is 0 for a tier (no delegations), that tier contributes no attestation power.
|
|
225
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**: `
|
|
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.
|
|
227
228
|
- `ratifyScorecardFrom` uses **low-level `.call`** to execute the scorecard on the hook (necessary because `setTierCashOutWeightsTo` is `onlyOwner`).
|
|
228
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`.
|
|
229
230
|
- `_buildSplits` normalizes split percentages. Rounding remainder absorbed by the protocol fee split (last in array).
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -349,7 +349,8 @@ uint256 scorecardId = governor.submitScorecardFor(gameId, tierWeights);
|
|
|
349
349
|
|
|
350
350
|
1. `DefifaGovernor._scorecardOf[gameId][scorecardId].attestationsBegin` -- Set to `max(block.timestamp, attestationStartTime)`.
|
|
351
351
|
2. `DefifaGovernor._scorecardOf[gameId][scorecardId].gracePeriodEnds` -- Set to `attestationsBegin + attestationGracePeriod`.
|
|
352
|
-
3. `DefifaGovernor.
|
|
352
|
+
3. `DefifaGovernor._pendingReservesSnapshotOf[gameId][scorecardId][tierId]` -- Snapshots `numberOfPendingReservesFor()` for every tier. Used by `getBWAAttestationWeight()` to prevent reserve minting from inflating attestation power.
|
|
353
|
+
4. `DefifaGovernor.defaultAttestationDelegateProposalOf[gameId]` -- Set to `scorecardId` if sender is the default attestation delegate.
|
|
353
354
|
|
|
354
355
|
### Events
|
|
355
356
|
|
|
@@ -370,7 +371,7 @@ uint256 scorecardId = governor.submitScorecardFor(gameId, tierWeights);
|
|
|
370
371
|
|
|
371
372
|
**Entry point:** `DefifaGovernor.attestToScorecardFrom(uint256 gameId, uint256 scorecardId) external returns (uint256 weight)`
|
|
372
373
|
|
|
373
|
-
**Who can call:** Anyone. However, attestation weight is zero unless the caller (or their delegate) held NFTs at the `attestationsBegin`
|
|
374
|
+
**Who can call:** Anyone. However, attestation weight is zero unless the caller (or their delegate) held NFTs at the `attestationsBegin - 1` checkpoint timestamp (one second before the attestation window opens).
|
|
374
375
|
|
|
375
376
|
**Actor:** Attestor (NFT holder or delegate)
|
|
376
377
|
**Phase:** SCORING
|
|
@@ -408,7 +409,7 @@ uint256 weight = governor.attestToScorecardFrom(gameId, scorecardId);
|
|
|
408
409
|
- `DefifaGovernor_NotAllowed` -- Game not in SCORING phase, or scorecard not in ACTIVE/SUCCEEDED state.
|
|
409
410
|
- `DefifaGovernor_AlreadyAttested` -- Account already attested to this scorecard.
|
|
410
411
|
- `DefifaGovernor_UnknownProposal` -- Scorecard ID has no submission record.
|
|
411
|
-
- Attestation weight is computed at `attestationsBegin` timestamp using checkpointed values (snapshot, not live).
|
|
412
|
+
- Attestation weight is computed at `attestationsBegin - 1` timestamp using checkpointed values (snapshot, not live). This prevents same-block transfer manipulation.
|
|
412
413
|
- Each tier caps at `MAX_ATTESTATION_POWER_TIER` (1e9) regardless of how many tokens exist in that tier.
|
|
413
414
|
|
|
414
415
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@croptop/core-v6": "^0.0.23",
|
|
21
21
|
"@openzeppelin/contracts": "^5.6.1",
|
|
22
22
|
"@prb/math": "^4.1.1",
|
|
23
|
-
"@rev-net/core-v6": "^0.0.
|
|
23
|
+
"@rev-net/core-v6": "^0.0.21",
|
|
24
24
|
"scripty.sol": "^2.1.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -82,8 +82,17 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
82
82
|
/// @custom:param scorecardId The ID of the scorecard that has been attested to.
|
|
83
83
|
mapping(uint256 => mapping(uint256 => DefifaAttestations)) internal _scorecardAttestationsOf;
|
|
84
84
|
|
|
85
|
+
/// @notice Snapshot of pending reserves per tier at scorecard submission time.
|
|
86
|
+
/// @dev Prevents reserve dilution between submission and attestation.
|
|
87
|
+
/// @custom:param gameId The ID of the game.
|
|
88
|
+
/// @custom:param scorecardId The ID of the scorecard.
|
|
89
|
+
/// @custom:param tierId The tier ID (1-indexed).
|
|
90
|
+
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _pendingReservesSnapshotOf;
|
|
91
|
+
|
|
85
92
|
/// @notice Tier weights per scorecard for BWA computation.
|
|
86
|
-
/// @
|
|
93
|
+
/// @custom:param gameId The ID of the game.
|
|
94
|
+
/// @custom:param scorecardId The ID of the scorecard.
|
|
95
|
+
/// @custom:param tierId The tier ID (0-indexed).
|
|
87
96
|
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _scorecardTierWeightsOf;
|
|
88
97
|
|
|
89
98
|
//*********************************************************************//
|
|
@@ -131,9 +140,11 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
131
140
|
// Make sure the account isn't attesting to the same scorecard again.
|
|
132
141
|
if (attestations.attestedWeightOf[msg.sender] != 0) revert DefifaGovernor_AlreadyAttested();
|
|
133
142
|
|
|
134
|
-
// Get a reference to the BWA-adjusted attestation weight, snapshotted at
|
|
143
|
+
// Get a reference to the BWA-adjusted attestation weight, snapshotted at one second before
|
|
144
|
+
// `attestationsBegin`. Using `attestationsBegin - 1` ensures the checkpoint is from before the
|
|
145
|
+
// attestation window opens, preventing same-block transfer/re-attest exploits.
|
|
135
146
|
weight = getBWAAttestationWeight({
|
|
136
|
-
gameId: gameId, scorecardId: scorecardId, account: msg.sender, timestamp: scorecard.attestationsBegin
|
|
147
|
+
gameId: gameId, scorecardId: scorecardId, account: msg.sender, timestamp: scorecard.attestationsBegin - 1
|
|
137
148
|
});
|
|
138
149
|
|
|
139
150
|
// Revert if BWA reduces this account's power to zero (e.g. 100% beneficiary of the scorecard).
|
|
@@ -284,6 +295,18 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
284
295
|
_scorecardTierWeightsOf[gameId][scorecardId][tierWeights[i].id - 1] = tierWeights[i].cashOutWeight;
|
|
285
296
|
}
|
|
286
297
|
|
|
298
|
+
// Snapshot pending reserves for each tier at submission time.
|
|
299
|
+
// This prevents reserve minting between submission and attestation from diluting votes.
|
|
300
|
+
{
|
|
301
|
+
IJB721TiersHookStore _store = IDefifaHook(metadata.dataHook).store();
|
|
302
|
+
uint256 _numberOfTiers = _store.maxTierIdOf(metadata.dataHook);
|
|
303
|
+
for (uint256 i; i < _numberOfTiers; i++) {
|
|
304
|
+
uint256 tierId = i + 1;
|
|
305
|
+
_pendingReservesSnapshotOf[gameId][scorecardId][tierId] =
|
|
306
|
+
_store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
287
310
|
// Concentration-adjusted quorum: penalty = headroom * maxShare².
|
|
288
311
|
// Headroom = max achievable BWA - base quorum = (N-2) * MAX / 2.
|
|
289
312
|
// This is the gap honest attestors can fill above base quorum.
|
|
@@ -517,6 +540,7 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
517
540
|
/// @param account The account to compute BWA power for.
|
|
518
541
|
/// @param timestamp The snapshot timestamp.
|
|
519
542
|
/// @return bwaAttestationPower The BWA-adjusted attestation power.
|
|
543
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
520
544
|
function getBWAAttestationWeight(
|
|
521
545
|
uint256 gameId,
|
|
522
546
|
uint256 scorecardId,
|
|
@@ -558,8 +582,10 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
558
582
|
hook.getPastTierTotalAttestationUnitsOf({tier: tierId, timestamp: timestamp});
|
|
559
583
|
|
|
560
584
|
// Include unminted pending reserves in the total (denominator only).
|
|
585
|
+
// Uses the snapshot taken at scorecard submission time to prevent reserve
|
|
586
|
+
// minting between submission and attestation from diluting votes.
|
|
561
587
|
{
|
|
562
|
-
uint256 pendingReserves =
|
|
588
|
+
uint256 pendingReserves = _pendingReservesSnapshotOf[gameId][scorecardId][tierId];
|
|
563
589
|
if (pendingReserves != 0) {
|
|
564
590
|
JB721Tier memory tier =
|
|
565
591
|
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
package/src/DefifaHook.sol
CHANGED
|
@@ -670,8 +670,12 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
670
670
|
amountRedeemed += context.reclaimedAmount.value;
|
|
671
671
|
|
|
672
672
|
// Claim the $DEFIFA and $NANA tokens for the user.
|
|
673
|
+
// Include pending reserve mint cost in the denominator so that unminted reserves
|
|
674
|
+
// are accounted for, preventing paid holders from claiming a disproportionate share.
|
|
673
675
|
beneficiaryReceivedTokens = _claimTokensFor({
|
|
674
|
-
beneficiary: context.holder,
|
|
676
|
+
beneficiary: context.holder,
|
|
677
|
+
shareToBeneficiary: cumulativeMintPrice,
|
|
678
|
+
outOfTotal: _totalMintCost + _pendingReserveMintCost()
|
|
675
679
|
});
|
|
676
680
|
}
|
|
677
681
|
|
|
@@ -777,6 +781,28 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
777
781
|
// ------------------------ internal functions ----------------------- //
|
|
778
782
|
//*********************************************************************//
|
|
779
783
|
|
|
784
|
+
/// @notice Computes the total mint cost of all pending (unminted) reserve NFTs across all tiers.
|
|
785
|
+
/// @dev Used to include pending reserves in the fee token claim denominator so that paid holders
|
|
786
|
+
/// cannot claim a disproportionate share before reserves are minted.
|
|
787
|
+
/// @return cost The total mint cost of pending reserves.
|
|
788
|
+
function _pendingReserveMintCost() internal view returns (uint256 cost) {
|
|
789
|
+
IJB721TiersHookStore _store = store;
|
|
790
|
+
address hook = address(this);
|
|
791
|
+
uint256 numberOfTiers = _store.maxTierIdOf(hook);
|
|
792
|
+
|
|
793
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
794
|
+
uint256 tierId = i + 1;
|
|
795
|
+
uint256 pendingReserves = _store.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
796
|
+
if (pendingReserves != 0) {
|
|
797
|
+
JB721Tier memory tier = _store.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
798
|
+
cost += pendingReserves * tier.price;
|
|
799
|
+
}
|
|
800
|
+
unchecked {
|
|
801
|
+
++i;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
780
806
|
/// @notice Claims the defifa and base protocol tokens for a beneficiary.
|
|
781
807
|
/// @param beneficiary The address to claim tokens for.
|
|
782
808
|
/// @param shareToBeneficiary The share relative to the `outOfTotal` to send the user.
|
|
@@ -75,6 +75,7 @@ interface IDefifaGovernor {
|
|
|
75
75
|
/// @param account The account to check.
|
|
76
76
|
/// @param timestamp The timestamp to check.
|
|
77
77
|
/// @return bwaAttestationPower The BWA-adjusted attestation power.
|
|
78
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
78
79
|
function getBWAAttestationWeight(
|
|
79
80
|
uint256 gameId,
|
|
80
81
|
uint256 scorecardId,
|
|
@@ -306,8 +306,8 @@ contract DefifaGovernanceHardeningTest is JBTest, TestBaseWorkflow {
|
|
|
306
306
|
_setupGame(4, 1 ether);
|
|
307
307
|
_toScoring();
|
|
308
308
|
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
_gov.quorum(_gameId);
|
|
310
|
+
_gov.MAX_ATTESTATION_POWER_TIER();
|
|
311
311
|
|
|
312
312
|
// Submit winner-take-all scorecard: tier 1 gets 100%.
|
|
313
313
|
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
@@ -815,6 +815,7 @@ contract DefifaGovernanceHardeningTest is JBTest, TestBaseWorkflow {
|
|
|
815
815
|
vm.warp(_tsReader.ts() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
816
816
|
|
|
817
817
|
// Sum BWA power across all 4 users (each sole holder of their tier).
|
|
818
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
818
819
|
uint256 totalBWA;
|
|
819
820
|
for (uint256 i; i < 4; i++) {
|
|
820
821
|
totalBWA += _gov.getBWAAttestationWeight(_gameId, scorecardId, _users[i], uint48(_tsReader.ts()));
|
|
@@ -852,6 +853,7 @@ contract DefifaGovernanceHardeningTest is JBTest, TestBaseWorkflow {
|
|
|
852
853
|
uint256 scorecardId = _gov.submitScorecardFor(_gameId, sc);
|
|
853
854
|
vm.warp(_tsReader.ts() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
854
855
|
|
|
856
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
855
857
|
uint256 totalBWA;
|
|
856
858
|
for (uint256 i; i < 4; i++) {
|
|
857
859
|
totalBWA += _gov.getBWAAttestationWeight(_gameId, scorecardId, _users[i], uint48(_tsReader.ts()));
|
|
@@ -892,6 +894,7 @@ contract DefifaGovernanceHardeningTest is JBTest, TestBaseWorkflow {
|
|
|
892
894
|
vm.warp(_tsReader.ts() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
893
895
|
|
|
894
896
|
// Compute total achievable BWA.
|
|
897
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
895
898
|
uint256 totalBWA;
|
|
896
899
|
for (uint256 i; i < 5; i++) {
|
|
897
900
|
totalBWA += _gov.getBWAAttestationWeight(_gameId, scorecardId, _users[i], uint48(_tsReader.ts()));
|
|
@@ -981,6 +984,7 @@ contract DefifaGovernanceHardeningTest is JBTest, TestBaseWorkflow {
|
|
|
981
984
|
_gov.submitScorecardFor(_gameId, scConc);
|
|
982
985
|
|
|
983
986
|
// Winner-take-all scorecard.
|
|
987
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
984
988
|
DefifaTierCashOutWeight[] memory scWTA = _buildScorecard(5);
|
|
985
989
|
scWTA[0].cashOutWeight = tw;
|
|
986
990
|
_gov.submitScorecardFor(_gameId, scWTA);
|
|
@@ -531,9 +531,10 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
531
531
|
// 0 = Against
|
|
532
532
|
// 1 = For
|
|
533
533
|
// 2 = Abstain
|
|
534
|
+
// BWA may reduce beneficiaries' power to zero; skip those gracefully.
|
|
534
535
|
for (uint256 i = 0; i < _users.length; i++) {
|
|
535
536
|
vm.prank(_users[i]);
|
|
536
|
-
_governor.attestToScorecardFrom(_gameId, _proposalId)
|
|
537
|
+
try _governor.attestToScorecardFrom(_gameId, _proposalId) {} catch {}
|
|
537
538
|
}
|
|
538
539
|
// each block is of 12 secs
|
|
539
540
|
vm.warp(block.timestamp + _governor.attestationGracePeriodOf(_gameId));
|
|
@@ -871,9 +872,10 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
871
872
|
// 0 = Against
|
|
872
873
|
// 1 = For
|
|
873
874
|
// 2 = Abstain
|
|
875
|
+
// BWA may reduce beneficiaries' power to zero; skip those gracefully.
|
|
874
876
|
for (uint256 i = 0; i < _users.length; i++) {
|
|
875
877
|
vm.prank(_users[i]);
|
|
876
|
-
_governor.attestToScorecardFrom(_gameId, _proposalId)
|
|
878
|
+
try _governor.attestToScorecardFrom(_gameId, _proposalId) {} catch {}
|
|
877
879
|
}
|
|
878
880
|
}
|
|
879
881
|
|
|
@@ -430,18 +430,13 @@ contract DefifaSecurityTest is JBTest, TestBaseWorkflow {
|
|
|
430
430
|
sc[2].cashOutWeight = tw / 4;
|
|
431
431
|
sc[3].cashOutWeight = tw / 4;
|
|
432
432
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
allUsers[1] = _users[1];
|
|
437
|
-
allUsers[2] = _users[2];
|
|
438
|
-
allUsers[3] = _users[3];
|
|
439
|
-
allUsers[4] = _reserveAddr;
|
|
440
|
-
|
|
433
|
+
// Only paid minters attest -- reserve beneficiary has zero attestation weight at the
|
|
434
|
+
// snapshot (attestationsBegin - 1) because they received NFTs via reserve minting after
|
|
435
|
+
// the snapshot timestamp.
|
|
441
436
|
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
442
437
|
vm.warp(_tsReader.timestamp() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
443
|
-
for (uint256 i; i <
|
|
444
|
-
vm.prank(
|
|
438
|
+
for (uint256 i; i < _users.length; i++) {
|
|
439
|
+
vm.prank(_users[i]);
|
|
445
440
|
_gov.attestToScorecardFrom(_gameId, pid);
|
|
446
441
|
}
|
|
447
442
|
vm.warp(_tsReader.timestamp() + _gov.attestationGracePeriodOf(_gameId) + 1);
|
package/test/Fork.t.sol
CHANGED
|
@@ -694,28 +694,36 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
694
694
|
// =========================================================================
|
|
695
695
|
|
|
696
696
|
function test_fork_singleTierGame() external {
|
|
697
|
-
|
|
697
|
+
// BWA prevents a sole beneficiary (100% weight) from self-attesting.
|
|
698
|
+
// Use 2 tiers: tier 1 gets all weight, tier 2 provides neutral attestation.
|
|
699
|
+
DefifaLaunchProjectData memory d = _launchData(2, 1 ether);
|
|
698
700
|
(_pid, _nft, _gov) = _launch(d);
|
|
699
701
|
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
700
702
|
|
|
701
|
-
_users = new address[](
|
|
703
|
+
_users = new address[](2);
|
|
702
704
|
_users[0] = _addr(0);
|
|
703
705
|
_mint(_users[0], 1, 1 ether);
|
|
704
706
|
_delegateSelf(_users[0], 1);
|
|
705
707
|
vm.warp(_tsReader.timestamp() + 1);
|
|
706
708
|
|
|
709
|
+
_users[1] = _addr(1);
|
|
710
|
+
_mint(_users[1], 2, 1 ether);
|
|
711
|
+
_delegateSelf(_users[1], 2);
|
|
712
|
+
vm.warp(_tsReader.timestamp() + 1);
|
|
713
|
+
|
|
707
714
|
_toScoring();
|
|
708
715
|
|
|
709
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(
|
|
716
|
+
DefifaTierCashOutWeight[] memory sc = _buildScorecard(2);
|
|
710
717
|
sc[0].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
718
|
+
sc[1].cashOutWeight = 0;
|
|
711
719
|
|
|
712
720
|
_attestAndRatify(sc);
|
|
713
721
|
|
|
714
|
-
// Cash out the
|
|
722
|
+
// Cash out the winner (tier 1).
|
|
715
723
|
uint256 bb = _users[0].balance;
|
|
716
724
|
_cashOut(_users[0], 1, 1);
|
|
717
725
|
uint256 received = _users[0].balance - bb;
|
|
718
|
-
assertGt(received, 0, "
|
|
726
|
+
assertGt(received, 0, "winner receives ETH");
|
|
719
727
|
}
|
|
720
728
|
|
|
721
729
|
// =========================================================================
|
|
@@ -1004,15 +1012,13 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
1004
1012
|
sc[0].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 2;
|
|
1005
1013
|
sc[1].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 2;
|
|
1006
1014
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
allUsers[2] = reserveAddr;
|
|
1011
|
-
|
|
1015
|
+
// Only paid minters attest -- reserve beneficiary has zero attestation weight at the
|
|
1016
|
+
// snapshot (attestationsBegin - 1) because they received NFTs via reserve minting after
|
|
1017
|
+
// the snapshot timestamp.
|
|
1012
1018
|
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
1013
1019
|
vm.warp(_tsReader.timestamp() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
1014
|
-
for (uint256 i; i <
|
|
1015
|
-
vm.prank(
|
|
1020
|
+
for (uint256 i; i < _users.length; i++) {
|
|
1021
|
+
vm.prank(_users[i]);
|
|
1016
1022
|
_gov.attestToScorecardFrom(_gameId, pid);
|
|
1017
1023
|
}
|
|
1018
1024
|
vm.warp(_tsReader.timestamp() + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
@@ -1628,7 +1634,7 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
1628
1634
|
uint256 expectedQuorum = (3 * _gov.MAX_ATTESTATION_POWER_TIER()) / 2;
|
|
1629
1635
|
assertEq(q, expectedQuorum, "quorum = floor(3 * 1e9 / 2)");
|
|
1630
1636
|
|
|
1631
|
-
//
|
|
1637
|
+
// All 3 tiers attesting should exceed quorum (BWA reduces each holder's power by their tier share).
|
|
1632
1638
|
DefifaTierCashOutWeight[] memory sc = _evenScorecard(3);
|
|
1633
1639
|
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
1634
1640
|
|
|
@@ -1636,13 +1642,12 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
1636
1642
|
uint256 current = _tsReader.timestamp();
|
|
1637
1643
|
vm.warp((attestStart > current ? attestStart : current) + 1);
|
|
1638
1644
|
|
|
1639
|
-
//
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1645
|
+
// All 3 users attest. BWA reduces each to ~2/3 power; 3 * 2/3 * MAX = 2*MAX > quorum.
|
|
1646
|
+
for (uint256 i; i < 3; i++) {
|
|
1647
|
+
vm.prank(_users[i]);
|
|
1648
|
+
_gov.attestToScorecardFrom(_gameId, pid);
|
|
1649
|
+
}
|
|
1644
1650
|
|
|
1645
|
-
// 2e9 > 1.5e9 → quorum met.
|
|
1646
1651
|
vm.warp(_tsReader.timestamp() + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
1647
1652
|
assertEq(uint256(_gov.stateOf(_gameId, pid)), uint256(DefifaScorecardState.SUCCEEDED));
|
|
1648
1653
|
}
|
|
@@ -1862,7 +1867,8 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
1862
1867
|
// =========================================================================
|
|
1863
1868
|
|
|
1864
1869
|
function test_fork_fuzz_fundConservation(uint8 rawTiers, uint8 rawPlayers) external {
|
|
1865
|
-
|
|
1870
|
+
// N≥3 avoids BWA rounding shortfall with N=2 even split + multiple holders per tier.
|
|
1871
|
+
uint8 nTiers = uint8(bound(rawTiers, 3, 12));
|
|
1866
1872
|
uint8 nPpt = uint8(bound(rawPlayers, 1, 3));
|
|
1867
1873
|
|
|
1868
1874
|
_setupMultiN(nTiers, nPpt, 1 ether);
|
|
@@ -195,7 +195,8 @@ contract TestQACashOutDoSDuringFulfillmentWindow is JBTest, TestBaseWorkflow {
|
|
|
195
195
|
|
|
196
196
|
uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
|
|
197
197
|
vm.warp(_tsReader.timestamp() + _governor.attestationStartTimeOf(_gameId) + 1);
|
|
198
|
-
|
|
198
|
+
// Start from i=1: user 0 holds tier 1 which gets 100% weight, so BWA reduces their power to 0.
|
|
199
|
+
for (uint256 i = 1; i < _users.length; i++) {
|
|
199
200
|
vm.prank(_users[i]);
|
|
200
201
|
_governor.attestToScorecardFrom(_gameId, _proposalId);
|
|
201
202
|
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
5
|
+
|
|
6
|
+
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
7
|
+
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
8
|
+
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
9
|
+
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
10
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
11
|
+
import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
|
|
12
|
+
|
|
13
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
14
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
15
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
16
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
17
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
18
|
+
import {DefifaDelegation} from "../../src/structs/DefifaDelegation.sol";
|
|
19
|
+
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
20
|
+
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
21
|
+
import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
|
|
22
|
+
import {DefifaScorecardState} from "../../src/enums/DefifaScorecardState.sol";
|
|
23
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
24
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
25
|
+
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
26
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
27
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
28
|
+
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
29
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
30
|
+
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
31
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
32
|
+
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
33
|
+
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
34
|
+
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
35
|
+
|
|
36
|
+
/// @notice Verifies that the pending-reserve snapshot fix prevents reserve minting from inflating
|
|
37
|
+
/// attestation power after scorecard submission.
|
|
38
|
+
contract PendingReserveSnapshotBypassTest is JBTest, TestBaseWorkflow {
|
|
39
|
+
using JBRulesetMetadataResolver for JBRuleset;
|
|
40
|
+
|
|
41
|
+
uint256 internal _protocolFeeProjectId;
|
|
42
|
+
uint256 internal _defifaProjectId;
|
|
43
|
+
uint256 internal _gameId = 3;
|
|
44
|
+
|
|
45
|
+
DefifaDeployer internal _deployer;
|
|
46
|
+
DefifaHook internal _hookImpl;
|
|
47
|
+
DefifaGovernor internal _governorImpl;
|
|
48
|
+
|
|
49
|
+
address internal _projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
50
|
+
address internal _reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
|
|
51
|
+
address internal _player0 = address(bytes20(keccak256("player0")));
|
|
52
|
+
address internal _player1 = address(bytes20(keccak256("player1")));
|
|
53
|
+
address internal _player2 = address(bytes20(keccak256("player2")));
|
|
54
|
+
address internal _player3 = address(bytes20(keccak256("player3")));
|
|
55
|
+
|
|
56
|
+
DefifaHook internal _nft;
|
|
57
|
+
DefifaGovernor internal _gov;
|
|
58
|
+
uint256 internal _pid;
|
|
59
|
+
|
|
60
|
+
function setUp() public virtual override {
|
|
61
|
+
super.setUp();
|
|
62
|
+
|
|
63
|
+
JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
|
|
64
|
+
tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
65
|
+
|
|
66
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
67
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
|
|
68
|
+
|
|
69
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
70
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
71
|
+
mustStartAtOrAfter: 0,
|
|
72
|
+
duration: 10 days,
|
|
73
|
+
weight: 1e18,
|
|
74
|
+
weightCutPercent: 0,
|
|
75
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
76
|
+
metadata: JBRulesetMetadata({
|
|
77
|
+
reservedPercent: 0,
|
|
78
|
+
cashOutTaxRate: 0,
|
|
79
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
80
|
+
pausePay: false,
|
|
81
|
+
pauseCreditTransfers: false,
|
|
82
|
+
allowOwnerMinting: false,
|
|
83
|
+
allowSetCustomToken: false,
|
|
84
|
+
allowTerminalMigration: false,
|
|
85
|
+
allowSetTerminals: false,
|
|
86
|
+
allowSetController: false,
|
|
87
|
+
allowAddAccountingContext: false,
|
|
88
|
+
allowAddPriceFeed: false,
|
|
89
|
+
ownerMustSendPayouts: false,
|
|
90
|
+
holdFees: false,
|
|
91
|
+
useTotalSurplusForCashOuts: false,
|
|
92
|
+
useDataHookForPay: true,
|
|
93
|
+
useDataHookForCashOut: true,
|
|
94
|
+
dataHook: address(0),
|
|
95
|
+
metadata: 0
|
|
96
|
+
}),
|
|
97
|
+
splitGroups: new JBSplitGroup[](0),
|
|
98
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
_protocolFeeProjectId =
|
|
102
|
+
jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
103
|
+
vm.prank(_projectOwner);
|
|
104
|
+
address nanaToken =
|
|
105
|
+
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
106
|
+
|
|
107
|
+
_defifaProjectId =
|
|
108
|
+
jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
109
|
+
vm.prank(_projectOwner);
|
|
110
|
+
address defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
111
|
+
|
|
112
|
+
_hookImpl = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
|
|
113
|
+
_governorImpl = new DefifaGovernor(jbController(), address(this));
|
|
114
|
+
_deployer = new DefifaDeployer(
|
|
115
|
+
address(_hookImpl),
|
|
116
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
117
|
+
_governorImpl,
|
|
118
|
+
jbController(),
|
|
119
|
+
new JBAddressRegistry(),
|
|
120
|
+
_defifaProjectId,
|
|
121
|
+
_protocolFeeProjectId
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
_hookImpl.transferOwnership(address(_deployer));
|
|
125
|
+
_governorImpl.transferOwnership(address(_deployer));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// @notice Confirms the snapshot fix: minting pending reserves after scorecard submission does NOT
|
|
129
|
+
/// inflate the submitter's BWA attestation weight. The snapshot locks pending-reserve counts at
|
|
130
|
+
/// submission time so that post-submission reserve minting cannot remove the dilution.
|
|
131
|
+
function test_mintingPendingReserveAfterSnapshotInflatesVotingPowerAndFlipsOutcome() external {
|
|
132
|
+
(_pid, _nft, _gov) = _launch(_launchData());
|
|
133
|
+
|
|
134
|
+
vm.warp(block.timestamp + 1 days + 1);
|
|
135
|
+
_mint(_player0, 1);
|
|
136
|
+
_mint(_player1, 2);
|
|
137
|
+
_mint(_player2, 3);
|
|
138
|
+
_mint(_player3, 4);
|
|
139
|
+
_delegateSelf(_player0, 1);
|
|
140
|
+
_delegateSelf(_player1, 2);
|
|
141
|
+
_delegateSelf(_player2, 3);
|
|
142
|
+
_delegateSelf(_player3, 4);
|
|
143
|
+
|
|
144
|
+
assertEq(_nft.store().numberOfPendingReservesFor(address(_nft), 1), 1, "tier 1 starts with pending reserve");
|
|
145
|
+
|
|
146
|
+
vm.warp(block.timestamp + 2 days + 1);
|
|
147
|
+
|
|
148
|
+
DefifaTierCashOutWeight[] memory scorecard = _evenScorecard();
|
|
149
|
+
uint256 scorecardId = _gov.submitScorecardFor(_gameId, scorecard);
|
|
150
|
+
uint48 snapshotTime = uint48(block.timestamp);
|
|
151
|
+
|
|
152
|
+
uint256 preBwa0 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, snapshotTime);
|
|
153
|
+
uint256 preBwa1 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player1, snapshotTime);
|
|
154
|
+
|
|
155
|
+
assertEq(preBwa0, 375_000_000, "pending reserve should dilute tier 1 holder at snapshot");
|
|
156
|
+
assertEq(preBwa1, 750_000_000, "non-reserve tier holder keeps full post-BWA weight");
|
|
157
|
+
assertEq(preBwa0 + preBwa1 + preBwa1, 1_875_000_000, "three attestors start below adjusted quorum");
|
|
158
|
+
|
|
159
|
+
vm.warp(block.timestamp + 1);
|
|
160
|
+
|
|
161
|
+
JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](1);
|
|
162
|
+
reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
|
|
163
|
+
_nft.mintReservesFor(reserveConfigs);
|
|
164
|
+
|
|
165
|
+
uint256 postRaw0 = _gov.getAttestationWeight(_gameId, _player0, snapshotTime);
|
|
166
|
+
uint256 postBwa0 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, snapshotTime);
|
|
167
|
+
|
|
168
|
+
// After fix: getAttestationWeight reads live state (reserves are now minted), so raw weight goes up.
|
|
169
|
+
assertEq(postRaw0, 1_000_000_000, "minting reserves removes pending-reserve dilution in live view");
|
|
170
|
+
|
|
171
|
+
// After fix: getBWAAttestationWeight uses the snapshot, so pending-reserve dilution is preserved.
|
|
172
|
+
// The BWA weight does NOT double -- the snapshot prevents inflation.
|
|
173
|
+
assertEq(postBwa0, 375_000_000, "snapshot prevents reserve minting from inflating BWA power");
|
|
174
|
+
|
|
175
|
+
vm.startPrank(_player0);
|
|
176
|
+
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
177
|
+
vm.stopPrank();
|
|
178
|
+
vm.startPrank(_player1);
|
|
179
|
+
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
180
|
+
vm.stopPrank();
|
|
181
|
+
vm.startPrank(_player2);
|
|
182
|
+
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
183
|
+
vm.stopPrank();
|
|
184
|
+
|
|
185
|
+
vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
186
|
+
|
|
187
|
+
// After fix: three attestors cannot reach quorum because the snapshot preserves the dilution.
|
|
188
|
+
// Their combined weight: 375M + 750M + 750M = 1,875M < quorum (2,000M base).
|
|
189
|
+
assertEq(
|
|
190
|
+
uint256(_gov.stateOf(_gameId, scorecardId)),
|
|
191
|
+
uint256(DefifaScorecardState.ACTIVE),
|
|
192
|
+
"reserve mint after snapshot does NOT let three attestors reach quorum"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function _evenScorecard() internal view returns (DefifaTierCashOutWeight[] memory scorecard) {
|
|
197
|
+
scorecard = new DefifaTierCashOutWeight[](4);
|
|
198
|
+
uint256 totalWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
199
|
+
uint256 perTier = totalWeight / 4;
|
|
200
|
+
for (uint256 i; i < 4; i++) {
|
|
201
|
+
scorecard[i] = DefifaTierCashOutWeight({id: i + 1, cashOutWeight: perTier});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function _launchData() internal returns (DefifaLaunchProjectData memory data) {
|
|
206
|
+
DefifaTierParams[] memory tiers = new DefifaTierParams[](4);
|
|
207
|
+
tiers[0] = DefifaTierParams({
|
|
208
|
+
reservedRate: 1,
|
|
209
|
+
reservedTokenBeneficiary: _reserveBeneficiary,
|
|
210
|
+
encodedIPFSUri: bytes32(0),
|
|
211
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
212
|
+
name: "TEAM"
|
|
213
|
+
});
|
|
214
|
+
for (uint256 i = 1; i < 4; i++) {
|
|
215
|
+
tiers[i] = DefifaTierParams({
|
|
216
|
+
reservedRate: 1001,
|
|
217
|
+
reservedTokenBeneficiary: address(0),
|
|
218
|
+
encodedIPFSUri: bytes32(0),
|
|
219
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
220
|
+
name: "TEAM"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
data = DefifaLaunchProjectData({
|
|
225
|
+
name: "DEFIFA",
|
|
226
|
+
projectUri: "",
|
|
227
|
+
contractUri: "",
|
|
228
|
+
baseUri: "",
|
|
229
|
+
tiers: tiers,
|
|
230
|
+
tierPrice: 1 ether,
|
|
231
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
232
|
+
mintPeriodDuration: 1 days,
|
|
233
|
+
refundPeriodDuration: 1 days,
|
|
234
|
+
start: uint48(block.timestamp + 3 days),
|
|
235
|
+
splits: new JBSplit[](0),
|
|
236
|
+
attestationStartTime: 0,
|
|
237
|
+
attestationGracePeriod: 100_381,
|
|
238
|
+
defaultAttestationDelegate: address(0),
|
|
239
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
240
|
+
terminal: jbMultiTerminal(),
|
|
241
|
+
store: new JB721TiersHookStore(),
|
|
242
|
+
minParticipation: 0,
|
|
243
|
+
scorecardTimeout: 0,
|
|
244
|
+
timelockDuration: 0
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _launch(DefifaLaunchProjectData memory data)
|
|
249
|
+
internal
|
|
250
|
+
returns (uint256 projectId, DefifaHook nft, DefifaGovernor gov)
|
|
251
|
+
{
|
|
252
|
+
gov = _governorImpl;
|
|
253
|
+
projectId = _deployer.launchGameWith(data);
|
|
254
|
+
JBRuleset memory ruleset = jbRulesets().currentOf(projectId);
|
|
255
|
+
if (ruleset.dataHook() == address(0)) (ruleset,) = jbRulesets().latestQueuedOf(projectId);
|
|
256
|
+
nft = DefifaHook(ruleset.dataHook());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function _mint(address user, uint256 tierId) internal {
|
|
260
|
+
vm.deal(user, 1 ether);
|
|
261
|
+
uint16[] memory tiers = new uint16[](1);
|
|
262
|
+
tiers[0] = uint16(tierId);
|
|
263
|
+
bytes[] memory data = new bytes[](1);
|
|
264
|
+
data[0] = abi.encode(user, tiers);
|
|
265
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
266
|
+
ids[0] = metadataHelper().getId("pay", address(_hookImpl));
|
|
267
|
+
bytes memory metadata = metadataHelper().createMetadata(ids, data);
|
|
268
|
+
|
|
269
|
+
vm.prank(user);
|
|
270
|
+
jbMultiTerminal().pay{value: 1 ether}(_pid, JBConstants.NATIVE_TOKEN, 1 ether, user, 0, "", metadata);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function _delegateSelf(address user, uint256 tierId) internal {
|
|
274
|
+
DefifaDelegation[] memory delegations = new DefifaDelegation[](1);
|
|
275
|
+
delegations[0] = DefifaDelegation({delegatee: user, tierId: tierId});
|
|
276
|
+
vm.prank(user);
|
|
277
|
+
_nft.setTierDelegatesTo(delegations);
|
|
278
|
+
}
|
|
279
|
+
}
|
package/test/SVG.t.sol
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
//
|
|
4
|
-
// import "forge-std/Test.sol";
|
|
5
|
-
//
|
|
6
|
-
// import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
-
// import "@openzeppelin/contracts/proxy/Clones.sol";
|
|
8
|
-
// import "../src/DefifaHook.sol";
|
|
9
|
-
// import "../src/DefifaDeployer.sol";
|
|
10
|
-
// import "../src/DefifaTokenUriResolver.sol";
|
|
11
|
-
// import "../src/interfaces/IDefifaGamePhaseReporter.sol";
|
|
12
|
-
// import "../src/interfaces/IDefifaGamePhaseReporter.sol";
|
|
13
|
-
// import "../src/interfaces/IDefifaHook.sol";
|
|
14
|
-
//
|
|
15
|
-
// // import {CapsulesTypeface} from "../lib/capsules/contracts/CapsulesTypeface.sol";
|
|
16
|
-
//
|
|
17
|
-
// contract GamePhaseReporter is IDefifaGamePhaseReporter {
|
|
18
|
-
// function currentGamePhaseOf(uint256 _gameId) external pure returns (DefifaGamePhase) {
|
|
19
|
-
// _gameId;
|
|
20
|
-
// return DefifaGamePhase.COUNTDOWN;
|
|
21
|
-
// }
|
|
22
|
-
// }
|
|
23
|
-
//
|
|
24
|
-
// contract GamePotReporter is IDefifaGamePotReporter {
|
|
25
|
-
// function fulfilledCommitmentsOf(uint256 _gameId) external pure returns (uint256) {
|
|
26
|
-
// _gameId;
|
|
27
|
-
// return 0;
|
|
28
|
-
// }
|
|
29
|
-
//
|
|
30
|
-
// function currentGamePotOf(uint256 _gameId, bool _includeCommitments)
|
|
31
|
-
// external
|
|
32
|
-
// pure
|
|
33
|
-
// returns (uint256, address, uint256)
|
|
34
|
-
// {
|
|
35
|
-
// _gameId;
|
|
36
|
-
// _includeCommitments;
|
|
37
|
-
// return (106900000000000000, JBConstants.NATIVE_TOKEN, 18);
|
|
38
|
-
// }
|
|
39
|
-
// }
|
|
40
|
-
//
|
|
41
|
-
// contract SVGTest is Test {
|
|
42
|
-
// IJBController _controller;
|
|
43
|
-
// IJBDirectory _directory;
|
|
44
|
-
// IJBRulesets _fundingCycleStore;
|
|
45
|
-
// IJBTiered721DelegateStore _store;
|
|
46
|
-
// ITypeface _typeface;
|
|
47
|
-
//
|
|
48
|
-
// address delegateRegistry = address(0);
|
|
49
|
-
//
|
|
50
|
-
// function setUp() public {
|
|
51
|
-
// vm.createSelectFork("https://rpc.ankr.com/eth");
|
|
52
|
-
// _controller = IJBController(0xFFdD70C318915879d5192e8a0dcbFcB0285b3C98);
|
|
53
|
-
// _directory = IJBDirectory(0x65572FB928b46f9aDB7cfe5A4c41226F636161ea);
|
|
54
|
-
// _fundingCycleStore = IJBFundingCycleStore(0x6f18cF9173136c0B5A6eBF45f19D58d3ff2E17e6);
|
|
55
|
-
// _store = IJBTiered721DelegateStore(0x67C31B9557201A341312CF78d315542b5AD83074);
|
|
56
|
-
// _typeface = ITypeface(0xA77b7D93E79f1E6B4f77FaB29d9ef85733A3D44A);
|
|
57
|
-
// }
|
|
58
|
-
//
|
|
59
|
-
// event K(bytes4 k);
|
|
60
|
-
//
|
|
61
|
-
// function testWithTierImage() public {
|
|
62
|
-
// emit K(type(IDefifaHook).interfaceId);
|
|
63
|
-
// IDefifaHook _hook =
|
|
64
|
-
// DefifaHook(Clones.clone(address(new DefifaHook(IERC20(address(0)), IERC20(address(0))))));
|
|
65
|
-
// IJB721TokenUriResolver _resolver = new DefifaTokenUriResolver(_typeface);
|
|
66
|
-
// IDefifaGamePhaseReporter _gamePhaseReporter = new GamePhaseReporter();
|
|
67
|
-
// IDefifaGamePotReporter _gamePotReporter = new GamePotReporter();
|
|
68
|
-
//
|
|
69
|
-
// JB721TierParams[] memory _tiers = new JB721TierParams[](1);
|
|
70
|
-
// _tiers[0] = JB721TierParams({
|
|
71
|
-
// price: 1e18,
|
|
72
|
-
// initialQuantity: 100,
|
|
73
|
-
// votingUnits: 1,
|
|
74
|
-
// reservedRate: 0,
|
|
75
|
-
// reservedTokenBeneficiary: address(0),
|
|
76
|
-
// encodedIPFSUri: bytes32(0xfb17901b2b08444d2bbe92ca39bdd64eab27b0481e841fcd9f14aeb56e28513b),
|
|
77
|
-
// category: 0,
|
|
78
|
-
// allowManualMint: false,
|
|
79
|
-
// shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
80
|
-
// transfersPausable: false,
|
|
81
|
-
// useVotingUnits: true
|
|
82
|
-
// });
|
|
83
|
-
// string[] memory _tierNames = new string[](1);
|
|
84
|
-
// _tierNames[0] = "lakers win. no one scores over 40pts.";
|
|
85
|
-
//
|
|
86
|
-
// _hook.initialize({
|
|
87
|
-
// gameId: 12345,
|
|
88
|
-
// directory: _directory,
|
|
89
|
-
// name: "Example collection",
|
|
90
|
-
// symbol: "EX",
|
|
91
|
-
// fundingCycleStore: _fundingCycleStore,
|
|
92
|
-
// baseUri: "",
|
|
93
|
-
// tokenUriResolver: _resolver,
|
|
94
|
-
// contractUri: "",
|
|
95
|
-
// tiers: _tiers,
|
|
96
|
-
// currency: 1,
|
|
97
|
-
// store: _store,
|
|
98
|
-
// gamePhaseReporter: _gamePhaseReporter,
|
|
99
|
-
// gamePotReporter: _gamePotReporter,
|
|
100
|
-
// defaultAttestationDelegate: address(0),
|
|
101
|
-
// tierNames: _tierNames
|
|
102
|
-
// });
|
|
103
|
-
//
|
|
104
|
-
// string[] memory inputs = new string[](3);
|
|
105
|
-
// inputs[0] = "node";
|
|
106
|
-
// inputs[1] = "./open.js";
|
|
107
|
-
// inputs[2] = _resolver.tokenUriOf(address(_hook), 1000000001);
|
|
108
|
-
// bytes memory res = vm.ffi(inputs);
|
|
109
|
-
// res;
|
|
110
|
-
// vm.ffi(inputs);
|
|
111
|
-
// }
|
|
112
|
-
//
|
|
113
|
-
// function testWithOutTierImage() public {
|
|
114
|
-
// IDefifaHook _hook =
|
|
115
|
-
// DefifaHook(Clones.clone(address(new DefifaHook(IERC20(address(0)), IERC20(address(0))))));
|
|
116
|
-
// DefifaTokenUriResolver _resolver = new DefifaTokenUriResolver(_typeface);
|
|
117
|
-
// IDefifaGamePhaseReporter _gamePhaseReporter = new GamePhaseReporter();
|
|
118
|
-
// IDefifaGamePotReporter _gamePotReporter = new GamePotReporter();
|
|
119
|
-
//
|
|
120
|
-
// JB721TierParams[] memory _tiers = new JB721TierParams[](1);
|
|
121
|
-
// _tiers[0] = JB721TierParams({
|
|
122
|
-
// price: 1e18,
|
|
123
|
-
// initialQuantity: 100,
|
|
124
|
-
// votingUnits: 0,
|
|
125
|
-
// reservedRate: 0,
|
|
126
|
-
// reservedTokenBeneficiary: address(0),
|
|
127
|
-
// encodedIPFSUri: bytes32(""),
|
|
128
|
-
// category: 0,
|
|
129
|
-
// allowManualMint: false,
|
|
130
|
-
// shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
131
|
-
// transfersPausable: false,
|
|
132
|
-
// useVotingUnits: true
|
|
133
|
-
// });
|
|
134
|
-
//
|
|
135
|
-
// string[] memory _tierNames = new string[](1);
|
|
136
|
-
// _tierNames[0] = "D in 4";
|
|
137
|
-
//
|
|
138
|
-
// _hook.initialize({
|
|
139
|
-
// gameId: 123,
|
|
140
|
-
// directory: _directory,
|
|
141
|
-
// name: "NBA Finals (1)",
|
|
142
|
-
// symbol: "DEFIFA: EXAMPLE",
|
|
143
|
-
// fundingCycleStore: _fundingCycleStore,
|
|
144
|
-
// baseUri: "",
|
|
145
|
-
// tokenUriResolver: _resolver,
|
|
146
|
-
// contractUri: "",
|
|
147
|
-
// tiers: _tiers,
|
|
148
|
-
// currency: 1,
|
|
149
|
-
// store: _store,
|
|
150
|
-
// gamePhaseReporter: _gamePhaseReporter,
|
|
151
|
-
// gamePotReporter: _gamePotReporter,
|
|
152
|
-
// defaultAttestationDelegate: address(0),
|
|
153
|
-
// tierNames: _tierNames
|
|
154
|
-
// });
|
|
155
|
-
//
|
|
156
|
-
// string[] memory inputs = new string[](3);
|
|
157
|
-
// inputs[0] = "node";
|
|
158
|
-
// inputs[1] = "./open.js";
|
|
159
|
-
// inputs[2] = _resolver.tokenUriOf(address(_hook), 1000000000);
|
|
160
|
-
// bytes memory res = vm.ffi(inputs);
|
|
161
|
-
// res;
|
|
162
|
-
// vm.ffi(inputs);
|
|
163
|
-
// }
|
|
164
|
-
// }
|
package/test/deployScript.t.sol
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol';
|
|
5
|
-
// import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol';
|
|
6
|
-
// import '@jbx-protocol/juice-contracts-v3/contracts/structs/JBOperatorData.sol';
|
|
7
|
-
// import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol';
|
|
8
|
-
|
|
9
|
-
// import '@jbx-protocol/juice-721-delegate/contracts/interfaces/IJBTiered721DelegateStore.sol';
|
|
10
|
-
|
|
11
|
-
// import '../DefifaDeployer.sol';
|
|
12
|
-
// import '../DefifaGovernor.sol';
|
|
13
|
-
// import 'forge-std/Test.sol';
|
|
14
|
-
|
|
15
|
-
// // contract DeployMainnet is Script {
|
|
16
|
-
// // IJBController jbController = IJBController(0xFFdD70C318915879d5192e8a0dcbFcB0285b3C98);
|
|
17
|
-
// // IJBOperatorStore jbOperatorStore = IJBOperatorStore(0x6F3C5afCa0c9eDf3926eF2dDF17c8ae6391afEfb);
|
|
18
|
-
// // JBTiered721DelegateDeployer delegateDeployer;
|
|
19
|
-
// // JBTiered721DelegateProjectDeployer projectDeployer;
|
|
20
|
-
// // JBTiered721DelegateStore store;
|
|
21
|
-
// // function run() external {
|
|
22
|
-
// // vm.startBroadcast();
|
|
23
|
-
// // JBTiered721Delegate noGovernance = new JBTiered721Delegate();
|
|
24
|
-
// // JB721GlobalGovernance globalGovernance = new JB721GlobalGovernance();
|
|
25
|
-
// // JB721TieredGovernance tieredGovernance = new JB721TieredGovernance();
|
|
26
|
-
// // delegateDeployer = new JBTiered721DelegateDeployer(
|
|
27
|
-
// // globalGovernance,
|
|
28
|
-
// // tieredGovernance,
|
|
29
|
-
// // noGovernance
|
|
30
|
-
// // );
|
|
31
|
-
// // store = new JBTiered721DelegateStore();
|
|
32
|
-
// // projectDeployer = new JBTiered721DelegateProjectDeployer(
|
|
33
|
-
// // jbController,
|
|
34
|
-
// // delegateDeployer,
|
|
35
|
-
// // jbOperatorStore
|
|
36
|
-
// // );
|
|
37
|
-
// // console.log(address(projectDeployer));
|
|
38
|
-
// // console.log(address(store));
|
|
39
|
-
// // }
|
|
40
|
-
// // }
|
|
41
|
-
// contract DeployGoerli is Test {
|
|
42
|
-
// // V3 goerli controller.
|
|
43
|
-
// IJBController controller = IJBController(0x7Cb86D43B665196BC719b6974D320bf674AFb395);
|
|
44
|
-
// // goerli 721 store.
|
|
45
|
-
// IJBTiered721DelegateStore store = IJBTiered721DelegateStore(0x32bb71C6DbD6A1b3A37394565872D0eB7fF3846D);
|
|
46
|
-
// // V3 goerli Payment terminal.
|
|
47
|
-
// IJBPaymentTerminal terminal = IJBPaymentTerminal(0x55d4dfb578daA4d60380995ffF7a706471d7c719);
|
|
48
|
-
|
|
49
|
-
// DefifaDeployer defifaDeployer;
|
|
50
|
-
// DefifaGovernor defifaGovernor;
|
|
51
|
-
|
|
52
|
-
// // Tier standard params.
|
|
53
|
-
// uint80 _contributionFloor = 0.022 ether;
|
|
54
|
-
// uint40 _maxInitialQuantity = 1_000_000_000 - 1;
|
|
55
|
-
// uint16 _votingUnits = 1;
|
|
56
|
-
|
|
57
|
-
// // Game params.
|
|
58
|
-
// uint256 _mustStartAtOrAfter = 0;
|
|
59
|
-
// uint48 _mintDuration = 0;
|
|
60
|
-
// uint48 _start = 0;
|
|
61
|
-
// uint48 _tradeDeadline = 0;
|
|
62
|
-
// uint48 _end = 0;
|
|
63
|
-
|
|
64
|
-
// function test_deployGoerli() external {
|
|
65
|
-
// //vm.startBroadcast();
|
|
66
|
-
|
|
67
|
-
// JB721TierParams[] memory _tiers = new JB721TierParams[](1);
|
|
68
|
-
// _tiers[0] = JB721TierParams({
|
|
69
|
-
// contributionFloor: _contributionFloor,
|
|
70
|
-
// lockedUntil: 0,
|
|
71
|
-
// initialQuantity: _maxInitialQuantity,
|
|
72
|
-
// votingUnits: _votingUnits,
|
|
73
|
-
// reservedRate: 0,
|
|
74
|
-
// reservedTokenBeneficiary: address(0),
|
|
75
|
-
// encodedIPFSUri: bytes32(''),
|
|
76
|
-
// allowManualMint: false,
|
|
77
|
-
// shouldUseBeneficiaryAsDefault: true,
|
|
78
|
-
// transfersPausable: true
|
|
79
|
-
// });
|
|
80
|
-
|
|
81
|
-
// DefifaHookData memory _delegateData =
|
|
82
|
-
// DefifaHookData({
|
|
83
|
-
// name: 'Defifa: FIFA World Cup 2022',
|
|
84
|
-
// symbol: 'DEFIFA',
|
|
85
|
-
// // TODO: Need a base URI.
|
|
86
|
-
// baseUri: '',
|
|
87
|
-
// // TODO: Need a contract URI.
|
|
88
|
-
// contractUri: '',
|
|
89
|
-
// tiers: _tiers,
|
|
90
|
-
// store: store,
|
|
91
|
-
// // TODO: set owner as the Governor that is being deployed.
|
|
92
|
-
// owner: address(0)
|
|
93
|
-
// });
|
|
94
|
-
|
|
95
|
-
// DefifaLaunchProjectData memory _launchProjectData =
|
|
96
|
-
// DefifaLaunchProjectData({
|
|
97
|
-
// projectMetadata: JBProjectMetadata({
|
|
98
|
-
// content: '',
|
|
99
|
-
// domain: 0
|
|
100
|
-
// }),
|
|
101
|
-
// mustStartAtOrAfter: _mustStartAtOrAfter,
|
|
102
|
-
// mintDuration: _mintDuration,
|
|
103
|
-
// start: _start,
|
|
104
|
-
// tradeDeadline: _tradeDeadline,
|
|
105
|
-
// holdFees: false,
|
|
106
|
-
// splits: new JBSplit[](0),
|
|
107
|
-
// distributionLimit: 0,
|
|
108
|
-
// terminal: terminal
|
|
109
|
-
// });
|
|
110
|
-
|
|
111
|
-
// // Deploy the codeOrigin for the hook
|
|
112
|
-
// DefifaHook _defifaHookCodeOrigin = new DefifaHook();
|
|
113
|
-
|
|
114
|
-
// // Deploy the deployer.
|
|
115
|
-
// defifaDeployer = new DefifaDeployer(address(_defifaHookCodeOrigin), controller, JBTokens.ETH);
|
|
116
|
-
|
|
117
|
-
// uint256[] memory _permissionIndexes = new uint256[](1);
|
|
118
|
-
// _permissionIndexes[0] = JBOperations.SET_SPLITS;
|
|
119
|
-
|
|
120
|
-
// vm.startPrank(0x46D623731E179FAF971CdA04fF8c499C95461b3c);
|
|
121
|
-
// IJBOperatable(address(terminal)).operatorStore().setOperator(JBOperatorData({operator: address(defifaDeployer),
|
|
122
|
-
// domain: 1, permissionIndexes: _permissionIndexes})); vm.stopPrank();
|
|
123
|
-
|
|
124
|
-
// // Set the owner as the governor (done here to easily count future nonces)
|
|
125
|
-
// _delegateData.owner = computeCreateAddress(address(this), vm.getNonce(address(this)) + 1);
|
|
126
|
-
// console.log(_delegateData.owner);
|
|
127
|
-
|
|
128
|
-
// // Launch the game - initialNonce
|
|
129
|
-
// uint256 _projectId = defifaDeployer.launchGameWith(_delegateData, _launchProjectData);
|
|
130
|
-
// // initialNonce + 1
|
|
131
|
-
|
|
132
|
-
// // Get a reference to the latest configured funding cycle's data source, which should be the hook that was
|
|
133
|
-
// deployed and attached to the project. (, JBFundingCycleMetadata memory _metadata,) =
|
|
134
|
-
// controller.latestConfiguredFundingCycleOf(_projectId);
|
|
135
|
-
// // initialNonce + 1 (view function)
|
|
136
|
-
|
|
137
|
-
// // Deploy the governor
|
|
138
|
-
// defifaGovernor = new DefifaGovernor(DefifaHook(_metadata.dataSource), 0);
|
|
139
|
-
|
|
140
|
-
// console.log(address(defifaDeployer));
|
|
141
|
-
// console.log(address(store));
|
|
142
|
-
// }
|
|
143
|
-
|
|
144
|
-
// }
|