@bananapus/router-terminal-v6 0.0.59 → 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 +1 -1
- package/package.json +1 -1
- package/src/JBRouterTerminal.sol +90 -22
- package/src/JBRouterTerminalRegistry.sol +36 -31
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
package/src/JBRouterTerminal.sol
CHANGED
|
@@ -79,6 +79,7 @@ contract JBRouterTerminal is
|
|
|
79
79
|
error JBRouterTerminal_CashOutDidNotDeliver(address sourceToken, address tokenToReclaim, uint256 cashOutCount);
|
|
80
80
|
error JBRouterTerminal_CashOutLoopLimit(uint256 maxIterations);
|
|
81
81
|
error JBRouterTerminal_InsufficientTwapHistory(address pool, uint256 twapWindow, uint256 minTwapWindow);
|
|
82
|
+
error JBRouterTerminal_ManipulationResistantQuoteRequired(PoolId poolId);
|
|
82
83
|
error JBRouterTerminal_NoCashOutPath(uint256 sourceProjectId, uint256 destProjectId);
|
|
83
84
|
error JBRouterTerminal_NoLiquidity(address pool, PoolId poolId);
|
|
84
85
|
error JBRouterTerminal_NoMsgValueAllowed(uint256 value);
|
|
@@ -191,6 +192,19 @@ contract JBRouterTerminal is
|
|
|
191
192
|
// ---------------------- internal stored properties ----------------- //
|
|
192
193
|
//*********************************************************************//
|
|
193
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
|
+
|
|
194
208
|
//*********************************************************************//
|
|
195
209
|
// -------------------------- constructor ---------------------------- //
|
|
196
210
|
//*********************************************************************//
|
|
@@ -266,6 +280,11 @@ contract JBRouterTerminal is
|
|
|
266
280
|
payable
|
|
267
281
|
override
|
|
268
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
|
+
|
|
269
288
|
// Keep a reference to the terminal that will ultimately receive the routed funds.
|
|
270
289
|
IJBTerminal destTerminal;
|
|
271
290
|
|
|
@@ -355,6 +374,10 @@ contract JBRouterTerminal is
|
|
|
355
374
|
override
|
|
356
375
|
returns (uint256 beneficiaryTokenCount)
|
|
357
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
|
+
|
|
358
381
|
// Keep a reference to the terminal that will receive the routed payment.
|
|
359
382
|
IJBTerminal destTerminal;
|
|
360
383
|
|
|
@@ -599,7 +622,9 @@ contract JBRouterTerminal is
|
|
|
599
622
|
return pool;
|
|
600
623
|
}
|
|
601
624
|
|
|
602
|
-
/// @notice
|
|
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.
|
|
603
628
|
/// @param normalizedTokenIn The input token (wrapped if native).
|
|
604
629
|
/// @param normalizedTokenOut The output token (wrapped if native).
|
|
605
630
|
/// @return pool The V3 pool with the highest liquidity.
|
|
@@ -612,12 +637,10 @@ contract JBRouterTerminal is
|
|
|
612
637
|
override
|
|
613
638
|
returns (IUniswapV3Pool pool)
|
|
614
639
|
{
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (!info.isV4 && address(info.v3Pool) == address(0)) {
|
|
640
|
+
pool = _discoverV3Pool({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
|
|
641
|
+
if (address(pool) == address(0)) {
|
|
618
642
|
revert JBRouterTerminal_NoPoolFound({tokenIn: normalizedTokenIn, tokenOut: normalizedTokenOut});
|
|
619
643
|
}
|
|
620
|
-
if (!info.isV4) pool = info.v3Pool;
|
|
621
644
|
}
|
|
622
645
|
|
|
623
646
|
/// @notice Preview a payment by simulating the router's routing logic in view context.
|
|
@@ -1910,9 +1933,49 @@ contract JBRouterTerminal is
|
|
|
1910
1933
|
view
|
|
1911
1934
|
returns (PoolInfo memory bestPool)
|
|
1912
1935
|
{
|
|
1936
|
+
// Search V3 for the deepest pool and its liquidity.
|
|
1913
1937
|
uint128 bestLiquidity;
|
|
1938
|
+
(bestPool.v3Pool, bestLiquidity) =
|
|
1939
|
+
_discoverV3PoolAndLiquidity({normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut});
|
|
1914
1940
|
|
|
1915
|
-
// Search V3.
|
|
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
|
+
}
|
|
1965
|
+
|
|
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
|
+
{
|
|
1916
1979
|
for (uint256 i; i < 4;) {
|
|
1917
1980
|
address poolAddr = _getPool({tokenA: normalizedTokenIn, tokenB: normalizedTokenOut, fee: _feeTier(i)});
|
|
1918
1981
|
|
|
@@ -1925,23 +1988,15 @@ contract JBRouterTerminal is
|
|
|
1925
1988
|
|
|
1926
1989
|
uint128 poolLiquidity = IUniswapV3Pool(poolAddr).liquidity();
|
|
1927
1990
|
|
|
1928
|
-
if (poolLiquidity >
|
|
1929
|
-
|
|
1930
|
-
|
|
1991
|
+
if (poolLiquidity > liquidity) {
|
|
1992
|
+
liquidity = poolLiquidity;
|
|
1993
|
+
pool = IUniswapV3Pool(poolAddr);
|
|
1931
1994
|
}
|
|
1932
1995
|
|
|
1933
1996
|
unchecked {
|
|
1934
1997
|
++i;
|
|
1935
1998
|
}
|
|
1936
1999
|
}
|
|
1937
|
-
|
|
1938
|
-
// Search V4.
|
|
1939
|
-
bestPool = _discoverV4Pool({
|
|
1940
|
-
normalizedTokenIn: normalizedTokenIn,
|
|
1941
|
-
normalizedTokenOut: normalizedTokenOut,
|
|
1942
|
-
currentBestLiquidity: bestLiquidity,
|
|
1943
|
-
bestPool: bestPool
|
|
1944
|
-
});
|
|
1945
2000
|
}
|
|
1946
2001
|
|
|
1947
2002
|
/// @notice Search supported V4 pools and update the best pool candidate if a deeper V4 pool exists.
|
|
@@ -2264,9 +2319,12 @@ contract JBRouterTerminal is
|
|
|
2264
2319
|
}
|
|
2265
2320
|
|
|
2266
2321
|
/// @notice Get an automatic V4 quote with dynamic slippage.
|
|
2267
|
-
/// @dev Prefers a hook-provided geomean/TWAP quote when available.
|
|
2268
|
-
///
|
|
2269
|
-
///
|
|
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.
|
|
2270
2328
|
///
|
|
2271
2329
|
/// SECURITY NOTE: The spot price read from `poolManager.getSlot0(id)` is an instantaneous value
|
|
2272
2330
|
/// that can be manipulated within the same block (e.g. via sandwich attacks or flash loans). Unlike V3 pools,
|
|
@@ -2291,11 +2349,15 @@ contract JBRouterTerminal is
|
|
|
2291
2349
|
/// scales up to the 88% ceiling via a continuous sigmoid curve.
|
|
2292
2350
|
/// 4. Pool discovery (`_discoverPool`) may select a V3 pool with TWAP if it has more liquidity, avoiding
|
|
2293
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.
|
|
2294
2355
|
///
|
|
2295
2356
|
/// Despite these mitigations, the spot-based fallback does NOT provide full MEV protection. Integrators and
|
|
2296
2357
|
/// front-ends should supply `pay` swap-quote metadata for V4 swaps whenever possible so the user's slippage
|
|
2297
2358
|
/// tolerance reflects a recent, off-chain-verified price. When no external quote can be provided, this fallback
|
|
2298
|
-
///
|
|
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).
|
|
2299
2361
|
/// @param key The V4 pool key describing the pool to quote against.
|
|
2300
2362
|
/// @param normalizedTokenIn The normalized token to sell into the pool.
|
|
2301
2363
|
/// @param normalizedTokenOut The normalized token to buy from the pool.
|
|
@@ -2348,8 +2410,14 @@ contract JBRouterTerminal is
|
|
|
2348
2410
|
} catch {}
|
|
2349
2411
|
}
|
|
2350
2412
|
|
|
2351
|
-
// If no TWAP was available (no hook, or hook doesn't implement observe),
|
|
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.
|
|
2352
2419
|
if (!usedTwap) {
|
|
2420
|
+
if (_strictSwapQuote) revert JBRouterTerminal_ManipulationResistantQuoteRequired({poolId: id});
|
|
2353
2421
|
(, tick,,) = _getSlot0(id);
|
|
2354
2422
|
}
|
|
2355
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
|
|
95
|
-
/// applies to projectIds in `[<previous threshold> + 1, segment[i].maxProjectId]`. Resolution walks
|
|
96
|
-
/// forward and returns the first segment whose `maxProjectId` covers the queried `projectId`.
|
|
97
|
-
/// the
|
|
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]
|
|
364
|
-
// was
|
|
365
|
-
//
|
|
366
|
-
//
|
|
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`
|
|
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
|
-
///
|
|
627
|
-
///
|
|
628
|
-
///
|
|
629
|
-
/// their
|
|
630
|
-
///
|
|
631
|
-
///
|
|
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
|
-
//
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
// the
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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;
|