@rev-net/core-v6 0.0.29 → 0.0.31

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 (77) hide show
  1. package/ADMINISTRATION.md +19 -9
  2. package/ARCHITECTURE.md +3 -0
  3. package/AUDIT_INSTRUCTIONS.md +11 -1
  4. package/CHANGELOG.md +26 -0
  5. package/README.md +1 -0
  6. package/RISKS.md +28 -4
  7. package/SKILLS.md +2 -1
  8. package/USER_JOURNEYS.md +28 -3
  9. package/package.json +8 -8
  10. package/references/operations.md +1 -1
  11. package/script/Deploy.s.sol +26 -4
  12. package/src/REVDeployer.sol +4 -2
  13. package/src/REVHiddenTokens.sol +149 -0
  14. package/src/REVLoans.sol +192 -199
  15. package/src/REVOwner.sol +51 -14
  16. package/src/interfaces/IREVHiddenTokens.sol +53 -0
  17. package/src/interfaces/IREVLoans.sol +8 -6
  18. package/test/REV.integrations.t.sol +12 -2
  19. package/test/REVAutoIssuanceFuzz.t.sol +12 -2
  20. package/test/REVDeployerRegressions.t.sol +14 -3
  21. package/test/REVInvincibility.t.sol +27 -8
  22. package/test/REVInvincibilityHandler.sol +1 -1
  23. package/test/REVLifecycle.t.sol +14 -3
  24. package/test/REVLoans.invariants.t.sol +15 -4
  25. package/test/REVLoansAttacks.t.sol +19 -7
  26. package/test/REVLoansFeeRecovery.t.sol +24 -13
  27. package/test/REVLoansFindings.t.sol +16 -5
  28. package/test/REVLoansRegressions.t.sol +15 -4
  29. package/test/REVLoansSourceFeeRecovery.t.sol +16 -5
  30. package/test/REVLoansSourced.t.sol +60 -25
  31. package/test/REVLoansUnSourced.t.sol +15 -4
  32. package/test/TestBurnHeldTokens.t.sol +14 -3
  33. package/test/TestCEIPattern.t.sol +19 -7
  34. package/test/TestCashOutCallerValidation.t.sol +15 -4
  35. package/test/TestConversionDocumentation.t.sol +14 -3
  36. package/test/TestCrossCurrencyReclaim.t.sol +14 -3
  37. package/test/TestCrossSourceReallocation.t.sol +15 -4
  38. package/test/TestERC2771MetaTx.t.sol +18 -5
  39. package/test/TestEmptyBuybackSpecs.t.sol +14 -3
  40. package/test/TestFlashLoanSurplus.t.sol +15 -4
  41. package/test/TestHiddenTokens.t.sol +431 -0
  42. package/test/TestHookArrayOOB.t.sol +14 -3
  43. package/test/TestLiquidationBehavior.t.sol +16 -5
  44. package/test/TestLoanSourceRotation.t.sol +20 -7
  45. package/test/TestLoansCashOutDelay.t.sol +18 -7
  46. package/test/TestLongTailEconomics.t.sol +14 -3
  47. package/test/TestLowFindings.t.sol +25 -9
  48. package/test/TestMixedFixes.t.sol +19 -8
  49. package/test/TestPermit2Signatures.t.sol +15 -4
  50. package/test/TestReallocationSandwich.t.sol +16 -4
  51. package/test/TestRevnetRegressions.t.sol +16 -5
  52. package/test/TestSplitWeightAdjustment.t.sol +16 -4
  53. package/test/TestSplitWeightE2E.t.sol +18 -4
  54. package/test/TestSplitWeightFork.t.sol +16 -3
  55. package/test/TestStageTransitionBorrowable.t.sol +14 -3
  56. package/test/TestSwapTerminalPermission.t.sol +14 -3
  57. package/test/TestUint112Overflow.t.sol +15 -4
  58. package/test/TestZeroAmountLoanGuard.t.sol +15 -4
  59. package/test/TestZeroRepayment.t.sol +15 -4
  60. package/test/audit/CodexPhantomSurplusTerminal.t.sol +367 -0
  61. package/test/audit/LoanIdOverflowGuard.t.sol +16 -5
  62. package/test/audit/NemesisOperatorDelegation.t.sol +289 -0
  63. package/test/fork/ForkTestBase.sol +18 -4
  64. package/test/fork/TestLoanBorrowFork.t.sol +2 -1
  65. package/test/fork/TestLoanERC20Fork.t.sol +4 -2
  66. package/test/fork/TestLoanTransferFork.t.sol +12 -2
  67. package/test/helpers/MaliciousContracts.sol +1 -1
  68. package/test/mock/MockBuybackCashOutRecorder.sol +2 -0
  69. package/test/mock/MockBuybackDataHook.sol +3 -1
  70. package/test/mock/MockBuybackDataHookMintPath.sol +2 -0
  71. package/test/mock/MockSuckerRegistry.sol +17 -0
  72. package/test/regression/TestBurnPermissionRequired.t.sol +16 -5
  73. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +16 -3
  74. package/test/regression/TestCrossRevnetLiquidation.t.sol +14 -3
  75. package/test/regression/TestCumulativeLoanCounter.t.sol +15 -4
  76. package/test/regression/TestLiquidateGapHandling.t.sol +15 -4
  77. package/test/regression/TestZeroPriceFeed.t.sol +17 -6
