@rev-net/core-v6 0.0.25 → 0.0.26

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 (56) hide show
  1. package/ADMINISTRATION.md +1 -1
  2. package/RISKS.md +1 -1
  3. package/package.json +4 -4
  4. package/references/operations.md +2 -2
  5. package/script/Deploy.s.sol +30 -29
  6. package/src/REVDeployer.sol +0 -3
  7. package/src/REVOwner.sol +30 -7
  8. package/src/interfaces/IREVOwner.sol +6 -0
  9. package/test/REV.integrations.t.sol +2 -0
  10. package/test/REVAutoIssuanceFuzz.t.sol +2 -0
  11. package/test/REVDeployerRegressions.t.sol +2 -0
  12. package/test/REVInvincibility.t.sol +3 -0
  13. package/test/REVLifecycle.t.sol +2 -0
  14. package/test/REVLoans.invariants.t.sol +2 -0
  15. package/test/REVLoansAttacks.t.sol +2 -0
  16. package/test/REVLoansFeeRecovery.t.sol +2 -0
  17. package/test/REVLoansFindings.t.sol +2 -0
  18. package/test/REVLoansRegressions.t.sol +2 -0
  19. package/test/REVLoansSourceFeeRecovery.t.sol +2 -0
  20. package/test/REVLoansSourced.t.sol +2 -0
  21. package/test/REVLoansUnSourced.t.sol +2 -0
  22. package/test/TestBurnHeldTokens.t.sol +2 -0
  23. package/test/TestCEIPattern.t.sol +2 -0
  24. package/test/TestCashOutCallerValidation.t.sol +2 -0
  25. package/test/TestConversionDocumentation.t.sol +2 -0
  26. package/test/TestCrossCurrencyReclaim.t.sol +2 -0
  27. package/test/TestCrossSourceReallocation.t.sol +2 -0
  28. package/test/TestERC2771MetaTx.t.sol +2 -0
  29. package/test/TestEmptyBuybackSpecs.t.sol +2 -0
  30. package/test/TestFlashLoanSurplus.t.sol +2 -0
  31. package/test/TestHookArrayOOB.t.sol +2 -0
  32. package/test/TestLiquidationBehavior.t.sol +2 -0
  33. package/test/TestLoanSourceRotation.t.sol +2 -0
  34. package/test/TestLoansCashOutDelay.t.sol +2 -0
  35. package/test/TestLongTailEconomics.t.sol +2 -0
  36. package/test/TestLowFindings.t.sol +2 -0
  37. package/test/TestMixedFixes.t.sol +2 -0
  38. package/test/TestPermit2Signatures.t.sol +2 -0
  39. package/test/TestReallocationSandwich.t.sol +2 -0
  40. package/test/TestRevnetRegressions.t.sol +2 -0
  41. package/test/TestSplitWeightAdjustment.t.sol +4 -0
  42. package/test/TestSplitWeightE2E.t.sol +4 -0
  43. package/test/TestSplitWeightFork.t.sol +2 -0
  44. package/test/TestStageTransitionBorrowable.t.sol +2 -0
  45. package/test/TestSwapTerminalPermission.t.sol +2 -0
  46. package/test/TestUint112Overflow.t.sol +2 -0
  47. package/test/TestZeroAmountLoanGuard.t.sol +2 -0
  48. package/test/TestZeroRepayment.t.sol +2 -0
  49. package/test/audit/LoanIdOverflowGuard.t.sol +2 -0
  50. package/test/fork/ForkTestBase.sol +2 -0
  51. package/test/regression/TestBurnPermissionRequired.t.sol +2 -0
  52. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +2 -0
  53. package/test/regression/TestCrossRevnetLiquidation.t.sol +2 -0
  54. package/test/regression/TestCumulativeLoanCounter.t.sol +2 -0
  55. package/test/regression/TestLiquidateGapHandling.t.sol +2 -0
  56. package/test/regression/TestZeroPriceFeed.t.sol +2 -0
package/ADMINISTRATION.md CHANGED
@@ -174,7 +174,7 @@ The following parameters are set at deployment and can never be changed:
174
174
  - `SUCKER_REGISTRY` -- the sucker registry (shared immutable)
175
175
  - `LOANS` -- the loans contract address (shared immutable)
176
176
  - `FEE` -- the cash-out fee constant (2.5%)
