@bananapus/core-v6 0.0.32 → 0.0.34

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.
@@ -208,8 +208,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
208
208
  caller: msg.sender
209
209
  });
210
210
 
211
- // Return the struct for the new ruleset's ID.
212
- return _getStructFor({projectId: projectId, rulesetId: rulesetId});
211
+ // Return the struct for the new ruleset's ID, with metadata.
212
+ return _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
213
213
  }
214
214
 
215
215
  /// @notice Cache the value of the ruleset weight for a specific ruleset.
@@ -221,7 +221,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
221
221
  /// @param rulesetId The ID of the ruleset to update the cache for.
222
222
  function updateRulesetWeightCache(uint256 projectId, uint256 rulesetId) external override {
223
223
  // Get the target ruleset.
224
- JBRuleset memory targetRuleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
224
+ JBRuleset memory targetRuleset =
225
+ _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
225
226
 
226
227
  // Nothing to cache if the target ruleset doesn't have a duration or a weight cut percent.
227
228
  // slither-disable-next-line incorrect-equality
@@ -296,8 +297,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
296
297
  // Keep a reference to the number of rulesets being returned.
297
298
  uint256 count = 0;
298
299
 
299
- // Keep a reference to the starting ruleset.
300
- JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: startingId});
300
+ // Keep a reference to the starting ruleset (metadata not needed — only counting).
301
+ JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: startingId, withMetadata: false});
301
302
 
302
303
  // First, count the number of rulesets to include in the result by iterating backwards from the starting
303
304
  // ruleset.
@@ -306,7 +307,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
306
307
  count++;
307
308
 
308
309
  // Iterate to the ruleset it was based on.
309
- ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
310
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: false});
310
311
  }
311
312
 
312
313
  // Keep a reference to the array of rulesets that'll be populated.
@@ -317,8 +318,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
317
318
  return rulesets;
318
319
  }
319
320
 
320
- // Reset the ruleset being iterated on to the starting ruleset.
321
- ruleset = _getStructFor({projectId: projectId, rulesetId: startingId});
321
+ // Reset the ruleset being iterated on to the starting ruleset, now with metadata for the return array.
322
+ ruleset = _getStructFor({projectId: projectId, rulesetId: startingId, withMetadata: true});
322
323
 
323
324
  // Set the counter.
324
325
  uint256 i;
@@ -328,8 +329,10 @@ contract JBRulesets is JBControlled, IJBRulesets {
328
329
  // Add the ruleset to the array.
329
330
  rulesets[i++] = ruleset;
330
331
 
331
- // Get the ruleset it was based on if needed.
332
- if (i != count) ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
332
+ // Get the ruleset it was based on if needed, with metadata for the return array.
333
+ if (i != count) {
334
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
335
+ }
333
336
  }
334
337
  }
335
338
 
@@ -345,8 +348,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
345
348
  // Get a reference to the latest ruleset ID.
346
349
  uint256 rulesetId = latestRulesetIdOf[projectId];
347
350
 
348
- // Resolve the struct for the latest ruleset.
349
- JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
351
+ // Resolve the struct for the latest ruleset, with metadata (forwarded to the external approval hook).
352
+ JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
350
353
 
351
354
  return _approvalStatusOf({projectId: projectId, ruleset: ruleset});
352
355
  }