@@ -38,6 +38,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
38
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
39
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
41
43
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
44
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
45
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
@@ -46,6 +48,7 @@ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
46
48
  import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
47
49
  import {REVOwner} from "../src/REVOwner.sol";
48
50
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
51
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
49
52
 
50
53
  /// @notice A malicious terminal that re-enters REVLoans during fee payment in _adjust().
51
54
  /// @dev Reentrancy during pay() callback in _adjust.
@@ -98,7 +101,8 @@ contract ReentrantTerminal is ERC165, IJBPayoutTerminal {
98
101
  0, // minBorrowAmount
99
102
  reenterCollateral,
100
103
  payable(address(this)),
101
- 25 // MIN_PREPAID_FEE_PERCENT
104
+ 25, // MIN_PREPAID_FEE_PERCENT
105
+ address(this)
102
106
  ) {}
103
107
  catch {
104
108
  // Expected to revert if reentrancy guard exists
@@ -368,7 +372,14 @@ contract REVLoansAttacks is TestBaseWorkflow {
368
372
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
369
373
  HOOK_STORE = new JB721TiersHookStore();
370
374
  EXAMPLE_HOOK = new JB721TiersHook(
371
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
375
+ jbDirectory(),
376
+ jbPermissions(),
377
+ jbPrices(),
378
+ jbRulesets(),
379
+ HOOK_STORE,
380
+ jbSplits(),
381
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
382
+ multisig()
372
383
  );
373
384
  ADDRESS_REGISTRY = new JBAddressRegistry();
374
385
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -383,7 +394,7 @@ contract REVLoansAttacks is TestBaseWorkflow {
383
394
 
384
395
  LOANS_CONTRACT = new REVLoans({
385
396
  controller: jbController(),
386
- projects: jbProjects(),
397
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
387
398
  revId: FEE_PROJECT_ID,
388
399
  owner: address(this),
389
400
  permit2: permit2(),
@@ -395,7 +406,8 @@ contract REVLoansAttacks is TestBaseWorkflow {
395
406
  jbDirectory(),
396
407
  FEE_PROJECT_ID,
397
408
  SUCKER_REGISTRY,
398
- address(LOANS_CONTRACT)
409
+ address(LOANS_CONTRACT),
410
+ address(0)
399
411
  );
400
412
 
401
413
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -474,7 +486,7 @@ contract REVLoansAttacks is TestBaseWorkflow {
474
486
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
475
487
 
476
488
  vm.prank(user);
477
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee);
489
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee, user);
478
490
  }
479
491
 
480
492
  // =========================================================================
@@ -646,7 +658,7 @@ contract REVLoansAttacks is TestBaseWorkflow {
646
658
  vm.assume(borrowable > 0);
647
659
 
648
660
  vm.prank(userA);
649
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokensA, payable(userA), 25);
661
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokensA, payable(userA), 25, userA);
650
662
 
651
663
  // After borrowing, tokensA are burned as collateral
652
664
  // But the surplus is adjusted by adding totalBorrowed
@@ -771,7 +783,7 @@ contract REVLoansAttacks is TestBaseWorkflow {
771
783
 
772
784
  // Borrow with max prepaid fee (so no additional fee on immediate repay)
773
785
  vm.prank(USER);
774
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 500);
786
+ (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 500, USER);
775
787
 
776
788
  REVLoan memory loan = LOANS_CONTRACT.loanOf(loanId);
777
789
 
@@ -39,6 +39,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
39
39
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
40
40
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
41
41
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
42
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
43
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
42
44
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
43
45
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
44
46
  import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
@@ -47,6 +49,7 @@ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSp
47
49
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
48
50
  import {REVOwner} from "../src/REVOwner.sol";
49
51
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
52
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
50
53
 
51
54
  /// @notice A terminal mock that always reverts on pay(), used to simulate fee payment failure.
52
55
  contract RevertingFeeTerminal is ERC165, IJBPayoutTerminal {
@@ -333,7 +336,14 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
333
336
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
334
337
  HOOK_STORE = new JB721TiersHookStore();
335
338
  EXAMPLE_HOOK = new JB721TiersHook(
336
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
339
+ jbDirectory(),
340
+ jbPermissions(),
341
+ jbPrices(),
342
+ jbRulesets(),
343
+ HOOK_STORE,
344
+ jbSplits(),
345
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
346
+ multisig()
337
347
  );
338
348
  ADDRESS_REGISTRY = new JBAddressRegistry();
339
349
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -349,7 +359,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
349
359
 
350
360
  LOANS_CONTRACT = new REVLoans({
351
361
  controller: jbController(),
352
- projects: jbProjects(),
362
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
353
363
  revId: FEE_PROJECT_ID,
354
364
  owner: address(this),
355
365
  permit2: permit2(),
@@ -361,7 +371,8 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
361
371
  jbDirectory(),
362
372
  FEE_PROJECT_ID,
363
373
  SUCKER_REGISTRY,
364
- address(LOANS_CONTRACT)
374
+ address(LOANS_CONTRACT),
375
+ address(0)
365
376
  );
366
377
 
367
378
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -450,7 +461,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
450
461
  borrowerBalanceBefore = user.balance;
451
462
 
452
463
  vm.prank(user);
453
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee);
464
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee, user);
454
465
 
455
466
  borrowerBalanceAfter = user.balance;
456
467
  }
@@ -492,7 +503,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
492
503
  // Normal borrow.
493
504
  uint256 balBefore = USER.balance;
494
505
  vm.prank(USER);
495
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25);
506
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25, USER);
496
507
  uint256 normalReceived = USER.balance - balBefore;
