@rev-net/core-v6 0.0.58 → 0.0.61

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.58",
3
+ "version": "0.0.61",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,19 +26,19 @@
26
26
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
27
27
  },
28
28
  "dependencies": {
29
- "@bananapus/721-hook-v6": "^0.0.51",
30
- "@bananapus/buyback-hook-v6": "^0.0.47",
31
- "@bananapus/core-v6": "^0.0.54",
32
- "@bananapus/ownable-v6": "^0.0.25",
33
- "@bananapus/permission-ids-v6": "^0.0.25",
34
- "@bananapus/router-terminal-v6": "^0.0.44",
35
- "@bananapus/suckers-v6": "^0.0.47",
36
- "@croptop/core-v6": "^0.0.48",
29
+ "@bananapus/721-hook-v6": "^0.0.54",
30
+ "@bananapus/buyback-hook-v6": "^0.0.49",
31
+ "@bananapus/core-v6": "^0.0.57",
32
+ "@bananapus/ownable-v6": "^0.0.27",
33
+ "@bananapus/permission-ids-v6": "^0.0.26",
34
+ "@bananapus/router-terminal-v6": "^0.0.46",
35
+ "@bananapus/suckers-v6": "^0.0.49",
36
+ "@croptop/core-v6": "^0.0.52",
37
37
  "@openzeppelin/contracts": "5.6.1",
38
38
  "@uniswap/permit2": "github:Uniswap/permit2#cc56ad0f3439c502c246fc5cfcc3db92bb8b7219"
39
39
  },
40
40
  "devDependencies": {
41
- "@bananapus/address-registry-v6": "0.0.25",
41
+ "@bananapus/address-registry-v6": "0.0.26",
42
42
  "@sphinx-labs/plugins": "0.33.3",
43
43
  "@uniswap/v4-core": "1.0.2"
44
44
  }
@@ -112,12 +112,12 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
112
112
 
113
113
  | Mapping | Visibility | Type | Purpose |
114
114
  |---------|-----------|------|---------|
115
- | `isLoanSourceOf` | `public` | `revnetId => terminal => token => bool` | Is this (terminal, token) pair used for loans? |
115
+ | `isLoanSourceOf` | `public` | `revnetId => token => bool` | Is this token used for loans? |
116
116
  | `totalLoansBorrowedFor` | `public` | `revnetId => uint256` | Counter for loan numbering |
117
- | `totalBorrowedFrom` | `public` | `revnetId => terminal => token => uint256` | Tracks debt per loan source |
117
+ | `totalBorrowedFrom` | `public` | `revnetId => token => uint256` | Tracks debt per loan source token |
118
118
  | `totalCollateralOf` | `public` | `revnetId => uint256` | Sum of all burned collateral |
119
119
  | `_loanOf` | `internal` | `loanId => REVLoan` | Per-loan state (use `loanOf(loanId)` view) |
120
- | `_loanSourcesOf` | `internal` | `revnetId => REVLoanSource[]` | Array of all loan sources used (use `loanSourcesOf(revnetId)` view) |
120
+ | `_loanSourceTokensOf` | `internal` | `revnetId => address[]` | Array of all loan source tokens used (use `loanSourceTokensOf(revnetId)` view) |
121
121
  | `tokenUriResolver` | `public` | `IJBTokenUriResolver` | Resolver for loan NFT token URIs |
122
122
 
123
123
  ## Gotchas
@@ -133,7 +133,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
133
133
  9. **`cashOutTaxRate` cannot be MAX.** Must be strictly less than `MAX_CASH_OUT_TAX_RATE` (10,000). Revnets cannot fully disable cash outs.
134
134
  10. **Split operator is singular.** Only ONE address can be operator at a time. The operator can replace itself via `setOperatorOf` but cannot delegate or multi-sig.
135
135
  11. **NATIVE_TOKEN on non-ETH chains.** `JBConstants.NATIVE_TOKEN` on Celo means CELO, on Polygon means MATIC -- not ETH. Use ERC-20 WETH instead. The config matching hash does NOT catch terminal configuration differences.
136
- 12. **Loan source array is unbounded.** `_loanSourcesOf[revnetId]` grows without limit. No validation that a terminal is actually registered for the project.
136
+ 12. **Loan source array is unbounded.** `_loanSourceTokensOf[revnetId]` grows without limit, bounded in practice by the token accounting contexts accepted by the canonical `MULTI_TERMINAL`.
137
137
  13. **Flash-loan surplus exposure.** `borrowableAmountFrom` reads live surplus. A flash loan can temporarily inflate the treasury to borrow more than the sustained value supports.
