@rev-net/core-v6 0.0.28 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,14 +19,14 @@
19
19
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
20
20
  },
21
21
  "dependencies": {
22
- "@bananapus/721-hook-v6": "^0.0.31",
23
- "@bananapus/buyback-hook-v6": "^0.0.25",
24
- "@bananapus/core-v6": "^0.0.31",
22
+ "@bananapus/721-hook-v6": "^0.0.32",
23
+ "@bananapus/buyback-hook-v6": "^0.0.26",
24
+ "@bananapus/core-v6": "^0.0.32",
25
25
  "@bananapus/ownable-v6": "^0.0.17",
26
26
  "@bananapus/permission-ids-v6": "^0.0.15",
27
- "@bananapus/router-terminal-v6": "^0.0.25",
28
- "@bananapus/suckers-v6": "^0.0.21",
29
- "@croptop/core-v6": "^0.0.29",
27
+ "@bananapus/router-terminal-v6": "^0.0.26",
28
+ "@bananapus/suckers-v6": "^0.0.22",
29
+ "@croptop/core-v6": "^0.0.30",
30
30
  "@openzeppelin/contracts": "^5.6.1",
31
31
  "@uniswap/v4-core": "^1.0.2",
32
32
  "@uniswap/v4-periphery": "^1.0.3"
@@ -275,8 +275,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
275
275
  {
276
276
  // Count the total number of accounting contexts across all terminals.
277
277
  uint256 count;
278
- for (uint256 i; i < terminalConfigurations.length; i++) {
278
+ for (uint256 i; i < terminalConfigurations.length;) {
279
279
  count += terminalConfigurations[i].accountingContextsToAccept.length;
280
+ unchecked {
281
+ ++i;
282
+ }
280
283
  }
281
284
 
282
285
  // Initialize the fund access limit groups.
@@ -284,9 +287,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
284
287
 
285
288
  // Set up the fund access limits.
286
289
  uint256 index;
287
- for (uint256 i; i < terminalConfigurations.length; i++) {
290
+ for (uint256 i; i < terminalConfigurations.length;) {
288
291
  JBTerminalConfig calldata terminalConfiguration = terminalConfigurations[i];
289
- for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length; j++) {
292
+ for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length;) {
290
293
  JBAccountingContext calldata accountingContext = terminalConfiguration.accountingContextsToAccept[j];
291
294
 
292
295
  // Set up an unlimited allowance for the loan contract to use.
@@ -300,6 +303,12 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
300
303
  payoutLimits: new JBCurrencyAmount[](0),
301
304
  surplusAllowances: loanAllowances
302
305
  });
306
+ unchecked {
307
+ ++j;
308
+ }
309
+ }
310
+ unchecked {
311
+ ++i;
303
312
  }
304
313
  }
305
314
  }
@@ -378,8 +387,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
378
387
  allOperatorPermissions[8] = JBPermissionIds.SET_TOKEN_METADATA;
379
388
 
380
389
  // Copy the custom permissions into the array.
381
- for (uint256 i; i < customSplitOperatorPermissionIndexes.length; i++) {
390
+ for (uint256 i; i < customSplitOperatorPermissionIndexes.length;) {
382
391
  allOperatorPermissions[9 + i] = customSplitOperatorPermissionIndexes[i];
392
+ unchecked {
393
+ ++i;
394
+ }
383
395
  }
384
396
  }
385
397
 
@@ -566,7 +578,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
566
578
  }
567
579
 
568
580
  // Store the tiered ERC-721 hook in the owner contract.
569
- REVOwner(OWNER).setTiered721HookOf(revnetId, hook);
581
+ REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
570
582
 
571
583
  // Grant the split operator all 721 permissions (no prevent* flags for default config).
572
584
  _extraOperatorPermissions[revnetId].push(JBPermissionIds.ADJUST_721_TIERS);
@@ -690,7 +702,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
690
702
  });
691
703
 
692
704
  // Store the tiered ERC-721 hook in the owner contract.
693
- REVOwner(OWNER).setTiered721HookOf(revnetId, hook);
705
+ REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
694
706
 
