@ballkidz/defifa 0.0.22 → 0.0.23
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 +55 -142
- package/ARCHITECTURE.md +109 -45
- package/AUDIT_INSTRUCTIONS.md +50 -52
- package/README.md +97 -32
- package/RISKS.md +41 -50
- package/SKILLS.md +18 -14
- package/USER_JOURNEYS.md +142 -42
- package/foundry.toml +2 -0
- package/package.json +1 -1
- package/references/operations.md +6 -1
- package/references/runtime.md +15 -4
- package/src/DefifaDeployer.sol +3 -0
- package/src/structs/DefifaTierParams.sol +0 -1
- package/test/audit/CodexTierCapMismatch.t.sol +171 -0
- package/test/audit/H5TierCapValidation.t.sol +184 -0
package/package.json
CHANGED
package/references/operations.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Defifa Operations
|
|
2
2
|
|
|
3
|
+
Use this file when the task is about launch config, phase timing, governance windows, or deciding whether a symptom is operational drift or runtime logic.
|
|
4
|
+
|
|
3
5
|
## Deployment Surface
|
|
4
6
|
|
|
5
7
|
- [`src/DefifaDeployer.sol`](../src/DefifaDeployer.sol) is the first stop for launch-time config, phase queueing, and post-ratification fulfillment.
|
|
6
|
-
- [`script/Deploy.s.sol`](../script/Deploy.s.sol)
|
|
8
|
+
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) and [`script/helpers/DefifaDeploymentLib.sol`](../script/helpers/DefifaDeploymentLib.sol) are the deployment entrypoints when the task is about current wiring rather than game mechanics.
|
|
7
9
|
- [`src/structs/`](../src/structs/) and [`src/enums/`](../src/enums/) define launch data, phase types, and other inputs that often drift from remembered assumptions.
|
|
8
10
|
|
|
9
11
|
## Change Checklist
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
- If you edit hook settlement logic, re-check fee accounting and mint-cost invariants.
|
|
13
15
|
- If you touch governance thresholds or attestation behavior, inspect the governor tests before assuming the change is local.
|
|
14
16
|
- If you touch token metadata or rendering, verify whether the bug belongs in the resolver instead of settlement code.
|
|
17
|
+
- If you touch anything supply-sensitive, inspect the audit tests around pending reserves and quorum before relying on current intuition.
|
|
15
18
|
|
|
16
19
|
## Common Failure Modes
|
|
17
20
|
|
|
@@ -19,9 +22,11 @@
|
|
|
19
22
|
- Governance behavior looks wrong, but the real issue is stale launch configuration.
|
|
20
23
|
- Settlement changes accidentally affect fee distribution or redemption accounting.
|
|
21
24
|
- Resolver issues get misdiagnosed as hook or governor problems because they surface through NFTs.
|
|
25
|
+
- Audit-style failures around reserve dilution or attestation counting are treated as isolated math issues even though they cross deployer, hook, and governor boundaries.
|
|
22
26
|
|
|
23
27
|
## Useful Proof Points
|
|
24
28
|
|
|
25
29
|
- [`test/Fork.t.sol`](../test/Fork.t.sol) for live-integration assumptions.
|
|
26
30
|
- [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) and [`test/TestQALastMile.t.sol`](../test/TestQALastMile.t.sol) for pinned edge cases.
|
|
27
31
|
- [`test/BWAFunctionComparison.t.sol`](../test/BWAFunctionComparison.t.sol) and [`test/DefifaUSDC.t.sol`](../test/DefifaUSDC.t.sol) when currency or accounting context matters.
|
|
32
|
+
- [`test/audit/`](../test/audit/) when a change touches pending reserves, registry alignment, quorum griefing, or double-counting.
|
package/references/runtime.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Defifa Runtime
|
|
2
2
|
|
|
3
|
+
Use this file when `defifa/SKILLS.md` has already routed you here and you need to reason about the game as a state machine rather than as isolated contracts.
|
|
4
|
+
|
|
3
5
|
## Contract Roles
|
|
4
6
|
|
|
5
7
|
- [`src/DefifaDeployer.sol`](../src/DefifaDeployer.sol) launches games, manages phase progression, fulfills commitments, and triggers safety exits such as no-contest.
|
|
@@ -12,9 +14,9 @@
|
|
|
12
14
|
|
|
13
15
|
1. Countdown before minting opens.
|
|
14
16
|
2. Mint phase where players buy outcome NFTs and can delegate attestation power.
|
|
15
|
-
3. Optional refund phase.
|
|
17
|
+
3. Optional refund phase if the launch configuration allows it.
|
|
16
18
|
4. Scoring phase where scorecards are submitted, attested, and ratified.
|
|
17
|
-
5. Complete or no-contest settlement depending on governance and safety
|
|
19
|
+
5. Complete or no-contest settlement depending on governance outcome and safety checks.
|
|
18
20
|
|
|
19
21
|
## High-Risk Areas
|
|
20
22
|
|
|
@@ -22,11 +24,20 @@
|
|
|
22
24
|
- No-contest and refund behavior: these paths are economic safety valves, not edge-case garnish.
|
|
23
25
|
- Fee accounting and commitment fulfillment: payout ordering and accounting drift can change final redemption value.
|
|
24
26
|
- Hook/governor/deployer coupling: many bugs come from changing one layer while assuming the others are passive.
|
|
27
|
+
- Pending reserved supply and snapshot assumptions: settlement and quorum logic can drift if supply-sensitive views are taken at the wrong time.
|
|
28
|
+
- Scorecards that miss quorum do not naturally “finish.” New scorecards can still be submitted until no-contest logic takes over, so do not assume a clean defeated terminal state.
|
|
29
|
+
|
|
30
|
+
## Common Misdiagnoses
|
|
31
|
+
|
|
32
|
+
- A settlement bug is blamed on [`src/DefifaHook.sol`](../src/DefifaHook.sol) even though the wrong phase or grace-period state was created in [`src/DefifaDeployer.sol`](../src/DefifaDeployer.sol) or [`src/DefifaGovernor.sol`](../src/DefifaGovernor.sol).
|
|
33
|
+
- A governance bug is blamed on the governor even though attestation power or delegation semantics were wrong in the hook layer.
|
|
34
|
+
- An NFT-facing bug is blamed on settlement code even though the problem is resolver output in [`src/DefifaTokenUriResolver.sol`](../src/DefifaTokenUriResolver.sol).
|
|
35
|
+
- A Defifa-specific payout result is patched in this repo when the real bug is shared 721 or core protocol behavior upstream.
|
|
25
36
|
|
|
26
37
|
## Tests To Trust First
|
|
27
38
|
|
|
28
39
|
- [`test/DefifaGovernor.t.sol`](../test/DefifaGovernor.t.sol) for governance flow.
|
|
29
40
|
- [`test/DefifaNoContest.t.sol`](../test/DefifaNoContest.t.sol) for safety exits.
|
|
30
41
|
- [`test/DefifaFeeAccounting.t.sol`](../test/DefifaFeeAccounting.t.sol) and [`test/DefifaMintCostInvariant.t.sol`](../test/DefifaMintCostInvariant.t.sol) for economic correctness.
|
|
31
|
-
- [`test/DefifaHookRegressions.t.sol`](../test/DefifaHookRegressions.t.sol) and [`test/regression
|
|
32
|
-
- [`test/DefifaSecurity.t.sol`](../test/DefifaSecurity.t.sol)
|
|
42
|
+
- [`test/DefifaHookRegressions.t.sol`](../test/DefifaHookRegressions.t.sol), [`test/regression/GracePeriodBypass.t.sol`](../test/regression/GracePeriodBypass.t.sol), [`test/regression/FulfillmentBlocksRatification.t.sol`](../test/regression/FulfillmentBlocksRatification.t.sol), and [`test/regression/AttestationDelegateBeneficiary.t.sol`](../test/regression/AttestationDelegateBeneficiary.t.sol) for pinned regressions.
|
|
43
|
+
- [`test/DefifaSecurity.t.sol`](../test/DefifaSecurity.t.sol), [`test/DefifaGovernanceHardening.t.sol`](../test/DefifaGovernanceHardening.t.sol), [`test/DefifaAdversarialQuorum.t.sol`](../test/DefifaAdversarialQuorum.t.sol), and [`test/audit/`](../test/audit/) for adversarial and audit-derived cases.
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -394,6 +394,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
394
394
|
< block.timestamp + launchProjectData.refundPeriodDuration + launchProjectData.mintPeriodDuration
|
|
395
395
|
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
396
396
|
|
|
397
|
+
// The hook and governor hardcode uint256[128] tier-weight tables, so reject games with more than 128 tiers.
|
|
398
|
+
if (launchProjectData.tiers.length > 128) revert DefifaDeployer_InvalidGameConfiguration();
|
|
399
|
+
|
|
397
400
|
// Get the game ID, optimistically knowing it will be one greater than the current count.
|
|
398
401
|
// Note: this prediction can race with other concurrent project deployments. If another project is
|
|
399
402
|
// created between reading count() and launchProjectFor(), the actual ID will differ. This is
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
5
|
+
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
6
|
+
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
7
|
+
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
8
|
+
import {DefifaGamePhase} from "../../src/enums/DefifaGamePhase.sol";
|
|
9
|
+
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
10
|
+
import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
|
|
11
|
+
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
12
|
+
|
|
13
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
14
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
15
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
16
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
17
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
18
|
+
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
19
|
+
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
20
|
+
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
21
|
+
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
22
|
+
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
23
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
24
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
25
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
26
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
27
|
+
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
28
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
29
|
+
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
30
|
+
|
|
31
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
32
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
33
|
+
|
|
34
|
+
contract CodexTierCapMismatchTest is JBTest, TestBaseWorkflow {
|
|
35
|
+
using JBRulesetMetadataResolver for JBRuleset;
|
|
36
|
+
|
|
37
|
+
DefifaDeployer internal deployer;
|
|
38
|
+
DefifaGovernor internal governor;
|
|
39
|
+
DefifaHook internal hookCodeOrigin;
|
|
40
|
+
|
|
41
|
+
function setUp() public virtual override {
|
|
42
|
+
super.setUp();
|
|
43
|
+
|
|
44
|
+
JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
|
|
45
|
+
tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
46
|
+
|
|
47
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
48
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
|
|
49
|
+
|
|
50
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
51
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
52
|
+
mustStartAtOrAfter: 0,
|
|
53
|
+
duration: 10 days,
|
|
54
|
+
weight: 1e18,
|
|
55
|
+
weightCutPercent: 0,
|
|
56
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
57
|
+
metadata: JBRulesetMetadata({
|
|
58
|
+
reservedPercent: 0,
|
|
59
|
+
cashOutTaxRate: 0,
|
|
60
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
61
|
+
pausePay: false,
|
|
62
|
+
pauseCreditTransfers: false,
|
|
63
|
+
allowOwnerMinting: false,
|
|
64
|
+
allowSetCustomToken: false,
|
|
65
|
+
allowTerminalMigration: false,
|
|
66
|
+
allowSetTerminals: false,
|
|
67
|
+
allowSetController: false,
|
|
68
|
+
allowAddAccountingContext: false,
|
|
69
|
+
allowAddPriceFeed: false,
|
|
70
|
+
ownerMustSendPayouts: false,
|
|
71
|
+
holdFees: false,
|
|
72
|
+
useTotalSurplusForCashOuts: false,
|
|
73
|
+
useDataHookForPay: true,
|
|
74
|
+
useDataHookForCashOut: true,
|
|
75
|
+
dataHook: address(0),
|
|
76
|
+
metadata: 0
|
|
77
|
+
}),
|
|
78
|
+
splitGroups: new JBSplitGroup[](0),
|
|
79
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
83
|
+
uint256 protocolFeeProjectId =
|
|
84
|
+
jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
85
|
+
vm.prank(projectOwner);
|
|
86
|
+
address nanaToken =
|
|
87
|
+
address(jbController().deployERC20For(protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
88
|
+
|
|
89
|
+
uint256 defifaProjectId = jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
90
|
+
vm.prank(projectOwner);
|
|
91
|
+
address defifaToken = address(jbController().deployERC20For(defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
92
|
+
|
|
93
|
+
hookCodeOrigin = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
|
|
94
|
+
governor = new DefifaGovernor(jbController(), address(this));
|
|
95
|
+
deployer = new DefifaDeployer(
|
|
96
|
+
address(hookCodeOrigin),
|
|
97
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
98
|
+
governor,
|
|
99
|
+
jbController(),
|
|
100
|
+
new JBAddressRegistry(),
|
|
101
|
+
defifaProjectId,
|
|
102
|
+
protocolFeeProjectId
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
hookCodeOrigin.transferOwnership(address(deployer));
|
|
106
|
+
governor.transferOwnership(address(deployer));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function test_launchRevertsFor129Tiers() external {
|
|
110
|
+
DefifaLaunchProjectData memory data = _launchData(129);
|
|
111
|
+
|
|
112
|
+
// H-5 fix: the deployer now caps tiers at 128, so launching with 129 reverts.
|
|
113
|
+
vm.expectRevert(DefifaDeployer.DefifaDeployer_InvalidGameConfiguration.selector);
|
|
114
|
+
deployer.launchGameWith(data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _launchData(uint256 tierCount) internal returns (DefifaLaunchProjectData memory) {
|
|
118
|
+
DefifaTierParams[] memory tiers = new DefifaTierParams[](tierCount);
|
|
119
|
+
for (uint256 i; i < tierCount; i++) {
|
|
120
|
+
tiers[i] = DefifaTierParams({
|
|
121
|
+
name: "TEAM",
|
|
122
|
+
reservedRate: 0,
|
|
123
|
+
reservedTokenBeneficiary: address(0),
|
|
124
|
+
encodedIPFSUri: bytes32(0),
|
|
125
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return DefifaLaunchProjectData({
|
|
130
|
+
name: "DEFIFA",
|
|
131
|
+
projectUri: "",
|
|
132
|
+
contractUri: "",
|
|
133
|
+
baseUri: "",
|
|
134
|
+
tiers: tiers,
|
|
135
|
+
tierPrice: 1 ether,
|
|
136
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
137
|
+
mintPeriodDuration: 1 days,
|
|
138
|
+
refundPeriodDuration: 1 days,
|
|
139
|
+
start: uint48(block.timestamp + 3 days),
|
|
140
|
+
splits: new JBSplit[](0),
|
|
141
|
+
attestationStartTime: 0,
|
|
142
|
+
attestationGracePeriod: 100_381,
|
|
143
|
+
defaultAttestationDelegate: address(0),
|
|
144
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
145
|
+
terminal: jbMultiTerminal(),
|
|
146
|
+
store: new JB721TiersHookStore(),
|
|
147
|
+
minParticipation: 0,
|
|
148
|
+
scorecardTimeout: 7 days,
|
|
149
|
+
timelockDuration: 0
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _mintTier(uint256 gameId, uint16 tierId, uint256 amount) internal {
|
|
154
|
+
address buyer = address(bytes20(keccak256(abi.encodePacked("buyer", tierId))));
|
|
155
|
+
vm.deal(buyer, amount);
|
|
156
|
+
|
|
157
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
158
|
+
tierIds[0] = tierId;
|
|
159
|
+
|
|
160
|
+
bytes[] memory payloads = new bytes[](1);
|
|
161
|
+
payloads[0] = abi.encode(address(0), tierIds);
|
|
162
|
+
|
|
163
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
164
|
+
ids[0] = metadataHelper().getId("pay", address(hookCodeOrigin));
|
|
165
|
+
|
|
166
|
+
bytes memory metadata = metadataHelper().createMetadata(ids, payloads);
|
|
167
|
+
|
|
168
|
+
vm.prank(buyer);
|
|
169
|
+
jbMultiTerminal().pay{value: amount}(gameId, JBConstants.NATIVE_TOKEN, amount, buyer, 0, "", metadata);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
5
|
+
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
6
|
+
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
7
|
+
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
8
|
+
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
9
|
+
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
10
|
+
|
|
11
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
12
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
13
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
14
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
15
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
16
|
+
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
17
|
+
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
18
|
+
import {JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
19
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
20
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
21
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
22
|
+
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
23
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
24
|
+
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
25
|
+
|
|
26
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
27
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
28
|
+
|
|
29
|
+
/// @notice Tests for H-5 audit fix: tier cap of 128 enforced in launchGameWith().
|
|
30
|
+
contract H5TierCapValidationTest is JBTest, TestBaseWorkflow {
|
|
31
|
+
DefifaDeployer internal deployer;
|
|
32
|
+
DefifaGovernor internal governor;
|
|
33
|
+
DefifaHook internal hookCodeOrigin;
|
|
34
|
+
|
|
35
|
+
function setUp() public virtual override {
|
|
36
|
+
super.setUp();
|
|
37
|
+
|
|
38
|
+
JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
|
|
39
|
+
tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
40
|
+
|
|
41
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
42
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
|
|
43
|
+
|
|
44
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
45
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
46
|
+
mustStartAtOrAfter: 0,
|
|
47
|
+
duration: 10 days,
|
|
48
|
+
weight: 1e18,
|
|
49
|
+
weightCutPercent: 0,
|
|
50
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
51
|
+
metadata: JBRulesetMetadata({
|
|
52
|
+
reservedPercent: 0,
|
|
53
|
+
cashOutTaxRate: 0,
|
|
54
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
55
|
+
pausePay: false,
|
|
56
|
+
pauseCreditTransfers: false,
|
|
57
|
+
allowOwnerMinting: false,
|
|
58
|
+
allowSetCustomToken: false,
|
|
59
|
+
allowTerminalMigration: false,
|
|
60
|
+
allowSetTerminals: false,
|
|
61
|
+
allowSetController: false,
|
|
62
|
+
allowAddAccountingContext: false,
|
|
63
|
+
allowAddPriceFeed: false,
|
|
64
|
+
ownerMustSendPayouts: false,
|
|
65
|
+
holdFees: false,
|
|
66
|
+
useTotalSurplusForCashOuts: false,
|
|
67
|
+
useDataHookForPay: true,
|
|
68
|
+
useDataHookForCashOut: true,
|
|
69
|
+
dataHook: address(0),
|
|
70
|
+
metadata: 0
|
|
71
|
+
}),
|
|
72
|
+
splitGroups: new JBSplitGroup[](0),
|
|
73
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
77
|
+
uint256 protocolFeeProjectId =
|
|
78
|
+
jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
79
|
+
vm.prank(projectOwner);
|
|
80
|
+
address nanaToken =
|
|
81
|
+
address(jbController().deployERC20For(protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
82
|
+
|
|
83
|
+
uint256 defifaProjectId = jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
84
|
+
vm.prank(projectOwner);
|
|
85
|
+
address defifaToken = address(jbController().deployERC20For(defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
86
|
+
|
|
87
|
+
hookCodeOrigin = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
|
|
88
|
+
governor = new DefifaGovernor(jbController(), address(this));
|
|
89
|
+
deployer = new DefifaDeployer(
|
|
90
|
+
address(hookCodeOrigin),
|
|
91
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
92
|
+
governor,
|
|
93
|
+
jbController(),
|
|
94
|
+
new JBAddressRegistry(),
|
|
95
|
+
defifaProjectId,
|
|
96
|
+
protocolFeeProjectId
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
hookCodeOrigin.transferOwnership(address(deployer));
|
|
100
|
+
governor.transferOwnership(address(deployer));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @notice Launching with exactly 128 tiers should succeed (boundary).
|
|
104
|
+
function test_launch128TiersSucceeds() external {
|
|
105
|
+
DefifaLaunchProjectData memory data = _launchData(128);
|
|
106
|
+
uint256 gameId = deployer.launchGameWith(data);
|
|
107
|
+
assertGt(gameId, 0, "game should be created with 128 tiers");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// @notice Launching with 129 tiers must revert with DefifaDeployer_InvalidGameConfiguration.
|
|
111
|
+
function test_launch129TiersReverts() external {
|
|
112
|
+
DefifaLaunchProjectData memory data = _launchData(129);
|
|
113
|
+
vm.expectRevert(DefifaDeployer.DefifaDeployer_InvalidGameConfiguration.selector);
|
|
114
|
+
deployer.launchGameWith(data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// @notice Launching with 1 tier should succeed (minimum valid).
|
|
118
|
+
function test_launch1TierSucceeds() external {
|
|
119
|
+
DefifaLaunchProjectData memory data = _launchData(1);
|
|
120
|
+
uint256 gameId = deployer.launchGameWith(data);
|
|
121
|
+
assertGt(gameId, 0, "game should be created with 1 tier");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// @notice Launching with 0 tiers does not revert at the tier cap check (no lower-bound validation exists).
|
|
125
|
+
/// @dev This documents current behavior: the deployer only enforces the upper cap of 128.
|
|
126
|
+
function test_launch0TiersDoesNotRevertAtTierCap() external {
|
|
127
|
+
DefifaLaunchProjectData memory data = _launchData(0);
|
|
128
|
+
uint256 gameId = deployer.launchGameWith(data);
|
|
129
|
+
assertGt(gameId, 0, "0-tier game created (no lower-bound check)");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// @notice Fuzz: any tier count above 128 reverts, any from 1-128 succeeds.
|
|
133
|
+
function test_fuzz_tierCapBoundary(uint256 tierCount) external {
|
|
134
|
+
tierCount = bound(tierCount, 1, 256);
|
|
135
|
+
DefifaLaunchProjectData memory data = _launchData(tierCount);
|
|
136
|
+
|
|
137
|
+
if (tierCount > 128) {
|
|
138
|
+
vm.expectRevert(DefifaDeployer.DefifaDeployer_InvalidGameConfiguration.selector);
|
|
139
|
+
deployer.launchGameWith(data);
|
|
140
|
+
} else {
|
|
141
|
+
uint256 gameId = deployer.launchGameWith(data);
|
|
142
|
+
assertGt(gameId, 0, "game should be created within tier cap");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Helpers
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function _launchData(uint256 tierCount) internal returns (DefifaLaunchProjectData memory) {
|
|
150
|
+
DefifaTierParams[] memory tiers = new DefifaTierParams[](tierCount);
|
|
151
|
+
for (uint256 i; i < tierCount; i++) {
|
|
152
|
+
tiers[i] = DefifaTierParams({
|
|
153
|
+
name: "TEAM",
|
|
154
|
+
reservedRate: 0,
|
|
155
|
+
reservedTokenBeneficiary: address(0),
|
|
156
|
+
encodedIPFSUri: bytes32(0),
|
|
157
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return DefifaLaunchProjectData({
|
|
162
|
+
name: "DEFIFA",
|
|
163
|
+
projectUri: "",
|
|
164
|
+
contractUri: "",
|
|
165
|
+
baseUri: "",
|
|
166
|
+
tiers: tiers,
|
|
167
|
+
tierPrice: 1 ether,
|
|
168
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
169
|
+
mintPeriodDuration: 1 days,
|
|
170
|
+
refundPeriodDuration: 1 days,
|
|
171
|
+
start: uint48(block.timestamp + 3 days),
|
|
172
|
+
splits: new JBSplit[](0),
|
|
173
|
+
attestationStartTime: 0,
|
|
174
|
+
attestationGracePeriod: 100_381,
|
|
175
|
+
defaultAttestationDelegate: address(0),
|
|
176
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
177
|
+
terminal: jbMultiTerminal(),
|
|
178
|
+
store: new JB721TiersHookStore(),
|
|
179
|
+
minParticipation: 0,
|
|
180
|
+
scorecardTimeout: 7 days,
|
|
181
|
+
timelockDuration: 0
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|