@rev-net/core-v6 0.0.7 → 0.0.9

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 (59) hide show
  1. package/ADMINISTRATION.md +186 -0
  2. package/ARCHITECTURE.md +87 -0
  3. package/README.md +4 -2
  4. package/RISKS.md +49 -0
  5. package/SKILLS.md +22 -2
  6. package/STYLE_GUIDE.md +482 -0
  7. package/foundry.toml +6 -6
  8. package/package.json +13 -10
  9. package/script/Deploy.s.sol +3 -2
  10. package/src/REVDeployer.sol +129 -72
  11. package/src/REVLoans.sol +174 -165
  12. package/src/interfaces/IREVDeployer.sol +111 -72
  13. package/src/interfaces/IREVLoans.sol +116 -76
  14. package/src/structs/REV721TiersHookFlags.sol +14 -0
  15. package/src/structs/REVBaseline721HookConfig.sol +27 -0
  16. package/src/structs/REVDeploy721TiersHookConfig.sol +2 -2
  17. package/test/REV.integrations.t.sol +4 -3
  18. package/test/REVAutoIssuanceFuzz.t.sol +12 -8
  19. package/test/REVDeployerAuditRegressions.t.sol +4 -3
  20. package/test/REVInvincibility.t.sol +8 -6
  21. package/test/REVInvincibilityHandler.sol +1 -0
  22. package/test/REVLifecycle.t.sol +4 -3
  23. package/test/REVLoans.invariants.t.sol +5 -3
  24. package/test/REVLoansAttacks.t.sol +4 -3
  25. package/test/REVLoansAuditRegressions.t.sol +13 -24
  26. package/test/REVLoansFeeRecovery.t.sol +4 -3
  27. package/test/REVLoansSourced.t.sol +4 -3
  28. package/test/REVLoansUnSourced.t.sol +4 -3
  29. package/test/REVLoans_AuditFindings.t.sol +644 -0
  30. package/test/TestEmptyBuybackSpecs.t.sol +4 -3
  31. package/test/TestPR09_ConversionDocumentation.t.sol +4 -3
  32. package/test/TestPR10_LiquidationBehavior.t.sol +4 -3
  33. package/test/TestPR11_LowFindings.t.sol +4 -3
  34. package/test/TestPR12_FlashLoanSurplus.t.sol +4 -3
  35. package/test/TestPR13_CrossSourceReallocation.t.sol +4 -3
  36. package/test/TestPR15_CashOutCallerValidation.t.sol +4 -3
  37. package/test/TestPR16_ZeroRepayment.t.sol +4 -3
  38. package/test/TestPR21_Uint112Overflow.t.sol +4 -3
  39. package/test/TestPR22_HookArrayOOB.t.sol +4 -3
  40. package/test/TestPR26_BurnHeldTokens.t.sol +4 -3
  41. package/test/TestPR27_CEIPattern.t.sol +4 -3
  42. package/test/TestPR29_SwapTerminalPermission.t.sol +4 -3
  43. package/test/TestPR32_MixedFixes.t.sol +4 -3
  44. package/test/TestSplitWeightAdjustment.t.sol +445 -0
  45. package/test/TestSplitWeightE2E.t.sol +528 -0
  46. package/test/TestSplitWeightFork.t.sol +821 -0
  47. package/test/TestStageTransitionBorrowable.t.sol +4 -3
  48. package/test/fork/ForkTestBase.sol +617 -0
  49. package/test/fork/TestCashOutFork.t.sol +245 -0
  50. package/test/fork/TestLoanBorrowFork.t.sol +163 -0
  51. package/test/fork/TestLoanLiquidationFork.t.sol +129 -0
  52. package/test/fork/TestLoanReallocateFork.t.sol +103 -0
  53. package/test/fork/TestLoanRepayFork.t.sol +184 -0
  54. package/test/fork/TestSplitWeightFork.t.sol +186 -0
  55. package/test/mock/MockBuybackDataHook.sol +11 -4
  56. package/test/mock/MockBuybackDataHookMintPath.sol +11 -3
  57. package/test/regression/TestI20_CumulativeLoanCounter.t.sol +6 -5
  58. package/test/regression/TestL27_LiquidateGapHandling.t.sol +7 -6
  59. package/SECURITY.md +0 -68
@@ -30,7 +30,7 @@ import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/
30
30
  /// @notice Fuzz tests for REVDeployer multi-stage auto-issuance.
31
31
  /// Tests stage ID computation consistency and multi-stage claiming behavior.
32
32
  /// Stage IDs use block.timestamp + i which may mismatch actual ruleset IDs.
