@rev-net/core-v6 0.0.11 → 0.0.13

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 (81) hide show
  1. package/ADMINISTRATION.md +7 -7
  2. package/ARCHITECTURE.md +11 -11
  3. package/AUDIT_INSTRUCTIONS.md +295 -0
  4. package/CHANGE_LOG.md +316 -0
  5. package/README.md +9 -6
  6. package/RISKS.md +180 -35
  7. package/SKILLS.md +9 -11
  8. package/STYLE_GUIDE.md +14 -1
  9. package/USER_JOURNEYS.md +489 -0
  10. package/package.json +9 -9
  11. package/script/Deploy.s.sol +124 -40
  12. package/script/helpers/RevnetCoreDeploymentLib.sol +19 -6
  13. package/src/REVDeployer.sol +183 -175
  14. package/src/REVLoans.sol +65 -28
  15. package/src/interfaces/IREVDeployer.sol +25 -23
  16. package/src/structs/REV721TiersHookFlags.sol +1 -0
  17. package/src/structs/REVAutoIssuance.sol +1 -0
  18. package/src/structs/REVBaseline721HookConfig.sol +1 -0
  19. package/src/structs/REVConfig.sol +1 -0
  20. package/src/structs/REVCroptopAllowedPost.sol +1 -0
  21. package/src/structs/REVDeploy721TiersHookConfig.sol +13 -14
  22. package/src/structs/REVDescription.sol +1 -0
  23. package/src/structs/REVLoan.sol +1 -0
  24. package/src/structs/REVLoanSource.sol +1 -0
  25. package/src/structs/REVStageConfig.sol +1 -0
  26. package/src/structs/REVSuckerDeploymentConfig.sol +1 -0
  27. package/test/REV.integrations.t.sol +148 -19
  28. package/test/REVAutoIssuanceFuzz.t.sol +31 -6
  29. package/test/REVDeployerRegressions.t.sol +47 -9
  30. package/test/REVInvincibility.t.sol +83 -19
  31. package/test/REVInvincibilityHandler.sol +29 -0
  32. package/test/REVLifecycle.t.sol +36 -6
  33. package/test/REVLoans.invariants.t.sol +64 -10
  34. package/test/REVLoansAttacks.t.sol +54 -9
  35. package/test/REVLoansFeeRecovery.t.sol +61 -15
  36. package/test/REVLoansFindings.t.sol +42 -9
  37. package/test/REVLoansRegressions.t.sol +33 -6
  38. package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
  39. package/test/REVLoansSourced.t.sol +79 -17
  40. package/test/REVLoansUnSourced.t.sol +61 -10
  41. package/test/TestBurnHeldTokens.t.sol +47 -11
  42. package/test/TestCEIPattern.t.sol +37 -6
  43. package/test/TestCashOutCallerValidation.t.sol +41 -8
  44. package/test/TestConversionDocumentation.t.sol +50 -13
  45. package/test/TestCrossCurrencyReclaim.t.sol +584 -0
  46. package/test/TestCrossSourceReallocation.t.sol +37 -6
  47. package/test/TestERC2771MetaTx.t.sol +557 -0
  48. package/test/TestEmptyBuybackSpecs.t.sol +45 -10
  49. package/test/TestFlashLoanSurplus.t.sol +39 -7
  50. package/test/TestHookArrayOOB.t.sol +42 -13
  51. package/test/TestLiquidationBehavior.t.sol +37 -7
  52. package/test/TestLoanSourceRotation.t.sol +525 -0
  53. package/test/TestLongTailEconomics.t.sol +651 -0
  54. package/test/TestLowFindings.t.sol +80 -8
  55. package/test/TestMixedFixes.t.sol +43 -9
  56. package/test/TestPermit2Signatures.t.sol +657 -0
  57. package/test/TestReallocationSandwich.t.sol +384 -0
  58. package/test/TestRevnetRegressions.t.sol +324 -0
  59. package/test/TestSplitWeightAdjustment.t.sol +52 -13
  60. package/test/TestSplitWeightE2E.t.sol +53 -18
  61. package/test/TestSplitWeightFork.t.sol +66 -21
  62. package/test/TestStageTransitionBorrowable.t.sol +38 -6
  63. package/test/TestSwapTerminalPermission.t.sol +37 -7
  64. package/test/TestUint112Overflow.t.sol +39 -6
  65. package/test/TestZeroRepayment.t.sol +37 -6
  66. package/test/fork/ForkTestBase.sol +66 -17
  67. package/test/fork/TestCashOutFork.t.sol +9 -3
  68. package/test/fork/TestLoanBorrowFork.t.sol +1 -0
  69. package/test/fork/TestLoanCrossRulesetFork.t.sol +11 -3
  70. package/test/fork/TestLoanLiquidationFork.t.sol +1 -0
  71. package/test/fork/TestLoanReallocateFork.t.sol +1 -0
  72. package/test/fork/TestLoanRepayFork.t.sol +1 -0
  73. package/test/fork/TestLoanTransferFork.t.sol +133 -0
  74. package/test/fork/TestSplitWeightFork.t.sol +3 -0
  75. package/test/helpers/REVEmpty721Config.sol +46 -0
  76. package/test/mock/MockBuybackDataHook.sol +1 -0
  77. package/test/regression/TestBurnPermissionRequired.t.sol +267 -0
  78. package/test/regression/TestCrossRevnetLiquidation.t.sol +228 -0
  79. package/test/regression/TestCumulativeLoanCounter.t.sol +38 -8
  80. package/test/regression/TestLiquidateGapHandling.t.sol +40 -8
  81. package/test/regression/TestZeroPriceFeed.t.sol +396 -0
