@rev-net/core-v6 0.0.66 → 0.0.68

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.
@@ -40,6 +40,9 @@ import {REVAutoIssuance} from "./structs/REVAutoIssuance.sol";
40
40
  import {REVConfig} from "./structs/REVConfig.sol";
41
41
  import {REVCroptopAllowedPost} from "./structs/REVCroptopAllowedPost.sol";
42
42
  import {REVDeploy721TiersHookConfig} from "./structs/REVDeploy721TiersHookConfig.sol";
43
+ import {REVOwnerAutoIssuance} from "./structs/REVOwnerAutoIssuance.sol";
44
+ import {REVOwnerExtraGrant} from "./structs/REVOwnerExtraGrant.sol";
45
+ import {REVOwnerRevnetInit} from "./structs/REVOwnerRevnetInit.sol";
43
46
  import {REVStageConfig} from "./structs/REVStageConfig.sol";
44
47
  import {REVSuckerDeploymentConfig} from "./structs/REVSuckerDeploymentConfig.sol";
45
48
 
@@ -60,10 +63,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
60
63
  error REVDeployer_AutoIssuanceBeneficiaryZeroAddress(uint256 stageIndex, uint256 autoIssuanceIndex);
61
64
  error REVDeployer_CashOutsCantBeTurnedOffCompletely(uint256 cashOutTaxRate, uint256 maxCashOutTaxRate);
62
65
  error REVDeployer_MustHaveSplits(uint256 stageIndex, uint256 splitPercent);
63
- error REVDeployer_NothingToAutoIssue(uint256 revnetId, uint256 stageId, address beneficiary);
64
- error REVDeployer_NothingToBurn(uint256 revnetId, address holder);
65
66
  error REVDeployer_RulesetDoesNotAllowDeployingSuckers(uint256 revnetId);
66
- error REVDeployer_StageNotStarted(uint256 stageId);
67
67
  error REVDeployer_StagesRequired(uint256 stageCount);
68
68
  error REVDeployer_StageTimesMustIncrease(uint256 stageIndex, uint256 previousStageStart, uint256 effectiveStart);
69
69
  error REVDeployer_Unauthorized(uint256 revnetId, address caller);
@@ -142,31 +142,12 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
142
142
  // --------------------- public stored properties -------------------- //
143
143
  //*********************************************************************//
144
144
 
145
- /// @notice The number of revnet tokens which can be "auto-minted" (minted without payments)
146
- /// for a specific beneficiary during a stage. Think of this as a per-stage premint.
147
- /// @dev These tokens can be minted with `autoIssueFor(…)`.
148
- /// @custom:param revnetId The ID of the revnet to check.
149
- /// @custom:param stageId The ID of the stage to check.
150
- /// @custom:param beneficiary The beneficiary to check.
151
- mapping(uint256 revnetId => mapping(uint256 stageId => mapping(address beneficiary => uint256)))
152
- public
153
- override amountToAutoIssue;
154
-
155
145
  /// @notice The hashed encoded configuration of each revnet.
156
146
  /// @dev This is used to ensure that the encoded configuration of a revnet is the same when deploying suckers for
157
147
  /// omnichain operations.
158
148
  /// @custom:param revnetId The ID of the revnet to look up.
159
149
  mapping(uint256 revnetId => bytes32 hashedEncodedConfiguration) public override hashedEncodedConfigurationOf;
160
150
 
161
- //*********************************************************************//
162
- // ------------------- internal stored properties -------------------- //
163
- //*********************************************************************//
164
-
165
- /// @notice A list of `JBPermissonIds` indices to grant to the operator of a specific revnet.
166
- /// @dev These should be set in the revnet's deployment process.
167
- /// @custom:param revnetId The ID of the revnet to look up.
168
- mapping(uint256 revnetId => uint256[]) internal _extraOperatorPermissions;
169
-
170
151
  //*********************************************************************//
171
152
  // -------------------------- constructor ---------------------------- //
172
153
  //*********************************************************************//
@@ -213,20 +194,25 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
213
194
  LOANS = loans;
214
195
  OWNER = owner;
215
196
 
