@rev-net/core-v6 0.0.18 → 0.0.20

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 (64) hide show
  1. package/ADMINISTRATION.md +14 -4
  2. package/ARCHITECTURE.md +13 -10
  3. package/AUDIT_INSTRUCTIONS.md +39 -18
  4. package/CHANGE_LOG.md +78 -1
  5. package/README.md +10 -5
  6. package/RISKS.md +12 -11
  7. package/SKILLS.md +27 -12
  8. package/USER_JOURNEYS.md +15 -14
  9. package/foundry.toml +1 -1
  10. package/package.json +1 -1
  11. package/script/Deploy.s.sol +37 -4
  12. package/src/REVDeployer.sol +23 -305
  13. package/src/REVLoans.sol +24 -29
  14. package/src/REVOwner.sol +429 -0
  15. package/src/interfaces/IREVDeployer.sol +4 -10
  16. package/src/interfaces/IREVOwner.sol +10 -0
  17. package/test/REV.integrations.t.sol +12 -1
  18. package/test/REVAutoIssuanceFuzz.t.sol +12 -1
  19. package/test/REVDeployerRegressions.t.sol +15 -2
  20. package/test/REVInvincibility.t.sol +27 -3
  21. package/test/REVLifecycle.t.sol +14 -1
  22. package/test/REVLoans.invariants.t.sol +14 -1
  23. package/test/REVLoansAttacks.t.sol +14 -1
  24. package/test/REVLoansFeeRecovery.t.sol +14 -1
  25. package/test/REVLoansFindings.t.sol +14 -1
  26. package/test/REVLoansRegressions.t.sol +14 -1
  27. package/test/REVLoansSourceFeeRecovery.t.sol +14 -1
  28. package/test/REVLoansSourced.t.sol +14 -1
  29. package/test/REVLoansUnSourced.t.sol +14 -1
  30. package/test/TestBurnHeldTokens.t.sol +14 -1
  31. package/test/TestCEIPattern.t.sol +15 -1
  32. package/test/TestCashOutCallerValidation.t.sol +17 -4
  33. package/test/TestConversionDocumentation.t.sol +14 -1
  34. package/test/TestCrossCurrencyReclaim.t.sol +14 -1
  35. package/test/TestCrossSourceReallocation.t.sol +15 -1
  36. package/test/TestERC2771MetaTx.t.sol +14 -1
  37. package/test/TestEmptyBuybackSpecs.t.sol +17 -3
  38. package/test/TestFlashLoanSurplus.t.sol +15 -1
  39. package/test/TestHookArrayOOB.t.sol +16 -2
  40. package/test/TestLiquidationBehavior.t.sol +15 -1
  41. package/test/TestLoanSourceRotation.t.sol +14 -1
  42. package/test/TestLoansCashOutDelay.t.sol +19 -6
  43. package/test/TestLongTailEconomics.t.sol +14 -1
  44. package/test/TestLowFindings.t.sol +14 -1
  45. package/test/TestMixedFixes.t.sol +15 -1
  46. package/test/TestPermit2Signatures.t.sol +14 -1
  47. package/test/TestReallocationSandwich.t.sol +15 -1
  48. package/test/TestRevnetRegressions.t.sol +14 -1
  49. package/test/TestSplitWeightAdjustment.t.sol +41 -19
  50. package/test/TestSplitWeightE2E.t.sol +23 -2
  51. package/test/TestSplitWeightFork.t.sol +14 -1
  52. package/test/TestStageTransitionBorrowable.t.sol +15 -1
  53. package/test/TestSwapTerminalPermission.t.sol +15 -1
  54. package/test/TestUint112Overflow.t.sol +14 -1
  55. package/test/TestZeroRepayment.t.sol +15 -1
  56. package/test/audit/LoanIdOverflowGuard.t.sol +14 -1
  57. package/test/fork/ForkTestBase.sol +14 -1
  58. package/test/fork/TestPermit2PaymentFork.t.sol +4 -3
  59. package/test/regression/TestBurnPermissionRequired.t.sol +15 -1
  60. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +13 -1
  61. package/test/regression/TestCrossRevnetLiquidation.t.sol +15 -1
  62. package/test/regression/TestCumulativeLoanCounter.t.sol +15 -1
  63. package/test/regression/TestLiquidateGapHandling.t.sol +15 -1
  64. package/test/regression/TestZeroPriceFeed.t.sol +14 -1
