@rev-net/core-v6 0.0.43 → 0.0.44

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/README.md CHANGED
@@ -72,7 +72,7 @@ Most mistakes come from assuming a deploy-time parameter can be changed later or
72
72
  2. `test/REVLoans.invariants.t.sol`
73
73
  3. `test/TestLongTailEconomics.t.sol`
74
74
  4. `test/fork/TestLoanBorrowFork.t.sol`
75
- 5. `test/audit/PhantomSurplusTerminal.t.sol`
75
+ 5. `test/regression/PhantomSurplusTerminal.t.sol`
76
76
 
77
77
  ## Install
78
78
 
@@ -108,7 +108,7 @@ src/
108
108
  interfaces/
109
109
  structs/
110
110
  test/
111
- lifecycle, deployment, loan, fork, invariant, audit, and regression coverage
111
+ lifecycle, deployment, loan, fork, invariant, review, and regression coverage
112
112
  script/
113
113
  Deploy.s.sol
114
114
  helpers/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -57,15 +57,15 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
57
57
  // --------------------------- custom errors ------------------------- //
58
58
  //*********************************************************************//
59
59
 
60
- error REVDeployer_AutoIssuanceBeneficiaryZeroAddress();
60
+ error REVDeployer_AutoIssuanceBeneficiaryZeroAddress(uint256 stageIndex, uint256 autoIssuanceIndex);
61
61
  error REVDeployer_CashOutsCantBeTurnedOffCompletely(uint256 cashOutTaxRate, uint256 maxCashOutTaxRate);
62
- error REVDeployer_MustHaveSplits();
63
- error REVDeployer_NothingToAutoIssue();
64
- error REVDeployer_NothingToBurn();
65
- error REVDeployer_RulesetDoesNotAllowDeployingSuckers();
62
+ error REVDeployer_MustHaveSplits(uint256 stageIndex, uint256 splitPercent);
63
+ error REVDeployer_NothingToAutoIssue(uint256 revnetId, uint256 stageId, address beneficiary);
64
+ error REVDeployer_NothingToBurn(uint256 revnetId, address holder);
65
+ error REVDeployer_RulesetDoesNotAllowDeployingSuckers(uint256 revnetId);
66
66
  error REVDeployer_StageNotStarted(uint256 stageId);
67
- error REVDeployer_StagesRequired();
68
- error REVDeployer_StageTimesMustIncrease();
67
+ error REVDeployer_StagesRequired(uint256 stageCount);
68
+ error REVDeployer_StageTimesMustIncrease(uint256 stageIndex, uint256 previousStageStart, uint256 effectiveStart);
69
69
  error REVDeployer_Unauthorized(uint256 revnetId, address caller);
70
70
 
71
71
  //*********************************************************************//
@@ -164,7 +164,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
164
164
  /// @notice A list of `JBPermissonIds` indices to grant to the split operator of a specific revnet.
165
165
  /// @dev These should be set in the revnet's deployment process.
166
166
  /// @custom:param revnetId The ID of the revnet to look up.
167
- // slither-disable-next-line uninitialized-state
168
167
  mapping(uint256 revnetId => uint256[]) internal _extraOperatorPermissions;
169
168
 
170
169
  //*********************************************************************//
@@ -203,7 +202,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
203
202
  PUBLISHER = publisher;
204
203
  BUYBACK_HOOK = buybackHook;
205
204
  LOANS = loans;
206
- // slither-disable-next-line missing-zero-check
207
205
  OWNER = owner;
208
206
 
209
207
  // Give the loan contract permission to use the surplus allowance of all revnets.
@@ -262,7 +260,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
262
260
  /// @param operator The address to check.
263
261
  function _checkIfIsSplitOperatorOf(uint256 revnetId, address operator) internal view {
264
262
  if (!isSplitOperatorOf({revnetId: revnetId, addr: operator})) {
265
- revert REVDeployer_Unauthorized(revnetId, operator);
263
+ revert REVDeployer_Unauthorized({revnetId: revnetId, caller: operator});
266
264
  }
267
265
  }
268
266
 
@@ -399,29 +397,36 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
399
397
  /// Silently catches failures (e.g., if the pool is already initialized).
400
398
  /// @param revnetId The ID of the revnet to initialize a pool for.
401
399
  /// @param terminalToken The terminal token to create a buyback pool for.
400
+ /// @param terminalTokenDecimals The number of decimals the terminal token uses.
402
401
  /// @param initialIssuance The initial issuance rate (project tokens per terminal token, 18 decimals).