216
- // Give the loan contract permission to use the surplus allowance of all revnets.
217
- // Uses wildcard revnetId=0 intentionally the loan contract is a singleton shared by all revnets,
218
- // and each revnet's surplus allowance limits already constrain how much can be drawn.
219
- _setPermission({operator: address(LOANS), revnetId: 0, permissionId: JBPermissionIds.USE_ALLOWANCE});
220
-
221
- // Give the buyback hook (registry) permission to configure pools on all revnets.
222
- _setPermission({operator: address(BUYBACK_HOOK), revnetId: 0, permissionId: JBPermissionIds.SET_BUYBACK_POOL});
197
+ // Wildcard-grant `SET_BUYBACK_POOL` to the buyback hook on this contract's account so the hook can
198
+ // initialize and configure pools for every revnet during the setup window where this contract still
199
+ // holds the JBProjects NFT, before ownership is handed to REVOwner at the end of `_deployRevnetFor`.
200
+ uint8[] memory buybackPermissionIds = new uint8[](1);
201
+ buybackPermissionIds[0] = JBPermissionIds.SET_BUYBACK_POOL;
202
+ PERMISSIONS.setPermissionsFor({
203
+ account: address(this),
204
+ permissionsData: JBPermissionsData({
205
+ operator: address(buybackHook), projectId: 0, permissionIds: buybackPermissionIds
206
+ })
207
+ });
223
208
  }
224
209
 
225
210
  //*********************************************************************//
226
211
  // ------------------------- external views -------------------------- //
227
212
  //*********************************************************************//
228
213
 
229
- /// @dev Make sure this contract can only receive project NFTs from `JBProjects`.
214
+ /// @dev Required to receive the JBProjects NFT briefly while initializing an existing project as a revnet.
215
+ /// The NFT is then forwarded to `OWNER` at the end of `_deployRevnetFor`.
230
216
  function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
231
217
  // Make sure the 721 received is from the `JBProjects` contract.
232
218
  if (msg.sender != address(PROJECTS)) revert();
@@ -238,21 +224,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
238
224
  // -------------------------- public views --------------------------- //
239
225
  //*********************************************************************//
240
226
 
241
- /// @notice Check whether an address is a revnet's operator.
242
- /// @param revnetId The ID of the revnet to check.
243
- /// @param addr The address to check.
244
- /// @return flag A flag indicating whether the address is the revnet's operator.
245
- function isOperatorOf(uint256 revnetId, address addr) public view override returns (bool) {
246
- return PERMISSIONS.hasPermissions({
247
- operator: addr,
248
- account: address(this),
249
- projectId: revnetId,
250
- permissionIds: _operatorPermissionIndexesOf(revnetId),
251
- includeRoot: false,
252
- includeWildcardProjectId: false
253
- });
254
- }
255
-
256
227
  /// @notice Indicates if this contract adheres to the specified interface.
257
228
  /// @dev See `IERC165.supportsInterface`.
258
229
  /// @return A flag indicating if the provided interface ID is supported.
@@ -264,11 +235,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
264
235
  // -------------------------- internal views ------------------------- //
265
236
  //*********************************************************************//
266
237
 
267
- /// @notice If the specified address is not the revnet's current operator, revert.
238
+ /// @notice If the specified address is not the revnet's current operator on the REVOwner registry, revert.
268
239
  /// @param revnetId The ID of the revnet to check.
269
240
  /// @param operator The address to check.
270
241
  function _checkIfIsOperatorOf(uint256 revnetId, address operator) internal view {
271
- if (!isOperatorOf({revnetId: revnetId, addr: operator})) {
242
+ if (!REVOwner(OWNER).isOperatorOf({revnetId: revnetId, addr: operator})) {
272
243
  revert REVDeployer_Unauthorized({revnetId: revnetId, caller: operator});
273
244
  }
274
245
  }
@@ -374,39 +345,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
374
345
  }
375
346
  }
376
347
 