package/src/REVLoans.sol CHANGED
@@ -59,6 +59,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
59
59
  error REVLoans_InvalidPrepaidFeePercent(uint256 prepaidFeePercent, uint256 min, uint256 max);
60
60
  error REVLoans_InvalidTerminal(address terminal, uint256 revnetId);
61
61
  error REVLoans_LoanExpired(uint256 timeSinceLoanCreated, uint256 loanLiquidationDuration);
62
+ error REVLoans_LoanIdOverflow();
62
63
  error REVLoans_NewBorrowAmountGreaterThanLoanAmount(uint256 newBorrowAmount, uint256 loanAmount);
63
64
  error REVLoans_NoMsgValueAllowed();
64
65
  error REVLoans_NotEnoughCollateral();
@@ -70,6 +71,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
70
71
  error REVLoans_SourceMismatch();
71
72
  error REVLoans_Unauthorized(address caller, address owner);
72
73
  error REVLoans_UnderMinBorrowAmount(uint256 minBorrowAmount, uint256 borrowAmount);
74
+ error REVLoans_ZeroBorrowAmount();
73
75
  error REVLoans_ZeroCollateralLoanIsInvalid();
74
76
 
75
77
  //*********************************************************************//
@@ -93,7 +95,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
93
95
  uint256 public constant override MIN_PREPAID_FEE_PERCENT = 25; // 2.5%
94
96
 
95
97
  //*********************************************************************//
96
- // -------------------- private constant properties ------------------ //
98
+ // ------------------------ private constants ------------------------ //
97
99
  //*********************************************************************//
98
100
 
99
101
  /// @notice Just a kind reminder to our readers.
@@ -255,7 +257,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
255
257
  /// @param amount The amount being paid off.
256
258
  /// @return sourceFeeAmount The source fee amount for the loan.
257
259
  function determineSourceFeeAmount(REVLoan memory loan, uint256 amount) public view returns (uint256) {
258
- return _determineSourceFeeAmount(loan, amount);
260
+ return _determineSourceFeeAmount({loan: loan, amount: amount});
259
261
  }
260
262
 
261
263
  /// @notice The revnet ID for the loan with the provided loan ID.
@@ -416,8 +418,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
416
418
  // If the loan period has passed the prepaid time frame, take a fee.
417
419
  if (timeSinceLoanCreated <= loan.prepaidDuration) return 0;
418
420
 
