@bananapus/core-v6 0.0.16 → 0.0.17

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.
Files changed (43) hide show
  1. package/ADMINISTRATION.md +1 -1
  2. package/ARCHITECTURE.md +2 -1
  3. package/AUDIT_INSTRUCTIONS.md +342 -0
  4. package/CHANGE_LOG.md +375 -0
  5. package/README.md +4 -4
  6. package/RISKS.md +171 -50
  7. package/SKILLS.md +9 -6
  8. package/USER_JOURNEYS.md +622 -0
  9. package/package.json +2 -2
  10. package/script/DeployPeriphery.s.sol +7 -1
  11. package/src/JBController.sol +5 -0
  12. package/src/JBDeadline.sol +3 -0
  13. package/src/JBDirectory.sol +2 -1
  14. package/src/JBMultiTerminal.sol +50 -9
  15. package/src/JBPermissions.sol +2 -0
  16. package/src/JBPrices.sol +8 -2
  17. package/src/JBRulesets.sol +3 -0
  18. package/src/JBSplits.sol +9 -5
  19. package/src/JBTerminalStore.sol +54 -47
  20. package/src/JBTokens.sol +3 -0
  21. package/src/interfaces/IJBTerminalStore.sol +3 -0
  22. package/src/libraries/JBFees.sol +2 -0
  23. package/src/libraries/JBMetadataResolver.sol +17 -4
  24. package/src/structs/JBBeforeCashOutRecordedContext.sol +4 -0
  25. package/test/TestAuditResponseDesignProofs.sol +434 -0
  26. package/test/TestDataHookFuzzing.sol +520 -0
  27. package/test/TestFeeFreeCashOutBypass.sol +617 -0
  28. package/test/TestL2SequencerPriceFeed.sol +292 -0
  29. package/test/TestMetadataOffsetOverflow.sol +179 -0
  30. package/test/TestMultiTerminalSurplus.sol +348 -0
  31. package/test/TestPermit2DataHook.t.sol +360 -0
  32. package/test/TestRulesetQueueing.sol +1 -2
  33. package/test/TestRulesetWeightCaching.sol +122 -124
  34. package/test/WeirdTokenTests.t.sol +37 -0
  35. package/test/regression/HoldFeesCashOutReserved.t.sol +415 -0
  36. package/test/regression/WeightCacheBoundary.t.sol +291 -0
  37. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +2 -2
  38. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +18 -17
  39. package/test/units/static/JBMultiTerminal/TestPay.sol +6 -4
  40. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +206 -18
  41. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +280 -0
  42. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +55 -12
  43. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +72 -0
@@ -57,9 +57,9 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
57
57
  // ──────────────────
58
58
 