497
508
 
498
509
  // Revert to snapshot — identical state.
@@ -504,7 +515,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
504
515
 
505
516
  balBefore = USER.balance;
506
517
  vm.prank(USER);
507
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25);
518
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25, USER);
508
519
  uint256 failReceived = USER.balance - balBefore;
509
520
 
510
521
  // The borrower with a failed fee terminal should receive MORE than the normal borrower,
@@ -537,7 +548,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
537
548
  // Normal borrow.
538
549
  uint256 balBefore = USER.balance;
539
550
  vm.prank(USER);
540
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25);
551
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25, USER);
541
552
  uint256 normalReceived = USER.balance - balBefore;
542
553
 
543
554
  // Get the actual borrow amount from the loan to compute expected REV fee.
@@ -555,7 +566,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
555
566
 
556
567
  balBefore = USER.balance;
557
568
  vm.prank(USER);
558
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25);
569
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25, USER);
559
570
  uint256 failReceived = USER.balance - balBefore;
560
571
 
561
572
  // The difference should be the REV fee amount.
@@ -594,7 +605,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
594
605
  assertEq(allowanceBefore, 0, "No pre-existing allowance");
595
606
 
596
607
  vm.prank(USER);
597
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25);
608
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25, USER);
598
609
 
599
610
  // After the borrow, the allowance to the reverting terminal should still be 0
600
611
  // (the catch block decreased it).
@@ -630,7 +641,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
630
641
  // Normal borrow.
631
642
  uint256 tokenBalBefore = TOKEN.balanceOf(USER);