@@ -362,15 +365,18 @@ contract JBRulesets is JBControlled, IJBRulesets {
362
365
  function currentOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
363
366
  // If the project does not have a ruleset, return an empty struct.
364
367
  // slither-disable-next-line incorrect-equality
365
- if (latestRulesetIdOf[projectId] == 0) return _getStructFor({projectId: 0, rulesetId: 0});
368
+ if (latestRulesetIdOf[projectId] == 0) {
369
+ return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
370
+ }
366
371
 
367
372
  // Get a reference to the currently approvable ruleset's ID.
368
373
  uint256 rulesetId = _currentlyApprovableRulesetIdOf(projectId);
369
374
 
370
375
  // If a currently approvable ruleset exists...
371
376
  if (rulesetId != 0) {
372
- // Resolve the struct for the currently approvable ruleset.
373
- ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
377
+ // Resolve the struct for the currently approvable ruleset, with metadata (forwarded to external hooks
378
+ // and potentially returned).
379
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
374
380
 
375
381
  // Get a reference to the approval status.
376
382
  JBApprovalStatus approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
@@ -387,15 +393,16 @@ contract JBRulesets is JBControlled, IJBRulesets {
387
393
  // which carries the last approved configuration.
388
394
  rulesetId = ruleset.basedOnId;
389
395
 
390
- // Keep a reference to its ruleset.
391
- ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
396
+ // Keep a reference to its ruleset, with metadata (used by `_simulateCycledRulesetBasedOn` and may be
397
+ // returned).
398
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
392
399
  } else {
393
400
  // No upcoming ruleset found that is currently approvable,
394
401
  // so use the latest ruleset ID.
395
402
  rulesetId = latestRulesetIdOf[projectId];
396
403
 
397
- // Get the struct for the latest ID.
398
- ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
404
+ // Get the struct for the latest ID, with metadata (forwarded to external hooks and may be returned).
405
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
399
406
 
400
407
  // Get a reference to the approval status.
401
408
  JBApprovalStatus approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
@@ -407,7 +414,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
407
414
  || block.timestamp < ruleset.start
408
415
  ) {
409
416
  rulesetId = ruleset.basedOnId;
410
- ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
417
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
411
418
  approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
412
419
  }
413
420
  }
@@ -433,7 +440,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
433
440
  override
434
441
  returns (JBRuleset memory ruleset)
435
442
  {
436
- return _getStructFor({projectId: projectId, rulesetId: rulesetId});
443
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
437
444
  }
438
445
 
439
446
  /// @notice The latest ruleset queued for a project. Returns the ruleset's struct and its current approval status.
@@ -451,8 +458,9 @@ contract JBRulesets is JBControlled, IJBRulesets {
451
458
  // Get a reference to the latest ruleset's ID.
452
459
  uint256 rulesetId = latestRulesetIdOf[projectId];
453
460
 
454
- // Resolve the struct for the latest ruleset.
455
- ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
461
+ // Resolve the struct for the latest ruleset, with metadata (forwarded to the external approval hook and
462
+ // returned).
463
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: true});
456
464
 
457
465
  // Resolve the approval status.
458
466
  approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
@@ -465,7 +473,9 @@ contract JBRulesets is JBControlled, IJBRulesets {
465
473
  function upcomingOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
466
474
  // If the project does not have a latest ruleset, return an empty struct.
467
475
  // slither-disable-next-line incorrect-equality
468
- if (latestRulesetIdOf[projectId] == 0) return _getStructFor({projectId: 0, rulesetId: 0});
476
+ if (latestRulesetIdOf[projectId] == 0) {
477
+ return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
478
+ }
469
479
 
470
480
  // Get a reference to the upcoming approvable ruleset's ID.
471
481
  uint256 upcomingApprovableRulesetId = _upcomingApprovableRulesetIdOf(projectId);
@@ -476,7 +486,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
476
486
  // If an upcoming approvable ruleset has been queued, and it's approval status is Approved or ApprovalExpected,
477
487
  // return its ruleset struct
478
488
  if (upcomingApprovableRulesetId != 0) {
479
- ruleset = _getStructFor({projectId: projectId, rulesetId: upcomingApprovableRulesetId});
489
+ ruleset = _getStructFor({projectId: projectId, rulesetId: upcomingApprovableRulesetId, withMetadata: true});
480
490
 
481
491
  // Get a reference to the approval status.
482
492
  approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
@@ -488,23 +498,25 @@ contract JBRulesets is JBControlled, IJBRulesets {
488
498
  || approvalStatus == JBApprovalStatus.Empty
489
499
  ) return ruleset;
490
500
 
491
- // Resolve the ruleset for the ruleset the upcoming approvable ruleset was based on.
492
- ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
501
+ // Resolve the ruleset for the ruleset the upcoming approvable ruleset was based on, with metadata
502
+ // (used by `_simulateCycledRulesetBasedOn`).
503
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
493
504
  } else {
494
- // Resolve the ruleset for the latest queued ruleset.
495
- ruleset = _getStructFor({projectId: projectId, rulesetId: latestRulesetIdOf[projectId]});
505
+ // Resolve the ruleset for the latest queued ruleset, with metadata (forwarded to external hooks and used
506
+ // by `_simulateCycledRulesetBasedOn`).
507
+ ruleset = _getStructFor({projectId: projectId, rulesetId: latestRulesetIdOf[projectId], withMetadata: true});
496
508
 
497
509
  // If the latest ruleset starts in the future, it must start in the distant future
498
510
  // Since its not the upcoming approvable ruleset. In this case, base the upcoming ruleset on the base
499
511
  // ruleset.
500
512
  while (ruleset.start > block.timestamp) {
501
- ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
513
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
502
514
  }
503
515
  }
504
516
 
505
517
  // There's no queued if the current has a duration of 0.
506
518
  // slither-disable-next-line incorrect-equality
507
- if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0});
519
+ if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
508
520
 