@@ -37,6 +37,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
37
37
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
38
38
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
39
39
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
40
+ import {REVOwner} from "../src/REVOwner.sol";
41
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
40
42
 
41
43
  /// @notice Deterministic test for the reallocation sandwich at stage boundaries.
42
44
  /// Documents that a borrower can extract additional value by calling `reallocateCollateralFromLoan` immediately
@@ -50,6 +52,8 @@ contract TestReallocationSandwich is TestBaseWorkflow {
50
52
  // forge-lint: disable-next-line(mixed-case-variable)
51
53
  REVDeployer REV_DEPLOYER;
52
54
  // forge-lint: disable-next-line(mixed-case-variable)
55
+ REVOwner REV_OWNER;
56
+ // forge-lint: disable-next-line(mixed-case-variable)
53
57
  JB721TiersHook EXAMPLE_HOOK;
54
58
  // forge-lint: disable-next-line(mixed-case-variable)
55
59
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -155,6 +159,14 @@ contract TestReallocationSandwich is TestBaseWorkflow {
155
159
  permit2: permit2(),
156
160
  trustedForwarder: TRUSTED_FORWARDER
157
161
  });
162
+ REV_OWNER = new REVOwner(
163
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
164
+ jbDirectory(),
165
+ FEE_PROJECT_ID,
166
+ SUCKER_REGISTRY,
167
+ address(LOANS_CONTRACT)
168
+ );
169
+
158
170
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
159
171
  jbController(),
160
172
  SUCKER_REGISTRY,
@@ -163,8 +175,10 @@ contract TestReallocationSandwich is TestBaseWorkflow {
163
175
  PUBLISHER,
164
176
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
165
177
  address(LOANS_CONTRACT),
166
- TRUSTED_FORWARDER
178
+ TRUSTED_FORWARDER,
179
+ address(REV_OWNER)
167
180
  );
181
+
168
182
  vm.prank(multisig());
169
183
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
170
184
 
@@ -40,6 +40,8 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
41
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
42
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
+ import {REVOwner} from "../src/REVOwner.sol";
44
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
43
45
 
44
46
  /// @notice A test harness that exposes REVLoans internal functions for direct testing.
45
47
  /// Used to test _totalBorrowedFrom without needing to set up a full borrow flow.
@@ -97,6 +99,8 @@ contract TestRevnetRegressions is TestBaseWorkflow {
97
99
  // forge-lint: disable-next-line(mixed-case-variable)
98
100
  REVDeployer REV_DEPLOYER;
99
101
  // forge-lint: disable-next-line(mixed-case-variable)
102
+ REVOwner REV_OWNER;
103
+ // forge-lint: disable-next-line(mixed-case-variable)
100
104
  JB721TiersHook EXAMPLE_HOOK;
101
105
  // forge-lint: disable-next-line(mixed-case-variable)
102
106
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -144,6 +148,14 @@ contract TestRevnetRegressions is TestBaseWorkflow {
144
148
  trustedForwarder: TRUSTED_FORWARDER
145
149
  });
146
150
 
151
+ REV_OWNER = new REVOwner(
152
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
153
+ jbDirectory(),
154
+ FEE_PROJECT_ID,
155
+ SUCKER_REGISTRY,
156
+ address(LOANS_CONTRACT)
157
+ );
158
+
147
159
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
148
160
  jbController(),
149
161
  SUCKER_REGISTRY,
@@ -152,7 +164,8 @@ contract TestRevnetRegressions is TestBaseWorkflow {
152
164
  PUBLISHER,
153
165
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
154
166
  address(LOANS_CONTRACT),
155
- TRUSTED_FORWARDER
167
+ TRUSTED_FORWARDER,
168
+ address(REV_OWNER)
156
169
  );
157
170
 
158
171
  vm.prank(multisig());