632
643
  vm.prank(USER);
633
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25);
644
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25, USER);
634
645
  uint256 normalReceived = TOKEN.balanceOf(USER) - tokenBalBefore;
635
646
 
636
647
  // Revert to snapshot.
@@ -642,7 +653,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
642
653
 
643
654
  tokenBalBefore = TOKEN.balanceOf(USER);
644
655
  vm.prank(USER);
645
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25);
656
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25, USER);
646
657
  uint256 failReceived = TOKEN.balanceOf(USER) - tokenBalBefore;
647
658
 
648
659
  // Failed-fee borrower should receive more tokens.
@@ -699,7 +710,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
699
710
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
700
711
 
701
712
  vm.prank(borrower);
702
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(borrower), 25);
713
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(borrower), 25, borrower);
703
714
  }
704
715
 
705
716
  // After 3 borrows with fee failures, no ETH should be stuck.
@@ -737,7 +748,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
737
748
 
738
749
  uint256 balanceBefore = borrower.balance;
739
750
  vm.prank(borrower);
740
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(borrower), 25);
751
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(borrower), 25, borrower);
741
752
  uint256 received = borrower.balance - balanceBefore;
742
753
 
743
754
  // The borrower should always receive something.
@@ -40,6 +40,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
40
40
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
41
41
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
42
42
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
43
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
44
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
43
45
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
44
46
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
45
47
  import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
@@ -47,6 +49,7 @@ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSp
47
49
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
48
50
  import {REVOwner} from "../src/REVOwner.sol";
49
51
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
52
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
50
53
 
51
54
  /// @notice A fake terminal that returns garbage accounting contexts.
52
55
  /// Used to test unvalidated loan source terminal rejection.
@@ -198,7 +201,14 @@ contract REVLoansFindings is TestBaseWorkflow {
198
201
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
199
202
  HOOK_STORE = new JB721TiersHookStore();
200
203
  EXAMPLE_HOOK = new JB721TiersHook(
201
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
204
+ jbDirectory(),
205
+ jbPermissions(),
206
+ jbPrices(),
207
+ jbRulesets(),
208
+ HOOK_STORE,
209
+ jbSplits(),
210
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
211
+ multisig()
202
212
  );
203
213
  ADDRESS_REGISTRY = new JBAddressRegistry();
204
214
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -207,7 +217,7 @@ contract REVLoansFindings is TestBaseWorkflow {
207
217
 
208
218
  LOANS_CONTRACT = new REVLoans({
209
219
  controller: jbController(),
210
- projects: jbProjects(),
220
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
211
221
  revId: FEE_PROJECT_ID,
212
222
  owner: address(this),
213
223
  permit2: permit2(),
@@ -219,7 +229,8 @@ contract REVLoansFindings is TestBaseWorkflow {
219
229
  jbDirectory(),
220
230
  FEE_PROJECT_ID,
221
231
  SUCKER_REGISTRY,
222
- address(LOANS_CONTRACT)
232
+ address(LOANS_CONTRACT),
233
+ address(0)
223
234
  );
224
235
 
225
236
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -372,7 +383,7 @@ contract REVLoansFindings is TestBaseWorkflow {
372
383
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
373
384
 
374
385
  vm.prank(USER);
375
- (loanId, loan) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, loanable, tokens, payable(USER), 25);
386
+ (loanId, loan) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, loanable, tokens, payable(USER), 25, USER);
376
387
  }
377
388
 
378
389
  //*********************************************************************//
@@ -411,7 +422,7 @@ contract REVLoansFindings is TestBaseWorkflow {
411
422
  );
412
423
 
413
424
  vm.prank(USER);
414
- LOANS_CONTRACT.borrowFrom(REVNET_ID, fakeSource, loanable, tokens, payable(USER), 25);
425
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, fakeSource, loanable, tokens, payable(USER), 25, USER);
415
426
  }
416
427
 
417
428
  //*********************************************************************//
@@ -35,6 +35,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
35
35
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
36
36
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
37
37
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
38
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
39
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
38
40
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
39
41
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
40
42
  import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
@@ -43,6 +45,7 @@ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSp
43
45
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
44
46
  import {REVOwner} from "../src/REVOwner.sol";
