@ballkidz/defifa 0.0.25 → 0.0.27
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/AUDIT_INSTRUCTIONS.md +6 -2
- package/README.md +11 -2
- package/RISKS.md +3 -1
- package/STYLE_GUIDE.md +14 -11
- package/package.json +31 -14
- package/script/Deploy.s.sol +4 -1
- package/src/DefifaDeployer.sol +79 -47
- package/src/DefifaGovernor.sol +57 -12
- package/src/DefifaHook.sol +83 -26
- package/src/DefifaProjectOwner.sol +4 -3
- package/src/DefifaTokenUriResolver.sol +113 -20
- package/src/enums/DefifaGamePhase.sol +6 -0
- package/src/enums/DefifaScorecardState.sol +4 -0
- package/src/interfaces/IDefifaDeployer.sol +5 -0
- package/src/interfaces/IDefifaGamePhaseReporter.sol +4 -0
- package/src/interfaces/IDefifaGamePotReporter.sol +10 -0
- package/src/interfaces/IDefifaGovernor.sol +4 -0
- package/src/interfaces/IDefifaHook.sol +5 -0
- package/src/interfaces/IDefifaTokenUriResolver.sol +3 -0
- package/src/libraries/DefifaFontImporter.sol +1 -1
- package/src/libraries/DefifaHookLib.sol +9 -10
- package/src/structs/DefifaAttestations.sol +3 -2
- package/src/structs/DefifaDelegation.sol +1 -0
- package/src/structs/DefifaLaunchProjectData.sol +2 -3
- package/src/structs/DefifaOpsData.sol +1 -0
- package/src/structs/DefifaScorecard.sol +2 -0
- package/src/structs/DefifaTierCashOutWeight.sol +3 -1
- package/src/structs/DefifaTierParams.sol +1 -0
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +0 -997
- package/foundry.lock +0 -17
- package/references/operations.md +0 -32
- package/references/runtime.md +0 -43
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -521
- package/test/BWAFunctionComparison.t.sol +0 -1320
- package/test/DefifaAdversarialQuorum.t.sol +0 -617
- package/test/DefifaAuditLowGuards.t.sol +0 -308
- package/test/DefifaFeeAccounting.t.sol +0 -581
- package/test/DefifaGovernanceHardening.t.sol +0 -1315
- package/test/DefifaGovernor.t.sol +0 -1378
- package/test/DefifaHookRegressions.t.sol +0 -415
- package/test/DefifaMintCostInvariant.t.sol +0 -319
- package/test/DefifaNoContest.t.sol +0 -941
- package/test/DefifaSecurity.t.sol +0 -741
- package/test/DefifaUSDC.t.sol +0 -480
- package/test/Fork.t.sol +0 -2388
- package/test/TestAuditGaps.sol +0 -984
- package/test/TestQALastMile.t.sol +0 -514
- package/test/audit/AttestationDoubleCount.t.sol +0 -218
- package/test/audit/CodexNemesisCurrencyMismatchBypass.t.sol +0 -112
- package/test/audit/CodexNemesisNoContestReserveDrain.t.sol +0 -238
- package/test/audit/CodexNemesisOneTierZeroTimeoutLockVerified.t.sol +0 -218
- package/test/audit/CodexNemesisSingleTierTimeoutLock.t.sol +0 -237
- package/test/audit/CodexRegistryMismatch.t.sol +0 -191
- package/test/audit/CodexTierCapMismatch.t.sol +0 -171
- package/test/audit/CurrencyMismatchFix.t.sol +0 -265
- package/test/audit/FixPendingReserveDilution.t.sol +0 -366
- package/test/audit/H5TierCapValidation.t.sol +0 -184
- package/test/audit/PendingReserveDilution.t.sol +0 -298
- package/test/audit/PendingReserveQuorumGrief.t.sol +0 -355
- package/test/audit/PendingReserveSnapshotBypass.t.sol +0 -319
- package/test/regression/AttestationDelegateBeneficiary.t.sol +0 -271
- package/test/regression/FulfillmentBlocksRatification.t.sol +0 -279
- package/test/regression/GracePeriodBypass.t.sol +0 -302
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -90,5 +90,9 @@ The contracts split responsibility as follows:
|
|
|
90
90
|
## Verification
|
|
91
91
|
|
|
92
92
|
- `npm install`
|
|
93
|
-
- `forge
|
|
94
|
-
- `forge
|
|
93
|
+
- `forge fmt --check`
|
|
94
|
+
- `forge build --deny notes`
|
|
95
|
+
- `forge build --deny notes --sizes --skip "*/test/**" --skip "*/script/**"`
|
|
96
|
+
- `forge build --deny notes --build-info --skip "*/test/**" --skip "*/script/**"`
|
|
97
|
+
- `forge test --deny notes`
|
|
98
|
+
- `npm pack --dry-run --json`
|
package/README.md
CHANGED
|
@@ -85,8 +85,17 @@ npm install @ballkidz/defifa
|
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
npm install
|
|
88
|
-
forge build
|
|
89
|
-
forge test
|
|
88
|
+
forge build --deny notes
|
|
89
|
+
forge test --deny notes
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Useful checks before opening or updating a PR:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
forge fmt --check
|
|
96
|
+
forge build --deny notes --sizes --skip "*/test/**" --skip "*/script/**"
|
|
97
|
+
forge build --deny notes --build-info --skip "*/test/**" --skip "*/script/**"
|
|
98
|
+
npm pack --dry-run --json
|
|
90
99
|
```
|
|
91
100
|
|
|
92
101
|
Useful scripts:
|
package/RISKS.md
CHANGED
|
@@ -26,9 +26,11 @@ This file focuses on the game-theoretic, governance, and settlement risks in Def
|
|
|
26
26
|
|
|
27
27
|
## 2. Economic Risks
|
|
28
28
|
|
|
29
|
-
- **Scorecard manipulation via quorum.** Enough attestation power can redirect the whole pot.
|
|
29
|
+
- **Scorecard manipulation via quorum.** Enough attestation power can redirect the whole pot. Once a coalition reaches the BWA (best winning attestation) quorum threshold, they can lock in a scorecard that favors their tiers. The grace period is the primary defense window for other participants to revoke attestations.
|
|
30
|
+
- **BWA quorum lockout.** If a scorecard reaches quorum and the grace period elapses before opponents can revoke, the scorecard becomes ratifiable. In games with concentrated attestation power (few large holders), quorum may be reached quickly, leaving little time for the grace period defense to be effective.
|
|
30
31
|
- **Supply and pending-reserve drift.** Governance and settlement both depend on correct reserve-aware denominators.
|
|
31
32
|
- **Cash-out-weight truncation.** Integer division can lock small dust amounts.
|
|
33
|
+
- **Commitment fee rounding to zero.** When commitment amounts are small relative to the fee percent, `mulDiv(amount, feePercent, MAX_FEE)` can round down to zero, allowing tiny commitments to bypass fees entirely. This is economically insignificant for practical game sizes but means fee accounting is not perfectly tight at dust amounts.
|
|
32
34
|
- **Fee-token dilution from reserved mints.** Reserved mints can dilute fee-token shares even though no ETH was paid for them.
|
|
33
35
|
- **128-tier settlement ceiling.** Games that rely on more than 128 scored tiers can fail settlement.
|
|
34
36
|
|
package/STYLE_GUIDE.md
CHANGED
|
@@ -419,17 +419,17 @@ jobs:
|
|
|
419
419
|
submodules: recursive
|
|
420
420
|
- uses: actions/setup-node@v4
|
|
421
421
|
with:
|
|
422
|
-
node-version:
|
|
422
|
+
node-version: 25.9.0
|
|
423
423
|
- name: Install npm dependencies
|
|
424
|
-
run: npm install
|
|
424
|
+
run: npm install
|
|
425
425
|
- name: Install Foundry
|
|
426
426
|
uses: foundry-rs/foundry-toolchain@v1
|
|
427
427
|
- name: Run tests
|
|
428
|
-
run: forge test --fail-fast --summary --detailed --skip "*/script/**"
|
|
428
|
+
run: forge test --deny notes --fail-fast --summary --detailed --skip "*/script/**"
|
|
429
429
|
env:
|
|
430
430
|
RPC_ETHEREUM_MAINNET: ${{ secrets.RPC_ETHEREUM_MAINNET }}
|
|
431
|
-
- name:
|
|
432
|
-
run: forge build --
|
|
431
|
+
- name: Build contracts
|
|
432
|
+
run: forge build --deny notes --skip "*/test/**" --skip "*/script/**"
|
|
433
433
|
```
|
|
434
434
|
|
|
435
435
|
**lint.yml:**
|
|
@@ -470,16 +470,19 @@ jobs:
|
|
|
470
470
|
submodules: recursive
|
|
471
471
|
- uses: actions/setup-node@v4
|
|
472
472
|
with:
|
|
473
|
-
node-version:
|
|
473
|
+
node-version: 25.9.0
|
|
474
474
|
- name: Install npm dependencies
|
|
475
475
|
run: npm install --omit=dev
|
|
476
476
|
- name: Install Foundry
|
|
477
477
|
uses: foundry-rs/foundry-toolchain@v1
|
|
478
|
+
- name: Prebuild contracts
|
|
479
|
+
run: forge build --deny notes --build-info --skip "*/test/**" --skip "*/script/**"
|
|
478
480
|
- name: Run slither
|
|
479
|
-
uses: crytic/slither-action@v0.
|
|
481
|
+
uses: crytic/slither-action@v0.4.1
|
|
480
482
|
with:
|
|
481
483
|
slither-config: slither-ci.config.json
|
|
482
484
|
fail-on: medium
|
|
485
|
+
ignore-compile: true
|
|
483
486
|
```
|
|
484
487
|
|
|
485
488
|
**slither-ci.config.json:**
|
|
@@ -508,14 +511,14 @@ jobs:
|
|
|
508
511
|
"version": "x.x.x",
|
|
509
512
|
"license": "MIT",
|
|
510
513
|
"repository": { "type": "git", "url": "git+https://github.com/Org/repo.git" },
|
|
511
|
-
"engines": { "node": "
|
|
514
|
+
"engines": { "node": "25.9.0" },
|
|
512
515
|
"scripts": {
|
|
513
516
|
"test": "forge test",
|
|
514
517
|
"coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary"
|
|
515
518
|
},
|
|
516
519
|
"dependencies": { ... },
|
|
517
520
|
"devDependencies": {
|
|
518
|
-
"@sphinx-labs/plugins": "
|
|
521
|
+
"@sphinx-labs/plugins": "0.33.3"
|
|
519
522
|
}
|
|
520
523
|
}
|
|
521
524
|
```
|
|
@@ -603,8 +606,8 @@ CI checks formatting via `forge fmt --check`.
|
|
|
603
606
|
- `forge-std` as a git submodule in `lib/`
|
|
604
607
|
- Sphinx plugins as a devDependency
|
|
605
608
|
- Cross-repo references use `file:../sibling-repo` in local development
|
|
606
|
-
- Published versions use semver ranges
|
|
609
|
+
- Published npm versions are pinned exactly; do not use semver ranges or `latest`
|
|
607
610
|
|
|
608
611
|
### Contract Size Checks
|
|
609
612
|
|
|
610
|
-
CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
|
|
613
|
+
CI runs `forge build --deny notes --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --deny notes --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
|
package/package.json
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
|
-
"node": "
|
|
6
|
+
"node": "25.9.0"
|
|
7
7
|
},
|
|
8
8
|
"bugs": {
|
|
9
|
-
"url": "https://github.com/BallKidz/defifa
|
|
9
|
+
"url": "https://github.com/BallKidz/defifa/issues"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/BallKidz/defifa
|
|
13
|
+
"url": "git+https://github.com/BallKidz/defifa.git"
|
|
14
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"script",
|
|
18
|
+
"lib/base64/base64.sol",
|
|
19
|
+
"lib/typeface/contracts/interfaces/ITypeface.sol",
|
|
20
|
+
"README.md",
|
|
21
|
+
"ARCHITECTURE.md",
|
|
22
|
+
"ADMINISTRATION.md",
|
|
23
|
+
"AUDIT_INSTRUCTIONS.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"CRYPTO_ECON.md",
|
|
26
|
+
"RISKS.md",
|
|
27
|
+
"SKILLS.md",
|
|
28
|
+
"STYLE_GUIDE.md",
|
|
29
|
+
"USER_JOURNEYS.md",
|
|
30
|
+
"foundry.toml",
|
|
31
|
+
"remappings.txt"
|
|
32
|
+
],
|
|
15
33
|
"dependencies": {
|
|
16
|
-
"@bananapus/721-hook-v6": "
|
|
17
|
-
"@bananapus/
|
|
18
|
-
"@bananapus/
|
|
19
|
-
"@
|
|
20
|
-
"@openzeppelin/contracts": "
|
|
21
|
-
"@prb/math": "
|
|
22
|
-
"
|
|
23
|
-
"scripty.sol": "^2.1.1"
|
|
34
|
+
"@bananapus/721-hook-v6": "0.0.43",
|
|
35
|
+
"@bananapus/address-registry-v6": "0.0.25",
|
|
36
|
+
"@bananapus/core-v6": "0.0.39",
|
|
37
|
+
"@bananapus/permission-ids-v6": "0.0.22",
|
|
38
|
+
"@openzeppelin/contracts": "5.6.1",
|
|
39
|
+
"@prb/math": "4.1.1",
|
|
40
|
+
"scripty.sol": "2.1.1"
|
|
24
41
|
},
|
|
25
42
|
"devDependencies": {
|
|
26
|
-
"@
|
|
27
|
-
"@sphinx-labs/plugins": "
|
|
43
|
+
"@sphinx-labs/contracts": "0.23.1",
|
|
44
|
+
"@sphinx-labs/plugins": "0.33.3"
|
|
28
45
|
},
|
|
29
46
|
"scripts": {
|
|
30
47
|
"test": "forge test",
|
package/script/Deploy.s.sol
CHANGED
|
@@ -4,6 +4,7 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
import {Script} from "forge-std/Script.sol";
|
|
5
5
|
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
6
6
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
7
8
|
import {DefifaHook} from "../src/DefifaHook.sol";
|
|
8
9
|
import {DefifaDeployer} from "../src/DefifaDeployer.sol";
|
|
9
10
|
import {DefifaGovernor} from "../src/DefifaGovernor.sol";
|
|
@@ -109,6 +110,7 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
109
110
|
});
|
|
110
111
|
DefifaTokenUriResolver tokenUriResolver = new DefifaTokenUriResolver{salt: _salt}(_typeface);
|
|
111
112
|
DefifaGovernor governor = new DefifaGovernor{salt: _salt}({controller: core.controller, owner: safeAddress()});
|
|
113
|
+
JB721TiersHookStore hookStore = new JB721TiersHookStore{salt: _salt}();
|
|
112
114
|
DefifaDeployer deployer = new DefifaDeployer{salt: _salt}({
|
|
113
115
|
_hookCodeOrigin: address(hook),
|
|
114
116
|
_tokenUriResolver: tokenUriResolver,
|
|
@@ -116,7 +118,8 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
116
118
|
_controller: core.controller,
|
|
117
119
|
_registry: registry.registry,
|
|
118
120
|
_defifaProjectId: _defifaProjectId,
|
|
119
|
-
_baseProtocolProjectId: _baseProtocolProjectId
|
|
121
|
+
_baseProtocolProjectId: _baseProtocolProjectId,
|
|
122
|
+
_hookStore: hookStore
|
|
120
123
|
});
|
|
121
124
|
|
|
122
125
|
governor.transferOwnership(address(deployer));
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -28,6 +28,8 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei
|
|
|
28
28
|
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|
29
29
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
30
30
|
|
|
31
|
+
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
32
|
+
|
|
31
33
|
import {DefifaHook} from "./DefifaHook.sol";
|
|
32
34
|
import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
|
|
33
35
|
import {IDefifaDeployer} from "./interfaces/IDefifaDeployer.sol";
|
|
@@ -39,7 +41,11 @@ import {DefifaLaunchProjectData} from "./structs/DefifaLaunchProjectData.sol";
|
|
|
39
41
|
import {DefifaOpsData} from "./structs/DefifaOpsData.sol";
|
|
40
42
|
import {DefifaTierParams} from "./structs/DefifaTierParams.sol";
|
|
41
43
|
|
|
42
|
-
/// @notice Deploys and manages Defifa games.
|
|
44
|
+
/// @notice Deploys and manages Defifa games — prediction-market-style contests built on Juicebox. Each game has
|
|
45
|
+
/// tiers representing outcomes (teams, players, events). Players mint tier NFTs during the mint phase, then after
|
|
46
|
+
/// the event concludes, a scorecard assigns cash-out weights to each tier. The treasury is distributed proportionally
|
|
47
|
+
/// to winning NFT holders. Games progress through phases: COUNTDOWN → MINT → REFUND → SCORING → COMPLETE (or
|
|
48
|
+
/// NO_CONTEST if minimum participation isn't met or scorecard ratification times out).
|
|
43
49
|
contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGamePotReporter, IERC721Receiver {
|
|
44
50
|
using Strings for uint256;
|
|
45
51
|
using SafeERC20 for IERC20;
|
|
@@ -102,6 +108,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
102
108
|
/// @notice The hooks registry.
|
|
103
109
|
IJBAddressRegistry public immutable REGISTRY;
|
|
104
110
|
|
|
111
|
+
/// @notice The 721 tiers hook store used by all games.
|
|
112
|
+
IJB721TiersHookStore public immutable HOOK_STORE;
|
|
113
|
+
|
|
105
114
|
/// @notice The divisor that describes the protocol fee that should be taken.
|
|
106
115
|
/// @dev This is equal to 100 divided by the fee percent (e.g. 40 = 2.5% fee).
|
|
107
116
|
uint256 public constant override BASE_PROTOCOL_FEE_DIVISOR = 40;
|
|
@@ -121,6 +130,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
121
130
|
/// @notice The total absolute split percent for each game (out of SPLITS_TOTAL_PERCENT).
|
|
122
131
|
mapping(uint256 => uint256) internal _commitmentPercentOf;
|
|
123
132
|
|
|
133
|
+
/// @notice Whether commitments have been fulfilled for a game.
|
|
134
|
+
mapping(uint256 => bool) public commitmentsFulfilledFor;
|
|
135
|
+
|
|
124
136
|
/// @notice Whether the no-contest refund ruleset has been triggered for a game.
|
|
125
137
|
/// @dev Once triggered, the game stays in NO_CONTEST and refunds are enabled.
|
|
126
138
|
mapping(uint256 => bool) public noContestTriggeredFor;
|
|
@@ -253,6 +265,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
253
265
|
|
|
254
266
|
// Check scorecard ratification timeout: if enough time has passed without a ratified scorecard, the game is
|
|
255
267
|
// NO_CONTEST.
|
|
268
|
+
// Game phase timing is timestamp-based by design.
|
|
269
|
+
// forge-lint: disable-next-line(block-timestamp)
|
|
256
270
|
if (ops.scorecardTimeout > 0 && block.timestamp > currentRuleset.start + ops.scorecardTimeout) {
|
|
257
271
|
return DefifaGamePhase.NO_CONTEST;
|
|
258
272
|
}
|
|
@@ -278,7 +292,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
278
292
|
IJBController _controller,
|
|
279
293
|
IJBAddressRegistry _registry,
|
|
280
294
|
uint256 _defifaProjectId,
|
|
281
|
-
uint256 _baseProtocolProjectId
|
|
295
|
+
uint256 _baseProtocolProjectId,
|
|
296
|
+
IJB721TiersHookStore _hookStore
|
|
282
297
|
) {
|
|
283
298
|
// slither-disable-next-line missing-zero-check
|
|
284
299
|
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
@@ -288,6 +303,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
288
303
|
REGISTRY = _registry;
|
|
289
304
|
DEFIFA_PROJECT_ID = _defifaProjectId;
|
|
290
305
|
BASE_PROTOCOL_PROJECT_ID = _baseProtocolProjectId;
|
|
306
|
+
HOOK_STORE = _hookStore;
|
|
291
307
|
/// @dev Uses the deployer address as group ID. Game scoring rulesets use uint160(token) as group ID.
|
|
292
308
|
SPLIT_GROUP = uint256(uint160(address(this)));
|
|
293
309
|
}
|
|
@@ -298,9 +314,11 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
298
314
|
|
|
299
315
|
/// @notice Fulfill split amounts between all splits for a game.
|
|
300
316
|
/// @param gameId The ID of the game to fulfill splits for.
|
|
317
|
+
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
301
318
|
function fulfillCommitmentsOf(uint256 gameId) external virtual override {
|
|
302
319
|
// Make sure commitments haven't already been fulfilled.
|
|
303
|
-
if (
|
|
320
|
+
if (commitmentsFulfilledFor[gameId]) return;
|
|
321
|
+
commitmentsFulfilledFor[gameId] = true;
|
|
304
322
|
|
|
305
323
|
// Get the game's current funding cycle along with its metadata.
|
|
306
324
|
// slither-disable-next-line unused-return
|
|
@@ -319,10 +337,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
319
337
|
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
320
338
|
uint256 pot = terminal.STORE().balanceOf({terminal: address(terminal), projectId: gameId, token: token});
|
|
321
339
|
|
|
322
|
-
// If the pot is empty,
|
|
340
|
+
// If the pot is empty, queue the final ruleset without attempting payouts.
|
|
323
341
|
// slither-disable-next-line incorrect-equality
|
|
324
342
|
if (pot == 0) {
|
|
325
|
-
fulfilledCommitmentsOf[gameId] = 1;
|
|
326
343
|
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
327
344
|
// slither-disable-next-line reentrancy-events
|
|
328
345
|
emit FulfilledCommitments({gameId: gameId, pot: 0, caller: msg.sender});
|
|
@@ -334,8 +351,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
334
351
|
mulDiv({x: pot, y: _commitmentPercentOf[gameId], denominator: JBConstants.SPLITS_TOTAL_PERCENT});
|
|
335
352
|
|
|
336
353
|
// Store the actual fee amount for accurate currentGamePotOf reporting.
|
|
337
|
-
|
|
338
|
-
fulfilledCommitmentsOf[gameId] = feeAmount > 0 ? feeAmount : 1;
|
|
354
|
+
fulfilledCommitmentsOf[gameId] = feeAmount;
|
|
339
355
|
|
|
340
356
|
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
341
357
|
// Use the ruleset's baseCurrency — this matches the currency under which payout limits were stored
|
|
@@ -346,9 +362,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
346
362
|
projectId: gameId, token: token, amount: feeAmount, currency: metadata.baseCurrency, minTokensPaidOut: 0
|
|
347
363
|
}) {}
|
|
348
364
|
catch (bytes memory reason) {
|
|
349
|
-
// Payout failed — fee stays in pot. Reset to
|
|
350
|
-
// doesn't double-count the fee
|
|
351
|
-
fulfilledCommitmentsOf[gameId] =
|
|
365
|
+
// Payout failed — fee stays in pot. Reset to 0 so currentGamePotOf
|
|
366
|
+
// doesn't double-count the fee.
|
|
367
|
+
fulfilledCommitmentsOf[gameId] = 0;
|
|
352
368
|
// slither-disable-next-line reentrancy-events
|
|
353
369
|
emit CommitmentPayoutFailed({gameId: gameId, amount: feeAmount, reason: reason});
|
|
354
370
|
}
|
|
@@ -363,24 +379,34 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
363
379
|
/// @notice Launches a new game owned by this contract with a DefifaHook attached.
|
|
364
380
|
/// @param launchProjectData Data necessary to fulfill the transaction to launch a game.
|
|
365
381
|
/// @return gameId The ID of the newly configured game.
|
|
382
|
+
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
366
383
|
function launchGameWith(DefifaLaunchProjectData memory launchProjectData)
|
|
367
384
|
external
|
|
368
385
|
override
|
|
369
386
|
returns (uint256 gameId)
|
|
370
387
|
{
|
|
371
|
-
//
|
|
388
|
+
// Game launch timing is timestamp-based by design.
|
|
389
|
+
uint256 currentTimestamp = block.timestamp;
|
|
390
|
+
|
|
391
|
+
// If no explicit game start is provided, start after the configured mint and refund windows.
|
|
372
392
|
if (launchProjectData.start == 0) {
|
|
373
|
-
|
|
374
|
-
|
|
393
|
+
uint256 start =
|
|
394
|
+
currentTimestamp + launchProjectData.mintPeriodDuration + launchProjectData.refundPeriodDuration;
|
|
395
|
+
if (start > type(uint48).max) revert DefifaDeployer_InvalidGameConfiguration();
|
|
396
|
+
|
|
397
|
+
// Casting to uint48 is safe because `start` was bounded above.
|
|
398
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
399
|
+
launchProjectData.start = uint48(start);
|
|
375
400
|
}
|
|
376
|
-
//
|
|
401
|
+
// If callers provide a future start with no mint duration, derive a mint window that begins now and ends
|
|
402
|
+
// before the refund window. This preserves the requested start while keeping minting immediately available.
|
|
377
403
|
// slither-disable-next-line incorrect-equality
|
|
378
404
|
else if (
|
|
379
405
|
launchProjectData.mintPeriodDuration == 0
|
|
380
|
-
&& launchProjectData.start >
|
|
406
|
+
&& launchProjectData.start > currentTimestamp + launchProjectData.refundPeriodDuration
|
|
381
407
|
) {
|
|
382
408
|
launchProjectData.mintPeriodDuration =
|
|
383
|
-
uint24(launchProjectData.start - (
|
|
409
|
+
uint24(launchProjectData.start - (currentTimestamp + launchProjectData.refundPeriodDuration));
|
|
384
410
|
}
|
|
385
411
|
|
|
386
412
|
// Make sure the provided gameplay timestamps are sequential and that there is a mint duration.
|
|
@@ -388,24 +414,29 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
388
414
|
// slither-disable-next-line incorrect-equality
|
|
389
415
|
launchProjectData.mintPeriodDuration == 0
|
|
390
416
|
|| launchProjectData.start
|
|
391
|
-
<
|
|
417
|
+
< currentTimestamp + launchProjectData.refundPeriodDuration + launchProjectData.mintPeriodDuration
|
|
392
418
|
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
393
419
|
|
|
394
420
|
// The hook and governor hardcode uint256[128] tier-weight tables, so reject games with more than 128 tiers.
|
|
395
421
|
if (launchProjectData.tiers.length > 128) revert DefifaDeployer_InvalidGameConfiguration();
|
|
396
422
|
|
|
397
|
-
// Reject ERC-20 games with a zero currency
|
|
423
|
+
// Reject ERC-20 games with a zero currency. A zero baseCurrency would cause payout limit lookups
|
|
398
424
|
// in fulfillCommitmentsOf to silently fail, skipping all commitment payouts.
|
|
399
425
|
// slither-disable-next-line incorrect-equality
|
|
400
426
|
if (launchProjectData.token.token != JBConstants.NATIVE_TOKEN && launchProjectData.token.currency == 0) {
|
|
401
427
|
revert DefifaDeployer_InvalidCurrency();
|
|
402
428
|
}
|
|
403
429
|
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
430
|
+
// If a scorecard timeout is set, it must exceed the grace period + timelock duration.
|
|
431
|
+
// Otherwise the game would enter NO_CONTEST before a scorecard could ever reach SUCCEEDED.
|
|
432
|
+
if (
|
|
433
|
+
launchProjectData.scorecardTimeout > 0
|
|
434
|
+
&& launchProjectData.scorecardTimeout
|
|
435
|
+
<= launchProjectData.attestationGracePeriod + launchProjectData.timelockDuration
|
|
436
|
+
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
437
|
+
|
|
438
|
+
// Reserve the game ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
439
|
+
gameId = CONTROLLER.PROJECTS().createFor(address(this));
|
|
409
440
|
|
|
410
441
|
{
|
|
411
442
|
// Store the timestamps that'll define the game phases.
|
|
@@ -536,19 +567,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
536
567
|
_contractUri: launchProjectData.contractUri,
|
|
537
568
|
_tiers: hookTiers,
|
|
538
569
|
_currency: launchProjectData.token.currency,
|
|
539
|
-
_store:
|
|
570
|
+
_store: HOOK_STORE,
|
|
540
571
|
_gamePhaseReporter: this,
|
|
541
572
|
_gamePotReporter: this,
|
|
542
573
|
_defaultAttestationDelegate: launchProjectData.defaultAttestationDelegate,
|
|
543
574
|
_tierNames: tierNames
|
|
544
575
|
});
|
|
545
576
|
|
|
546
|
-
// Launch the Juicebox project.
|
|
547
|
-
|
|
548
|
-
_launchGame({launchProjectData: launchProjectData, gameId: gameId, dataHook: address(hook)});
|
|
549
|
-
|
|
550
|
-
// Revert if the game ID does not match (e.g. front-run by another project creation).
|
|
551
|
-
if (gameId != actualGameId) revert DefifaDeployer_InvalidGameConfiguration();
|
|
577
|
+
// Launch the Juicebox rulesets for the reserved project.
|
|
578
|
+
_launchGame({launchProjectData: launchProjectData, gameId: gameId, dataHook: address(hook)});
|
|
552
579
|
|
|
553
580
|
// Clone and initialize the new governor.
|
|
554
581
|
GOVERNOR.initializeGame({
|
|
@@ -742,15 +769,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
742
769
|
return groupedSplits;
|
|
743
770
|
}
|
|
744
771
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
{
|
|
753
|
-
//
|
|
772
|
+
/// @notice Launch the Juicebox project rulesets that define a Defifa game's lifecycle.
|
|
773
|
+
/// @dev The project ID is reserved before the hook clone is initialized, so this function launches rulesets for
|
|
774
|
+
/// that existing project instead of creating a new one. It also forwards `projectUri` to the controller so the
|
|
775
|
+
/// project metadata is set atomically with the first rulesets.
|
|
776
|
+
/// @param launchProjectData The normalized launch data for the game.
|
|
777
|
+
/// @param gameId The reserved Juicebox project ID for the game.
|
|
778
|
+
/// @param dataHook The initialized Defifa hook clone used by every ruleset.
|
|
779
|
+
function _launchGame(DefifaLaunchProjectData memory launchProjectData, uint256 gameId, address dataHook) internal {
|
|
780
|
+
// Accept exactly the token/accounting context the game was configured to use.
|
|
754
781
|
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
755
782
|
accountingContexts[0] = launchProjectData.token;
|
|
756
783
|
|
|
@@ -759,11 +786,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
759
786
|
terminalConfigurations[0] =
|
|
760
787
|
JBTerminalConfig({terminal: launchProjectData.terminal, accountingContextsToAccept: accountingContexts});
|
|
761
788
|
|
|
762
|
-
// Build the rulesets that this Defifa game will go through.
|
|
789
|
+
// Build the rulesets that this Defifa game will go through. Games always have MINT and SCORING phases; the
|
|
790
|
+
// REFUND phase is omitted entirely when its duration is zero so the scoring ruleset can follow minting.
|
|
763
791
|
bool hasRefundPhase = launchProjectData.refundPeriodDuration != 0;
|
|
764
792
|
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](hasRefundPhase ? 3 : 2);
|
|
765
793
|
|
|
766
|
-
//
|
|
794
|
+
// MINT cycle: payments are accepted, cash-outs are enabled, and pending reserved tokens are paused so the
|
|
795
|
+
// scoring/final phases decide how reserved tokens are distributed.
|
|
767
796
|
rulesetConfigs[0] = JBRulesetConfig({
|
|
768
797
|
mustStartAtOrAfter: launchProjectData.start - launchProjectData.mintPeriodDuration
|
|
769
798
|
- launchProjectData.refundPeriodDuration,
|
|
@@ -806,7 +835,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
806
835
|
|
|
807
836
|
uint256 cycleNumber = 1;
|
|
808
837
|
if (hasRefundPhase) {
|
|
809
|
-
//
|
|
838
|
+
// REFUND cycle: payments are paused while cash-outs remain available at pre-scoring terms.
|
|
810
839
|
rulesetConfigs[cycleNumber++] = JBRulesetConfig({
|
|
811
840
|
mustStartAtOrAfter: launchProjectData.start - launchProjectData.refundPeriodDuration,
|
|
812
841
|
duration: launchProjectData.refundPeriodDuration,
|
|
@@ -848,7 +877,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
848
877
|
});
|
|
849
878
|
}
|
|
850
879
|
|
|
851
|
-
//
|
|
880
|
+
// The scoring ruleset can send the whole pot as payouts. `fulfillCommitmentsOf` only sends the commitment
|
|
881
|
+
// portion and leaves the rest as surplus for the final cash-out ruleset.
|
|
852
882
|
JBCurrencyAmount[] memory payoutAmounts = new JBCurrencyAmount[](1);
|
|
853
883
|
payoutAmounts[0] = JBCurrencyAmount({
|
|
854
884
|
// We allow a payout of the full amount, this will then mostly be added back to the balance of the project.
|
|
@@ -864,7 +894,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
864
894
|
surplusAllowances: new JBCurrencyAmount[](0)
|
|
865
895
|
});
|
|
866
896
|
|
|
867
|
-
//
|
|
897
|
+
// SCORING cycle: payments pause, the game owner must send payouts, and the Defifa hook determines the final
|
|
898
|
+
// cash-out weights once a scorecard is ratified.
|
|
868
899
|
rulesetConfigs[cycleNumber++] = JBRulesetConfig({
|
|
869
900
|
mustStartAtOrAfter: launchProjectData.start,
|
|
870
901
|
duration: 0,
|
|
@@ -906,9 +937,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
906
937
|
fundAccessLimitGroups: fundAccessConstraints
|
|
907
938
|
});
|
|
908
939
|
|
|
909
|
-
//
|
|
910
|
-
return
|
|
911
|
-
|
|
940
|
+
// Launch the rulesets for the reserved project and set the project URI in the same controller call.
|
|
941
|
+
// slither-disable-next-line unused-return
|
|
942
|
+
CONTROLLER.launchRulesetsFor({
|
|
943
|
+
projectId: gameId,
|
|
912
944
|
projectUri: launchProjectData.projectUri,
|
|
913
945
|
rulesetConfigurations: rulesetConfigs,
|
|
914
946
|
terminalConfigurations: terminalConfigurations,
|