@@ -41,6 +41,8 @@ import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBefor
41
41
  import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
42
42
  import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
43
43
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
44
+ import {REVOwner} from "../src/REVOwner.sol";
45
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
44
46
 
45
47
  /// @notice Tests for the split weight adjustment in REVDeployer.beforePayRecordedWith.
46
48
  contract TestSplitWeightAdjustment is TestBaseWorkflow {
@@ -50,6 +52,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
50
52
  // forge-lint: disable-next-line(mixed-case-variable)
51
53
  REVDeployer REV_DEPLOYER;
52
54
  // forge-lint: disable-next-line(mixed-case-variable)
55
+ REVOwner REV_OWNER;
56
+ // forge-lint: disable-next-line(mixed-case-variable)
53
57
  JB721TiersHook EXAMPLE_HOOK;
54
58
  // forge-lint: disable-next-line(mixed-case-variable)
55
59
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -93,6 +97,14 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
93
97
  permit2: permit2(),
94
98
  trustedForwarder: TRUSTED_FORWARDER
95
99
  });
100
+ REV_OWNER = new REVOwner(
101
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
102
+ jbDirectory(),
103
+ FEE_PROJECT_ID,
104
+ SUCKER_REGISTRY,
105
+ address(LOANS_CONTRACT)
106
+ );
107
+
96
108
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
97
109
  jbController(),
98
110
  SUCKER_REGISTRY,
@@ -101,8 +113,10 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
101
113
  PUBLISHER,
102
114
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
103
115
  address(LOANS_CONTRACT),
104
- TRUSTED_FORWARDER
116
+ TRUSTED_FORWARDER,
117
+ address(REV_OWNER)
105
118
  );
119
+
106
120
  vm.prank(multisig());
107
121
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
108
122
  }
@@ -199,7 +213,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
199
213
  });
200
214
 
201
215
  // 721 hook is deployed but has no tiers, buyback returns context.weight.
202
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
216
+ (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
203
217
 
204
218
  assertEq(weight, context.weight, "weight should pass through unchanged");
205
219
  assertEq(specs.length, 1, "should have 721 hook spec even with no tiers");
@@ -217,11 +231,11 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
217
231
  // tiered721HookOf is internal, so we use vm.store.
218
232
  // Slot for tiered721HookOf[revnetId]: keccak256(abi.encode(revnetId, slot))
219
233
  // Need to find the storage slot for tiered721HookOf mapping.
220
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3))); // slot 9 for tiered721HookOf in REVDeployer
221
- vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
234
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
235
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
222
236
 
223
237
  // Verify the store worked.
224
- assertEq(address(REV_DEPLOYER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
238
+ assertEq(address(REV_OWNER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
225
239
 
226
240
  // Mock 721 hook returning 0.3 ETH split on 1 ETH payment.
227
241
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -250,7 +264,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
250
264
  metadata: ""
251
265
  });
252
266
 
253
- (uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
267
+ (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
254
268
 
255
269
  // Buyback returns context.weight (1000e18) since mock buyback passes through.
256
270
  // Weight adjusted for 0.3 ETH split on 1 ETH: 1000e18 * 0.7 = 700e18.
@@ -263,8 +277,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
263
277
 
264
278
  address mock721 = makeAddr("mock721_full");
265
279
  vm.etch(mock721, bytes("0x01"));
266
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
267
- vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
280
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
281
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
268
282
 
269
283
  // Mock 721 hook returning full 1 ETH split.
270
284
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -293,7 +307,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
293
307
  metadata: ""
294
308
  });
295
309
 
296
- (uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
310
+ (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
297
311
 
298
312
  assertEq(weight, 0, "full split = zero weight");
299
313
  }
@@ -308,6 +322,13 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
308
322
 
309
323
  // Deploy a new REVDeployer with the AMM buyback mock.
310
324
  MockBuybackDataHook ammBuyback = new MockBuybackDataHook();
325
+ REVOwner ammOwner = new REVOwner(
326
+ IJBBuybackHookRegistry(address(ammBuyback)),
327
+ jbDirectory(),
328
+ FEE_PROJECT_ID,
329
+ SUCKER_REGISTRY,
330
+ address(LOANS_CONTRACT)
331
+ );
311
332
  REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM"}(
312
333
  jbController(),
313
334
  SUCKER_REGISTRY,
@@ -316,7 +337,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
316
337
  PUBLISHER,
317
338
  IJBBuybackHookRegistry(address(ammBuyback)),
318
339
  address(LOANS_CONTRACT),
319
- TRUSTED_FORWARDER
340
+ TRUSTED_FORWARDER,
341
+ address(ammOwner)
320
342
  );
321
343
 
322
344
  vm.prank(multisig());
@@ -348,8 +370,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
348
370
  // Mock a 721 hook for this project.
349
371
  address mock721 = makeAddr("mock721_amm");
350
372
  vm.etch(mock721, bytes("0x01"));
351
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
352
- vm.store(address(ammDeployer), slot, bytes32(uint256(uint160(mock721))));
373
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
374
+ vm.store(address(ammOwner), slot, bytes32(uint256(uint160(mock721))));
353
375
 
354
376
  // Mock 721 hook returning 0.4 ETH split on 1 ETH payment.
355
377
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -378,7 +400,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
378
400
  metadata: ""
379
401
  });