33
- contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
33
+ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow {
34
34
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
35
35
 
36
36
  REVDeployer REV_DEPLOYER;
@@ -55,7 +55,8 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
55
55
 
56
56
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
57
57
  HOOK_STORE = new JB721TiersHookStore();
58
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
58
+ EXAMPLE_HOOK =
59
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
59
60
  ADDRESS_REGISTRY = new JBAddressRegistry();
60
61
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
61
62
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -67,7 +68,7 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
67
68
  FEE_PROJECT_ID,
68
69
  HOOK_DEPLOYER,
69
70
  PUBLISHER,
70
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
71
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
71
72
  makeAddr("loans"),
72
73
  TRUSTED_FORWARDER
73
74
  );
@@ -165,9 +166,12 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
165
166
 
166
167
  /// @notice Deploy 3-stage revnet, advance time, claim auto-issuance from each stage.
167
168
  function test_multiStage_allStagesClaimable() external {
169
+ // Save the deploy timestamp for absolute warp targets.
170
+ uint256 deployTs = block.timestamp;
171
+
168
172
  (uint256 revnetId, uint256[] memory stageIds) = _deployMultiStageRevnet(3);
169
173
 
170
- // Stage 0 starts at block.timestamp — immediately claimable.
174
+ // Stage 0 starts at deploy time — immediately claimable.
171
175
  REV_DEPLOYER.autoIssueFor(revnetId, stageIds[0], multisig());
172
176
 
173
177
  uint256 stage0Amount = (10_000) * decimalMultiplier;
@@ -175,8 +179,8 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
175
179
  IJBToken(jbTokens().tokenOf(revnetId)).balanceOf(multisig()), stage0Amount, "Stage 0 auto-issuance claimed"
176
180
  );
177
181
 
178
- // Stage 1 starts at block.timestamp + 180 days.
179
- vm.warp(block.timestamp + 180 days);
182
+ // Stage 1 starts at deployTs + 180 days.
183
+ vm.warp(deployTs + 180 days);
180
184
  REV_DEPLOYER.autoIssueFor(revnetId, stageIds[1], multisig());
181
185
 
182
186
  uint256 stage1Amount = (11_000) * decimalMultiplier;
@@ -186,8 +190,8 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
186
190
  "Stage 1 auto-issuance claimed"
187
191
  );
188
192
 
189
- // Stage 2 starts at block.timestamp + 360 days.
190
- vm.warp(block.timestamp + 180 days);
193
+ // Stage 2 starts at deployTs + 360 days.
194
+ vm.warp(deployTs + 360 days);
191
195
  REV_DEPLOYER.autoIssueFor(revnetId, stageIds[2], multisig());
192
196
 
193
197
  uint256 stage2Amount = (12_000) * decimalMultiplier;
@@ -29,7 +29,7 @@ import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressReg
29
29
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
30
30
 
31
31
  /// @notice Regression tests for REVDeployer.
