@ballkidz/defifa 0.0.10 → 0.0.11

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.
@@ -0,0 +1,272 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity ^0.8.26;
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 When an explicit delegate is set, it should be used regardless of payer/beneficiary.
182
+ function test_explicitDelegateOverridesDefault() 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
+ // With the fix, delegation is stored on the beneficiary's account, not the payer's.
208
+ address beneficiaryDelegate = _nft.getTierDelegateOf(beneficiary, 1);
209
+ assertEq(beneficiaryDelegate, explicitDelegate, "Explicit delegate should override default on beneficiary");
210
+ // Payer should have no delegation.
211
+ address payerDelegate = _nft.getTierDelegateOf(payer, 1);
212
+ assertEq(payerDelegate, address(0), "Payer should have no delegation when payer != beneficiary");
213
+ }
214
+
215
+ // ----- Internal helpers ------
216
+
217
+ /// @dev MINT phase starts at `start - mintPeriodDuration - refundPeriodDuration`.
218
+ uint256 internal _mintPhaseStart;
219
+
220
+ function _launchGame() internal returns (uint256 projectId, DefifaHook nft) {
221
+ DefifaTierParams[] memory tierParams = new DefifaTierParams[](2);
222
+ for (uint256 i = 0; i < 2; i++) {
223
+ tierParams[i] = DefifaTierParams({
224
+ reservedRate: 1001,
225
+ reservedTokenBeneficiary: address(0),
226
+ encodedIPFSUri: bytes32(0),
227
+ shouldUseReservedTokenBeneficiaryAsDefault: false,
228
+ name: "DEFIFA"
229
+ });
230
+ }
231
+
232
+ DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
233
+ name: "DEFIFA",
234
+ projectUri: "",
235
+ contractUri: "",
236
+ baseUri: "",
237
+ tierPrice: 1 ether,
238
+ token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
239
+ mintPeriodDuration: 1 days,
240
+ start: uint48(block.timestamp + 3 days),
241
+ refundPeriodDuration: 1 days,
242
+ store: new JB721TiersHookStore(),
243
+ splits: new JBSplit[](0),
244
+ attestationStartTime: 0,
245
+ attestationGracePeriod: 100_381,
246
+ defaultAttestationDelegate: address(0), // No default delegate -- should fall back to beneficiary
247
+ tiers: tierParams,
248
+ defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
249
+ terminal: jbMultiTerminal(),
250
+ minParticipation: 0,
251
+ scorecardTimeout: 0
252
+ });
253
+
254
+ _mintPhaseStart = d.start - d.mintPeriodDuration - d.refundPeriodDuration;
255
+
256
+ projectId = deployer.launchGameWith(d);
257
+
258
+ JBRuleset memory fc = jbRulesets().currentOf(projectId);
259
+ if (fc.dataHook() == address(0)) {
260
+ (fc,) = jbRulesets().latestQueuedOf(projectId);
261
+ }
262
+ nft = DefifaHook(fc.dataHook());
263
+ }
264
+
265
+ function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
266
+ bytes[] memory data = new bytes[](1);
267
+ data[0] = metadata;
268
+ bytes4[] memory ids = new bytes4[](1);
269
+ ids[0] = metadataHelper().getId("pay", address(hook));
270
+ return metadataHelper().createMetadata(ids, data);
271
+ }
272
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
5
5