@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.
- package/ADMINISTRATION.md +8 -7
- package/RISKS.md +38 -1
- package/package.json +2 -2
- package/references/entrypoints.md +1 -1
- package/script/Deploy.s.sol +2 -1
- package/src/JBERC20.sol +101 -30
- package/src/JBRulesets.sol +67 -94
- package/src/JBTerminalStore.sol +64 -23
- package/src/JBTokens.sol +1 -1
- package/src/abstract/JBPermissioned.sol +28 -0
- package/src/interfaces/IJBRulesetDataHook.sol +6 -1
- package/src/interfaces/IJBToken.sol +3 -3
- package/test/TestCashOutHooks.sol +12 -2
- package/test/TestDataHookFuzzing.sol +4 -4
- package/test/TestForwardedTokenConsumption.sol +7 -1
- package/test/TestJBERC20Inheritance.sol +3 -1
- package/test/TestTokenFlow.sol +2 -2
- package/test/audit/CashOutReenterPay.t.sol +5 -0
- package/test/audit/CodexHeldFeeRounding.t.sol +159 -0
- package/test/helpers/TestBaseWorkflow.sol +1 -1
- package/test/units/static/JBERC20/JBERC20Setup.sol +8 -3
- package/test/units/static/JBERC20/TestInitialize.sol +12 -13
- package/test/units/static/JBERC20/TestName.sol +1 -1
- package/test/units/static/JBERC20/TestNonces.sol +2 -1
- package/test/units/static/JBERC20/TestSymbol.sol +1 -1
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +1 -1
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +4 -4
- package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
package/src/JBRulesets.sol
CHANGED
|
@@ -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 =
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
961
|
-
/// @dev
|
|
962
|
-
/// only
|
|
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
|
-
/// @
|
|
967
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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) {
|
package/src/JBTerminalStore.sol
CHANGED
|
@@ -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
|
-
|
|
961
|
-
|
|
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
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
33
|
-
function initialize(string memory name, string memory symbol, address
|
|
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(),
|
|
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(
|
|
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(
|
|
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());
|