138
138
  14. **Fee revnet must have terminals.** Cash-out fees and loan protocol fees are paid to `FEE_REVNET_ID`. If that project has no terminal for the token, the fee silently fails (try-catch).
139
139
  15. **Buyback hook is immutable per deployer.** `BUYBACK_HOOK` is set at construction time on both REVDeployer and REVOwner. All revnets deployed by the same deployer share the same buyback hook.
@@ -202,10 +202,10 @@ Quick-reference for common read operations. All functions are `view`/`pure` and
202
202
  | What | Call | Returns |
203
203
  |------|------|---------|
204
204
  | Borrowable amount for collateral | `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | `uint256` |
205
- | Total borrowed (per source) | `REVLoans.totalBorrowedFrom(revnetId, terminal, token)` | `uint256` |
205
+ | Total borrowed (per source token) | `REVLoans.totalBorrowedFrom(revnetId, token)` | `uint256` |
206
206
  | Total collateral locked | `REVLoans.totalCollateralOf(revnetId)` | `uint256` |
207
207
  | Loan details | `REVLoans.loanOf(loanId)` | `REVLoan` struct |
208
- | All loan sources | `REVLoans.loanSourcesOf(revnetId)` | `REVLoanSource[]` |
208
+ | All loan source tokens | `REVLoans.loanSourceTokensOf(revnetId)` | `address[]` |
209
209
  | Loan count | `REVLoans.totalLoansBorrowedFor(revnetId)` | `uint256` |
210
210
  | Source fee for repayment | `REVLoans.determineSourceFeeAmount(loan, amount)` | `uint256` |
211
211
  | Revnet ID from loan ID | `REVLoans.revnetIdOfLoanWith(loanId)` | `uint256` (pure) |
@@ -276,7 +276,7 @@ deployer.deployFor({
276
276
 
277
277
  loans.borrowFrom({
278
278
  revnetId: revnetId,
279
- source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
279
+ token: JBConstants.NATIVE_TOKEN,
280
280
  minBorrowAmount: 0,
281
281
  collateralCount: 1000e18, // Burn 1000 tokens as collateral
282
282
  beneficiary: msg.sender, // Receive borrowed funds
@@ -290,7 +290,7 @@ loans.borrowFrom({
290
290
  (uint256 reallocatedLoanId, uint256 newLoanId, , ) = loans.reallocateCollateralFromLoan({
291
291
  loanId: loanId,
292
292
  collateralCountToTransfer: 500e18, // Move 500 tokens out of existing loan
293
- source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
293
+ token: JBConstants.NATIVE_TOKEN,
294
294
  minBorrowAmount: 0,
295
295
  collateralCountToAdd: 200e18, // Add 200 fresh tokens on top
296
296
  beneficiary: payable(msg.sender), // Receive new loan proceeds
@@ -64,7 +64,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
64
64
  | `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | Calculate how much can be borrowed for a given collateral amount. Returns 0 during the cash-out delay period. Aggregates surplus from all terminals, applies bonding curve. |
65
65
  | `REVLoans.determineSourceFeeAmount(loan, amount)` | Calculate the time-proportional source fee for a loan repayment. Zero during prepaid window, linear accrual after. |
66
66
  | `REVLoans.loanOf(loanId)` | Returns the full `REVLoan` struct for a loan. |
67
- | `REVLoans.loanSourcesOf(revnetId)` | Returns all `(terminal, token)` pairs used for loans by a revnet. |
67
+ | `REVLoans.loanSourceTokensOf(revnetId)` | Returns all token sources used for loans by a revnet. Loans always source funds from the canonical `MULTI_TERMINAL`. |
68
68
  | `REVLoans.revnetIdOfLoanWith(loanId)` | Decode the revnet ID from a loan ID (`loanId / 1_000_000_000_000`). |
69
69
  ## Integration Points
70
70
 
@@ -88,8 +88,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
88
88
  | `REVStageConfig` | `startsAtOrAfter` (uint48), `initialIssuance` (uint112), `issuanceCutFrequency` (uint32), `issuanceCutPercent` (uint32), `cashOutTaxRate` (uint16), `splitPercent` (uint16), `splits[]`, `autoIssuances[]`, `extraMetadata` (uint16) | Translated into `JBRulesetConfig` |
