@ballkidz/defifa 0.0.1 → 0.0.3
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 +125 -124
- package/src/DefifaGovernor.sol +160 -145
- package/src/DefifaHook.sol +287 -293
- package/src/DefifaTokenUriResolver.sol +30 -30
- 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.3",
|
|
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.10",
|
|
19
|
+
"@bananapus/permission-ids-v6": "^0.0.5",
|
|
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
|
@@ -135,37 +135,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
135
135
|
// ------------------------- external views -------------------------- //
|
|
136
136
|
//*********************************************************************//
|
|
137
137
|
|
|
138
|
-
/// @notice The game times.
|
|
139
|
-
/// @param gameId The ID of the game for which the game times apply.
|
|
140
|
-
/// @return The game's start time, as a unix timestamp.
|
|
141
|
-
/// @return The game's minting period duration, in seconds.
|
|
142
|
-
/// @return The game's refund period duration, in seconds.
|
|
143
|
-
function timesFor(uint256 gameId) external view override returns (uint48, uint24, uint24) {
|
|
144
|
-
DefifaOpsData memory _ops = _opsOf[gameId];
|
|
145
|
-
return (_ops.start, _ops.mintPeriodDuration, _ops.refundPeriodDuration);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/// @notice The token of a game.
|
|
149
|
-
/// @param gameId The ID of the game to get the token of.
|
|
150
|
-
/// @return The game's token.
|
|
151
|
-
function tokenOf(uint256 gameId) external view override returns (address) {
|
|
152
|
-
return _opsOf[gameId].token;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/// @notice The safety mechanism parameters of a game.
|
|
156
|
-
/// @param gameId The ID of the game to get the safety params of.
|
|
157
|
-
/// @return minParticipation The minimum treasury balance for the game to proceed to scoring.
|
|
158
|
-
/// @return scorecardTimeout The maximum time after scoring begins for a scorecard to be ratified.
|
|
159
|
-
function safetyParamsOf(uint256 gameId)
|
|
160
|
-
external
|
|
161
|
-
view
|
|
162
|
-
override
|
|
163
|
-
returns (uint256 minParticipation, uint32 scorecardTimeout)
|
|
164
|
-
{
|
|
165
|
-
DefifaOpsData memory _ops = _opsOf[gameId];
|
|
166
|
-
return (_ops.minParticipation, _ops.scorecardTimeout);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
138
|
/// @notice The current pot the game is being played with.
|
|
170
139
|
/// @param gameId The ID of the game for which the pot applies.
|
|
171
140
|
/// @param includeCommitments A flag indicating if the portion of the pot committed to fulfill preprogrammed
|
|
@@ -214,6 +183,37 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
214
183
|
return _currentRuleset.duration != 0 && _currentRuleset.id == _queuedRuleset.id;
|
|
215
184
|
}
|
|
216
185
|
|
|
186
|
+
/// @notice The safety mechanism parameters of a game.
|
|
187
|
+
/// @param gameId The ID of the game to get the safety params of.
|
|
188
|
+
/// @return minParticipation The minimum treasury balance for the game to proceed to scoring.
|
|
189
|
+
/// @return scorecardTimeout The maximum time after scoring begins for a scorecard to be ratified.
|
|
190
|
+
function safetyParamsOf(uint256 gameId)
|
|
191
|
+
external
|
|
192
|
+
view
|
|
193
|
+
override
|
|
194
|
+
returns (uint256 minParticipation, uint32 scorecardTimeout)
|
|
195
|
+
{
|
|
196
|
+
DefifaOpsData memory _ops = _opsOf[gameId];
|
|
197
|
+
return (_ops.minParticipation, _ops.scorecardTimeout);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// @notice The game times.
|
|
201
|
+
/// @param gameId The ID of the game for which the game times apply.
|
|
202
|
+
/// @return The game's start time, as a unix timestamp.
|
|
203
|
+
/// @return The game's minting period duration, in seconds.
|
|
204
|
+
/// @return The game's refund period duration, in seconds.
|
|
205
|
+
function timesFor(uint256 gameId) external view override returns (uint48, uint24, uint24) {
|
|
206
|
+
DefifaOpsData memory _ops = _opsOf[gameId];
|
|
207
|
+
return (_ops.start, _ops.mintPeriodDuration, _ops.refundPeriodDuration);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// @notice The token of a game.
|
|
211
|
+
/// @param gameId The ID of the game to get the token of.
|
|
212
|
+
/// @return The game's token.
|
|
213
|
+
function tokenOf(uint256 gameId) external view override returns (address) {
|
|
214
|
+
return _opsOf[gameId].token;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
217
|
//*********************************************************************//
|
|
218
218
|
// -------------------------- public views --------------------------- //
|
|
219
219
|
//*********************************************************************//
|
|
@@ -297,6 +297,96 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
297
297
|
// ---------------------- external transactions ---------------------- //
|
|
298
298
|
//*********************************************************************//
|
|
299
299
|
|
|
300
|
+
/// @notice Fulfill split amounts between all splits for a game.
|
|
301
|
+
/// @param gameId The ID of the game to fulfill splits for.
|
|
302
|
+
function fulfillCommitmentsOf(uint256 gameId) external virtual override {
|
|
303
|
+
// Make sure commitments haven't already been fulfilled.
|
|
304
|
+
if (fulfilledCommitmentsOf[gameId] != 0) return;
|
|
305
|
+
|
|
306
|
+
// Get the game's current funding cycle along with its metadata.
|
|
307
|
+
// slither-disable-next-line unused-return
|
|
308
|
+
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
309
|
+
|
|
310
|
+
// Make sure the game's commitments can be fulfilled.
|
|
311
|
+
if (!IDefifaHook(_metadata.dataHook).cashOutWeightIsSet()) {
|
|
312
|
+
revert DefifaDeployer_CantFulfillYet();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Get the game token and the terminal.
|
|
316
|
+
address _token = _opsOf[gameId].token;
|
|
317
|
+
IJBMultiTerminal _terminal =
|
|
318
|
+
IJBMultiTerminal(address(controller.DIRECTORY().primaryTerminalOf({projectId: gameId, token: _token})));
|
|
319
|
+
|
|
320
|
+
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
321
|
+
uint256 _pot = _terminal.STORE().balanceOf({terminal: address(_terminal), projectId: gameId, token: _token});
|
|
322
|
+
// slither-disable-next-line incorrect-equality
|
|
323
|
+
if (_pot == 0) revert DefifaDeployer_NothingToFulfill();
|
|
324
|
+
|
|
325
|
+
// Compute the fee amount based on the total absolute split percent stored at game creation.
|
|
326
|
+
uint256 _feeAmount = mulDiv(_pot, _commitmentPercentOf[gameId], JBConstants.SPLITS_TOTAL_PERCENT);
|
|
327
|
+
|
|
328
|
+
// Store the actual fee amount for accurate currentGamePotOf reporting.
|
|
329
|
+
// Use max(feeAmount, 1) to preserve the reentrancy guard when pot is 0.
|
|
330
|
+
fulfilledCommitmentsOf[gameId] = _feeAmount > 0 ? _feeAmount : 1;
|
|
331
|
+
|
|
332
|
+
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
333
|
+
// slither-disable-next-line unused-return
|
|
334
|
+
_terminal.sendPayoutsOf({
|
|
335
|
+
projectId: gameId,
|
|
336
|
+
token: _token,
|
|
337
|
+
amount: _feeAmount,
|
|
338
|
+
currency: _token == JBConstants.NATIVE_TOKEN ? _metadata.baseCurrency : uint32(uint160(_token)),
|
|
339
|
+
minTokensPaidOut: _feeAmount
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Queue the final ruleset.
|
|
343
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
344
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
345
|
+
mustStartAtOrAfter: 0,
|
|
346
|
+
duration: 0,
|
|
347
|
+
weight: 0,
|
|
348
|
+
weightCutPercent: 0,
|
|
349
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
350
|
+
metadata: JBRulesetMetadata({
|
|
351
|
+
reservedPercent: 0,
|
|
352
|
+
cashOutTaxRate: 0,
|
|
353
|
+
baseCurrency: _metadata.baseCurrency,
|
|
354
|
+
pausePay: true,
|
|
355
|
+
pauseCreditTransfers: false,
|
|
356
|
+
allowOwnerMinting: false,
|
|
357
|
+
allowSetCustomToken: false,
|
|
358
|
+
allowTerminalMigration: false,
|
|
359
|
+
allowSetTerminals: false,
|
|
360
|
+
allowSetController: false,
|
|
361
|
+
allowAddAccountingContext: false,
|
|
362
|
+
allowAddPriceFeed: false,
|
|
363
|
+
// Set this to true so only the deployer can fulfill the commitments.
|
|
364
|
+
ownerMustSendPayouts: true,
|
|
365
|
+
holdFees: false,
|
|
366
|
+
useTotalSurplusForCashOuts: false,
|
|
367
|
+
useDataHookForPay: true,
|
|
368
|
+
useDataHookForCashOut: true,
|
|
369
|
+
dataHook: _metadata.dataHook,
|
|
370
|
+
metadata: uint16(
|
|
371
|
+
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
372
|
+
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
}),
|
|
376
|
+
// No more payouts.
|
|
377
|
+
splitGroups: new JBSplitGroup[](0),
|
|
378
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Update the ruleset to the final one.
|
|
382
|
+
// slither-disable-next-line unused-return
|
|
383
|
+
controller.queueRulesetsOf({
|
|
384
|
+
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
emit FulfilledCommitments({gameId: gameId, pot: _pot, caller: msg.sender});
|
|
388
|
+
}
|
|
389
|
+
|
|
300
390
|
/// @notice Launches a new game owned by this contract with a DefifaHook attached.
|
|
301
391
|
/// @param launchProjectData Data necessary to fulfill the transaction to launch a game.
|
|
302
392
|
/// @return gameId The ID of the newly configured game.
|
|
@@ -311,6 +401,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
311
401
|
uint48(block.timestamp + launchProjectData.mintPeriodDuration + launchProjectData.refundPeriodDuration);
|
|
312
402
|
}
|
|
313
403
|
// Start minting right away if a start time isn't provided.
|
|
404
|
+
// slither-disable-next-line incorrect-equality
|
|
314
405
|
else if (
|
|
315
406
|
launchProjectData.mintPeriodDuration == 0
|
|
316
407
|
&& launchProjectData.start > block.timestamp + launchProjectData.refundPeriodDuration
|
|
@@ -481,94 +572,9 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
481
572
|
emit LaunchGame(gameId, _hook, governor, _uriResolver, msg.sender);
|
|
482
573
|
}
|
|
483
574
|
|
|
484
|
-
/// @notice
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
// Make sure commitments haven't already been fulfilled.
|
|
488
|
-
if (fulfilledCommitmentsOf[gameId] != 0) return;
|
|
489
|
-
|
|
490
|
-
// Get the game's current funding cycle along with its metadata.
|
|
491
|
-
// slither-disable-next-line unused-return
|
|
492
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
493
|
-
|
|
494
|
-
// Make sure the game's commitments can be fulfilled.
|
|
495
|
-
if (!IDefifaHook(_metadata.dataHook).cashOutWeightIsSet()) {
|
|
496
|
-
revert DefifaDeployer_CantFulfillYet();
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Get the game token and the terminal.
|
|
500
|
-
address _token = _opsOf[gameId].token;
|
|
501
|
-
IJBMultiTerminal _terminal =
|
|
502
|
-
IJBMultiTerminal(address(controller.DIRECTORY().primaryTerminalOf({projectId: gameId, token: _token})));
|
|
503
|
-
|
|
504
|
-
// Get the current pot and store it. This also prevents re-entrance since the check above will return early.
|
|
505
|
-
uint256 _pot = _terminal.STORE().balanceOf({terminal: address(_terminal), projectId: gameId, token: _token});
|
|
506
|
-
// slither-disable-next-line incorrect-equality
|
|
507
|
-
if (_pot == 0) revert DefifaDeployer_NothingToFulfill();
|
|
508
|
-
|
|
509
|
-
// Compute the fee amount based on the total absolute split percent stored at game creation.
|
|
510
|
-
uint256 _feeAmount = mulDiv(_pot, _commitmentPercentOf[gameId], JBConstants.SPLITS_TOTAL_PERCENT);
|
|
511
|
-
|
|
512
|
-
// Store the actual fee amount for accurate currentGamePotOf reporting.
|
|
513
|
-
// Use max(feeAmount, 1) to preserve the reentrancy guard when pot is 0.
|
|
514
|
-
fulfilledCommitmentsOf[gameId] = _feeAmount > 0 ? _feeAmount : 1;
|
|
515
|
-
|
|
516
|
-
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
517
|
-
// slither-disable-next-line unused-return
|
|
518
|
-
_terminal.sendPayoutsOf({
|
|
519
|
-
projectId: gameId,
|
|
520
|
-
token: _token,
|
|
521
|
-
amount: _feeAmount,
|
|
522
|
-
currency: _token == JBConstants.NATIVE_TOKEN ? _metadata.baseCurrency : uint32(uint160(_token)),
|
|
523
|
-
minTokensPaidOut: _feeAmount
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Queue the final ruleset.
|
|
527
|
-
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
528
|
-
rulesetConfigs[0] = JBRulesetConfig({
|
|
529
|
-
mustStartAtOrAfter: 0,
|
|
530
|
-
duration: 0,
|
|
531
|
-
weight: 0,
|
|
532
|
-
weightCutPercent: 0,
|
|
533
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
534
|
-
metadata: JBRulesetMetadata({
|
|
535
|
-
reservedPercent: 0,
|
|
536
|
-
cashOutTaxRate: 0,
|
|
537
|
-
baseCurrency: _metadata.baseCurrency,
|
|
538
|
-
pausePay: true,
|
|
539
|
-
pauseCreditTransfers: false,
|
|
540
|
-
allowOwnerMinting: false,
|
|
541
|
-
allowSetCustomToken: false,
|
|
542
|
-
allowTerminalMigration: false,
|
|
543
|
-
allowSetTerminals: false,
|
|
544
|
-
allowSetController: false,
|
|
545
|
-
allowAddAccountingContext: false,
|
|
546
|
-
allowAddPriceFeed: false,
|
|
547
|
-
// Set this to true so only the deployer can fulfill the commitments.
|
|
548
|
-
ownerMustSendPayouts: true,
|
|
549
|
-
holdFees: false,
|
|
550
|
-
useTotalSurplusForCashOuts: false,
|
|
551
|
-
useDataHookForPay: true,
|
|
552
|
-
useDataHookForCashOut: true,
|
|
553
|
-
dataHook: _metadata.dataHook,
|
|
554
|
-
metadata: uint16(
|
|
555
|
-
JB721TiersRulesetMetadataResolver.pack721TiersRulesetMetadata(
|
|
556
|
-
JB721TiersRulesetMetadata({pauseTransfers: false, pauseMintPendingReserves: false})
|
|
557
|
-
)
|
|
558
|
-
)
|
|
559
|
-
}),
|
|
560
|
-
// No more payouts.
|
|
561
|
-
splitGroups: new JBSplitGroup[](0),
|
|
562
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// Update the ruleset to the final one.
|
|
566
|
-
// slither-disable-next-line unused-return
|
|
567
|
-
controller.queueRulesetsOf({
|
|
568
|
-
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
emit FulfilledCommitments({gameId: gameId, pot: _pot, caller: msg.sender});
|
|
575
|
+
/// @notice Allows this contract to receive 721s.
|
|
576
|
+
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
|
|
577
|
+
return IERC721Receiver.onERC721Received.selector;
|
|
572
578
|
}
|
|
573
579
|
|
|
574
580
|
/// @notice Triggers the no-contest refund mechanism for a game.
|
|
@@ -638,11 +644,6 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
638
644
|
emit QueuedNoContest(gameId, msg.sender);
|
|
639
645
|
}
|
|
640
646
|
|
|
641
|
-
/// @notice Allows this contract to receive 721s.
|
|
642
|
-
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
|
|
643
|
-
return IERC721Receiver.onERC721Received.selector;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
647
|
//*********************************************************************//
|
|
647
648
|
// ------------------------ internal functions ----------------------- //
|
|
648
649
|
//*********************************************************************//
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -123,43 +123,12 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
123
123
|
// -------------------------- public views --------------------------- //
|
|
124
124
|
//*********************************************************************//
|
|
125
125
|
|
|
126
|
-
/// @notice The
|
|
127
|
-
/// @param gameId The ID of the game to get
|
|
128
|
-
/// @
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
uint256 _ratifiedScorecardId = ratifiedScorecardIdOf[gameId];
|
|
133
|
-
|
|
134
|
-
// If the game has already ratified a scorecard, return succeeded if the ratified proposal is being checked.
|
|
135
|
-
// Else return defeated.
|
|
136
|
-
if (_ratifiedScorecardId != 0) {
|
|
137
|
-
return _ratifiedScorecardId == scorecardId ? DefifaScorecardState.RATIFIED : DefifaScorecardState.DEFEATED;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Get a reference to the scorecard.
|
|
141
|
-
DefifaScorecard memory _scorecard = _scorecardOf[gameId][scorecardId];
|
|
142
|
-
|
|
143
|
-
// Make sure the proposal is known.
|
|
144
|
-
// slither-disable-next-line incorrect-equality
|
|
145
|
-
if (_scorecard.attestationsBegin == 0) {
|
|
146
|
-
revert DefifaGovernor_UnknownProposal();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// If the scorecard has attestations beginning in the future, the state is PENDING.
|
|
150
|
-
if (_scorecard.attestationsBegin >= block.timestamp) {
|
|
151
|
-
return DefifaScorecardState.PENDING;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// If the scorecard has a grace period expiring in the future, the state is ACTIVE.
|
|
155
|
-
if (_scorecard.gracePeriodEnds >= block.timestamp) {
|
|
156
|
-
return DefifaScorecardState.ACTIVE;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// If quorum has been reached, the state is SUCCEEDED, otherwise it is ACTIVE.
|
|
160
|
-
return quorum(gameId) <= _scorecardAttestationsOf[gameId][scorecardId].count
|
|
161
|
-
? DefifaScorecardState.SUCCEEDED
|
|
162
|
-
: DefifaScorecardState.ACTIVE;
|
|
126
|
+
/// @notice The amount of time that must go by before a scorecard can be ratified.
|
|
127
|
+
/// @param gameId The ID of the game to get the attestation period of.
|
|
128
|
+
/// @return The attestation period in number of blocks.
|
|
129
|
+
function attestationGracePeriodOf(uint256 gameId) public view override returns (uint256) {
|
|
130
|
+
// attestation grace period in bits 48-95 (48 bits).
|
|
131
|
+
return uint256(uint48(_packedScorecardInfoOf[gameId] >> 48));
|
|
163
132
|
}
|
|
164
133
|
|
|
165
134
|
/// @notice The amount of time between a scorecard being submitted and attestations to it being enabled, measured in
|
|
@@ -173,43 +142,6 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
173
142
|
return uint256(uint48(_packedScorecardInfoOf[gameId]));
|
|
174
143
|
}
|
|
175
144
|
|
|
176
|
-
/// @notice The amount of time that must go by before a scorecard can be ratified.
|
|
177
|
-
/// @param gameId The ID of the game to get the attestation period of.
|
|
178
|
-
/// @return The attestation period in number of blocks.
|
|
179
|
-
function attestationGracePeriodOf(uint256 gameId) public view override returns (uint256) {
|
|
180
|
-
// attestation grace period in bits 48-95 (48 bits).
|
|
181
|
-
return uint256(uint48(_packedScorecardInfoOf[gameId] >> 48));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/// @notice The number of attestation units that must have participated in a proposal for it to be ratified.
|
|
185
|
-
/// @dev Each tier with at least one minted token contributes MAX_ATTESTATION_POWER_TIER to the total
|
|
186
|
-
/// eligible weight. Quorum is 50% of this total. Because every tier has equal max attestation power
|
|
187
|
-
/// regardless of supply, each tier's community has equal influence — a tier with 1 token and a tier
|
|
188
|
-
/// with 100 tokens both cap at MAX_ATTESTATION_POWER_TIER when fully attested. This prevents
|
|
189
|
-
/// high-supply tiers from dominating governance, keeping the game fair across all outcomes.
|
|
190
|
-
/// @return The quorum number of attestations.
|
|
191
|
-
function quorum(uint256 gameId) public view override returns (uint256) {
|
|
192
|
-
// Get the game's current funding cycle along with its metadata.
|
|
193
|
-
// slither-disable-next-line unused-return
|
|
194
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
195
|
-
|
|
196
|
-
// Get a reference to the number of tiers.
|
|
197
|
-
uint256 _numberOfTiers = IDefifaHook(_metadata.dataHook).store().maxTierIdOf(_metadata.dataHook);
|
|
198
|
-
|
|
199
|
-
// Keep a reference to the total eligible tier weight.
|
|
200
|
-
uint256 _eligibleTierWeights;
|
|
201
|
-
|
|
202
|
-
for (uint256 _i; _i < _numberOfTiers; _i++) {
|
|
203
|
-
// Each minted tier contributes MAX_ATTESTATION_POWER_TIER to the quorum denominator.
|
|
204
|
-
if (IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_i + 1) != 0) {
|
|
205
|
-
_eligibleTierWeights += MAX_ATTESTATION_POWER_TIER;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Quorum = 50% of all minted tiers' attestation power.
|
|
210
|
-
return _eligibleTierWeights / 2;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
145
|
/// @notice Gets an account's attestation power given a number of tiers to look through.
|
|
214
146
|
/// @dev An account's power per tier = MAX_ATTESTATION_POWER_TIER * (account's units / tier's total units).
|
|
215
147
|
/// This means within a tier, power is proportional to token holdings, but across tiers, each tier's
|
|
@@ -261,6 +193,80 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
261
193
|
}
|
|
262
194
|
}
|
|
263
195
|
|
|
196
|
+
/// @notice The number of attestation units that must have participated in a proposal for it to be ratified.
|
|
197
|
+
/// @dev Each tier with at least one minted token contributes MAX_ATTESTATION_POWER_TIER to the total
|
|
198
|
+
/// eligible weight. Quorum is 50% of this total. Because every tier has equal max attestation power
|
|
199
|
+
/// regardless of supply, each tier's community has equal influence — a tier with 1 token and a tier
|
|
200
|
+
/// with 100 tokens both cap at MAX_ATTESTATION_POWER_TIER when fully attested. This prevents
|
|
201
|
+
/// high-supply tiers from dominating governance, keeping the game fair across all outcomes.
|
|
202
|
+
/// @return The quorum number of attestations.
|
|
203
|
+
function quorum(uint256 gameId) public view override returns (uint256) {
|
|
204
|
+
// Get the game's current funding cycle along with its metadata.
|
|
205
|
+
// slither-disable-next-line unused-return
|
|
206
|
+
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
207
|
+
|
|
208
|
+
// Get a reference to the number of tiers.
|
|
209
|
+
uint256 _numberOfTiers = IDefifaHook(_metadata.dataHook).store().maxTierIdOf(_metadata.dataHook);
|
|
210
|
+
|
|
211
|
+
// Keep a reference to the total eligible tier weight.
|
|
212
|
+
uint256 _eligibleTierWeights;
|
|
213
|
+
|
|
214
|
+
for (uint256 _i; _i < _numberOfTiers; _i++) {
|
|
215
|
+
// Each minted tier contributes MAX_ATTESTATION_POWER_TIER to the quorum denominator.
|
|
216
|
+
if (IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_i + 1) != 0) {
|
|
217
|
+
_eligibleTierWeights += MAX_ATTESTATION_POWER_TIER;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Quorum = 50% of all minted tiers' attestation power.
|
|
222
|
+
return _eligibleTierWeights / 2;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// @notice The state of a proposal.
|
|
226
|
+
/// @param gameId The ID of the game to get a proposal state of.
|
|
227
|
+
/// @param scorecardId The ID of the proposal to get the state of.
|
|
228
|
+
/// @return The state.
|
|
229
|
+
/// @dev Boundary semantics (inclusive):
|
|
230
|
+
/// - At exactly `attestationsBegin`, the state transitions from PENDING to ACTIVE (attestations are open).
|
|
231
|
+
/// - At exactly `gracePeriodEnds`, the grace period has elapsed and the state transitions from ACTIVE to
|
|
232
|
+
/// SUCCEEDED (if quorum is met) or remains ACTIVE (if not).
|
|
233
|
+
function stateOf(uint256 gameId, uint256 scorecardId) public view virtual override returns (DefifaScorecardState) {
|
|
234
|
+
// Keep a reference to the ratified scorecard ID.
|
|
235
|
+
uint256 _ratifiedScorecardId = ratifiedScorecardIdOf[gameId];
|
|
236
|
+
|
|
237
|
+
// If the game has already ratified a scorecard, return succeeded if the ratified proposal is being checked.
|
|
238
|
+
// Else return defeated.
|
|
239
|
+
if (_ratifiedScorecardId != 0) {
|
|
240
|
+
return _ratifiedScorecardId == scorecardId ? DefifaScorecardState.RATIFIED : DefifaScorecardState.DEFEATED;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Get a reference to the scorecard.
|
|
244
|
+
DefifaScorecard memory _scorecard = _scorecardOf[gameId][scorecardId];
|
|
245
|
+
|
|
246
|
+
// Make sure the proposal is known.
|
|
247
|
+
// slither-disable-next-line incorrect-equality
|
|
248
|
+
if (_scorecard.attestationsBegin == 0) {
|
|
249
|
+
revert DefifaGovernor_UnknownProposal();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// If the scorecard has attestations beginning in the future, the state is PENDING.
|
|
253
|
+
// At exactly `attestationsBegin`, attestations are open so the state is ACTIVE.
|
|
254
|
+
if (_scorecard.attestationsBegin > block.timestamp) {
|
|
255
|
+
return DefifaScorecardState.PENDING;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If the scorecard's grace period has not yet ended, the state is ACTIVE.
|
|
259
|
+
// At exactly `gracePeriodEnds`, the grace period has elapsed so we fall through to the quorum check.
|
|
260
|
+
if (_scorecard.gracePeriodEnds > block.timestamp) {
|
|
261
|
+
return DefifaScorecardState.ACTIVE;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// If quorum has been reached, the state is SUCCEEDED, otherwise it is ACTIVE.
|
|
265
|
+
return quorum(gameId) <= _scorecardAttestationsOf[gameId][scorecardId].count
|
|
266
|
+
? DefifaScorecardState.SUCCEEDED
|
|
267
|
+
: DefifaScorecardState.ACTIVE;
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
//*********************************************************************//
|
|
265
271
|
// -------------------------- constructor ---------------------------- //
|
|
266
272
|
//*********************************************************************//
|
|
@@ -310,75 +316,6 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
310
316
|
// ---------------------- external transactions ---------------------- //
|
|
311
317
|
//*********************************************************************//
|
|
312
318
|
|
|
313
|
-
/// @notice Submits a scorecard to be attested to.
|
|
314
|
-
/// @param _tierWeights The weights of each tier in the scorecard.
|
|
315
|
-
/// @return scorecardId The scorecard's ID.
|
|
316
|
-
function submitScorecardFor(
|
|
317
|
-
uint256 _gameId,
|
|
318
|
-
DefifaTierCashOutWeight[] calldata _tierWeights
|
|
319
|
-
)
|
|
320
|
-
external
|
|
321
|
-
override
|
|
322
|
-
returns (uint256 scorecardId)
|
|
323
|
-
{
|
|
324
|
-
// Make sure a proposal hasn't yet been ratified.
|
|
325
|
-
if (ratifiedScorecardIdOf[_gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
326
|
-
|
|
327
|
-
// Make sure the game has been initialized.
|
|
328
|
-
// slither-disable-next-line incorrect-equality
|
|
329
|
-
if (_packedScorecardInfoOf[_gameId] == 0) revert DefifaGovernor_GameNotFound();
|
|
330
|
-
|
|
331
|
-
// Make sure no weight is assigned to an unowned tier.
|
|
332
|
-
uint256 _numberOfTierWeights = _tierWeights.length;
|
|
333
|
-
|
|
334
|
-
// Get the game's current funding cycle along with its metadata.
|
|
335
|
-
// slither-disable-next-line unused-return
|
|
336
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(_gameId);
|
|
337
|
-
|
|
338
|
-
// Make sure the game is in its scoring phase.
|
|
339
|
-
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(_gameId) != DefifaGamePhase.SCORING)
|
|
340
|
-
{
|
|
341
|
-
revert DefifaGovernor_NotAllowed();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
345
|
-
for (uint256 _i; _i < _numberOfTierWeights; _i++) {
|
|
346
|
-
if (
|
|
347
|
-
_tierWeights[_i].cashOutWeight > 0
|
|
348
|
-
&& IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_tierWeights[_i].id) == 0
|
|
349
|
-
) {
|
|
350
|
-
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Hash the scorecard.
|
|
355
|
-
scorecardId =
|
|
356
|
-
_hashScorecardOf({_gameHook: _metadata.dataHook, _calldata: _buildScorecardCalldataFor(_tierWeights)});
|
|
357
|
-
|
|
358
|
-
// Store the scorecard
|
|
359
|
-
DefifaScorecard storage _scorecard = _scorecardOf[_gameId][scorecardId];
|
|
360
|
-
if (_scorecard.attestationsBegin != 0) revert DefifaGovernor_DuplicateScorecard();
|
|
361
|
-
|
|
362
|
-
uint256 _attestationStartTime = attestationStartTimeOf(_gameId);
|
|
363
|
-
uint256 _timeUntilAttestationsBegin =
|
|
364
|
-
block.timestamp > _attestationStartTime ? 0 : _attestationStartTime - block.timestamp;
|
|
365
|
-
|
|
366
|
-
_scorecard.attestationsBegin = uint48(block.timestamp + _timeUntilAttestationsBegin);
|
|
367
|
-
_scorecard.gracePeriodEnds = uint48(block.timestamp + attestationGracePeriodOf(_gameId));
|
|
368
|
-
|
|
369
|
-
// Keep a reference to the default attestation delegate.
|
|
370
|
-
address _defaultAttestationDelegate = IDefifaHook(_metadata.dataHook).defaultAttestationDelegate();
|
|
371
|
-
|
|
372
|
-
// If the scorecard is being sent from the default attestation delegate, store it.
|
|
373
|
-
if (msg.sender == _defaultAttestationDelegate) {
|
|
374
|
-
defaultAttestationDelegateProposalOf[_gameId] = scorecardId;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
emit ScorecardSubmitted(
|
|
378
|
-
_gameId, scorecardId, _tierWeights, msg.sender == _defaultAttestationDelegate, msg.sender
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
319
|
/// @notice Attests to a scorecard.
|
|
383
320
|
/// @param gameId The ID of the game to which the scorecard belongs.
|
|
384
321
|
/// @param scorecardId The scorecard ID.
|
|
@@ -459,12 +396,90 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
459
396
|
// slither-disable-next-line unused-return
|
|
460
397
|
Address.verifyCallResult({success: success, returndata: returndata});
|
|
461
398
|
|
|
462
|
-
// Fulfill any commitments for the game.
|
|
463
|
-
|
|
399
|
+
// Fulfill any commitments for the game. Wrapped in try-catch so that a fulfillment
|
|
400
|
+
// failure (e.g. from sendPayoutsOf reverting) does not permanently block ratification.
|
|
401
|
+
// Fulfillment can be retried separately by calling fulfillCommitmentsOf directly.
|
|
402
|
+
try IDefifaDeployer(controller.PROJECTS().ownerOf(gameId)).fulfillCommitmentsOf(gameId) {}
|
|
403
|
+
catch (bytes memory reason) {
|
|
404
|
+
emit FulfillmentFailed(gameId, reason);
|
|
405
|
+
}
|
|
464
406
|
|
|
465
407
|
emit ScorecardRatified(gameId, scorecardId, msg.sender);
|
|
466
408
|
}
|
|
467
409
|
|
|
410
|
+
/// @notice Submits a scorecard to be attested to.
|
|
411
|
+
/// @param _tierWeights The weights of each tier in the scorecard.
|
|
412
|
+
/// @return scorecardId The scorecard's ID.
|
|
413
|
+
function submitScorecardFor(
|
|
414
|
+
uint256 _gameId,
|
|
415
|
+
DefifaTierCashOutWeight[] calldata _tierWeights
|
|
416
|
+
)
|
|
417
|
+
external
|
|
418
|
+
override
|
|
419
|
+
returns (uint256 scorecardId)
|
|
420
|
+
{
|
|
421
|
+
// Make sure a proposal hasn't yet been ratified.
|
|
422
|
+
if (ratifiedScorecardIdOf[_gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
423
|
+
|
|
424
|
+
// Make sure the game has been initialized.
|
|
425
|
+
// slither-disable-next-line incorrect-equality
|
|
426
|
+
if (_packedScorecardInfoOf[_gameId] == 0) revert DefifaGovernor_GameNotFound();
|
|
427
|
+
|
|
428
|
+
// Make sure no weight is assigned to an unowned tier.
|
|
429
|
+
uint256 _numberOfTierWeights = _tierWeights.length;
|
|
430
|
+
|
|
431
|
+
// Get the game's current funding cycle along with its metadata.
|
|
432
|
+
// slither-disable-next-line unused-return
|
|
433
|
+
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(_gameId);
|
|
434
|
+
|
|
435
|
+
// Make sure the game is in its scoring phase.
|
|
436
|
+
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(_gameId) != DefifaGamePhase.SCORING)
|
|
437
|
+
{
|
|
438
|
+
revert DefifaGovernor_NotAllowed();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
442
|
+
for (uint256 _i; _i < _numberOfTierWeights; _i++) {
|
|
443
|
+
if (
|
|
444
|
+
_tierWeights[_i].cashOutWeight > 0
|
|
445
|
+
&& IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_tierWeights[_i].id) == 0
|
|
446
|
+
) {
|
|
447
|
+
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Hash the scorecard.
|
|
452
|
+
scorecardId =
|
|
453
|
+
_hashScorecardOf({_gameHook: _metadata.dataHook, _calldata: _buildScorecardCalldataFor(_tierWeights)});
|
|
454
|
+
|
|
455
|
+
// Store the scorecard
|
|
456
|
+
DefifaScorecard storage _scorecard = _scorecardOf[_gameId][scorecardId];
|
|
457
|
+
if (_scorecard.attestationsBegin != 0) revert DefifaGovernor_DuplicateScorecard();
|
|
458
|
+
|
|
459
|
+
uint256 _attestationStartTime = attestationStartTimeOf(_gameId);
|
|
460
|
+
uint256 _timeUntilAttestationsBegin =
|
|
461
|
+
block.timestamp > _attestationStartTime ? 0 : _attestationStartTime - block.timestamp;
|
|
462
|
+
|
|
463
|
+
uint48 _attestationsBegin = uint48(block.timestamp + _timeUntilAttestationsBegin);
|
|
464
|
+
_scorecard.attestationsBegin = _attestationsBegin;
|
|
465
|
+
// Grace period extends from when attestations begin, not from submission time.
|
|
466
|
+
// This prevents the grace period from expiring before attestations even start
|
|
467
|
+
// when a scorecard is submitted early.
|
|
468
|
+
_scorecard.gracePeriodEnds = uint48(_attestationsBegin + attestationGracePeriodOf(_gameId));
|
|
469
|
+
|
|
470
|
+
// Keep a reference to the default attestation delegate.
|
|
471
|
+
address _defaultAttestationDelegate = IDefifaHook(_metadata.dataHook).defaultAttestationDelegate();
|
|
472
|
+
|
|
473
|
+
// If the scorecard is being sent from the default attestation delegate, store it.
|
|
474
|
+
if (msg.sender == _defaultAttestationDelegate) {
|
|
475
|
+
defaultAttestationDelegateProposalOf[_gameId] = scorecardId;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
emit ScorecardSubmitted(
|
|
479
|
+
_gameId, scorecardId, _tierWeights, msg.sender == _defaultAttestationDelegate, msg.sender
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
468
483
|
//*********************************************************************//
|
|
469
484
|
// ------------------------ internal functions ----------------------- //
|
|
470
485
|
//*********************************************************************//
|