695
707
  // Give the split operator permission to add and remove tiers unless prevented.
696
708
  if (!tiered721HookConfiguration.preventSplitOperatorAdjustingTiers) {
@@ -722,7 +734,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
722
734
  CTAllowedPost[] memory formattedAllowedPosts = new CTAllowedPost[](allowedPosts.length);
723
735
 
724
736
  // Iterate through each post to add it to the formatted list.
725
- for (uint256 i; i < allowedPosts.length; i++) {
737
+ for (uint256 i; i < allowedPosts.length;) {
726
738
  // Set the post being iterated on.
727
739
  REVCroptopAllowedPost memory post = allowedPosts[i];
728
740
 
@@ -736,6 +748,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
736
748
  maximumSplitPercent: post.maximumSplitPercent,
737
749
  allowedAddresses: post.allowedAddresses
738
750
  });
751
+ unchecked {
752
+ ++i;
753
+ }
739
754
  }
740
755
 
741
756
  // Set up the allowed posts in the publisher.
@@ -830,15 +845,21 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
830
845
  });
831
846
 
832
847
  // Now that the ERC-20 exists, initialize buyback pools for each terminal token.
833
- for (uint256 i; i < terminalConfigurations.length; i++) {
848
+ for (uint256 i; i < terminalConfigurations.length;) {
834
849
  JBTerminalConfig calldata terminalConfiguration = terminalConfigurations[i];
835
- for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length; j++) {
850
+ for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length;) {
836
851
  // slither-disable-next-line calls-loop
837
852
  _tryInitializeBuybackPoolFor({
838
853
  revnetId: revnetId,
839
854
  terminalToken: terminalConfiguration.accountingContextsToAccept[j].token,
840
855
  initialIssuance: configuration.stageConfigurations[0].initialIssuance
841
856
  });
857
+ unchecked {
858
+ ++j;
859
+ }
860
+ }
861
+ unchecked {
862
+ ++i;
842
863
  }
843
864
  }
844
865
 
@@ -937,7 +958,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
937
958
  _makeLoanFundAccessLimits({terminalConfigurations: terminalConfigurations});
938
959
 
939
960
  // Iterate through each stage to set up its ruleset.
940
- for (uint256 i; i < configuration.stageConfigurations.length; i++) {
961
+ for (uint256 i; i < configuration.stageConfigurations.length;) {
941
962
  // Set the stage being iterated on.
942
963
  REVStageConfig calldata stageConfiguration = configuration.stageConfigurations[i];
943
964
 
@@ -1019,6 +1040,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1019
1040
  // slither-disable-next-line reentrancy-benign
1020
1041
  amountToAutoIssue[revnetId][block.timestamp + i][autoIssuance.beneficiary] += autoIssuance.count;
1021
1042
  }
1043
+ unchecked {
1044
+ ++i;
1045
+ }
1022
1046
  }
1023
1047
 
1024
1048
  // Hash the encoded configuration.
@@ -1039,7 +1063,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1039
1063
  uint256 cashOutDelay = block.timestamp + CASH_OUT_DELAY;
1040
1064
 
1041
1065
  // Store the cash out delay in the owner contract.
1042
- REVOwner(OWNER).setCashOutDelayOf(revnetId, cashOutDelay);
1066
+ REVOwner(OWNER).setCashOutDelayOf({revnetId: revnetId, cashOutDelay: cashOutDelay});
1043
1067
 
1044
1068
  emit SetCashOutDelay({revnetId: revnetId, cashOutDelay: cashOutDelay, caller: _msgSender()});
1045
1069
  }
@@ -1089,8 +1113,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1089
1113
  uint256[] memory permissionIndexes = _splitOperatorPermissionIndexesOf(revnetId);
1090
1114
  uint8[] memory permissionIds = new uint8[](permissionIndexes.length);
1091
1115
 
