@rev-net/core-v6 0.0.17 → 0.0.19

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 (66) hide show
  1. package/ADMINISTRATION.md +14 -4
  2. package/ARCHITECTURE.md +14 -10
  3. package/AUDIT_INSTRUCTIONS.md +40 -17
  4. package/CHANGE_LOG.md +87 -0
  5. package/README.md +10 -5
  6. package/RISKS.md +15 -10
  7. package/SKILLS.md +31 -15
  8. package/USER_JOURNEYS.md +16 -12
  9. package/foundry.toml +1 -1
  10. package/package.json +8 -8
  11. package/script/Deploy.s.sol +60 -19
  12. package/src/REVDeployer.sol +21 -303
  13. package/src/REVLoans.sol +31 -0
  14. package/src/REVOwner.sol +430 -0
  15. package/src/interfaces/IREVDeployer.sol +4 -10
  16. package/src/interfaces/IREVOwner.sol +10 -0
  17. package/src/structs/REVBaseline721HookConfig.sol +0 -2
  18. package/test/REV.integrations.t.sol +14 -1
  19. package/test/REVAutoIssuanceFuzz.t.sol +14 -1
  20. package/test/REVDeployerRegressions.t.sol +17 -2
  21. package/test/REVInvincibility.t.sol +31 -3
  22. package/test/REVLifecycle.t.sol +16 -1
  23. package/test/REVLoans.invariants.t.sol +16 -1
  24. package/test/REVLoansAttacks.t.sol +16 -1
  25. package/test/REVLoansFeeRecovery.t.sol +16 -1
  26. package/test/REVLoansFindings.t.sol +16 -1
  27. package/test/REVLoansRegressions.t.sol +16 -1
  28. package/test/REVLoansSourceFeeRecovery.t.sol +16 -1
  29. package/test/REVLoansSourced.t.sol +16 -1
  30. package/test/REVLoansUnSourced.t.sol +16 -1
  31. package/test/TestBurnHeldTokens.t.sol +16 -1
  32. package/test/TestCEIPattern.t.sol +16 -1
  33. package/test/TestCashOutCallerValidation.t.sol +19 -4
  34. package/test/TestConversionDocumentation.t.sol +16 -1
  35. package/test/TestCrossCurrencyReclaim.t.sol +16 -1
  36. package/test/TestCrossSourceReallocation.t.sol +16 -1
  37. package/test/TestERC2771MetaTx.t.sol +16 -1
  38. package/test/TestEmptyBuybackSpecs.t.sol +18 -3
  39. package/test/TestFlashLoanSurplus.t.sol +16 -1
  40. package/test/TestHookArrayOOB.t.sol +17 -2
  41. package/test/TestLiquidationBehavior.t.sol +16 -1
  42. package/test/TestLoanSourceRotation.t.sol +16 -1
  43. package/test/TestLoansCashOutDelay.t.sol +482 -0
  44. package/test/TestLongTailEconomics.t.sol +16 -1
  45. package/test/TestLowFindings.t.sol +16 -1
  46. package/test/TestMixedFixes.t.sol +16 -1
  47. package/test/TestPermit2Signatures.t.sol +16 -1
  48. package/test/TestReallocationSandwich.t.sol +16 -1
  49. package/test/TestRevnetRegressions.t.sol +16 -1
  50. package/test/TestSplitWeightAdjustment.t.sol +43 -19
  51. package/test/TestSplitWeightE2E.t.sol +26 -3
  52. package/test/TestSplitWeightFork.t.sol +16 -2
  53. package/test/TestStageTransitionBorrowable.t.sol +16 -1
  54. package/test/TestSwapTerminalPermission.t.sol +16 -1
  55. package/test/TestUint112Overflow.t.sol +16 -1
  56. package/test/TestZeroRepayment.t.sol +16 -1
  57. package/test/audit/LoanIdOverflowGuard.t.sol +16 -1
  58. package/test/fork/ForkTestBase.sol +16 -2
  59. package/test/fork/TestPermit2PaymentFork.t.sol +4 -3
  60. package/test/helpers/REVEmpty721Config.sol +0 -1
  61. package/test/regression/TestBurnPermissionRequired.t.sol +16 -1
  62. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +15 -1
  63. package/test/regression/TestCrossRevnetLiquidation.t.sol +16 -1
  64. package/test/regression/TestCumulativeLoanCounter.t.sol +16 -1
  65. package/test/regression/TestLiquidateGapHandling.t.sol +16 -1
  66. package/test/regression/TestZeroPriceFeed.t.sol +16 -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,11 @@ 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