377
- /// @notice Returns the permissions that the operator should have for a revnet.
378
- /// @param revnetId The ID of the revnet to look up.
379
- /// @return allOperatorPermissions The permissions the operator should have for the revnet,
380
- /// including both default and custom permissions.
381
- function _operatorPermissionIndexesOf(uint256 revnetId)
382
- internal
383
- view
384
- returns (uint256[] memory allOperatorPermissions)
385
- {
386
- // Keep a reference to the custom operator permissions.
387
- uint256[] memory customOperatorPermissionIndexes = _extraOperatorPermissions[revnetId];
388
-
389
- // Make the array that merges the default and custom operator permissions.
390
- allOperatorPermissions = new uint256[](9 + customOperatorPermissionIndexes.length);
391
- allOperatorPermissions[0] = JBPermissionIds.SET_SPLIT_GROUPS;
392
- allOperatorPermissions[1] = JBPermissionIds.SET_BUYBACK_POOL;
393
- allOperatorPermissions[2] = JBPermissionIds.SET_BUYBACK_TWAP;
394
- allOperatorPermissions[3] = JBPermissionIds.SET_PROJECT_URI;
395
- allOperatorPermissions[4] = JBPermissionIds.SUCKER_SAFETY;
396
- allOperatorPermissions[5] = JBPermissionIds.SET_BUYBACK_HOOK;
397
- allOperatorPermissions[6] = JBPermissionIds.SET_ROUTER_TERMINAL;
398
- allOperatorPermissions[7] = JBPermissionIds.SET_TOKEN_METADATA;
399
- allOperatorPermissions[8] = JBPermissionIds.SIGN_FOR_ERC20;
400
-
401
- // Copy the custom permissions into the array.
402
- for (uint256 i; i < customOperatorPermissionIndexes.length;) {
403
- allOperatorPermissions[9 + i] = customOperatorPermissionIndexes[i];
404
- unchecked {
405
- ++i;
406
- }
407
- }
408
- }
409
-
410
348
  /// @notice Try to initialize a Uniswap V4 buyback pool for a terminal token at its fair issuance price.
411
349
  /// @dev Called after the ERC-20 token is deployed so the pool can be initialized in the PoolManager.
412
350
  /// Computes `sqrtPriceX96` from `initialIssuance` so the pool starts at the same price as the bonding curve.
@@ -471,57 +409,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
471
409
  // --------------------- external transactions ----------------------- //
472
410
  //*********************************************************************//
473
411
 
474
- /// @notice Auto-mint a revnet's tokens from a stage for a beneficiary.
475
- /// @param revnetId The ID of the revnet to auto-mint tokens for.
476
- /// @param stageId The ID of the stage to auto-mint tokens from.
477
- /// @param beneficiary The address to send auto-minted tokens to.
478
- function autoIssueFor(uint256 revnetId, uint256 stageId, address beneficiary) external override {
479
- // Get the ruleset for the stage to check if it has started.
480
- // Stage IDs are `block.timestamp + i` where `i` is the stage index. These match real JB ruleset IDs
481
- // because JBRulesets assigns IDs the same way: `latestId >= block.timestamp ? latestId + 1 : block.timestamp`
482
- // (see JBRulesets.sol L172). When all stages are queued in a single deployFor() call, the sequential
483
- // IDs `block.timestamp`, `block.timestamp + 1`, ... exactly correspond to the JB-assigned ruleset IDs.
484
- // The returned `ruleset.start` contains the derived start time (from `deriveStartFrom` using the stage's
485
- // `mustStartAtOrAfter`), NOT the queue timestamp — so the timing guard correctly blocks early claims.
486
- (JBRuleset memory ruleset,) = CONTROLLER.getRulesetOf({projectId: revnetId, rulesetId: stageId});
487
-
488
- // Make sure the stage has started.
489
- // forge-lint: disable-next-line(block-timestamp)
490
- if (ruleset.start > block.timestamp) {
491
- revert REVDeployer_StageNotStarted({stageId: stageId});
492
- }
493
-
494
- // Get a reference to the number of tokens to auto-issue.
495
- uint256 count = amountToAutoIssue[revnetId][stageId][beneficiary];
496
-
497
- // If there's nothing to auto-mint, return.
498
- if (count == 0) {
499
- revert REVDeployer_NothingToAutoIssue({revnetId: revnetId, stageId: stageId, beneficiary: beneficiary});
500
- }
501
-
502
- // Reset the auto-mint amount.
503
- amountToAutoIssue[revnetId][stageId][beneficiary] = 0;
504
-
505
- emit AutoIssue({
506
- revnetId: revnetId, stageId: stageId, beneficiary: beneficiary, count: count, caller: _msgSender()
507
- });
508
-
509
- // Mint the tokens.
510
- CONTROLLER.mintTokensOf({
511
- projectId: revnetId, tokenCount: count, beneficiary: beneficiary, memo: "", useReservedPercent: false
512
- });
513
- }
514
-
515
- /// @notice Burn any of a revnet's tokens held by this contract.
516
- /// @dev Project tokens can end up here from reserved token distribution when splits don't sum to 100%.
517
- /// @param revnetId The ID of the revnet to burn tokens for.
518
- function burnHeldTokensOf(uint256 revnetId) external override {
519
- uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
520
- if (balance == 0) revert REVDeployer_NothingToBurn({revnetId: revnetId, holder: address(this)});
521
- CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
522
- emit BurnHeldTokens({revnetId: revnetId, count: balance, caller: _msgSender()});
523
- }
524
-
525
412
  /// @notice Launch a revnet, or initialize an existing Juicebox project as a revnet.
