@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 +132 -45
- package/foundry.toml +3 -4
- package/package.json +4 -3
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/RouterTerminalDeploymentLib.sol +1 -1
- package/src/JBRouterTerminal.sol +26 -45
- package/test/RouterTerminal.t.sol +5 -2
- package/test/RouterTerminalFeeCashOutFork.t.sol +1 -6
- package/test/RouterTerminalFork.t.sol +1 -7
- package/test/RouterTerminalSandwichFork.t.sol +1 -6
- package/test/regression/{L30_CashOutLoopLimit.t.sol → CashOutLoopLimit.t.sol} +1 -1
- package/test/regression/{L29_LockTerminalRace.t.sol → LockTerminalRace.t.sol} +1 -1
- package/test/regression/{M3_V4SpotPriceSlippage.t.sol → V4SpotPriceSlippage.t.sol} +1 -1
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.
|
|
147
|
-
2.
|
|
148
|
-
3.
|
|
149
|
-
4.
|
|
150
|
-
5.
|
|
151
|
-
6.
|
|
152
|
-
7.
|
|
153
|
-
8.
|
|
154
|
-
9.
|
|
155
|
-
10.
|
|
156
|
-
11.
|
|
157
|
-
12.
|
|
158
|
-
13.
|
|
159
|
-
14.
|
|
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
|
-
**
|
|
358
|
-
- `
|
|
359
|
-
- `
|
|
360
|
-
|
|
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:
|
|
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/
|
|
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
|
|
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
|
-
|
|
480
|
+
forge-std/=lib/forge-std/src/
|
|
441
481
|
```
|
|
442
482
|
|
|
443
|
-
|
|
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 `
|
|
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.
|
|
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.
|
|
21
|
-
"@
|
|
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",
|
package/script/Deploy.s.sol
CHANGED
|
@@ -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";
|
package/src/JBRouterTerminal.sol
CHANGED
|
@@ -223,7 +223,10 @@ contract JBRouterTerminal is
|
|
|
223
223
|
override
|
|
224
224
|
returns (PoolInfo memory pool)
|
|
225
225
|
{
|
|
226
|
-
|
|
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
|
|
396
|
-
address normalizedTokenOut = 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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
691
|
-
address nOut = 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
40
|
+
contract V4SpotPriceSlippageTest is Test {
|
|
41
41
|
SwapLibHarness lib;
|
|
42
42
|
|
|
43
43
|
/// @notice The same constants from JBSwapLib / JBRouterTerminal.
|