380
402
 
381
- (uint256 weight, JBPayHookSpecification[] memory specs) = ammDeployer.beforePayRecordedWith(context);
403
+ (uint256 weight, JBPayHookSpecification[] memory specs) = ammOwner.beforePayRecordedWith(context);
382
404
 
383
405
  // AMM buyback returns context.weight (passes through the reduced weight from context).
384
406
  // The buyback mock receives a reduced context with 0.6 ETH and returns that weight.
@@ -398,8 +420,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
398
420
 
399
421
  address mock721 = makeAddr("mock721_mint");
400
422
  vm.etch(mock721, bytes("0x01"));
401
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
402
- vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
423
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
424
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
403
425
 
404
426
  // Mock 721 hook returning 0.2 ETH split.
405
427
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -428,7 +450,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
428
450
  metadata: ""
429
451
  });
430
452
 
431
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
453
+ (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
432
454
 
433
455
  // Buyback mint path: returns context.weight (1000e18 from reduced context = 0.8 ETH context).
434
456
  // Actually MockBuybackDataHookMintPath returns context.weight with empty specs.
@@ -447,8 +469,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
447
469
 
448
470
  address mock721 = makeAddr("mock721_specs");
449
471
  vm.etch(mock721, bytes("0x01"));
450
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
451
- vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
472
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
473
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
452
474
 
453
475
  // Mock 721 hook returning split amount with metadata.
454
476
  bytes memory splitMeta = abi.encode(uint256(42));
@@ -478,7 +500,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
478
500
  metadata: ""
479
501
  });
480
502
 
481
- (, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
503
+ (, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
482
504
 
483
505
  // Should have 721 hook spec (buyback empty).
484
506
  assertEq(specs.length, 1, "should have 1 spec (721 hook, buyback empty)");
@@ -46,6 +46,8 @@ import {REVBaseline721HookConfig} from "../src/structs/REVBaseline721HookConfig.
46
46
  import {REV721TiersHookFlags} from "../src/structs/REV721TiersHookFlags.sol";
47
47
  import {REVCroptopAllowedPost} from "../src/structs/REVCroptopAllowedPost.sol";
48
48
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
49
+ import {REVOwner} from "../src/REVOwner.sol";
50
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
49
51
 
50
52
  /// @notice E2E tests verifying that the split weight adjustment in REVDeployer produces correct token counts
51
53
  /// when payments flow through the full terminal → store → dataHook → mint pipeline.
@@ -59,6 +61,8 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
59
61
  // forge-lint: disable-next-line(mixed-case-variable)
60
62
  REVDeployer REV_DEPLOYER;
61
63
  // forge-lint: disable-next-line(mixed-case-variable)
64
+ REVOwner REV_OWNER;
65
+ // forge-lint: disable-next-line(mixed-case-variable)
62
66
  JB721TiersHook EXAMPLE_HOOK;
63
67
  // forge-lint: disable-next-line(mixed-case-variable)
64
68
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -113,6 +117,14 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
113
117
  trustedForwarder: TRUSTED_FORWARDER
114
118
  });
