@bananapus/router-terminal-v6 0.0.58 → 0.0.60

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/README.md CHANGED
@@ -26,7 +26,7 @@ It can route through:
26
26
  - Uniswap V3 or V4 swaps
27
27
  - recursive Juicebox token cash outs when the input is itself a project token
28
28
 
29
- Projects can use the registry to choose, and optionally lock, a project-specific router terminal or fall back to the registry's default. The default is cohort-pinned: when the registry owner calls `setDefaultTerminal` again, the new default applies only to projects created after that call; existing projects continue to resolve against the default that was current when their ID range was active (see `defaultTerminalFor(projectId)`).
29
+ Projects can use the registry to choose, and optionally lock, a project-specific router terminal or fall back to the registry's default. The first `setDefaultTerminal` serves every project that already existed when it was called — including the canonical fee project (ID 1) — so those pre-existing projects can route tokens through the default. After that, the default is cohort-pinned: when the registry owner calls `setDefaultTerminal` again, the new default applies only to projects created after that call; existing projects continue to resolve against the default that was current when their ID range was active (see `defaultTerminalFor(projectId)`).
30
30
 
31
31
  Use this repo when UX requires "pay with many tokens, settle into the right one." Do not use it as a replacement for downstream terminal accounting or as an authoritative decimal source.
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/router-terminal-v6",
3
- "version": "0.0.58",
3
+ "version": "0.0.60",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -2,6 +2,7 @@
2
2
  pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBCashOutTerminal} from "@bananapus/core-v6/src/interfaces/IJBCashOutTerminal.sol";
5
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
5
6
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
7
  import {IJBPermitTerminal} from "@bananapus/core-v6/src/interfaces/IJBPermitTerminal.sol";
7
8
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
@@ -78,6 +79,7 @@ contract JBRouterTerminal is
78
79
  error JBRouterTerminal_CashOutDidNotDeliver(address sourceToken, address tokenToReclaim, uint256 cashOutCount);
79
80
  error JBRouterTerminal_CashOutLoopLimit(uint256 maxIterations);
80
81
  error JBRouterTerminal_InsufficientTwapHistory(address pool, uint256 twapWindow, uint256 minTwapWindow);
82
+ error JBRouterTerminal_ManipulationResistantQuoteRequired(PoolId poolId);
81
83
  error JBRouterTerminal_NoCashOutPath(uint256 sourceProjectId, uint256 destProjectId);
82
84
  error JBRouterTerminal_NoLiquidity(address pool, PoolId poolId);
83
85
  error JBRouterTerminal_NoMsgValueAllowed(uint256 value);
@@ -190,6 +192,19 @@ contract JBRouterTerminal is
190
192
  // ---------------------- internal stored properties ----------------- //
191
193
  //*********************************************************************//
192
194
 
195
+ //*********************************************************************//
196
+ // ------------------- transient stored properties ------------------- //
197
+ //*********************************************************************//
198
+
199
+ /// @notice Transient flag: when set, a quote-less swap may NOT fall back to a manipulable spot price — if the
200
+ /// selected pool exposes no manipulation-resistant oracle (a vanilla V4 pool) and the caller supplied no `pay`
201
+ /// quote, `_getV4SpotQuote` reverts and the caller must provide a quote.
202
+ /// @dev Set true by `addToBalanceOf` (whose swap has no downstream `minReturnedTokens` backstop) and false by
203
+ /// `pay` (whose top-level `minReturnedTokens` guards the entire routed result end-to-end). Read at quote time,
204
+ /// which is synchronous and always precedes the swap's pool callbacks, so it reflects the originating entrypoint.
205
+ /// Off-chain previews leave it at its default (false), so estimates still resolve. Transient, so it auto-clears.
206
+ bool internal transient _strictSwapQuote;
207
+
193
208
  //*********************************************************************//
194
209
  // -------------------------- constructor ---------------------------- //
195
210
  //*********************************************************************//
@@ -265,6 +280,11 @@ contract JBRouterTerminal is
265
280
  payable
266
281
  override