89
89
  | `REVDescription` | `name`, `ticker`, `uri`, `salt` | ERC-20 token deployment and project metadata |
90
90
  | `REVAutoIssuance` | `chainId` (uint32), `count` (uint104), `beneficiary` | Per-stage cross-chain token auto-minting |
91
- | `REVLoan` | `amount` (uint112), `collateral` (uint112), `createdAt` (uint48), `prepaidFeePercent` (uint16), `prepaidDuration` (uint32), `source` (REVLoanSource) | Per-loan state in `REVLoans` |
92
- | `REVLoanSource` | `token`, `terminal` (IJBPayoutTerminal) | Identifies which terminal and token a loan draws from |
91
+ | `REVLoan` | `amount` (uint112), `collateral` (uint112), `createdAt` (uint48), `prepaidFeePercent` (uint16), `prepaidDuration` (uint32), `sourceToken` (address) | Per-loan state in `REVLoans` |
93
92
  | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration` (REVBaseline721HookConfig), `salt`, `preventOperatorAdjustingTiers`, `preventOperatorUpdatingMetadata`, `preventOperatorMinting`, `preventOperatorIncreasingDiscountPercent` | 721 hook deployment with operator permissions (preventive flags — `false` = allowed). Uses `REVBaseline721HookConfig` (not `JBDeploy721TiersHookConfig`) to omit `issueTokensForSplits` — revnets always force it to `false`. |
94
93
  | `REVBaseline721HookConfig` | `name`, `symbol`, `baseUri`, `tokenUriResolver`, `contractUri`, `tiersConfig`, `reserveBeneficiary`, `flags` (REV721TiersHookFlags) | Same as `JBDeploy721TiersHookConfig` but uses `REV721TiersHookFlags` which omits `issueTokensForSplits`. |
95
94
  | `REV721TiersHookFlags` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending` | Same as `JB721TiersHookFlags` minus `issueTokensForSplits`. Revnets do their own weight adjustment for splits. |
@@ -20,7 +20,6 @@ import {Script} from "forge-std/Script.sol";
20
20
  import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
21
21
  import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
22
22
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
23
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
24
23
  import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
25
24
  import {JBTokenMapping} from "@bananapus/suckers-v6/src/structs/JBTokenMapping.sol";
26
25
  import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
@@ -48,7 +47,7 @@ import {REV721TiersHookFlags} from "../src/structs/REV721TiersHookFlags.sol";
48
47
 
49
48
  struct FeeProjectConfig {
50
49
  REVConfig configuration;
51
- JBTerminalConfig[] terminalConfigurations;
50
+ JBAccountingContext[] accountingContextsToAccept;
52
51
  REVSuckerDeploymentConfig suckerDeploymentConfiguration;
53
52
  REVDeploy721TiersHookConfig tiered721HookConfiguration;
54
53
  REVCroptopAllowedPost[] allowedPosts;
@@ -65,7 +64,7 @@ contract DeployScript is Script, Sphinx {
65
64
  Hook721Deployment hook;
66
65
  /// @notice tracks the deployment of the sucker contracts for the chain we are deploying to.
67
66
  SuckerDeployment suckers;
68
- /// @notice tracks the deployment of the router terminal.
67
+ /// @notice tracks the deployment of the router terminal registry package.
69
68
  RouterTerminalDeployment routerTerminal;
70
69
  uint32 private constant _PREMINT_CHAIN_ID = 1;
71
70
  string private constant _NAME = "Revnet";
@@ -130,7 +129,7 @@ contract DeployScript is Script, Sphinx {
130
129
  defaultValue: string("node_modules/@bananapus/721-hook-v6/deployments/")
131
130
  })
132
131
  );
