@ballkidz/defifa 0.0.25 → 0.0.27
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/AUDIT_INSTRUCTIONS.md +6 -2
- package/README.md +11 -2
- package/RISKS.md +3 -1
- package/STYLE_GUIDE.md +14 -11
- package/package.json +31 -14
- package/script/Deploy.s.sol +4 -1
- package/src/DefifaDeployer.sol +79 -47
- package/src/DefifaGovernor.sol +57 -12
- package/src/DefifaHook.sol +83 -26
- package/src/DefifaProjectOwner.sol +4 -3
- package/src/DefifaTokenUriResolver.sol +113 -20
- package/src/enums/DefifaGamePhase.sol +6 -0
- package/src/enums/DefifaScorecardState.sol +4 -0
- package/src/interfaces/IDefifaDeployer.sol +5 -0
- package/src/interfaces/IDefifaGamePhaseReporter.sol +4 -0
- package/src/interfaces/IDefifaGamePotReporter.sol +10 -0
- package/src/interfaces/IDefifaGovernor.sol +4 -0
- package/src/interfaces/IDefifaHook.sol +5 -0
- package/src/interfaces/IDefifaTokenUriResolver.sol +3 -0
- package/src/libraries/DefifaFontImporter.sol +1 -1
- package/src/libraries/DefifaHookLib.sol +9 -10
- package/src/structs/DefifaAttestations.sol +3 -2
- package/src/structs/DefifaDelegation.sol +1 -0
- package/src/structs/DefifaLaunchProjectData.sol +2 -3
- package/src/structs/DefifaOpsData.sol +1 -0
- package/src/structs/DefifaScorecard.sol +2 -0
- package/src/structs/DefifaTierCashOutWeight.sol +3 -1
- package/src/structs/DefifaTierParams.sol +1 -0
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +0 -997
- package/foundry.lock +0 -17
- package/references/operations.md +0 -32
- package/references/runtime.md +0 -43
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -521
- package/test/BWAFunctionComparison.t.sol +0 -1320
- package/test/DefifaAdversarialQuorum.t.sol +0 -617
- package/test/DefifaAuditLowGuards.t.sol +0 -308
- package/test/DefifaFeeAccounting.t.sol +0 -581
- package/test/DefifaGovernanceHardening.t.sol +0 -1315
- package/test/DefifaGovernor.t.sol +0 -1378
- package/test/DefifaHookRegressions.t.sol +0 -415
- package/test/DefifaMintCostInvariant.t.sol +0 -319
- package/test/DefifaNoContest.t.sol +0 -941
- package/test/DefifaSecurity.t.sol +0 -741
- package/test/DefifaUSDC.t.sol +0 -480
- package/test/Fork.t.sol +0 -2388
- package/test/TestAuditGaps.sol +0 -984
- package/test/TestQALastMile.t.sol +0 -514
- package/test/audit/AttestationDoubleCount.t.sol +0 -218
- package/test/audit/CodexNemesisCurrencyMismatchBypass.t.sol +0 -112
- package/test/audit/CodexNemesisNoContestReserveDrain.t.sol +0 -238
- package/test/audit/CodexNemesisOneTierZeroTimeoutLockVerified.t.sol +0 -218
- package/test/audit/CodexNemesisSingleTierTimeoutLock.t.sol +0 -237
- package/test/audit/CodexRegistryMismatch.t.sol +0 -191
- package/test/audit/CodexTierCapMismatch.t.sol +0 -171
- package/test/audit/CurrencyMismatchFix.t.sol +0 -265
- package/test/audit/FixPendingReserveDilution.t.sol +0 -366
- package/test/audit/H5TierCapValidation.t.sol +0 -184
- package/test/audit/PendingReserveDilution.t.sol +0 -298
- package/test/audit/PendingReserveQuorumGrief.t.sol +0 -355
- package/test/audit/PendingReserveSnapshotBypass.t.sol +0 -319
- package/test/regression/AttestationDelegateBeneficiary.t.sol +0 -271
- package/test/regression/FulfillmentBlocksRatification.t.sol +0 -279
- package/test/regression/GracePeriodBypass.t.sol +0 -302
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
5
|
-
|
|
6
|
-
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
7
|
-
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
8
|
-
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
9
|
-
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
10
|
-
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
11
|
-
import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
|
|
12
|
-
|
|
13
|
-
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
14
|
-
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
15
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
16
|
-
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
17
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
18
|
-
import {DefifaDelegation} from "../../src/structs/DefifaDelegation.sol";
|
|
19
|
-
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
20
|
-
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
21
|
-
import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
|
|
22
|
-
import {DefifaScorecardState} from "../../src/enums/DefifaScorecardState.sol";
|
|
23
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
24
|
-
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
25
|
-
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
26
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
27
|
-
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
28
|
-
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
29
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
30
|
-
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
31
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
32
|
-
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
33
|
-
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
34
|
-
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
35
|
-
|
|
36
|
-
/// @notice Verifies that the pending-reserve snapshot fix prevents reserve minting from inflating
|
|
37
|
-
/// attestation power after scorecard submission.
|
|
38
|
-
contract PendingReserveSnapshotBypassTest is JBTest, TestBaseWorkflow {
|
|
39
|
-
using JBRulesetMetadataResolver for JBRuleset;
|
|
40
|
-
|
|
41
|
-
uint256 internal _protocolFeeProjectId;
|
|
42
|
-
uint256 internal _defifaProjectId;
|
|
43
|
-
uint256 internal _gameId = 3;
|
|
44
|
-
|
|
45
|
-
DefifaDeployer internal _deployer;
|
|
46
|
-
DefifaHook internal _hookImpl;
|
|
47
|
-
DefifaGovernor internal _governorImpl;
|
|
48
|
-
|
|
49
|
-
address internal _projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
50
|
-
address internal _reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
|
|
51
|
-
address internal _player0 = address(bytes20(keccak256("player0")));
|
|
52
|
-
address internal _player1 = address(bytes20(keccak256("player1")));
|
|
53
|
-
address internal _player2 = address(bytes20(keccak256("player2")));
|
|
54
|
-
address internal _player3 = address(bytes20(keccak256("player3")));
|
|
55
|
-
|
|
56
|
-
DefifaHook internal _nft;
|
|
57
|
-
DefifaGovernor internal _gov;
|
|
58
|
-
uint256 internal _pid;
|
|
59
|
-
|
|
60
|
-
function setUp() public virtual override {
|
|
61
|
-
super.setUp();
|
|
62
|
-
|
|
63
|
-
JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
|
|
64
|
-
tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
65
|
-
|
|
66
|
-
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
67
|
-
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
|
|
68
|
-
|
|
69
|
-
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
70
|
-
rulesetConfigs[0] = JBRulesetConfig({
|
|
71
|
-
mustStartAtOrAfter: 0,
|
|
72
|
-
duration: 10 days,
|
|
73
|
-
weight: 1e18,
|
|
74
|
-
weightCutPercent: 0,
|
|
75
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
76
|
-
metadata: JBRulesetMetadata({
|
|
77
|
-
reservedPercent: 0,
|
|
78
|
-
cashOutTaxRate: 0,
|
|
79
|
-
baseCurrency: JBCurrencyIds.ETH,
|
|
80
|
-
pausePay: false,
|
|
81
|
-
pauseCreditTransfers: false,
|
|
82
|
-
allowOwnerMinting: false,
|
|
83
|
-
allowSetCustomToken: false,
|
|
84
|
-
allowTerminalMigration: false,
|
|
85
|
-
allowSetTerminals: false,
|
|
86
|
-
allowSetController: false,
|
|
87
|
-
allowAddAccountingContext: false,
|
|
88
|
-
allowAddPriceFeed: false,
|
|
89
|
-
ownerMustSendPayouts: false,
|
|
90
|
-
holdFees: false,
|
|
91
|
-
useTotalSurplusForCashOuts: false,
|
|
92
|
-
useDataHookForPay: true,
|
|
93
|
-
useDataHookForCashOut: true,
|
|
94
|
-
dataHook: address(0),
|
|
95
|
-
metadata: 0
|
|
96
|
-
}),
|
|
97
|
-
splitGroups: new JBSplitGroup[](0),
|
|
98
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
_protocolFeeProjectId =
|
|
102
|
-
jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
103
|
-
vm.prank(_projectOwner);
|
|
104
|
-
address nanaToken =
|
|
105
|
-
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
106
|
-
|
|
107
|
-
_defifaProjectId =
|
|
108
|
-
jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
109
|
-
vm.prank(_projectOwner);
|
|
110
|
-
address defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
111
|
-
|
|
112
|
-
_hookImpl = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
|
|
113
|
-
_governorImpl = new DefifaGovernor(jbController(), address(this));
|
|
114
|
-
_deployer = new DefifaDeployer(
|
|
115
|
-
address(_hookImpl),
|
|
116
|
-
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
117
|
-
_governorImpl,
|
|
118
|
-
jbController(),
|
|
119
|
-
new JBAddressRegistry(),
|
|
120
|
-
_defifaProjectId,
|
|
121
|
-
_protocolFeeProjectId
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
_hookImpl.transferOwnership(address(_deployer));
|
|
125
|
-
_governorImpl.transferOwnership(address(_deployer));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/// @notice Confirms the snapshot fix: minting pending reserves after scorecard submission does NOT
|
|
129
|
-
/// inflate the submitter's BWA attestation weight. The snapshot locks pending-reserve counts at
|
|
130
|
-
/// submission time so that post-submission reserve minting cannot remove the dilution.
|
|
131
|
-
function test_mintingPendingReserveAfterSnapshotInflatesVotingPowerAndFlipsOutcome() external {
|
|
132
|
-
(_pid, _nft, _gov) = _launch(_launchData());
|
|
133
|
-
|
|
134
|
-
vm.warp(block.timestamp + 1 days + 1);
|
|
135
|
-
_mint(_player0, 1);
|
|
136
|
-
_mint(_player1, 2);
|
|
137
|
-
_mint(_player2, 3);
|
|
138
|
-
_mint(_player3, 4);
|
|
139
|
-
_delegateSelf(_player0, 1);
|
|
140
|
-
_delegateSelf(_player1, 2);
|
|
141
|
-
_delegateSelf(_player2, 3);
|
|
142
|
-
_delegateSelf(_player3, 4);
|
|
143
|
-
|
|
144
|
-
assertEq(_nft.store().numberOfPendingReservesFor(address(_nft), 1), 1, "tier 1 starts with pending reserve");
|
|
145
|
-
|
|
146
|
-
vm.warp(block.timestamp + 2 days + 1);
|
|
147
|
-
|
|
148
|
-
DefifaTierCashOutWeight[] memory scorecard = _evenScorecard();
|
|
149
|
-
uint256 scorecardId = _gov.submitScorecardFor(_gameId, scorecard);
|
|
150
|
-
uint48 snapshotTime = uint48(block.timestamp);
|
|
151
|
-
|
|
152
|
-
uint256 preBwa0 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, snapshotTime);
|
|
153
|
-
uint256 preBwa1 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player1, snapshotTime);
|
|
154
|
-
|
|
155
|
-
assertEq(preBwa0, 375_000_000, "pending reserve should dilute tier 1 holder at snapshot");
|
|
156
|
-
assertEq(preBwa1, 750_000_000, "non-reserve tier holder keeps full post-BWA weight");
|
|
157
|
-
assertEq(preBwa0 + preBwa1 + preBwa1, 1_875_000_000, "three attestors start below adjusted quorum");
|
|
158
|
-
|
|
159
|
-
vm.warp(block.timestamp + 1);
|
|
160
|
-
|
|
161
|
-
JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](1);
|
|
162
|
-
reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
|
|
163
|
-
_nft.mintReservesFor(reserveConfigs);
|
|
164
|
-
|
|
165
|
-
uint256 postRaw0 = _gov.getAttestationWeight(_gameId, _player0, snapshotTime);
|
|
166
|
-
uint256 postBwa0 = _gov.getBWAAttestationWeight(_gameId, scorecardId, _player0, snapshotTime);
|
|
167
|
-
|
|
168
|
-
// After fix: getAttestationWeight reads live state (reserves are now minted), so raw weight goes up.
|
|
169
|
-
assertEq(postRaw0, 1_000_000_000, "minting reserves removes pending-reserve dilution in live view");
|
|
170
|
-
|
|
171
|
-
// After fix: getBWAAttestationWeight uses the snapshot, so pending-reserve dilution is preserved.
|
|
172
|
-
// The BWA weight does NOT double -- the snapshot prevents inflation.
|
|
173
|
-
assertEq(postBwa0, 375_000_000, "snapshot prevents reserve minting from inflating BWA power");
|
|
174
|
-
|
|
175
|
-
vm.startPrank(_player0);
|
|
176
|
-
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
177
|
-
vm.stopPrank();
|
|
178
|
-
vm.startPrank(_player1);
|
|
179
|
-
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
180
|
-
vm.stopPrank();
|
|
181
|
-
vm.startPrank(_player2);
|
|
182
|
-
_gov.attestToScorecardFrom(_gameId, scorecardId);
|
|
183
|
-
vm.stopPrank();
|
|
184
|
-
|
|
185
|
-
vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
186
|
-
|
|
187
|
-
// After fix: three attestors cannot reach quorum because the snapshot preserves the dilution.
|
|
188
|
-
// Their combined weight: 375M + 750M + 750M = 1,875M < quorum (2,000M base).
|
|
189
|
-
assertEq(
|
|
190
|
-
uint256(_gov.stateOf(_gameId, scorecardId)),
|
|
191
|
-
uint256(DefifaScorecardState.ACTIVE),
|
|
192
|
-
"reserve mint after snapshot does NOT let three attestors reach quorum"
|
|
193
|
-
);
|
|
194
|
-
}
|
|
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
|
-
|
|
235
|
-
function _evenScorecard() internal view returns (DefifaTierCashOutWeight[] memory scorecard) {
|
|
236
|
-
scorecard = new DefifaTierCashOutWeight[](4);
|
|
237
|
-
uint256 totalWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
238
|
-
uint256 perTier = totalWeight / 4;
|
|
239
|
-
for (uint256 i; i < 4; i++) {
|
|
240
|
-
scorecard[i] = DefifaTierCashOutWeight({id: i + 1, cashOutWeight: perTier});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function _launchData() internal returns (DefifaLaunchProjectData memory data) {
|
|
245
|
-
DefifaTierParams[] memory tiers = new DefifaTierParams[](4);
|
|
246
|
-
tiers[0] = DefifaTierParams({
|
|
247
|
-
reservedRate: 1,
|
|
248
|
-
reservedTokenBeneficiary: _reserveBeneficiary,
|
|
249
|
-
encodedIPFSUri: bytes32(0),
|
|
250
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
251
|
-
name: "TEAM"
|
|
252
|
-
});
|
|
253
|
-
for (uint256 i = 1; i < 4; i++) {
|
|
254
|
-
tiers[i] = DefifaTierParams({
|
|
255
|
-
reservedRate: 1001,
|
|
256
|
-
reservedTokenBeneficiary: address(0),
|
|
257
|
-
encodedIPFSUri: bytes32(0),
|
|
258
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
259
|
-
name: "TEAM"
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
data = DefifaLaunchProjectData({
|
|
264
|
-
name: "DEFIFA",
|
|
265
|
-
projectUri: "",
|
|
266
|
-
contractUri: "",
|
|
267
|
-
baseUri: "",
|
|
268
|
-
tiers: tiers,
|
|
269
|
-
tierPrice: 1 ether,
|
|
270
|
-
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
271
|
-
mintPeriodDuration: 1 days,
|
|
272
|
-
refundPeriodDuration: 1 days,
|
|
273
|
-
start: uint48(block.timestamp + 3 days),
|
|
274
|
-
splits: new JBSplit[](0),
|
|
275
|
-
attestationStartTime: 0,
|
|
276
|
-
attestationGracePeriod: 100_381,
|
|
277
|
-
defaultAttestationDelegate: address(0),
|
|
278
|
-
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
279
|
-
terminal: jbMultiTerminal(),
|
|
280
|
-
store: new JB721TiersHookStore(),
|
|
281
|
-
minParticipation: 0,
|
|
282
|
-
scorecardTimeout: 0,
|
|
283
|
-
timelockDuration: 0
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function _launch(DefifaLaunchProjectData memory data)
|
|
288
|
-
internal
|
|
289
|
-
returns (uint256 projectId, DefifaHook nft, DefifaGovernor gov)
|
|
290
|
-
{
|
|
291
|
-
gov = _governorImpl;
|
|
292
|
-
projectId = _deployer.launchGameWith(data);
|
|
293
|
-
JBRuleset memory ruleset = jbRulesets().currentOf(projectId);
|
|
294
|
-
if (ruleset.dataHook() == address(0)) (ruleset,) = jbRulesets().latestQueuedOf(projectId);
|
|
295
|
-
nft = DefifaHook(ruleset.dataHook());
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function _mint(address user, uint256 tierId) internal {
|
|
299
|
-
vm.deal(user, 1 ether);
|
|
300
|
-
uint16[] memory tiers = new uint16[](1);
|
|
301
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
302
|
-
tiers[0] = uint16(tierId);
|
|
303
|
-
bytes[] memory data = new bytes[](1);
|
|
304
|
-
data[0] = abi.encode(user, tiers);
|
|
305
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
306
|
-
ids[0] = metadataHelper().getId("pay", address(_hookImpl));
|
|
307
|
-
bytes memory metadata = metadataHelper().createMetadata(ids, data);
|
|
308
|
-
|
|
309
|
-
vm.prank(user);
|
|
310
|
-
jbMultiTerminal().pay{value: 1 ether}(_pid, JBConstants.NATIVE_TOKEN, 1 ether, user, 0, "", metadata);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function _delegateSelf(address user, uint256 tierId) internal {
|
|
314
|
-
DefifaDelegation[] memory delegations = new DefifaDelegation[](1);
|
|
315
|
-
delegations[0] = DefifaDelegation({delegatee: user, tierId: tierId});
|
|
316
|
-
vm.prank(user);
|
|
317
|
-
_nft.setTierDelegatesTo(delegations);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
5
|
-
|
|
6
|
-
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
7
|
-
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
8
|
-
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
9
|
-
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
10
|
-
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
11
|
-
|
|
12
|
-
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
13
|
-
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
14
|
-
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
15
|
-
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
16
|
-
|
|
17
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
18
|
-
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
19
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
20
|
-
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
21
|
-
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
22
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
23
|
-
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
24
|
-
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
25
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
26
|
-
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
27
|
-
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
28
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
29
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
30
|
-
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
31
|
-
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
32
|
-
|
|
33
|
-
/// @title AttestationDelegateBeneficiary
|
|
34
|
-
/// @notice Regression test for H-6: when payer != beneficiary and no explicit delegate is set,
|
|
35
|
-
/// attestation delegation should default to the beneficiary (NFT recipient), not the payer.
|
|
36
|
-
contract AttestationDelegateBeneficiary is JBTest, TestBaseWorkflow {
|
|
37
|
-
using JBRulesetMetadataResolver for JBRuleset;
|
|
38
|
-
|
|
39
|
-
uint256 _protocolFeeProjectId;
|
|
40
|
-
uint256 _defifaProjectId;
|
|
41
|
-
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
42
|
-
|
|
43
|
-
DefifaDeployer deployer;
|
|
44
|
-
DefifaHook hook;
|
|
45
|
-
DefifaGovernor governor;
|
|
46
|
-
|
|
47
|
-
function setUp() public virtual override {
|
|
48
|
-
super.setUp();
|
|
49
|
-
|
|
50
|
-
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
51
|
-
_tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
52
|
-
|
|
53
|
-
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
54
|
-
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
55
|
-
|
|
56
|
-
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
57
|
-
rulesetConfigs[0] = JBRulesetConfig({
|
|
58
|
-
mustStartAtOrAfter: 0,
|
|
59
|
-
duration: 10 days,
|
|
60
|
-
weight: 1e18,
|
|
61
|
-
weightCutPercent: 0,
|
|
62
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
63
|
-
metadata: JBRulesetMetadata({
|
|
64
|
-
reservedPercent: 0,
|
|
65
|
-
cashOutTaxRate: 0,
|
|
66
|
-
baseCurrency: JBCurrencyIds.ETH,
|
|
67
|
-
pausePay: false,
|
|
68
|
-
pauseCreditTransfers: false,
|
|
69
|
-
allowOwnerMinting: false,
|
|
70
|
-
allowSetCustomToken: false,
|
|
71
|
-
allowTerminalMigration: false,
|
|
72
|
-
allowSetTerminals: false,
|
|
73
|
-
allowSetController: false,
|
|
74
|
-
allowAddAccountingContext: false,
|
|
75
|
-
allowAddPriceFeed: false,
|
|
76
|
-
ownerMustSendPayouts: false,
|
|
77
|
-
holdFees: false,
|
|
78
|
-
useTotalSurplusForCashOuts: false,
|
|
79
|
-
useDataHookForPay: true,
|
|
80
|
-
useDataHookForCashOut: true,
|
|
81
|
-
dataHook: address(0),
|
|
82
|
-
metadata: 0
|
|
83
|
-
}),
|
|
84
|
-
splitGroups: new JBSplitGroup[](0),
|
|
85
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
_protocolFeeProjectId =
|
|
89
|
-
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
90
|
-
vm.prank(projectOwner);
|
|
91
|
-
address _nanaToken =
|
|
92
|
-
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
93
|
-
|
|
94
|
-
_defifaProjectId =
|
|
95
|
-
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
96
|
-
vm.prank(projectOwner);
|
|
97
|
-
address _defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
98
|
-
|
|
99
|
-
hook = new DefifaHook(jbDirectory(), IERC20(_defifaToken), IERC20(_nanaToken));
|
|
100
|
-
governor = new DefifaGovernor(jbController(), address(this));
|
|
101
|
-
deployer = new DefifaDeployer(
|
|
102
|
-
address(hook),
|
|
103
|
-
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
104
|
-
governor,
|
|
105
|
-
jbController(),
|
|
106
|
-
new JBAddressRegistry(),
|
|
107
|
-
_defifaProjectId,
|
|
108
|
-
_protocolFeeProjectId
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
hook.transferOwnership(address(deployer));
|
|
112
|
-
governor.transferOwnership(address(deployer));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/// @notice H-6: Default attestation delegate should be the beneficiary, not the payer.
|
|
116
|
-
function test_defaultDelegateIsBeneficiaryNotPayer() public {
|
|
117
|
-
address payer = address(bytes20(keccak256("payer")));
|
|
118
|
-
address beneficiary = address(bytes20(keccak256("beneficiary")));
|
|
119
|
-
|
|
120
|
-
// Launch game with no default attestation delegate.
|
|
121
|
-
(uint256 _projectId, DefifaHook _nft) = _launchGame();
|
|
122
|
-
|
|
123
|
-
// Warp to MINT phase.
|
|
124
|
-
vm.warp(_mintPhaseStart);
|
|
125
|
-
|
|
126
|
-
// Payer pays on behalf of beneficiary, no explicit delegate (address(0)).
|
|
127
|
-
vm.deal(payer, 1 ether);
|
|
128
|
-
uint16[] memory tierIds = new uint16[](1);
|
|
129
|
-
tierIds[0] = 1;
|
|
130
|
-
bytes memory payMetadata = abi.encode(address(0), tierIds); // attestationDelegate = address(0)
|
|
131
|
-
bytes memory metadata = _buildPayMetadata(payMetadata);
|
|
132
|
-
|
|
133
|
-
vm.prank(payer);
|
|
134
|
-
jbMultiTerminal().pay{value: 1 ether}({
|
|
135
|
-
projectId: _projectId,
|
|
136
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
137
|
-
amount: 1 ether,
|
|
138
|
-
beneficiary: beneficiary, // NFT goes to beneficiary, NOT payer
|
|
139
|
-
minReturnedTokens: 0,
|
|
140
|
-
memo: "",
|
|
141
|
-
metadata: metadata
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// H-6 fix: delegation should be on the beneficiary's account, not the payer's.
|
|
145
|
-
// The beneficiary's delegate is themselves (default when no explicit delegate is set).
|
|
146
|
-
address beneficiaryDelegate = _nft.getTierDelegateOf(beneficiary, 1);
|
|
147
|
-
assertEq(beneficiaryDelegate, beneficiary, "H-6: default delegate should be beneficiary, not payer");
|
|
148
|
-
// The payer should have no delegation since they didn't receive attestation units.
|
|
149
|
-
address payerDelegate = _nft.getTierDelegateOf(payer, 1);
|
|
150
|
-
assertEq(payerDelegate, address(0), "H-6: payer should have no delegation when payer != beneficiary");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/// @notice When payer == beneficiary, the default delegate should be that same address.
|
|
154
|
-
function test_defaultDelegateIsSelfWhenPayerEqualsBeneficiary() public {
|
|
155
|
-
address user = address(bytes20(keccak256("user")));
|
|
156
|
-
|
|
157
|
-
(uint256 _projectId, DefifaHook _nft) = _launchGame();
|
|
158
|
-
vm.warp(_mintPhaseStart);
|
|
159
|
-
|
|
160
|
-
vm.deal(user, 1 ether);
|
|
161
|
-
uint16[] memory tierIds = new uint16[](1);
|
|
162
|
-
tierIds[0] = 1;
|
|
163
|
-
bytes memory payMetadata = abi.encode(address(0), tierIds);
|
|
164
|
-
bytes memory metadata = _buildPayMetadata(payMetadata);
|
|
165
|
-
|
|
166
|
-
vm.prank(user);
|
|
167
|
-
jbMultiTerminal().pay{value: 1 ether}({
|
|
168
|
-
projectId: _projectId,
|
|
169
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
170
|
-
amount: 1 ether,
|
|
171
|
-
beneficiary: user, // payer == beneficiary
|
|
172
|
-
minReturnedTokens: 0,
|
|
173
|
-
memo: "",
|
|
174
|
-
metadata: metadata
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
address delegate = _nft.getTierDelegateOf(user, 1);
|
|
178
|
-
assertEq(delegate, user, "Default delegate should be self when payer == beneficiary");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/// @notice A third-party payer cannot override the beneficiary's delegate.
|
|
182
|
-
function test_explicitDelegateFromThirdPartyDoesNotOverrideBeneficiaryDefault() public {
|
|
183
|
-
address payer = address(bytes20(keccak256("payer2")));
|
|
184
|
-
address beneficiary = address(bytes20(keccak256("beneficiary2")));
|
|
185
|
-
address explicitDelegate = address(bytes20(keccak256("explicitDelegate")));
|
|
186
|
-
|
|
187
|
-
(uint256 _projectId, DefifaHook _nft) = _launchGame();
|
|
188
|
-
vm.warp(_mintPhaseStart);
|
|
189
|
-
|
|
190
|
-
vm.deal(payer, 1 ether);
|
|
191
|
-
uint16[] memory tierIds = new uint16[](1);
|
|
192
|
-
tierIds[0] = 1;
|
|
193
|
-
bytes memory payMetadata = abi.encode(explicitDelegate, tierIds);
|
|
194
|
-
bytes memory metadata = _buildPayMetadata(payMetadata);
|
|
195
|
-
|
|
196
|
-
vm.prank(payer);
|
|
197
|
-
jbMultiTerminal().pay{value: 1 ether}({
|
|
198
|
-
projectId: _projectId,
|
|
199
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
200
|
-
amount: 1 ether,
|
|
201
|
-
beneficiary: beneficiary,
|
|
202
|
-
minReturnedTokens: 0,
|
|
203
|
-
memo: "",
|
|
204
|
-
metadata: metadata
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
address beneficiaryDelegate = _nft.getTierDelegateOf(beneficiary, 1);
|
|
208
|
-
assertEq(beneficiaryDelegate, beneficiary, "third-party payer cannot overwrite beneficiary delegation");
|
|
209
|
-
address payerDelegate = _nft.getTierDelegateOf(payer, 1);
|
|
210
|
-
assertEq(payerDelegate, address(0), "Payer should have no delegation when payer != beneficiary");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ----- Internal helpers ------
|
|
214
|
-
|
|
215
|
-
/// @dev MINT phase starts at `start - mintPeriodDuration - refundPeriodDuration`.
|
|
216
|
-
uint256 internal _mintPhaseStart;
|
|
217
|
-
|
|
218
|
-
function _launchGame() internal returns (uint256 projectId, DefifaHook nft) {
|
|
219
|
-
DefifaTierParams[] memory tierParams = new DefifaTierParams[](2);
|
|
220
|
-
for (uint256 i = 0; i < 2; i++) {
|
|
221
|
-
tierParams[i] = DefifaTierParams({
|
|
222
|
-
reservedRate: 1001,
|
|
223
|
-
reservedTokenBeneficiary: address(0),
|
|
224
|
-
encodedIPFSUri: bytes32(0),
|
|
225
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
226
|
-
name: "DEFIFA"
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
|
|
231
|
-
name: "DEFIFA",
|
|
232
|
-
projectUri: "",
|
|
233
|
-
contractUri: "",
|
|
234
|
-
baseUri: "",
|
|
235
|
-
tierPrice: 1 ether,
|
|
236
|
-
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
237
|
-
mintPeriodDuration: 1 days,
|
|
238
|
-
start: uint48(block.timestamp + 3 days),
|
|
239
|
-
refundPeriodDuration: 1 days,
|
|
240
|
-
store: new JB721TiersHookStore(),
|
|
241
|
-
splits: new JBSplit[](0),
|
|
242
|
-
attestationStartTime: 0,
|
|
243
|
-
attestationGracePeriod: 100_381,
|
|
244
|
-
defaultAttestationDelegate: address(0), // No default delegate -- should fall back to beneficiary
|
|
245
|
-
tiers: tierParams,
|
|
246
|
-
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
247
|
-
terminal: jbMultiTerminal(),
|
|
248
|
-
minParticipation: 0,
|
|
249
|
-
scorecardTimeout: 0,
|
|
250
|
-
timelockDuration: 0
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
_mintPhaseStart = d.start - d.mintPeriodDuration - d.refundPeriodDuration;
|
|
254
|
-
|
|
255
|
-
projectId = deployer.launchGameWith(d);
|
|
256
|
-
|
|
257
|
-
JBRuleset memory fc = jbRulesets().currentOf(projectId);
|
|
258
|
-
if (fc.dataHook() == address(0)) {
|
|
259
|
-
(fc,) = jbRulesets().latestQueuedOf(projectId);
|
|
260
|
-
}
|
|
261
|
-
nft = DefifaHook(fc.dataHook());
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
|
|
265
|
-
bytes[] memory data = new bytes[](1);
|
|
266
|
-
data[0] = metadata;
|
|
267
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
268
|
-
ids[0] = metadataHelper().getId("pay", address(hook));
|
|
269
|
-
return metadataHelper().createMetadata(ids, data);
|
|
270
|
-
}
|
|
271
|
-
}
|