@bananapus/core-v6 0.0.27 → 0.0.29
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 +1 -1
- package/ARCHITECTURE.md +3 -3
- package/AUDIT_INSTRUCTIONS.md +1 -1
- package/CHANGE_LOG.md +41 -0
- package/README.md +2 -2
- package/RISKS.md +11 -6
- package/SKILLS.md +5 -4
- package/USER_JOURNEYS.md +6 -3
- package/package.json +4 -4
- package/script/DeployPeriphery.s.sol +14 -13
- package/src/JBChainlinkV3PriceFeed.sol +4 -1
- package/src/JBChainlinkV3SequencerPriceFeed.sol +1 -1
- package/src/JBController.sol +6 -2
- package/src/JBDirectory.sol +7 -0
- package/src/JBMultiTerminal.sol +63 -22
- package/src/JBTerminalStore.sol +7 -4
- package/src/interfaces/IJBController.sol +6 -0
- package/test/AuditFixes.t.sol +808 -0
- package/test/TestFeeFreeCashOutBypass.sol +2 -2
- package/test/TestFees.sol +6 -4
- package/test/TestTerminalMigration.sol +104 -2
- package/test/audit/FeeFreeSurplusLifecycle.t.sol +5 -2
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +4 -10
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +4 -53
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +2 -5
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +17 -5
- package/test/units/static/JBMultiTerminal/TestSelfPayRevert.sol +55 -0
|
@@ -802,7 +802,7 @@ contract TestFeeFreeCashOutBypass is TestBaseWorkflow {
|
|
|
802
802
|
beneficiary: payable(user),
|
|
803
803
|
metadata: new bytes(0)
|
|
804
804
|
});
|
|
805
|
-
// Balance dropped significantly.
|
|
805
|
+
// Balance dropped significantly. _capFeeFreeSurplus should have capped fee-free at remaining balance.
|
|
806
806
|
|
|
807
807
|
// Step 4: Switch back to zero tax. Cash out remaining.
|
|
808
808
|
_reconfigureWithTaxRate(_projectIdB, 0);
|
|
@@ -855,7 +855,7 @@ contract TestFeeFreeCashOutBypass is TestBaseWorkflow {
|
|
|
855
855
|
vm.prank(multisig());
|
|
856
856
|
jbFeelessAddresses().setFeelessAddress(_attacker, true);
|
|
857
857
|
|
|
858
|
-
// Step 3: Feeless cashout — no fees charged, but
|
|
858
|
+
// Step 3: Feeless cashout — no fees charged, but _capFeeFreeSurplus should still cap.
|
|
859
859
|
uint256 attackerTokens = _tokens.totalBalanceOf(_attacker, _projectIdB);
|
|
860
860
|
vm.prank(_attacker);
|
|
861
861
|
uint256 reclaim = _terminal.cashOutTokensOf({
|
package/test/TestFees.sol
CHANGED
|
@@ -216,8 +216,9 @@ contract TestFees_Local is TestBaseWorkflow {
|
|
|
216
216
|
// Send: Migration to terminal2
|
|
217
217
|
_terminal.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminal2);
|
|
218
218
|
|
|
219
|
-
// Check: Held
|
|
220
|
-
|
|
219
|
+
// Check: Held fee remains in terminal, plus migration fee paid to fee project (on same terminal)
|
|
220
|
+
uint256 _migrationFee = _nativeDistLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
221
|
+
assertEq(address(_terminal).balance, _feeAmount + _migrationFee);
|
|
221
222
|
|
|
222
223
|
vm.stopPrank();
|
|
223
224
|
}
|
|
@@ -265,8 +266,9 @@ contract TestFees_Local is TestBaseWorkflow {
|
|
|
265
266
|
// Send: Migration to terminal2
|
|
266
267
|
_terminal.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminal2);
|
|
267
268
|
|
|
268
|
-
// Check: Held fee has been repaid
|
|
269
|
-
|
|
269
|
+
// Check: Held fee has been repaid. Migration fee paid to fee project remains on this terminal.
|
|
270
|
+
uint256 _migrationFee = _nativePayAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
271
|
+
assertEq(address(_terminal).balance, _migrationFee);
|
|
270
272
|
|
|
271
273
|
vm.stopPrank();
|
|
272
274
|
}
|
|
@@ -6,6 +6,7 @@ import {JBMultiTerminal} from "../src/JBMultiTerminal.sol";
|
|
|
6
6
|
import {IJBController} from "../src/interfaces/IJBController.sol";
|
|
7
7
|
import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
8
8
|
import {JBConstants} from "../src/libraries/JBConstants.sol";
|
|
9
|
+
import {JBFees} from "../src/libraries/JBFees.sol";
|
|
9
10
|
import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
|
|
10
11
|
import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
|
|
11
12
|
import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
|
|
@@ -105,7 +106,7 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
|
|
|
105
106
|
uint256 migratedBalance = _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
|
|
106
107
|
assertEq(migratedBalance, payAmount, "full balance should be migrated");
|
|
107
108
|
|
|
108
|
-
// Step 4: Verify balances after migration
|
|
109
|
+
// Step 4: Verify balances after migration (fee project is exempt from migration fee)
|
|
109
110
|
uint256 balanceAAfter = jbTerminalStore().balanceOf(address(_terminalA), _projectId, JBConstants.NATIVE_TOKEN);
|
|
110
111
|
assertEq(balanceAAfter, 0, "terminal A should have zero after migration");
|
|
111
112
|
|
|
@@ -152,7 +153,7 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
|
|
|
152
153
|
vm.prank(_projectOwner);
|
|
153
154
|
_terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
|
|
154
155
|
|
|
155
|
-
// Check surplus from terminal B
|
|
156
|
+
// Check surplus from terminal B (fee project exempt from migration fee)
|
|
156
157
|
uint256 surplusAfter =
|
|
157
158
|
_terminalB.currentSurplusOf(_projectId, new address[](0), 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
|
|
158
159
|
assertEq(surplusAfter, surplusBefore, "surplus should be preserved after migration");
|
|
@@ -169,4 +170,105 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
|
|
|
169
170
|
vm.expectRevert();
|
|
170
171
|
_terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
|
|
171
172
|
}
|
|
173
|
+
|
|
174
|
+
/// @notice Non-fee project migration charges the 2.5% fee; fee project balance increases.
|
|
175
|
+
function test_migration_nonFeeProject_chargesFee() public {
|
|
176
|
+
// Launch a second project (project 2) — this is NOT the fee project.
|
|
177
|
+
JBRulesetMetadata memory _metadata2 = JBRulesetMetadata({
|
|
178
|
+
reservedPercent: 0,
|
|
179
|
+
cashOutTaxRate: 0,
|
|
180
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
181
|
+
pausePay: false,
|
|
182
|
+
pauseCreditTransfers: false,
|
|
183
|
+
allowOwnerMinting: false,
|
|
184
|
+
allowSetCustomToken: false,
|
|
185
|
+
allowTerminalMigration: true,
|
|
186
|
+
allowSetTerminals: true,
|
|
187
|
+
allowSetController: false,
|
|
188
|
+
allowAddAccountingContext: false,
|
|
189
|
+
allowAddPriceFeed: false,
|
|
190
|
+
ownerMustSendPayouts: false,
|
|
191
|
+
holdFees: false,
|
|
192
|
+
useTotalSurplusForCashOuts: false,
|
|
193
|
+
useDataHookForPay: false,
|
|
194
|
+
useDataHookForCashOut: false,
|
|
195
|
+
dataHook: address(0),
|
|
196
|
+
metadata: 0
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
JBRulesetConfig[] memory _rulesetConfig2 = new JBRulesetConfig[](1);
|
|
200
|
+
_rulesetConfig2[0].mustStartAtOrAfter = 0;
|
|
201
|
+
_rulesetConfig2[0].duration = 0;
|
|
202
|
+
_rulesetConfig2[0].weight = 1000 * 10 ** 18;
|
|
203
|
+
_rulesetConfig2[0].weightCutPercent = 0;
|
|
204
|
+
_rulesetConfig2[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
205
|
+
_rulesetConfig2[0].metadata = _metadata2;
|
|
206
|
+
_rulesetConfig2[0].splitGroups = new JBSplitGroup[](0);
|
|
207
|
+
_rulesetConfig2[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
|
|
208
|
+
|
|
209
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
210
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
211
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
JBTerminalConfig[] memory _terminalConfigs2 = new JBTerminalConfig[](2);
|
|
215
|
+
_terminalConfigs2[0] = JBTerminalConfig({terminal: _terminalA, accountingContextsToAccept: _tokensToAccept});
|
|
216
|
+
_terminalConfigs2[1] = JBTerminalConfig({terminal: _terminalB, accountingContextsToAccept: _tokensToAccept});
|
|
217
|
+
|
|
218
|
+
uint256 project2 = _controller.launchProjectFor({
|
|
219
|
+
owner: _projectOwner,
|
|
220
|
+
projectUri: "non-fee-project",
|
|
221
|
+
rulesetConfigurations: _rulesetConfig2,
|
|
222
|
+
terminalConfigurations: _terminalConfigs2,
|
|
223
|
+
memo: ""
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Confirm project 2 is NOT the fee project.
|
|
227
|
+
assertGt(project2, 1, "project2 should not be the fee project");
|
|
228
|
+
|
|
229
|
+
uint256 payAmount = 10 ether;
|
|
230
|
+
|
|
231
|
+
// Pay into terminal A for project 2.
|
|
232
|
+
vm.deal(_beneficiary, payAmount);
|
|
233
|
+
vm.prank(_beneficiary);
|
|
234
|
+
_terminalA.pay{value: payAmount}(project2, JBConstants.NATIVE_TOKEN, payAmount, _beneficiary, 0, "", "");
|
|
235
|
+
|
|
236
|
+
// Snapshot fee project balance before migration.
|
|
237
|
+
uint256 feeBalanceBefore = jbTerminalStore().balanceOf(address(_terminalA), 1, JBConstants.NATIVE_TOKEN);
|
|
238
|
+
|
|
239
|
+
// Migrate project 2 from terminal A to terminal B.
|
|
240
|
+
vm.prank(_projectOwner);
|
|
241
|
+
_terminalA.migrateBalanceOf(project2, JBConstants.NATIVE_TOKEN, _terminalB);
|
|
242
|
+
|
|
243
|
+
// Fee project balance should have increased (fee was charged).
|
|
244
|
+
uint256 feeBalanceAfter = jbTerminalStore().balanceOf(address(_terminalA), 1, JBConstants.NATIVE_TOKEN);
|
|
245
|
+
assertGt(feeBalanceAfter, feeBalanceBefore, "fee project should receive migration fee");
|
|
246
|
+
|
|
247
|
+
// Terminal B should have received payAmount minus the 2.5% fee.
|
|
248
|
+
uint256 expectedFee = JBFees.feeAmountFrom({amountBeforeFee: payAmount, feePercent: 25});
|
|
249
|
+
uint256 balanceBAfter = jbTerminalStore().balanceOf(address(_terminalB), project2, JBConstants.NATIVE_TOKEN);
|
|
250
|
+
assertEq(balanceBAfter, payAmount - expectedFee, "terminal B balance should reflect fee deduction");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @notice Fee project (project 1) migration is exempt from fees.
|
|
254
|
+
function test_migration_feeProject_noFeeCharged() public {
|
|
255
|
+
// _projectId is project 1, which IS the fee project.
|
|
256
|
+
assertEq(_projectId, 1, "project under test should be the fee project");
|
|
257
|
+
|
|
258
|
+
uint256 payAmount = 10 ether;
|
|
259
|
+
|
|
260
|
+
// Pay into terminal A for the fee project.
|
|
261
|
+
vm.deal(_beneficiary, payAmount);
|
|
262
|
+
vm.prank(_beneficiary);
|
|
263
|
+
_terminalA.pay{value: payAmount}(_projectId, JBConstants.NATIVE_TOKEN, payAmount, _beneficiary, 0, "", "");
|
|
264
|
+
|
|
265
|
+
// Migrate fee project from terminal A to terminal B.
|
|
266
|
+
vm.prank(_projectOwner);
|
|
267
|
+
uint256 migratedBalance = _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
|
|
268
|
+
assertEq(migratedBalance, payAmount, "full balance should be migrated without fee");
|
|
269
|
+
|
|
270
|
+
// Terminal B should have the full amount (no fee deducted).
|
|
271
|
+
uint256 balanceBAfter = jbTerminalStore().balanceOf(address(_terminalB), _projectId, JBConstants.NATIVE_TOKEN);
|
|
272
|
+
assertEq(balanceBAfter, payAmount, "fee project should not be charged migration fee");
|
|
273
|
+
}
|
|
172
274
|
}
|
|
@@ -318,10 +318,13 @@ contract FeeFreeSurplusLifecycleTest is TestBaseWorkflow {
|
|
|
318
318
|
jbTerminalStore().balanceOf(address(_terminal), _recipientProjectId, JBConstants.NATIVE_TOKEN);
|
|
319
319
|
assertEq(balanceAfterOnOldTerminal, 0, "Old terminal balance should be zero after migration");
|
|
320
320
|
|
|
321
|
-
// Verify the new terminal received the balance.
|
|
321
|
+
// Verify the new terminal received the balance minus the 2.5% migration fee.
|
|
322
322
|
uint256 balanceOnNewTerminal =
|
|
323
323
|
jbTerminalStore().balanceOf(address(_terminal2), _recipientProjectId, JBConstants.NATIVE_TOKEN);
|
|
324
|
-
|
|
324
|
+
uint256 migrationFee = balanceBefore * 25 / 1000;
|
|
325
|
+
assertEq(
|
|
326
|
+
balanceOnNewTerminal, balanceBefore - migrationFee, "New terminal should have balance minus migration fee"
|
|
327
|
+
);
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
// --- Helpers ---
|
|
@@ -340,7 +340,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
340
340
|
whenADataHookIsConfigured
|
|
341
341
|
whenCallerHasPermission
|
|
342
342
|
{
|
|
343
|
-
// it will
|
|
343
|
+
// it will forceApprove pass the full amount to the hook and emit HookAfterRecordCashOut
|
|
344
344
|
|
|
345
345
|
// mint mocked erc20 tokens to hodler
|
|
346
346
|
_mockToken2.mint(address(_terminal), _defaultAmount * 10);
|
|
@@ -350,9 +350,6 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
350
350
|
vm.prank(_holder);
|
|
351
351
|
_mockToken2.approve(address(_terminal), _defaultAmount);
|
|
352
352
|
|
|
353
|
-
vm.prank(address(_terminal));
|
|
354
|
-
_mockToken2.approve(address(_mockHook), _defaultAmount);
|
|
355
|
-
|
|
356
353
|
uint256 reclaimAmount = 1e9;
|
|
357
354
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
358
355
|
hookSpecifications[0] =
|
|
@@ -431,8 +428,8 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
431
428
|
|
|
432
429
|
mockExpect(address(_mockHook), abi.encodeCall(IJBCashOutHook.afterCashOutRecordedWith, (context)), "");
|
|
433
430
|
|
|
434
|
-
// ensure approval is
|
|
435
|
-
vm.expectCall(address(_mockToken2), abi.encodeCall(IERC20.approve, (address(_mockHook), _defaultAmount
|
|
431
|
+
// ensure approval is set via forceApprove
|
|
432
|
+
vm.expectCall(address(_mockToken2), abi.encodeCall(IERC20.approve, (address(_mockHook), _defaultAmount)));
|
|
436
433
|
|
|
437
434
|
_acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
|
|
438
435
|
|
|
@@ -456,7 +453,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
456
453
|
whenADataHookIsConfigured
|
|
457
454
|
whenCallerHasPermission
|
|
458
455
|
{
|
|
459
|
-
// it will
|
|
456
|
+
// it will forceApprove pass the amount to the hook and emit HookAfterRecordCashOut
|
|
460
457
|
|
|
461
458
|
// mint mocked erc20 tokens to hodler
|
|
462
459
|
_mockToken2.mint(address(_terminal), _defaultAmount * 10);
|
|
@@ -466,9 +463,6 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
466
463
|
vm.prank(_holder);
|
|
467
464
|
_mockToken2.approve(address(_terminal), _defaultAmount);
|
|
468
465
|
|
|
469
|
-
vm.prank(address(_terminal));
|
|
470
|
-
_mockToken2.approve(address(_mockHook), _defaultAmount);
|
|
471
|
-
|
|
472
466
|
uint256 reclaimAmount = 1e9;
|
|
473
467
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
474
468
|
JBCashOutHookSpecification[] memory paySpecs = new JBCashOutHookSpecification[](0);
|
|
@@ -4,18 +4,14 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
5
5
|
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
6
6
|
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
7
|
-
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
8
7
|
import {IJBSplitHook} from "../../../../src/interfaces/IJBSplitHook.sol";
|
|
9
8
|
import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
|
|
10
9
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
11
10
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
12
11
|
import {JBFees} from "../../../../src/libraries/JBFees.sol";
|
|
13
12
|
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
14
|
-
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
15
|
-
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
16
13
|
import {JBSplit} from "../../../../src/structs/JBSplit.sol";
|
|
17
14
|
import {JBSplitHookContext} from "../../../../src/structs/JBSplitHookContext.sol";
|
|
18
|
-
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
19
15
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
20
16
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
21
17
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
@@ -336,12 +332,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
336
332
|
uint256 taxedAmount = JBFees.feeAmountFrom(_defaultAmount, _fee);
|
|
337
333
|
uint256 amountAfterTax = _defaultAmount - taxedAmount;
|
|
338
334
|
|
|
339
|
-
// mock call for SafeERC20s
|
|
340
|
-
mockExpect(
|
|
341
|
-
_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockSecondTerminal))), abi.encode(0)
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
// mock call for SafeERC20s safeIncreaseAllowance approval
|
|
335
|
+
// mock call for SafeERC20s forceApprove approval
|
|
345
336
|
mockExpect(_usdc, abi.encodeCall(IERC20.approve, (_mockSecondTerminal, amountAfterTax)), "");
|
|
346
337
|
|
|
347
338
|
// mock call to second terminals addToBalanceOf
|
|
@@ -366,9 +357,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
366
357
|
}
|
|
367
358
|
|
|
368
359
|
function test_GivenPreferAddToBalanceDNEQTrueAndTerminalEQThisAddress() external {
|
|
369
|
-
// it will
|
|
370
|
-
|
|
371
|
-
_setAccountingContext(_projectId, _usdc, 0, _usdcCurrency);
|
|
360
|
+
// it will revert with MintNotAllowed because same-project same-terminal pay splits are blocked
|
|
372
361
|
|
|
373
362
|
// mock call to directory primaryTerminalOf
|
|
374
363
|
mockExpect(
|
|
@@ -386,41 +375,8 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
386
375
|
hook: IJBSplitHook(address(0))
|
|
387
376
|
});
|
|
388
377
|
|
|
389
|
-
// needed for next mock call returns
|
|
390
|
-
JBTokenAmount memory tokenAmount =
|
|
391
|
-
JBTokenAmount({token: _usdc, decimals: 0, currency: _usdcCurrency, value: _defaultAmount});
|
|
392
|
-
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](0);
|
|
393
|
-
JBRuleset memory returnedRuleset = JBRuleset({
|
|
394
|
-
cycleNumber: 1,
|
|
395
|
-
id: 1,
|
|
396
|
-
basedOnId: 0,
|
|
397
|
-
start: 0,
|
|
398
|
-
duration: 0,
|
|
399
|
-
weight: 0,
|
|
400
|
-
weightCutPercent: 0,
|
|
401
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
402
|
-
metadata: 0
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// mock call to JBTerminalStore recordPaymentFrom
|
|
406
|
-
mockExpect(
|
|
407
|
-
address(store),
|
|
408
|
-
abi.encodeCall(
|
|
409
|
-
IJBTerminalStore.recordPaymentFrom,
|
|
410
|
-
(
|
|
411
|
-
address(_terminal),
|
|
412
|
-
tokenAmount,
|
|
413
|
-
_projectId,
|
|
414
|
-
address(this),
|
|
415
|
-
bytes(abi.encodePacked(uint256(_projectId)))
|
|
416
|
-
)
|
|
417
|
-
),
|
|
418
|
-
abi.encode(returnedRuleset, 0, hookSpecifications)
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
// for safe ERC20 check of code length at token address
|
|
422
378
|
vm.prank(address(_terminal));
|
|
423
|
-
|
|
379
|
+
vm.expectRevert(JBMultiTerminal.JBMultiTerminal_MintNotAllowed.selector);
|
|
424
380
|
JBMultiTerminal(address(_terminal))
|
|
425
381
|
.executePayout({
|
|
426
382
|
split: _splitMemory,
|
|
@@ -460,12 +416,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
460
416
|
uint256 taxedAmount = JBFees.feeAmountFrom(_defaultAmount, _fee);
|
|
461
417
|
uint256 amountAfterTax = _defaultAmount - taxedAmount;
|
|
462
418
|
|
|
463
|
-
// mock call for SafeERC20s
|
|
464
|
-
mockExpect(
|
|
465
|
-
_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockSecondTerminal))), abi.encode(0)
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
// mock call for SafeERC20s safeIncreaseAllowance approval
|
|
419
|
+
// mock call for SafeERC20s forceApprove approval
|
|
469
420
|
mockExpect(_usdc, abi.encodeCall(IERC20.approve, (_mockSecondTerminal, amountAfterTax)), "");
|
|
470
421
|
|
|
471
422
|
// mock call to second terminals pay function
|
|
@@ -68,12 +68,9 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function test_WhenTokenIsErc20AndFeeTerminalIsExternal() external {
|
|
71
|
-
// it will
|
|
71
|
+
// it will forceApprove
|
|
72
72
|
|
|
73
|
-
// mock
|
|
74
|
-
mockExpect(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_feeTerminal))), abi.encode(0));
|
|
75
|
-
|
|
76
|
-
// mock approval call
|
|
73
|
+
// mock approval call for forceApprove
|
|
77
74
|
mockExpect(_usdc, abi.encodeCall(IERC20.approve, (address(_feeTerminal), _defaultAmount)), "");
|
|
78
75
|
|
|
79
76
|
// mock pay call to fee terminal
|
|
@@ -11,6 +11,7 @@ import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.s
|
|
|
11
11
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
12
12
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
13
13
|
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
14
|
+
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
14
15
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
15
16
|
|
|
16
17
|
contract TestMigrateBalanceOf_Local is JBMultiTerminalSetup {
|
|
@@ -102,7 +103,14 @@ contract TestMigrateBalanceOf_Local is JBMultiTerminalSetup {
|
|
|
102
103
|
} */
|
|
103
104
|
|
|
104
105
|
function test_GivenTokenIsERC20() external whenPermissioned {
|
|
105
|
-
// it will
|
|
106
|
+
// it will forceApprove and addToBalanceOf
|
|
107
|
+
|
|
108
|
+
// mock _isFeeless to return true (skip migration fee for this unit test)
|
|
109
|
+
mockExpect(
|
|
110
|
+
address(feelessAddresses),
|
|
111
|
+
abi.encodeCall(IJBFeelessAddresses.isFeeless, (address(_newTerminal))),
|
|
112
|
+
abi.encode(true)
|
|
113
|
+
);
|
|
106
114
|
|
|
107
115
|
// for next mock
|
|
108
116
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
@@ -124,10 +132,7 @@ contract TestMigrateBalanceOf_Local is JBMultiTerminalSetup {
|
|
|
124
132
|
abi.encode(_defaultAmount)
|
|
125
133
|
);
|
|
126
134
|
|
|
127
|
-
// mock call for SafeERC20s
|
|
128
|
-
mockExpect(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_newTerminal))), abi.encode(0));
|
|
129
|
-
|
|
130
|
-
// mock call for SafeERC20s safeIncreaseAllowance approval
|
|
135
|
+
// mock call for SafeERC20s forceApprove approval
|
|
131
136
|
mockExpect(_usdc, abi.encodeCall(IERC20.approve, (address(_newTerminal), _defaultAmount)), "");
|
|
132
137
|
|
|
133
138
|
// mock call to new terminal addToBalance
|
|
@@ -143,6 +148,13 @@ contract TestMigrateBalanceOf_Local is JBMultiTerminalSetup {
|
|
|
143
148
|
function test_GivenTokenIsNative() external whenPermissioned {
|
|
144
149
|
// it will addToBalanceOf with value in msgvalue
|
|
145
150
|
|
|
151
|
+
// mock _isFeeless to return true (skip migration fee for this unit test)
|
|
152
|
+
mockExpect(
|
|
153
|
+
address(feelessAddresses),
|
|
154
|
+
abi.encodeCall(IJBFeelessAddresses.isFeeless, (address(_newTerminal))),
|
|
155
|
+
abi.encode(true)
|
|
156
|
+
);
|
|
157
|
+
|
|
146
158
|
// for next mock
|
|
147
159
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
148
160
|
JBAccountingContext memory _context =
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
5
|
+
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
6
|
+
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
7
|
+
import {JBSplit} from "../../../../src/structs/JBSplit.sol";
|
|
8
|
+
import {IJBSplitHook} from "../../../../src/interfaces/IJBSplitHook.sol";
|
|
9
|
+
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
10
|
+
|
|
11
|
+
/// @notice Tests that a pay-type split back into the same terminal reverts with MintNotAllowed.
|
|
12
|
+
contract TestSelfPayRevert_Local is JBMultiTerminalSetup {
|
|
13
|
+
uint64 _projectId = 1;
|
|
14
|
+
uint256 _defaultAmount = 1e18;
|
|
15
|
+
address _sender = makeAddr("sender");
|
|
16
|
+
address _native = JBConstants.NATIVE_TOKEN;
|
|
17
|
+
|
|
18
|
+
function setUp() public {
|
|
19
|
+
super.multiTerminalSetup();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @notice When a split routes a pay back to the same project on the same terminal,
|
|
23
|
+
/// executePayout should revert with MintNotAllowed.
|
|
24
|
+
function test_RevertWhen_SplitPaysBackToSameTerminal() external {
|
|
25
|
+
// Build a split targeting the SAME project with preferAddToBalance = false (pay path).
|
|
26
|
+
JBSplit memory split = JBSplit({
|
|
27
|
+
preferAddToBalance: false,
|
|
28
|
+
percent: 1_000_000_000,
|
|
29
|
+
projectId: _projectId,
|
|
30
|
+
beneficiary: payable(_sender),
|
|
31
|
+
lockedUntil: 0,
|
|
32
|
+
hook: IJBSplitHook(address(0))
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Mock primaryTerminalOf to return this terminal (self-referencing).
|
|
36
|
+
mockExpect(
|
|
37
|
+
address(directory),
|
|
38
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (_projectId, _native)),
|
|
39
|
+
abi.encode(address(_terminal))
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// executePayout requires msg.sender == address(this), so we call it via the terminal.
|
|
43
|
+
// The terminal's try-catch in the split group lib would normally catch this.
|
|
44
|
+
vm.prank(address(_terminal));
|
|
45
|
+
vm.expectRevert(JBMultiTerminal.JBMultiTerminal_MintNotAllowed.selector);
|
|
46
|
+
JBMultiTerminal(payable(address(_terminal)))
|
|
47
|
+
.executePayout({
|
|
48
|
+
split: split,
|
|
49
|
+
projectId: uint256(_projectId),
|
|
50
|
+
token: _native,
|
|
51
|
+
amount: _defaultAmount,
|
|
52
|
+
originalMessageSender: _sender
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|