115
119
 
120
+ REV_OWNER = new REVOwner(
121
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK_MINT)),
122
+ jbDirectory(),
123
+ FEE_PROJECT_ID,
124
+ SUCKER_REGISTRY,
125
+ address(LOANS_CONTRACT)
126
+ );
127
+
116
128
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
117
129
  jbController(),
118
130
  SUCKER_REGISTRY,
@@ -121,7 +133,8 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
121
133
  PUBLISHER,
122
134
  IJBBuybackHookRegistry(address(MOCK_BUYBACK_MINT)),
123
135
  address(LOANS_CONTRACT),
124
- TRUSTED_FORWARDER
136
+ TRUSTED_FORWARDER,
137
+ address(REV_OWNER)
125
138
  );
126
139
 
127
140
  vm.prank(multisig());
@@ -405,6 +418,13 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
405
418
  vm.prank(multisig());
406
419
  jbProjects().approve(address(0), FEE_PROJECT_ID); // Clear old approval.
407
420
 
421
+ REVOwner ammOwner = new REVOwner(
422
+ IJBBuybackHookRegistry(address(ammBuyback)),
423
+ jbDirectory(),
424
+ FEE_PROJECT_ID,
425
+ SUCKER_REGISTRY,
426
+ address(LOANS_CONTRACT)
427
+ );
408
428
  REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM_E2E"}(
409
429
  jbController(),
410
430
  SUCKER_REGISTRY,
@@ -413,7 +433,8 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
413
433
  PUBLISHER,
414
434
  IJBBuybackHookRegistry(address(ammBuyback)),
415
435
  address(LOANS_CONTRACT),
416
- TRUSTED_FORWARDER
436
+ TRUSTED_FORWARDER,
437
+ address(ammOwner)
417
438
  );
418
439
 
419
440
  vm.prank(multisig());
@@ -63,6 +63,8 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
63
63
  import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
64
64
 
65
65
  import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
66
+ import {REVOwner} from "../src/REVOwner.sol";
67
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
66
68
 
67
69
  /// @notice Helper that adds liquidity to and swaps on a V4 pool via the unlock/callback pattern.
68
70
  contract LiquidityHelper is IUnlockCallback {
@@ -234,6 +236,8 @@ contract TestSplitWeightFork is TestBaseWorkflow {
234
236
  // forge-lint: disable-next-line(mixed-case-variable)
235
237
  REVDeployer REV_DEPLOYER;
236
238
  // forge-lint: disable-next-line(mixed-case-variable)
239
+ REVOwner REV_OWNER;
240
+ // forge-lint: disable-next-line(mixed-case-variable)
237
241
  JBBuybackHook BUYBACK_HOOK;
238
242
  // forge-lint: disable-next-line(mixed-case-variable)
239
243
  JBBuybackHookRegistry BUYBACK_REGISTRY;
@@ -325,6 +329,14 @@ contract TestSplitWeightFork is TestBaseWorkflow {
325
329
  trustedForwarder: TRUSTED_FORWARDER
326
330
  });
327
331
 
332
+ REV_OWNER = new REVOwner(
333
+ IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
334
+ jbDirectory(),
335
+ FEE_PROJECT_ID,
336
+ SUCKER_REGISTRY,
337
+ address(LOANS_CONTRACT)
338
+ );
339
+
328
340
  REV_DEPLOYER = new REVDeployer{salt: "REVDeployer_Fork"}(
329
341
  jbController(),
330
342
  SUCKER_REGISTRY,
@@ -333,7 +345,8 @@ contract TestSplitWeightFork is TestBaseWorkflow {
333
345
  PUBLISHER,
334
346
  IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
335
347
  address(LOANS_CONTRACT),
336
- TRUSTED_FORWARDER
348
+ TRUSTED_FORWARDER,
349
+ address(REV_OWNER)
337
350
  );
338
351
 
339
352
  vm.prank(multisig());
@@ -34,6 +34,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
34
34
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
35
35
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
36
36
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
37
+ import {REVOwner} from "../src/REVOwner.sol";
38
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
37
39
 
38
40
  /// @notice Documents and verifies that stage transitions change the borrowable amount for the same collateral.
39
41
  /// This is by design: loan value tracks the current bonding curve parameters (cashOutTaxRate),
@@ -45,6 +47,8 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
45
47
  // forge-lint: disable-next-line(mixed-case-variable)
46
48
  REVDeployer REV_DEPLOYER;
47
49
  // forge-lint: disable-next-line(mixed-case-variable)
50
+ REVOwner REV_OWNER;
51
+ // forge-lint: disable-next-line(mixed-case-variable)
48
52
  JB721TiersHook EXAMPLE_HOOK;
49
53
  // forge-lint: disable-next-line(mixed-case-variable)
50
54
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -148,6 +152,14 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
148
152
  permit2: permit2(),
149
153
  trustedForwarder: TRUSTED_FORWARDER
150
154
  });