419
- // If the loan period has reached or passed the liquidation time frame, do not allow loan management.
420
- if (timeSinceLoanCreated >= LOAN_LIQUIDATION_DURATION) {
421
+ // If the loan period has passed the liquidation time frame, do not allow loan management.
422
+ // Uses `>` (not `>=`) so the exact boundary second is still repayable — the liquidation path
423
+ // uses `<=`, and matching `>=` here would create a 1-second window where neither path is available.
424
+ if (timeSinceLoanCreated > LOAN_LIQUIDATION_DURATION) {
421
425
  revert REVLoans_LoanExpired(timeSinceLoanCreated, LOAN_LIQUIDATION_DURATION);
422
426
  }
423
427
 
@@ -426,15 +430,15 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
426
430
 
427
431
  uint256 fullSourceFeeAmount = JBFees.feeAmountFrom({
428
432
  amountBeforeFee: loan.amount - prepaid,
429
- feePercent: mulDiv(
430
- timeSinceLoanCreated - loan.prepaidDuration,
431
- JBConstants.MAX_FEE,
432
- LOAN_LIQUIDATION_DURATION - loan.prepaidDuration
433
- )
433
+ feePercent: mulDiv({
434
+ x: timeSinceLoanCreated - loan.prepaidDuration,
435
+ y: JBConstants.MAX_FEE,
436
+ denominator: LOAN_LIQUIDATION_DURATION - loan.prepaidDuration
437
+ })
434
438
  });
435
439
 
436
440
  // Calculate the source fee amount for the amount being paid off.
437
- return mulDiv(fullSourceFeeAmount, amount, loan.amount);
441
+ return mulDiv({x: fullSourceFeeAmount, y: amount, denominator: loan.amount});
438
442
  }
439
443
 
440
444
  /// @notice Generate a ID for a loan given a revnet ID and a loan number within that revnet.
@@ -461,9 +465,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
461
465
  /// @dev Each source's `totalBorrowedFrom` is stored in the source token's native decimals (e.g. 6 for USDC,
462
466
  /// 18 for ETH). Before aggregation, each amount is normalized to the target `decimals` to prevent mixed-decimal
463
467
  /// arithmetic errors. For cross-currency sources, the normalized amount is then converted via the price feed.
464
- /// @dev Callers should ensure the price feed has sufficient precision for the target `decimals`. Inverse price
465
- /// feeds may truncate to zero at low decimal counts (e.g. a feed returning 1e21 at 6 decimals inverts to
466
- /// mulDiv(1e6, 1e6, 1e21) = 0), which would cause a division-by-zero in the price conversion.
468
+ /// @dev Inverse price feeds may truncate to zero at low decimal counts (e.g. a feed returning 1e21 at 6 decimals
469
+ /// inverts to mulDiv(1e6, 1e6, 1e21) = 0). Sources with a zero price are skipped to prevent division-by-zero.
467
470
  /// @param revnetId The ID of the revnet to check for borrowed assets from.
468
471
  /// @param decimals The decimals the resulting fixed point value will include.
469
472
  /// @param currency The currency the resulting value will be in terms of.
@@ -519,7 +522,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
519
522
  decimals: decimals
520
523
  });
521
524
 
522
- borrowedAmount += mulDiv(normalizedTokens, 10 ** decimals, pricePerUnit);
525
+ // If the price feed returns zero, skip this source to avoid a division-by-zero panic
526
+ // that would DoS all loan operations. This intentionally understates total debt for
527
+ // the affected source — an acceptable tradeoff vs. blocking every borrow/repay.
528
+ if (pricePerUnit == 0) continue;
529
+
530
+ borrowedAmount += mulDiv({x: normalizedTokens, y: 10 ** decimals, denominator: pricePerUnit});
523
531
  }
524
532
  }
525
533
  }
@@ -529,6 +537,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
529
537
  //*********************************************************************//
530
538
 
531
539
  /// @notice Open a loan by borrowing from a revnet.
540
+ /// @dev The caller must first grant BURN_TOKENS permission to this contract via JBPermissions.setPermissionsFor().
541
+ /// This is required because collateral posting burns the caller's tokens through the controller.
532
542
  /// @dev Collateral tokens are permanently burned when the loan is created. They are re-minted to the borrower