177
- - `DEPLOYER` -- the REVDeployer address (storage variable, set once via `setDeployer()` called from REVDeployer's constructor)
177
+ - `DEPLOYER` -- the REVDeployer address (storage variable, set once by the REVOwner initializer account using the precomputed canonical deployer address)
178
178
 
179
179
  ### REVLoans (global, set at contract deployment)
180
180
  - `CONTROLLER`, `DIRECTORY`, `PRICES`, `PROJECTS` -- protocol infrastructure
package/RISKS.md CHANGED
@@ -23,7 +23,7 @@ Read [ARCHITECTURE.md](./ARCHITECTURE.md) and [SKILLS.md](./SKILLS.md) for proto
23
23
  ### What the system assumes to be correct
24
24
 
25
25
  - **REVOwner is a singleton data hook.** Every revnet shares one `beforePayRecordedWith` and `beforeCashOutRecordedWith` implementation in REVOwner. A bug in either function affects ALL revnets deployed by that deployer simultaneously. There is no per-project isolation and no circuit breaker.
26
- - **REVOwner circular dependency.** REVOwner and REVDeployer have a circular dependency broken by `setDeployer()`, called atomically from REVDeployer's constructor. `REVOwner.DEPLOYER` is a storage variable (not immutable). `setDeployer()` sets `msg.sender` as `DEPLOYER` and reverts if already set. If `setDeployer()` is never called, REVDeployer cannot call the DEPLOYER-restricted setters (`setCashOutDelayOf`, `setTiered721HookOf`) on REVOwner, and `cashOutDelayOf`/`tiered721HookOf` will never be populated, breaking all runtime hook behavior. Because the call is made atomically from REVDeployer's constructor, the correct deployer address is guaranteed to be set during deployment.
26
+ - **REVOwner deployer binding is precomputed.** REVOwner records the account that deployed it as an internal one-time binder, and only that account can bind `DEPLOYER`. Deployment tooling must precompute the canonical REVDeployer address and call `setDeployer(...)` before constructing REVDeployer. This removes the old frontrunnable ambient initializer, but it also means deployment scripts must not skip the prebind step.
27
27
  - **Stage immutability is the trust model.** Once `deployFor()` completes, stage parameters (issuance, `cashOutTaxRate`, splits, auto-issuances) are locked forever. No owner, no governance, no upgrade path. A misconfigured deployment is permanent. This is intentional -- the absence of admin keys IS the security property.
28
28
  - **Bonding curve is the sole collateral oracle.** `REVLoans` uses `JBCashOuts.cashOutFrom` to value collateral. There is no external price oracle, no liquidation margin, and no health factor. The borrowable amount equals the cash-out value at the moment of borrowing.
29
29
  - **Juicebox core contracts are correct.** `JBController`, `JBMultiTerminal`, `JBTerminalStore`, `JBTokens`, `JBPrices` -- a bug in any of these is a bug in every revnet.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,11 +20,11 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@bananapus/721-hook-v6": "^0.0.30",
23
- "@bananapus/buyback-hook-v6": "^0.0.24",
24
- "@bananapus/core-v6": "^0.0.30",
23
+ "@bananapus/buyback-hook-v6": "^0.0.25",
24
+ "@bananapus/core-v6": "^0.0.31",
25
25
  "@bananapus/ownable-v6": "^0.0.16",
26
26
  "@bananapus/permission-ids-v6": "^0.0.15",
27
- "@bananapus/router-terminal-v6": "^0.0.24",
27
+ "@bananapus/router-terminal-v6": "^0.0.25",
28
28
  "@bananapus/suckers-v6": "^0.0.20",
29
29
  "@croptop/core-v6": "^0.0.28",
30
30
  "@openzeppelin/contracts": "^5.6.1",
@@ -104,7 +104,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
104
104
 
105
105
  | Mapping | Visibility | Type | Purpose |
106
106
  |---------|-----------|------|---------|
107
- | `DEPLOYER` | `public` | `address` | REVDeployer address (storage variable, set once via `setDeployer()` called from REVDeployer's constructor) |
107
+ | `DEPLOYER` | `public` | `address` | REVDeployer address (storage variable, set once by the REVOwner initializer using the precomputed canonical deployer address) |
108
108
  | `cashOutDelayOf` | `public` | `revnetId => uint256` | Timestamp when cash outs unlock (0 = no delay). Set by REVDeployer via `setCashOutDelayOf()`. |
109
109
  | `tiered721HookOf` | `public` | `revnetId => address` | Deployed 721 hook address (if any). Set by REVDeployer via `setTiered721HookOf()`. |
110
110
 
@@ -142,7 +142,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
142
142
  18. **Permit2 fallback.** `REVLoans` uses permit2 for ERC-20 transfers as a fallback when standard allowance is insufficient. Wrapped in try-catch.
143
143
  19. **39.16% cash-out tax crossover.** Below ~39% cash-out tax, cashing out is more capital-efficient than borrowing. Above ~39%, loans become more efficient because they preserve upside while providing liquidity. Based on CryptoEconLab academic research. Design implication: revnets intended for active token trading should consider this threshold when setting `cashOutTaxRate`.
144
144
  20. **REVDeployer always deploys a 721 hook** via `HOOK_DEPLOYER.deployHookFor` — even if `baseline721HookConfiguration` has empty tiers. This is correct by design: it lets the split operator add and sell NFTs later without migration. Non-revnet projects should follow the same pattern by using `JB721TiersHookProjectDeployer.launchProjectFor` (or `JBOmnichainDeployer.launchProjectFor`) instead of bare `launchProjectFor`.
145
- 21. **REVOwner circular dependency.** REVOwner and REVDeployer have a circular dependency broken by `setDeployer()`, called atomically from REVDeployer's constructor. `REVOwner.DEPLOYER` is a storage variable (not immutable). `setDeployer()` sets `msg.sender` as `DEPLOYER` if not already set -- reverts if already set. If `setDeployer()` is never called, all runtime hook behavior breaks. Deploy order: REVOwner first, then REVDeployer(owner=REVOwner) -- the constructor calls `REVOwner.setDeployer()` atomically. REVOwner stores `cashOutDelayOf` and `tiered721HookOf` mappings, which are set by REVDeployer via DEPLOYER-restricted setters (`setCashOutDelayOf()`, `setTiered721HookOf()`).
145
+ 21. **REVOwner deployer binding is precomputed.** REVOwner records the account that created it as an internal one-time binder. That account must call `setDeployer(precomputedRevDeployerAddress)` exactly once before the canonical REVDeployer is deployed. This avoids an ambient public initializer while keeping the circular dependency manageable. If `setDeployer(...)` is never called, all DEPLOYER-gated runtime configuration breaks.
146
146
 
147
147
  ### NATIVE_TOKEN Accounting on Non-ETH Chains
148
148
 
@@ -368,8 +368,9 @@ contract DeployScript is Script, Sphinx {
368
368
  uint256 _nextProjectId = core.projects.count() + 1;
369
369
 
370
370
  // Try to find an existing deployment by checking all project IDs that have already been created.
371
- // Whether previously deployed singletons were found.
372
- bool _singletonsExist;
371
+ bool _revloansExists;
372
+ bool _revOwnerExists;
373
+ bool _revDeployerExists;
373
374
  // The address of the previously deployed REVLoans, if found.
374
375
  address _existingRevloansAddr;
375
376
  // The address of the previously deployed REVOwner, if found.
@@ -394,12 +395,10 @@ contract DeployScript is Script, Sphinx {
394
395
  FEE_PROJECT_ID = _candidateId;
395
396
  // Record the existing REVLoans address.
396
397
  _existingRevloansAddr = _candidateRevloansAddr;
397
- // Flag that singletons were found.
398
- _singletonsExist = true;
398
+ _revloansExists = true;
399
399
 
400
400
  // Also predict and verify the owner.
401
- bool _ownerDeployed;
402
- (_existingOwnerAddr, _ownerDeployed) = _isDeployed({
401
+ (_existingOwnerAddr, _revOwnerExists) = _isDeployed({
403
402
  salt: REVOWNER_SALT,
404
403
  creationCode: type(REVOwner).creationCode,
405
404
  arguments: abi.encode(
@@ -412,8 +411,7 @@ contract DeployScript is Script, Sphinx {
412
411
  });
413
412
 
414
413
  // Also predict and verify the deployer.
415
- bool _deployerDeployed;
416
- (_existingDeployerAddr, _deployerDeployed) = _isDeployed({
414
+ (_existingDeployerAddr, _revDeployerExists) = _isDeployed({
417
415
  salt: DEPLOYER_SALT,
418
416
  creationCode: type(REVDeployer).creationCode,
419
417
  arguments: abi.encode(
@@ -429,16 +427,14 @@ contract DeployScript is Script, Sphinx {
429
427
  )
430
428
  });
431
429
 
432
- // If either singleton is missing, we cannot reuse the set.
433
- if (!_ownerDeployed || !_deployerDeployed) _singletonsExist = false;
434
430
  // Stop searching — we found the deployed singletons.
435
431
  break;
436
432
  }
437
433
  }
438
434
  }
439
435
 
440
- // Only create a new fee project if no singletons were found.
441
- if (!_singletonsExist) {
436
+ // Only create a new fee project if no singleton state was found.
437
+ if (!_revloansExists) {
442
438
  // Check if safeAddress() already owns a blank project (from a previous interrupted run).
443
439
  bool _foundExisting;
444
440
  for (uint256 i = _nextProjectId - 1; i >= 1; i--) {
@@ -457,7 +453,7 @@ contract DeployScript is Script, Sphinx {
457
453
  }
458
454
 
459
455
  // Deploy REVLoans first — it only depends on the controller.
460
- REVLoans revloans = _singletonsExist
456
+ REVLoans revloans = _revloansExists
461
457
  ? REVLoans(payable(_existingRevloansAddr))
462
458
  : new REVLoans{salt: REVLOANS_SALT}({
463
459
  controller: core.controller,
@@ -469,7 +465,7 @@ contract DeployScript is Script, Sphinx {
469
465
  });
470
466
 
471
467
  // Deploy REVOwner — the runtime data hook that handles pay and cash out callbacks.
472
- REVOwner revOwner = _singletonsExist
468
+ REVOwner revOwner = _revOwnerExists
473
469
  ? REVOwner(_existingOwnerAddr)
474
470
  : new REVOwner{salt: REVOWNER_SALT}({
475
471
  buybackHook: IJBBuybackHookRegistry(address(buybackHook.registry)),
@@ -480,21 +476,26 @@ contract DeployScript is Script, Sphinx {
480
476
  });
481
477
 
482
478
  // Deploy REVDeployer with the REVLoans, buyback hook, and REVOwner addresses.
483
- (address _deployerAddr, bool _deployerIsDeployed) = _isDeployed({
484
- salt: DEPLOYER_SALT,
485
- creationCode: type(REVDeployer).creationCode,
486
- arguments: abi.encode(
487
- core.controller,
488
- suckers.registry,
489
- FEE_PROJECT_ID,
490
- hook.hook_deployer,
491
- croptop.publisher,
492
- IJBBuybackHookRegistry(address(buybackHook.registry)),
493
- address(revloans),
494
- TRUSTED_FORWARDER,
495
- address(revOwner)
496
- )
497
- });
479
+ (address _deployerAddr, bool _deployerIsDeployed) = _revDeployerExists
480
+ ? (_existingDeployerAddr, true)
481
+ : _isDeployed({
482
+ salt: DEPLOYER_SALT,
483
+ creationCode: type(REVDeployer).creationCode,
484
+ arguments: abi.encode(
485
+ core.controller,
486
+ suckers.registry,
487
+ FEE_PROJECT_ID,
488
+ hook.hook_deployer,
489
+ croptop.publisher,
490
+ IJBBuybackHookRegistry(address(buybackHook.registry)),
491
+ address(revloans),
492
+ TRUSTED_FORWARDER,
493
+ address(revOwner)
494
+ )
495
+ });
496
+ if (address(revOwner.DEPLOYER()) == address(0)) {
497
+ revOwner.setDeployer(IREVDeployer(_deployerAddr));
498
+ }
498
499
  REVDeployer _basicDeployer = _deployerIsDeployed
499
500
  ? REVDeployer(payable(_deployerAddr))
500
501
  : new REVDeployer{salt: DEPLOYER_SALT}({
@@ -210,9 +210,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
210
210
 
211
211
  // Give the buyback hook (registry) permission to configure pools on all revnets.
212
212
  _setPermission({operator: address(BUYBACK_HOOK), revnetId: 0, permissionId: JBPermissionIds.SET_BUYBACK_POOL});
213
-
214
- // Link the REVOwner to this deployer so it can accept setter calls.
215
- REVOwner(OWNER).setDeployer();
216
213
  }
217
214
 
218
215
  //*********************************************************************//
package/src/REVOwner.sol CHANGED
@@ -80,9 +80,16 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
80
80
  mapping(uint256 revnetId => IJB721TiersHook tiered721Hook) public tiered721HookOf;
81
81
 
82
82
  /// @notice The deployer that manages revnet state.
83
- /// @dev Set once via `setDeployer()` from the REVDeployer's constructor. Reverts if called again.
83
+ /// @dev Set once via `setDeployer()` using the precomputed canonical REVDeployer address.
84
84
  IREVDeployer public DEPLOYER;
85
85
 
86
+ //*********************************************************************//
87
+ // -------------------- private stored properties -------------------- //
88
+ //*********************************************************************//
89
+
90
+ /// @notice The account allowed to bind the canonical deployer exactly once.
91
+ address private immutable _DEPLOYER_BINDER;
92
+
86
93
  //*********************************************************************//
87
94
  // -------------------------- constructor ---------------------------- //
88
95
  //*********************************************************************//
@@ -105,6 +112,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
105
112
  SUCKER_REGISTRY = suckerRegistry;
106
113
  // slither-disable-next-line missing-zero-check
107
114
  LOANS = loans;
115
+ _DEPLOYER_BINDER = msg.sender;
108
116
  }
109
117
 
110
118
  //*********************************************************************//
@@ -335,8 +343,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
335
343
  minReturnedTokens: 0,
336
344
  memo: "",
337
345
  metadata: bytes(abi.encodePacked(context.projectId))
338
- }) {}
339
- catch (bytes memory) {
346
+ }) {
347
+ _afterTransferTo({to: address(feeTerminal), token: context.forwardedAmount.token});
348
+ } catch (bytes memory) {
340
349
  // Decrease the allowance for the fee terminal if the token is not the native token.
341
350
  if (context.forwardedAmount.token != JBConstants.NATIVE_TOKEN) {
342
351
  IERC20(context.forwardedAmount.token)
@@ -359,14 +368,22 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
359
368
  memo: "",
360
369
  metadata: bytes(abi.encodePacked(FEE_REVNET_ID))
361
370
  });
371
+ _afterTransferTo({to: msg.sender, token: context.forwardedAmount.token});
362
372
  }
363
373
  }
364
374
 
365
- /// @notice Set the caller as this contract's deployer.
366
- /// @dev Called by the REVDeployer's constructor. Reverts if a deployer is already set.
367
- function setDeployer() external {
375
+ /// @notice Bind the canonical deployer address exactly once.
376
+ /// @dev The deployer address is precomputed and supplied by the account that created this REVOwner instance.
377
+ /// Only that deploy-time binder may call this, which avoids an ambient public initializer where any first caller
378
+ /// could seize the deployer role before the deterministic REVDeployer is actually deployed.
379
+ /// @param deployer The canonical REVDeployer instance that will manage revnet runtime state.
380
+ function setDeployer(IREVDeployer deployer) external {
381
+ // Only the account that deployed this REVOwner may complete the one-time deployer binding.
382
+ if (msg.sender != _DEPLOYER_BINDER) revert REVOwner_Unauthorized();
383
+ // Prevent the deployer binding from being overwritten after initialization.
368
384
  if (address(DEPLOYER) != address(0)) revert REVOwner_AlreadyInitialized();
369
- DEPLOYER = IREVDeployer(msg.sender);
385
+ // Store the canonical REVDeployer that is authorized to manage runtime hook state.
386
+ DEPLOYER = deployer;
370
387
  }
371
388
 
372
389
  /// @notice Store the cash out delay for a revnet.
@@ -426,4 +443,10 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
426
443
  IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
427
444
  return 0;
428
445
  }
446
+
447
+ /// @notice Clears any token allowance granted by `_beforeTransferTo`.
448
+ function _afterTransferTo(address to, address token) internal {
449
+ if (token == JBConstants.NATIVE_TOKEN) return;
450
+ IERC20(token).forceApprove({spender: to, value: 0});
451
+ }
429
452
  }
@@ -1,10 +1,16 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
+ import {IREVDeployer} from "./IREVDeployer.sol";
5
+
4
6
  /// @notice Interface for the REVOwner contract that handles runtime data hook and cash out hook behavior for revnets.
5
7
  interface IREVOwner {
6
8
  /// @notice The timestamp of when cashouts will become available to a specific revnet's participants.
7
9
  /// @param revnetId The ID of the revnet.
8
10
  /// @return The cash out delay timestamp.
9
11
  function cashOutDelayOf(uint256 revnetId) external view returns (uint256);
12
+
13
+ /// @notice Bind the canonical deployer exactly once.
14
+ /// @param deployer The revnet deployer instance.
15
+ function setDeployer(IREVDeployer deployer) external;
10
16
  }
@@ -241,6 +241,8 @@ contract REVnet_Integrations is TestBaseWorkflow {
241
241
  address(revOwner)
242
242
  );
243
243
 
244
+ revOwner.setDeployer(REV_DEPLOYER);
245
+
244
246
  // Deploy the ARB sucker deployer.
245
247
  JBArbitrumSuckerDeployer _deployer =
246
248
  new JBArbitrumSuckerDeployer(jbDirectory(), jbPermissions(), jbTokens(), address(this), address(0));
@@ -104,6 +104,8 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow {
104
104
  address(revOwner)
105
105
  );
106
106
 
107
+ revOwner.setDeployer(REV_DEPLOYER);
108
+
107
109
  vm.prank(multisig());
108
110
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
109
111
  }
@@ -119,6 +119,8 @@ contract REVDeployerRegressions is TestBaseWorkflow {
119
119
  address(REV_OWNER)
120
120
  );
121
121
 
122
+ REV_OWNER.setDeployer(REV_DEPLOYER);
123
+
122
124
  vm.prank(multisig());
123
125
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
124
126
  }
@@ -271,6 +271,8 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
271
271
  address(REV_OWNER)
272
272
  );
273
273
 
274
+ REV_OWNER.setDeployer(REV_DEPLOYER);
275
+
274
276
  // Deploy fee project
275
277
  vm.prank(multisig());
276
278
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -1052,6 +1054,7 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow {
1052
1054
  TRUSTED_FORWARDER,
1053
1055
  address(REV_OWNER)
1054
1056
  );
1057
+ REV_OWNER.setDeployer(REV_DEPLOYER);
1055
1058
 
1056
1059
  // Deploy fee project
1057
1060
  {
@@ -127,6 +127,8 @@ contract REVLifecycle_Local is TestBaseWorkflow {
127
127
  address(REV_OWNER)
128
128
  );
129
129
 
130
+ REV_OWNER.setDeployer(REV_DEPLOYER);
131
+
130
132
  vm.prank(multisig());
131
133
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
132
134
 
@@ -569,6 +569,8 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow {
569
569
  address(REV_OWNER)
570
570
  );
571
571
 
572
+ REV_OWNER.setDeployer(REV_DEPLOYER);
573
+
572
574
  // Approve the basic deployer to configure the project.
573
575
  vm.prank(address(multisig()));
574
576
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -410,6 +410,8 @@ contract REVLoansAttacks is TestBaseWorkflow {
410
410
  address(REV_OWNER)
411
411
  );
412
412
 
413
+ REV_OWNER.setDeployer(REV_DEPLOYER);
414
+
413
415
  // Deploy fee project
414
416
  vm.prank(address(multisig()));
415
417
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -376,6 +376,8 @@ contract REVLoansFeeRecovery is TestBaseWorkflow {
376
376
  address(REV_OWNER)
377
377
  );
378
378
 
379
+ REV_OWNER.setDeployer(REV_DEPLOYER);
380
+
379
381
  // Deploy fee project.
380
382
  vm.prank(multisig());
381
383
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -234,6 +234,8 @@ contract REVLoansFindings is TestBaseWorkflow {
234
234
  address(REV_OWNER)
235
235
  );
236
236
 
237
+ REV_OWNER.setDeployer(REV_DEPLOYER);
238
+
237
239
  vm.prank(multisig());
238
240
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
239
241
 
@@ -229,6 +229,8 @@ contract REVLoansRegressions is TestBaseWorkflow {
229
229
  address(REV_OWNER)
230
230
  );
231
231
 
232
+ REV_OWNER.setDeployer(REV_DEPLOYER);
233
+
232
234
  vm.prank(multisig());
233
235
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
234
236
 
@@ -256,6 +256,8 @@ contract REVLoansSourceFeeRecovery is TestBaseWorkflow {
256
256
  address(REV_OWNER)
257
257
  );
258
258
 
259
+ REV_OWNER.setDeployer(REV_DEPLOYER);
260
+
259
261
  // Deploy fee project.
260
262
  vm.prank(multisig());
261
263
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -355,6 +355,8 @@ contract REVLoansSourcedTests is TestBaseWorkflow {
355
355
  address(REV_OWNER)
356
356
  );
357
357
 
358
+ REV_OWNER.setDeployer(REV_DEPLOYER);
359
+
358
360
  // Approve the basic deployer to configure the project.
359
361
  vm.prank(address(multisig()));
360
362
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -331,6 +331,8 @@ contract REVLoansUnsourcedTests is TestBaseWorkflow {
331
331
  address(REV_OWNER)
332
332
  );
333
333
 
334
+ REV_OWNER.setDeployer(REV_DEPLOYER);
335
+
334
336
  // Approve the basic deployer to configure the project.
335
337
  vm.prank(address(multisig()));
336
338
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -234,6 +234,8 @@ contract TestBurnHeldTokens is TestBaseWorkflow {
234
234
  address(REV_OWNER)
235
235
  );
236
236
 
237
+ REV_OWNER.setDeployer(REV_DEPLOYER);
238
+
237
239
  // Deploy fee project.
238
240
  vm.prank(multisig());
239
241
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -162,6 +162,8 @@ contract TestCEIPattern is TestBaseWorkflow {
162
162
  address(REV_OWNER)
163
163
  );
164
164
 
165
+ REV_OWNER.setDeployer(REV_DEPLOYER);
166
+
165
167
  vm.prank(multisig());
166
168
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
167
169
  _deployFeeProject();
@@ -247,6 +247,8 @@ contract TestCashOutCallerValidation is TestBaseWorkflow {
247
247
  address(REV_OWNER)
248
248
  );
249
249
 
250
+ REV_OWNER.setDeployer(REV_DEPLOYER);
251
+
250
252
  // Approve the deployer to configure the fee project.
251
253
  vm.prank(multisig());
252
254
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -177,6 +177,8 @@ contract TestConversionDocumentation is TestBaseWorkflow {
177
177
  address(REV_OWNER)
178
178
  );
179
179
 
180
+ REV_OWNER.setDeployer(REV_DEPLOYER);
181
+
180
182
  // Deploy fee project as revnet.
181
183
  vm.prank(multisig());
182
184
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -128,6 +128,8 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
128
128
  address(REV_OWNER)
129
129
  );
130
130
 
131
+ REV_OWNER.setDeployer(REV_DEPLOYER);
132
+
131
133
  vm.prank(multisig());
132
134
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
133
135
 
@@ -125,6 +125,8 @@ contract TestCrossSourceReallocation is TestBaseWorkflow {
125
125
  address(REV_OWNER)
126
126
  );
127
127
 
128
+ REV_OWNER.setDeployer(REV_DEPLOYER);
129
+
128
130
  vm.prank(multisig());
129
131
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
130
132
  _deployFeeProject();
@@ -318,6 +318,8 @@ contract TestERC2771MetaTx is TestBaseWorkflow {
318
318
  address(REV_OWNER)
319
319
  );
320
320
 
321
+ REV_OWNER.setDeployer(REV_DEPLOYER);
322
+
321
323
  // Approve the deployer to configure the project.
322
324
  vm.prank(address(multisig()));
323
325
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -116,6 +116,8 @@ contract TestEmptyBuybackSpecs is TestBaseWorkflow {
116
116
  address(REV_OWNER)
117
117
  );
118
118
 
119
+ REV_OWNER.setDeployer(REV_DEPLOYER);
120
+
119
121
  vm.prank(multisig());
120
122
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
121
123
  }
@@ -128,6 +128,8 @@ contract TestFlashLoanSurplus is TestBaseWorkflow {
128
128
  address(REV_OWNER)
129
129
  );
130
130
 
131
+ REV_OWNER.setDeployer(REV_DEPLOYER);
132
+
131
133
  vm.prank(multisig());
132
134
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
133
135
  _deployFeeProject();
@@ -118,6 +118,8 @@ contract TestHookArrayOOB is TestBaseWorkflow {
118
118
  address(REV_OWNER)
119
119
  );
120
120
 
121
+ REV_OWNER.setDeployer(REV_DEPLOYER);
122
+
121
123
  vm.prank(multisig());
122
124
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
123
125
  }
@@ -126,6 +126,8 @@ contract TestLiquidationBehavior is TestBaseWorkflow {
126
126
  address(REV_OWNER)
127
127
  );
128
128
 
129
+ REV_OWNER.setDeployer(REV_DEPLOYER);
130
+
129
131
  vm.prank(multisig());
130
132
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
131
133
  _deployFeeProject();
@@ -135,6 +135,8 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
135
135
  address(REV_OWNER)
136
136
  );
137
137
 
138
+ REV_OWNER.setDeployer(REV_DEPLOYER);
139
+
138
140
  vm.prank(multisig());
139
141
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
140
142
 
@@ -238,6 +238,8 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
238
238
  address(REV_OWNER)
239
239
  );