1092
- for (uint256 i; i < permissionIndexes.length; i++) {
1116
+ for (uint256 i; i < permissionIndexes.length;) {
1093
1117
  permissionIds[i] = uint8(permissionIndexes[i]);
1118
+ unchecked {
1119
+ ++i;
1120
+ }
1094
1121
  }
1095
1122
 
1096
1123
  _setPermissionsFor({
package/src/REVLoans.sol CHANGED
@@ -227,15 +227,20 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
227
227
  view
228
228
  returns (uint256)
229
229
  {
230
+ // Cache the current ruleset once — used by both _cashOutDelayOf and _borrowableAmountFrom.
231
+ // slither-disable-next-line unused-return
232
+ (JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
233
+
230
234
  // If the cash out delay hasn't passed yet, no amount is borrowable.
231
- if (_cashOutDelayOf(revnetId) > block.timestamp) return 0;
235
+ if (_cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset}) > block.timestamp) return 0;
232
236
 
233
237
  return _borrowableAmountFrom({
234
238
  revnetId: revnetId,
235
239
  collateralCount: collateralCount,
236
240
  decimals: decimals,
237
241
  currency: currency,
238
- terminals: DIRECTORY.terminalsOf(revnetId)
242
+ terminals: DIRECTORY.terminalsOf(revnetId),
243
+ currentStage: currentRuleset
239
244
  });
240
245
  }
241
246
 
@@ -298,6 +303,14 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
298
303
  // slither-disable-next-line unused-return
299
304
  (JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
300
305
 
306
+ return _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
307
+ }
308
+
309
+ /// @notice Returns the cash out delay timestamp using a pre-fetched ruleset (avoids redundant external call).
310
+ /// @param revnetId The ID of the revnet.
311
+ /// @param currentRuleset The pre-fetched current ruleset.
312
+ /// @return The cash out delay timestamp. Returns 0 if no data hook is set or no delay exists.
313
+ function _cashOutDelayOf(uint256 revnetId, JBRuleset memory currentRuleset) internal view returns (uint256) {
301
314
  // Extract the data hook address from the ruleset's packed metadata.
302
315
  address dataHook = currentRuleset.dataHook();
303
316
 
@@ -360,6 +373,29 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
360
373
  // slither-disable-next-line unused-return
361
374
  (JBRuleset memory currentStage,) = CONTROLLER.currentRulesetOf(revnetId);
362
375
 
376
+ return _borrowableAmountFrom({
377
+ revnetId: revnetId,
378
+ collateralCount: collateralCount,
379
+ decimals: decimals,
380
+ currency: currency,
381
+ terminals: terminals,
382
+ currentStage: currentStage
383
+ });
384
+ }
385
+
386
+ /// @dev Overload that accepts a pre-fetched ruleset to avoid redundant `currentRulesetOf` calls.
387
+ function _borrowableAmountFrom(
388
+ uint256 revnetId,
389
+ uint256 collateralCount,
390
+ uint256 decimals,
391
+ uint256 currency,
392
+ IJBTerminal[] memory terminals,
393
+ JBRuleset memory currentStage
394
+ )
395
+ internal
396
+ view
397
+ returns (uint256)
398
+ {
363
399
  // Get the surplus of all the revnet's terminals in terms of the native currency.
364
400
  uint256 totalSurplus = JBSurplus.currentSurplusOf({
365
401
  projectId: revnetId, terminals: terminals, tokens: new address[](0), decimals: decimals, currency: currency
@@ -421,6 +457,37 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
421
457
  });
422
458
  }
423
459
 
460
+ /// @dev Overload that accepts a pre-fetched ruleset to avoid redundant `currentRulesetOf` calls.
461
+ function _borrowAmountFrom(
462
+ REVLoan storage loan,
463
+ uint256 revnetId,
464
+ uint256 collateralCount,
465
+ JBRuleset memory currentRuleset
466
+ )
467
+ internal
468
+ view
469
+ returns (uint256)
470
+ {
471
+ // If there's no collateral, there's no loan.
472
+ if (collateralCount == 0) return 0;
473
+
474
+ // Get a reference to the accounting context for the source.
475
+ JBAccountingContext memory accountingContext =
476
+ loan.source.terminal.accountingContextForTokenOf({projectId: revnetId, token: loan.source.token});
477
+
478
+ // Keep a reference to the revnet's terminals.
479
+ IJBTerminal[] memory terminals = DIRECTORY.terminalsOf(revnetId);
480
+
481
+ return _borrowableAmountFrom({
482
+ revnetId: revnetId,
483
+ collateralCount: collateralCount,
484
+ decimals: accountingContext.decimals,
485
+ currency: accountingContext.currency,
486
+ terminals: terminals,
487
+ currentStage: currentRuleset
488
+ });
489
+ }
490
+
424
491
  /// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
425
492
  function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
426
493
  return super._contextSuffixLength();
@@ -503,12 +570,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
503
570
  returns (uint256 borrowedAmount)
504
571
  {
505
572
  // Keep a reference to all sources being used to loaned out from this revnet.
506
- REVLoanSource[] memory sources = _loanSourcesOf[revnetId];
573
+ // Use storage ref to avoid bulk-copying the entire array to memory.
574
+ REVLoanSource[] storage sources = _loanSourcesOf[revnetId];
507
575
 
508
576
  // Iterate over all sources being used to loaned out.
509
577
  for (uint256 i; i < sources.length; i++) {
510
578
  // Get a reference to the token being iterated on.
511
- REVLoanSource memory source = sources[i];
579
+ REVLoanSource storage source = sources[i];
512
580
 
513
581
  // Get a reference to the accounting context for the source.
514
582
  // slither-disable-next-line calls-loop
@@ -603,9 +671,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
603
671
  );
604
672
  }
605
673
 
674
+ // Cache the current ruleset once — used by both _cashOutDelayOf and _borrowAmountFrom.
675
+ // slither-disable-next-line unused-return
676
+ (JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
677
+
606
678
  // Enforce the cash out delay.
607
679
  {
608
- uint256 cashOutDelay = _cashOutDelayOf(revnetId);
680
+ uint256 cashOutDelay = _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
609
681
  if (cashOutDelay > block.timestamp) {
610
682
  revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
611
683
  }
@@ -628,8 +700,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
628
700
  loan.prepaidDuration =
629
701
  uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
630
702
 
631
- // Get the amount of the loan.
632
- uint256 borrowAmount = _borrowAmountFrom({loan: loan, revnetId: revnetId, collateralCount: collateralCount});
703
+ // Get the amount of the loan, using the cached ruleset.
704
+ uint256 borrowAmount = _borrowAmountFrom({
705
+ loan: loan, revnetId: revnetId, collateralCount: collateralCount, currentRuleset: currentRuleset
706
+ });
633
707
 
634
708
  // Revert if the bonding curve returns zero to prevent creating zero-amount loans.
635
709
  if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
@@ -651,8 +725,11 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
651
725
  beneficiary: beneficiary
652
726
  });
653
727
 
728
+ // Cache the sender to avoid repeated ERC2771 context reads.
729
+ address sender = _msgSender();
730
+
654
731
  // Mint the loan.
655
- _mint({to: _msgSender(), tokenId: loanId});
732
+ _mint({to: sender, tokenId: loanId});
656
733
 
657
734
  emit Borrow({
658
735
  loanId: loanId,
@@ -663,7 +740,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
663
740
  collateralCount: collateralCount,
664
741
  sourceFeeAmount: sourceFeeAmount,
665
742
  beneficiary: beneficiary,
666
- caller: _msgSender()
743
+ caller: sender
667
744
  });
668
745
 
669
746
  return (loanId, loan);
@@ -686,18 +763,21 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
686
763
  // Prevent cross-revnet accounting corruption: loan numbers must stay within the revnet's ID namespace.
687
764
  if (startingLoanId + count > _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
688
765
 
766
+ // Cache the sender to avoid repeated ERC2771 context reads inside the loop.
767
+ address sender = _msgSender();
768
+
689
769
  // Iterate over the desired number of loans to check for liquidation.
690
770
  for (uint256 i; i < count; i++) {
691
771
  // Get a reference to the next loan ID.
692
772
  uint256 loanId = _generateLoanId({revnetId: revnetId, loanNumber: startingLoanId + i});
693
773
 
774
+ // Check createdAt via storage ref first to avoid loading the full struct for empty slots.
775
+ // slither-disable-next-line incorrect-equality
776
+ if (_loanOf[loanId].createdAt == 0) continue;
777
+
694
778
  // Get a reference to the loan being iterated on.
695
779
  REVLoan memory loan = _loanOf[loanId];
696
780
 
697
- // If the loan doesn't exist (repaid or already liquidated), skip past this gap and continue.
698
- // slither-disable-next-line incorrect-equality
699
- if (loan.createdAt == 0) continue;
700
-
701
781
  // Keep a reference to the loan's owner.
702
782
  address owner = _ownerOf(loanId);
703
783
 
@@ -722,7 +802,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
722
802
  totalBorrowedFrom[revnetId][loan.source.terminal][loan.source.token] -= loan.amount;
723
803
  }
724
804
 
725
- emit Liquidate({loanId: loanId, revnetId: revnetId, loan: loan, caller: _msgSender()});
805
+ emit Liquidate({loanId: loanId, revnetId: revnetId, loan: loan, caller: sender});
726
806
  }
727
807
  }
728
808
 
@@ -756,8 +836,14 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
756
836
  override
757
837
  returns (uint256 reallocatedLoanId, uint256 newLoanId, REVLoan memory reallocatedLoan, REVLoan memory newLoan)
758
838
  {
839
+ // Cache the sender to avoid repeated ERC2771 context reads.
840
+ address sender = _msgSender();
841
+
759
842
  // Make sure only the loan's owner can manage it.
760
- if (_ownerOf(loanId) != _msgSender()) revert REVLoans_Unauthorized(_msgSender(), _ownerOf(loanId));
843
+ {
844
+ address loanOwner = _ownerOf(loanId);
845
+ if (loanOwner != sender) revert REVLoans_Unauthorized(sender, loanOwner);
846
+ }
761
847
 
762
848
  // Make sure the loan hasn't expired.
763
849
  if (block.timestamp - _loanOf[loanId].createdAt > LOAN_LIQUIDATION_DURATION) {
@@ -815,8 +901,14 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
815
901
  override
816
902
  returns (uint256 paidOffLoanId, REVLoan memory paidOffloan)
817
903
  {
904
+ // Cache the sender to avoid repeated ERC2771 context reads.
905
+ address sender = _msgSender();
906
+
818
907
  // Make sure only the loan's owner can manage it.
819
- if (_ownerOf(loanId) != _msgSender()) revert REVLoans_Unauthorized(_msgSender(), _ownerOf(loanId));
908
+ {
909
+ address loanOwner = _ownerOf(loanId);
910
+ if (loanOwner != sender) revert REVLoans_Unauthorized(sender, loanOwner);
911
+ }
820
912
 
821
913
  // Keep a reference to the fee being iterated on.
822
914
  REVLoan storage loan = _loanOf[loanId];
@@ -887,7 +979,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
887
979
  if (maxRepayBorrowAmount > repayBorrowAmount) {
888
980
  _transferFrom({
889
981
  from: address(this),
890
- to: payable(_msgSender()),
982
+ to: payable(sender),
891
983
  token: sourceToken,
892
984
  amount: maxRepayBorrowAmount - repayBorrowAmount
893
985
  });
@@ -915,7 +1007,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
915
1007
  function _acceptFundsFor(
916
1008
  address token,
917
1009
  uint256 amount,
918
- JBSingleAllowance memory allowance
1010
+ JBSingleAllowance calldata allowance
919
1011
  )
920
1012
  internal
921
1013
  returns (uint256)
@@ -1095,6 +1187,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1095
1187
  )
1096
1188
  internal
1097
1189
  {
1190
+ // Cache frequently-read storage fields to avoid repeated SLOAD.
1191
+ address sourceToken = loan.source.token;
1192
+ IJBPayoutTerminal sourceTerminal = loan.source.terminal;
1193
+
1098
1194
  // Snapshot deltas from current state before writing.
1099
1195
  uint256 addedBorrowAmount = newBorrowAmount > loan.amount ? newBorrowAmount - loan.amount : 0;
1100
1196
  uint256 repaidBorrowAmount = loan.amount > newBorrowAmount ? loan.amount - newBorrowAmount : 0;
@@ -1143,9 +1239,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1143
1239
  // cannot block all loan operations (matching the REV fee pattern above).
1144
1240
  if (sourceFeeAmount > 0) {
1145
1241
  // Increase the allowance for the source terminal.
1146
- uint256 payValue = _beforeTransferTo({
1147
- to: address(loan.source.terminal), token: loan.source.token, amount: sourceFeeAmount
1148
- });
1242
+ uint256 payValue =
1243
+ _beforeTransferTo({to: address(sourceTerminal), token: sourceToken, amount: sourceFeeAmount});
1149
1244
 
1150
1245
  // Pay the fee. If it fails, reclaim the allowance and give the amount back to the borrower.
1151
1246
  // NOTE: When terminal.pay() reverts (e.g. due to a misconfigured terminal or paused payments),
@@ -1153,9 +1248,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1153
1248
  // source fee portion. This is acceptable — it requires a broken/misconfigured source terminal and
1154
1249
  // the borrower still pays the REV fee and protocol fee.
1155
1250
  // slither-disable-next-line unused-return,arbitrary-send-eth
1156
- try loan.source.terminal.pay{value: payValue}({
1251
+ try sourceTerminal.pay{value: payValue}({
1157
1252
  projectId: revnetId,
1158
- token: loan.source.token,
1253
+ token: sourceToken,
1159
1254
  amount: sourceFeeAmount,
1160
1255
  beneficiary: beneficiary,
1161
1256
  minReturnedTokens: 0,
@@ -1165,13 +1260,11 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1165
1260
  catch (bytes memory) {
1166
1261
  // If the fee can't be processed, decrease the ERC-20 allowance and return the amount
1167
1262
  // to the beneficiary instead.
1168
- if (loan.source.token != JBConstants.NATIVE_TOKEN) {
1169
- IERC20(loan.source.token)
1170
- .safeDecreaseAllowance({
1171
- spender: address(loan.source.terminal), requestedDecrease: sourceFeeAmount
1172
- });
1263
+ if (sourceToken != JBConstants.NATIVE_TOKEN) {
1264
+ IERC20(sourceToken)
1265
+ .safeDecreaseAllowance({spender: address(sourceTerminal), requestedDecrease: sourceFeeAmount});
1173
1266
  }
1174
- _transferFrom({from: address(this), to: beneficiary, token: loan.source.token, amount: sourceFeeAmount});
1267
+ _transferFrom({from: address(this), to: beneficiary, token: sourceToken, amount: sourceFeeAmount});
1175
1268
  }
1176
1269
  }
1177
1270
  }
@@ -1364,7 +1457,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1364
1457
  // Get a reference to the loan being paid off.
1365
1458
  REVLoan storage paidOffLoan = _loanOf[paidOffLoanId];
1366
1459
 
1367
- // Set the paid off loan's values the same as the original loan.
1460
+ // Copy the original loan's values. amount and collateral are written here so _adjust
1461
+ // can compute correct deltas, then _adjust overwrites them with the final values.
1368
1462
  paidOffLoan.amount = loan.amount;
1369
1463
  paidOffLoan.collateral = loan.collateral;
1370
1464
  paidOffLoan.createdAt = loan.createdAt;
@@ -9,8 +9,8 @@ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
9
9
  import {IJBTokenUriResolver} from "@bananapus/core-v6/src/interfaces/IJBTokenUriResolver.sol";
10
10
  import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
11
11
  import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
12
- import {REVLoan} from "./../structs/REVLoan.sol";
13
- import {REVLoanSource} from "./../structs/REVLoanSource.sol";
12
+ import {REVLoan} from "../structs/REVLoan.sol";
13
+ import {REVLoanSource} from "../structs/REVLoanSource.sol";
14
14
 
15
15
  /// @notice Manages loans against revnet token collateral.
16
16
  interface IREVLoans {