509
521
  // Get a reference to the approval status.
510
522
  approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
@@ -516,12 +528,13 @@ contract JBRulesets is JBControlled, IJBRulesets {
516
528
  return _simulateCycledRulesetBasedOn({projectId: projectId, baseRuleset: ruleset, allowMidRuleset: false});
517
529
  }
518
530
 
519
- // Get the ruleset of its base ruleset, which carries the last approved configuration.
520
- ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
531
+ // Get the ruleset of its base ruleset, which carries the last approved configuration. Metadata is needed by
532
+ // `_simulateCycledRulesetBasedOn`.
533
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
521
534
 
522
535
  // There's no queued if the base, which must still be the current, has a duration of 0.
523
536
  // slither-disable-next-line incorrect-equality
524
- if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0});
537
+ if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
525
538
 
526
539
  // Return a simulated cycled ruleset.
527
540
  return _simulateCycledRulesetBasedOn({projectId: projectId, baseRuleset: ruleset, allowMidRuleset: false});
@@ -715,16 +728,16 @@ contract JBRulesets is JBControlled, IJBRulesets {
715
728
  // Use an empty ruleset as the base.
716
729
  return _initializeRulesetFor({
717
730
  projectId: projectId,
718
- baseRuleset: _getStructFor({projectId: 0, rulesetId: 0}),
731
+ baseRuleset: _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false}),
719
732
  rulesetId: rulesetId,
720
733
  mustStartAtOrAfter: mustStartAtOrAfter,
721
734
  weight: weight
722
735
  });
723
736
  }
724
737
 
725
- // Get a reference to the latest ruleset's struct.
726
- // Note: full metadata is loaded because `_approvalStatusOf` forwards the struct to external approval hooks.
727
- JBRuleset memory baseRuleset = _getStructFor({projectId: projectId, rulesetId: latestId});
738
+ // Get a reference to the latest ruleset's struct, with metadata because `_approvalStatusOf` forwards the
739
+ // struct to external approval hooks.
740
+ JBRuleset memory baseRuleset = _getStructFor({projectId: projectId, rulesetId: latestId, withMetadata: true});
728
741
 
729
742
  // Get a reference to the approval status.
730
743
  JBApprovalStatus approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: baseRuleset});
@@ -748,7 +761,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
748
761
  ) {
749
762
  // Metadata not needed — the fallback ruleset is only used for intrinsic fields (start, basedOnId, etc.)
750
763
  // and not forwarded to any external approval hook.
751
- baseRuleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: baseRuleset.basedOnId});
764
+ baseRuleset = _getStructFor({projectId: projectId, rulesetId: baseRuleset.basedOnId, withMetadata: false});
752
765
  }
753
766
 
754
767
  // Make sure the ruleset starts after the base ruleset.
@@ -935,7 +948,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
935
948
  uint256 rulesetId = latestRulesetIdOf[projectId];
936
949
 
937
950
  // Get the struct for the latest ruleset (metadata not needed — only traversal fields are checked).
938
- JBRuleset memory ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
951
+ JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
939
952
 