32
- contract REVDeployerRegressions_Local is TestBaseWorkflow, JBTest {
32
+ contract REVDeployerRegressions_Local is TestBaseWorkflow {
33
33
  using JBRulesetMetadataResolver for JBRuleset;
34
34
 
35
35
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
@@ -56,7 +56,8 @@ contract REVDeployerRegressions_Local is TestBaseWorkflow, JBTest {
56
56
 
57
57
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
58
58
  HOOK_STORE = new JB721TiersHookStore();
59
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
59
+ EXAMPLE_HOOK =
60
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
60
61
  ADDRESS_REGISTRY = new JBAddressRegistry();
61
62
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
62
63
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -77,7 +78,7 @@ contract REVDeployerRegressions_Local is TestBaseWorkflow, JBTest {
77
78
  FEE_PROJECT_ID,
78
79
  HOOK_DEPLOYER,
79
80
  PUBLISHER,
80
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
81
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
81
82
  address(LOANS_CONTRACT),
82
83
  TRUSTED_FORWARDER
83
84
  );
@@ -53,7 +53,7 @@ struct InvincibilityProjectConfig {
53
53
  // =========================================================================
54
54
  // Section A + B: Property Verification & Economic Tests
55
55
  // =========================================================================
56
- contract REVInvincibility_PropertyTests is TestBaseWorkflow, JBTest {
56
+ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
57
57
  using JBRulesetMetadataResolver for JBRuleset;
58
58
 
59
59
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
@@ -202,7 +202,8 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow, JBTest {
202
202
 
203
203
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
204
204
  HOOK_STORE = new JB721TiersHookStore();
205
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
205
+ EXAMPLE_HOOK =
206
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
206
207
  ADDRESS_REGISTRY = new JBAddressRegistry();
207
208
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
208
209
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -224,7 +225,7 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow, JBTest {
224
225
  FEE_PROJECT_ID,
225
226
  HOOK_DEPLOYER,
226
227
  PUBLISHER,
227
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
228
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
228
229
  address(LOANS_CONTRACT),
229
230
  TRUSTED_FORWARDER
230
231
  );
@@ -922,7 +923,7 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow, JBTest {
922
923
  // =========================================================================
923
924
  // Section C: Invariant Properties (6 invariants)
924
925
  // =========================================================================
925
- contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow, JBTest {
926
+ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow {
926
927
  using JBRulesetMetadataResolver for JBRuleset;
927
928
 
928
929
  bytes32 REV_DEPLOYER_SALT = "REVDeployer_INV";
@@ -956,7 +957,8 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow, JBTest {
956
957
 
957
958
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
958
959
  HOOK_STORE = new JB721TiersHookStore();
959
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
960
+ EXAMPLE_HOOK =
961
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
960
962
  ADDRESS_REGISTRY = new JBAddressRegistry();
961
963
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
962
964
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -977,7 +979,7 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow, JBTest {
977
979
  FEE_PROJECT_ID,
978
980
  HOOK_DEPLOYER,
979
981
  PUBLISHER,
980
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
982
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
981
983
  address(LOANS_CONTRACT),
982
984
  TRUSTED_FORWARDER
983
985
  );
@@ -11,6 +11,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
11
  import {IREVLoans} from "../src/interfaces/IREVLoans.sol";
12
12
  import {REVLoan} from "../src/structs/REVLoan.sol";
13
13
  import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
14
+ import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
14
15
 
15
16
  /// @title REVInvincibilityHandler
16
17
  /// @notice Stateful fuzzing handler for the revnet + loans interaction surface.
@@ -30,7 +30,7 @@ import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressReg
30
30
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
31
31
 
32
32
  /// @notice Full revnet lifecycle E2E: deploy 3-stage -> pay -> advance stages -> cash out.
33
- contract REVLifecycle_Local is TestBaseWorkflow, JBTest {
33
+ contract REVLifecycle_Local is TestBaseWorkflow {
34
34
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
35
35
  bytes32 ERC20_SALT = "REV_TOKEN";
36
36
 
@@ -61,7 +61,8 @@ contract REVLifecycle_Local is TestBaseWorkflow, JBTest {
61
61
 
62
62
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
63
63
  HOOK_STORE = new JB721TiersHookStore();
64
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
64
+ EXAMPLE_HOOK =
65
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
65
66
  ADDRESS_REGISTRY = new JBAddressRegistry();
66
67
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
67
68
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -82,7 +83,7 @@ contract REVLifecycle_Local is TestBaseWorkflow, JBTest {
82
83
  FEE_PROJECT_ID,
83
84
  HOOK_DEPLOYER,
84
85
  PUBLISHER,
85
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
86
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
86
87
  address(LOANS_CONTRACT),
87
88
  TRUSTED_FORWARDER
88
89
  );
@@ -31,6 +31,7 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
31
31
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
32
32
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
33
33
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
34
+ import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
34
35
 
35
36
  struct FeeProjectConfig {
36
37
  REVConfig configuration;
@@ -252,7 +253,7 @@ contract REVLoansCallHandler is JBTest {
252
253
  }
253
254
  }
254
255
 
255
- contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow, JBTest {
256
+ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow {
256
257
  // A library that parses the packed ruleset metadata into a friendlier format.
257
258
  using JBRulesetMetadataResolver for JBRuleset;
258
259
 
@@ -475,7 +476,8 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow, JBTest {
475
476
 
476
477
  HOOK_STORE = new JB721TiersHookStore();
477
478
 
478
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
479
+ EXAMPLE_HOOK =
480
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
479
481
 
480
482
  ADDRESS_REGISTRY = new JBAddressRegistry();
481
483
 
@@ -499,7 +501,7 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow, JBTest {
499
501
  FEE_PROJECT_ID,
500
502
  HOOK_DEPLOYER,
501
503
  PUBLISHER,
502
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
504
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
503
505
  address(LOANS_CONTRACT),
504
506
  TRUSTED_FORWARDER
505
507
  );
@@ -176,7 +176,7 @@ struct AttackProjectConfig {
176
176
  /// @title REVLoansAttacks
177
177
  /// @notice Attack tests for REVLoans covering uint112 truncation, reentrancy,
178
178
  /// collateral race conditions, liquidation edge cases, and fuzz testing.
179
- contract REVLoansAttacks is TestBaseWorkflow, JBTest {
179
+ contract REVLoansAttacks is TestBaseWorkflow {
180
180
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
181
181
  bytes32 ERC20_SALT = "REV_TOKEN";
182
182
 
@@ -318,7 +318,8 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
318
318
 
319
319
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
320
320
  HOOK_STORE = new JB721TiersHookStore();
321
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
321
+ EXAMPLE_HOOK =
322
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
322
323
  ADDRESS_REGISTRY = new JBAddressRegistry();
323
324
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
324
325
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -345,7 +346,7 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
345
346
  FEE_PROJECT_ID,
346
347
  HOOK_DEPLOYER,
347
348
  PUBLISHER,
348
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
349
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
349
350
  address(LOANS_CONTRACT),
350
351
  TRUSTED_FORWARDER
351
352
  );
@@ -129,7 +129,7 @@ contract FakeTerminal is ERC165, IJBPayoutTerminal {
129
129
  }
130
130
 
131
131
  /// @notice Regression tests for REVLoans unvalidated source terminal.
132
- contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
132
+ contract REVLoansRegressions_Local is TestBaseWorkflow {
133
133
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
134
134
  bytes32 ERC20_SALT = "REV_TOKEN";
135
135
 
@@ -157,7 +157,8 @@ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
157
157
 
158
158
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
159
159
  HOOK_STORE = new JB721TiersHookStore();
160
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
160
+ EXAMPLE_HOOK =
161
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
161
162
  ADDRESS_REGISTRY = new JBAddressRegistry();
162
163
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
163
164
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -178,7 +179,7 @@ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
178
179
  FEE_PROJECT_ID,
179
180
  HOOK_DEPLOYER,
180
181
  PUBLISHER,
181
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
182
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
182
183
  address(LOANS_CONTRACT),
183
184
  TRUSTED_FORWARDER
184
185
  );
@@ -246,6 +247,9 @@ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
246
247
  /// it is registered in the JBDirectory for the project.
247
248
  /// @dev The fake terminal's useAllowanceOf is called, showing no directory check occurs.
248
249
  /// In production, a malicious terminal could return fake amounts or misroute funds.
250
+ /// @notice Verifies that borrowFrom now rejects unregistered terminals.
251
+ /// @dev Previously this test demonstrated the vulnerability. After the fix,
252
+ /// borrowFrom reverts with REVLoans_InvalidTerminal before reaching the fake terminal.
249
253
  function test_unvalidatedSourceTerminal() public {
250
254
  // Step 1: User pays into the revnet to get tokens (collateral)
251
255
  vm.prank(USER);
@@ -265,9 +269,8 @@ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
265
269
  }
266
270
  assertFalse(found, "fake terminal should NOT be in the directory");
267
271
 
268
- // Step 3: Try to borrow using the fake terminal as the source
269
- // Vulnerability: REVLoans.borrowFrom does NOT check if the terminal
270
- // is registered in the directory before calling useAllowanceOf on it.
272
+ // Step 3: Try to borrow using the fake terminal as the source.
273
+ // This now correctly reverts with REVLoans_InvalidTerminal.
271
274
  REVLoanSource memory fakeSource =
272
275
  REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: IJBPayoutTerminal(address(fakeTerminal))});
273
276
 
@@ -275,28 +278,14 @@ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
275
278
  LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
276
279
  assertGt(borrowable, 0, "should have borrowable amount");
277
280
 
278
- // Use vm.expectCall to verify the fake terminal's useAllowanceOf
279
- // is called. This works even if the outer call reverts, because expectCall
280
- // records the call was made regardless.
281
- // The code calls accountingContextForTokenOf first, then useAllowanceOf.
282
- vm.expectCall(
283
- address(fakeTerminal),
284
- abi.encodeWithSelector(
285
- IJBTerminal.accountingContextForTokenOf.selector, REVNET_ID, JBConstants.NATIVE_TOKEN
286
- )
281
+ // The borrow should revert with REVLoans_InvalidTerminal because the fake terminal
282
+ // is not registered in the directory. The fake terminal is never called.
283
+ vm.expectRevert(
284
+ abi.encodeWithSelector(REVLoans.REVLoans_InvalidTerminal.selector, address(fakeTerminal), REVNET_ID)
287
285
  );
288
- vm.expectCall(address(fakeTerminal), abi.encodeWithSelector(IJBPayoutTerminal.useAllowanceOf.selector));
289
286
 
290
- // The borrow will reach the fake terminal (showing no validation),
291
- // but will revert downstream when trying to transfer 0 - fees (underflow).
292
287
  vm.prank(USER);
293
- vm.expectRevert();
294
288
  LOANS_CONTRACT.borrowFrom(REVNET_ID, fakeSource, borrowable, tokens, payable(USER), 500);
295
-
296
- // If we reach here, both vm.expectCall checks passed:
297
- // 1. accountingContextForTokenOf was called on the fake terminal
298
- // 2. useAllowanceOf was called on the fake terminal
299
- // This shows no directory validation before calling the source terminal
300
289
  }
301
290
 
302
291
  /// @notice Verify that the configured loan source (real terminal) is properly registered.
@@ -136,7 +136,7 @@ struct FeeRecoveryProjectConfig {
136
136
  /// @notice Tests for the fee payment error recovery in REVLoans._addTo().
137
137
  /// @dev When feeTerminal.pay() reverts, the borrower should receive the fee amount back
138
138
  /// instead of losing it. For ERC-20 tokens, the dangling allowance must also be cleaned up.
139
- contract REVLoansFeeRecovery is TestBaseWorkflow, JBTest {
139
+ contract REVLoansFeeRecovery is TestBaseWorkflow {
140
140
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
141
141
  bytes32 ERC20_SALT = "REV_TOKEN";
142
142
 
@@ -281,7 +281,8 @@ contract REVLoansFeeRecovery is TestBaseWorkflow, JBTest {
281
281
 
282
282
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
283
283
  HOOK_STORE = new JB721TiersHookStore();
284
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
284
+ EXAMPLE_HOOK =
285
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
285
286
  ADDRESS_REGISTRY = new JBAddressRegistry();
286
287
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
287
288
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -309,7 +310,7 @@ contract REVLoansFeeRecovery is TestBaseWorkflow, JBTest {
309
310
  FEE_PROJECT_ID,
310
311
  HOOK_DEPLOYER,
311
312
  PUBLISHER,
312
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
313
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
313
314
  address(LOANS_CONTRACT),
314
315
  TRUSTED_FORWARDER
315
316
  );
@@ -38,7 +38,7 @@ struct FeeProjectConfig {
38
38
  REVSuckerDeploymentConfig suckerDeploymentConfiguration;
39
39
  }
40
40
 
41
- contract REVLoansSourcedTests is TestBaseWorkflow, JBTest {
41
+ contract REVLoansSourcedTests is TestBaseWorkflow {
42
42
  /// @notice the salts that are used to deploy the contracts.
43
43
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
44
44
  bytes32 ERC20_SALT = "REV_TOKEN";
@@ -262,7 +262,8 @@ contract REVLoansSourcedTests is TestBaseWorkflow, JBTest {
262
262
 
263
263
  HOOK_STORE = new JB721TiersHookStore();
264
264
 
265
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
265
+ EXAMPLE_HOOK =
266
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
266
267
 
267
268
  ADDRESS_REGISTRY = new JBAddressRegistry();
268
269
 
@@ -298,7 +299,7 @@ contract REVLoansSourcedTests is TestBaseWorkflow, JBTest {
298
299
  FEE_PROJECT_ID,
299
300
  HOOK_DEPLOYER,
300
301
  PUBLISHER,
301
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
302
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
302
303
  address(LOANS_CONTRACT),
303
304
  TRUSTED_FORWARDER
304
305
  );
@@ -35,7 +35,7 @@ struct FeeProjectConfig {
35
35
  REVSuckerDeploymentConfig suckerDeploymentConfiguration;
36
36
  }
37
37
 
38
- contract REVLoansUnsourcedTests is TestBaseWorkflow, JBTest {
38
+ contract REVLoansUnsourcedTests is TestBaseWorkflow {
39
39
  /// @notice the salts that are used to deploy the contracts.
40
40
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
41
41
  bytes32 ERC20_SALT = "REV_TOKEN";
@@ -249,7 +249,8 @@ contract REVLoansUnsourcedTests is TestBaseWorkflow, JBTest {
249
249
 
250
250
  HOOK_STORE = new JB721TiersHookStore();
251
251
 
252
- EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
252
+ EXAMPLE_HOOK =
253
+ new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
253
254
 
254
255
  ADDRESS_REGISTRY = new JBAddressRegistry();
255
256
 
@@ -273,7 +274,7 @@ contract REVLoansUnsourcedTests is TestBaseWorkflow, JBTest {
273
274
  FEE_PROJECT_ID,
274
275
  HOOK_DEPLOYER,
275
276
  PUBLISHER,
276
- IJBRulesetDataHook(address(MOCK_BUYBACK)),
277
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
277
278
  address(LOANS_CONTRACT),
278
279
  TRUSTED_FORWARDER
279
280
  );