526
413
  /// @dev When initializing an existing project (revnetId != 0):
527
414
  /// - The project must not yet have a controller or rulesets. `JBController.launchRulesetsFor` enforces this —
@@ -588,7 +475,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
588
475
  if (shouldDeployNewRevnet) revnetId = PROJECTS.createFor(address(this));
589
476
 
590
477
  // Deploy the revnet (project, rulesets, ERC-20, suckers, etc.).
591
- bytes32 encodedConfigurationHash = _deployRevnetFor({
478
+ (bytes32 encodedConfigurationHash, REVOwnerRevnetInit memory ownerInit) = _deployRevnetFor({
592
479
  revnetId: revnetId,
593
480
  shouldDeployNewRevnet: shouldDeployNewRevnet,
594
481
  configuration: configuration,
@@ -609,19 +496,18 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
609
496
  });
610
497
  }
611
498
 
612
- // Store the tiered ERC-721 hook in the owner contract.
613
- REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
614
-
615
499
  // Grant the operator all 721 permissions (no prevent* flags for default config).
616
- // These permission IDs are only consumed by `_setOperatorOf` below, after revnet setup has either
617
- // completed or reverted atomically.
618
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.ADJUST_721_TIERS);
619
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.SET_721_METADATA);
620
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.MINT_721);
621
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.SET_721_DISCOUNT_PERCENT);
500
+ ownerInit.extraOperatorPermissionIds = new uint256[](4);
501
+ ownerInit.extraOperatorPermissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
502
+ ownerInit.extraOperatorPermissionIds[1] = JBPermissionIds.SET_721_METADATA;
503
+ ownerInit.extraOperatorPermissionIds[2] = JBPermissionIds.MINT_721;
504
+ ownerInit.extraOperatorPermissionIds[3] = JBPermissionIds.SET_721_DISCOUNT_PERCENT;
505
+
506
+ ownerInit.tiered721Hook = hook;
507
+ ownerInit.operator = configuration.operator;
622
508
 
623
- // Give the operator their permissions (base + 721 extras).
624
- _setOperatorOf({revnetId: revnetId, operator: configuration.operator});
509
+ // Bind every piece of revnet-scoped state on the owner contract in a single call.
510
+ REVOwner(OWNER).initializeRevnet({revnetId: revnetId, init: ownerInit});
625
511
 
626
512
  return (revnetId, hook);
627
513
  }
@@ -659,27 +545,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
659
545
  });
660
546
  }
661
547
 
662
- /// @notice Change a revnet's operator.
663
- /// @dev Only a revnet's current operator can set a new operator.
664
- /// @dev Passing `address(0)` as `newOperator` relinquishes operator powers permanently — the permissions
665
- /// are granted to the zero address (which cannot execute transactions), effectively burning them.
666
- /// @param revnetId The ID of the revnet to change the operator for.
667
- /// @param newOperator The new operator's address. Use `address(0)` to relinquish operator powers.
668
- function setOperatorOf(uint256 revnetId, address newOperator) external override {
669
- // Enforce permissions.
670
- _checkIfIsOperatorOf({revnetId: revnetId, operator: _msgSender()});
671
-
672
- emit ReplaceOperator({revnetId: revnetId, newOperator: newOperator, caller: _msgSender()});
673
-
674
- // Remove operator permissions from the old operator.
675
- _setPermissionsFor({
676
- account: address(this), operator: _msgSender(), revnetId: revnetId, permissionIds: new uint8[](0)
677
- });
678
-
679
- // Set the new operator.
680
- _setOperatorOf({revnetId: revnetId, operator: newOperator});
681
- }
682
-
683
548
  //*********************************************************************//
684
549
  // --------------------- internal transactions ----------------------- //
685
550
  //*********************************************************************//
@@ -699,7 +564,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
699
564
  returns (IJB721TiersHook hook)