940
953
  // Loop through all most recently queued rulesets until an approvable one is found, or we've proven one can't
941
954
  // exist.
@@ -951,22 +964,23 @@ contract JBRulesets is JBControlled, IJBRulesets {
951
964
  return ruleset.id;
952
965
  }
953
966
 
954
- ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: ruleset.basedOnId});
967
+ ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: false});
955
968
  } while (ruleset.cycleNumber != 0);
956
969
 
957
970
  return 0;
958
971
  }
959
972
 
960
- /// @notice Unpack a ruleset's intrinsic and user properties without loading metadata.
961
- /// @dev Saves one cold SLOAD (~2,100 gas) compared to `_getStructFor`. Use this for linked-list traversal where
962
- /// only `id`, `start`, `duration`, `basedOnId`, `cycleNumber`, `weight`, `weightCutPercent`, and `approvalHook`
963
- /// are needed.
973
+ /// @notice Unpack a ruleset's packed stored values into a struct.
974
+ /// @dev When `withMetadata` is false, one cold SLOAD (~2,100 gas) is skipped use this for linked-list traversal
975
+ /// where only id, start, duration, basedOnId, etc. are needed.
964
976
  /// @param projectId The ID of the project the ruleset belongs to.
965
977
  /// @param rulesetId The ID of the ruleset to get the struct for.
966
- /// @return ruleset A ruleset struct with `metadata` set to 0.
967
- function _getStructWithoutMetadataFor(
978
+ /// @param withMetadata Whether to load the packed metadata from storage.
979
+ /// @return ruleset The ruleset struct (`metadata` is 0 when `withMetadata` is false).
980
+ function _getStructFor(
968
981
  uint256 projectId,
969
- uint256 rulesetId
982
+ uint256 rulesetId,
983
+ bool withMetadata
970
984
  )
971
985
  internal
972
986
  view
@@ -1006,49 +1020,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
1006
1020
  // forge-lint: disable-next-line(unsafe-typecast)
1007
1021
  ruleset.weightCutPercent = uint32(packedUserProperties >> 192);
1008
1022
 
1009
- // metadata intentionally not loaded — saves one cold SLOAD (~2,100 gas).
1010
- }
1011
-
1012
- /// @notice Unpack a ruleset's packed stored values into an easy-to-work-with ruleset struct.
1013
- /// @param projectId The ID of the project the ruleset belongs to.
1014
- /// @param rulesetId The ID of the ruleset to get the full struct for.
1015
- /// @return ruleset A ruleset struct.
1016
- function _getStructFor(uint256 projectId, uint256 rulesetId) internal view returns (JBRuleset memory ruleset) {
1017
- // Return an empty ruleset if the specified `rulesetId` is 0.
1018
- // slither-disable-next-line incorrect-equality
1019
- if (rulesetId == 0) return ruleset;
1020
-
1021
- // forge-lint: disable-next-line(unsafe-typecast)
1022
- ruleset.id = uint48(rulesetId);
1023
-
1024
- uint256 packedIntrinsicProperties = _packedIntrinsicPropertiesOf[projectId][rulesetId];
1025
-
1026
- // `weight` in bits 0-111 bits.
1027
- // forge-lint: disable-next-line(unsafe-typecast)
1028
- ruleset.weight = uint112(packedIntrinsicProperties);
1029
- // `basedOnId` in bits 112-159 bits.
1030
- // forge-lint: disable-next-line(unsafe-typecast)
1031
- ruleset.basedOnId = uint48(packedIntrinsicProperties >> 112);
1032
- // `start` in bits 160-207 bits.
1033
- // forge-lint: disable-next-line(unsafe-typecast)
1034
- ruleset.start = uint48(packedIntrinsicProperties >> 160);
1035
- // `cycleNumber` in bits 208-255 bits.
1036
- // forge-lint: disable-next-line(unsafe-typecast)
1037
- ruleset.cycleNumber = uint48(packedIntrinsicProperties >> 208);
1038
-
1039
- uint256 packedUserProperties = _packedUserPropertiesOf[projectId][rulesetId];
1040
-
1041
- // approval hook in bits 0-159 bits.
1042
- // forge-lint: disable-next-line(unsafe-typecast)
1043
- ruleset.approvalHook = IJBRulesetApprovalHook(address(uint160(packedUserProperties)));
1044
- // `duration` in bits 160-191 bits.
1045
- // forge-lint: disable-next-line(unsafe-typecast)
1046
- ruleset.duration = uint32(packedUserProperties >> 160);
1047
- // weight cut percent in bits 192-223 bits.
1048
- // forge-lint: disable-next-line(unsafe-typecast)
1049
- ruleset.weightCutPercent = uint32(packedUserProperties >> 192);
1050
-
1051
- ruleset.metadata = _metadataOf[projectId][rulesetId];
1023
+ // Load metadata only when needed — saves one cold SLOAD (~2,100 gas) for traversal-only paths.
1024
+ if (withMetadata) ruleset.metadata = _metadataOf[projectId][rulesetId];
1052
1025
  }
