@ballkidz/defifa 0.0.1 → 0.0.2
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/package.json +5 -5
- package/src/DefifaDeployer.sol +1 -0
- package/src/DefifaGovernor.sol +22 -7
- package/src/DefifaHook.sol +0 -6
- package/src/interfaces/IDefifaGovernor.sol +2 -0
- package/test/regression/M35_GracePeriodBypass.t.sol +296 -0
- package/test/regression/M36_FulfillmentBlocksRatification.t.sol +272 -0
- package/.gas-snapshot +0 -2
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaDelegate.json +0 -4867
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaDeployer.json +0 -1719
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaGovernor.json +0 -1535
- package/deployments/defifa-v5/arbitrum_sepolia/DefifaTokenUriResolver.json +0 -295
- package/deployments/defifa-v5/base_sepolia/DefifaDelegate.json +0 -4875
- package/deployments/defifa-v5/base_sepolia/DefifaDeployer.json +0 -1725
- package/deployments/defifa-v5/base_sepolia/DefifaGovernor.json +0 -1543
- package/deployments/defifa-v5/base_sepolia/DefifaTokenUriResolver.json +0 -301
- package/deployments/defifa-v5/optimism_sepolia/DefifaDelegate.json +0 -4875
- package/deployments/defifa-v5/optimism_sepolia/DefifaDeployer.json +0 -1725
- package/deployments/defifa-v5/optimism_sepolia/DefifaGovernor.json +0 -1543
- package/deployments/defifa-v5/optimism_sepolia/DefifaTokenUriResolver.json +0 -301
- package/deployments/defifa-v5/sepolia/DefifaDelegate.json +0 -4875
- package/deployments/defifa-v5/sepolia/DefifaDeployer.json +0 -1725
- package/deployments/defifa-v5/sepolia/DefifaGovernor.json +0 -1543
- package/deployments/defifa-v5/sepolia/DefifaTokenUriResolver.json +0 -301
- package/foundry.lock +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"url": "https://github.com/BallKidz/defifa-collection-deployer"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
17
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
18
|
-
"@bananapus/core-v6": "^0.0.
|
|
19
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
16
|
+
"@bananapus/721-hook-v6": "^0.0.9",
|
|
17
|
+
"@bananapus/address-registry-v6": "^0.0.4",
|
|
18
|
+
"@bananapus/core-v6": "^0.0.9",
|
|
19
|
+
"@bananapus/permission-ids-v6": "^0.0.4",
|
|
20
20
|
"@openzeppelin/contracts": "5.2.0",
|
|
21
21
|
"@prb/math": "^4.1.1",
|
|
22
22
|
"scripty.sol": "^2.1.1"
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -311,6 +311,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
311
311
|
uint48(block.timestamp + launchProjectData.mintPeriodDuration + launchProjectData.refundPeriodDuration);
|
|
312
312
|
}
|
|
313
313
|
// Start minting right away if a start time isn't provided.
|
|
314
|
+
// slither-disable-next-line incorrect-equality
|
|
314
315
|
else if (
|
|
315
316
|
launchProjectData.mintPeriodDuration == 0
|
|
316
317
|
&& launchProjectData.start > block.timestamp + launchProjectData.refundPeriodDuration
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -127,6 +127,10 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
127
127
|
/// @param gameId The ID of the game to get a proposal state of.
|
|
128
128
|
/// @param scorecardId The ID of the proposal to get the state of.
|
|
129
129
|
/// @return The state.
|
|
130
|
+
/// @dev Boundary semantics (inclusive):
|
|
131
|
+
/// - At exactly `attestationsBegin`, the state transitions from PENDING to ACTIVE (attestations are open).
|
|
132
|
+
/// - At exactly `gracePeriodEnds`, the grace period has elapsed and the state transitions from ACTIVE to
|
|
133
|
+
/// SUCCEEDED (if quorum is met) or remains ACTIVE (if not).
|
|
130
134
|
function stateOf(uint256 gameId, uint256 scorecardId) public view virtual override returns (DefifaScorecardState) {
|
|
131
135
|
// Keep a reference to the ratified scorecard ID.
|
|
132
136
|
uint256 _ratifiedScorecardId = ratifiedScorecardIdOf[gameId];
|
|
@@ -147,12 +151,14 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
// If the scorecard has attestations beginning in the future, the state is PENDING.
|
|
150
|
-
|
|
154
|
+
// At exactly `attestationsBegin`, attestations are open so the state is ACTIVE.
|
|
155
|
+
if (_scorecard.attestationsBegin > block.timestamp) {
|
|
151
156
|
return DefifaScorecardState.PENDING;
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
// If the scorecard
|
|
155
|
-
|
|
159
|
+
// If the scorecard's grace period has not yet ended, the state is ACTIVE.
|
|
160
|
+
// At exactly `gracePeriodEnds`, the grace period has elapsed so we fall through to the quorum check.
|
|
161
|
+
if (_scorecard.gracePeriodEnds > block.timestamp) {
|
|
156
162
|
return DefifaScorecardState.ACTIVE;
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -363,8 +369,12 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
363
369
|
uint256 _timeUntilAttestationsBegin =
|
|
364
370
|
block.timestamp > _attestationStartTime ? 0 : _attestationStartTime - block.timestamp;
|
|
365
371
|
|
|
366
|
-
|
|
367
|
-
_scorecard.
|
|
372
|
+
uint48 _attestationsBegin = uint48(block.timestamp + _timeUntilAttestationsBegin);
|
|
373
|
+
_scorecard.attestationsBegin = _attestationsBegin;
|
|
374
|
+
// Grace period extends from when attestations begin, not from submission time.
|
|
375
|
+
// This prevents the grace period from expiring before attestations even start
|
|
376
|
+
// when a scorecard is submitted early.
|
|
377
|
+
_scorecard.gracePeriodEnds = uint48(_attestationsBegin + attestationGracePeriodOf(_gameId));
|
|
368
378
|
|
|
369
379
|
// Keep a reference to the default attestation delegate.
|
|
370
380
|
address _defaultAttestationDelegate = IDefifaHook(_metadata.dataHook).defaultAttestationDelegate();
|
|
@@ -459,8 +469,13 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
459
469
|
// slither-disable-next-line unused-return
|
|
460
470
|
Address.verifyCallResult({success: success, returndata: returndata});
|
|
461
471
|
|
|
462
|
-
// Fulfill any commitments for the game.
|
|
463
|
-
|
|
472
|
+
// Fulfill any commitments for the game. Wrapped in try-catch so that a fulfillment
|
|
473
|
+
// failure (e.g. from sendPayoutsOf reverting) does not permanently block ratification.
|
|
474
|
+
// Fulfillment can be retried separately by calling fulfillCommitmentsOf directly.
|
|
475
|
+
try IDefifaDeployer(controller.PROJECTS().ownerOf(gameId)).fulfillCommitmentsOf(gameId) {}
|
|
476
|
+
catch (bytes memory reason) {
|
|
477
|
+
emit FulfillmentFailed(gameId, reason);
|
|
478
|
+
}
|
|
464
479
|
|
|
465
480
|
emit ScorecardRatified(gameId, scorecardId, msg.sender);
|
|
466
481
|
}
|
package/src/DefifaHook.sol
CHANGED
|
@@ -54,7 +54,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
54
54
|
error DefifaHook_NothingToClaim();
|
|
55
55
|
error DefifaHook_NothingToMint();
|
|
56
56
|
error DefifaHook_WrongCurrency();
|
|
57
|
-
error DefifaHook_NoContest();
|
|
58
57
|
error DefifaHook_Overspending();
|
|
59
58
|
error DefifaHook_CashoutWeightsAlreadySet();
|
|
60
59
|
error DefifaHook_ReservedTokenMintingPaused();
|
|
@@ -610,11 +609,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
610
609
|
// Make sure the cashOut weights haven't already been set.
|
|
611
610
|
if (cashOutWeightIsSet) revert DefifaHook_CashoutWeightsAlreadySet();
|
|
612
611
|
|
|
613
|
-
// Make sure the game is not in no contest.
|
|
614
|
-
if (_gamePhase == DefifaGamePhase.NO_CONTEST) {
|
|
615
|
-
revert DefifaHook_NoContest();
|
|
616
|
-
}
|
|
617
|
-
|
|
618
612
|
// Validate weights and build the array. Reverts on invalid input.
|
|
619
613
|
_tierCashOutWeights =
|
|
620
614
|
DefifaHookLib.validateAndBuildWeights({tierWeights: tierWeights, _store: store, hook: address(this)});
|
|
@@ -24,6 +24,8 @@ interface IDefifaGovernor {
|
|
|
24
24
|
|
|
25
25
|
event ScorecardRatified(uint256 indexed gameId, uint256 indexed scorecardId, address caller);
|
|
26
26
|
|
|
27
|
+
event FulfillmentFailed(uint256 indexed gameId, bytes reason);
|
|
28
|
+
|
|
27
29
|
/// @notice The maximum tier ID that contributes attestation power.
|
|
28
30
|
/// @return The maximum attestation power tier.
|
|
29
31
|
function MAX_ATTESTATION_POWER_TIER() external view returns (uint256);
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
6
|
+
|
|
7
|
+
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
8
|
+
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
9
|
+
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
10
|
+
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
11
|
+
import {DefifaScorecardState} from "../../src/enums/DefifaScorecardState.sol";
|
|
12
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
13
|
+
|
|
14
|
+
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
15
|
+
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
16
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
17
|
+
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
18
|
+
import {
|
|
19
|
+
JB721TiersRulesetMetadataResolver
|
|
20
|
+
} from "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
21
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
22
|
+
|
|
23
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
24
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
25
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
26
|
+
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
27
|
+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|
28
|
+
import {DefifaDelegation} from "../../src/structs/DefifaDelegation.sol";
|
|
29
|
+
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
30
|
+
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
31
|
+
import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
|
|
32
|
+
|
|
33
|
+
/// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
|
|
34
|
+
contract TimestampReader2 {
|
|
35
|
+
function timestamp() external view returns (uint256) {
|
|
36
|
+
return block.timestamp;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// @title M35_GracePeriodBypass
|
|
41
|
+
/// @notice Regression test: grace period should extend from attestation start, not submission time.
|
|
42
|
+
/// When a scorecard is submitted early (before attestationStartTime), the grace period
|
|
43
|
+
/// must not expire before attestations begin.
|
|
44
|
+
contract M35_GracePeriodBypass is JBTest, TestBaseWorkflow {
|
|
45
|
+
using JBRulesetMetadataResolver for JBRuleset;
|
|
46
|
+
|
|
47
|
+
TimestampReader2 private _tsReader = new TimestampReader2();
|
|
48
|
+
|
|
49
|
+
address _protocolFeeProjectTokenAccount;
|
|
50
|
+
address _defifaProjectTokenAccount;
|
|
51
|
+
uint256 _protocolFeeProjectId;
|
|
52
|
+
uint256 _defifaProjectId;
|
|
53
|
+
uint256 _gameId = 3;
|
|
54
|
+
|
|
55
|
+
DefifaDeployer deployer;
|
|
56
|
+
DefifaHook hook;
|
|
57
|
+
DefifaGovernor governor;
|
|
58
|
+
|
|
59
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
60
|
+
|
|
61
|
+
function setUp() public virtual override {
|
|
62
|
+
super.setUp();
|
|
63
|
+
|
|
64
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
65
|
+
_tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
66
|
+
|
|
67
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
68
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
69
|
+
|
|
70
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
71
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
72
|
+
mustStartAtOrAfter: 0,
|
|
73
|
+
duration: 10 days,
|
|
74
|
+
weight: 1e18,
|
|
75
|
+
weightCutPercent: 0,
|
|
76
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
77
|
+
metadata: JBRulesetMetadata({
|
|
78
|
+
reservedPercent: 0,
|
|
79
|
+
cashOutTaxRate: 0,
|
|
80
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
81
|
+
pausePay: false,
|
|
82
|
+
pauseCreditTransfers: false,
|
|
83
|
+
allowOwnerMinting: false,
|
|
84
|
+
allowSetCustomToken: false,
|
|
85
|
+
allowTerminalMigration: false,
|
|
86
|
+
allowSetTerminals: false,
|
|
87
|
+
allowSetController: false,
|
|
88
|
+
allowAddAccountingContext: false,
|
|
89
|
+
allowAddPriceFeed: false,
|
|
90
|
+
ownerMustSendPayouts: false,
|
|
91
|
+
holdFees: false,
|
|
92
|
+
useTotalSurplusForCashOuts: false,
|
|
93
|
+
useDataHookForPay: true,
|
|
94
|
+
useDataHookForCashOut: true,
|
|
95
|
+
dataHook: address(0),
|
|
96
|
+
metadata: 0
|
|
97
|
+
}),
|
|
98
|
+
splitGroups: new JBSplitGroup[](0),
|
|
99
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
_protocolFeeProjectId =
|
|
103
|
+
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
104
|
+
vm.prank(projectOwner);
|
|
105
|
+
_protocolFeeProjectTokenAccount =
|
|
106
|
+
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
107
|
+
|
|
108
|
+
_defifaProjectId =
|
|
109
|
+
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
110
|
+
vm.prank(projectOwner);
|
|
111
|
+
_defifaProjectTokenAccount =
|
|
112
|
+
address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
113
|
+
|
|
114
|
+
hook = new DefifaHook(
|
|
115
|
+
jbDirectory(), IERC20(address(_defifaProjectTokenAccount)), IERC20(_protocolFeeProjectTokenAccount)
|
|
116
|
+
);
|
|
117
|
+
governor = new DefifaGovernor(jbController(), address(this));
|
|
118
|
+
JBAddressRegistry _registry = new JBAddressRegistry();
|
|
119
|
+
DefifaTokenUriResolver _tokenURIResolver = new DefifaTokenUriResolver(ITypeface(address(0)));
|
|
120
|
+
deployer = new DefifaDeployer(
|
|
121
|
+
address(hook),
|
|
122
|
+
_tokenURIResolver,
|
|
123
|
+
governor,
|
|
124
|
+
jbController(),
|
|
125
|
+
_registry,
|
|
126
|
+
_defifaProjectId,
|
|
127
|
+
_protocolFeeProjectId
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
hook.transferOwnership(address(deployer));
|
|
131
|
+
governor.transferOwnership(address(deployer));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// @notice Test that grace period extends from attestation start, not submission time.
|
|
135
|
+
/// @dev With the fix, a scorecard submitted early should have its grace period start after
|
|
136
|
+
/// attestationsBegin, ensuring the grace period doesn't expire before attestations start.
|
|
137
|
+
function test_gracePeriodExtendsFromAttestationStart() public {
|
|
138
|
+
uint8 nTiers = 4;
|
|
139
|
+
address[] memory _users = new address[](nTiers);
|
|
140
|
+
|
|
141
|
+
// Set attestation start time far in the future (e.g. block.timestamp + 10 days)
|
|
142
|
+
// Grace period of 1 day
|
|
143
|
+
uint256 futureAttestationStart = block.timestamp + 10 days;
|
|
144
|
+
uint256 gracePeriod = 1 days;
|
|
145
|
+
|
|
146
|
+
DefifaLaunchProjectData memory defifaData =
|
|
147
|
+
_getBasicLaunchDataWithAttestationTiming(nTiers, futureAttestationStart, gracePeriod);
|
|
148
|
+
(uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = _createProject(defifaData);
|
|
149
|
+
|
|
150
|
+
// Phase 1: Mint
|
|
151
|
+
vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
|
|
152
|
+
for (uint256 i = 0; i < nTiers; i++) {
|
|
153
|
+
_users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
|
|
154
|
+
vm.deal(_users[i], 1 ether);
|
|
155
|
+
|
|
156
|
+
uint16[] memory rawMetadata = new uint16[](1);
|
|
157
|
+
rawMetadata[0] = uint16(i + 1);
|
|
158
|
+
bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
|
|
159
|
+
|
|
160
|
+
vm.prank(_users[i]);
|
|
161
|
+
jbMultiTerminal().pay{value: 1 ether}(
|
|
162
|
+
_projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
DefifaDelegation[] memory delegations = new DefifaDelegation[](1);
|
|
166
|
+
delegations[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
|
|
167
|
+
vm.prank(_users[i]);
|
|
168
|
+
_nft.setTierDelegatesTo(delegations);
|
|
169
|
+
|
|
170
|
+
vm.warp(_tsReader.timestamp() + 1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Warp to scoring phase
|
|
174
|
+
vm.warp(defifaData.start + 1);
|
|
175
|
+
|
|
176
|
+
// Submit scorecard early (attestation start time is still in the future)
|
|
177
|
+
DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
|
|
178
|
+
uint256 weightPerTier = _nft.TOTAL_CASHOUT_WEIGHT() / nTiers;
|
|
179
|
+
uint256 assigned;
|
|
180
|
+
for (uint256 i = 0; i < nTiers; i++) {
|
|
181
|
+
scorecards[i].id = i + 1;
|
|
182
|
+
scorecards[i].cashOutWeight = weightPerTier;
|
|
183
|
+
assigned += weightPerTier;
|
|
184
|
+
}
|
|
185
|
+
if (assigned < _nft.TOTAL_CASHOUT_WEIGHT()) {
|
|
186
|
+
scorecards[0].cashOutWeight += _nft.TOTAL_CASHOUT_WEIGHT() - assigned;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
uint256 submissionTime = _tsReader.timestamp();
|
|
190
|
+
uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
|
|
191
|
+
|
|
192
|
+
// The scorecard should be PENDING (attestations haven't started yet)
|
|
193
|
+
assertEq(
|
|
194
|
+
uint256(_governor.stateOf(_gameId, _proposalId)),
|
|
195
|
+
uint256(DefifaScorecardState.PENDING),
|
|
196
|
+
"Scorecard should be PENDING before attestation start"
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Key assertion: warp past the old grace period end (submissionTime + gracePeriod)
|
|
200
|
+
// but BEFORE attestations begin. The scorecard should still be PENDING, NOT in a post-grace state.
|
|
201
|
+
vm.warp(submissionTime + gracePeriod + 1);
|
|
202
|
+
|
|
203
|
+
// With the fix, the scorecard should still be PENDING because attestationsBegin hasn't arrived yet.
|
|
204
|
+
assertEq(
|
|
205
|
+
uint256(_governor.stateOf(_gameId, _proposalId)),
|
|
206
|
+
uint256(DefifaScorecardState.PENDING),
|
|
207
|
+
"Scorecard should still be PENDING even after old grace period would have ended"
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Now warp to after attestation start (attestation begin + 1)
|
|
211
|
+
vm.warp(futureAttestationStart + 1);
|
|
212
|
+
|
|
213
|
+
// Now the scorecard should be ACTIVE (attestations are open and grace period hasn't ended yet)
|
|
214
|
+
assertEq(
|
|
215
|
+
uint256(_governor.stateOf(_gameId, _proposalId)),
|
|
216
|
+
uint256(DefifaScorecardState.ACTIVE),
|
|
217
|
+
"Scorecard should be ACTIVE after attestation start but before grace period ends"
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Warp to after attestation start + grace period
|
|
221
|
+
vm.warp(futureAttestationStart + gracePeriod + 1);
|
|
222
|
+
|
|
223
|
+
// Now grace period has truly ended, so the state should be ACTIVE (quorum not met)
|
|
224
|
+
// The key here is that it transitioned properly - grace period ran from attestation start
|
|
225
|
+
assertEq(
|
|
226
|
+
uint256(_governor.stateOf(_gameId, _proposalId)),
|
|
227
|
+
uint256(DefifaScorecardState.ACTIVE),
|
|
228
|
+
"Scorecard should be ACTIVE (no quorum) after grace period truly ends"
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ----- Internal helpers ------
|
|
233
|
+
|
|
234
|
+
function _getBasicLaunchDataWithAttestationTiming(
|
|
235
|
+
uint8 nTiers,
|
|
236
|
+
uint256 attestationStartTime,
|
|
237
|
+
uint256 attestationGracePeriod
|
|
238
|
+
)
|
|
239
|
+
internal
|
|
240
|
+
returns (DefifaLaunchProjectData memory)
|
|
241
|
+
{
|
|
242
|
+
DefifaTierParams[] memory tierParams = new DefifaTierParams[](nTiers);
|
|
243
|
+
for (uint256 i = 0; i < nTiers; i++) {
|
|
244
|
+
tierParams[i] = DefifaTierParams({
|
|
245
|
+
reservedRate: 1001,
|
|
246
|
+
reservedTokenBeneficiary: address(0),
|
|
247
|
+
encodedIPFSUri: bytes32(0),
|
|
248
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
249
|
+
name: "DEFIFA"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return DefifaLaunchProjectData({
|
|
254
|
+
name: "DEFIFA",
|
|
255
|
+
projectUri: "",
|
|
256
|
+
contractUri: "",
|
|
257
|
+
baseUri: "",
|
|
258
|
+
tierPrice: 1 ether,
|
|
259
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
260
|
+
mintPeriodDuration: 1 days,
|
|
261
|
+
start: uint48(block.timestamp + 3 days),
|
|
262
|
+
refundPeriodDuration: 1 days,
|
|
263
|
+
store: new JB721TiersHookStore(),
|
|
264
|
+
splits: new JBSplit[](0),
|
|
265
|
+
attestationStartTime: attestationStartTime,
|
|
266
|
+
attestationGracePeriod: attestationGracePeriod,
|
|
267
|
+
defaultAttestationDelegate: address(0),
|
|
268
|
+
tiers: tierParams,
|
|
269
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
270
|
+
terminal: jbMultiTerminal(),
|
|
271
|
+
minParticipation: 0,
|
|
272
|
+
scorecardTimeout: 0
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function _createProject(DefifaLaunchProjectData memory defifaLaunchData)
|
|
277
|
+
internal
|
|
278
|
+
returns (uint256 projectId, DefifaHook nft, DefifaGovernor _governor)
|
|
279
|
+
{
|
|
280
|
+
_governor = governor;
|
|
281
|
+
(projectId) = deployer.launchGameWith(defifaLaunchData);
|
|
282
|
+
JBRuleset memory _fc = jbRulesets().currentOf(projectId);
|
|
283
|
+
if (_fc.dataHook() == address(0)) {
|
|
284
|
+
(_fc,) = jbRulesets().latestQueuedOf(projectId);
|
|
285
|
+
}
|
|
286
|
+
nft = DefifaHook(_fc.dataHook());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function _buildPayMetadata(bytes memory metadata) internal returns (bytes memory) {
|
|
290
|
+
bytes[] memory data = new bytes[](1);
|
|
291
|
+
data[0] = metadata;
|
|
292
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
293
|
+
ids[0] = metadataHelper().getId("pay", address(hook));
|
|
294
|
+
return metadataHelper().createMetadata(ids, data);
|
|
295
|
+
}
|
|
296
|
+
}
|