@ballkidz/defifa 0.0.24 → 0.0.26
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 +74 -46
- package/src/DefifaGovernor.sol +53 -11
- package/src/DefifaHook.sol +79 -25
- package/src/DefifaTokenUriResolver.sol +111 -19
- package/src/interfaces/IDefifaDeployer.sol +5 -0
- package/src/interfaces/IDefifaGovernor.sol +4 -0
- package/src/interfaces/IDefifaHook.sol +5 -0
- package/src/libraries/DefifaHookLib.sol +9 -10
- package/src/structs/DefifaLaunchProjectData.sol +0 -3
- 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/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.26",
|
|
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";
|
|
@@ -102,6 +104,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
102
104
|
/// @notice The hooks registry.
|
|
103
105
|
IJBAddressRegistry public immutable REGISTRY;
|
|
104
106
|
|
|
107
|
+
/// @notice The 721 tiers hook store used by all games.
|
|
108
|
+
IJB721TiersHookStore public immutable HOOK_STORE;
|
|
109
|
+
|
|
105
110
|
/// @notice The divisor that describes the protocol fee that should be taken.
|
|
106
111
|
/// @dev This is equal to 100 divided by the fee percent (e.g. 40 = 2.5% fee).
|
|
107
112
|
uint256 public constant override BASE_PROTOCOL_FEE_DIVISOR = 40;
|
|
@@ -121,6 +126,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
121
126
|
/// @notice The total absolute split percent for each game (out of SPLITS_TOTAL_PERCENT).
|
|
122
127
|
mapping(uint256 => uint256) internal _commitmentPercentOf;
|
|
123
128
|
|
|
129
|
+
/// @notice Whether commitments have been fulfilled for a game.
|
|
130
|
+
mapping(uint256 => bool) public commitmentsFulfilledFor;
|
|
131
|
+
|
|
124
132
|
/// @notice Whether the no-contest refund ruleset has been triggered for a game.
|
|
125
133
|
/// @dev Once triggered, the game stays in NO_CONTEST and refunds are enabled.
|
|
126
134
|
mapping(uint256 => bool) public noContestTriggeredFor;
|
|
@@ -253,6 +261,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
253
261
|
|
|
254
262
|
// Check scorecard ratification timeout: if enough time has passed without a ratified scorecard, the game is
|
|
255
263
|
// NO_CONTEST.
|
|
264
|
+
// Game phase timing is timestamp-based by design.
|
|
265
|
+
// forge-lint: disable-next-line(block-timestamp)
|
|
256
266
|
if (ops.scorecardTimeout > 0 && block.timestamp > currentRuleset.start + ops.scorecardTimeout) {
|
|
257
267
|
return DefifaGamePhase.NO_CONTEST;
|
|
258
268
|
}
|
|
@@ -278,7 +288,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
278
288
|
IJBController _controller,
|
|
279
289
|
IJBAddressRegistry _registry,
|
|
280
290
|
uint256 _defifaProjectId,
|
|
281
|
-
uint256 _baseProtocolProjectId
|
|
291
|
+
uint256 _baseProtocolProjectId,
|
|
292
|
+
IJB721TiersHookStore _hookStore
|
|
282
293
|
) {
|
|
283
294
|
// slither-disable-next-line missing-zero-check
|
|
284
295
|
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
@@ -288,6 +299,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
288
299
|
REGISTRY = _registry;
|
|
289
300
|
DEFIFA_PROJECT_ID = _defifaProjectId;
|
|
290
301
|
BASE_PROTOCOL_PROJECT_ID = _baseProtocolProjectId;
|
|
302
|
+
HOOK_STORE = _hookStore;
|
|
291
303
|
/// @dev Uses the deployer address as group ID. Game scoring rulesets use uint160(token) as group ID.
|
|
292
304
|
SPLIT_GROUP = uint256(uint160(address(this)));
|
|
293
305
|
}
|
|
@@ -298,9 +310,11 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
298
310
|
|
|
299
311
|
/// @notice Fulfill split amounts between all splits for a game.
|
|
300
312
|
/// @param gameId The ID of the game to fulfill splits for.
|
|
313
|
+
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
301
314
|
function fulfillCommitmentsOf(uint256 gameId) external virtual override {
|
|
302
315
|
// Make sure commitments haven't already been fulfilled.
|
|
303
|
-
if (
|
|
316
|
+
if (commitmentsFulfilledFor[gameId]) return;
|
|
317
|
+
commitmentsFulfilledFor[gameId] = true;
|
|
304
318
|
|
|
305
319
|
// Get the game's current funding cycle along with its metadata.
|
|
306
320
|
// slither-disable-next-line unused-return
|
|
@@ -319,10 +333,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
319
333
|
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
320
334
|
uint256 pot = terminal.STORE().balanceOf({terminal: address(terminal), projectId: gameId, token: token});
|
|
321
335
|
|
|
322
|
-
// If the pot is empty,
|
|
336
|
+
// If the pot is empty, queue the final ruleset without attempting payouts.
|
|
323
337
|
// slither-disable-next-line incorrect-equality
|
|
324
338
|
if (pot == 0) {
|
|
325
|
-
fulfilledCommitmentsOf[gameId] = 1;
|
|
326
339
|
_queueFinalRuleset({gameId: gameId, metadata: metadata});
|
|
327
340
|
// slither-disable-next-line reentrancy-events
|
|
328
341
|
emit FulfilledCommitments({gameId: gameId, pot: 0, caller: msg.sender});
|
|
@@ -334,8 +347,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
334
347
|
mulDiv({x: pot, y: _commitmentPercentOf[gameId], denominator: JBConstants.SPLITS_TOTAL_PERCENT});
|
|
335
348
|
|
|
336
349
|
// Store the actual fee amount for accurate currentGamePotOf reporting.
|
|
337
|
-
|
|
338
|
-
fulfilledCommitmentsOf[gameId] = feeAmount > 0 ? feeAmount : 1;
|
|
350
|
+
fulfilledCommitmentsOf[gameId] = feeAmount;
|
|
339
351
|
|
|
340
352
|
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
341
353
|
// Use the ruleset's baseCurrency — this matches the currency under which payout limits were stored
|
|
@@ -346,9 +358,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
346
358
|
projectId: gameId, token: token, amount: feeAmount, currency: metadata.baseCurrency, minTokensPaidOut: 0
|
|
347
359
|
}) {}
|
|
348
360
|
catch (bytes memory reason) {
|
|
349
|
-
// Payout failed — fee stays in pot. Reset to
|
|
350
|
-
// doesn't double-count the fee
|
|
351
|
-
fulfilledCommitmentsOf[gameId] =
|
|
361
|
+
// Payout failed — fee stays in pot. Reset to 0 so currentGamePotOf
|
|
362
|
+
// doesn't double-count the fee.
|
|
363
|
+
fulfilledCommitmentsOf[gameId] = 0;
|
|
352
364
|
// slither-disable-next-line reentrancy-events
|
|
353
365
|
emit CommitmentPayoutFailed({gameId: gameId, amount: feeAmount, reason: reason});
|
|
354
366
|
}
|
|
@@ -363,24 +375,34 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
363
375
|
/// @notice Launches a new game owned by this contract with a DefifaHook attached.
|
|
364
376
|
/// @param launchProjectData Data necessary to fulfill the transaction to launch a game.
|
|
365
377
|
/// @return gameId The ID of the newly configured game.
|
|
378
|
+
// slither-disable-next-line reentrancy-benign,reentrancy-no-eth
|
|
366
379
|
function launchGameWith(DefifaLaunchProjectData memory launchProjectData)
|
|
367
380
|
external
|
|
368
381
|
override
|
|
369
382
|
returns (uint256 gameId)
|
|
370
383
|
{
|
|
371
|
-
//
|
|
384
|
+
// Game launch timing is timestamp-based by design.
|
|
385
|
+
uint256 currentTimestamp = block.timestamp;
|
|
386
|
+
|
|
387
|
+
// If no explicit game start is provided, start after the configured mint and refund windows.
|
|
372
388
|
if (launchProjectData.start == 0) {
|
|
373
|
-
|
|
374
|
-
|
|
389
|
+
uint256 start =
|
|
390
|
+
currentTimestamp + launchProjectData.mintPeriodDuration + launchProjectData.refundPeriodDuration;
|
|
391
|
+
if (start > type(uint48).max) revert DefifaDeployer_InvalidGameConfiguration();
|
|
392
|
+
|
|
393
|
+
// Casting to uint48 is safe because `start` was bounded above.
|
|
394
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
395
|
+
launchProjectData.start = uint48(start);
|
|
375
396
|
}
|
|
376
|
-
//
|
|
397
|
+
// If callers provide a future start with no mint duration, derive a mint window that begins now and ends
|
|
398
|
+
// before the refund window. This preserves the requested start while keeping minting immediately available.
|
|
377
399
|
// slither-disable-next-line incorrect-equality
|
|
378
400
|
else if (
|
|
379
401
|
launchProjectData.mintPeriodDuration == 0
|
|
380
|
-
&& launchProjectData.start >
|
|
402
|
+
&& launchProjectData.start > currentTimestamp + launchProjectData.refundPeriodDuration
|
|
381
403
|
) {
|
|
382
404
|
launchProjectData.mintPeriodDuration =
|
|
383
|
-
uint24(launchProjectData.start - (
|
|
405
|
+
uint24(launchProjectData.start - (currentTimestamp + launchProjectData.refundPeriodDuration));
|
|
384
406
|
}
|
|
385
407
|
|
|
386
408
|
// Make sure the provided gameplay timestamps are sequential and that there is a mint duration.
|
|
@@ -388,24 +410,29 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
388
410
|
// slither-disable-next-line incorrect-equality
|
|
389
411
|
launchProjectData.mintPeriodDuration == 0
|
|
390
412
|
|| launchProjectData.start
|
|
391
|
-
<
|
|
413
|
+
< currentTimestamp + launchProjectData.refundPeriodDuration + launchProjectData.mintPeriodDuration
|
|
392
414
|
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
393
415
|
|
|
394
416
|
// The hook and governor hardcode uint256[128] tier-weight tables, so reject games with more than 128 tiers.
|
|
395
417
|
if (launchProjectData.tiers.length > 128) revert DefifaDeployer_InvalidGameConfiguration();
|
|
396
418
|
|
|
397
|
-
// Reject ERC-20 games with a zero currency
|
|
419
|
+
// Reject ERC-20 games with a zero currency. A zero baseCurrency would cause payout limit lookups
|
|
398
420
|
// in fulfillCommitmentsOf to silently fail, skipping all commitment payouts.
|
|
399
421
|
// slither-disable-next-line incorrect-equality
|
|
400
422
|
if (launchProjectData.token.token != JBConstants.NATIVE_TOKEN && launchProjectData.token.currency == 0) {
|
|
401
423
|
revert DefifaDeployer_InvalidCurrency();
|
|
402
424
|
}
|
|
403
425
|
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
426
|
+
// If a scorecard timeout is set, it must exceed the grace period + timelock duration.
|
|
427
|
+
// Otherwise the game would enter NO_CONTEST before a scorecard could ever reach SUCCEEDED.
|
|
428
|
+
if (
|
|
429
|
+
launchProjectData.scorecardTimeout > 0
|
|
430
|
+
&& launchProjectData.scorecardTimeout
|
|
431
|
+
<= launchProjectData.attestationGracePeriod + launchProjectData.timelockDuration
|
|
432
|
+
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
433
|
+
|
|
434
|
+
// Reserve the game ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
435
|
+
gameId = CONTROLLER.PROJECTS().createFor(address(this));
|
|
409
436
|
|
|
410
437
|
{
|
|
411
438
|
// Store the timestamps that'll define the game phases.
|
|
@@ -536,19 +563,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
536
563
|
_contractUri: launchProjectData.contractUri,
|
|
537
564
|
_tiers: hookTiers,
|
|
538
565
|
_currency: launchProjectData.token.currency,
|
|
539
|
-
_store:
|
|
566
|
+
_store: HOOK_STORE,
|
|
540
567
|
_gamePhaseReporter: this,
|
|
541
568
|
_gamePotReporter: this,
|
|
542
569
|
_defaultAttestationDelegate: launchProjectData.defaultAttestationDelegate,
|
|
543
570
|
_tierNames: tierNames
|
|
544
571
|
});
|
|
545
572
|
|
|
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();
|
|
573
|
+
// Launch the Juicebox rulesets for the reserved project.
|
|
574
|
+
_launchGame({launchProjectData: launchProjectData, gameId: gameId, dataHook: address(hook)});
|
|
552
575
|
|
|
553
576
|
// Clone and initialize the new governor.
|
|
554
577
|
GOVERNOR.initializeGame({
|
|
@@ -742,15 +765,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
742
765
|
return groupedSplits;
|
|
743
766
|
}
|
|
744
767
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
{
|
|
753
|
-
//
|
|
768
|
+
/// @notice Launch the Juicebox project rulesets that define a Defifa game's lifecycle.
|
|
769
|
+
/// @dev The project ID is reserved before the hook clone is initialized, so this function launches rulesets for
|
|
770
|
+
/// that existing project instead of creating a new one. It also forwards `projectUri` to the controller so the
|
|
771
|
+
/// project metadata is set atomically with the first rulesets.
|
|
772
|
+
/// @param launchProjectData The normalized launch data for the game.
|
|
773
|
+
/// @param gameId The reserved Juicebox project ID for the game.
|
|
774
|
+
/// @param dataHook The initialized Defifa hook clone used by every ruleset.
|
|
775
|
+
function _launchGame(DefifaLaunchProjectData memory launchProjectData, uint256 gameId, address dataHook) internal {
|
|
776
|
+
// Accept exactly the token/accounting context the game was configured to use.
|
|
754
777
|
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
755
778
|
accountingContexts[0] = launchProjectData.token;
|
|
756
779
|
|
|
@@ -759,11 +782,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
759
782
|
terminalConfigurations[0] =
|
|
760
783
|
JBTerminalConfig({terminal: launchProjectData.terminal, accountingContextsToAccept: accountingContexts});
|
|
761
784
|
|
|
762
|
-
// Build the rulesets that this Defifa game will go through.
|
|
785
|
+
// Build the rulesets that this Defifa game will go through. Games always have MINT and SCORING phases; the
|
|
786
|
+
// REFUND phase is omitted entirely when its duration is zero so the scoring ruleset can follow minting.
|
|
763
787
|
bool hasRefundPhase = launchProjectData.refundPeriodDuration != 0;
|
|
764
788
|
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](hasRefundPhase ? 3 : 2);
|
|
765
789
|
|
|
766
|
-
//
|
|
790
|
+
// MINT cycle: payments are accepted, cash-outs are enabled, and pending reserved tokens are paused so the
|
|
791
|
+
// scoring/final phases decide how reserved tokens are distributed.
|
|
767
792
|
rulesetConfigs[0] = JBRulesetConfig({
|
|
768
793
|
mustStartAtOrAfter: launchProjectData.start - launchProjectData.mintPeriodDuration
|
|
769
794
|
- launchProjectData.refundPeriodDuration,
|
|
@@ -806,7 +831,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
806
831
|
|
|
807
832
|
uint256 cycleNumber = 1;
|
|
808
833
|
if (hasRefundPhase) {
|
|
809
|
-
//
|
|
834
|
+
// REFUND cycle: payments are paused while cash-outs remain available at pre-scoring terms.
|
|
810
835
|
rulesetConfigs[cycleNumber++] = JBRulesetConfig({
|
|
811
836
|
mustStartAtOrAfter: launchProjectData.start - launchProjectData.refundPeriodDuration,
|
|
812
837
|
duration: launchProjectData.refundPeriodDuration,
|
|
@@ -848,7 +873,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
848
873
|
});
|
|
849
874
|
}
|
|
850
875
|
|
|
851
|
-
//
|
|
876
|
+
// The scoring ruleset can send the whole pot as payouts. `fulfillCommitmentsOf` only sends the commitment
|
|
877
|
+
// portion and leaves the rest as surplus for the final cash-out ruleset.
|
|
852
878
|
JBCurrencyAmount[] memory payoutAmounts = new JBCurrencyAmount[](1);
|
|
853
879
|
payoutAmounts[0] = JBCurrencyAmount({
|
|
854
880
|
// We allow a payout of the full amount, this will then mostly be added back to the balance of the project.
|
|
@@ -864,7 +890,8 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
864
890
|
surplusAllowances: new JBCurrencyAmount[](0)
|
|
865
891
|
});
|
|
866
892
|
|
|
867
|
-
//
|
|
893
|
+
// SCORING cycle: payments pause, the game owner must send payouts, and the Defifa hook determines the final
|
|
894
|
+
// cash-out weights once a scorecard is ratified.
|
|
868
895
|
rulesetConfigs[cycleNumber++] = JBRulesetConfig({
|
|
869
896
|
mustStartAtOrAfter: launchProjectData.start,
|
|
870
897
|
duration: 0,
|
|
@@ -906,9 +933,10 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
906
933
|
fundAccessLimitGroups: fundAccessConstraints
|
|
907
934
|
});
|
|
908
935
|
|
|
909
|
-
//
|
|
910
|
-
return
|
|
911
|
-
|
|
936
|
+
// Launch the rulesets for the reserved project and set the project URI in the same controller call.
|
|
937
|
+
// slither-disable-next-line unused-return
|
|
938
|
+
CONTROLLER.launchRulesetsFor({
|
|
939
|
+
projectId: gameId,
|
|
912
940
|
projectUri: launchProjectData.projectUri,
|
|
913
941
|
rulesetConfigurations: rulesetConfigs,
|
|
914
942
|
terminalConfigurations: terminalConfigurations,
|