@bananapus/router-terminal-v6 0.0.9 → 0.0.10

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/STYLE_GUIDE.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  How we write Solidity and organize repos across the Juicebox V6 ecosystem. `nana-core-v6` is the gold standard — when in doubt, match what it does.
4
4
 
5
- **This repo's deviations:** `evm_version = 'cancun'`, `via_ir = true` in ci_sizes profile.
6
-
7
5
  ## File Organization
8
6
 
9
7
  ```
@@ -19,8 +17,6 @@ src/
19
17
 
20
18
  One contract/interface/struct/enum per file. Name the file after the type it contains.
21
19
 
22
- **Structs, enums, libraries, and interfaces always go in their subdirectories** (`src/structs/`, `src/enums/`, `src/libraries/`, `src/interfaces/`) — never inline in contract files or placed in `src/` root. This keeps type definitions discoverable and import paths consistent across repos.
23
-
24
20
  ## Pragma Versions
25
21
 
26
22
  ```solidity
@@ -108,14 +104,6 @@ contract JBExample is JBPermissioned, IJBExample {
108
104
  // -------------------------- constructor ---------------------------- //
109
105
  //*********************************************************************//
110
106
 
111
- //*********************************************************************//
112
- // ---------------------- receive / fallback ------------------------- //
113
- //*********************************************************************//
114
-
115
- //*********************************************************************//
116
- // --------------------------- modifiers ----------------------------- //
117
- //*********************************************************************//
118
-
119
107
  //*********************************************************************//
120
108
  // ---------------------- external transactions ---------------------- //
121
109
  //*********************************************************************//
@@ -143,28 +131,23 @@ contract JBExample is JBPermissioned, IJBExample {
143
131
  ```
144
132
 
145
133
  **Section order:**
146
- 1. `using` declarations
147
- 2. Custom errors
148
- 3. Public constants
149
- 4. Internal constants
150
- 5. Public immutable stored properties
151
- 6. Internal immutable stored properties
152
- 7. Public stored properties
153
- 8. Internal stored properties
154
- 9. Constructor
155
- 10. `receive` / `fallback`
156
- 11. Modifiers
157
- 12. External transactions
158
- 13. External views
159
- 14. Public transactions
160
- 15. Internal helpers
161
- 16. Internal views
162
- 17. Private helpers
134
+ 1. Custom errors
135
+ 2. Public constants
136
+ 3. Internal constants
137
+ 4. Public immutable stored properties
138
+ 5. Internal immutable stored properties
139
+ 6. Public stored properties
140
+ 7. Internal stored properties
141
+ 8. Constructor
142
+ 9. External transactions
143
+ 10. External views
144
+ 11. Public transactions
145
+ 12. Internal helpers
146
+ 13. Internal views
147
+ 14. Private helpers
163
148
 
164
149
  Functions are alphabetized within each section.
165
150
 
166
- **Events:** Events are declared in interfaces only, never in implementation contracts. Implementations inherit events from their interface and emit them unqualified. This keeps the ABI definition in one place and allows tests to use interface-qualified event expectations (e.g., `emit IJBController.LaunchProject(...)`).
167
-
168
151
  ## Interface Structure
169
152
 
170
153
  ```solidity
@@ -336,10 +319,6 @@ optimizer_runs = 200
336
319
  libs = ["node_modules", "lib"]
337
320
  fs_permissions = [{ access = "read-write", path = "./"}]
338
321
 
339
- [profile.ci_sizes]
340
- via_ir = true
341
- optimizer_runs = 200
342
-
343
322
  [fuzz]
344
323
  runs = 4096
345
324
 
@@ -354,10 +333,14 @@ multiline_func_header = "all"
354
333
  wrap_comments = true
355
334
  ```
356
335
 
357
- **Variations:**
358
- - `evm_version = 'cancun'` for repos using transient storage (buyback-hook, router-terminal, univ4-router)
359
- - `via_ir = true` for repos hitting stack-too-deep (buyback-hook, banny-retail, univ4-lp-split-hook, deploy-all)
360
- - `optimizer = false` only for deploy-all-v6 (stack-too-deep with optimization)
336
+ **Optional sections (add only when needed):**
337
+ - `[rpc_endpoints]` repos with fork tests. Maps named endpoints to env vars (e.g. `ethereum = "${RPC_ETHEREUM_MAINNET}"`).
338
+ - `[profile.ci_sizes]` only when CI needs different optimizer settings than defaults for the size check step (e.g. `optimizer_runs = 200` when the default profile uses a lower value).
339
+
340
+ **Common variations:**
341
+ - `via_ir = true` when hitting stack-too-deep
342
+ - `optimizer = false` when optimization causes stack-too-deep
343
+ - `optimizer_runs` reduced when deep struct nesting causes stack-too-deep at 200 runs
361
344
 
362
345
  ### CI Workflows
363
346
 
@@ -387,8 +370,10 @@ jobs:
387
370
  uses: foundry-rs/foundry-toolchain@v1
388
371
  - name: Run tests
389
372
  run: forge test --fail-fast --summary --detailed --skip "*/script/**"
373
+ env:
374
+ RPC_ETHEREUM_MAINNET: ${{ secrets.RPC_ETHEREUM_MAINNET }}
390
375
  - name: Check contract sizes
391
- run: FOUNDRY_PROFILE=ci_sizes forge build --sizes --skip "*/test/**" --skip "*/script/**" --skip SphinxUtils
376
+ run: forge build --sizes --skip "*/test/**" --skip "*/script/**" --skip SphinxUtils
392
377
  ```
393
378
 
394
379
  **lint.yml:**
@@ -410,11 +395,60 @@ jobs:
410
395
  run: forge fmt --check
411
396
  ```
412
397
 
398
+ **slither.yml** (repos with `src/` contracts only):
399
+ ```yaml
400
+ name: slither
401
+ on:
402
+ pull_request:
403
+ branches:
404
+ - main
405
+ push:
406
+ branches:
407
+ - main
408
+ jobs:
409
+ analyze:
410
+ runs-on: ubuntu-latest
411
+ steps:
412
+ - uses: actions/checkout@v4
413
+ with:
414
+ submodules: recursive
415
+ - uses: actions/setup-node@v4
416
+ with:
417
+ node-version: latest
418
+ - name: Install npm dependencies
419
+ run: npm install --omit=dev
420
+ - name: Install Foundry
421
+ uses: foundry-rs/foundry-toolchain@v1
422
+ - name: Run slither
423
+ uses: crytic/slither-action@v0.3.1
424
+ with:
425
+ slither-config: slither-ci.config.json
426
+ fail-on: medium
427
+ ```
428
+
429
+ **slither-ci.config.json:**
430
+ ```json
431
+ {
432
+ "detectors_to_exclude": "timestamp,uninitialized-local,naming-convention,solc-version,shadowing-local",
433
+ "exclude_informational": true,
434
+ "exclude_low": false,
435
+ "exclude_medium": false,
436
+ "exclude_high": false,
437
+ "disable_color": false,
438
+ "filter_paths": "(mocks/|test/|node_modules/|lib/)",
439
+ "legacy_ast": false
440
+ }
441
+ ```
442
+
443
+ **Variations:**
444
+ - Deployer-only repos (no `src/`, only `script/`) skip slither entirely — the action's internal `forge build` skips `test/` and `script/` by default, leaving nothing to compile.
445
+ - Use inline `// slither-disable-next-line <detector>` to suppress known false positives rather than adding to `detectors_to_exclude` in the config. The comment must be on the line immediately before the flagged expression.
446
+
413
447
  ### package.json
414
448
 
415
449
  ```json
416
450
  {
417
- "name": "@bananapus/router-terminal-v6",
451
+ "name": "@bananapus/package-name-v6",
418
452
  "version": "x.x.x",
419
453
  "license": "MIT",
420
454
  "repository": { "type": "git", "url": "git+https://github.com/Org/repo.git" },
@@ -434,13 +468,62 @@ jobs:
434
468
 
435
469
  ### remappings.txt
436
470
 
437
- Every repo has a `remappings.txt`. Minimal content:
471
+ Every repo has a `remappings.txt` as the **single source of truth** for import remappings. Never add remappings to `foundry.toml`.
472
+
473
+ **Principle:** Import paths in Solidity source must match npm package names exactly. With `libs = ["node_modules", "lib"]`, Foundry auto-resolves `@scope/package/path/File.sol` → `node_modules/@scope/package/path/File.sol`. No remapping needed for packages installed as real directories.
474
+
475
+ **Note:** Auto-resolution does **not** work for symlinked packages (e.g. npm workspace links). Workspace repos like `deploy-all-v6` and `nana-cli-v6` need explicit `@scope/package/=node_modules/@scope/package/` remappings for each symlinked dependency.
476
+
477
+ **Minimal content** (most repos):
438
478
 
439
479
  ```
440
- @sphinx-labs/contracts/=lib/sphinx/packages/contracts/contracts/foundry
480
+ forge-std/=lib/forge-std/src/
441
481
  ```
442
482
 
443
- Additional mappings as needed for repo-specific dependencies.
483
+ Only add extra remappings for:
484
+ - **`forge-std`** — always needed (git submodule with `src/` subdirectory)
485
+ - **Repo-specific `lib/` submodules** that have no npm package (e.g., `hookmate/=lib/hookmate/src/`)
486
+ - **Symlinked npm packages** — need explicit `@scope/package/=node_modules/@scope/package/` entries
487
+ - **Nested transitive deps** — e.g., `@chainlink/contracts-ccip/` nested inside `@bananapus/suckers-v6/node_modules/`
488
+
489
+ **Never add remappings for:**
490
+ - npm packages that match their import path and are installed as real directories — they auto-resolve
491
+ - Short-form aliases (e.g., `@bananapus/core/` → `@bananapus/core-v6/src/`) — fix the import instead
492
+ - Packages available via npm that are also git submodules — remove the submodule, use npm
493
+
494
+ **Import path convention:**
495
+
496
+ | Package | Import path | Resolves to |
497
+ |---------|------------|-------------|
498
+ | `@bananapus/core-v6` | `@bananapus/core-v6/src/libraries/JBConstants.sol` | `node_modules/@bananapus/core-v6/src/...` |
499
+ | `@openzeppelin/contracts` | `@openzeppelin/contracts/token/ERC20/IERC20.sol` | `node_modules/@openzeppelin/contracts/...` |
500
+ | `@uniswap/v4-core` | `@uniswap/v4-core/src/interfaces/IPoolManager.sol` | `node_modules/@uniswap/v4-core/src/...` |
501
+
502
+ ### Linting
503
+
504
+ Solar (Foundry's built-in linter) runs automatically during `forge build`. It scans all `.sol` files in `libs` directories, including `node_modules`.
505
+
506
+ **All test helpers must use relative imports** (e.g. `../../src/structs/JBRuleset.sol`), not bare `src/` imports. This ensures solar can resolve paths when the helper is consumed via npm in downstream repos.
507
+
508
+ ### Fork Tests
509
+
510
+ Fork tests use named RPC endpoints defined in `[rpc_endpoints]` of `foundry.toml`. No skip guards — fork tests should hard-fail if the RPC endpoint is unavailable, making CI failures explicit.
511
+
512
+ ```solidity
513
+ function setUp() public {
514
+ vm.createSelectFork("ethereum");
515
+ // ... setup code
516
+ }
517
+ ```
518
+
519
+ The endpoint name (e.g. `"ethereum"`) maps to an env var via `foundry.toml`:
520
+
521
+ ```toml
522
+ [rpc_endpoints]
523
+ ethereum = "${RPC_ETHEREUM_MAINNET}"
524
+ ```
525
+
526
+ For multi-chain fork tests, add all needed endpoints.
444
527
 
445
528
  ### Formatting
446
529
 
@@ -468,4 +551,8 @@ CI checks formatting via `forge fmt --check`.
468
551
 
469
552
  ### Contract Size Checks
470
553
 
471
- CI runs `FOUNDRY_PROFILE=ci_sizes forge build --sizes` to catch contracts approaching the 24KB limit. The `ci_sizes` profile uses `optimizer_runs = 200` for realistic size measurement even when the default profile has different optimizer settings.
554
+ CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
555
+
556
+ ## Repo-Specific Deviations
557
+
558
+ None. This repo follows the standard configuration exactly.
package/foundry.toml CHANGED
@@ -5,10 +5,6 @@ optimizer_runs = 200
5
5
  libs = ["node_modules", "lib"]
6
6
  fs_permissions = [{ access = "read-write", path = "./"}]
7
7
 
8
- [profile.ci_sizes]
9
- via_ir = true
10
- optimizer_runs = 200
11
-
12
8
  [fuzz]
13
9
  runs = 4096
14
10
 
@@ -21,3 +17,6 @@ fail_on_revert = false
21
17
  number_underscore = "thousands"
22
18
  multiline_func_header = "all"
23
19
  wrap_comments = true
20
+
21
+ [rpc_endpoints]
22
+ ethereum = "${RPC_ETHEREUM_MAINNET}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/router-terminal-v6",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,8 +17,9 @@
17
17
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-router-terminal-v6'"
18
18
  },
19
19
  "dependencies": {
20
- "@bananapus/core-v6": "^0.0.11",
21
- "@openzeppelin/contracts": "^5.2.0",
20
+ "@bananapus/core-v6": "^0.0.15",
21
+ "@bananapus/permission-ids-v6": "^0.0.7",
22
+ "@openzeppelin/contracts": "^5.6.1",
22
23
  "@uniswap/permit2": "github:Uniswap/permit2",
23
24
  "@uniswap/v3-core": "github:Uniswap/v3-core#0.8",
24
25
  "@uniswap/v3-periphery": "github:Uniswap/v3-periphery#0.8",
@@ -3,7 +3,7 @@ pragma solidity 0.8.26;
3
3
 
4
4
  import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
5
5
 
6
- import {Sphinx} from "@sphinx-labs/contracts/SphinxPlugin.sol";
6
+ import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
7
7
  import {Script} from "forge-std/Script.sol";
8
8
 
9
9
  import {JBRouterTerminal} from "../src/JBRouterTerminal.sol";
@@ -4,7 +4,7 @@ pragma solidity 0.8.26;
4
4
  import {stdJson} from "forge-std/Script.sol";
5
5
  import {Vm} from "forge-std/Vm.sol";
6
6
 
7
- import {SphinxConstants, NetworkInfo} from "@sphinx-labs/contracts/SphinxConstants.sol";
7
+ import {SphinxConstants, NetworkInfo} from "@sphinx-labs/contracts/contracts/foundry/SphinxConstants.sol";
8
8
 
9
9
  import {IJBRouterTerminal} from "../../src/interfaces/IJBRouterTerminal.sol";
10
10
  import {IJBRouterTerminalRegistry} from "../../src/interfaces/IJBRouterTerminalRegistry.sol";
@@ -223,7 +223,10 @@ contract JBRouterTerminal is
223
223
  override
224
224
  returns (PoolInfo memory pool)
225
225
  {
226
- return _discoverPool(normalizedTokenIn, normalizedTokenOut);
226
+ pool = _discoverPool(normalizedTokenIn, normalizedTokenOut);
227
+ if (!pool.isV4 && address(pool.v3Pool) == address(0)) {
228
+ revert JBRouterTerminal_NoPoolFound(normalizedTokenIn, normalizedTokenOut);
229
+ }
227
230
  }
228
231
 
229
232
  /// @notice Public wrapper for V3-only _discoverPool, useful for off-chain queries.
@@ -240,6 +243,9 @@ contract JBRouterTerminal is
240
243
  returns (IUniswapV3Pool pool)
241
244
  {
242
245
  PoolInfo memory info = _discoverPool(normalizedTokenIn, normalizedTokenOut);
246
+ if (!info.isV4 && address(info.v3Pool) == address(0)) {
247
+ revert JBRouterTerminal_NoPoolFound(normalizedTokenIn, normalizedTokenOut);
248
+ }
243
249
  if (!info.isV4) pool = info.v3Pool;
244
250
  }
245
251
 
@@ -277,6 +283,11 @@ contract JBRouterTerminal is
277
283
  return ERC2771Context._msgSender();
278
284
  }
279
285
 
286
+ /// @notice Normalize a token address by replacing the native token sentinel with WETH.
287
+ function _normalize(address token) internal view returns (address) {
288
+ return token == JBConstants.NATIVE_TOKEN ? address(WETH) : token;
289
+ }
290
+
280
291
  //*********************************************************************//
281
292
  // ---------------------- external transactions ---------------------- //
282
293
  //*********************************************************************//
@@ -392,8 +403,8 @@ contract JBRouterTerminal is
392
403
  (, address tokenIn, address tokenOut) = abi.decode(data, (uint256, address, address));
393
404
 
394
405
  // Normalize tokens (wrap native token if needed).
395
- address normalizedTokenIn = tokenIn == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenIn;
396
- address normalizedTokenOut = tokenOut == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenOut;
406
+ address normalizedTokenIn = _normalize(tokenIn);
407
+ address normalizedTokenOut = _normalize(tokenOut);
397
408
 
398
409
  // Verify caller is a legitimate pool via the factory.
399
410
  uint24 fee = IUniswapV3Pool(msg.sender).fee();
@@ -553,37 +564,9 @@ contract JBRouterTerminal is
553
564
  /// @param tokenB The other token in the pair.
554
565
  /// @return bestLiquidity The highest liquidity found, or 0 if no pool exists.
555
566
  function _bestPoolLiquidity(address tokenA, address tokenB) internal view returns (uint128 bestLiquidity) {
556
- // Search V3.
557
- for (uint256 i; i < 4; i++) {
558
- // slither-disable-next-line calls-loop
559
- address poolAddr = FACTORY.getPool(tokenA, tokenB, _FEE_TIERS[i]);
560
- if (poolAddr == address(0)) continue;
561
-
562
- // slither-disable-next-line calls-loop
563
- uint128 liquidity = IUniswapV3Pool(poolAddr).liquidity();
564
- if (liquidity > bestLiquidity) bestLiquidity = liquidity;
565
- }
566
-
567
- // Search V4 — reuse _discoverV4Pool with current best.
568
- PoolInfo memory v4Result = _discoverV4Pool(
569
- tokenA,
570
- tokenB,
571
- bestLiquidity,
572
- PoolInfo({
573
- isV4: false,
574
- v3Pool: IUniswapV3Pool(address(0)),
575
- v4Key: PoolKey({
576
- currency0: Currency.wrap(address(0)),
577
- currency1: Currency.wrap(address(0)),
578
- fee: 0,
579
- tickSpacing: 0,
580
- hooks: IHooks(address(0))
581
- })
582
- })
583
- );
584
- if (v4Result.isV4) {
585
- bestLiquidity = POOL_MANAGER.getLiquidity(v4Result.v4Key.toId());
586
- }
567
+ PoolInfo memory pool = _discoverPool(tokenA, tokenB);
568
+ if (pool.isV4) return POOL_MANAGER.getLiquidity(pool.v4Key.toId());
569
+ if (address(pool.v3Pool) != address(0)) return pool.v3Pool.liquidity();
587
570
  }
588
571
 
589
572
  /// @notice The maximum number of cashout iterations before reverting. Prevents infinite loops from circular
@@ -687,8 +670,8 @@ contract JBRouterTerminal is
687
670
  // Exact same token — no conversion needed.
688
671
  if (tokenIn == tokenOut) return amount;
689
672
 
690
- address nIn = tokenIn == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenIn;
691
- address nOut = tokenOut == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenOut;
673
+ address nIn = _normalize(tokenIn);
674
+ address nOut = _normalize(tokenOut);
692
675
 
693
676
  if (nIn == nOut) {
694
677
  // Same underlying token — just wrap or unwrap.
@@ -721,7 +704,7 @@ contract JBRouterTerminal is
721
704
  view
722
705
  returns (address tokenOut, IJBTerminal destTerminal)
723
706
  {
724
- address normalizedTokenIn = tokenIn == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenIn;
707
+ address normalizedTokenIn = _normalize(tokenIn);
725
708
  IJBTerminal[] memory terminals = DIRECTORY.terminalsOf(projectId);
726
709
 
727
710
  uint128 bestLiquidity;
@@ -733,8 +716,7 @@ contract JBRouterTerminal is
733
716
 
734
717
  for (uint256 j; j < contexts.length; j++) {
735
718
  address candidateToken = contexts[j].token;
736
- address normalizedCandidate =
737
- candidateToken == JBConstants.NATIVE_TOKEN ? address(WETH) : candidateToken;
719
+ address normalizedCandidate = _normalize(candidateToken);
738
720
 
739
721
  if (normalizedCandidate == normalizedTokenIn) continue;
740
722
 
@@ -801,10 +783,6 @@ contract JBRouterTerminal is
801
783
 
802
784
  // Search V4.
803
785
  bestPool = _discoverV4Pool(normalizedTokenIn, normalizedTokenOut, bestLiquidity, bestPool);
804
-
805
- if (!bestPool.isV4 && address(bestPool.v3Pool) == address(0)) {
806
- revert JBRouterTerminal_NoPoolFound(normalizedTokenIn, normalizedTokenOut);
807
- }
808
786
  }
809
787
 
810
788
  /// @notice Search V4 vanilla pools and update bestPool if a V4 pool has higher liquidity.
@@ -1169,7 +1147,7 @@ contract JBRouterTerminal is
1169
1147
  internal
1170
1148
  returns (uint256 amountOut)
1171
1149
  {
1172
- address normalizedTokenIn = tokenIn == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenIn;
1150
+ address normalizedTokenIn = _normalize(tokenIn);
1173
1151
 
1174
1152
  // Snapshot the input token balance before the swap to compute the leftover delta accurately.
1175
1153
  uint256 balanceBefore = IERC20(normalizedTokenIn).balanceOf(address(this));
@@ -1177,7 +1155,7 @@ contract JBRouterTerminal is
1177
1155
  // Execute the swap in a scoped block to manage stack depth.
1178
1156
  amountOut = _executeSwap({
1179
1157
  normalizedTokenIn: normalizedTokenIn,
1180
- normalizedTokenOut: tokenOut == JBConstants.NATIVE_TOKEN ? address(WETH) : tokenOut,
1158
+ normalizedTokenOut: _normalize(tokenOut),
1181
1159
  amount: amount,
1182
1160
  metadata: metadata,
1183
1161
  callbackData: abi.encode(projectId, tokenIn, tokenOut)
@@ -1231,6 +1209,9 @@ contract JBRouterTerminal is
1231
1209
  {
1232
1210
  // Discover the best pool across V3 and V4 fee tiers.
1233
1211
  pool = _discoverPool(normalizedTokenIn, normalizedTokenOut);
1212
+ if (!pool.isV4 && address(pool.v3Pool) == address(0)) {
1213
+ revert JBRouterTerminal_NoPoolFound(normalizedTokenIn, normalizedTokenOut);
1214
+ }
1234
1215
 
1235
1216
  // Check for a user-provided quote.
1236
1217
  (bool exists, bytes memory quote) =
@@ -94,9 +94,12 @@ contract RouterTerminalHarness is JBRouterTerminal {
94
94
  )
95
95
  external
96
96
  view
97
- returns (PoolInfo memory)
97
+ returns (PoolInfo memory pool)
98
98
  {
99
- return _discoverPool(normalizedTokenIn, normalizedTokenOut);
99
+ pool = _discoverPool(normalizedTokenIn, normalizedTokenOut);
100
+ if (!pool.isV4 && address(pool.v3Pool) == address(0)) {
101
+ revert JBRouterTerminal_NoPoolFound(normalizedTokenIn, normalizedTokenOut);
102
+ }
100
103
  }
101
104
  }
102
105
 
@@ -111,12 +111,7 @@ contract RouterTerminalFeeCashOutForkTest is Test {
111
111
  // ──────────────────────────
112
112
 
113
113
  function setUp() public {
114
- string memory rpcUrl = vm.envOr("RPC_ETHEREUM_MAINNET", string(""));
115
- if (bytes(rpcUrl).length == 0) {
116
- vm.skip(true);
117
- return;
118
- }
119
- vm.createSelectFork(rpcUrl, BLOCK_NUMBER);
114
+ vm.createSelectFork("ethereum", BLOCK_NUMBER);
120
115
 
121
116
  _deployJBCore();
122
117
 
@@ -94,13 +94,7 @@ contract RouterTerminalForkTest is Test {
94
94
  // ──────────────────────────────────────
95
95
 
96
96
  function setUp() public {
97
- // Skip fork tests in CI when no RPC URL is configured.
98
- string memory rpcUrl = vm.envOr("RPC_ETHEREUM_MAINNET", string(""));
99
- if (bytes(rpcUrl).length == 0) {
100
- vm.skip(true);
101
- return;
102
- }
103
- vm.createSelectFork(rpcUrl, BLOCK_NUMBER);
97
+ vm.createSelectFork("ethereum", BLOCK_NUMBER);
104
98
 
105
99
  // Deploy all JB core contracts fresh within the fork.
106
100
  _deployJBCore();
@@ -305,12 +305,7 @@ contract RouterTerminalSandwichForkTest is Test {
305
305
  //*********************************************************************//
306
306
 
307
307
  function setUp() public {
308
- string memory rpcUrl = vm.envOr("RPC_ETHEREUM_MAINNET", string(""));
309
- if (bytes(rpcUrl).length == 0) {
310
- vm.skip(true);
311
- return;
312
- }
313
- vm.createSelectFork(rpcUrl, BLOCK_NUMBER);
308
+ vm.createSelectFork("ethereum", BLOCK_NUMBER);
314
309
 
315
310
  _deployJBCore();
316
311
 
@@ -51,7 +51,7 @@ contract MockToken {
51
51
 
52
52
  /// @notice _cashOutLoop should revert with CashOutLoopLimit when circular token
53
53
  /// dependencies cause more than 20 iterations, instead of consuming all gas.
54
- contract L30_CashOutLoopLimitTest is Test {
54
+ contract CashOutLoopLimitTest is Test {
55
55
  JBRouterTerminal routerTerminal;
56
56
 
57
57
  IJBDirectory directory = IJBDirectory(makeAddr("directory"));
@@ -13,7 +13,7 @@ import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
13
13
  import {JBRouterTerminalRegistry} from "../../src/JBRouterTerminalRegistry.sol";
14
14
 
15
15
  /// @notice lockTerminalFor should revert if current terminal doesn't match expected.
16
- contract L29_LockTerminalRaceTest is Test {
16
+ contract LockTerminalRaceTest is Test {
17
17
  JBRouterTerminalRegistry registry;
18
18
 
19
19
  IJBPermissions permissions = IJBPermissions(makeAddr("permissions"));
@@ -37,7 +37,7 @@ contract SwapLibHarness {
37
37
  /// Because setting up a full V4 PoolManager in unit tests requires significant infrastructure, these tests
38
38
  /// exercise the slippage math in isolation via JBSwapLib, which is the same code path used by
39
39
  /// `_getV4SpotQuote` and `_getV3TwapQuote`.
40
- contract M3_V4SpotPriceSlippageTest is Test {
40
+ contract V4SpotPriceSlippageTest is Test {
41
41
  SwapLibHarness lib;
42
42
 
43
43
  /// @notice The same constants from JBSwapLib / JBRouterTerminal.