700
565
  {
701
566
  // Deploy the revnet (project, rulesets, ERC-20, suckers, etc.).
702
- bytes32 encodedConfigurationHash = _deployRevnetFor({
567
+ (bytes32 encodedConfigurationHash, REVOwnerRevnetInit memory ownerInit) = _deployRevnetFor({
703
568
  revnetId: revnetId,
704
569
  shouldDeployNewRevnet: shouldDeployNewRevnet,
705
570
  configuration: configuration,
@@ -733,35 +598,33 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
733
598
  salt: keccak256(abi.encode(tiered721HookConfiguration.salt, encodedConfigurationHash, _msgSender()))
734
599
  });
735
600
 
736
- // Store the tiered ERC-721 hook in the owner contract.
737
- REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
738
-
739
- // These permission IDs are only consumed by `_setOperatorOf` below, after revnet setup has either
740
- // completed or reverted atomically.
741
-
742
- // Give the operator permission to add and remove tiers unless prevented.
743
- if (!tiered721HookConfiguration.preventOperatorAdjustingTiers) {
744
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.ADJUST_721_TIERS);
745
- }
746
-
747
- // Give the operator permission to set ERC-721 tier metadata unless prevented.
748
- if (!tiered721HookConfiguration.preventOperatorUpdatingMetadata) {
749
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.SET_721_METADATA);
750
- }
751
-
752
- // Give the operator permission to mint ERC-721s (without a payment)
753
- // from tiers with `allowOwnerMint` set to true, unless prevented.
754
- if (!tiered721HookConfiguration.preventOperatorMinting) {
755
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.MINT_721);
756
- }
757
-
758
- // Give the operator permission to increase the discount of a tier unless prevented.
759
- if (!tiered721HookConfiguration.preventOperatorIncreasingDiscountPercent) {
760
- _extraOperatorPermissions[revnetId].push(JBPermissionIds.SET_721_DISCOUNT_PERCENT);
601
+ // Build the 721 permission additions based on the deployer's `preventOperator*` flags.
602
+ {
603
+ uint256 extraCount;
604
+ if (!tiered721HookConfiguration.preventOperatorAdjustingTiers) ++extraCount;
605
+ if (!tiered721HookConfiguration.preventOperatorUpdatingMetadata) ++extraCount;
606
+ if (!tiered721HookConfiguration.preventOperatorMinting) ++extraCount;
607
+ if (!tiered721HookConfiguration.preventOperatorIncreasingDiscountPercent) ++extraCount;
608
+
609
+ uint256[] memory extraPermissions = new uint256[](extraCount);
610
+ uint256 idx;
611
+ if (!tiered721HookConfiguration.preventOperatorAdjustingTiers) {
612
+ extraPermissions[idx++] = JBPermissionIds.ADJUST_721_TIERS;
613
+ }
614
+ if (!tiered721HookConfiguration.preventOperatorUpdatingMetadata) {
615
+ extraPermissions[idx++] = JBPermissionIds.SET_721_METADATA;
616
+ }
617
+ if (!tiered721HookConfiguration.preventOperatorMinting) {
618
+ extraPermissions[idx++] = JBPermissionIds.MINT_721;
619
+ }
620
+ if (!tiered721HookConfiguration.preventOperatorIncreasingDiscountPercent) {
621
+ extraPermissions[idx++] = JBPermissionIds.SET_721_DISCOUNT_PERCENT;
622
+ }
623
+ ownerInit.extraOperatorPermissionIds = extraPermissions;
761
624
  }
762
625
 
763
- // Give the operator their permissions (base + 721 extras).
764
- _setOperatorOf({revnetId: revnetId, operator: configuration.operator});
626
+ ownerInit.tiered721Hook = hook;
627
+ ownerInit.operator = configuration.operator;
765
628
 
766
629
  // If there are posts to allow, configure them.
767
630
  if (allowedPosts.length != 0) {
@@ -791,11 +654,14 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
791
654
  // Set up the allowed posts in the publisher.
792
655
  PUBLISHER.configurePostingCriteriaFor({allowedPosts: formattedAllowedPosts});
793
656
 
794
- // Give the croptop publisher permission to post new ERC-721 tiers on this contract's behalf.
795
- _setPermission({
796
- operator: address(PUBLISHER), revnetId: revnetId, permissionId: JBPermissionIds.ADJUST_721_TIERS
797
- });
657
+ // Give the croptop publisher permission to post new ERC-721 tiers on the revnet's behalf.
658
+ ownerInit.extraGrants = new REVOwnerExtraGrant[](1);
659
+ ownerInit.extraGrants[0] =
660
+ REVOwnerExtraGrant({operator: address(PUBLISHER), permissionId: JBPermissionIds.ADJUST_721_TIERS});
798
661
  }
662
+
663
+ // Bind every piece of revnet-scoped state on the owner contract in a single call.
664
+ REVOwner(OWNER).initializeRevnet({revnetId: revnetId, init: ownerInit});
799
665
  }
800
666
 
801
667
  /// @notice Deploy a revnet, or initialize an existing Juicebox project as a revnet.
@@ -820,11 +686,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
820
686
  REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