133
- // Get the deployment addresses for the router terminal contracts for this chain.
132
+ // Get the deployment addresses for the router terminal registry package for this chain.
134
133
  routerTerminal = RouterTerminalDeploymentLib.getDeployment(
135
134
  vm.envOr({
136
135
  name: "NANA_ROUTER_TERMINAL_DEPLOYMENT_PATH",
@@ -161,15 +160,6 @@ contract DeployScript is Script, Sphinx {
161
160
  accountingContextsToAccept[0] =
162
161
  JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: _DECIMALS, currency: _NATIVE_CURRENCY});
163
162
 
164
- // The terminals that the project will accept funds through.
165
- JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](2);
166
- terminalConfigurations[0] =
167
- JBTerminalConfig({terminal: core.terminal, accountingContextsToAccept: accountingContextsToAccept});
168
- terminalConfigurations[1] = JBTerminalConfig({
169
- terminal: IJBTerminal(address(routerTerminal.registry)),
170
- accountingContextsToAccept: new JBAccountingContext[](0)
171
- });
172
-
173
163
  // The project's revnet stage configurations.
174
164
  REVStageConfig[] memory stageConfigurations = new REVStageConfig[](3);
175
165
 
@@ -296,7 +286,7 @@ contract DeployScript is Script, Sphinx {
296
286
 
297
287
  return FeeProjectConfig({
298
288
  configuration: revnetConfiguration,
299
- terminalConfigurations: terminalConfigurations,
289
+ accountingContextsToAccept: accountingContextsToAccept,
300
290
  suckerDeploymentConfiguration: suckerDeploymentConfiguration,
301
291
  tiered721HookConfiguration: REVDeploy721TiersHookConfig({
302
292
  baseline721HookConfiguration: REVBaseline721HookConfig({
@@ -364,7 +354,13 @@ contract DeployScript is Script, Sphinx {
364
354
  salt: _REVLOANS_SALT,
365
355
  creationCode: type(REVLoans).creationCode,
366
356
  arguments: abi.encode(
367
- core.controller, suckers.registry, _candidateId, loansOwner, permit2, trustedForwarder
357
+ core.controller,
358
+ core.terminal,
359
+ suckers.registry,
360
+ _candidateId,
361
+ loansOwner,
362
+ permit2,
363
+ trustedForwarder
368
364
  )
369
365
  });
370
366
 
@@ -397,9 +393,11 @@ contract DeployScript is Script, Sphinx {
397
393
  creationCode: type(REVDeployer).creationCode,
398
394
  arguments: abi.encode(
399
395
  core.controller,
396
+ core.terminal,
397
+ IJBTerminal(address(routerTerminal.registry)),
400
398
  suckers.registry,
401
399
  _candidateId,
402
- hook.hook_deployer,
400
+ hook.hookDeployer,
403
401
  croptop.publisher,
404
402
  IJBBuybackHookRegistry(address(buybackHook.registry)),
405
403
  _candidateRevloansAddr,
@@ -432,11 +430,12 @@ contract DeployScript is Script, Sphinx {
432
430
  }
433
431
  }
434
432
 
435
- // Deploy REVLoans first — it only depends on the controller.
433
+ // Deploy REVLoans first — it depends on the canonical controller and multi terminal.
436
434
  REVLoans revloans = _revloansExists
437
435
  ? REVLoans(payable(_existingRevloansAddr))
438
436
  : new REVLoans{salt: _REVLOANS_SALT}({
439
437
  controller: core.controller,
438
+ terminal: core.terminal,
440
439
  suckerRegistry: suckers.registry,
441
440
  revId: feeProjectId,
442
441
  owner: loansOwner,
@@ -464,9 +463,11 @@ contract DeployScript is Script, Sphinx {
464
463
  creationCode: type(REVDeployer).creationCode,
465
464
  arguments: abi.encode(
466
465
  core.controller,
466
+ core.terminal,
467
+ IJBTerminal(address(routerTerminal.registry)),
467
468
  suckers.registry,
468
469
  feeProjectId,
469
- hook.hook_deployer,
470
+ hook.hookDeployer,
470
471
  croptop.publisher,
471
472
  IJBBuybackHookRegistry(address(buybackHook.registry)),
472
473
  revloans,
@@ -481,9 +482,11 @@ contract DeployScript is Script, Sphinx {
481
482
  ? REVDeployer(payable(_deployerAddr))
482
483
  : new REVDeployer{salt: _DEPLOYER_SALT}({
483
484
  controller: core.controller,
485
+ multiTerminal: core.terminal,
486
+ routerTerminalRegistry: IJBTerminal(address(routerTerminal.registry)),
484
487
  suckerRegistry: suckers.registry,
485
488
  feeRevnetId: feeProjectId,
486
- hookDeployer: hook.hook_deployer,
489
+ hookDeployer: hook.hookDeployer,
487
490
  publisher: croptop.publisher,
488
491
  buybackHook: IJBBuybackHookRegistry(address(buybackHook.registry)),
489
492
  loans: revloans,
@@ -506,7 +509,7 @@ contract DeployScript is Script, Sphinx {
506
509
  _basicDeployer.deployFor({
507
510
  revnetId: feeProjectId,
508
511
  configuration: feeProjectConfig.configuration,
509
- terminalConfigurations: feeProjectConfig.terminalConfigurations,
512
+ accountingContextsToAccept: feeProjectConfig.accountingContextsToAccept,
510
513
  suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration,
511
514
  tiered721HookConfiguration: feeProjectConfig.tiered721HookConfiguration,
512
515
  allowedPosts: feeProjectConfig.allowedPosts
@@ -12,6 +12,7 @@ import {IJBPermissioned} from "@bananapus/core-v6/src/interfaces/IJBPermissioned
12
12
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
13
13
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
14
14
  import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
15
+ import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
15
16
  import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
16
17
  import {JBSplitGroupIds} from "@bananapus/core-v6/src/libraries/JBSplitGroupIds.sol";
17
18
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
@@ -22,7 +23,6 @@ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
22
23
  import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
23
24
  import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
24
25
  import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
25
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
26
26
  import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
27
27
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
28
28
  import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
@@ -115,6 +115,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
115
115
  /// Participants can borrow up to the current cash out value of their tokens.
116
116
  IREVLoans public immutable override LOANS;
117
117
 
118
+ /// @notice The canonical terminal that holds revnet treasury balances.
119
+ IJBTerminal public immutable override MULTI_TERMINAL;
120
+
118
121
  /// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
119
122
  /// @dev Set as `dataHook` in each revnet's ruleset metadata. Implements `IJBRulesetDataHook` and `IJBCashOutHook`.
120
123
  address public immutable override OWNER;
@@ -128,6 +131,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
128
131
  /// @notice Manages the publishing of ERC-721 posts to revnet's tiered ERC-721 hooks.
129
132
  CTPublisher public immutable override PUBLISHER;
130
133
 
134
+ /// @notice The canonical router terminal registry installed as a project terminal for alternate payment routes.
135
+ /// @dev Deployments pass the router terminal registry here, not the underlying router terminal implementation.
136
+ IJBTerminal public immutable override ROUTER_TERMINAL_REGISTRY;
137
+
131
138
  /// @notice Deploys and tracks suckers for revnets.
132
139
  IJBSuckerRegistry public immutable override SUCKER_REGISTRY;
133
140
 
@@ -165,6 +172,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
165
172
  //*********************************************************************//
166
173
 
167
174
  /// @param controller The controller to use for launching and operating the Juicebox projects which will be revnets.
175
+ /// @param multiTerminal The canonical terminal that holds revnet treasury balances. Assumed to be a valid
176
+ /// deployment-time dependency.
177
+ /// @param routerTerminalRegistry The canonical router terminal registry used for alternate payment routes. Assumed
178
+ /// to be a valid deployment-time dependency.
168
179
  /// @param suckerRegistry The registry to use for deploying and tracking each revnet's suckers.
169
180
  /// @param feeRevnetId The Juicebox project ID of the revnet that will receive fees.
170
181
  /// @param hookDeployer The deployer to use for revnet's tiered ERC-721 hooks.
@@ -175,6 +186,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
175
186
  /// @param owner The runtime data hook contract (REVOwner) that handles pay and cash out callbacks.
176
187
  constructor(
177
188
  IJBController controller,
189
+ IJBTerminal multiTerminal,
190
+ IJBTerminal routerTerminalRegistry,
178
191
  IJBSuckerRegistry suckerRegistry,
179
192
  uint256 feeRevnetId,
180
193
  IJB721TiersHookDeployer hookDeployer,
@@ -190,6 +203,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
190
203
  DIRECTORY = controller.DIRECTORY();
191
204
  PROJECTS = controller.PROJECTS();
192
205
  PERMISSIONS = IJBPermissioned(address(CONTROLLER)).PERMISSIONS();
206
+ MULTI_TERMINAL = multiTerminal;
207
+ ROUTER_TERMINAL_REGISTRY = routerTerminalRegistry;
193
208
  SUCKER_REGISTRY = suckerRegistry;
194
209
  FEE_REVNET_ID = feeRevnetId;
195
210
  HOOK_DEPLOYER = hookDeployer;
@@ -259,49 +274,32 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
259
274
  }
260
275
 
261
276
  /// @notice Initialize fund access limits for the loan contract.
262
- /// @dev Returns an unlimited surplus allowance for each terminal+token pair derived from the terminal
263
- /// configurations.
264
- /// @param terminalConfigurations The terminals to set up for the revnet.
277
+ /// @dev Returns an unlimited surplus allowance for each accepted token in the canonical multi terminal.
278
+ /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
265
279
  /// @return fundAccessLimitGroups The fund access limit groups for the loans.
266
- function _makeLoanFundAccessLimits(JBTerminalConfig[] calldata terminalConfigurations)
280
+ function _makeLoanFundAccessLimits(JBAccountingContext[] calldata accountingContextsToAccept)
267
281
  internal
268
- pure
282
+ view
269
283
  returns (JBFundAccessLimitGroup[] memory fundAccessLimitGroups)
270
284
  {
271
- // Count the total number of accounting contexts across all terminals.
272
- uint256 count;
273
- for (uint256 i; i < terminalConfigurations.length;) {
274
- count += terminalConfigurations[i].accountingContextsToAccept.length;
275
- unchecked {
276
- ++i;
277
- }
278
- }
279
-
280
285
  // Initialize the fund access limit groups.
281
- fundAccessLimitGroups = new JBFundAccessLimitGroup[](count);
286
+ fundAccessLimitGroups = new JBFundAccessLimitGroup[](accountingContextsToAccept.length);
282
287
 
283
288
  // Set up the fund access limits.
284
- uint256 index;
285
- for (uint256 i; i < terminalConfigurations.length;) {
286
- JBTerminalConfig calldata terminalConfiguration = terminalConfigurations[i];
287
- for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length;) {
288
- JBAccountingContext calldata accountingContext = terminalConfiguration.accountingContextsToAccept[j];
289
-
290
- // Set up an unlimited allowance for the loan contract to use.
291
- JBCurrencyAmount[] memory loanAllowances = new JBCurrencyAmount[](1);
292
- loanAllowances[0] = JBCurrencyAmount({currency: accountingContext.currency, amount: type(uint224).max});
293
-
294
- // Set up the fund access limits for the loans.
295
- fundAccessLimitGroups[index++] = JBFundAccessLimitGroup({
296
- terminal: address(terminalConfiguration.terminal),
297
- token: accountingContext.token,
298
- payoutLimits: new JBCurrencyAmount[](0),
299
- surplusAllowances: loanAllowances
300
- });
301
- unchecked {
302
- ++j;
303
- }
304
- }
289
+ for (uint256 i; i < accountingContextsToAccept.length;) {
290
+ JBAccountingContext calldata accountingContext = accountingContextsToAccept[i];
291
+
292
+ // Set up an unlimited allowance for the loan contract to use.
293
+ JBCurrencyAmount[] memory loanAllowances = new JBCurrencyAmount[](1);
294
+ loanAllowances[0] = JBCurrencyAmount({currency: accountingContext.currency, amount: type(uint224).max});
295
+
296
+ // Set up the fund access limits for the loans.
297
+ fundAccessLimitGroups[i] = JBFundAccessLimitGroup({
298
+ terminal: address(MULTI_TERMINAL),
299
+ token: accountingContext.token,
300
+ payoutLimits: new JBCurrencyAmount[](0),
301
+ surplusAllowances: loanAllowances
302
+ });
305
303
  unchecked {
306
304
  ++i;
307
305
  }
@@ -353,6 +351,26 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
353
351
  });
354
352
  }
355
353
 
354
+ /// @notice Build the canonical terminal configuration used by every revnet.
355
+ /// @dev `MULTI_TERMINAL` accepts the revnet's accounting contexts and owns the treasury/loan accounting surface.
356
+ /// `ROUTER_TERMINAL_REGISTRY` is registered without accounting contexts so users can pay through the router path
357
+ /// without letting callers choose arbitrary terminals or loan sources. The deployer is constructed with distinct
358
+ /// addresses in the two slots; reusing the same address would be rejected by the directory's duplicate check.
359
+ /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
360
+ /// @return terminalConfigurations The canonical terminal configuration for the revnet.
361
+ function _makeTerminalConfigurations(JBAccountingContext[] calldata accountingContextsToAccept)
362
+ internal
363
+ view
364
+ returns (JBTerminalConfig[] memory terminalConfigurations)
365
+ {
366
+ terminalConfigurations = new JBTerminalConfig[](2);
367
+ terminalConfigurations[0] =
368
+ JBTerminalConfig({terminal: MULTI_TERMINAL, accountingContextsToAccept: accountingContextsToAccept});
369
+ terminalConfigurations[1] = JBTerminalConfig({
370
+ terminal: ROUTER_TERMINAL_REGISTRY, accountingContextsToAccept: new JBAccountingContext[](0)
371
+ });
372
+ }
373
+
356
374
  /// @notice Returns the permissions that the operator should have for a revnet.
357
375
  /// @param revnetId The ID of the revnet to look up.
358
376
  /// @return allOperatorPermissions The permissions the operator should have for the revnet,
@@ -512,7 +530,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
512
530
  /// REVDeployer, and the project becomes subject to immutable revnet rules. This cannot be undone.
513
531
  /// @param revnetId The ID of the Juicebox project to initialize as a revnet. Send 0 to deploy a new revnet.
514
532
  /// @param configuration Core revnet configuration. See `REVConfig`.
515
- /// @param terminalConfigurations The terminals to set up for the revnet.
533
+ /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
516
534
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
517
535
  /// @param tiered721HookConfiguration How to configure the tiered ERC-721 hook for the revnet.
518
536
  /// @param allowedPosts Restrictions on which croptop posts to allow on the revnet's ERC-721 tiers.
@@ -522,7 +540,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
522
540
  function deployFor(
523
541
  uint256 revnetId,
524
542
  REVConfig calldata configuration,
525
- JBTerminalConfig[] calldata terminalConfigurations,
543
+ JBAccountingContext[] calldata accountingContextsToAccept,
526
544
  REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
527
545
  REVDeploy721TiersHookConfig calldata tiered721HookConfiguration,
528
546
  REVCroptopAllowedPost[] calldata allowedPosts
@@ -542,7 +560,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
542
560
  revnetId: revnetId,
543
561
  shouldDeployNewRevnet: shouldDeployNewRevnet,
544
562
  configuration: configuration,
545
- terminalConfigurations: terminalConfigurations,
563
+ accountingContextsToAccept: accountingContextsToAccept,
546
564
  suckerDeploymentConfiguration: suckerDeploymentConfiguration,
547
565
  tiered721HookConfiguration: tiered721HookConfiguration,
548
566
  allowedPosts: allowedPosts
@@ -556,7 +574,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
556
574
  function deployFor(
557
575
  uint256 revnetId,
558
576
  REVConfig calldata configuration,
559
- JBTerminalConfig[] calldata terminalConfigurations,
577
+ JBAccountingContext[] calldata accountingContextsToAccept,
560
578
  REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
561
579
  )
562
580
  external
@@ -571,7 +589,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
571
589
  revnetId: revnetId,
572
590
  shouldDeployNewRevnet: shouldDeployNewRevnet,
573
591
  configuration: configuration,
574
- terminalConfigurations: terminalConfigurations,
592
+ accountingContextsToAccept: accountingContextsToAccept,
575
593
  suckerDeploymentConfiguration: suckerDeploymentConfiguration
576
594
  });
577
595
 
@@ -669,7 +687,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
669
687
  uint256 revnetId,
670
688
  bool shouldDeployNewRevnet,
671
689
  REVConfig calldata configuration,
672
- JBTerminalConfig[] calldata terminalConfigurations,
690
+ JBAccountingContext[] calldata accountingContextsToAccept,
673
691
  REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
674
692
  REVDeploy721TiersHookConfig memory tiered721HookConfiguration,
675
693
  REVCroptopAllowedPost[] memory allowedPosts
@@ -682,7 +700,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
682
700
  revnetId: revnetId,
683
701
  shouldDeployNewRevnet: shouldDeployNewRevnet,
684
702
  configuration: configuration,
685
- terminalConfigurations: terminalConfigurations,
703
+ accountingContextsToAccept: accountingContextsToAccept,
686
704
  suckerDeploymentConfiguration: suckerDeploymentConfiguration
687
705
  });
688
706
 
@@ -788,14 +806,14 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
788
806
  /// @param shouldDeployNewRevnet Whether the revnet ID was reserved by this deployment call, or the caller is
789
807
  /// converting an existing Juicebox project into a revnet.
790
808
  /// @param configuration Core revnet configuration. See `REVConfig`.
791
- /// @param terminalConfigurations The terminals to set up for the revnet.
809
+ /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
792
810
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
793
811
  /// @return encodedConfigurationHash A hash that represents the revnet's configuration.
794
812
  function _deployRevnetFor(
795
813
  uint256 revnetId,
796
814
  bool shouldDeployNewRevnet,
797
815
  REVConfig calldata configuration,
798
- JBTerminalConfig[] calldata terminalConfigurations,
816
+ JBAccountingContext[] calldata accountingContextsToAccept,
799
817
  REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
800
818
  )
801
819
  internal
@@ -804,9 +822,13 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
804
822
  // Normalize and encode the configurations.
805
823
  JBRulesetConfig[] memory rulesetConfigurations;
806
824
  (rulesetConfigurations, encodedConfigurationHash) = _makeRulesetConfigurations({
807
- revnetId: revnetId, configuration: configuration, terminalConfigurations: terminalConfigurations
825
+ revnetId: revnetId, configuration: configuration, accountingContextsToAccept: accountingContextsToAccept
808
826
  });
809
827
 
828
+ // Build the canonical terminal set from the deployer's immutable terminal choices.
829
+ JBTerminalConfig[] memory terminalConfigurations =
830
+ _makeTerminalConfigurations({accountingContextsToAccept: accountingContextsToAccept});
831
+
810
832
  address owner;
811
833
  if (!shouldDeployNewRevnet) {
812
834
  // Keep a reference to the Juicebox project's owner.
@@ -848,20 +870,14 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
848
870
  salt: keccak256(abi.encode(configuration.description.salt, encodedConfigurationHash, _msgSender()))
849
871
  });
850
872
 
851
- // Now that the ERC-20 exists, initialize buyback pools for each terminal token.
852
- for (uint256 i; i < terminalConfigurations.length;) {
853
- JBTerminalConfig calldata terminalConfiguration = terminalConfigurations[i];
854
- for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length;) {
855
- _tryInitializeBuybackPoolFor({
856
- revnetId: revnetId,
857
- terminalToken: terminalConfiguration.accountingContextsToAccept[j].token,
858
- terminalTokenDecimals: terminalConfiguration.accountingContextsToAccept[j].decimals,
859
- initialIssuance: configuration.stageConfigurations[0].initialIssuance
860
- });
861
- unchecked {
862
- ++j;
863
- }
864
- }
873
+ // Now that the ERC-20 exists, initialize buyback pools for each accepted treasury token.
874
+ for (uint256 i; i < accountingContextsToAccept.length;) {
875
+ _tryInitializeBuybackPoolFor({
876
+ revnetId: revnetId,
877
+ terminalToken: accountingContextsToAccept[i].token,
878
+ terminalTokenDecimals: accountingContextsToAccept[i].decimals,
879
+ initialIssuance: configuration.stageConfigurations[0].initialIssuance
880
+ });
865
881
  unchecked {
866
882
  ++i;
867
883
  }
@@ -926,13 +942,13 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
926
942
  /// configured immutably at deployment time.
927
943
  /// @param revnetId The ID of the revnet to build rulesets for.
928
944
  /// @param configuration The configuration containing the revnet's stages.
929
- /// @param terminalConfigurations The terminals to set up for the revnet.
945
+ /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
930
946
  /// @return rulesetConfigurations A list of ruleset configurations derived from the stages.
931
947
  /// @return encodedConfigurationHash A hash that represents the revnet's configuration.
932
948
  function _makeRulesetConfigurations(
933
949
  uint256 revnetId,
934
950
  REVConfig calldata configuration,
935
- JBTerminalConfig[] calldata terminalConfigurations
951
+ JBAccountingContext[] calldata accountingContextsToAccept
936
952
  )
937
953
  internal
938
954
  returns (JBRulesetConfig[] memory rulesetConfigurations, bytes32 encodedConfigurationHash)
@@ -954,19 +970,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
954
970
  configuration.description.salt
955
971
  );
956
972
 
957
- // Include terminal addresses in the hash so cross-chain expansions must use the same terminals.
958
- // Terminal addresses are deterministic across chains. Accounting contexts are excluded because
959
- // token addresses (e.g. USDC) legitimately differ per chain.
960
- for (uint256 i; i < terminalConfigurations.length;) {
961
- encodedConfiguration = abi.encode(encodedConfiguration, terminalConfigurations[i].terminal);
962
- unchecked {
963
- ++i;
964
- }
965
- }
966
-
967
973
  // Initialize fund access limit groups for the loan contract.
968
974
  JBFundAccessLimitGroup[] memory fundAccessLimitGroups =
969
- _makeLoanFundAccessLimits({terminalConfigurations: terminalConfigurations});
975
+ _makeLoanFundAccessLimits({accountingContextsToAccept: accountingContextsToAccept});
970
976
 
971
977
  // Track the previous stage's effective start time for ordering validation.
972
978
  // When stage 0 uses `startsAtOrAfter == 0`, the effective value is `block.timestamp`.