@ballkidz/defifa 0.0.18 → 0.0.20
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/foundry.lock +17 -0
- package/package.json +6 -6
- package/script/Deploy.s.sol +5 -3
- package/src/DefifaDeployer.sol +23 -2
- package/src/DefifaGovernor.sol +63 -30
- package/src/DefifaHook.sol +31 -11
- package/src/libraries/DefifaHookLib.sol +8 -0
- package/test/DefifaGovernor.t.sol +55 -32
- package/test/DefifaSecurity.t.sol +1 -3
- package/test/Fork.t.sol +3 -9
- package/test/audit/CodexRegistryMismatch.t.sol +191 -0
- package/test/audit/PendingReserveSnapshotBypass.t.sol +40 -0
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -5
package/foundry.lock
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lib/base64": {
|
|
3
|
+
"branch": {
|
|
4
|
+
"name": "v1.1.0",
|
|
5
|
+
"rev": "dcbf852ba545b3d15de0ac0ef88dce934c090c8e"
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"lib/capsules": {
|
|
9
|
+
"rev": "7432ba95ac69ba4902076660b0dc9a90aeb26706"
|
|
10
|
+
},
|
|
11
|
+
"lib/forge-std": {
|
|
12
|
+
"rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824"
|
|
13
|
+
},
|
|
14
|
+
"lib/typeface": {
|
|
15
|
+
"rev": "edd52e2f7c8adc44e75b207bd279710f9b72f5e9"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"url": "https://github.com/BallKidz/defifa-collection-deployer"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
17
|
-
"@bananapus/
|
|
18
|
-
"@bananapus/core-v6": "^0.0.30",
|
|
16
|
+
"@bananapus/721-hook-v6": "^0.0.31",
|
|
17
|
+
"@bananapus/core-v6": "^0.0.31",
|
|
19
18
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
20
|
-
"@croptop/core-v6": "^0.0.
|
|
19
|
+
"@croptop/core-v6": "^0.0.30",
|
|
21
20
|
"@openzeppelin/contracts": "^5.6.1",
|
|
22
21
|
"@prb/math": "^4.1.1",
|
|
23
|
-
"@rev-net/core-v6": "^0.0.
|
|
22
|
+
"@rev-net/core-v6": "^0.0.28",
|
|
24
23
|
"scripty.sol": "^2.1.1"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
26
|
+
"@bananapus/address-registry-v6": "^0.0.17",
|
|
27
27
|
"@sphinx-labs/plugins": "^0.33.1"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
package/script/Deploy.s.sol
CHANGED
|
@@ -22,9 +22,8 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
22
22
|
/// @notice tracks the deployment of the address registry for the chain we are deploying to.
|
|
23
23
|
AddressRegistryDeployment registry;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
uint256
|
|
27
|
-
uint256 _baseProtocolProjectId = 1;
|
|
25
|
+
uint256 _defifaProjectId;
|
|
26
|
+
uint256 _baseProtocolProjectId;
|
|
28
27
|
|
|
29
28
|
bytes32 _salt = bytes32(keccak256("0.0.2"));
|
|
30
29
|
|
|
@@ -53,6 +52,9 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
53
52
|
)
|
|
54
53
|
);
|
|
55
54
|
|
|
55
|
+
_defifaProjectId = vm.envUint("DEFIFA_PROJECT_ID");
|
|
56
|
+
_baseProtocolProjectId = vm.envUint("BASE_PROTOCOL_PROJECT_ID");
|
|
57
|
+
|
|
56
58
|
defifaToken = IERC20(address(core.tokens.tokenOf(_defifaProjectId)));
|
|
57
59
|
baseProtocolToken = IERC20(address(core.tokens.tokenOf(_baseProtocolProjectId)));
|
|
58
60
|
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -275,6 +275,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
275
275
|
uint256 _defifaProjectId,
|
|
276
276
|
uint256 _baseProtocolProjectId
|
|
277
277
|
) {
|
|
278
|
+
// slither-disable-next-line missing-zero-check
|
|
278
279
|
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
279
280
|
TOKEN_URI_RESOLVER = _tokenUriResolver;
|
|
280
281
|
GOVERNOR = _governor;
|
|
@@ -549,8 +550,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
549
550
|
// Transfer ownership to the specified owner.
|
|
550
551
|
hook.transferOwnership(address(GOVERNOR));
|
|
551
552
|
|
|
552
|
-
//
|
|
553
|
-
|
|
553
|
+
// Register the actual CREATE2 clone address using the same salt and minimal-proxy init code
|
|
554
|
+
// that produced the deployed hook.
|
|
555
|
+
REGISTRY.registerAddress({
|
|
556
|
+
deployer: address(this),
|
|
557
|
+
salt: keccak256(abi.encodePacked(msg.sender, currentNonce)),
|
|
558
|
+
bytecode: _cloneCreationCodeFor(address(HOOK_CODE_ORIGIN))
|
|
559
|
+
});
|
|
554
560
|
|
|
555
561
|
// slither-disable-next-line reentrancy-events
|
|
556
562
|
emit LaunchGame(gameId, hook, GOVERNOR, uriResolver, msg.sender);
|
|
@@ -939,4 +945,19 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
939
945
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
940
946
|
});
|
|
941
947
|
}
|
|
948
|
+
|
|
949
|
+
/// @notice Returns the minimal-proxy init code used to deploy a clone for the provided implementation.
|
|
950
|
+
/// @dev Defifa deploys hooks with `Clones.cloneDeterministic`, which uses `CREATE2`.
|
|
951
|
+
/// The address registry's CREATE2 path must be given the exact init code hash that was used at deployment time,
|
|
952
|
+
/// not the runtime bytecode and not a CREATE nonce. This helper reconstructs the standard EIP-1167 creation code
|
|
953
|
+
/// by inserting the implementation address into OpenZeppelin's minimal-proxy init-code template.
|
|
954
|
+
/// @param implementation The contract address the clone will delegate all calls to.
|
|
955
|
+
/// @return bytecode The full EIP-1167 creation bytecode hashed by CREATE2 to derive the clone address.
|
|
956
|
+
function _cloneCreationCodeFor(address implementation) internal pure returns (bytes memory bytecode) {
|
|
957
|
+
// EIP-1167 minimal proxy init code, mirroring OpenZeppelin's Clones.sol layout:
|
|
958
|
+
// [prefix (20 bytes)] [implementation address (20 bytes)] [suffix (15 bytes)]
|
|
959
|
+
bytecode = abi.encodePacked(
|
|
960
|
+
hex"3d602d80600a3d3981f3363d3d373d3d3d363d73", bytes20(implementation), hex"5af43d82803e903d91602b57fd5bf3"
|
|
961
|
+
);
|
|
962
|
+
}
|
|
942
963
|
}
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -18,6 +18,7 @@ import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
|
|
|
18
18
|
import {DefifaAttestations} from "./structs/DefifaAttestations.sol";
|
|
19
19
|
import {DefifaScorecard} from "./structs/DefifaScorecard.sol";
|
|
20
20
|
import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
|
|
21
|
+
import {DefifaHookLib} from "./libraries/DefifaHookLib.sol";
|
|
21
22
|
|
|
22
23
|
/// @notice Manages the ratification of Defifa scorecards.
|
|
23
24
|
contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
@@ -83,12 +84,20 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
83
84
|
mapping(uint256 => mapping(uint256 => DefifaAttestations)) internal _scorecardAttestationsOf;
|
|
84
85
|
|
|
85
86
|
/// @notice Snapshot of pending reserves per tier at scorecard submission time.
|
|
86
|
-
/// @dev
|
|
87
|
+
/// @dev Used to keep unminted reserve units in the BWA denominator.
|
|
87
88
|
/// @custom:param gameId The ID of the game.
|
|
88
89
|
/// @custom:param scorecardId The ID of the scorecard.
|
|
89
90
|
/// @custom:param tierId The tier ID (1-indexed).
|
|
90
91
|
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _pendingReservesSnapshotOf;
|
|
91
92
|
|
|
93
|
+
/// @notice Snapshot of each tier's minted attestation units at scorecard submission time.
|
|
94
|
+
/// @dev Caps later checkpoint reads so reserve mints after submission can't increase the denominator
|
|
95
|
+
/// before the pending-reserve snapshot is added back in.
|
|
96
|
+
/// @custom:param gameId The ID of the game.
|
|
97
|
+
/// @custom:param scorecardId The ID of the scorecard.
|
|
98
|
+
/// @custom:param tierId The tier ID (1-indexed).
|
|
99
|
+
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _submittedTierAttestationUnitsOf;
|
|
100
|
+
|
|
92
101
|
/// @notice Tier weights per scorecard for BWA computation.
|
|
93
102
|
/// @custom:param gameId The ID of the game.
|
|
94
103
|
/// @custom:param scorecardId The ID of the scorecard.
|
|
@@ -260,15 +269,23 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
272
|
+
// slither-disable-next-line calls-loop
|
|
263
273
|
for (uint256 i; i < numberOfTierWeights; i++) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
) {
|
|
274
|
+
// A nonzero cashout weight is only valid once that tier has live ownership.
|
|
275
|
+
// slither-disable-next-line calls-loop
|
|
276
|
+
uint256 currentTierSupply = IDefifaHook(metadata.dataHook).currentSupplyOfTier(tierWeights[i].id);
|
|
277
|
+
if (tierWeights[i].cashOutWeight > 0 && currentTierSupply == 0) {
|
|
268
278
|
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
269
279
|
}
|
|
270
280
|
}
|
|
271
281
|
|
|
282
|
+
// Run the same structural validation the hook will apply at ratification time so malformed
|
|
283
|
+
// scorecards fail on submission instead of reaching a misleading SUCCEEDED state first.
|
|
284
|
+
// slither-disable-next-line unused-return
|
|
285
|
+
DefifaHookLib.validateAndBuildWeights({
|
|
286
|
+
tierWeights: tierWeights, hookStore: IDefifaHook(metadata.dataHook).store(), hook: metadata.dataHook
|
|
287
|
+
});
|
|
288
|
+
|
|
272
289
|
// Hash the scorecard.
|
|
273
290
|
scorecardId =
|
|
274
291
|
_hashScorecardOf({gameHook: metadata.dataHook, calldataBytes: _buildScorecardCalldataFor(tierWeights)});
|
|
@@ -295,15 +312,25 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
295
312
|
_scorecardTierWeightsOf[gameId][scorecardId][tierWeights[i].id - 1] = tierWeights[i].cashOutWeight;
|
|
296
313
|
}
|
|
297
314
|
|
|
298
|
-
// Snapshot pending reserves
|
|
299
|
-
//
|
|
315
|
+
// Snapshot each tier's pending reserves and minted attestation units at submission time.
|
|
316
|
+
// BWA later reads an account checkpoint at `attestationsBegin - 1`. If reserve mints happen
|
|
317
|
+
// after submission but before that checkpoint, clamp the live total back down to the minted
|
|
318
|
+
// units that existed at submission and only then add the snapshotted pending reserves.
|
|
300
319
|
{
|
|
301
320
|
IJB721TiersHookStore _store = IDefifaHook(metadata.dataHook).store();
|
|
302
321
|
uint256 _numberOfTiers = _store.maxTierIdOf(metadata.dataHook);
|
|
322
|
+
// slither-disable-next-line calls-loop
|
|
303
323
|
for (uint256 i; i < _numberOfTiers; i++) {
|
|
304
324
|
uint256 tierId = i + 1;
|
|
305
|
-
|
|
306
|
-
|
|
325
|
+
// slither-disable-next-line calls-loop
|
|
326
|
+
JB721Tier memory tier = _store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
327
|
+
// slither-disable-next-line calls-loop
|
|
328
|
+
uint256 pendingReserves = _store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
329
|
+
// slither-disable-next-line calls-loop
|
|
330
|
+
uint256 submittedTierAttestationUnits =
|
|
331
|
+
IDefifaHook(metadata.dataHook).currentSupplyOfTier(tierId) * tier.votingUnits;
|
|
332
|
+
_pendingReservesSnapshotOf[gameId][scorecardId][tierId] = pendingReserves;
|
|
333
|
+
_submittedTierAttestationUnitsOf[gameId][scorecardId][tierId] = submittedTierAttestationUnits;
|
|
307
334
|
}
|
|
308
335
|
}
|
|
309
336
|
|
|
@@ -490,16 +517,17 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
490
517
|
// Get a reference to the number of tiers.
|
|
491
518
|
uint256 numberOfTiers = store.maxTierIdOf(metadata.dataHook);
|
|
492
519
|
|
|
493
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
494
520
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
495
521
|
// Tiers are 1-indexed.
|
|
496
522
|
uint256 tierId = i + 1;
|
|
497
523
|
|
|
498
524
|
// Get this account's attestation units within the tier (snapshot at timestamp).
|
|
525
|
+
// slither-disable-next-line calls-loop
|
|
499
526
|
uint256 tierAttestationUnitsForAccount =
|
|
500
527
|
hook.getPastTierAttestationUnitsOf({account: account, tier: tierId, timestamp: timestamp});
|
|
501
528
|
|
|
502
529
|
// Get the total attestation units for this tier (snapshot at timestamp).
|
|
530
|
+
// slither-disable-next-line calls-loop
|
|
503
531
|
uint256 tierTotalAttestationUnits =
|
|
504
532
|
hook.getPastTierTotalAttestationUnitsOf({tier: tierId, timestamp: timestamp});
|
|
505
533
|
|
|
@@ -508,8 +536,10 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
508
536
|
// When the reserve beneficiary later mints, their new NFTs add to the numerator while
|
|
509
537
|
// pending reserves decrease by the same amount — so no one's voting power shifts.
|
|
510
538
|
{
|
|
539
|
+
// slither-disable-next-line calls-loop
|
|
511
540
|
uint256 pendingReserves = store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
512
541
|
if (pendingReserves != 0) {
|
|
542
|
+
// slither-disable-next-line calls-loop
|
|
513
543
|
JB721Tier memory tier =
|
|
514
544
|
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
515
545
|
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
@@ -567,30 +597,33 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
567
597
|
// Cache the total cashout weight denominator from the hook.
|
|
568
598
|
uint256 totalCashOutWeight = hook.TOTAL_CASHOUT_WEIGHT();
|
|
569
599
|
|
|
570
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
571
600
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
572
601
|
// Tiers are 1-indexed.
|
|
573
602
|
uint256 tierId = i + 1;
|
|
574
603
|
|
|
575
604
|
// Get this account's attestation units within the tier (snapshot at timestamp).
|
|
605
|
+
// slither-disable-next-line calls-loop
|
|
576
606
|
uint256 tierAttestationUnitsForAccount =
|
|
577
607
|
hook.getPastTierAttestationUnitsOf({account: account, tier: tierId, timestamp: timestamp});
|
|
578
608
|
|
|
579
609
|
if (tierAttestationUnitsForAccount != 0) {
|
|
580
|
-
//
|
|
610
|
+
// Start from the checkpointed tier total at the requested timestamp.
|
|
611
|
+
// If reserve mints happened after submission, clamp them out before adding the
|
|
612
|
+
// pending-reserve snapshot back in so each reserve unit is counted exactly once.
|
|
613
|
+
// slither-disable-next-line calls-loop
|
|
581
614
|
uint256 tierTotalAttestationUnits =
|
|
582
615
|
hook.getPastTierTotalAttestationUnitsOf({tier: tierId, timestamp: timestamp});
|
|
616
|
+
uint256 submittedTierAttestationUnits = _submittedTierAttestationUnitsOf[gameId][scorecardId][tierId];
|
|
617
|
+
if (tierTotalAttestationUnits > submittedTierAttestationUnits) {
|
|
618
|
+
tierTotalAttestationUnits = submittedTierAttestationUnits;
|
|
619
|
+
}
|
|
583
620
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
JB721Tier memory tier =
|
|
591
|
-
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
592
|
-
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
593
|
-
}
|
|
621
|
+
uint256 pendingReserves = _pendingReservesSnapshotOf[gameId][scorecardId][tierId];
|
|
622
|
+
if (pendingReserves != 0) {
|
|
623
|
+
// slither-disable-next-line calls-loop
|
|
624
|
+
JB721Tier memory tier =
|
|
625
|
+
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
626
|
+
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
594
627
|
}
|
|
595
628
|
|
|
596
629
|
// Raw power for this tier.
|
|
@@ -630,18 +663,18 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
630
663
|
// Keep a reference to the total eligible tier weight.
|
|
631
664
|
uint256 eligibleTierWeights;
|
|
632
665
|
|
|
633
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
634
666
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
635
667
|
uint256 tierId = i + 1;
|
|
636
668
|
|
|
637
669
|
// A tier contributes to quorum if it has circulating tokens OR unminted pending reserves.
|
|
638
|
-
// Pending reserves
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
670
|
+
// Pending reserves still belong economically to the reserve beneficiary, even after the
|
|
671
|
+
// last paid token in the tier is burned during REFUND, so excluding them would let a
|
|
672
|
+
// burner erase another participant's quorum contribution without erasing their claim.
|
|
673
|
+
// slither-disable-next-line calls-loop
|
|
674
|
+
uint256 currentTierSupply = hook.currentSupplyOfTier(tierId);
|
|
675
|
+
// slither-disable-next-line calls-loop
|
|
676
|
+
uint256 pendingReserves = store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
677
|
+
if (currentTierSupply != 0 || pendingReserves != 0) {
|
|
645
678
|
eligibleTierWeights += MAX_ATTESTATION_POWER_TIER;
|
|
646
679
|
}
|
|
647
680
|
}
|
package/src/DefifaHook.sol
CHANGED
|
@@ -59,6 +59,8 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
59
59
|
error DefifaHook_TransfersPaused();
|
|
60
60
|
error DefifaHook_Unauthorized(uint256 tokenId, address owner, address caller);
|
|
61
61
|
|
|
62
|
+
event PricingCurrencySet(uint256 currency, address caller);
|
|
63
|
+
|
|
62
64
|
//*********************************************************************//
|
|
63
65
|
// --------------------- public constant properties ------------------ //
|
|
64
66
|
//*********************************************************************//
|
|
@@ -497,6 +499,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
497
499
|
pricingCurrency = _currency;
|
|
498
500
|
gamePhaseReporter = _gamePhaseReporter;
|
|
499
501
|
gamePotReporter = _gamePotReporter;
|
|
502
|
+
// slither-disable-next-line missing-zero-check
|
|
500
503
|
defaultAttestationDelegate = _defaultAttestationDelegate;
|
|
501
504
|
|
|
502
505
|
// Store the base URI if provided.
|
|
@@ -529,19 +532,22 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
529
532
|
|
|
530
533
|
// Transfer ownership to the initializer.
|
|
531
534
|
_transferOwnership(msg.sender);
|
|
535
|
+
|
|
536
|
+
emit PricingCurrencySet(_currency, msg.sender);
|
|
532
537
|
}
|
|
533
538
|
|
|
534
539
|
/// @notice Mint reserved tokens within the tier for the provided value.
|
|
535
540
|
/// @param tierId The ID of the tier to mint within.
|
|
536
541
|
/// @param count The number of reserved tokens to mint.
|
|
537
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
538
542
|
function mintReservesFor(uint256 tierId, uint256 count) public override {
|
|
539
543
|
// Minting reserves must not be paused.
|
|
544
|
+
// slither-disable-next-line calls-loop
|
|
540
545
|
if (JB721TiersRulesetMetadataResolver.mintPendingReservesPaused(
|
|
541
546
|
(JBRulesetMetadataResolver.metadata(rulesets.currentOf(PROJECT_ID)))
|
|
542
547
|
)) revert DefifaHook_ReservedTokenMintingPaused();
|
|
543
548
|
|
|
544
549
|
// Keep a reference to the reserved token beneficiary.
|
|
550
|
+
// slither-disable-next-line calls-loop
|
|
545
551
|
address reservedTokenBeneficiary = store.reserveBeneficiaryOf({hook: address(this), tierId: tierId});
|
|
546
552
|
|
|
547
553
|
// Get a reference to the old delegate.
|
|
@@ -559,12 +565,14 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
559
565
|
}
|
|
560
566
|
|
|
561
567
|
// Record the minted reserves for the tier.
|
|
568
|
+
// slither-disable-next-line calls-loop
|
|
562
569
|
uint256[] memory tokenIds = store.recordMintReservesFor({tierId: tierId, count: count});
|
|
563
570
|
|
|
564
571
|
// Keep a reference to the token ID being iterated on.
|
|
565
572
|
uint256 tokenId;
|
|
566
573
|
|
|
567
574
|
// Fetch the tier details (needed for votingUnits below).
|
|
575
|
+
// slither-disable-next-line calls-loop
|
|
568
576
|
JB721Tier memory tier = store.tierOf({hook: address(this), id: tierId, includeResolvedUri: false});
|
|
569
577
|
|
|
570
578
|
// Increment _totalMintCost so reserved recipients can claim their share of fee tokens ($DEFIFA/$NANA).
|
|
@@ -579,6 +587,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
579
587
|
tokenId = tokenIds[i];
|
|
580
588
|
|
|
581
589
|
// Mint the token.
|
|
590
|
+
// slither-disable-next-line reentrancy-no-eth
|
|
582
591
|
_mint({to: reservedTokenBeneficiary, tokenId: tokenId});
|
|
583
592
|
|
|
584
593
|
emit MintReservedToken(tokenId, tierId, reservedTokenBeneficiary, msg.sender);
|
|
@@ -589,6 +598,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
589
598
|
}
|
|
590
599
|
|
|
591
600
|
// Transfer the attestation units to the delegate.
|
|
601
|
+
// slither-disable-next-line reentrancy-no-eth
|
|
592
602
|
_transferTierAttestationUnits({
|
|
593
603
|
from: address(0), to: reservedTokenBeneficiary, tierId: tierId, amount: tier.votingUnits * tokenIds.length
|
|
594
604
|
});
|
|
@@ -602,7 +612,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
602
612
|
/// `context.beneficiary`. Part of `IJBCashOutHook`.
|
|
603
613
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
604
614
|
/// @param context The cash out context passed in by the terminal.
|
|
605
|
-
// slither-disable-next-line locked-ether,reentrancy-no-eth
|
|
606
615
|
function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context)
|
|
607
616
|
external
|
|
608
617
|
payable
|
|
@@ -654,6 +663,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
654
663
|
|
|
655
664
|
if (isComplete) {
|
|
656
665
|
unchecked {
|
|
666
|
+
// slither-disable-next-line reentrancy-no-eth,calls-loop
|
|
657
667
|
++tokensRedeemedFrom[store.tierIdOfToken(tokenId)];
|
|
658
668
|
}
|
|
659
669
|
}
|
|
@@ -674,6 +684,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
674
684
|
// Claim the $DEFIFA and $NANA tokens for the user.
|
|
675
685
|
// Include pending reserve mint cost in the denominator so that unminted reserves
|
|
676
686
|
// are accounted for, preventing paid holders from claiming a disproportionate share.
|
|
687
|
+
// slither-disable-next-line reentrancy-events
|
|
677
688
|
beneficiaryReceivedTokens = _claimTokensFor({
|
|
678
689
|
beneficiary: context.holder,
|
|
679
690
|
shareToBeneficiary: cumulativeMintPrice,
|
|
@@ -795,8 +806,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
795
806
|
|
|
796
807
|
for (uint256 i; i < numberOfTiers;) {
|
|
797
808
|
uint256 tierId = i + 1;
|
|
809
|
+
// slither-disable-next-line calls-loop
|
|
798
810
|
uint256 pendingReserves = _store.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
799
811
|
if (pendingReserves != 0) {
|
|
812
|
+
// slither-disable-next-line calls-loop
|
|
800
813
|
JB721Tier memory tier = _store.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
801
814
|
cost += pendingReserves * tier.price;
|
|
802
815
|
}
|
|
@@ -862,6 +875,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
862
875
|
/// @param tierId The ID of the tier to get attestation units for.
|
|
863
876
|
/// @return The attestation units.
|
|
864
877
|
function _getTierAttestationUnits(address account, uint256 tierId) internal view virtual returns (uint256) {
|
|
878
|
+
// slither-disable-next-line calls-loop
|
|
865
879
|
return store.tierVotingUnitsOf({hook: address(this), account: account, tierId: tierId});
|
|
866
880
|
}
|
|
867
881
|
|
|
@@ -994,8 +1008,12 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
994
1008
|
|
|
995
1009
|
// If there's either a new delegate or old delegate, set delegation and transfer units.
|
|
996
1010
|
if (attestationDelegate != address(0) || oldDelegate != address(0)) {
|
|
997
|
-
//
|
|
998
|
-
|
|
1011
|
+
// Delegation is beneficiary-owned state. A third-party payer can fund this mint, but
|
|
1012
|
+
// cannot overwrite the beneficiary's long-lived delegate preference through metadata.
|
|
1013
|
+
if (
|
|
1014
|
+
context.payer == context.beneficiary && attestationDelegate != address(0)
|
|
1015
|
+
&& attestationDelegate != oldDelegate
|
|
1016
|
+
) {
|
|
999
1017
|
_delegateTier({account: context.beneficiary, delegatee: attestationDelegate, tierId: tierId});
|
|
1000
1018
|
}
|
|
1001
1019
|
|
|
@@ -1085,6 +1103,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1085
1103
|
// If transfers are pausable, check if they're paused.
|
|
1086
1104
|
if (tier.flags.transfersPausable) {
|
|
1087
1105
|
// Get a reference to the project's current ruleset.
|
|
1106
|
+
// slither-disable-next-line calls-loop
|
|
1088
1107
|
JBRuleset memory ruleset = rulesets.currentOf(PROJECT_ID);
|
|
1089
1108
|
|
|
1090
1109
|
// If transfers are paused and the NFT isn't being transferred to the zero address, revert.
|
|
@@ -1101,14 +1120,15 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1101
1120
|
if (_firstOwnerOf[tokenId] == address(0)) _firstOwnerOf[tokenId] = from;
|
|
1102
1121
|
}
|
|
1103
1122
|
|
|
1104
|
-
// Record the transfer.
|
|
1105
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
1106
|
-
store.recordTransferForTier({tierId: tier.id, from: from, to: to});
|
|
1107
|
-
|
|
1108
1123
|
// Dont transfer on mint since the delegation will be transferred more efficiently in _processPayment.
|
|
1109
|
-
if (from
|
|
1124
|
+
if (from != address(0)) {
|
|
1125
|
+
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Record the transfer after local delegation state has been finalized.
|
|
1129
|
+
// slither-disable-next-line calls-loop
|
|
1130
|
+
store.recordTransferForTier({tierId: tier.id, from: from, to: to});
|
|
1110
1131
|
|
|
1111
|
-
|
|
1112
|
-
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1132
|
+
return from;
|
|
1113
1133
|
}
|
|
1114
1134
|
}
|
|
@@ -62,6 +62,7 @@ library DefifaHookLib {
|
|
|
62
62
|
lastTierId = tierWeights[i].id;
|
|
63
63
|
|
|
64
64
|
// Get the tier.
|
|
65
|
+
// slither-disable-next-line calls-loop
|
|
65
66
|
tier = hookStore.tierOf({hook: hook, id: tierWeights[i].id, includeResolvedUri: false});
|
|
66
67
|
|
|
67
68
|
// Can't set a cashOut weight for tiers not in category 0.
|
|
@@ -104,9 +105,11 @@ library DefifaHookLib {
|
|
|
104
105
|
returns (uint256)
|
|
105
106
|
{
|
|
106
107
|
// Keep a reference to the token's tier ID.
|
|
108
|
+
// slither-disable-next-line calls-loop
|
|
107
109
|
uint256 tierId = hookStore.tierIdOfToken(tokenId);
|
|
108
110
|
|
|
109
111
|
// Keep a reference to the tier.
|
|
112
|
+
// slither-disable-next-line calls-loop
|
|
110
113
|
JB721Tier memory tier = hookStore.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
111
114
|
|
|
112
115
|
// Get the tier's weight.
|
|
@@ -116,6 +119,7 @@ library DefifaHookLib {
|
|
|
116
119
|
if (weight == 0) return 0;
|
|
117
120
|
|
|
118
121
|
// Get the amount of tokens that have already been burned.
|
|
122
|
+
// slither-disable-next-line calls-loop
|
|
119
123
|
uint256 burnedTokens = hookStore.numberOfBurnedFor({hook: hook, tierId: tierId});
|
|
120
124
|
|
|
121
125
|
// If no tiers were minted, nothing to redeem.
|
|
@@ -129,6 +133,7 @@ library DefifaHookLib {
|
|
|
129
133
|
// could cash out before reserves are minted and extract value that should be diluted across
|
|
130
134
|
// both paid and reserved holders. By counting pending reserves, each token's share of the
|
|
131
135
|
// tier weight is computed against the full eventual supply.
|
|
136
|
+
// slither-disable-next-line calls-loop
|
|
132
137
|
uint256 pendingReserves = hookStore.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
133
138
|
totalTokensForCashoutInTier += pendingReserves;
|
|
134
139
|
|
|
@@ -202,6 +207,7 @@ library DefifaHookLib {
|
|
|
202
207
|
// Calculate the amount paid to mint the tokens that are being burned.
|
|
203
208
|
uint256 cumulativeMintPrice;
|
|
204
209
|
for (uint256 i; i < numberOfTokens; i++) {
|
|
210
|
+
// slither-disable-next-line calls-loop
|
|
205
211
|
cumulativeMintPrice += hookStore.tierOfTokenId({
|
|
206
212
|
hook: hook, tokenId: tokenIds[i], includeResolvedUri: false
|
|
207
213
|
})
|
|
@@ -229,6 +235,7 @@ library DefifaHookLib {
|
|
|
229
235
|
{
|
|
230
236
|
uint256 numberOfTokenIds = tokenIds.length;
|
|
231
237
|
for (uint256 i; i < numberOfTokenIds; i++) {
|
|
238
|
+
// slither-disable-next-line calls-loop
|
|
232
239
|
cumulativeMintPrice += hookStore.tierOfTokenId({
|
|
233
240
|
hook: hook, tokenId: tokenIds[i], includeResolvedUri: false
|
|
234
241
|
})
|
|
@@ -323,6 +330,7 @@ library DefifaHookLib {
|
|
|
323
330
|
}
|
|
324
331
|
if (tierIdsToMint[i] < currentTierId) revert DefifaHook_BadTierOrder();
|
|
325
332
|
currentTierId = tierIdsToMint[i];
|
|
333
|
+
// slither-disable-next-line calls-loop
|
|
326
334
|
attestationUnits =
|
|
327
335
|
hookStore.tierOf({hook: hook, id: currentTierId, includeResolvedUri: false}).votingUnits;
|
|
328
336
|
accumulated = attestationUnits;
|
|
@@ -433,7 +433,7 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
433
433
|
// We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
|
|
434
434
|
for (uint256 i = 0; i < scorecards.length; i++) {
|
|
435
435
|
scorecards[i].id = i + 1;
|
|
436
|
-
scorecards[i].cashOutWeight = i % 2 == 0 ?
|
|
436
|
+
scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
|
|
437
437
|
}
|
|
438
438
|
// Forward time so proposals can be created
|
|
439
439
|
uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
|
|
@@ -1019,7 +1019,7 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
1019
1019
|
// We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
|
|
1020
1020
|
for (uint256 i = 0; i < scorecards.length; i++) {
|
|
1021
1021
|
scorecards[i].id = i + 1;
|
|
1022
|
-
scorecards[i].cashOutWeight = i % 2 == 0 ?
|
|
1022
|
+
scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
vm.expectRevert(abi.encodeWithSignature("DefifaGovernor_UnownedProposedCashoutValue()"));
|
|
@@ -1111,7 +1111,7 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
1111
1111
|
// We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
|
|
1112
1112
|
for (uint256 i = 0; i < scorecards.length; i++) {
|
|
1113
1113
|
scorecards[i].id = i + 1;
|
|
1114
|
-
scorecards[i].cashOutWeight = i % 2 == 0 ?
|
|
1114
|
+
scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
|
|
1115
1115
|
}
|
|
1116
1116
|
// Forward time so proposals can be created
|
|
1117
1117
|
uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
|
|
@@ -1132,80 +1132,103 @@ contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
|
|
|
1132
1132
|
// As a sanity check we let it also run for less than 10 to see if it does not error in that case.
|
|
1133
1133
|
nTiers = uint8(bound(nTiers, 2, 20));
|
|
1134
1134
|
|
|
1135
|
+
// With exact-weight validation, only nTiers == 10 produces weights that sum to TOTAL_CASHOUT_WEIGHT.
|
|
1136
|
+
// Delegate to separate helpers to avoid stack-too-deep.
|
|
1137
|
+
if (nTiers == 10) {
|
|
1138
|
+
_testCashOutWeightExact(nTiers);
|
|
1139
|
+
} else {
|
|
1140
|
+
_testCashOutWeightInvalid(nTiers);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/// @dev nTiers == 10: all weights valid, full flow (submit → vote → ratify).
|
|
1145
|
+
function _testCashOutWeightExact(uint8 nTiers) internal {
|
|
1135
1146
|
address[] memory _users = new address[](nTiers);
|
|
1136
1147
|
DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
|
|
1137
1148
|
(uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
|
|
1138
1149
|
|
|
1139
1150
|
uint256 cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 10;
|
|
1140
1151
|
|
|
1141
|
-
// Phase 1: Mint
|
|
1142
1152
|
vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
|
|
1143
|
-
//deployer.queueNextPhaseOf(_projectId);
|
|
1144
1153
|
for (uint256 i = 0; i < nTiers; i++) {
|
|
1145
|
-
// Generate a new address for each tier
|
|
1146
1154
|
_users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
|
|
1147
|
-
// fund user
|
|
1148
1155
|
vm.deal(_users[i], 1 ether);
|
|
1149
|
-
// Build metadata to buy specific NFT
|
|
1150
1156
|
uint16[] memory rawMetadata = new uint16[](1);
|
|
1151
1157
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1152
|
-
rawMetadata[0] = uint16(i + 1);
|
|
1158
|
+
rawMetadata[0] = uint16(i + 1);
|
|
1153
1159
|
bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
|
|
1154
|
-
// Pay to the project and mint an NFT
|
|
1155
1160
|
vm.prank(_users[i]);
|
|
1156
1161
|
jbMultiTerminal().pay{value: 1 ether}(
|
|
1157
1162
|
_projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
|
|
1158
1163
|
);
|
|
1159
|
-
// Set the delegate as the user themselves
|
|
1160
1164
|
DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
|
|
1161
1165
|
tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
|
|
1162
1166
|
vm.prank(_users[i]);
|
|
1163
1167
|
_nft.setTierDelegatesTo(tiered721SetDelegatesData);
|
|
1164
|
-
// Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
|
|
1165
1168
|
assertEq(
|
|
1166
1169
|
_governor.MAX_ATTESTATION_POWER_TIER(),
|
|
1167
1170
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1168
1171
|
_governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
|
|
1169
1172
|
);
|
|
1170
1173
|
}
|
|
1171
|
-
// Warp to scoring phase (past start time)
|
|
1172
1174
|
vm.warp(defifaData.start + 1);
|
|
1173
1175
|
|
|
1174
|
-
// Generate the scorecards
|
|
1175
1176
|
DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
|
|
1176
|
-
|
|
1177
|
-
// We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
|
|
1178
1177
|
for (uint256 i = 0; i < scorecards.length; i++) {
|
|
1179
1178
|
scorecards[i].id = i + 1;
|
|
1180
1179
|
scorecards[i].cashOutWeight = cashOutWeight;
|
|
1181
1180
|
}
|
|
1182
1181
|
|
|
1183
|
-
// Forward time so proposals can be created
|
|
1184
1182
|
uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
|
|
1185
|
-
// Forward time so voting becomes active
|
|
1186
1183
|
vm.warp(block.timestamp + _governor.attestationStartTimeOf(_gameId));
|
|
1187
|
-
// No voting delay after the initial voting delay has passed in
|
|
1188
|
-
// assertEq(_governor.attestationStartTimeOf(_gameId), 0);
|
|
1189
|
-
// All the users vote
|
|
1190
|
-
// 0 = Against
|
|
1191
|
-
// 1 = For
|
|
1192
|
-
// 2 = Abstain
|
|
1193
1184
|
for (uint256 i = 0; i < _users.length; i++) {
|
|
1194
1185
|
vm.prank(_users[i]);
|
|
1195
1186
|
_governor.attestToScorecardFrom(_gameId, _proposalId);
|
|
1196
1187
|
}
|
|
1197
|
-
|
|
1198
|
-
// Forward the amount of blocks needed to reach the end (and round up)
|
|
1199
1188
|
vm.warp(block.timestamp + _governor.attestationGracePeriodOf(_gameId) + 1);
|
|
1189
|
+
_governor.ratifyScorecardFrom(_gameId, scorecards);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/// @dev nTiers != 10: weights don't sum to TOTAL_CASHOUT_WEIGHT, submitScorecardFor reverts.
|
|
1193
|
+
function _testCashOutWeightInvalid(uint8 nTiers) internal {
|
|
1194
|
+
address[] memory _users = new address[](nTiers);
|
|
1195
|
+
DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
|
|
1196
|
+
(uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
|
|
1200
1197
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1198
|
+
uint256 cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 10;
|
|
1199
|
+
|
|
1200
|
+
vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
|
|
1201
|
+
for (uint256 i = 0; i < nTiers; i++) {
|
|
1202
|
+
_users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
|
|
1203
|
+
vm.deal(_users[i], 1 ether);
|
|
1204
|
+
uint16[] memory rawMetadata = new uint16[](1);
|
|
1205
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1206
|
+
rawMetadata[0] = uint16(i + 1);
|
|
1207
|
+
bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
|
|
1208
|
+
vm.prank(_users[i]);
|
|
1209
|
+
jbMultiTerminal().pay{value: 1 ether}(
|
|
1210
|
+
_projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
|
|
1211
|
+
);
|
|
1212
|
+
DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
|
|
1213
|
+
tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
|
|
1214
|
+
vm.prank(_users[i]);
|
|
1215
|
+
_nft.setTierDelegatesTo(tiered721SetDelegatesData);
|
|
1216
|
+
assertEq(
|
|
1217
|
+
_governor.MAX_ATTESTATION_POWER_TIER(),
|
|
1218
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1219
|
+
_governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
|
|
1220
|
+
);
|
|
1205
1221
|
}
|
|
1222
|
+
vm.warp(defifaData.start + 1);
|
|
1206
1223
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1224
|
+
DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
|
|
1225
|
+
for (uint256 i = 0; i < scorecards.length; i++) {
|
|
1226
|
+
scorecards[i].id = i + 1;
|
|
1227
|
+
scorecards[i].cashOutWeight = cashOutWeight;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
|
|
1231
|
+
_governor.submitScorecardFor(_gameId, scorecards);
|
|
1209
1232
|
}
|
|
1210
1233
|
|
|
1211
1234
|
function getBasicDefifaLaunchData(uint8 nTiers) internal returns (DefifaLaunchProjectData memory) {
|
|
@@ -253,10 +253,8 @@ contract DefifaSecurityTest is JBTest, TestBaseWorkflow {
|
|
|
253
253
|
sc[i].cashOutWeight = (_nft.TOTAL_CASHOUT_WEIGHT() * 30) / 100; // 120% total
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
257
|
-
_attestAllFor(pid);
|
|
258
256
|
vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
|
|
259
|
-
_gov.
|
|
257
|
+
_gov.submitScorecardFor(_gameId, sc);
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
// =========================================================================
|
package/test/Fork.t.sol
CHANGED
|
@@ -339,10 +339,8 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
339
339
|
sc[i].cashOutWeight = (_nft.TOTAL_CASHOUT_WEIGHT() * 30) / 100; // 120% total
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
343
|
-
_attestAllFor(pid);
|
|
344
342
|
vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
|
|
345
|
-
_gov.
|
|
343
|
+
_gov.submitScorecardFor(_gameId, sc);
|
|
346
344
|
}
|
|
347
345
|
|
|
348
346
|
// =========================================================================
|
|
@@ -358,10 +356,8 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
358
356
|
sc[i].cashOutWeight = (_nft.TOTAL_CASHOUT_WEIGHT() * 20) / 100; // 80% total
|
|
359
357
|
}
|
|
360
358
|
|
|
361
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
362
|
-
_attestAllFor(pid);
|
|
363
359
|
vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
|
|
364
|
-
_gov.
|
|
360
|
+
_gov.submitScorecardFor(_gameId, sc);
|
|
365
361
|
}
|
|
366
362
|
|
|
367
363
|
// =========================================================================
|
|
@@ -2010,10 +2006,8 @@ contract DefifaForkTest is JBTest, TestBaseWorkflow {
|
|
|
2010
2006
|
sc[2] = DefifaTierCashOutWeight({id: 2, cashOutWeight: _nft.TOTAL_CASHOUT_WEIGHT() / 4});
|
|
2011
2007
|
sc[3] = DefifaTierCashOutWeight({id: 4, cashOutWeight: _nft.TOTAL_CASHOUT_WEIGHT() / 4});
|
|
2012
2008
|
|
|
2013
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
2014
|
-
_attestAllFor(pid);
|
|
2015
2009
|
vm.expectRevert(DefifaHookLib.DefifaHook_BadTierOrder.selector);
|
|
2016
|
-
_gov.
|
|
2010
|
+
_gov.submitScorecardFor(_gameId, sc);
|
|
2017
2011
|
}
|
|
2018
2012
|
|
|
2019
2013
|
// =========================================================================
|
|
@@ -0,0 +1,191 @@
|
|
|
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 {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
15
|
+
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.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 {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
21
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
22
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
23
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
24
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
25
|
+
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
26
|
+
|
|
27
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
28
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
29
|
+
|
|
30
|
+
contract CodexRegistryMismatchTest is JBTest, TestBaseWorkflow {
|
|
31
|
+
JBAddressRegistry internal registry;
|
|
32
|
+
DefifaDeployer internal deployer;
|
|
33
|
+
|
|
34
|
+
function setUp() public virtual override {
|
|
35
|
+
super.setUp();
|
|
36
|
+
|
|
37
|
+
JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
|
|
38
|
+
tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
39
|
+
|
|
40
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
41
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
|
|
42
|
+
|
|
43
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
44
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
45
|
+
mustStartAtOrAfter: 0,
|
|
46
|
+
duration: 10 days,
|
|
47
|
+
weight: 1e18,
|
|
48
|
+
weightCutPercent: 0,
|
|
49
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
50
|
+
metadata: JBRulesetMetadata({
|
|
51
|
+
reservedPercent: 0,
|
|
52
|
+
cashOutTaxRate: 0,
|
|
53
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
54
|
+
pausePay: false,
|
|
55
|
+
pauseCreditTransfers: false,
|
|
56
|
+
allowOwnerMinting: false,
|
|
57
|
+
allowSetCustomToken: false,
|
|
58
|
+
allowTerminalMigration: false,
|
|
59
|
+
allowSetTerminals: false,
|
|
60
|
+
allowSetController: false,
|
|
61
|
+
allowAddAccountingContext: false,
|
|
62
|
+
allowAddPriceFeed: false,
|
|
63
|
+
ownerMustSendPayouts: false,
|
|
64
|
+
holdFees: false,
|
|
65
|
+
useTotalSurplusForCashOuts: false,
|
|
66
|
+
useDataHookForPay: true,
|
|
67
|
+
useDataHookForCashOut: true,
|
|
68
|
+
dataHook: address(0),
|
|
69
|
+
metadata: 0
|
|
70
|
+
}),
|
|
71
|
+
splitGroups: new JBSplitGroup[](0),
|
|
72
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
76
|
+
uint256 protocolFeeProjectId =
|
|
77
|
+
jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
78
|
+
vm.prank(projectOwner);
|
|
79
|
+
address nanaToken =
|
|
80
|
+
address(jbController().deployERC20For(protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
81
|
+
|
|
82
|
+
uint256 defifaProjectId = jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
|
|
83
|
+
vm.prank(projectOwner);
|
|
84
|
+
address defifaToken = address(jbController().deployERC20For(defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
85
|
+
|
|
86
|
+
DefifaHook hookCodeOrigin = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
|
|
87
|
+
DefifaGovernor governor = new DefifaGovernor(jbController(), address(this));
|
|
88
|
+
registry = new JBAddressRegistry();
|
|
89
|
+
deployer = new DefifaDeployer(
|
|
90
|
+
address(hookCodeOrigin),
|
|
91
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
92
|
+
governor,
|
|
93
|
+
jbController(),
|
|
94
|
+
registry,
|
|
95
|
+
defifaProjectId,
|
|
96
|
+
protocolFeeProjectId
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
hookCodeOrigin.transferOwnership(address(deployer));
|
|
100
|
+
governor.transferOwnership(address(deployer));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function test_launchRegistersActualHookAddressInRegistry() external {
|
|
104
|
+
uint256 projectId = deployer.launchGameWith(_launchData());
|
|
105
|
+
(, JBRulesetMetadata memory metadata,) = jbController().latestQueuedRulesetOf(projectId);
|
|
106
|
+
address actualHook = metadata.dataHook;
|
|
107
|
+
|
|
108
|
+
address expectedCreateAddress = _createAddress(address(deployer), 1);
|
|
109
|
+
|
|
110
|
+
assertNotEq(actualHook, address(0), "queued ruleset should reference the deployed hook");
|
|
111
|
+
assertNotEq(actualHook, expectedCreateAddress, "cloneDeterministic did not use CREATE");
|
|
112
|
+
assertEq(registry.deployerOf(actualHook), address(deployer), "actual hook should be registered");
|
|
113
|
+
assertEq(
|
|
114
|
+
registry.deployerOf(expectedCreateAddress), address(0), "legacy CREATE address should stay unregistered"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function _launchData() internal returns (DefifaLaunchProjectData memory) {
|
|
119
|
+
DefifaTierParams[] memory tiers = new DefifaTierParams[](1);
|
|
120
|
+
tiers[0] = DefifaTierParams({
|
|
121
|
+
name: "Team 1",
|
|
122
|
+
reservedRate: 0,
|
|
123
|
+
reservedTokenBeneficiary: address(0),
|
|
124
|
+
encodedIPFSUri: bytes32(0),
|
|
125
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return DefifaLaunchProjectData({
|
|
129
|
+
name: "DEFIFA",
|
|
130
|
+
projectUri: "",
|
|
131
|
+
contractUri: "",
|
|
132
|
+
baseUri: "",
|
|
133
|
+
tiers: tiers,
|
|
134
|
+
tierPrice: 1 ether,
|
|
135
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
136
|
+
mintPeriodDuration: 1 days,
|
|
137
|
+
refundPeriodDuration: 1 days,
|
|
138
|
+
start: uint48(block.timestamp + 3 days),
|
|
139
|
+
splits: new JBSplit[](0),
|
|
140
|
+
attestationStartTime: 0,
|
|
141
|
+
attestationGracePeriod: 100_381,
|
|
142
|
+
defaultAttestationDelegate: address(0),
|
|
143
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
144
|
+
terminal: jbMultiTerminal(),
|
|
145
|
+
store: new JB721TiersHookStore(),
|
|
146
|
+
minParticipation: 0,
|
|
147
|
+
scorecardTimeout: 0,
|
|
148
|
+
timelockDuration: 0
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function _createAddress(address origin, uint256 nonce) internal pure returns (address addr) {
|
|
153
|
+
bytes memory data;
|
|
154
|
+
if (nonce == 0x00) {
|
|
155
|
+
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, bytes1(0x80));
|
|
156
|
+
} else if (nonce <= 0x7f) {
|
|
157
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
158
|
+
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, uint8(nonce));
|
|
159
|
+
} else if (nonce <= 0xff) {
|
|
160
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
161
|
+
data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), origin, bytes1(0x81), uint8(nonce));
|
|
162
|
+
} else if (nonce <= 0xffff) {
|
|
163
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
164
|
+
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), origin, bytes1(0x82), uint16(nonce));
|
|
165
|
+
} else if (nonce <= 0xffffff) {
|
|
166
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
167
|
+
data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), origin, bytes1(0x83), uint24(nonce));
|
|
168
|
+
} else if (nonce <= 0xffffffff) {
|
|
169
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
170
|
+
data = abi.encodePacked(bytes1(0xda), bytes1(0x94), origin, bytes1(0x84), uint32(nonce));
|
|
171
|
+
} else if (nonce <= 0xffffffffff) {
|
|
172
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
173
|
+
data = abi.encodePacked(bytes1(0xdb), bytes1(0x94), origin, bytes1(0x85), uint40(nonce));
|
|
174
|
+
} else if (nonce <= 0xffffffffffff) {
|
|
175
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
176
|
+
data = abi.encodePacked(bytes1(0xdc), bytes1(0x94), origin, bytes1(0x86), uint48(nonce));
|
|
177
|
+
} else if (nonce <= 0xffffffffffffff) {
|
|
178
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
179
|
+
data = abi.encodePacked(bytes1(0xdd), bytes1(0x94), origin, bytes1(0x87), uint56(nonce));
|
|
180
|
+
} else {
|
|
181
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
182
|
+
data = abi.encodePacked(bytes1(0xde), bytes1(0x94), origin, bytes1(0x88), uint64(nonce));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
bytes32 hash = keccak256(data);
|
|
186
|
+
assembly {
|
|
187
|
+
mstore(0, hash)
|
|
188
|
+
addr := mload(0)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -193,6 +193,45 @@ contract PendingReserveSnapshotBypassTest is JBTest, TestBaseWorkflow {
|
|
|
193
193
|
);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/// @notice Pending reserve mints in the delayed-attestation window must not change BWA power.
|
|
197
|
+
function test_mintingPendingReserveBeforeDelayedAttestationDoesNotChangeBWA() external {
|
|
198
|
+
DefifaLaunchProjectData memory data = _launchData();
|
|
199
|
+
data.attestationStartTime = uint48(block.timestamp + 5 days);
|
|
200
|
+
|
|
201
|
+
(_pid, _nft, _gov) = _launch(data);
|
|
202
|
+
|
|
203
|
+
vm.warp(block.timestamp + 1 days + 1);
|
|
204
|
+
_mint(_player0, 1);
|
|
205
|
+
_mint(_player1, 2);
|
|
206
|
+
_mint(_player2, 3);
|
|
207
|
+
_mint(_player3, 4);
|
|
208
|
+
_delegateSelf(_player0, 1);
|
|
209
|
+
_delegateSelf(_player1, 2);
|
|
210
|
+
_delegateSelf(_player2, 3);
|
|
211
|
+
_delegateSelf(_player3, 4);
|
|
212
|
+
|
|
213
|
+
vm.warp(block.timestamp + 2 days);
|
|
214
|
+
|
|
215
|
+
DefifaTierCashOutWeight[] memory scorecard = _evenScorecard();
|
|
216
|
+
uint256 scorecardId = _gov.submitScorecardFor(_gameId, scorecard);
|
|
217
|
+
uint48 futureSnapshotTime = uint48(_gov.attestationStartTimeOf(_gameId) - 1);
|
|
218
|
+
|
|
219
|
+
uint256 preRaw = _gov.getAttestationWeight(_gameId, _player0, futureSnapshotTime);
|
|
220
|
+
uint256 preBwa = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, futureSnapshotTime);
|
|
221
|
+
|
|
222
|
+
JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](1);
|
|
223
|
+
reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
|
|
224
|
+
_nft.mintReservesFor(reserveConfigs);
|
|
225
|
+
|
|
226
|
+
uint256 postRaw = _gov.getAttestationWeight(_gameId, _player0, futureSnapshotTime);
|
|
227
|
+
uint256 postBwa = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, futureSnapshotTime);
|
|
228
|
+
|
|
229
|
+
assertEq(preRaw, 500_000_000, "future raw snapshot includes the pending reserve exactly once");
|
|
230
|
+
assertEq(preBwa, 375_000_000, "future BWA starts from the reserve-adjusted submission denominator");
|
|
231
|
+
assertEq(postRaw, preRaw, "future raw power stays frozen before attestation begins");
|
|
232
|
+
assertEq(postBwa, preBwa, "reserve mint in delayed window must not change BWA power");
|
|
233
|
+
}
|
|
234
|
+
|
|
196
235
|
function _evenScorecard() internal view returns (DefifaTierCashOutWeight[] memory scorecard) {
|
|
197
236
|
scorecard = new DefifaTierCashOutWeight[](4);
|
|
198
237
|
uint256 totalWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
@@ -259,6 +298,7 @@ contract PendingReserveSnapshotBypassTest is JBTest, TestBaseWorkflow {
|
|
|
259
298
|
function _mint(address user, uint256 tierId) internal {
|
|
260
299
|
vm.deal(user, 1 ether);
|
|
261
300
|
uint16[] memory tiers = new uint16[](1);
|
|
301
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
262
302
|
tiers[0] = uint16(tierId);
|
|
263
303
|
bytes[] memory data = new bytes[](1);
|
|
264
304
|
data[0] = abi.encode(user, tiers);
|
|
@@ -178,8 +178,8 @@ contract AttestationDelegateBeneficiary is JBTest, TestBaseWorkflow {
|
|
|
178
178
|
assertEq(delegate, user, "Default delegate should be self when payer == beneficiary");
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/// @notice
|
|
182
|
-
function
|
|
181
|
+
/// @notice A third-party payer cannot override the beneficiary's delegate.
|
|
182
|
+
function test_explicitDelegateFromThirdPartyDoesNotOverrideBeneficiaryDefault() public {
|
|
183
183
|
address payer = address(bytes20(keccak256("payer2")));
|
|
184
184
|
address beneficiary = address(bytes20(keccak256("beneficiary2")));
|
|
185
185
|
address explicitDelegate = address(bytes20(keccak256("explicitDelegate")));
|
|
@@ -204,10 +204,8 @@ contract AttestationDelegateBeneficiary is JBTest, TestBaseWorkflow {
|
|
|
204
204
|
metadata: metadata
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
// With the fix, delegation is stored on the beneficiary's account, not the payer's.
|
|
208
207
|
address beneficiaryDelegate = _nft.getTierDelegateOf(beneficiary, 1);
|
|
209
|
-
assertEq(beneficiaryDelegate,
|
|
210
|
-
// Payer should have no delegation.
|
|
208
|
+
assertEq(beneficiaryDelegate, beneficiary, "third-party payer cannot overwrite beneficiary delegation");
|
|
211
209
|
address payerDelegate = _nft.getTierDelegateOf(payer, 1);
|
|
212
210
|
assertEq(payerDelegate, address(0), "Payer should have no delegation when payer != beneficiary");
|
|
213
211
|
}
|