155
+ REV_OWNER = new REVOwner(
156
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
157
+ jbDirectory(),
158
+ FEE_PROJECT_ID,
159
+ SUCKER_REGISTRY,
160
+ address(LOANS_CONTRACT)
161
+ );
162
+
151
163
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
152
164
  jbController(),
153
165
  SUCKER_REGISTRY,
@@ -156,8 +168,10 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
156
168
  PUBLISHER,
157
169
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
158
170
  address(LOANS_CONTRACT),
159
- TRUSTED_FORWARDER
171
+ TRUSTED_FORWARDER,
172
+ address(REV_OWNER)
160
173
  );
174
+
161
175
  vm.prank(multisig());
162
176
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
163
177
 
@@ -36,6 +36,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
36
36
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
37
37
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
38
38
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
39
+ import {REVOwner} from "../src/REVOwner.sol";
40
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
39
41
 
40
42
  /// @notice Tests for default operator permissions including SET_ROUTER_TERMINAL.
41
43
  /// Verifies that all default split operator permissions are granted correctly.
@@ -46,6 +48,8 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
46
48
  // forge-lint: disable-next-line(mixed-case-variable)
47
49
  REVDeployer REV_DEPLOYER;
48
50
  // forge-lint: disable-next-line(mixed-case-variable)
51
+ REVOwner REV_OWNER;
52
+ // forge-lint: disable-next-line(mixed-case-variable)
49
53
  JB721TiersHook EXAMPLE_HOOK;
50
54
  // forge-lint: disable-next-line(mixed-case-variable)
51
55
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -89,6 +93,14 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
89
93
  permit2: permit2(),
90
94
  trustedForwarder: TRUSTED_FORWARDER
91
95
  });
96
+ REV_OWNER = new REVOwner(
97
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
98
+ jbDirectory(),
99
+ FEE_PROJECT_ID,
100
+ SUCKER_REGISTRY,
101
+ address(LOANS_CONTRACT)
102
+ );
103
+
92
104
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
93
105
  jbController(),
94
106
  SUCKER_REGISTRY,
@@ -97,8 +109,10 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
97
109
  PUBLISHER,
98
110
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
99
111
  address(LOANS_CONTRACT),
100
- TRUSTED_FORWARDER
112
+ TRUSTED_FORWARDER,
113
+ address(REV_OWNER)
101
114
  );
115
+
102
116
  vm.prank(multisig());
103
117
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
104
118
 
@@ -40,6 +40,8 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
41
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
42
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
+ import {REVOwner} from "../src/REVOwner.sol";
44
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
43
45
 
44
46
  /// @title TestUint112Overflow
45
47
  /// @notice Tests for uint112 truncation fix in REVLoans._adjust()
@@ -52,6 +54,8 @@ contract TestUint112Overflow is TestBaseWorkflow {
52
54
  // forge-lint: disable-next-line(mixed-case-variable)
53
55
  REVDeployer REV_DEPLOYER;
54
56
  // forge-lint: disable-next-line(mixed-case-variable)
57
+ REVOwner REV_OWNER;
58
+ // forge-lint: disable-next-line(mixed-case-variable)
55
59
  JB721TiersHook EXAMPLE_HOOK;
56
60
  // forge-lint: disable-next-line(mixed-case-variable)
57
61
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -109,6 +113,14 @@ contract TestUint112Overflow is TestBaseWorkflow {
109
113
  trustedForwarder: TRUSTED_FORWARDER
110
114
  });