267
282
  {
283
+ // This leg settles via `addToBalanceOf`, which has no `minReturnedTokens` (or any downstream) backstop, so a
284
+ // quote-less swap must not silently price against a manipulable spot. Require a manipulation-resistant source
285
+ // (a canonical-hook V4 oracle, a V3 TWAP) or an explicit `pay` quote — see `_getV4SpotQuote`.
286
+ _strictSwapQuote = true;
287
+
268
288
  // Keep a reference to the terminal that will ultimately receive the routed funds.
269
289
  IJBTerminal destTerminal;
270
290
 
@@ -354,6 +374,10 @@ contract JBRouterTerminal is
354
374
  override
355
375
  returns (uint256 beneficiaryTokenCount)
356
376
  {
377
+ // The top-level `minReturnedTokens` guards the entire routed result end-to-end (a bad intermediate swap
378
+ // yields fewer final tokens and reverts here), so this leg may use the bounded spot-quote convenience path.
379
+ _strictSwapQuote = false;
380
+
357
381
  // Keep a reference to the terminal that will receive the routed payment.
358
382
  IJBTerminal destTerminal;
359
383
 
@@ -598,7 +622,9 @@ contract JBRouterTerminal is
598
622
  return pool;
599
623
  }
600
624
 
601
- /// @notice Public wrapper for V3-only _discoverPool, useful for off-chain queries.
625
+ /// @notice The best Uniswap V3 pool for a token pair, useful for off-chain queries.
626
+ /// @dev V3-only by design: returns the deepest V3 pool whenever one exists, independent of whether a deeper V4
627
+ /// pool exists for the same pair. Reverts only when no V3 pool exists at all.
602
628
  /// @param normalizedTokenIn The input token (wrapped if native).
603
629
  /// @param normalizedTokenOut The output token (wrapped if native).
604
630
  /// @return pool The V3 pool with the highest liquidity.
@@ -611,12 +637,10 @@ contract JBRouterTerminal is
611
637
  override
612
638
  returns (IUniswapV3Pool pool)
613
639
  {
614
- PoolInfo memory info =
615
- _discoverPool({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
616
- if (!info.isV4 && address(info.v3Pool) == address(0)) {
640
+ pool = _discoverV3Pool({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
641
+ if (address(pool) == address(0)) {
617
642
  revert JBRouterTerminal_NoPoolFound({tokenIn: normalizedTokenIn, tokenOut: normalizedTokenOut});
618
643
  }
619
- if (!info.isV4) pool = info.v3Pool;
620
644
  }
621
645
 
622
646
  /// @notice Preview a payment by simulating the router's routing logic in view context.
@@ -1180,6 +1204,8 @@ contract JBRouterTerminal is
1180
1204
  sourceProjectId: sourceProjectId, destProjectId: destProjectId, preferredToken: preferredToken
1181
1205
  });
1182
1206
 
1207
+ _claimRouterCreditsFor({projectId: sourceProjectId});
1208
+
1183
1209
  uint256 cashOutCount = amount;
1184
1210
  uint256 balanceBefore = _balanceOf({token: tokenToReclaim, account: address(this)});
1185
1211
 
@@ -1235,6 +1261,20 @@ contract JBRouterTerminal is
1235
1261
  revert JBRouterTerminal_CashOutLoopLimit({maxIterations: _MAX_CASHOUT_ITERATIONS});
1236
1262
  }
1237
1263
 
1264
+ /// @notice Converts this router's internal project-token credits into ERC-20s before a source cash-out.
1265
+ /// @dev Core burns holder credits before ERC-20 balances. Normalizing first keeps a source cash-out scoped to
1266
+ /// transferable token balances already visible to the router.
1267
+ /// @param projectId The Juicebox project whose tokens are being cashed out.
1268
+ function _claimRouterCreditsFor(uint256 projectId) internal {
1269
+ uint256 creditCount = TOKENS.creditBalanceOf({holder: address(this), projectId: projectId});
1270
+ if (creditCount == 0) return;
1271
+
1272
+ IJBController controller = IJBController(address(DIRECTORY.controllerOf(projectId)));
1273
+ controller.claimTokensFor({
1274
+ holder: address(this), projectId: projectId, tokenCount: creditCount, beneficiary: address(this)
1275
+ });
1276
+ }
1277
+
1238
1278
  /// @notice Convert tokenIn to tokenOut. No-op if same, wrap/unwrap for native/wrapped-native, or swap via Uniswap.
1239
1279
  /// @param tokenIn The token to convert from.
1240
1280
  /// @param tokenOut The token to convert into.
@@ -1893,9 +1933,49 @@ contract JBRouterTerminal is
1893
1933
  view
1894
1934
  returns (PoolInfo memory bestPool)
1895
1935
  {
1936
+ // Search V3 for the deepest pool and its liquidity.
1896
1937
  uint128 bestLiquidity;
1938
+ (bestPool.v3Pool, bestLiquidity) =
1939
+ _discoverV3PoolAndLiquidity({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
1940
+
1941
+ // Search V4, promoting a V4 pool only when it is deeper than the best V3 pool found above.
1942
+ bestPool = _discoverV4Pool({
1943
+ normalizedTokenIn: normalizedTokenIn,
1944
+ normalizedTokenOut: normalizedTokenOut,
1945
+ currentBestLiquidity: bestLiquidity,
1946
+ bestPool: bestPool
1947
+ });
1948
+ }
1949
+
1950
+ /// @notice Find the highest-liquidity Uniswap V3 pool for a token pair across the common fee tiers.
1951
+ /// @param normalizedTokenIn The input token (wrapped if native).
1952
+ /// @param normalizedTokenOut The output token (wrapped if native).
1953
+ /// @return pool The V3 pool with the highest liquidity, or address(0) if none exists.
1954
+ function _discoverV3Pool(
1955
+ address normalizedTokenIn,
1956
+ address normalizedTokenOut
1957
+ )
1958
+ internal
1959
+ view
1960
+ returns (IUniswapV3Pool pool)
1961
+ {
1962
+ (pool,) =
1963
+ _discoverV3PoolAndLiquidity({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
1964
+ }
1897
1965
 
1898
- // Search V3.
1966
+ /// @notice Find the highest-liquidity Uniswap V3 pool for a token pair along with its liquidity.
1967
+ /// @param normalizedTokenIn The input token (wrapped if native).
1968
+ /// @param normalizedTokenOut The output token (wrapped if native).
1969
+ /// @return pool The V3 pool with the highest liquidity, or address(0) if none exists.
1970
+ /// @return liquidity The liquidity of the returned pool, or 0 if none exists.
1971
+ function _discoverV3PoolAndLiquidity(
1972
+ address normalizedTokenIn,
1973
+ address normalizedTokenOut
1974
+ )
1975
+ internal
1976
+ view
1977
+ returns (IUniswapV3Pool pool, uint128 liquidity)
1978
+ {
1899
1979
  for (uint256 i; i < 4;) {
1900
1980
  address poolAddr = _getPool({tokenA: normalizedTokenIn, tokenB: normalizedTokenOut, fee: _feeTier(i)});
1901
1981
 
@@ -1908,23 +1988,15 @@ contract JBRouterTerminal is
1908
1988
 
1909
1989
  uint128 poolLiquidity = IUniswapV3Pool(poolAddr).liquidity();
1910
1990
 
1911
- if (poolLiquidity > bestLiquidity) {
1912
- bestLiquidity = poolLiquidity;
1913
- bestPool.v3Pool = IUniswapV3Pool(poolAddr);
1991
+ if (poolLiquidity > liquidity) {
1992
+ liquidity = poolLiquidity;
1993
+ pool = IUniswapV3Pool(poolAddr);
1914
1994
  }
1915
1995
 
1916
1996
  unchecked {
1917
1997
  ++i;
1918
1998
  }
1919
1999
  }
1920
-
1921
- // Search V4.
1922
- bestPool = _discoverV4Pool({
1923
- normalizedTokenIn: normalizedTokenIn,
1924
- normalizedTokenOut: normalizedTokenOut,
1925
- currentBestLiquidity: bestLiquidity,
1926
- bestPool: bestPool
1927
- });
1928
2000
  }
1929
2001
 
1930
2002
  /// @notice Search supported V4 pools and update the best pool candidate if a deeper V4 pool exists.
@@ -2247,9 +2319,12 @@ contract JBRouterTerminal is
2247
2319
  }
2248
2320
 
2249
2321
  /// @notice Get an automatic V4 quote with dynamic slippage.
2250
- /// @dev Prefers a hook-provided geomean/TWAP quote when available. Falls back to the pool's spot tick otherwise.
2251
- /// This fallback is an accepted product risk for programmatic integrations that cannot provide an external quote,
2252
- /// but it should be understood as a bounded-convenience path rather than a fully manipulation-resistant one.
2322
+ /// @dev Prefers a hook-provided geomean/TWAP quote when available. For a pool with no such oracle it falls back to
2323
+ /// the pool's spot tick ONLY when `_strictSwapQuote` is false (the `pay` leg backstopped end-to-end by
2324
+ /// `minReturnedTokens` and off-chain previews). When `_strictSwapQuote` is true (the `addToBalanceOf` and
2325
+ /// cash-out-swap legs, which have no downstream backstop) it instead reverts
2326
+ /// `JBRouterTerminal_ManipulationResistantQuoteRequired`, forcing the caller to supply a `pay` quote. The spot
2327
+ /// fallback is a bounded-convenience path, not a fully manipulation-resistant one.
2253
2328
  ///
2254
2329
  /// SECURITY NOTE: The spot price read from `poolManager.getSlot0(id)` is an instantaneous value
2255
2330
  /// that can be manipulated within the same block (e.g. via sandwich attacks or flash loans). Unlike V3 pools,
@@ -2274,11 +2349,15 @@ contract JBRouterTerminal is
2274
2349
  /// scales up to the 88% ceiling via a continuous sigmoid curve.
2275
2350
  /// 4. Pool discovery (`_discoverPool`) may select a V3 pool with TWAP if it has more liquidity, avoiding
2276
2351
  /// this V4 spot-price path altogether.
2352
+ /// 5. On legs with no downstream `minReturnedTokens` backstop (`addToBalanceOf`, and cash-out routes that
2353
+ /// settle via add-to-balance), `_strictSwapQuote` is set and this spot fallback is REFUSED: the call reverts
2354
+ /// `JBRouterTerminal_ManipulationResistantQuoteRequired` unless the caller supplied a `pay` quote.
2277
2355
  ///
2278
2356
  /// Despite these mitigations, the spot-based fallback does NOT provide full MEV protection. Integrators and
2279
2357
  /// front-ends should supply `pay` swap-quote metadata for V4 swaps whenever possible so the user's slippage
2280
2358
  /// tolerance reflects a recent, off-chain-verified price. When no external quote can be provided, this fallback
2281
- /// is still available as an accepted-risk convenience path.
2359
+ /// remains available as a bounded convenience path ONLY for the backstopped `pay` leg and off-chain previews; the
2360
+ /// un-backstopped `addToBalanceOf` and cash-out-swap legs refuse it (mitigation 5).
2282
2361
  /// @param key The V4 pool key describing the pool to quote against.
2283
2362
  /// @param normalizedTokenIn The normalized token to sell into the pool.
2284
2363
  /// @param normalizedTokenOut The normalized token to buy from the pool.
@@ -2331,8 +2410,14 @@ contract JBRouterTerminal is
2331
2410
  } catch {}
2332
2411
  }
2333
2412
 
2334
- // If no TWAP was available (no hook, or hook doesn't implement observe), use the instantaneous spot tick.
2413
+ // If no TWAP was available (no hook, or hook doesn't implement observe), there is no manipulation-resistant
2414
+ // price source for this pool. For legs with a downstream backstop (pay's `minReturnedTokens`) or for off-chain
2415
+ // previews, fall back to the instantaneous spot tick (a bounded-convenience path). For legs with NO downstream
2416
+ // backstop (`addToBalanceOf`, and cash-out routes that settle via add-to-balance), refuse: a self-referential
2417
+ // spot floor against an attacker-initialized vanilla pool offers no real protection, so require the caller to
2418
+ // supply a `pay` quote instead.
2335
2419
  if (!usedTwap) {
2420
+ if (_strictSwapQuote) revert JBRouterTerminal_ManipulationResistantQuoteRequired({poolId: id});
2336
2421
  (, tick,,) = _getSlot0(id);
2337
2422
  }
2338
2423
 
@@ -74,9 +74,11 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
74
74
  /// @notice The `PROJECTS.count()` snapshot at the moment of the last `setDefaultTerminal` call.
75
75
  /// Projects with `ID <= defaultTerminalProjectIdThreshold` (i.e. already existing when the most
76
76
  /// recent default was set) DO NOT pick up `defaultTerminal` on fall-through; instead they
77
- /// resolve against the historical entry in `_defaultTerminalHistory` that covers their ID.
77
+ /// resolve against the historical entry in `_defaultTerminalHistory` that covers their ID. The
78
+ /// first default's segment covers every project that already existed when it was set (so those
79
+ /// projects route through it), while later segments pin each outgoing default to its own cohort.
78
80
  /// This prevents the registry owner from silently rerouting payments for already-deployed
79
- /// projects via a default change.
81
+ /// projects via a later default change.
80
82
  uint256 public override defaultTerminalProjectIdThreshold;
81
83
 
82
84
  /// @notice Whether the terminal for a given project has been locked against future updates.
@@ -91,10 +93,11 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
91
93
  // --------------------- internal stored properties ------------------ //
92
94
  //*********************************************************************//
93
95
 
94
- /// @notice Append-only history of previous defaults captured at each `setDefaultTerminal` call. Each `segment[i]`
95
- /// applies to projectIds in `[<previous threshold> + 1, segment[i].maxProjectId]`. Resolution walks the array
96
- /// forward and returns the first segment whose `maxProjectId` covers the queried `projectId`. New defaults push
97
- /// the current default onto this history before updating `defaultTerminal`.
96
+ /// @notice Append-only history of default-terminal cohorts captured at each `setDefaultTerminal` call. Each
97
+ /// `segment[i]` applies to projectIds in `[<previous threshold> + 1, segment[i].maxProjectId]`. Resolution walks
98
+ /// the array forward and returns the first segment whose `maxProjectId` covers the queried `projectId`. The first
99
+ /// call records the projects that already existed mapped to the new default; later calls push the outgoing default
100
+ /// onto this history before updating `defaultTerminal`.
98
101
  DefaultTerminalSegment[] internal _defaultTerminalHistory;
99
102
 
100
103
  /// @notice The terminal explicitly configured for a project before default-terminal fallback is applied.
@@ -360,10 +363,10 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
360
363
  if (projectId > defaultTerminalProjectIdThreshold) return defaultTerminal;
361
364
 
362
365
  // Older projects walk the history. Each segment covers a half-open range
363
- // `(minProjectIdExclusive, maxProjectId]` exactly the cohort that was issued while that segment's terminal
364
- // was the active default. Projects whose IDs pre-date every recorded default (the cold-start cohort)
365
- // do not match any segment and resolve to `address(0)` preserving the documented "the default only applies
366
- // to projects created AFTER it was set" property at registry cold-start.
366
+ // `(minProjectIdExclusive, maxProjectId]`. The first segment covers every project that already existed when the
367
+ // first default was set (mapped to that first default, so they route through it); later segments each cover the
368
+ // cohort issued while their terminal was the active default. A project only resolves to `address(0)` here when
369
+ // no default has ever been set.
367
370
  uint256 len = _defaultTerminalHistory.length;
368
371
  for (uint256 i; i < len; ++i) {
369
372
  DefaultTerminalSegment storage segment = _defaultTerminalHistory[i];
@@ -388,7 +391,7 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
388
391
  }
389
392
 
390
393
  /// @notice Resolve the effective terminal for call paths that need to forward into a real terminal.
391
- /// @dev `terminalOf`/`defaultTerminalFor` may intentionally return zero for the cold-start cohort. Transactional
394
+ /// @dev `terminalOf`/`defaultTerminalFor` return zero only when no default has ever been set. Transactional
392
395
  /// and passthrough view paths must fail before accepting funds or calling address(0).
393
396
  /// @param projectId The project to resolve the terminal for.
394
397
  /// @return terminal The project-specific terminal or threshold-resolved default.
@@ -623,12 +626,14 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
623
626
 
624
627
  /// @notice Change the registry-wide default terminal for projects created AFTER this call.
625
628
  /// @dev Only the registry owner can call this. Automatically allowlists the new default.
626
- /// Existing projects (ID <= current `PROJECTS.count()` at call time) keep their historical
627
- /// default — the previous `defaultTerminal` is pushed onto `_defaultTerminalHistory` so
628
- /// fall-through resolution for those projects continues to return what was current when
629
- /// their cohort was last addressed. This eliminates the silent-reroute attack vector
630
- /// where the registry owner could redirect payments for already-deployed projects that
631
- /// never set an explicit `_terminalOf` override.
629
+ /// The very first call also maps every project that already existed onto the new default (via a
630
+ /// history segment) so those pre-existing projects including the canonical fee project (ID 1)
631
+ /// can route tokens through it. Existing projects (ID <= current `PROJECTS.count()` at call time)
632
+ /// keep their historical default on later changes the previous `defaultTerminal` is pushed onto
633
+ /// `_defaultTerminalHistory` so fall-through resolution for those projects continues to return what
634
+ /// was current when their cohort was last addressed. This means a later default change never
635
+ /// silently reroutes payments for already-deployed projects that never set an explicit
636
+ /// `_terminalOf` override.
632
637
  /// @param terminal The terminal to set as the default for future projects.
633
638
  function setDefaultTerminal(IJBTerminal terminal) external onlyOwner {
634
639
  if (address(terminal) == address(0)) revert JBRouterTerminalRegistry_ZeroAddress(address(terminal));
@@ -641,20 +646,20 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
641
646
  // default is what unconfigured (and all-future) projects will resolve to.
642
647
  _requireNonCircularTerminalFor({projectId: count, terminal: terminal});
643
648
 
644
- // Snapshot the OUTGOING default so projects whose IDs were issued while it was active keep resolving to it.
645
- // The segment encodes the half-open range `(prevThreshold, currentCount]` i.e. exactly the cohort that was
646
- // assigned the outgoing default at creation time. The first call ever has `defaultTerminal == 0` and nothing
647
- // to snapshot; in that case projects whose IDs already existed remain unaffected by the new default, keeping
648
- // the cold-start cohort outside any retroactive routing change.
649
- if (address(defaultTerminal) != address(0)) {
650
- _defaultTerminalHistory.push(
651
- DefaultTerminalSegment({
652
- minProjectIdExclusive: defaultTerminalProjectIdThreshold,
653
- maxProjectId: count,
654
- terminal: defaultTerminal
655
- })
656
- );
657
- }
649
+ // Record a history segment for the cohort whose IDs fall in the half-open range `(prevThreshold,
650
+ // currentCount]`. On the first call ever (`defaultTerminal == 0`) the segment maps the projects that already
651
+ // existed — including the canonical fee project (ID 1) onto the NEW default so they can route tokens
652
+ // through
653
+ // it instead of resolving to nothing. On every later call the segment instead pins the OUTGOING default to its
654
+ // own cohort, so projects whose IDs were issued while it was active keep resolving to it and a default change
655
+ // never silently reroutes an already-deployed project.
656
+ _defaultTerminalHistory.push(
657
+ DefaultTerminalSegment({
658
+ minProjectIdExclusive: defaultTerminalProjectIdThreshold,
659
+ maxProjectId: count,
660
+ terminal: address(defaultTerminal) != address(0) ? defaultTerminal : terminal
661
+ })
662
+ );
658
663
 
659
664
  defaultTerminal = terminal;
660
665
  defaultTerminalProjectIdThreshold = count;