240
240
 
241
+ REV_OWNER.setDeployer(REV_DEPLOYER);
242
+
241
243
  // Approve the deployer to configure the fee project.
242
244
  vm.prank(multisig());
243
245
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -128,6 +128,8 @@ contract TestLongTailEconomics is TestBaseWorkflow {
128
128
  address(REV_OWNER)
129
129
  );
130
130
 
131
+ REV_OWNER.setDeployer(REV_DEPLOYER);
132
+
131
133
  vm.prank(multisig());
132
134
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
133
135
 
@@ -317,6 +317,8 @@ contract TestLowFindings is TestBaseWorkflow {
317
317
  address(REV_OWNER)
318
318
  );
319
319
 
320
+ REV_OWNER.setDeployer(REV_DEPLOYER);
321
+
320
322
  // Deploy fee project.
321
323
  vm.prank(multisig());
322
324
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -124,6 +124,8 @@ contract TestMixedFixes is TestBaseWorkflow {
124
124
  address(REV_OWNER)
125
125
  );
126
126
 
127
+ REV_OWNER.setDeployer(REV_DEPLOYER);
128
+
127
129
  vm.prank(multisig());
128
130
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
129
131
  _deployFeeProject();
@@ -285,6 +285,8 @@ contract TestPermit2Signatures is TestBaseWorkflow {
285
285
  address(REV_OWNER)
286
286
  );
