@ballkidz/defifa 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADMINISTRATION.md +34 -6
- package/ARCHITECTURE.md +54 -102
- package/AUDIT_INSTRUCTIONS.md +96 -504
- package/CHANGELOG.md +26 -0
- package/README.md +61 -207
- package/RISKS.md +19 -2
- package/SKILLS.md +29 -281
- package/STYLE_GUIDE.md +57 -18
- package/USER_JOURNEYS.md +45 -1011
- package/foundry.lock +17 -0
- package/package.json +5 -6
- package/references/operations.md +27 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +5 -3
- package/src/DefifaDeployer.sol +33 -9
- package/src/DefifaGovernor.sol +63 -30
- package/src/DefifaHook.sol +33 -13
- package/src/interfaces/IDefifaDeployer.sol +28 -1
- package/src/interfaces/IDefifaGovernor.sol +26 -0
- package/src/interfaces/IDefifaHook.sol +30 -1
- 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/CHANGE_LOG.md +0 -164
|
@@ -8,6 +8,12 @@ import {DefifaTierCashOutWeight} from "../structs/DefifaTierCashOutWeight.sol";
|
|
|
8
8
|
|
|
9
9
|
/// @notice Manages the ratification of Defifa scorecards through attestation-based governance.
|
|
10
10
|
interface IDefifaGovernor {
|
|
11
|
+
/// @notice Emitted when governance is initialized for a game.
|
|
12
|
+
/// @param gameId The ID of the game.
|
|
13
|
+
/// @param attestationStartTime The timestamp when attestation begins.
|
|
14
|
+
/// @param attestationGracePeriod The grace period after attestation begins.
|
|
15
|
+
/// @param timelockDuration The timelock duration after quorum is met.
|
|
16
|
+
/// @param caller The address that initialized the game.
|
|
11
17
|
event GameInitialized(
|
|
12
18
|
uint256 indexed gameId,
|
|
13
19
|
uint256 attestationStartTime,
|
|
@@ -16,10 +22,25 @@ interface IDefifaGovernor {
|
|
|
16
22
|
address caller
|
|
17
23
|
);
|
|
18
24
|
|
|
25
|
+
/// @notice Emitted when an account attests to a scorecard.
|
|
26
|
+
/// @param gameId The ID of the game.
|
|
27
|
+
/// @param scorecardId The ID of the scorecard being attested to.
|
|
28
|
+
/// @param weight The attestation weight applied.
|
|
29
|
+
/// @param caller The address that submitted the attestation.
|
|
19
30
|
event ScorecardAttested(uint256 indexed gameId, uint256 indexed scorecardId, uint256 weight, address caller);
|
|
20
31
|
|
|
32
|
+
/// @notice Emitted when a scorecard is ratified.
|
|
33
|
+
/// @param gameId The ID of the game.
|
|
34
|
+
/// @param scorecardId The ID of the ratified scorecard.
|
|
35
|
+
/// @param caller The address that ratified the scorecard.
|
|
21
36
|
event ScorecardRatified(uint256 indexed gameId, uint256 indexed scorecardId, address caller);
|
|
22
37
|
|
|
38
|
+
/// @notice Emitted when a scorecard is submitted for attestation.
|
|
39
|
+
/// @param gameId The ID of the game.
|
|
40
|
+
/// @param scorecardId The ID of the submitted scorecard.
|
|
41
|
+
/// @param tierWeights The proposed tier cash out weights.
|
|
42
|
+
/// @param isDefaultAttestationDelegate Whether the submitter is the default attestation delegate.
|
|
43
|
+
/// @param caller The address that submitted the scorecard.
|
|
23
44
|
event ScorecardSubmitted(
|
|
24
45
|
uint256 indexed gameId,
|
|
25
46
|
uint256 indexed scorecardId,
|
|
@@ -28,6 +49,11 @@ interface IDefifaGovernor {
|
|
|
28
49
|
address caller
|
|
29
50
|
);
|
|
30
51
|
|
|
52
|
+
/// @notice Emitted when an attestation is revoked from a scorecard.
|
|
53
|
+
/// @param gameId The ID of the game.
|
|
54
|
+
/// @param scorecardId The ID of the scorecard.
|
|
55
|
+
/// @param account The address whose attestation was revoked.
|
|
56
|
+
/// @param weight The revoked attestation weight.
|
|
31
57
|
event AttestationRevoked(uint256 indexed gameId, uint256 indexed scorecardId, address account, uint256 weight);
|
|
32
58
|
|
|
33
59
|
/// @notice The number of attestations for a scorecard.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {IJB721Hook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Hook.sol";
|
|
5
4
|
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
6
5
|
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
6
|
+
import {IJB721Hook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Hook.sol";
|
|
7
7
|
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
8
8
|
import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
|
|
9
9
|
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
@@ -17,6 +17,12 @@ import {IDefifaGamePotReporter} from "./IDefifaGamePotReporter.sol";
|
|
|
17
17
|
/// @notice The hook interface for Defifa games, extending the 721 hook with game-specific attestation delegation,
|
|
18
18
|
/// scorecard-based cash out weights, and token claiming.
|
|
19
19
|
interface IDefifaHook is IJB721Hook {
|
|
20
|
+
/// @notice Emitted when an NFT is minted from a contribution.
|
|
21
|
+
/// @param tokenId The token ID of the minted NFT.
|
|
22
|
+
/// @param tierId The tier the NFT was minted from.
|
|
23
|
+
/// @param beneficiary The address that received the NFT.
|
|
24
|
+
/// @param totalAmountContributed The total amount contributed in the minting transaction.
|
|
25
|
+
/// @param caller The address that triggered the mint.
|
|
20
26
|
event Mint(
|
|
21
27
|
uint256 indexed tokenId,
|
|
22
28
|
uint256 indexed tierId,
|
|
@@ -25,20 +31,43 @@ interface IDefifaHook is IJB721Hook {
|
|
|
25
31
|
address caller
|
|
26
32
|
);
|
|
27
33
|
|
|
34
|
+
/// @notice Emitted when a reserved token is minted.
|
|
35
|
+
/// @param tokenId The token ID of the minted reserved token.
|
|
36
|
+
/// @param tierId The tier the reserved token was minted from.
|
|
37
|
+
/// @param beneficiary The address that received the reserved token.
|
|
38
|
+
/// @param caller The address that triggered the mint.
|
|
28
39
|
event MintReservedToken(
|
|
29
40
|
uint256 indexed tokenId, uint256 indexed tierId, address indexed beneficiary, address caller
|
|
30
41
|
);
|
|
31
42
|
|
|
43
|
+
/// @notice Emitted when a delegate's attestation balance changes for a tier.
|
|
44
|
+
/// @param delegate The delegate whose attestation balance changed.
|
|
45
|
+
/// @param tierId The tier whose attestation balance changed.
|
|
46
|
+
/// @param previousBalance The prior attestation balance.
|
|
47
|
+
/// @param newBalance The updated attestation balance.
|
|
48
|
+
/// @param caller The address that triggered the change.
|
|
32
49
|
event TierDelegateAttestationsChanged(
|
|
33
50
|
address indexed delegate, uint256 indexed tierId, uint256 previousBalance, uint256 newBalance, address caller
|
|
34
51
|
);
|
|
35
52
|
|
|
53
|
+
/// @notice Emitted when a delegator changes delegates for a tier.
|
|
54
|
+
/// @param delegator The address changing its delegate.
|
|
55
|
+
/// @param fromDelegate The previous delegate.
|
|
56
|
+
/// @param toDelegate The new delegate.
|
|
36
57
|
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
|
|
37
58
|
|
|
59
|
+
/// @notice Emitted when claimable game tokens are claimed.
|
|
60
|
+
/// @param beneficiary The address receiving the claimed tokens.
|
|
61
|
+
/// @param defifaTokenAmount The amount of Defifa tokens claimed.
|
|
62
|
+
/// @param baseProtocolTokenAmount The amount of base protocol tokens claimed.
|
|
63
|
+
/// @param caller The address that triggered the claim.
|
|
38
64
|
event ClaimedTokens(
|
|
39
65
|
address indexed beneficiary, uint256 defifaTokenAmount, uint256 baseProtocolTokenAmount, address caller
|
|
40
66
|
);
|
|
41
67
|
|
|
68
|
+
/// @notice Emitted when tier cash out weights are set.
|
|
69
|
+
/// @param tierWeights The cash out weights that were set for each tier.
|
|
70
|
+
/// @param caller The address that set the tier weights.
|
|
42
71
|
event TierCashOutWeightsSet(DefifaTierCashOutWeight[] tierWeights, address caller);
|
|
43
72
|
|
|
44
73
|
/// @notice The total amount redeemed from this game (refunds not counted).
|
|
@@ -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
|
}
|