111
115
 
116
+ REV_OWNER = new REVOwner(
117
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
118
+ jbDirectory(),
119
+ FEE_PROJECT_ID,
120
+ SUCKER_REGISTRY,
121
+ address(LOANS_CONTRACT)
122
+ );
123
+
112
124
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
113
125
  jbController(),
114
126
  SUCKER_REGISTRY,
@@ -117,7 +129,8 @@ contract TestUint112Overflow is TestBaseWorkflow {
117
129
  PUBLISHER,
118
130
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
119
131
  address(LOANS_CONTRACT),
120
- TRUSTED_FORWARDER
132
+ TRUSTED_FORWARDER,
133
+ address(REV_OWNER)
121
134
  );
122
135
 
123
136
  // Deploy fee project
@@ -40,6 +40,8 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
41
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
42
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
+ import {REVOwner} from "../src/REVOwner.sol";
44
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
43
45
 
44
46
  /// @notice Tests for PR #16: zero repayment prevention.
45
47
  contract TestZeroRepayment is TestBaseWorkflow {
@@ -49,6 +51,8 @@ contract TestZeroRepayment is TestBaseWorkflow {
49
51
  // forge-lint: disable-next-line(mixed-case-variable)
50
52
  REVDeployer REV_DEPLOYER;
51
53
  // forge-lint: disable-next-line(mixed-case-variable)
54
+ REVOwner REV_OWNER;
55
+ // forge-lint: disable-next-line(mixed-case-variable)
52
56
  JB721TiersHook EXAMPLE_HOOK;
53
57
  // forge-lint: disable-next-line(mixed-case-variable)
54
58
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -102,6 +106,14 @@ contract TestZeroRepayment is TestBaseWorkflow {
102
106
  permit2: permit2(),
103
107
  trustedForwarder: TRUSTED_FORWARDER
104
108
  });
109
+ REV_OWNER = new REVOwner(
110
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
111
+ jbDirectory(),
112
+ FEE_PROJECT_ID,
113
+ SUCKER_REGISTRY,
114
+ address(LOANS_CONTRACT)
115
+ );
116
+
105
117
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
106
118
  jbController(),
107
119
  SUCKER_REGISTRY,
@@ -110,8 +122,10 @@ contract TestZeroRepayment is TestBaseWorkflow {
110
122
  PUBLISHER,
111
123
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
112
124
  address(LOANS_CONTRACT),
113
- TRUSTED_FORWARDER
125
+ TRUSTED_FORWARDER,
126
+ address(REV_OWNER)
114
127
  );
128
+
115
129
  vm.prank(multisig());
116
130
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
117
131
  _deployFeeProject();
@@ -42,6 +42,8 @@ import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressReg
42
42
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
43
  // Helper that provides empty 721 tier configs for revnet deployment.
44
44
  import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
45
+ import {REVOwner} from "../../src/REVOwner.sol";
46
+ import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
45
47
 
46
48
  /// @notice Regression tests for the loan ID overflow guard in REVLoans.
47
49
  /// @dev The totalLoansBorrowedFor counter must never exceed _ONE_TRILLION (1e12).
@@ -74,6 +76,8 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
74
76
  // forge-lint: disable-next-line(mixed-case-variable)
75
77
  REVDeployer REV_DEPLOYER;
76
78
  // forge-lint: disable-next-line(mixed-case-variable)
79
+ REVOwner REV_OWNER;
80
+ // forge-lint: disable-next-line(mixed-case-variable)
77
81
  JB721TiersHook EXAMPLE_HOOK;
78
82
  // forge-lint: disable-next-line(mixed-case-variable)
79
83
  IJB721TiersHookDeployer HOOK_DEPLOYER;
@@ -159,6 +163,14 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
159
163
  });