1053
1026
 
1054
1027
  /// @notice A simulated view of the ruleset that would be created if the provided one cycled over (if the project
@@ -1126,7 +1099,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1126
1099
  rulesetId = latestRulesetIdOf[projectId];
1127
1100
 
1128
1101
  // Get the struct for the latest ruleset (metadata not needed — only traversal fields are checked).
1129
- JBRuleset memory ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
1102
+ JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
1130
1103
 
1131
1104
  // There is no upcoming ruleset if the latest ruleset has already started.
1132
1105
  // slither-disable-next-line incorrect-equality
@@ -1144,7 +1117,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1144
1117
 
1145
1118
  // Find the base ruleset that is not still queued.
1146
1119
  while (true) {
1147
- baseRuleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: basedOnId});
1120
+ baseRuleset = _getStructFor({projectId: projectId, rulesetId: basedOnId, withMetadata: false});
1148
1121
 
1149
1122
  // If the base ruleset starts in the future,
1150
1123
  if (block.timestamp < baseRuleset.start) {
@@ -1159,7 +1132,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1159
1132
  }
1160
1133
 
1161
1134
  // Get the ruleset struct for the ID found (metadata not needed — only `start` and `duration` are checked).
1162
- ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
1135
+ ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
1163
1136
 
1164
1137
  // If the latest ruleset doesn't start until after another base ruleset return 0.