+
182
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
168
183
  vm.prank(multisig());
169
184
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
170
185
 
@@ -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,9 +164,12 @@ 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
 
171
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
172
+
158
173
  vm.prank(multisig());
159
174
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
160
175
  }
@@ -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,11 @@ 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
+
120
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
106
121
  vm.prank(multisig());
107
122
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
108
123
  }
@@ -199,7 +214,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
199
214
  });
200
215
 
201
216
  // 721 hook is deployed but has no tiers, buyback returns context.weight.
202
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
217
+ (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
203
218
 
204
219
  assertEq(weight, context.weight, "weight should pass through unchanged");
205
220
  assertEq(specs.length, 1, "should have 721 hook spec even with no tiers");
@@ -217,11 +232,11 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
217
232
  // tiered721HookOf is internal, so we use vm.store.
218
233
  // Slot for tiered721HookOf[revnetId]: keccak256(abi.encode(revnetId, slot))
219
234
  // 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))));
235
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
236
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
222
237
 
223
238
  // Verify the store worked.
224
- assertEq(address(REV_DEPLOYER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
239
+ assertEq(address(REV_OWNER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
225
240
 
226
241
  // Mock 721 hook returning 0.3 ETH split on 1 ETH payment.
227
242
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -250,7 +265,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
250
265
  metadata: ""
251
266
  });
252
267
 
253
- (uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
268
+ (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
254
269
 
255
270
  // Buyback returns context.weight (1000e18) since mock buyback passes through.
256
271
  // Weight adjusted for 0.3 ETH split on 1 ETH: 1000e18 * 0.7 = 700e18.
@@ -263,8 +278,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
263
278
 
264
279
  address mock721 = makeAddr("mock721_full");
265
280
  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))));
281
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
282
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
268
283
 
269
284
  // Mock 721 hook returning full 1 ETH split.
270
285
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -293,7 +308,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
293
308
  metadata: ""
294
309
  });
295
310
 
296
- (uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
311
+ (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
297
312
 
298
313
  assertEq(weight, 0, "full split = zero weight");
299
314
  }
@@ -308,6 +323,13 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
308
323
 
309
324
  // Deploy a new REVDeployer with the AMM buyback mock.
310
325
  MockBuybackDataHook ammBuyback = new MockBuybackDataHook();
326
+ REVOwner ammOwner = new REVOwner(
327
+ IJBBuybackHookRegistry(address(ammBuyback)),
328
+ jbDirectory(),
329
+ FEE_PROJECT_ID,
330
+ SUCKER_REGISTRY,
331
+ address(LOANS_CONTRACT)
332
+ );
311
333
  REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM"}(
312
334
  jbController(),
313
335
  SUCKER_REGISTRY,
@@ -316,8 +338,10 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
316
338
  PUBLISHER,
317
339
  IJBBuybackHookRegistry(address(ammBuyback)),
318
340
  address(LOANS_CONTRACT),
319
- TRUSTED_FORWARDER
341
+ TRUSTED_FORWARDER,
342
+ address(ammOwner)
320
343
  );
344
+ ammOwner.initialize(IREVDeployer(address(ammDeployer)));
321
345
 
322
346
  vm.prank(multisig());
323
347
  jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
@@ -348,8 +372,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
348
372
  // Mock a 721 hook for this project.
349
373
  address mock721 = makeAddr("mock721_amm");
350
374
  vm.etch(mock721, bytes("0x01"));
351
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
352
- vm.store(address(ammDeployer), slot, bytes32(uint256(uint160(mock721))));
375
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
376
+ vm.store(address(ammOwner), slot, bytes32(uint256(uint160(mock721))));
353
377
 
354
378
  // Mock 721 hook returning 0.4 ETH split on 1 ETH payment.
355
379
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -378,7 +402,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
378
402
  metadata: ""
379
403
  });
380
404
 
381
- (uint256 weight, JBPayHookSpecification[] memory specs) = ammDeployer.beforePayRecordedWith(context);
405
+ (uint256 weight, JBPayHookSpecification[] memory specs) = ammOwner.beforePayRecordedWith(context);
382
406
 