59
59
  function test_CallerCanSetSplitsInOwnGroupNamespace() external {
60
- // Any contract can set splits when groupId's lower 160 bits == msg.sender.
60
+ // Self-auth requires non-zero upper 96 bits + lower 160 bits == msg.sender.
61
61
  address caller = makeAddr("hookContract");
62
- uint256 groupId = uint256(uint160(caller));
62
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
63
63
 
64
64
  JBSplitGroup[] memory groups = _makeSplitGroup(groupId, JBConstants.SPLITS_TOTAL_PERCENT / 2, _bene);
65
65
 
@@ -121,7 +121,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
121
121
  function test_CallerCanSetMultipleSplitsInOwnGroup() external {
122
122
  // Multiple splits in the same self-managed group.
123
123
  address caller = makeAddr("hookContract");
124
- uint256 groupId = uint256(uint160(caller));
124
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
125
125
 
126
126
  address payable bene1 = payable(makeAddr("bene1"));
127
127
  address payable bene2 = payable(makeAddr("bene2"));
@@ -158,7 +158,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
158
158
  function test_CallerCanOverwriteOwnSplits() external {
159
159
  // Caller can overwrite their own splits.
160
160
  address caller = makeAddr("hookContract");
161
- uint256 groupId = uint256(uint160(caller));
161
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
162
162
 
163
163
  address payable beneOld = payable(makeAddr("beneOld"));
164
164
  address payable beneNew = payable(makeAddr("beneNew"));
@@ -183,7 +183,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
183
183
  function test_SelfManagedSplitsEmitSetSplitEvent() external {
184
184
  // Setting self-managed splits emits SetSplit with correct caller.
185
185
  address caller = makeAddr("hookContract");
186
- uint256 groupId = uint256(uint160(caller));
186
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
187
187
 
188
188
  JBSplitGroup[] memory groups = _makeSplitGroup(groupId, JBConstants.SPLITS_TOTAL_PERCENT, _bene);
189
189
 
@@ -197,7 +197,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
197
197
  function test_SelfManagedSplitsWorkAcrossRulesets() external {
198
198
  // Caller can set splits in the same group but different rulesets.
199
199
  address caller = makeAddr("hookContract");
200
- uint256 groupId = uint256(uint160(caller));
200
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
201
201
 
202
202
  uint256 rulesetA = 100;
203
203
  uint256 rulesetB = 200;
@@ -227,7 +227,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
227
227
 
228
228
  function test_SelfManagedSplitsRevertOnZeroPercent() external {
229
229
  address caller = makeAddr("hookContract");
230
- uint256 groupId = uint256(uint160(caller));
230
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
231
231
 
232
232
  JBSplitGroup[] memory groups = new JBSplitGroup[](1);
233
233
  JBSplit[] memory splits = new JBSplit[](1);
@@ -248,7 +248,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
248
248
 
249
249
  function test_SelfManagedSplitsRevertOnExcessPercent() external {
250
250
  address caller = makeAddr("hookContract");
251
- uint256 groupId = uint256(uint160(caller));
251
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
252
252
 
253
253
  JBSplitGroup[] memory groups = new JBSplitGroup[](1);
254
254
  JBSplit[] memory splits = new JBSplit[](2);
@@ -278,7 +278,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
278
278
  function test_SelfManagedSplitsEnforceLocks() external {
279
279
  // Locked splits in a self-managed group cannot be removed.
280
280
  address caller = makeAddr("hookContract");
281
- uint256 groupId = uint256(uint160(caller));
281
+ uint256 groupId = (1 << 160) | uint256(uint160(caller));
282
282
 
283
283
  // Set a locked split.
284
284
  JBSplitGroup[] memory groups = new JBSplitGroup[](1);
@@ -357,6 +357,46 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
357
357
  _splits.setSplitGroupsOf(_projectId, _rulesetId, groups);
358
358
  }
359
359
 
360
+ // ──────────────── Bare-address groupIds require controller auth
361
+ // ───────────────
362
+
363
+ function test_BareAddressGroupIdRequiresControllerEvenForMatchingSender() external {
364
+ // A contract cannot self-auth for groupId == uint256(uint160(self)) (upper 96 bits = 0).
365
+ // This prevents token contracts from hijacking terminal payout splits.
366
+ address caller = makeAddr("maliciousToken");
367
+ uint256 groupId = uint256(uint160(caller)); // bare address, upper bits = 0
368
+
369
+ JBSplitGroup[] memory groups = _makeSplitGroup(groupId, JBConstants.SPLITS_TOTAL_PERCENT, _bene);
370
+
371
+ _mockController(makeAddr("realController"));
372
+
373
+ vm.prank(caller);
374
+ vm.expectRevert(
375
+ abi.encodeWithSelector(
376
+ JBControlled.JBControlled_ControllerUnauthorized.selector, makeAddr("realController")
377
+ )
378
+ );
379
+ _splits.setSplitGroupsOf(_projectId, _rulesetId, groups);
380
+ }
381
+
382
+ function test_BareAddressGroupIdSucceedsWithControllerAuth() external {
383
+ // The controller CAN still set splits for bare-address groupIds (terminal payout groups).
384
+ address controller = makeAddr("controller");
385
+ address token = makeAddr("someToken");
386
+ uint256 groupId = uint256(uint160(token));
387
+
388
+ JBSplitGroup[] memory groups = _makeSplitGroup(groupId, JBConstants.SPLITS_TOTAL_PERCENT, _bene);
389
+
390
+ _mockController(controller);
391
+
392
+ vm.prank(controller);
393
+ _splits.setSplitGroupsOf(_projectId, _rulesetId, groups);
394
+
395
+ JBSplit[] memory result = _splits.splitsOf(_projectId, _rulesetId, groupId);
396
+ assertEq(result.length, 1);
397
+ assertEq(result[0].beneficiary, _bene);
398
+ }
399
+
360
400
  // ───────────────────── Controller can still set any group
361
401
  // ──────────────────
362
402
 
@@ -382,9 +422,10 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
382
422
  // ─────────────────────────────
383
423
 
384
424
  function test_MixedSelfManagedAndControllerGroupsInOneCall() external {
385
- // A single setSplitGroupsOf call with one self-managed group and one controller-gated group.
425
+ // A single setSplitGroupsOf call with one self-managed group (non-zero upper bits) and one controller-gated
426
+ // group.
386
427
  address caller = makeAddr("hookContract");
387
- uint256 selfGroupId = uint256(uint160(caller));
428
+ uint256 selfGroupId = (1 << 160) | uint256(uint160(caller));
388
429
  uint256 otherGroupId = 0;
389
430
 
390
431
  // Mock: caller IS the controller.
@@ -427,7 +468,7 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
427
468
  function test_MixedCallRevertsIfNonControllerSetsOtherGroup() external {
428
469
  // A call with one self-managed group (ok) and one non-owned group (reverts) in the same call.
429
470
  address caller = makeAddr("hookContract");
430
- uint256 selfGroupId = uint256(uint160(caller));
471
+ uint256 selfGroupId = (1 << 160) | uint256(uint160(caller));
431
472
  uint256 otherGroupId = 0;
432
473
 
433
474
  _mockController(makeAddr("realController"));
@@ -472,6 +513,8 @@ contract TestSelfManagedSplitGroups_Local is JBSplitsSetup {
472
513
  function testFuzz_AnyAddressCanSetOwnNamespace(address caller, uint96 upperBits, uint32 percent) external {
473
514
  vm.assume(caller != address(0));
474
515
  vm.assume(percent > 0 && percent <= JBConstants.SPLITS_TOTAL_PERCENT);
516
+ // Self-auth requires non-zero upper bits.
517
+ vm.assume(upperBits > 0);
475
518
 
476
519
  uint256 groupId = (uint256(upperBits) << 160) | uint256(uint160(caller));
477
520
 
@@ -336,6 +336,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
336
336
  cashOutCount: _cashOutCount,
337
337
  accountingContext: _accountingContexts,
338
338
  balanceAccountingContexts: _balanceContexts,
339
+ beneficiaryIsFeeless: false,
339
340
  metadata: ""
340
341
  });
341
342
  }
@@ -369,6 +370,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
369
370
  cashOutCount: _cashOutCount,
370
371
  accountingContext: _accountingContexts,
371
372
  balanceAccountingContexts: _balanceContexts,
373
+ beneficiaryIsFeeless: false,
372
374
  metadata: ""
373
375
  });
374
376
 
@@ -420,6 +422,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
420
422
  surplus: _reclaimedTokenAmount,
421
423
  useTotalSurplus: true,
422
424
  cashOutTaxRate: 0,
425
+ beneficiaryIsFeeless: false,
423
426
  metadata: ""
424
427
  });
425
428
 
@@ -442,6 +445,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
442
445
  cashOutCount: _cashOutCount,
443
446
  accountingContext: _accountingContexts,
444
447
  balanceAccountingContexts: _balanceContexts,
448
+ beneficiaryIsFeeless: false,
445
449
  metadata: ""
446
450
  });
447
451
 
@@ -487,10 +491,78 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
487
491
  cashOutCount: _cashOutCount,
488
492
  accountingContext: _accountingContexts,
489
493
  balanceAccountingContexts: _balanceContexts,
494
+ beneficiaryIsFeeless: false,
490
495
  metadata: ""
491
496
  });