533
543
  /// only upon repayment. If the loan expires (after LOAN_LIQUIDATION_DURATION), the collateral is permanently
534
544
  /// lost and cannot be recovered.
@@ -558,7 +568,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
558
568
  if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
559
569
 
560
570
  // Make sure the source terminal is registered in the directory for this revnet.
561
- if (!DIRECTORY.isTerminalOf(revnetId, IJBTerminal(address(source.terminal)))) {
571
+ if (!DIRECTORY.isTerminalOf({projectId: revnetId, terminal: IJBTerminal(address(source.terminal))})) {
562
572
  revert REVLoans_InvalidTerminal(address(source.terminal), revnetId);
563
573
  }
564
574
 
@@ -579,17 +589,23 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
579
589
 
580
590
  // Set the loan's values.
581
591
  loan.source = source;
582
- loan.createdAt = uint40(block.timestamp);
592
+ loan.createdAt = uint48(block.timestamp);
593
+ // forge-lint: disable-next-line(unsafe-typecast)
583
594
  loan.prepaidFeePercent = uint16(prepaidFeePercent);
584
- loan.prepaidDuration = uint32(mulDiv(prepaidFeePercent, LOAN_LIQUIDATION_DURATION, MAX_PREPAID_FEE_PERCENT));
595
+ loan.prepaidDuration =
596
+ uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
585
597
 
586
598
  // Get the amount of the loan.
587
599
  uint256 borrowAmount = _borrowAmountFrom({loan: loan, revnetId: revnetId, collateralCount: collateralCount});
588
600
 
601
+ // Revert if the bonding curve returns zero to prevent creating zero-amount loans.
602
+ if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
603
+
589
604
  // Make sure the minimum borrow amount is met.
590
605
  if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
591
606
 
592
607
  // Get the amount of additional fee to take for the revnet issuing the loan.
608
+ // Fee rounding may leave a few wei of dust — economically insignificant relative to gas costs.
593
609
  uint256 sourceFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
594
610
 
595
611
  // Borrow the amount.
@@ -634,6 +650,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
634
650
  /// @param startingLoanId The ID of the loan to start iterating from.
635
651
  /// @param count The amount of loans iterate over since the last liquidated loan.
636
652
  function liquidateExpiredLoansFrom(uint256 revnetId, uint256 startingLoanId, uint256 count) external override {
653
+ // Prevent cross-revnet accounting corruption: loan numbers must stay within the revnet's ID namespace.
654
+ if (startingLoanId + count > _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
655
+
637
656
  // Iterate over the desired number of loans to check for liquidation.
638
657
  for (uint256 i; i < count; i++) {
639
658
  // Get a reference to the next loan ID.
@@ -794,7 +813,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
794
813
  if (repayBorrowAmount == 0 && collateralCountToReturn == 0) revert REVLoans_NothingToRepay();
795
814
 
796
815
  // Keep a reference to the fee that'll be taken.
797
- uint256 sourceFeeAmount = _determineSourceFeeAmount(loan, repayBorrowAmount);
816
+ uint256 sourceFeeAmount = _determineSourceFeeAmount({loan: loan, amount: repayBorrowAmount});
798
817
 
799
818
  // Add the fee to the repay amount.
800
819
  repayBorrowAmount += sourceFeeAmount;
@@ -927,8 +946,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
927
946
  internal
928
947
  {
929
948
  // Register the source if this is the first time its being used for this revnet.
930
- // Note: Sources are only appended, never removed. This is acceptable because the number of distinct
931
- // (terminal, token) pairs per revnet is practically bounded.
949
+ // Note: Sources are only appended, never removed. Gas accumulation from iteration is bounded
950
+ // because the number of distinct (terminal, token) pairs per revnet is practically small (~5-20).
932
951
  if (!isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token]) {
933
952
  isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token] = true;
934
953
  _loanSourcesOf[revnetId].push(REVLoanSource({token: loan.source.token, terminal: loan.source.terminal}));
@@ -994,6 +1013,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
994
1013
  }
995
1014
 
996
1015
  // Transfer the remaining balance to the borrower.
1016
+ // Note: In extreme fee configurations the subtraction could theoretically underflow, but the
1017
+ // protocol fee (2.5%) and source fee (capped at prepaidFeePercent) are both small fractions of
1018
+ // the borrowed amount, so `netAmountPaidOut` will always exceed their sum in practice.
997
1019
  _transferFrom({
998
1020
  from: address(this),
999
1021
  to: beneficiary,
@@ -1034,7 +1056,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1034
1056
  if (newCollateralCount > type(uint112).max) {
1035
1057
  revert REVLoans_OverflowAlert(newCollateralCount, type(uint112).max);
1036
1058
  }
1059
+ // forge-lint: disable-next-line(unsafe-typecast)
1037
1060
  loan.amount = uint112(newBorrowAmount);
1061
+ // forge-lint: disable-next-line(unsafe-typecast)
1038
1062
  loan.collateral = uint112(newCollateralCount);
1039
1063
 
1040
1064
  // INTERACTIONS: Execute external calls with pre-computed deltas.
@@ -1063,16 +1087,17 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1063
1087
  });
1064
1088
  }
1065
1089
 
1066
- // If there is a source fee, pay it.
1090
+ // If there is a source fee, pay it. Wrapped in try-catch so a reverting source terminal
1091
+ // cannot block all loan operations (matching the REV fee pattern above).
1067
1092
  if (sourceFeeAmount > 0) {
1068
- // Increase the allowance for the beneficiary.
1093
+ // Increase the allowance for the source terminal.
1069
1094
  uint256 payValue = _beforeTransferTo({
1070
1095
  to: address(loan.source.terminal), token: loan.source.token, amount: sourceFeeAmount
1071
1096
  });
1072
1097
 
1073
- // Pay the fee.
1074
- // slither-disable-next-line unused-return
1075
- loan.source.terminal.pay{value: payValue}({
1098
+ // Pay the fee. If it fails, reclaim the allowance and give the amount back to the borrower.
1099
+ // slither-disable-next-line unused-return,arbitrary-send-eth
1100
+ try loan.source.terminal.pay{value: payValue}({
1076
1101
  projectId: revnetId,
1077
1102
  token: loan.source.token,
1078
1103
  amount: sourceFeeAmount,
@@ -1080,7 +1105,18 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1080
1105
  minReturnedTokens: 0,
1081
1106
  memo: "Fee from loan",
1082
1107
  metadata: bytes(abi.encodePacked(REV_ID))
1083
- });
1108
+ }) {}
1109
+ catch (bytes memory) {
1110
+ // If the fee can't be processed, decrease the ERC-20 allowance and return the amount
1111
+ // to the beneficiary instead.
1112
+ if (loan.source.token != JBConstants.NATIVE_TOKEN) {
1113
+ IERC20(loan.source.token)
1114
+ .safeDecreaseAllowance({
1115
+ spender: address(loan.source.terminal), requestedDecrease: sourceFeeAmount
1116
+ });
1117
+ }
1118
+ _transferFrom({from: address(this), to: beneficiary, token: loan.source.token, amount: sourceFeeAmount});
1119
+ }
1084
1120
  }
1085
1121
  }
1086
1122
 
@@ -1093,7 +1129,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1093
1129
  function _beforeTransferTo(address to, address token, uint256 amount) internal returns (uint256) {
1094
1130
  // If the token is the native token, no allowance needed.
1095
1131
  if (token == JBConstants.NATIVE_TOKEN) return amount;
1096
- IERC20(token).safeIncreaseAllowance(to, amount);
1132
+ IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
1097
1133
  return 0;
1098
1134
  }
1099
1135
 
@@ -1342,7 +1378,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1342
1378
  }