1165
1138
  if (baseRuleset.duration != 0 && block.timestamp < ruleset.start - baseRuleset.duration) {
@@ -599,6 +599,8 @@ contract JBTerminalStore is IJBTerminalStore {
599
599
  JBRuleset memory ruleset = RULESETS.currentOf(projectId);
600
600
 
601
601
  // Return the amount of surplus terminal tokens that would be reclaimed.
602
+ // NOTE: This view does not run the data hook, so it cannot reflect a cross-chain totalSupply override.
603
+ // For accurate omnichain estimates, use the data hook or simulate recordCashOutFor.
602
604
  return JBCashOuts.cashOutFrom({
603
605
  surplus: surplus,
604
606
  cashOutCount: cashOutCount,
@@ -814,6 +816,8 @@ contract JBTerminalStore is IJBTerminalStore {
814
816
  if (cashOutCount > totalSupply) return 0;
815
817
 
816
818
  // Return the amount of surplus terminal tokens that would be reclaimed.
819
+ // NOTE: This view does not run the data hook, so it cannot reflect a cross-chain totalSupply override.
820
+ // For accurate omnichain estimates, use the data hook or simulate recordCashOutFor.
817
821
  return JBCashOuts.cashOutFrom({
818
822
  surplus: currentSurplus,
819
823
  cashOutCount: cashOutCount,
@@ -877,6 +881,54 @@ contract JBTerminalStore is IJBTerminalStore {
877
881
  });
878
882
  }
879
883
 
884
+ /// @notice Calls the data hook, validates noop specifications, and computes the bonding curve reclaim amount.
885
+ /// @dev Extracted from `_computeCashOutFrom` to keep it under the EVM stack depth limit (16 slots).
886
+ /// @param ruleset The current ruleset (used to resolve the data hook address).
887
+ /// @param context The fully-populated cash out context to forward to the data hook.
888
+ /// @param surplus The locally available surplus (used as a cap — can't reclaim more than what's on-chain here).
889
+ /// @return reclaimAmount The amount of surplus tokens reclaimable after the bonding curve and cap.
890
+ /// @return cashOutTaxRate The cash out tax rate returned by the data hook.
891
+ /// @return hookSpecifications The hook specifications returned by the data hook.
892
+ function _cashOutWithDataHook(
893
+ JBRuleset memory ruleset,
894
+ JBBeforeCashOutRecordedContext memory context,
895
+ uint256 surplus
896
+ )
897
+ internal
898
+ view
899
+ returns (uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] memory hookSpecifications)
900
+ {
901
+ // Ask the data hook for the effective bonding curve parameters and any hook specifications.
902
+ uint256 effectiveCashOutCount;
903
+ uint256 effectiveTotalSupply;
904
+ uint256 effectiveSurplusValue;
905
+ (cashOutTaxRate, effectiveCashOutCount, effectiveTotalSupply, effectiveSurplusValue, hookSpecifications) =
906
+ IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
907
+
908
+ // Noop specifications are informational only, so they can't also request forwarded funds.
909
+ for (uint256 i; i < hookSpecifications.length;) {
910
+ if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
911
+ revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
912
+ }
913
+ unchecked {
914
+ ++i;
915
+ }
916
+ }
917
+
918
+ // Apply the bonding curve to calculate how much of the surplus is reclaimable.
919
+ if (surplus != 0) {
920
+ reclaimAmount = JBCashOuts.cashOutFrom({
921
+ surplus: effectiveSurplusValue,
922
+ cashOutCount: effectiveCashOutCount,
923
+ totalSupply: effectiveTotalSupply,
924
+ cashOutTaxRate: cashOutTaxRate
925
+ });
926
+
927
+ // Cap at local surplus — can't reclaim more than what's locally available.
928
+ if (reclaimAmount > surplus) reclaimAmount = surplus;
929
+ }
930
+ }
931
+
880
932
  /// @notice Computes cash out results without writing state.
881
933
  /// @param terminal The terminal recording the cash out.
882
934
  /// @param holder The account that is cashing out tokens.
@@ -934,8 +986,6 @@ contract JBTerminalStore is IJBTerminalStore {
934
986
  // The terminal still burns the caller-supplied cashOutCount after pricing completes.
935
987
  // Project owners MUST audit their data hooks with the same rigor as the terminal.
936
988
 
937
- uint256 effectiveCashOutCount = cashOutCount;
938
-
939
989
  // If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
940
990
  if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
941
991
  // Build the cash out context field-by-field — the struct has 11 fields, too many for a literal.
@@ -957,30 +1007,21 @@ contract JBTerminalStore is IJBTerminalStore {
957
1007
  context.beneficiaryIsFeeless = beneficiaryIsFeeless;
958
1008
  context.metadata = metadata;
959
1009
 
960
- (cashOutTaxRate, effectiveCashOutCount, effectiveTotalSupply, hookSpecifications) =
961
- IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
962
-
963
- // Noop specifications are informational only, so they can't also request forwarded funds.
964
- for (uint256 i; i < hookSpecifications.length;) {
965
- if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
966
- revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
967
- }
968
- unchecked {
969
- ++i;
970
- }
971
- }
1010
+ // Hook call + bonding curve in a helper to stay under the stack depth limit.
1011
+ (reclaimAmount, cashOutTaxRate, hookSpecifications) =
1012
+ _cashOutWithDataHook({ruleset: ruleset, context: context, surplus: surplus});
972
1013
  } else {
973
1014
  cashOutTaxRate = ruleset.cashOutTaxRate();
974
- }
975
1015
 
976
- // Apply the bonding curve to calculate how much of the surplus is reclaimable.
977
- if (surplus != 0) {
978
- reclaimAmount = JBCashOuts.cashOutFrom({
979
- surplus: surplus,
980
- cashOutCount: effectiveCashOutCount,
981
- totalSupply: effectiveTotalSupply,
982
- cashOutTaxRate: cashOutTaxRate
983
- });
1016
+ // Apply the bonding curve to calculate how much of the surplus is reclaimable.
1017
+ if (surplus != 0) {
1018
+ reclaimAmount = JBCashOuts.cashOutFrom({
1019
+ surplus: surplus,
1020
+ cashOutCount: cashOutCount,
1021
+ totalSupply: effectiveTotalSupply,
1022
+ cashOutTaxRate: cashOutTaxRate
1023
+ });
1024
+ }
984
1025
  }
985
1026
  }
986
1027
 
package/src/JBTokens.sol CHANGED
@@ -228,7 +228,7 @@ contract JBTokens is JBControlled, IJBTokens {
228
228
  });
229
229
 
230
230
  // Initialize the token.
231
- token.initialize({name: name, symbol: symbol, owner: address(this)});
231
+ token.initialize({name: name, symbol: symbol, tokens: address(this)});
232
232
  }