45
47
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
48
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
46
49
 
47
50
  /// @notice A fake terminal that tracks whether useAllowanceOf was called.
48
51
  /// @dev REVLoans.borrowFrom does not validate source terminal registration.
@@ -193,7 +196,14 @@ contract REVLoansRegressions is TestBaseWorkflow {
193
196
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
194
197
  HOOK_STORE = new JB721TiersHookStore();
195
198
  EXAMPLE_HOOK = new JB721TiersHook(
196
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
199
+ jbDirectory(),
200
+ jbPermissions(),
201
+ jbPrices(),
202
+ jbRulesets(),
203
+ HOOK_STORE,
204
+ jbSplits(),
205
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
206
+ multisig()
197
207
  );
198
208
  ADDRESS_REGISTRY = new JBAddressRegistry();
199
209
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -202,7 +212,7 @@ contract REVLoansRegressions is TestBaseWorkflow {
202
212
 
203
213
  LOANS_CONTRACT = new REVLoans({
204
214
  controller: jbController(),
205
- projects: jbProjects(),
215
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
206
216
  revId: FEE_PROJECT_ID,
207
217
  owner: address(this),
208
218
  permit2: permit2(),
@@ -214,7 +224,8 @@ contract REVLoansRegressions is TestBaseWorkflow {
214
224
  jbDirectory(),
215
225
  FEE_PROJECT_ID,
216
226
  SUCKER_REGISTRY,
217
- address(LOANS_CONTRACT)
227
+ address(LOANS_CONTRACT),
228
+ address(0)
218
229
  );
219
230
 
220
231
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -335,7 +346,7 @@ contract REVLoansRegressions is TestBaseWorkflow {
335
346
  );
336
347
 
337
348
  vm.prank(USER);
338
- LOANS_CONTRACT.borrowFrom(REVNET_ID, fakeSource, borrowable, tokens, payable(USER), 500);
349
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, fakeSource, borrowable, tokens, payable(USER), 500, USER);
339
350
  }
340
351
 
341
352
  /// @notice Verify that the configured loan source (real terminal) is properly registered.
@@ -35,11 +35,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
35
35
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
36
36
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
37
37
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
38
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
39
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
38
40
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
39
41
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
40
42
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
41
43
  import {REVOwner} from "../src/REVOwner.sol";
42
44
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
45
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
43
46
 
44
47
  struct SourceFeeProjectConfig {
45
48
  REVConfig configuration;
@@ -220,7 +223,14 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
220
223
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
221
224
  HOOK_STORE = new JB721TiersHookStore();
222
225
  EXAMPLE_HOOK = new JB721TiersHook(
223
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
226
+ jbDirectory(),
227
+ jbPermissions(),
228
+ jbPrices(),
229
+ jbRulesets(),
230
+ HOOK_STORE,
231
+ jbSplits(),
232
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
233
+ multisig()
224
234
  );
225
235
  ADDRESS_REGISTRY = new JBAddressRegistry();
226
236
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -229,7 +239,7 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
229
239
 
230
240
  LOANS_CONTRACT = new REVLoans({
231
241
  controller: jbController(),
232
- projects: jbProjects(),
242
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
233
243
  revId: FEE_PROJECT_ID,
234
244
  owner: address(this),
235
245
  permit2: permit2(),
@@ -241,7 +251,8 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
241
251
  jbDirectory(),
242
252
  FEE_PROJECT_ID,
243
253
  SUCKER_REGISTRY,
244
- address(LOANS_CONTRACT)
254
+ address(LOANS_CONTRACT),
255
+ address(0)
245
256
  );
246
257
 
247
258
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -310,7 +321,7 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
310
321
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
311
322
 
312
323
  vm.prank(user);
313
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
324
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
314
325
  }
315
326
 
316
327
  // =========================================================================
@@ -495,7 +506,7 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
495
506
 
496
507
  uint256 balBefore = USER.balance;
497
508
  vm.prank(USER);
498
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25);
509
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25, USER);
499
510
 
500
511
  uint256 received = USER.balance - balBefore;
501
512
  assertGt(received, 0, "Borrower should receive ETH even when source fee terminal fails");