287
287
 
288
+ REV_OWNER.setDeployer(REV_DEPLOYER);
289
+
288
290
  // Approve the basic deployer to configure the project.
289
291
  vm.prank(address(multisig()));
290
292
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -179,6 +179,8 @@ contract TestReallocationSandwich is TestBaseWorkflow {
179
179
  address(REV_OWNER)
180
180
  );
181
181
 
182
+ REV_OWNER.setDeployer(REV_DEPLOYER);
183
+
182
184
  vm.prank(multisig());
183
185
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
184
186
 
@@ -168,6 +168,8 @@ contract TestRevnetRegressions is TestBaseWorkflow {
168
168
  address(REV_OWNER)
169
169
  );
170
170
 
171
+ REV_OWNER.setDeployer(REV_DEPLOYER);
172
+
171
173
  vm.prank(multisig());
172
174
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
173
175
  }
@@ -117,6 +117,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
117
117
  address(REV_OWNER)
118
118
  );
119
119
 
120
+ REV_OWNER.setDeployer(REV_DEPLOYER);
121
+
120
122
  vm.prank(multisig());
121
123
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
122
124
  }
@@ -341,6 +343,8 @@ contract TestSplitWeightAdjustment is TestBaseWorkflow {
341
343
  address(ammOwner)
342
344
  );