233
233
 
234
234
  /// @notice Mint (create) new tokens or credits.
@@ -34,6 +34,34 @@ abstract contract JBPermissioned is Context, IJBPermissioned {
34
34
  // -------------------------- internal views ------------------------- //
35
35
  //*********************************************************************//
36
36
 
37
+ /// @notice Check whether an operator is the account or has the relevant permission.
38
+ /// @param operator The address to check.
39
+ /// @param account The account to allow.
40
+ /// @param projectId The project ID to check the permission under.
41
+ /// @param permissionId The required permission ID. The operator must have this permission within the specified
42
+ /// project ID.
43
+ /// @return Whether the operator is the account or has the permission.
44
+ function _hasPermissionFrom(
45
+ address operator,
46
+ address account,
47
+ uint256 projectId,
48
+ uint256 permissionId
49
+ )
50
+ internal
51
+ view
52
+ returns (bool)
53
+ {
54
+ return operator == account
55
+ || PERMISSIONS.hasPermission({
56
+ operator: operator,
57
+ account: account,
58
+ projectId: projectId,
59
+ permissionId: permissionId,
60
+ includeRoot: true,
61
+ includeWildcardProjectId: true
62
+ });
63
+ }
64
+
37
65
  /// @notice Require the message sender to be the account or have the relevant permission.
38
66
  /// @param account The account to allow.
39
67
  /// @param projectId The project ID to check the permission under.
@@ -19,7 +19,11 @@ interface IJBRulesetDataHook is IERC165 {
19
19
  /// @return cashOutTaxRate The rate determining the reclaimable amount for a given surplus and token supply.
20
20
  /// @return effectiveCashOutCount The effective token count to use for pricing the cash out. The terminal still
21
21
  /// burns the caller-supplied token count.
22
- /// @return effectiveTotalSupply The effective total supply to use for pricing the cash out.
22
+ /// @return effectiveTotalSupply The effective total supply to use for both the proportional reclaim and tax
23
+ /// calculations. For omnichain projects, this should include tokens on other chains so the tax cannot be bypassed.
24
+ /// @return effectiveSurplusValue The surplus value to use for the bonding curve calculation, denominated in the
25
+ /// same token, decimals, and currency as `context.surplus`. The terminal caps the reclaim at locally available
26
+ /// funds.
23
27
  /// @return hookSpecifications The amount and data to send to cash out hooks instead of returning to the
24
28
  /// beneficiary.
25
29
  function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
@@ -29,6 +33,7 @@ interface IJBRulesetDataHook is IERC165 {
29
33
  uint256 cashOutTaxRate,
30
34
  uint256 effectiveCashOutCount,
31
35
  uint256 effectiveTotalSupply,
36
+ uint256 effectiveSurplusValue,
32
37
  JBCashOutHookSpecification[] memory hookSpecifications
33
38
  );
34
39
 
@@ -26,11 +26,11 @@ interface IJBToken {
26
26
  /// @param amount The amount of tokens to burn.
27
27
  function burn(address account, uint256 amount) external;
28
28
 
29
- /// @notice Initializes the token with a name, symbol, and owner.
29
+ /// @notice Initializes the token with a name, symbol, and the JBTokens contract.
30
30
  /// @param name The token's name.
31
31
  /// @param symbol The token's symbol.
32
- /// @param owner The token contract's owner.
33
- function initialize(string memory name, string memory symbol, address owner) external;
32
+ /// @param tokens The JBTokens contract that manages this token.
33
+ function initialize(string memory name, string memory symbol, address tokens) external;
34
34
 
35
35
  /// @notice Mints tokens to an account.
36
36
  /// @param account The address to mint tokens to.
@@ -207,7 +207,11 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
207
207
  _DATA_HOOK,
208
208
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
209
209
  abi.encode(
210
- _ruleset.cashOutTaxRate(), _beneficiaryTokenBalance / 2, _beneficiaryTokenBalance, _specifications
210
+ _ruleset.cashOutTaxRate(),
211
+ _beneficiaryTokenBalance / 2,
212
+ _beneficiaryTokenBalance,
213
+ _nativePayAmount,
214
+ _specifications
211
215
  )
212
216
  );
213
217
 
@@ -322,7 +326,13 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
322
326
  vm.mockCall(
323
327
  _DATA_HOOK,
324
328
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
325
- abi.encode(_customCashOutTaxRate, _customCashOutCount, _customTotalSupply, _specifications)
329
+ abi.encode(
330
+ _customCashOutTaxRate,
331
+ _customCashOutCount,
332
+ _customTotalSupply,
333
+ _nativeTerminalBalance, // Same as _nativePayAmount (no payouts); avoids stack depth limit.
334
+ _specifications
335
+ )
326
336
  );
327
337
 
328
338
  _terminal.cashOutTokensOf({
@@ -261,12 +261,12 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
261
261
  uint256 cashOutCount = tokenBalance / 2;
262
262
  _hookTotalSupply = bound(_hookTotalSupply, cashOutCount, tokenBalance * 10);
263
263
 
264
- // Data hook returns: cashOutTaxRate=0, cashOutCount=half, custom totalSupply, no hook specs.
264
+ // Data hook returns: cashOutTaxRate=0, cashOutCount=half, custom totalSupply, local surplus, no hook specs.
265
265
  JBCashOutHookSpecification[] memory _emptyCashOutSpecs = new JBCashOutHookSpecification[](0);
266
266
  vm.mockCall(
267
267
  _DATA_HOOK,
268
268
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
269
- abi.encode(uint256(0), cashOutCount, _hookTotalSupply, _emptyCashOutSpecs)
269
+ abi.encode(uint256(0), cashOutCount, _hookTotalSupply, _payAmount, _emptyCashOutSpecs)
270
270
  );
271
271
 
272
272
  uint256 balanceBefore = address(this).balance;
@@ -336,7 +336,7 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
336
336
  vm.mockCall(
337
337
  _DATA_HOOK,
338
338
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
339
- abi.encode(uint256(0), cashOutCount, tokenBalance, _specs)
339
+ abi.encode(uint256(0), cashOutCount, tokenBalance, _payAmount, _specs)
340
340
  );
341
341
 
342
342
  // Mock the cash out hook call.
@@ -500,7 +500,7 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
500
500
  vm.mockCall(
501
501
  _DATA_HOOK,
502
502
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
503
- abi.encode(uint256(0), tokenBalance, tokenBalance, _specs)
503
+ abi.encode(uint256(0), tokenBalance, tokenBalance, _payAmount, _specs)
504
504
  );
505
505
 
506
506
  vm.expectRevert(
@@ -145,7 +145,13 @@ contract TestForwardedTokenConsumption_Local is TestBaseWorkflow {
145
145
  vm.mockCall(
146
146
  _DATA_HOOK,
147
147
  abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
148
- abi.encode(0, cashOutCount, _controller.totalTokenSupplyWithReservedTokensOf(_projectId), specifications)
148
+ abi.encode(
149
+ 0,
150
+ cashOutCount,
151
+ _controller.totalTokenSupplyWithReservedTokensOf(_projectId),
152
+ _PAY_AMOUNT,
153
+ specifications
154
+ )
149
155
  );
150
156
 
151
157
  vm.prank(multisig());