821
687
  )
822
688
  internal
823
- returns (bytes32 encodedConfigurationHash)
689
+ returns (bytes32 encodedConfigurationHash, REVOwnerRevnetInit memory ownerInit)
824
690
  {
825
691
  // Normalize and encode the configurations.
826
692
  JBRulesetConfig[] memory rulesetConfigurations;
827
- (rulesetConfigurations, encodedConfigurationHash) = _makeRulesetConfigurations({
693
+ (rulesetConfigurations, encodedConfigurationHash, ownerInit.autoIssuances) = _makeRulesetConfigurations({
828
694
  revnetId: revnetId, configuration: configuration, accountingContextsToAccept: accountingContextsToAccept
829
695
  });
830
696
 
@@ -832,20 +698,17 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
832
698
  JBTerminalConfig[] memory terminalConfigurations =
833
699
  _makeTerminalConfigurations({accountingContextsToAccept: accountingContextsToAccept});
834
700
 
835
- address owner;
701
+ // Store the hash before setup callbacks so reentrant readers cannot observe a zero configuration hash. Any
702
+ // subsequent revert rolls this write back.
703
+ hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash;
704
+
836
705
  if (!shouldDeployNewRevnet) {
837
706
  // Keep a reference to the Juicebox project's owner.
838
- owner = PROJECTS.ownerOf(revnetId);
707
+ address owner = PROJECTS.ownerOf(revnetId);
839
708
 
840
709
  // Make sure the caller is the owner of the Juicebox project.
841
710
  if (_msgSender() != owner) revert REVDeployer_Unauthorized(revnetId, _msgSender());
842
- }
843
711
 
844
- // Store the hash before setup callbacks so reentrant readers cannot observe a zero configuration hash. Any
845
- // subsequent revert rolls this write back.
846
- hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash;
847
-
848
- if (!shouldDeployNewRevnet) {
849
712
  // Initialize the existing Juicebox project as a revnet by transferring the `JBProjects` NFT to this
850
713
  // deployer. This is irreversible.
851
714
  IERC721(PROJECTS).safeTransferFrom({from: owner, to: address(this), tokenId: revnetId});
@@ -860,10 +723,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
860
723
  memo: ""
861
724
  });
862
725
 
863
- // Store the cash out delay of the revnet if its stages are already in progress.
864
- // This prevents cash out liquidity/arbitrage issues for existing revnets which
865
- // are deploying to a new chain.
866
- _setCashOutDelayIfNeeded({revnetId: revnetId, firstStageConfig: configuration.stageConfigurations[0]});
726
+ // Compute the cash out delay if the revnet's stages are already in progress. This prevents cash out
727
+ // liquidity/arbitrage issues for existing revnets which are deploying to a new chain.
728
+ ownerInit.cashOutDelay =
729
+ _computeCashOutDelayIfNeeded({revnetId: revnetId, firstStageConfig: configuration.stageConfigurations[0]});
867
730
 
868
731
  // Deploy the revnet's ERC-20 token.
869
732
  CONTROLLER.deployERC20For({
@@ -886,6 +749,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
886
749
  }
887
750
  }
888
751
 
752
+ // Transfer the JBProjects NFT to REVOwner. REVOwner is the project's authoritative owner.
753
+ IERC721(PROJECTS).safeTransferFrom({from: address(this), to: OWNER, tokenId: revnetId});
754
+
889
755
  // Deploy the suckers (if applicable).
890
756
  if (suckerDeploymentConfiguration.salt != bytes32(0)) {
891
757
  _deploySuckersFor({
@@ -906,6 +772,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
906
772
  });
907
773
  }
908
774
 
775
+ /// @notice Deploy suckers for a revnet against the sucker registry, scoped on REVOwner's account.
776
+ /// @param revnetId The ID of the revnet to deploy suckers for.
909
777
  /// @param encodedConfigurationHash A hash that represents the revnet's configuration.
910
778
  /// @param suckerDeploymentConfiguration The suckers to set up for the revnet.
911
779
  function _deploySuckersFor(
@@ -923,8 +791,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
923
791
  caller: _msgSender()
924
792
  });
925
793
 
926
- // Include the caller so two revnets with identical configuration and user salt cannot collide. Same-address
927
- // cross-chain deployment still works when the same operator calls this helper on each chain.
928
794
  suckers = SUCKER_REGISTRY.deploySuckersFor({
929
795
  projectId: revnetId,
930
796
  salt: keccak256(abi.encode(encodedConfigurationHash, suckerDeploymentConfiguration.salt, _msgSender())),
@@ -954,7 +820,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
954
820
  JBAccountingContext[] calldata accountingContextsToAccept
955
821
  )
956
822
  internal
957
- returns (JBRulesetConfig[] memory rulesetConfigurations, bytes32 encodedConfigurationHash)
823
+ returns (
824
+ JBRulesetConfig[] memory rulesetConfigurations,
825
+ bytes32 encodedConfigurationHash,
826
+ REVOwnerAutoIssuance[] memory autoIssuances
827
+ )
958
828
  {
959
829
  // If there are no stages, revert.
960
830
  if (configuration.stageConfigurations.length == 0) {
@@ -977,6 +847,25 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
977
847
  JBFundAccessLimitGroup[] memory fundAccessLimitGroups =
978
848
  _makeLoanFundAccessLimits({accountingContextsToAccept: accountingContextsToAccept});
979
849
 
850
+ // Pre-count auto-issuances that will land on this chain so the owner-init array can be sized exactly.
851
+ uint256 autoIssuanceCount;
852
+ for (uint256 i; i < configuration.stageConfigurations.length;) {
853
+ REVAutoIssuance[] calldata stageAutoIssuances = configuration.stageConfigurations[i].autoIssuances;
854
+ for (uint256 j; j < stageAutoIssuances.length;) {
855
+ if (stageAutoIssuances[j].count != 0 && stageAutoIssuances[j].chainId == block.chainid) {
856
+ ++autoIssuanceCount;
857
+ }
858
+ unchecked {
859
+ ++j;
860
+ }
861
+ }
862
+ unchecked {
863
+ ++i;
864
+ }
865
+ }
866
+ autoIssuances = new REVOwnerAutoIssuance[](autoIssuanceCount);
867
+ uint256 autoIssuanceIdx;
868
+
980
869
  // Track the previous stage's effective start time for ordering validation.
981
870
  // When stage 0 uses `startsAtOrAfter == 0`, the effective value is `block.timestamp`.
982
871
  // Subsequent stages must be validated against this normalized value, not the raw calldata,
@@ -1069,13 +958,16 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1069
958
  caller: _msgSender()
1070
959
  });
1071
960
 
1072
- // Store the amount of tokens that can be auto-minted on this chain during this stage.
1073
- // The stage ID is `block.timestamp + i`. This matches the ruleset ID that JBRulesets assigns
1074
- // because JBRulesets uses `latestId >= block.timestamp ? latestId + 1 : block.timestamp`
961
+ // Record the amount of tokens that can be auto-minted on this chain during this stage. The stage
962
+ // ID is `block.timestamp + i`. This matches the ruleset ID that JBRulesets assigns because
963
+ // JBRulesets uses `latestId >= block.timestamp ? latestId + 1 : block.timestamp`
1075
964
  // (JBRulesets.sol L172), producing the same sequential IDs when all stages are queued in one tx.
1076
- // `autoIssueFor` later calls `getRulesetOf(revnetId, stageId)` — the returned `ruleset.start`
1077
- // is the derived start time (not the queue time), so the timing guard works correctly.
1078
- amountToAutoIssue[revnetId][block.timestamp + i][autoIssuance.beneficiary] += autoIssuance.count;
965
+ // `REVOwner.autoIssueFor` later calls `getRulesetOf(revnetId, stageId)` — the returned
966
+ // `ruleset.start` is the derived start time (not the queue time), so the timing guard works
967
+ // correctly.
968
+ autoIssuances[autoIssuanceIdx++] = REVOwnerAutoIssuance({
969
+ stageId: block.timestamp + i, beneficiary: autoIssuance.beneficiary, count: autoIssuance.count
970
+ });
1079
971
  }
1080
972
  unchecked {
1081
973
  ++i;
@@ -1086,79 +978,27 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1086
978
  encodedConfigurationHash = keccak256(encodedConfiguration);
1087
979
  }
1088
980
 
1089
- /// @notice Set the cash out delay if the revnet's stages are already in progress.
1090
- /// @dev This prevents cash out liquidity/arbitrage issues for existing revnets deploying to a new chain.
1091
- /// @param revnetId The ID of the revnet to set the cash out delay for.
981
+ /// @notice Compute the cash out delay for the revnet's first stage if its stages are already in progress.
982
+ /// @dev Returns 0 when no delay is needed. Emits `SetCashOutDelay` so deployer-side telemetry stays unchanged
983
+ /// even though the actual storage write happens later inside `REVOwner.initializeRevnet`.
984
+ /// @param revnetId The ID of the revnet to compute the cash out delay for.
1092
985
  /// @param firstStageConfig The revnet's first stage.
1093
- function _setCashOutDelayIfNeeded(uint256 revnetId, REVStageConfig calldata firstStageConfig) internal {
986
+ /// @return cashOutDelay The timestamp after which cash outs are allowed, or 0 if no delay applies.
987
+ function _computeCashOutDelayIfNeeded(
988
+ uint256 revnetId,
989
+ REVStageConfig calldata firstStageConfig
990
+ )
991
+ internal
992
+ returns (uint256 cashOutDelay)
993
+ {
1094
994
  // If this is the first revnet being deployed (with a `startsAtOrAfter` of 0),
1095
995
  // or if the first stage hasn't started yet, we don't need to set a cash out delay.
1096
996
  // forge-lint: disable-next-line(block-timestamp)
1097
- if (firstStageConfig.startsAtOrAfter == 0 || firstStageConfig.startsAtOrAfter >= block.timestamp) return;
997
+ if (firstStageConfig.startsAtOrAfter == 0 || firstStageConfig.startsAtOrAfter >= block.timestamp) return 0;
1098
998
 
1099
999
  // Calculate the timestamp at which the cash out delay ends.
1100
- uint256 cashOutDelay = block.timestamp + CASH_OUT_DELAY;
1101
-
1102
- // Store the cash out delay in the owner contract.
1103
- REVOwner(OWNER).setCashOutDelayOf({revnetId: revnetId, cashOutDelay: cashOutDelay});
1000
+ cashOutDelay = block.timestamp + CASH_OUT_DELAY;
1104
1001
 
1105
1002
  emit SetCashOutDelay({revnetId: revnetId, cashOutDelay: cashOutDelay, caller: _msgSender()});
1106
1003
  }
1107
-
1108
- /// @notice Grant a permission to an address (an "operator").
1109
- /// @param operator The address to grant the permission to.
1110
- /// @param revnetId The ID of the revnet to scope the permission for.
1111
- /// @param permissionId The ID of the permission to grant. See `JBPermissionIds`.
1112
- function _setPermission(address operator, uint256 revnetId, uint8 permissionId) internal {
1113
- uint8[] memory permissionsIds = new uint8[](1);
1114
- permissionsIds[0] = permissionId;
1115
-
1116
- // Give the operator the permission.
1117
- _setPermissionsFor({
1118
- account: address(this), operator: operator, revnetId: revnetId, permissionIds: permissionsIds
1119
- });
1120
- }
1121
-
1122
- /// @notice Grant permissions to an address (an "operator").
1123
- /// @param account The account granting the permissions.
1124
- /// @param operator The address to grant the permissions to.
1125
- /// @param revnetId The ID of the revnet to scope the permissions for.
1126
- /// @param permissionIds An array of permission IDs to grant. See `JBPermissionIds`.
1127
- function _setPermissionsFor(
1128
- address account,
1129
- address operator,
1130
- uint256 revnetId,
1131
- uint8[] memory permissionIds
1132
- )
1133
- internal
1134
- {
1135
- // Set up the permission data.
1136
- JBPermissionsData memory permissionData =
1137
- // forge-lint: disable-next-line(unsafe-typecast)
1138
- JBPermissionsData({operator: operator, projectId: uint64(revnetId), permissionIds: permissionIds});
1139
-
1140
- // Set the permissions.
1141
- PERMISSIONS.setPermissionsFor({account: account, permissionsData: permissionData});
1142
- }
1143
-
1144
- /// @notice Give a operator their permissions.
1145
- /// @dev Only a revnet's current operator can set a new operator, by calling `setOperatorOf(…)`.
1146
- /// @param revnetId The ID of the revnet to grant operator permissions for.
1147
- /// @param operator The new operator's address.
1148
- function _setOperatorOf(uint256 revnetId, address operator) internal {
1149
- // Get the permission indexes for the operator.
1150
- uint256[] memory permissionIndexes = _operatorPermissionIndexesOf(revnetId);
1151
- uint8[] memory permissionIds = new uint8[](permissionIndexes.length);
1152
-
1153
- for (uint256 i; i < permissionIndexes.length;) {
1154
- permissionIds[i] = uint8(permissionIndexes[i]);
1155
- unchecked {
1156
- ++i;
1157
- }
1158
- }
1159
-
1160
- _setPermissionsFor({
1161
- account: address(this), operator: operator, revnetId: revnetId, permissionIds: permissionIds
1162
- });
1163
- }
1164
1004
  }