1343
1379
 
1344
1380
  // If there's sufficient approval, transfer normally.
1345
- if (IERC20(token).allowance(address(from), address(this)) >= amount) {
1381
+ if (IERC20(token).allowance({owner: address(from), spender: address(this)}) >= amount) {
1346
1382
  return IERC20(token).safeTransferFrom({from: from, to: to, value: amount});
1347
1383
  }
1348
1384
 
@@ -1350,6 +1386,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1350
1386
  if (amount > type(uint160).max) revert REVLoans_OverflowAlert(amount, type(uint160).max);
1351
1387
 
1352
1388
  // Otherwise, attempt to use the `permit2` method.
1389
+ // forge-lint: disable-next-line(unsafe-typecast)
1353
1390
  PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
1354
1391
  }
1355
1392
 
@@ -8,7 +8,6 @@ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
8
8
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
9
9
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
10
10
  import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
11
- import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
12
11
  import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
13
12
  import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
14
13
  import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
@@ -182,52 +181,55 @@ interface IREVDeployer {
182
181
  /// @param revnetId The ID of the revnet whose tokens should be burned.
183
182
  function burnHeldTokensOf(uint256 revnetId) external;
184
183
 
185
- /// @notice Deploy a revnet, or initialize an existing Juicebox project as a revnet.
184
+ /// @notice Deploy a revnet with a tiered ERC-721 hook and optional croptop posting support.
185
+ /// @dev Every revnet gets a 721 hook — pass an empty config if no tiers are needed initially.
186
186
  /// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
187
187
  /// @param configuration Core revnet configuration.
188
188
  /// @param terminalConfigurations The terminals to set up for the revnet.
189
189
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
190
+ /// @param tiered721HookConfiguration How to set up the tiered ERC-721 hook for the revnet.
191
+ /// @param allowedPosts Restrictions on which croptop posts are allowed on the revnet's ERC-721 tiers.
190
192
  /// @return The ID of the newly created or initialized revnet.
193
+ /// @return hook The tiered ERC-721 hook that was deployed for the revnet.
191
194
  function deployFor(
192
195
  uint256 revnetId,
193
196
  REVConfig memory configuration,
194
197
  JBTerminalConfig[] memory terminalConfigurations,
195
- REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
196
- )
197
- external
198
- returns (uint256);
199
-
200
- /// @notice Deploy new suckers for an existing revnet.
201
- /// @param revnetId The ID of the revnet to deploy suckers for.
202
- /// @param suckerDeploymentConfiguration The suckers to set up for the revnet.
203
- /// @return suckers The addresses of the deployed suckers.
204
- function deploySuckersFor(
205
- uint256 revnetId,
206
- REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
198
+ REVSuckerDeploymentConfig memory suckerDeploymentConfiguration,
199
+ REVDeploy721TiersHookConfig memory tiered721HookConfiguration,
200
+ REVCroptopAllowedPost[] memory allowedPosts
207
201
  )
208
202
  external
209
- returns (address[] memory suckers);
203
+ returns (uint256, IJB721TiersHook hook);
210
204
 
211
- /// @notice Deploy a revnet with tiered ERC-721s and optional croptop posting support.
205
+ /// @notice Deploy a revnet with a default empty tiered ERC-721 hook.
206
+ /// @dev Convenience overload — constructs an empty 721 config internally and delegates to the 6-arg version.
212
207
  /// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
213
208
  /// @param configuration Core revnet configuration.
214
209
  /// @param terminalConfigurations The terminals to set up for the revnet.
215
210
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
216
- /// @param tiered721HookConfiguration How to set up the tiered ERC-721 hook.
217
- /// @param allowedPosts Restrictions on which croptop posts are allowed on the revnet's ERC-721 tiers.
218
211
  /// @return The ID of the newly created or initialized revnet.
219
212
  /// @return hook The tiered ERC-721 hook that was deployed for the revnet.
220
- function deployWith721sFor(
213
+ function deployFor(
221
214
  uint256 revnetId,
222
- REVConfig calldata configuration,
215
+ REVConfig memory configuration,
223
216
  JBTerminalConfig[] memory terminalConfigurations,
224
- REVSuckerDeploymentConfig memory suckerDeploymentConfiguration,
225
- REVDeploy721TiersHookConfig memory tiered721HookConfiguration,
226
- REVCroptopAllowedPost[] memory allowedPosts
217
+ REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
227
218
  )
228
219
  external
229
220
  returns (uint256, IJB721TiersHook hook);
230
221
 
222
+ /// @notice Deploy new suckers for an existing revnet.
223
+ /// @param revnetId The ID of the revnet to deploy suckers for.
224
+ /// @param suckerDeploymentConfiguration The suckers to set up for the revnet.
225
+ /// @return suckers The addresses of the deployed suckers.
226
+ function deploySuckersFor(
227
+ uint256 revnetId,
228
+ REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
229
+ )
230
+ external
231
+ returns (address[] memory suckers);
232
+
231
233
  /// @notice Change a revnet's split operator. Only the current split operator can call this.
232
234
  /// @param revnetId The ID of the revnet.
233
235
  /// @param newSplitOperator The new split operator's address.
@@ -6,6 +6,7 @@ pragma solidity ^0.8.0;
6
6
  /// @custom:member noNewTiersWithOwnerMinting A flag indicating if new tiers with owner minting are forbidden.
7
7
  /// @custom:member preventOverspending A flag indicating if payments exceeding the price of minted NFTs should be
8
8
  /// prevented.
9
+ // forge-lint: disable-next-line(pascal-case-struct)
9
10
  struct REV721TiersHookFlags {
10
11
  bool noNewTiersWithReserves;
11
12
  bool noNewTiersWithVotes;
@@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
4
4
  /// @custom:member chainId The ID of the chain on which the mint should be honored.
5
5
  /// @custom:member count The number of tokens that should be minted.
6
6
  /// @custom:member beneficiary The address that will receive the minted tokens.
7
+ // forge-lint: disable-next-line(pascal-case-struct)
7
8
  struct REVAutoIssuance {
8
9
  uint32 chainId;
9
10
  uint104 count;
@@ -15,6 +15,7 @@ import {REV721TiersHookFlags} from "./REV721TiersHookFlags.sol";
15
15
  /// @custom:member reserveBeneficiary The default reserve beneficiary for the NFT collection.
16
16
  /// @custom:member flags A set of flags that configure the 721 hook. Omits `issueTokensForSplits` since revnets
17
17
  /// always force it to `false`.
18
+ // forge-lint: disable-next-line(pascal-case-struct)
18
19
  struct REVBaseline721HookConfig {
19
20
  string name;
20
21
  string symbol;
@@ -9,6 +9,7 @@ import {REVStageConfig} from "./REVStageConfig.sol";
9
9
  /// @custom:member splitOperator The address that will receive the token premint and initial production split,
10
10
  /// and who is allowed to change who the operator is. Only the operator can replace itself after deployment.
11
11
  /// @custom:member stageConfigurations The periods of changing constraints.
12
+ // forge-lint: disable-next-line(pascal-case-struct)
12
13
  struct REVConfig {
13
14
  REVDescription description;
14
15
  uint32 baseCurrency;
@@ -10,6 +10,7 @@ pragma solidity ^0.8.0;
10
10
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
11
11
  /// poster can set. 0 means splits are not allowed.
12
12
  /// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
13
+ // forge-lint: disable-next-line(pascal-case-struct)
13
14
  struct REVCroptopAllowedPost {
14
15
  uint24 category;
15
16
  uint104 minimumPrice;
@@ -5,21 +5,20 @@ import {REVBaseline721HookConfig} from "./REVBaseline721HookConfig.sol";
5
5
 
6
6
  /// @custom:member baseline721HookConfiguration The baseline config.
7
7
  /// @custom:member salt The salt to base the collection's address on.
8
- /// @custom:member splitOperatorCanAdjustTiers A flag indicating if the revnet's split operator can add tiers and remove
9
- /// tiers if
10
- /// the tier is allowed to be removed
11
- /// @custom:member splitOperatorCanUpdateMetadata A flag indicating if the revnet's split operator can update the 721's
12
- /// metadata.
13
- /// @custom:member splitOperatorCanMint A flag indicating if the revnet's split operator can mint 721's from tiers that
14
- /// allow it.
15
- /// @custom:member splitOperatorCanIncreaseDiscountPercent A flag indicating if the revnet's split operator can increase
16
- /// the
17
- /// discount of a tier.
8
+ /// @custom:member preventSplitOperatorAdjustingTiers A flag indicating if the revnet's split operator should be
9
+ /// prevented from adding tiers and removing tiers that are allowed to be removed.
10
+ /// @custom:member preventSplitOperatorUpdatingMetadata A flag indicating if the revnet's split operator should be
11
+ /// prevented from updating the 721's metadata.
12
+ /// @custom:member preventSplitOperatorMinting A flag indicating if the revnet's split operator should be prevented from
13
+ /// minting 721's from tiers that allow it.
14
+ /// @custom:member preventSplitOperatorIncreasingDiscountPercent A flag indicating if the revnet's split operator should
15
+ /// be prevented from increasing the discount of a tier.
16
+ // forge-lint: disable-next-line(pascal-case-struct)
18
17
  struct REVDeploy721TiersHookConfig {
19
18
  REVBaseline721HookConfig baseline721HookConfiguration;
20
19
  bytes32 salt;
21
- bool splitOperatorCanAdjustTiers;
22
- bool splitOperatorCanUpdateMetadata;
23
- bool splitOperatorCanMint;
24
- bool splitOperatorCanIncreaseDiscountPercent;
20
+ bool preventSplitOperatorAdjustingTiers;
21
+ bool preventSplitOperatorUpdatingMetadata;
22
+ bool preventSplitOperatorMinting;
23
+ bool preventSplitOperatorIncreasingDiscountPercent;
25
24
  }
@@ -6,6 +6,7 @@ pragma solidity ^0.8.0;
6
6
  /// @custom:member uri The metadata URI containing revnet's info.
7
7
  /// @custom:member salt Revnets deployed across chains by the same address with the same salt will have the same
8
8
  /// address.
9
+ // forge-lint: disable-next-line(pascal-case-struct)
9
10
  struct REVDescription {
10
11
  string name;
11
12
  string ticker;
@@ -9,6 +9,7 @@ import {REVLoanSource} from "./REVLoanSource.sol";
9
9
  /// @custom:member prepaidFeePercent The percentage of the loan's fees that were prepaid.
10
10
  /// @custom:member prepaidDuration The duration that the loan was prepaid for.
11
11
  /// @custom:member source The source of the loan.
12
+ // forge-lint: disable-next-line(pascal-case-struct)
12
13
  struct REVLoan {
13
14
  uint112 amount;
14
15
  uint112 collateral;
@@ -5,6 +5,7 @@ import {IJBPayoutTerminal} from "@bananapus/core-v6/src/interfaces/IJBPayoutTerm
5
5
 
6
6
  /// @custom:member token The token that is being loaned.
7
7
  /// @custom:member terminal The terminal that the loan is being made from.
8
+ // forge-lint: disable-next-line(pascal-case-struct)
8
9
  struct REVLoanSource {
9
10
  address token;
10
11
  IJBPayoutTerminal terminal;
@@ -21,6 +21,7 @@ import {REVAutoIssuance} from "./REVAutoIssuance.sol";
21
21
  /// cashed out. This rate is out of 10_000 (JBConstants.MAX_CASH_OUT_TAX_RATE). 0% corresponds to no tax when cashing
22
22
  /// out.
23
23
  /// @custom:member extraMetadata Extra info to attach set into this stage that may affect hooks.
24
+ // forge-lint: disable-next-line(pascal-case-struct)
24
25
  struct REVStageConfig {
25
26
  uint48 startsAtOrAfter;
26
27
  REVAutoIssuance[] autoIssuances;
@@ -5,6 +5,7 @@ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSucker
5
5
 
6
6
  /// @custom:member deployerConfigurations The information for how to suck tokens to other chains.
7
7
  /// @custom:member salt The salt to use for creating suckers so that they use the same address across chains.
8
+ // forge-lint: disable-next-line(pascal-case-struct)
8
9
  struct REVSuckerDeploymentConfig {
9
10
  JBSuckerDeployerConfig[] deployerConfigurations;
10
11
  bytes32 salt;