403
- function _tryInitializeBuybackPoolFor(uint256 revnetId, address terminalToken, uint112 initialIssuance) internal {
402
+ function _tryInitializeBuybackPoolFor(
403
+ uint256 revnetId,
404
+ address terminalToken,
405
+ uint8 terminalTokenDecimals,
406
+ uint112 initialIssuance
407
+ )
408
+ internal
409
+ {
404
410
  uint160 sqrtPriceX96;
411
+ uint256 terminalTokenUnit = 10 ** terminalTokenDecimals;
405
412
 
406
413
  if (initialIssuance == 0) {
407
414
  sqrtPriceX96 = uint160(1 << 96);
408
415
  } else {
409
416
  address normalizedTerminalToken = terminalToken == JBConstants.NATIVE_TOKEN ? address(0) : terminalToken;
410
- // slither-disable-next-line calls-loop
411
417
  address projectToken = address(CONTROLLER.TOKENS().tokenOf(revnetId));
412
418
 
413
419
  if (projectToken == address(0) || projectToken == normalizedTerminalToken) {
414
420
  sqrtPriceX96 = uint160(1 << 96);
415
421
  } else if (normalizedTerminalToken < projectToken) {
416
- // token0 = terminal, token1 = project → price = issuance / 1e18
417
- sqrtPriceX96 = uint160(sqrt(mulDiv(uint256(initialIssuance), 1 << 192, 1e18)));
422
+ // token0 = terminal, token1 = project → price = issuance / terminalTokenUnit
423
+ sqrtPriceX96 = uint160(sqrt(mulDiv(uint256(initialIssuance), 1 << 192, terminalTokenUnit)));
418
424
  } else {
419
- // token0 = project, token1 = terminal → price = 1e18 / issuance
420
- sqrtPriceX96 = uint160(sqrt(mulDiv(1e18, 1 << 192, uint256(initialIssuance))));
425
+ // token0 = project, token1 = terminal → price = terminalTokenUnit / issuance
426
+ sqrtPriceX96 = uint160(sqrt(mulDiv(terminalTokenUnit, 1 << 192, uint256(initialIssuance))));
421
427
  }
422
428
  }
423
429
 
424
- // slither-disable-next-line calls-loop
425
430
  try BUYBACK_HOOK.initializePoolFor({
426
431
  projectId: revnetId,
427
432
  fee: DEFAULT_BUYBACK_POOL_FEE,
@@ -449,19 +454,21 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
449
454
  // IDs `block.timestamp`, `block.timestamp + 1`, ... exactly correspond to the JB-assigned ruleset IDs.
450
455
  // The returned `ruleset.start` contains the derived start time (from `deriveStartFrom` using the stage's
451
456
  // `mustStartAtOrAfter`), NOT the queue timestamp — so the timing guard correctly blocks early claims.
452
- // slither-disable-next-line unused-return
453
457
  (JBRuleset memory ruleset,) = CONTROLLER.getRulesetOf({projectId: revnetId, rulesetId: stageId});
454
458
 
455
459
  // Make sure the stage has started.
460
+ // forge-lint: disable-next-line(block-timestamp)
456
461
  if (ruleset.start > block.timestamp) {
457
- revert REVDeployer_StageNotStarted(stageId);
462
+ revert REVDeployer_StageNotStarted({stageId: stageId});
458
463
  }
459
464
 
460
465
  // Get a reference to the number of tokens to auto-issue.
461
466
  uint256 count = amountToAutoIssue[revnetId][stageId][beneficiary];
462
467
 
463
468
  // If there's nothing to auto-mint, return.
464
- if (count == 0) revert REVDeployer_NothingToAutoIssue();
469
+ if (count == 0) {
470
+ revert REVDeployer_NothingToAutoIssue({revnetId: revnetId, stageId: stageId, beneficiary: beneficiary});
471
+ }
465
472
 
466
473
  // Reset the auto-mint amount.
467
474
  amountToAutoIssue[revnetId][stageId][beneficiary] = 0;
@@ -471,7 +478,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
471
478
  });
472
479
 
473
480
  // Mint the tokens.
474
- // slither-disable-next-line unused-return
475
481
  CONTROLLER.mintTokensOf({
476
482
  projectId: revnetId, tokenCount: count, beneficiary: beneficiary, memo: "", useReservedPercent: false
477
483
  });
@@ -482,10 +488,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
482
488
  /// @param revnetId The ID of the revnet to burn tokens for.
483
489
  function burnHeldTokensOf(uint256 revnetId) external override {
484
490
  uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
485
- if (balance == 0) revert REVDeployer_NothingToBurn();
491
+ if (balance == 0) revert REVDeployer_NothingToBurn({revnetId: revnetId, holder: address(this)});
486
492
  CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
487
- // slither-disable-next-line reentrancy-events
488
- emit BurnHeldTokens(revnetId, balance, _msgSender());
493
+ emit BurnHeldTokens({revnetId: revnetId, count: balance, caller: _msgSender()});
489
494
  }
490
495
 
491
496
  /// @notice Launch a revnet, or initialize an existing Juicebox project as a revnet.
@@ -506,7 +511,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
506
511
  /// @return revnetId The ID of the newly created revnet.
507
512
  /// @return hook The address of the tiered ERC-721 hook deployed for the revnet.
508
513
  // The deployment flow makes external setup calls, but any observed state is revnet-scoped and reverts atomically.
509
- // slither-disable-next-line reentrancy-benign
510
514
  function deployFor(
511
515
  uint256 revnetId,
512
516
  REVConfig calldata configuration,
@@ -541,7 +545,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
541
545
 
542
546
  /// @inheritdoc IREVDeployer
543
547
  // The deployment flow makes external setup calls, but any observed state is revnet-scoped and reverts atomically.
544
- // slither-disable-next-line reentrancy-benign
545
548
  function deployFor(
546
549
  uint256 revnetId,
547
550
  REVConfig calldata configuration,
@@ -610,14 +613,13 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
610
613
  _checkIfIsSplitOperatorOf({revnetId: revnetId, operator: _msgSender()});
611
614
 
612
615
  // Check if the current ruleset allows deploying new suckers.
613
- // slither-disable-next-line unused-return
614
616
  (, JBRulesetMetadata memory metadata) = CONTROLLER.currentRulesetOf(revnetId);
615
617
 
616
618
  // Check the third bit, it indicates if the ruleset allows new suckers to be deployed.
617
619
  bool allowsDeployingSuckers = ((metadata.metadata >> 2) & 1) == 1;
618
620
 
619
621
  if (!allowsDeployingSuckers) {
620
- revert REVDeployer_RulesetDoesNotAllowDeployingSuckers();
622
+ revert REVDeployer_RulesetDoesNotAllowDeployingSuckers({revnetId: revnetId});
621
623
  }
622
624
 
623
625
  // Deploy the suckers.
@@ -655,7 +657,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
655
657
 
656
658
  /// @notice Deploy a revnet which sells tiered ERC-721s and (optionally) allows croptop posts to its ERC-721 tiers.
657
659
  // The helper performs external hook/post setup after core revnet setup; any failure reverts the whole deployment.
658
- // slither-disable-next-line reentrancy-benign
659
660
  function _deploy721RevnetFor(
660
661
  uint256 revnetId,
661
662
  bool shouldDeployNewRevnet,
@@ -814,12 +815,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
814
815
  if (!shouldDeployNewRevnet) {
815
816
  // Initialize the existing Juicebox project as a revnet by transferring the `JBProjects` NFT to this
816
817
  // deployer. This is irreversible.
817
- // slither-disable-next-line reentrancy-benign
818
818
  IERC721(PROJECTS).safeTransferFrom({from: owner, to: address(this), tokenId: revnetId});
819
819
  }
820
820
 
821
821
  // Launch the revnet rulesets for the reserved or pre-existing blank project.
822
- // slither-disable-next-line unused-return
823
822
  CONTROLLER.launchRulesetsFor({
824
823
  projectId: revnetId,
825
824
  projectUri: configuration.description.uri,
@@ -831,11 +830,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
831
830
  // Store the cash out delay of the revnet if its stages are already in progress.
832
831
  // This prevents cash out liquidity/arbitrage issues for existing revnets which
833
832
  // are deploying to a new chain.
834
- // slither-disable-next-line reentrancy-events
835
833
  _setCashOutDelayIfNeeded({revnetId: revnetId, firstStageConfig: configuration.stageConfigurations[0]});
836
834
 
837
835
  // Deploy the revnet's ERC-20 token.
838
- // slither-disable-next-line unused-return
839
836
  CONTROLLER.deployERC20For({
840
837
  projectId: revnetId,
841
838
  name: configuration.description.name,
@@ -847,10 +844,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
847
844
  for (uint256 i; i < terminalConfigurations.length;) {
848
845
  JBTerminalConfig calldata terminalConfiguration = terminalConfigurations[i];
849
846
  for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length;) {
850
- // slither-disable-next-line calls-loop
851
847
  _tryInitializeBuybackPoolFor({
852
848
  revnetId: revnetId,
853
849
  terminalToken: terminalConfiguration.accountingContextsToAccept[j].token,
850
+ terminalTokenDecimals: terminalConfiguration.accountingContextsToAccept[j].decimals,
854
851
  initialIssuance: configuration.stageConfigurations[0].initialIssuance
855
852
  });
856
853
  unchecked {
@@ -901,7 +898,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
901
898
 
902
899
  // Include the caller so two revnets with identical configuration and user salt cannot collide. Same-address
903
900
  // cross-chain deployment still works when the same operator calls this helper on each chain.
904
- // slither-disable-next-line unused-return
905
901
  suckers = SUCKER_REGISTRY.deploySuckersFor({
906
902
  projectId: revnetId,
907
903
  salt: keccak256(abi.encode(encodedConfigurationHash, suckerDeploymentConfiguration.salt, _msgSender())),
@@ -934,7 +930,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
934
930
  returns (JBRulesetConfig[] memory rulesetConfigurations, bytes32 encodedConfigurationHash)
935
931
  {
936
932
  // If there are no stages, revert.
937
- if (configuration.stageConfigurations.length == 0) revert REVDeployer_StagesRequired();
933
+ if (configuration.stageConfigurations.length == 0) {
934
+ revert REVDeployer_StagesRequired({stageCount: configuration.stageConfigurations.length});
935
+ }
938
936
 
939
937
  // Initialize the array of rulesets.
940
938
  rulesetConfigurations = new JBRulesetConfig[](configuration.stageConfigurations.length);
@@ -975,7 +973,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
975
973
  // Make sure the revnet has at least one split if it has a split percent.
976
974
  // Otherwise, the split would go to this contract since its the revnet's owner.
977
975
  if (stageConfiguration.splitPercent > 0 && stageConfiguration.splits.length == 0) {
978
- revert REVDeployer_MustHaveSplits();
976
+ revert REVDeployer_MustHaveSplits({stageIndex: i, splitPercent: stageConfiguration.splitPercent});
979
977
  }
980
978
 
981
979
  // Compute the effective start time for this stage.
@@ -985,7 +983,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
985
983
 
986
984
  // If the stage's start time is not after the previous stage's start time, revert.
987
985
  if (i > 0 && effectiveStart <= previousStageStart) {
988
- revert REVDeployer_StageTimesMustIncrease();
986
+ revert REVDeployer_StageTimesMustIncrease({
987
+ stageIndex: i, previousStageStart: previousStageStart, effectiveStart: effectiveStart
988
+ });
989
989
  }
990
990
 
991
991
  // Store for the next iteration's ordering check.
@@ -993,9 +993,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
993
993
 
994
994
  // Make sure the revnet doesn't prevent cashouts all together.
995
995
  if (stageConfiguration.cashOutTaxRate >= JBConstants.MAX_CASH_OUT_TAX_RATE) {
996
- revert REVDeployer_CashOutsCantBeTurnedOffCompletely(
997
- stageConfiguration.cashOutTaxRate, JBConstants.MAX_CASH_OUT_TAX_RATE
998
- );
996
+ revert REVDeployer_CashOutsCantBeTurnedOffCompletely({
997
+ cashOutTaxRate: stageConfiguration.cashOutTaxRate,
998
+ maxCashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE
999
+ });
999
1000
  }
1000
1001
 
1001
1002
  // Set up the ruleset.
@@ -1027,7 +1028,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1027
1028
  REVAutoIssuance calldata autoIssuance = stageConfiguration.autoIssuances[j];
1028
1029
 
1029
1030
  // Make sure the beneficiary is not the zero address.
1030
- if (autoIssuance.beneficiary == address(0)) revert REVDeployer_AutoIssuanceBeneficiaryZeroAddress();
1031
+ if (autoIssuance.beneficiary == address(0)) {
1032
+ revert REVDeployer_AutoIssuanceBeneficiaryZeroAddress({stageIndex: i, autoIssuanceIndex: j});
1033
+ }
1031
1034
 
1032
1035
  // If there's nothing to auto-mint, continue.
1033
1036
  if (autoIssuance.count == 0) continue;
@@ -1039,7 +1042,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1039
1042
  // If the issuance config is for another chain, skip it.
1040
1043
  if (autoIssuance.chainId != block.chainid) continue;
1041
1044
 
1042
- // slither-disable-next-line reentrancy-events
1043
1045
  emit StoreAutoIssuanceAmount({
1044
1046
  revnetId: revnetId,
1045
1047
  stageId: block.timestamp + i,
@@ -1054,7 +1056,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1054
1056
  // (JBRulesets.sol L172), producing the same sequential IDs when all stages are queued in one tx.
1055
1057
  // `autoIssueFor` later calls `getRulesetOf(revnetId, stageId)` — the returned `ruleset.start`
1056
1058
  // is the derived start time (not the queue time), so the timing guard works correctly.
1057
- // slither-disable-next-line reentrancy-benign
1058
1059
  amountToAutoIssue[revnetId][block.timestamp + i][autoIssuance.beneficiary] += autoIssuance.count;
1059
1060
  }
1060
1061
  unchecked {
@@ -1073,13 +1074,13 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1073
1074
  function _setCashOutDelayIfNeeded(uint256 revnetId, REVStageConfig calldata firstStageConfig) internal {
1074
1075
  // If this is the first revnet being deployed (with a `startsAtOrAfter` of 0),
1075
1076
  // or if the first stage hasn't started yet, we don't need to set a cash out delay.
1077
+ // forge-lint: disable-next-line(block-timestamp)
1076
1078
  if (firstStageConfig.startsAtOrAfter == 0 || firstStageConfig.startsAtOrAfter >= block.timestamp) return;
1077
1079
 
1078
1080
  // Calculate the timestamp at which the cash out delay ends.
1079
1081
  uint256 cashOutDelay = block.timestamp + CASH_OUT_DELAY;
1080
1082
 
1081
1083
  // Store the cash out delay in the owner contract.
1082
- // slither-disable-next-line reentrancy-events
1083
1084
  REVOwner(OWNER).setCashOutDelayOf({revnetId: revnetId, cashOutDelay: cashOutDelay});
1084
1085
 
1085
1086
  emit SetCashOutDelay({revnetId: revnetId, cashOutDelay: cashOutDelay, caller: _msgSender()});
@@ -85,7 +85,7 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
85
85
  _hasPermissionFrom(caller, PROJECTS.ownerOf(revnetId), revnetId, JBPermissionIds.HIDE_TOKENS);
86
86
 
87
87
  if (!isAllowlistedHolder && !isPermissionedOperator) {
88
- revert REVHiddenTokens_Unauthorized(revnetId, caller);
88
+ revert REVHiddenTokens_Unauthorized({revnetId: revnetId, caller: caller});
89
89
  }
90
90
 
91
91
  // Increment the holder's hidden balance.
@@ -95,7 +95,6 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
95
95
  totalHiddenOf[revnetId] += tokenCount;
96
96
 
97
97
  // Burn the tokens from the holder. The holder must have granted BURN_TOKENS permission.
98
- // slither-disable-next-line reentrancy-events
99
98
  CONTROLLER.burnTokensOf({holder: holder, projectId: revnetId, tokenCount: tokenCount, memo: ""});
100
99
 
101
100
  emit HideTokens({revnetId: revnetId, tokenCount: tokenCount, holder: holder, caller: _msgSender()});
@@ -125,7 +124,6 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
125
124
  totalHiddenOf[revnetId] -= tokenCount;
126
125
 
127
126
  // Mint the tokens to the beneficiary without applying the reserved percent.
128
- // slither-disable-next-line unused-return,reentrancy-events
129
127
  CONTROLLER.mintTokensOf({
130
128
  projectId: revnetId, tokenCount: tokenCount, beneficiary: holder, memo: "", useReservedPercent: false
131
129
  });
package/src/REVLoans.sol CHANGED
@@ -61,19 +61,21 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
61
61
  error REVLoans_InvalidPrepaidFeePercent(uint256 prepaidFeePercent, uint256 min, uint256 max);
62
62
  error REVLoans_InvalidTerminal(address terminal, uint256 revnetId);
63
63
  error REVLoans_LoanExpired(uint256 timeSinceLoanCreated, uint256 loanLiquidationDuration);
64
- error REVLoans_LoanIdOverflow();
64
+ error REVLoans_LoanIdOverflow(uint256 revnetId, uint256 loanNumber, uint256 maxLoanNumber);
65
65
  error REVLoans_NewBorrowAmountGreaterThanLoanAmount(uint256 newBorrowAmount, uint256 loanAmount);
66
- error REVLoans_NoMsgValueAllowed();
67
- error REVLoans_NotEnoughCollateral();
68
- error REVLoans_NothingToRepay();
66
+ error REVLoans_NoMsgValueAllowed(uint256 msgValue, address token);
67
+ error REVLoans_NotEnoughCollateral(uint256 collateralCountToRemove, uint256 loanCollateral);
68
+ error REVLoans_NothingToRepay(uint256 repayBorrowAmount, uint256 collateralCountToReturn);
69
69
  error REVLoans_OverMaxRepayBorrowAmount(uint256 maxRepayBorrowAmount, uint256 repayBorrowAmount);
70
70
  error REVLoans_OverflowAlert(uint256 value, uint256 limit);
71
71
  error REVLoans_PermitAllowanceNotEnough(uint256 allowanceAmount, uint256 requiredAmount);
72
72
  error REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(uint256 newBorrowAmount, uint256 loanAmount);
73
- error REVLoans_SourceMismatch();
73
+ error REVLoans_SourceMismatch(
74
+ address expectedToken, address actualToken, address expectedTerminal, address actualTerminal
75
+ );
74
76
  error REVLoans_UnderMinBorrowAmount(uint256 minBorrowAmount, uint256 borrowAmount);
75
- error REVLoans_ZeroBorrowAmount();
76
- error REVLoans_ZeroCollateralLoanIsInvalid();
77
+ error REVLoans_ZeroBorrowAmount(uint256 revnetId, uint256 collateralCount);
78
+ error REVLoans_ZeroCollateralLoanIsInvalid(uint256 collateralCount);
77
79
 
78
80
  //*********************************************************************//
79
81
  // ------------------------- public constants ------------------------ //
@@ -229,6 +231,7 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
229
231
  JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
230
232
 
231
233
  // If the cash out delay hasn't passed yet, no amount is borrowable.
234
+ // forge-lint: disable-next-line(block-timestamp)
232
235
  if (_cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset}) > block.timestamp) return 0;
233
236
 
234
237
  return _borrowableAmountFrom({
@@ -443,7 +446,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
443
446
  /// @param revnetId The ID of the revnet.
444
447
  /// @return currentRuleset The current ruleset.
445
448
  function _currentRulesetOf(uint256 revnetId) internal view returns (JBRuleset memory currentRuleset) {
446
- // slither-disable-next-line unused-return
447
449
  (currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
448
450
  }
449
451
 
@@ -462,7 +464,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
462
464
  // Uses `>` (not `>=`) so the exact boundary second is still repayable — the liquidation path
463
465
  // uses `<=`, and matching `>=` here would create a 1-second window where neither path is available.
464
466
  if (timeSinceLoanCreated > LOAN_LIQUIDATION_DURATION) {
465
- revert REVLoans_LoanExpired(timeSinceLoanCreated, LOAN_LIQUIDATION_DURATION);
467
+ revert REVLoans_LoanExpired({
468
+ timeSinceLoanCreated: timeSinceLoanCreated, loanLiquidationDuration: LOAN_LIQUIDATION_DURATION
469
+ });
466
470
  }
467
471
 
468
472
  // Get a reference to the amount prepaid for the full loan.
@@ -548,7 +552,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
548
552
  if (tokensLoaned == 0) continue;
549
553
 
550
554
  // Get a reference to the accounting context for the source.
551
- // slither-disable-next-line calls-loop
552
555
  JBAccountingContext memory accountingContext =
553
556
  source.terminal.accountingContextForTokenOf({projectId: revnetId, token: source.token});
554
557
 
@@ -567,7 +570,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
567
570
  borrowedAmount += normalizedTokens;
568
571
  } else {
569
572
  // Otherwise, convert via the price feed.
570
- // slither-disable-next-line calls-loop
571
573
  uint256 pricePerUnit = PRICES.pricePerUnitOf({
572
574
  projectId: revnetId,
573
575
  pricingCurrency: accountingContext.currency,
@@ -649,7 +651,12 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
649
651
  /// @param count The number of loans to iterate over.
650
652
  function liquidateExpiredLoansFrom(uint256 revnetId, uint256 startingLoanId, uint256 count) external override {
651
653
  // Prevent cross-revnet accounting corruption: loan numbers must stay within the revnet's ID namespace.
652
- if (startingLoanId + count > _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
654
+ uint256 endLoanNumber = startingLoanId + count;
655
+ if (endLoanNumber > _ONE_TRILLION) {
656
+ revert REVLoans_LoanIdOverflow({
657
+ revnetId: revnetId, loanNumber: endLoanNumber, maxLoanNumber: _ONE_TRILLION
658
+ });
659
+ }
653
660
 
654
661
  // Cache the sender to avoid repeated ERC2771 context reads inside the loop.
655
662
  address sender = _msgSender();
@@ -660,7 +667,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
660
667
  uint256 loanId = _generateLoanId({revnetId: revnetId, loanNumber: startingLoanId + i});
661
668
 
662
669
  // Check createdAt via storage ref first to avoid loading the full struct for empty slots.
663
- // slither-disable-next-line incorrect-equality
664
670
  if (_loanOf[loanId].createdAt == 0) continue;
665
671
 
666
672
  // Get a reference to the loan being iterated on.
@@ -670,6 +676,7 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
670
676
  address owner = _ownerOf(loanId);
671
677
 
672
678
  // If the loan is already burned, or if it hasn't passed its liquidation duration, continue.
679
+ // forge-lint: disable-next-line(block-timestamp)
673
680
  if (owner == address(0) || (block.timestamp <= loan.createdAt + LOAN_LIQUIDATION_DURATION)) continue;
674
681
 
675
682
  // Burn the loan.
@@ -735,15 +742,24 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
735
742
  _requirePermissionFrom({account: loanOwner, projectId: revnetId, permissionId: JBPermissionIds.REALLOCATE_LOAN});
736
743
 
737
744
  // Make sure the loan hasn't expired.
745
+ // forge-lint: disable-next-line(block-timestamp)
738
746
  if (block.timestamp - _loanOf[loanId].createdAt > LOAN_LIQUIDATION_DURATION) {
739
- revert REVLoans_LoanExpired(block.timestamp - _loanOf[loanId].createdAt, LOAN_LIQUIDATION_DURATION);
747
+ revert REVLoans_LoanExpired({
748
+ timeSinceLoanCreated: block.timestamp - _loanOf[loanId].createdAt,
749
+ loanLiquidationDuration: LOAN_LIQUIDATION_DURATION
750
+ });
740
751
  }
741
752
 
742
753
  // Make sure the new loan's source matches the existing loan's source to prevent cross-source value extraction.
743
754
  {
744
755
  REVLoanSource storage existingSource = _loanOf[loanId].source;
745
756
  if (source.token != existingSource.token || source.terminal != existingSource.terminal) {
746
- revert REVLoans_SourceMismatch();
757
+ revert REVLoans_SourceMismatch({
758
+ expectedToken: existingSource.token,
759
+ actualToken: source.token,
760
+ expectedTerminal: address(existingSource.terminal),
761
+ actualTerminal: address(source.terminal)
762
+ });
747
763
  }
748
764
  }
749
765
 
@@ -806,7 +822,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
806
822
  REVLoan storage loan = _loanOf[loanId];
807
823
 
808
824
  if (collateralCountToReturn > loan.collateral) {
809
- revert REVLoans_CollateralExceedsLoan(collateralCountToReturn, loan.collateral);
825
+ revert REVLoans_CollateralExceedsLoan({
826
+ collateralToReturn: collateralCountToReturn, loanCollateral: loan.collateral
827
+ });
810
828
  }
811
829
 
812
830
  // Get a reference to the revnet ID of the loan being repaid.
@@ -833,7 +851,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
833
851
 
834
852
  // Make sure the new borrow amount is less than the loan's amount.
835
853
  if (newBorrowAmount > loan.amount) {
836
- revert REVLoans_NewBorrowAmountGreaterThanLoanAmount(newBorrowAmount, loan.amount);
854
+ revert REVLoans_NewBorrowAmountGreaterThanLoanAmount({
855
+ newBorrowAmount: newBorrowAmount, loanAmount: loan.amount
856
+ });
837
857
  }
838
858
 
839
859
  // Get the amount of the loan being repaid.
@@ -843,7 +863,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
843
863
  // Revert if this repayment would do nothing — no borrow amount repaid and no collateral returned.
844
864
  // Without this check, a zero-amount repayment would burn the old loan NFT and mint a new one,
845
865
  // incrementing totalLoansBorrowedFor without limit.
846
- if (repayBorrowAmount == 0 && collateralCountToReturn == 0) revert REVLoans_NothingToRepay();
866
+ if (repayBorrowAmount == 0 && collateralCountToReturn == 0) {
867
+ revert REVLoans_NothingToRepay({
868
+ repayBorrowAmount: repayBorrowAmount, collateralCountToReturn: collateralCountToReturn
869
+ });
870
+ }
847
871
 
848
872
  // Keep a reference to the fee that'll be taken.
849
873
  uint256 sourceFeeAmount = _determineSourceFeeAmount({loan: loan, amount: repayBorrowAmount});
@@ -857,7 +881,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
857
881
 
858
882
  // Make sure the minimum borrow amount is met.
859
883
  if (repayBorrowAmount > maxRepayBorrowAmount) {
860
- revert REVLoans_OverMaxRepayBorrowAmount(maxRepayBorrowAmount, repayBorrowAmount);
884
+ revert REVLoans_OverMaxRepayBorrowAmount({
885
+ maxRepayBorrowAmount: maxRepayBorrowAmount, repayBorrowAmount: repayBorrowAmount
886
+ });
861
887
  }
862
888
 
863
889
  // Cache the source token before _repayLoan deletes the loan storage.
@@ -915,13 +941,13 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
915
941
  if (token == JBConstants.NATIVE_TOKEN) return msg.value;
916
942
 
917
943
  // If the token is not native, revert if there is a non-zero `msg.value`.
918
- if (msg.value != 0) revert REVLoans_NoMsgValueAllowed();
944
+ if (msg.value != 0) revert REVLoans_NoMsgValueAllowed({msgValue: msg.value, token: token});
919
945
 
920
946
  // Check if the metadata contains permit data.
921
947
  if (allowance.amount != 0) {
922
948
  // Make sure the permit allowance is enough for this payment. If not we revert early.
923
949
  if (allowance.amount < amount) {
924
- revert REVLoans_PermitAllowanceNotEnough(allowance.amount, amount);
950
+ revert REVLoans_PermitAllowanceNotEnough({allowanceAmount: allowance.amount, requiredAmount: amount});
925
951
  }
926
952
 
927
953
  // Keep a reference to the permit rules.
@@ -995,7 +1021,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
995
1021
  loan.source.terminal.accountingContextForTokenOf({projectId: revnetId, token: loan.source.token});
996
1022
 
997
1023
  // Pull the amount to be loaned out of the revnet. This will incure the protocol fee.
998
- // slither-disable-next-line unused-return
999
1024
  netAmountPaidOut = loan.source.terminal
1000
1025
  .useAllowanceOf({
1001
1026
  projectId: revnetId,
@@ -1083,7 +1108,7 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1083
1108
  // Any reentrant call will see the updated loan values, reverting on overflow.
1084
1109
  if (newBorrowAmount > type(uint112).max) revert REVLoans_OverflowAlert(newBorrowAmount, type(uint112).max);
1085
1110
  if (newCollateralCount > type(uint112).max) {
1086
- revert REVLoans_OverflowAlert(newCollateralCount, type(uint112).max);
1111
+ revert REVLoans_OverflowAlert({value: newCollateralCount, limit: type(uint112).max});
1087
1112
  }
1088
1113
  // forge-lint: disable-next-line(unsafe-typecast)
1089
1114
  loan.amount = uint112(newBorrowAmount);
@@ -1177,20 +1202,20 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1177
1202
  returns (uint256 loanId, REVLoan memory)
1178
1203
  {
1179
1204
  // A loan needs to have collateral.
1180
- if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
1205
+ if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid({collateralCount: collateralCount});
1181
1206
 
1182
1207
  // Make sure the source terminal is registered in the directory for this revnet.
1183
1208
  if (!DIRECTORY.isTerminalOf({projectId: revnetId, terminal: IJBTerminal(address(source.terminal))})) {
1184
- revert REVLoans_InvalidTerminal(address(source.terminal), revnetId);
1209
+ revert REVLoans_InvalidTerminal({terminal: address(source.terminal), revnetId: revnetId});
1185
1210
  }
1186
1211
 
1187
1212
  // Make sure the prepaid fee percent is between `MIN_PREPAID_FEE_PERCENT` and `MAX_PREPAID_FEE_PERCENT`. Meaning
1188
1213
  // an 16 year loan can be paid upfront with a
1189
1214
  // payment of 50% of the borrowed assets, the cheapest possible rate.
1190
1215
  if (prepaidFeePercent < MIN_PREPAID_FEE_PERCENT || prepaidFeePercent > MAX_PREPAID_FEE_PERCENT) {
1191
- revert REVLoans_InvalidPrepaidFeePercent(
1192
- prepaidFeePercent, MIN_PREPAID_FEE_PERCENT, MAX_PREPAID_FEE_PERCENT
1193
- );
1216
+ revert REVLoans_InvalidPrepaidFeePercent({
1217
+ prepaidFeePercent: prepaidFeePercent, min: MIN_PREPAID_FEE_PERCENT, max: MAX_PREPAID_FEE_PERCENT
1218
+ });
1194
1219
  }
1195
1220
 
1196
1221
  // Cache the current ruleset once — used by both _cashOutDelayOf and _borrowAmountFrom.
@@ -1199,16 +1224,14 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1199
1224
  // Enforce the cash out delay.
1200
1225
  {
1201
1226
  uint256 cashOutDelay = _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
1227
+ // forge-lint: disable-next-line(block-timestamp)
1202
1228
  if (cashOutDelay > block.timestamp) {
1203
- revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
1229
+ revert REVLoans_CashOutDelayNotFinished({cashOutDelay: cashOutDelay, blockTimestamp: block.timestamp});
1204
1230
  }
1205
1231
  }
1206
1232
 
1207
- // Prevent the loan number from exceeding the ID namespace for this revnet.
1208
- if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
1209
-
1210
1233
  // Get a reference to the loan ID.
1211
- loanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
1234
+ loanId = _nextLoanIdFor(revnetId);
1212
1235
 
1213
1236
  // Get a reference to the loan being created.
1214
1237
  REVLoan storage loan = _loanOf[loanId];
@@ -1227,7 +1250,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1227
1250
  });
1228
1251
 
1229
1252
  // Revert if the bonding curve returns zero to prevent creating zero-amount loans.
1230
- if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
1253
+ if (borrowAmount == 0) {
1254
+ revert REVLoans_ZeroBorrowAmount({revnetId: revnetId, collateralCount: collateralCount});
1255
+ }
1231
1256
 
1232
1257
  // Make sure the minimum borrow amount is met.
1233
1258
  if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
@@ -1264,6 +1289,18 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1264
1289
  return (loanId, loan);
1265
1290
  }
1266
1291
 
1292
+ /// @notice Allocate the next loan ID for a revnet.
1293
+ /// @param revnetId The ID of the revnet.
1294
+ /// @return loanId The allocated loan ID.
1295
+ function _nextLoanIdFor(uint256 revnetId) internal returns (uint256 loanId) {
1296
+ uint256 loanNumber = totalLoansBorrowedFor[revnetId] + 1;
1297
+ if (loanNumber > _ONE_TRILLION) {
1298
+ revert REVLoans_LoanIdOverflow({revnetId: revnetId, loanNumber: loanNumber, maxLoanNumber: _ONE_TRILLION});
1299
+ }
1300
+ totalLoansBorrowedFor[revnetId] = loanNumber;
1301
+ return _generateLoanId({revnetId: revnetId, loanNumber: loanNumber});
1302
+ }
1303
+
1267
1304
  /// @notice Reallocate collateral from a loan by making a new loan based on the original, with reduced collateral.
1268
1305
  /// @param loanId The ID of the loan to reallocate collateral from.
1269
1306
  /// @param revnetId The ID of the revnet the loan is from.
@@ -1286,7 +1323,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1286
1323
  REVLoan storage loan = _loanOf[loanId];
1287
1324
 
1288
1325
  // Make sure there is enough collateral to transfer.
1289
- if (collateralCountToRemove > loan.collateral) revert REVLoans_NotEnoughCollateral();
1326
+ if (collateralCountToRemove > loan.collateral) {
1327
+ revert REVLoans_NotEnoughCollateral({
1328
+ collateralCountToRemove: collateralCountToRemove, loanCollateral: loan.collateral
1329
+ });
1330
+ }
1290
1331
 
1291
1332
  // Keep a reference to the new collateral amount.
1292
1333
  uint256 newCollateralCount = loan.collateral - collateralCountToRemove;
@@ -1301,14 +1342,13 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1301
1342
 
1302
1343
  // Make sure the borrow amount is not less than the original loan's amount.
1303
1344
  if (borrowAmount < loan.amount) {
1304
- revert REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(borrowAmount, loan.amount);
1345
+ revert REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows({
1346
+ newBorrowAmount: borrowAmount, loanAmount: loan.amount
1347
+ });
1305
1348
  }
1306
1349
 
1307
- // Prevent the loan number from exceeding the ID namespace for this revnet.
1308
- if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
1309
-
1310
1350
  // Get a reference to the replacement loan ID.
1311
- reallocatedLoanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
1351
+ reallocatedLoanId = _nextLoanIdFor(revnetId);
1312
1352
 
1313
1353
  // Get a reference to the loan being created.
1314
1354
  reallocatedLoan = _loanOf[reallocatedLoanId];
@@ -1364,7 +1404,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1364
1404
  });
1365
1405
 
1366
1406
  // Add the loaned amount back to the revnet.
1367
- // slither-disable-next-line arbitrary-send-eth
1368
1407
  loan.source.terminal.addToBalanceOf{value: payValue}({
1369
1408
  projectId: revnetId,
1370
1409
  token: loan.source.token,
@@ -1385,7 +1424,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1385
1424
  /// @param collateralCountToReturn The amount of collateral to return that the loan no longer requires.
1386
1425
  /// @param beneficiary The address to receive the returned collateral and any tokens resulting from paying fees.
1387
1426
  /// @param loanOwner The owner of the loan NFT (receives replacement loan if partial repay).
1388
- // slither-disable-next-line reentrancy-eth,reentrancy-events
1389
1427
  function _repayLoan(
1390
1428
  uint256 loanId,
1391
1429
  REVLoan storage loan,
@@ -1403,7 +1441,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1403
1441
  _burn(loanId);
1404
1442
 
1405
1443
  // If the loan will carry no more amount or collateral, store its changes directly.
1406
- // slither-disable-next-line incorrect-equality
1407
1444
  if (collateralCountToReturn == loan.collateral) {
1408
1445
  // Snapshot the loan to memory BEFORE _adjust zeroes the storage pointer.
1409
1446
  REVLoan memory loanSnapshot = loan;
@@ -1440,12 +1477,8 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1440
1477
 
1441
1478
  return (loanId, paidOffSnapshot);
1442
1479
  } else {
1443
- // Make a new loan with the remaining amount and collateral.
1444
- // Prevent the loan number from exceeding the ID namespace for this revnet.
1445
- if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
1446
-
1447
1480
  // Get a reference to the replacement loan ID.
1448
- uint256 paidOffLoanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
1481
+ uint256 paidOffLoanId = _nextLoanIdFor(revnetId);
1449
1482
 
1450
1483
  // Get a reference to the loan being paid off.
1451
1484
  REVLoan storage paidOffLoan = _loanOf[paidOffLoanId];
@@ -1502,7 +1535,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1502
1535
  totalCollateralOf[revnetId] -= collateralCount;
1503
1536
 
1504
1537
  // Mint the collateral tokens back to the loan payer.
1505
- // slither-disable-next-line unused-return,calls-loop
1506
1538
  CONTROLLER.mintTokensOf({
1507
1539
  projectId: revnetId,
1508
1540
  tokenCount: collateralCount,
@@ -1561,7 +1593,6 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1561
1593
  {
1562
1594
  uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
1563
1595
 
1564
- // slither-disable-next-line arbitrary-send-eth,unused-return
1565
1596
  try terminal.pay{value: payValue}({
1566
1597
  projectId: projectId,
1567
1598
  token: token,
@@ -1580,6 +1611,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1580
1611
  }
1581
1612
  }
1582
1613
 
1614
+ /// @notice Accepts calldata sent with native tokens so repayment helpers can refund or settle value.
1583
1615
  fallback() external payable {}
1616
+
1617
+ /// @notice Accepts native tokens sent directly to the loan contract.
1584
1618
  receive() external payable {}
1585
1619
  }
package/src/REVOwner.sol CHANGED
@@ -43,9 +43,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
43
43
  // --------------------------- custom errors ------------------------- //
44
44
  //*********************************************************************//
45
45
 
46
- error REVOwner_AlreadyInitialized();
46
+ error REVOwner_AlreadyInitialized(address deployer);
47
47
  error REVOwner_CashOutDelayNotFinished(uint256 cashOutDelay, uint256 blockTimestamp);
48
- error REVOwner_Unauthorized();
48
+ error REVOwner_Unauthorized(address caller, address expectedCaller);
49
49
 
50
50
  //*********************************************************************//
51
51
  // ------------------------- public constants ------------------------ //
@@ -89,7 +89,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
89
89
 
90
90
  /// @notice Each revnet's tiered ERC-721 hook.
91
91
  /// @custom:param revnetId The ID of the revnet to look up.
92
- // slither-disable-next-line uninitialized-state
93
92
  mapping(uint256 revnetId => IJB721TiersHook tiered721Hook) public tiered721HookOf;
94
93
 
95
94
  /// @notice The deployer that manages revnet state.
@@ -183,8 +182,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
183
182
  uint256 cashOutDelay = cashOutDelayOf[context.projectId];
184
183
 
185
184
  // Enforce the cash out delay.
185
+ // forge-lint: disable-next-line(block-timestamp)
186
186
  if (cashOutDelay > block.timestamp) {
187
- revert REVOwner_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
187
+ revert REVOwner_CashOutDelayNotFinished({cashOutDelay: cashOutDelay, blockTimestamp: block.timestamp});
188
188
  }
189
189
 
190
190
  // Get the terminal that will receive the cash out fee.
@@ -209,7 +209,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
209
209
  JBBeforeCashOutRecordedContext memory routedContext = context;
210
210
  routedContext.totalSupply = totalSupply;
211
211
  routedContext.surplus.value = effectiveSurplusValue;
212
- // slither-disable-next-line unused-return
213
212
  (cashOutTaxRate, cashOutCount,,, hookSpecifications) = BUYBACK_HOOK.beforeCashOutRecordedWith(routedContext);
214
213
  return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
215
214
  }
@@ -261,7 +260,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
261
260
 
262
261
  // Let the buyback hook adjust the cash out parameters and optionally return a hook specification.
263
262
  JBCashOutHookSpecification[] memory buybackHookSpecifications;
264
- // slither-disable-next-line unused-return
265
263
  (cashOutTaxRate, cashOutCount,,, buybackHookSpecifications) =
266
264
  BUYBACK_HOOK.beforeCashOutRecordedWith(buybackHookContext);
267
265
 
@@ -316,7 +314,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
316
314
  bool usesTiered721Hook = address(tiered721Hook) != address(0);
317
315
  if (usesTiered721Hook) {
318
316
  JBPayHookSpecification[] memory specs;
319
- // slither-disable-next-line unused-return
320
317
  (, specs) = IJBRulesetDataHook(address(tiered721Hook)).beforePayRecordedWith(context);
321
318
  // The 721 hook returns a single spec (itself) whose amount is the total split amount.
322
319
  if (specs.length > 0) {
@@ -429,7 +426,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
429
426
  });
430
427
 
431
428
  // Pay the fee.
432
- // slither-disable-next-line arbitrary-send-eth,unused-return
433
429
  try feeTerminal.pay{value: payValue}({
434
430
  projectId: FEE_REVNET_ID,
435
431
  token: context.forwardedAmount.token,
@@ -454,7 +450,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
454
450
  to: msg.sender, token: context.forwardedAmount.token, amount: context.forwardedAmount.value
455
451
  });
456
452
 
457
- // slither-disable-next-line arbitrary-send-eth
458
453
  IJBTerminal(msg.sender).addToBalanceOf{value: payValue}({
459
454
  projectId: context.projectId,
460
455
  token: context.forwardedAmount.token,
@@ -474,9 +469,11 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
474
469
  /// @param deployer The canonical REVDeployer instance that will manage revnet runtime state.
475
470
  function setDeployer(IREVDeployer deployer) external {
476
471
  // Only the account that deployed this REVOwner may complete the one-time deployer binding.
477
- if (msg.sender != _DEPLOYER_BINDER) revert REVOwner_Unauthorized();
472
+ if (msg.sender != _DEPLOYER_BINDER) {
473
+ revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: _DEPLOYER_BINDER});
474
+ }
478
475
  // Prevent the deployer binding from being overwritten after initialization.
479
- if (address(DEPLOYER) != address(0)) revert REVOwner_AlreadyInitialized();
476
+ if (address(DEPLOYER) != address(0)) revert REVOwner_AlreadyInitialized({deployer: address(DEPLOYER)});
480
477
  // Store the canonical REVDeployer that is authorized to manage runtime hook state.
481
478
  DEPLOYER = deployer;
482
479
  }
@@ -486,7 +483,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
486
483
  /// @param revnetId The ID of the revnet.
487
484
  /// @param cashOutDelay The timestamp after which cash outs are allowed.
488
485
  function setCashOutDelayOf(uint256 revnetId, uint256 cashOutDelay) external {
489
- if (msg.sender != address(DEPLOYER)) revert REVOwner_Unauthorized();
486
+ if (msg.sender != address(DEPLOYER)) {
487
+ revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: address(DEPLOYER)});
488
+ }
490
489
  cashOutDelayOf[revnetId] = cashOutDelay;
491
490
  }
492
491
 
@@ -495,7 +494,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
495
494
  /// @param revnetId The ID of the revnet.
496
495
  /// @param hook The tiered ERC-721 hook.
497
496
  function setTiered721HookOf(uint256 revnetId, IJB721TiersHook hook) external {
498
- if (msg.sender != address(DEPLOYER)) revert REVOwner_Unauthorized();
497
+ if (msg.sender != address(DEPLOYER)) {
498
+ revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: address(DEPLOYER)});
499
+ }
499
500
  tiered721HookOf[revnetId] = hook;
500
501
  }
501
502
 
@@ -552,13 +553,11 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
552
553
  for (uint256 i; i < sources.length; i++) {
553
554
  REVLoanSource memory source = sources[i];
554
555
  // Each configured source must be queried live so cash-out math includes current outstanding debt.
555
- // slither-disable-next-line calls-loop
556
556
  uint256 tokensLoaned =
557
557
  loans.totalBorrowedFrom({revnetId: revnetId, terminal: source.terminal, token: source.token});
558
558
  if (tokensLoaned == 0) continue;
559
559
 
560
560
  // Read the source token's accounting context so debt can be normalized before cross-currency conversion.
561
- // slither-disable-next-line calls-loop
562
561
  JBAccountingContext memory accountingContext =
563
562
  source.terminal.accountingContextForTokenOf({projectId: revnetId, token: source.token});
564
563
 
@@ -576,7 +575,6 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
576
575
  borrowedAmount += normalizedTokens;
577
576
  } else {
578
577
  // Convert source-token debt into the requested currency using the loans contract's shared prices.
579
- // slither-disable-next-line calls-loop
580
578
  uint256 pricePerUnit = loans.PRICES()
581
579
  .pricePerUnitOf({
582
580
  projectId: revnetId,