343
345
 
346
+ ammOwner.setDeployer(ammDeployer);
347
+
344
348
  vm.prank(multisig());
345
349
  jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
346
350
 
@@ -138,6 +138,8 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
138
138
  address(REV_OWNER)
139
139
  );
140
140
 
141
+ REV_OWNER.setDeployer(REV_DEPLOYER);
142
+
141
143
  vm.prank(multisig());
142
144
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
143
145
 
@@ -441,6 +443,8 @@ contract TestSplitWeightE2E is TestBaseWorkflow {
441
443
  address(ammOwner)
442
444
  );
443
445
 
446
+ ammOwner.setDeployer(ammDeployer);
447
+
444
448
  vm.prank(multisig());
445
449
  jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
446
450
 
@@ -350,6 +350,8 @@ contract TestSplitWeightFork is TestBaseWorkflow {
350
350
  address(REV_OWNER)
351
351
  );
352
352
 
353
+ REV_OWNER.setDeployer(REV_DEPLOYER);
354
+
353
355
  vm.prank(multisig());
354
356
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
355
357
 
@@ -172,6 +172,8 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
172
172
  address(REV_OWNER)
173
173
  );
174
174
 
175
+ REV_OWNER.setDeployer(REV_DEPLOYER);
176
+
175
177
  vm.prank(multisig());