492
497
  }
493
498
 
499
+ function test_GivenBeneficiaryIsFeeless_DataHookReceivesTrue()
500
+ external
501
+ whenCurrentRulesetUseTotalSurplusForCashOutsEqTrueWithHook
502
+ {
503
+ // it will pass beneficiaryIsFeeless=true through to the data hook context
504
+
505
+ // mock JBController totalTokenSupplyWithReservedTokensOf
506
+ mockExpect(
507
+ address(_controller),
508
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
509
+ abi.encode(_totalSupply)
510
+ );
511
+
512
+ // call params
513
+ JBAccountingContext memory _accountingContexts =
514
+ JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
515
+ JBAccountingContext[] memory _balanceContexts = new JBAccountingContext[](1);
516
+
517
+ _balanceContexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
518
+
519
+ uint256 _cashOutCount = 1e18;
520
+ uint256 expectedCashOuts = mulDiv(3e18, _cashOutCount, _totalSupply);
521
+
522
+ // Create the expected context — beneficiaryIsFeeless should be true.
523
+ JBBeforeCashOutRecordedContext memory _context = JBBeforeCashOutRecordedContext({
524
+ terminal: address(this),
525
+ holder: address(this),
526
+ projectId: _projectId,
527
+ rulesetId: uint48(block.timestamp),
528
+ cashOutCount: _cashOutCount,
529
+ totalSupply: _totalSupply,
530
+ surplus: JBTokenAmount({
531
+ token: _accountingContexts.token,
532
+ value: 3e18,
533
+ decimals: _accountingContexts.decimals,
534
+ currency: _accountingContexts.currency
535
+ }),
536
+ useTotalSurplus: true,
537
+ cashOutTaxRate: 0,
538
+ beneficiaryIsFeeless: true,
539
+ metadata: ""
540
+ });
541
+
542
+ // return data
543
+ JBCashOutHookSpecification[] memory _spec = new JBCashOutHookSpecification[](1);
544
+ _spec[0] = JBCashOutHookSpecification({hook: _cashOutHook, amount: 0, metadata: ""});
545
+
546
+ // The mock will only match if the context has beneficiaryIsFeeless=true.
547
+ mockExpect(
548
+ address(_dataHook),
549
+ abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
550
+ abi.encode(0, 1e18, _totalSupply, _spec)
551
+ );
552
+
553
+ (, uint256 reclaimed,,) = _store.recordCashOutFor({
554
+ holder: address(this),
555
+ projectId: _projectId,
556
+ cashOutCount: _cashOutCount,
557
+ accountingContext: _accountingContexts,
558
+ balanceAccountingContexts: _balanceContexts,
559
+ beneficiaryIsFeeless: true,
560
+ metadata: ""
561
+ });
562
+
563
+ assertEq(expectedCashOuts, reclaimed);
564
+ }
565
+
494
566
  // Probably unnecessary even though it may give us a bit of cov %.. skipping for now
495
567
  /* function test_WhenTheCurrentRulesetUseTotalSurplusForCashOutsEqFalse() external {
496
568
  // it will use the standard surplus calculation