383
407
  // AMM buyback returns context.weight (passes through the reduced weight from context).
384
408
  // The buyback mock receives a reduced context with 0.6 ETH and returns that weight.
@@ -398,8 +422,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
398
422
 
399
423
  address mock721 = makeAddr("mock721_mint");
400
424
  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))));
425
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
426
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
403
427
 
404
428
  // Mock 721 hook returning 0.2 ETH split.
405
429
  JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
@@ -428,7 +452,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
428
452
  metadata: ""
429
453
  });
430
454
 
431
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
455
+ (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
432
456
 
433
457
  // Buyback mint path: returns context.weight (1000e18 from reduced context = 0.8 ETH context).
434
458
  // Actually MockBuybackDataHookMintPath returns context.weight with empty specs.
@@ -447,8 +471,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
447
471
 
448
472
  address mock721 = makeAddr("mock721_specs");
449
473
  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))));
474
+ bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
475
+ vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
452
476
 
453
477
  // Mock 721 hook returning split amount with metadata.
454
478
  bytes memory splitMeta = abi.encode(uint256(42));
@@ -478,7 +502,7 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
478
502
  metadata: ""
479
503
  });
480
504
 
481
- (, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
505
+ (, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
482
506
 
483
507
  // Should have 721 hook spec (buyback empty).
484
508
  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,9 +133,12 @@ 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
 
140
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
141
+
127
142
  vm.prank(multisig());
128
143
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
129
144
 
@@ -218,7 +233,6 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
218
233
  tiersConfig: JB721InitTiersConfig({
219
234
  tiers: tiers, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
220
235
  }),
221
- reserveBeneficiary: address(0),
222
236
  flags: REV721TiersHookFlags({
223
237
  noNewTiersWithReserves: false,
224
238
  noNewTiersWithVotes: false,
@@ -406,6 +420,13 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
406
420
  vm.prank(multisig());
407
421
  jbProjects().approve(address(0), FEE_PROJECT_ID); // Clear old approval.
408
422
 
423
+ REVOwner ammOwner = new REVOwner(
424
+ IJBBuybackHookRegistry(address(ammBuyback)),
425
+ jbDirectory(),
426
+ FEE_PROJECT_ID,
427
+ SUCKER_REGISTRY,
428
+ address(LOANS_CONTRACT)
429
+ );
409
430
  REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM_E2E"}(
410
431
  jbController(),
411
432
  SUCKER_REGISTRY,
@@ -414,8 +435,10 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
414
435
  PUBLISHER,
415
436
  IJBBuybackHookRegistry(address(ammBuyback)),
416
437
  address(LOANS_CONTRACT),
417
- TRUSTED_FORWARDER
438
+ TRUSTED_FORWARDER,
439
+ address(ammOwner)
418
440
  );
441
+ ammOwner.initialize(IREVDeployer(address(ammDeployer)));
419
442
 
420
443
  vm.prank(multisig());
421
444
  jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
@@ -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,9 +345,12 @@ 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
 
352
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
353
+
339
354
  vm.prank(multisig());
340
355
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
341
356
 
@@ -429,7 +444,6 @@ contract TestSplitWeightFork is TestBaseWorkflow {
429
444
  tiersConfig: JB721InitTiersConfig({
430
445
  tiers: tiers, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
431
446
  }),
432
- reserveBeneficiary: address(0),
433
447
  flags: REV721TiersHookFlags({
434
448
  noNewTiersWithReserves: false,
435
449
  noNewTiersWithVotes: false,
@@ -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,11 @@ 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
+
175
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
161
176
  vm.prank(multisig());
162
177
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
163
178
 
@@ -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,11 @@ 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
+
116
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
102
117
  vm.prank(multisig());
103
118
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
104
119
 
@@ -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,9 +129,12 @@ 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
 
136
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
137
+
123
138
  // Deploy fee project
124
139
  vm.prank(multisig());
125
140
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -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,11 @@ 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
+
129
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
115
130
  vm.prank(multisig());
116
131
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
117
132
  _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,9 +179,12 @@ 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
 
186
+ REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
187
+
173
188
  // Approve the deployer to configure the fee project.
174
189
  vm.prank(multisig());
175
190
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);