176
178
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
177
179
 
@@ -113,6 +113,8 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
113
113
  address(REV_OWNER)
114
114
  );
115
115
 
116
+ REV_OWNER.setDeployer(REV_DEPLOYER);
117
+
116
118
  vm.prank(multisig());
117
119
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
118
120
 
@@ -133,6 +133,8 @@ contract TestUint112Overflow is TestBaseWorkflow {
133
133
  address(REV_OWNER)
134
134
  );
135
135
 
136
+ REV_OWNER.setDeployer(REV_DEPLOYER);
137
+
136
138
  // Deploy fee project
137
139
  vm.prank(multisig());
138
140
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -126,6 +126,8 @@ contract TestZeroAmountLoanGuard is TestBaseWorkflow {
126
126
  address(REV_OWNER)
127
127
  );
128
128
 
129
+ REV_OWNER.setDeployer(REV_DEPLOYER);
130
+
129
131
  vm.prank(multisig());
130
132
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
131
133
  _deployFeeProject();
@@ -126,6 +126,8 @@ contract TestZeroRepayment is TestBaseWorkflow {
126
126
  address(REV_OWNER)
127
127
  );
128
128
 
129
+ REV_OWNER.setDeployer(REV_DEPLOYER);
130
+
129
131
  vm.prank(multisig());