160
164
 
161
165
  // Deploy the REVDeployer with a deterministic salt.
166
+ REV_OWNER = new REVOwner(
167
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
168
+ jbDirectory(),
169
+ FEE_PROJECT_ID,
170
+ SUCKER_REGISTRY,
171
+ address(LOANS_CONTRACT)
172
+ );
173
+
162
174
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
163
175
  jbController(),
164
176
  SUCKER_REGISTRY,
@@ -167,7 +179,8 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
167
179
  PUBLISHER,
168
180
  IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
169
181
  address(LOANS_CONTRACT),
170
- TRUSTED_FORWARDER
182
+ TRUSTED_FORWARDER,
183
+ address(REV_OWNER)
171
184
  );
172
185
 
173
186
  // Approve the deployer to configure the fee project.
@@ -71,6 +71,8 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
71
71
  import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
72
72
 
73
73
  import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
74
+ import {REVOwner} from "../../src/REVOwner.sol";
75
+ import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
74
76
 
75
77
  /// @notice Helper that adds liquidity to a V4 pool via the unlock/callback pattern.
76
78
  contract LiquidityHelper is IUnlockCallback {
@@ -237,6 +239,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
237
239
  // forge-lint: disable-next-line(mixed-case-variable)
238
240
  REVDeployer REV_DEPLOYER;
239
241
  // forge-lint: disable-next-line(mixed-case-variable)
242
+ REVOwner REV_OWNER;
243
+ // forge-lint: disable-next-line(mixed-case-variable)
240
244
  JBBuybackHook BUYBACK_HOOK;
241
245
  // forge-lint: disable-next-line(mixed-case-variable)
242
246
  JBBuybackHookRegistry BUYBACK_REGISTRY;
@@ -330,6 +334,14 @@ abstract contract ForkTestBase is TestBaseWorkflow {
330
334
  trustedForwarder: TRUSTED_FORWARDER
331
335
  });
332
336
 
337
+ REV_OWNER = new REVOwner(
338
+ IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
339
+ jbDirectory(),
340
+ FEE_PROJECT_ID,
341
+ SUCKER_REGISTRY,
342
+ address(LOANS_CONTRACT)
343
+ );
344
+
333
345
  REV_DEPLOYER = new REVDeployer{salt: "REVDeployer_Fork"}(
334
346
  jbController(),
335
347
  SUCKER_REGISTRY,
@@ -338,7 +350,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
338
350
  PUBLISHER,
339
351
  IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
340
352
  address(LOANS_CONTRACT),
341
- TRUSTED_FORWARDER
353
+ TRUSTED_FORWARDER,
354
+ address(REV_OWNER)
342
355
  );
343
356
 
344
357
  vm.prank(multisig());
@@ -149,14 +149,15 @@ contract TestPermit2PaymentFork is ForkTestBase {
149
149
  sigDeadline: sigDeadline, amount: amount, expiration: expiration, nonce: nonce, signature: sig
150
150
  });
151
151
 
152
- // Encode as metadata with the "permit2" key targeting the terminal, plus a zero "quote"
153
- // so the buyback hook skips the TWAP pool lookup (no pool exists for testToken).
152
+ // Encode as metadata with the "permit2" key targeting the terminal, plus a "quote" entry
153
+ // targeting the buyback hook with minimumSwapAmountOut=1 so the buyback hook skips the
154
+ // TWAP oracle and falls through to normal minting (no V4 pool has liquidity for testToken).
154
155
  bytes4[] memory ids = new bytes4[](2);
155
156
  bytes[] memory datas = new bytes[](2);
156
157
  ids[0] = JBMetadataResolver.getId("permit2", address(jbMultiTerminal()));
157
158
  datas[0] = abi.encode(allowance);
158
159
  ids[1] = JBMetadataResolver.getId("quote", address(BUYBACK_HOOK));
159
- datas[1] = abi.encode(uint256(0), uint256(0));
160
+ datas[1] = abi.encode(uint256(0), uint256(1));
160
161
  metadata = JBMetadataResolver.createMetadata(ids, datas);
161
162
  }
162
163