130
132
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
131
133
  _deployFeeProject();
@@ -183,6 +183,8 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
183
183
  address(REV_OWNER)
184
184
  );
185
185
 
186
+ REV_OWNER.setDeployer(REV_DEPLOYER);
187
+
186
188
  // Approve the deployer to configure the fee project.
187
189
  vm.prank(multisig());
188
190
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
@@ -355,6 +355,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
355
355
  address(REV_OWNER)
356
356
  );
357
357
 
358
+ REV_OWNER.setDeployer(REV_DEPLOYER);
359
+
358
360
  vm.prank(multisig());
359
361
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
360
362
 
@@ -128,6 +128,8 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
128
128
  address(REV_OWNER)
129
129
  );
130
130
 
131
+ REV_OWNER.setDeployer(REV_DEPLOYER);
132
+
131
133
  vm.prank(multisig());
132
134
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
133
135
  _deployFeeProject();
@@ -104,6 +104,8 @@ contract TestCashOutBuybackFeeLeak is TestBaseWorkflow {
104
104
  address(revOwner)
105
105
  );
106
106
 
107
+ revOwner.setDeployer(revDeployer);
108
+
107
109
  vm.prank(multisig());
108
110
  jbProjects().approve(address(revDeployer), feeProjectId);
109
111
 
@@ -126,6 +126,8 @@ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
126
126
  address(REV_OWNER)
127
127
  );
128
128
 
129
+ REV_OWNER.setDeployer(REV_DEPLOYER);
130
+
129
131
  vm.prank(multisig());
130
132
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
131
133
  _deployFeeProject();
@@ -130,6 +130,8 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
130
130
  address(REV_OWNER)
131
131
  );
132
132
 
133
+ REV_OWNER.setDeployer(REV_DEPLOYER);
134
+
133
135
  vm.prank(multisig());
134
136
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
135
137
  _deployFeeProject();
@@ -132,6 +132,8 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
132
132
  address(REV_OWNER)
133
133
  );
134
134
 
135
+ REV_OWNER.setDeployer(REV_DEPLOYER);
136
+
135
137
  vm.prank(multisig());
136
138
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
137
139
  _deployFeeProject();
@@ -141,6 +141,8 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
141
141
  address(REV_OWNER)
142
142
  );
143
143
 
144
+ REV_OWNER.setDeployer(REV_DEPLOYER);
145
+
144
146
  vm.prank(multisig());
145
147
  jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
146
148