@bananapus/router-terminal-v6 0.0.25 → 0.0.27
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/ADMINISTRATION.md +54 -119
- package/ARCHITECTURE.md +70 -45
- package/AUDIT_INSTRUCTIONS.md +26 -23
- package/CHANGELOG.md +5 -0
- package/README.md +24 -4
- package/RISKS.md +10 -6
- package/SKILLS.md +9 -2
- package/USER_JOURNEYS.md +114 -29
- package/package.json +4 -4
- package/references/operations.md +5 -2
- package/references/runtime.md +4 -2
- package/src/JBPayRouteResolver.sol +393 -340
- package/src/JBRouterTerminal.sol +345 -129
- package/src/JBRouterTerminalRegistry.sol +12 -10
- package/src/interfaces/IJBForwardingTerminal.sol +2 -2
- package/src/interfaces/IJBRouterTerminalRegistry.sol +0 -5
- package/test/RouterTerminalBuybackHookFork.t.sol +1 -1
- package/test/RouterTerminalCashOutFork.t.sol +1 -1
- package/test/RouterTerminalERC2771.t.sol +4 -6
- package/test/RouterTerminalFeeCashOutFork.t.sol +1 -1
- package/test/RouterTerminalFork.t.sol +1 -1
- package/test/RouterTerminalMultihopFork.t.sol +1 -1
- package/test/RouterTerminalPreviewFork.t.sol +1 -1
- package/test/RouterTerminalSandwichFork.t.sol +1 -1
- package/test/TestAuditGaps.sol +8 -12
- package/test/audit/MultiHopForwardCycle.t.sol +185 -0
- package/test/audit/Permit2AllowanceFailed.t.sol +1 -1
- package/test/audit/PreviewPrimaryTerminalMismatch.t.sol +1 -1
- package/test/fork/V4QuoteAndSettlementFork.t.sol +1 -1
- package/test/regression/CashOutLoopLimit.t.sol +2 -3
package/ADMINISTRATION.md
CHANGED
|
@@ -1,145 +1,80 @@
|
|
|
1
1
|
# Administration
|
|
2
2
|
|
|
3
|
-
Admin privileges and their scope in nana-router-terminal-v6.
|
|
4
|
-
|
|
5
3
|
## At A Glance
|
|
6
4
|
|
|
7
5
|
| Item | Details |
|
|
8
|
-
|
|
9
|
-
| Scope |
|
|
10
|
-
|
|
|
11
|
-
| Highest-risk actions |
|
|
12
|
-
| Recovery posture | Unlocked projects can
|
|
13
|
-
|
|
14
|
-
## Routine Operations
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Scope | Global router terminal allowlisting and project-local terminal selection |
|
|
8
|
+
| Control posture | Mixed registry-owner and project-local delegated control |
|
|
9
|
+
| Highest-risk actions | Changing the default terminal, locking a project to the wrong terminal, and relying on misconfigured credit-cashout routing |
|
|
10
|
+
| Recovery posture | Unlocked projects can move; locked projects and immutable router wiring limit recovery |
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
- Change the default terminal only when you are comfortable affecting every project that still relies on fallback resolution.
|
|
18
|
-
- Encourage projects to lock their terminal only after verifying the resolved terminal address and expected routing behavior.
|
|
19
|
-
- For credit-cashout routing, verify the payer has granted the router terminal the needed `TRANSFER_CREDITS` permission before relying on that path.
|
|
12
|
+
## Purpose
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
`nana-router-terminal-v6` splits administration between a global registry and project-local terminal selection. The router logic itself is mostly immutable; the mutable control plane lives in `JBRouterTerminalRegistry`.
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
- The current default terminal cannot be disallowed; the registry owner must move the default first before removing the old implementation from the allowlist.
|
|
16
|
+
## Control Model
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
18
|
+
- `JBRouterTerminalRegistry` is globally `Ownable`.
|
|
19
|
+
- Project owners or delegates choose and can lock their router terminal.
|
|
20
|
+
- `JBRouterTerminal` has immutable routing dependencies and no owner-controlled strategy knobs.
|
|
21
|
+
- Some transaction paths depend on project-local `JBPermissions`, such as `TRANSFER_CREDITS`.
|
|
30
22
|
|
|
31
23
|
## Roles
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
### 2. Project Owner / SET_ROUTER_TERMINAL Delegate
|
|
40
|
-
|
|
41
|
-
**Contract**: `JBRouterTerminalRegistry`
|
|
42
|
-
**Assigned via**: Ownership of the project's ERC-721 NFT (via `JBProjects.ownerOf(projectId)`), or delegation via `JBPermissions` with permission ID `SET_ROUTER_TERMINAL` (30).
|
|
43
|
-
**Scope**: Per-project -- controls which router terminal a specific project uses, and can permanently lock that choice.
|
|
44
|
-
|
|
45
|
-
### 3. Credit Cashout Payer (Implicit)
|
|
46
|
-
|
|
47
|
-
**Contract**: `JBRouterTerminal`
|
|
48
|
-
**Required permission**: `TRANSFER_CREDITS` (permission ID 13) -- must be granted by the payer to the router terminal address for the source project via `JBPermissions`.
|
|
49
|
-
**Scope**: Per-transaction. Required only when using the `cashOutSource` metadata key to route payments through credit cashouts.
|
|
25
|
+
| Role | How Assigned | Scope | Notes |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| Registry owner | `Ownable(owner)` | Global | Controls allowlist and default terminal |
|
|
28
|
+
| Project owner | `JBProjects.ownerOf(projectId)` | Per project | May delegate `SET_ROUTER_TERMINAL` |
|
|
29
|
+
| Terminal delegate | `JBPermissions` grant | Per project | Usually `SET_ROUTER_TERMINAL` |
|
|
30
|
+
| Payer | Per transaction | Per payment | May need `TRANSFER_CREDITS` for credit-cashout routing |
|
|
50
31
|
|
|
51
|
-
##
|
|
32
|
+
## Privileged Surfaces
|
|
52
33
|
|
|
53
|
-
|
|
34
|
+
| Contract | Function | Who Can Call | Effect |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `JBRouterTerminalRegistry` | `allowTerminal(...)`, `disallowTerminal(...)`, `setDefaultTerminal(...)` | Registry owner | Controls global terminal availability and fallback |
|
|
37
|
+
| `JBRouterTerminalRegistry` | `setTerminalFor(...)` | Project owner or `SET_ROUTER_TERMINAL` delegate | Sets a project's explicit router terminal |
|
|
38
|
+
| `JBRouterTerminalRegistry` | `lockTerminalFor(...)` | Project owner or `SET_ROUTER_TERMINAL` delegate | Irreversibly locks the resolved terminal for a project |
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
2. If no explicit terminal is set, the registry's `defaultTerminal` is used.
|
|
57
|
-
3. If neither exists, the forwarding reverts.
|
|
40
|
+
## Immutable And One-Way
|
|
58
41
|
|
|
59
|
-
|
|
42
|
+
- `lockTerminalFor(...)` is irreversible.
|
|
43
|
+
- Constructor dependencies on the router are immutable.
|
|
44
|
+
- The current default terminal must move before the old default can be disallowed.
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
## Operational Notes
|
|
62
47
|
|
|
63
|
-
|
|
48
|
+
- Keep the terminal allowlist small and explicit.
|
|
49
|
+
- Change the default terminal carefully because unconfigured projects inherit it.
|
|
50
|
+
- Encourage projects to lock only after validating the resolved terminal and routing behavior.
|
|
51
|
+
- Review credit-cashout routing permissions before relying on that path operationally.
|
|
52
|
+
- Distinguish configuration risk from quote-quality risk: some route-discovery paths are best-effort, and some V4 quote paths can rely on weaker spot-style assumptions when robust history is unavailable.
|
|
64
53
|
|
|
65
|
-
|
|
54
|
+
## Machine Notes
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
| `setDefaultTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Sets the default terminal for all projects that have not set a project-specific terminal. Also auto-allows the terminal. |
|
|
72
|
-
| `setTerminalFor(projectId, terminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (30) | Per-project | Routes a specific project to a specific allowed terminal. Reverts if the terminal is not allowlisted or if the project's terminal is locked. |
|
|
73
|
-
| `lockTerminalFor(projectId, expectedTerminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (30) | Per-project | Permanently locks the terminal choice for a project. If no explicit terminal is set, snapshots the current default into `_terminalOf[projectId]`. Reverts with `TerminalMismatch` if the resolved terminal differs from `expectedTerminal` (race condition protection). **Irreversible.** |
|
|
56
|
+
- Do not treat registry ownership as authority to override locked project choice.
|
|
57
|
+
- Inspect `src/JBRouterTerminalRegistry.sol` and `src/JBRouterTerminal.sol` separately; they govern different control boundaries.
|
|
58
|
+
- If the effective terminal resolution and the documented default differ, stop and resolve the registry state before further actions.
|
|
59
|
+
- If route previews are falling back to weaker discovery or quote paths, do not describe the router as offering uniform oracle-quality guarantees across all pools and states.
|
|
74
60
|
|
|
75
|
-
|
|
61
|
+
## Recovery
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
63
|
+
- Unlocked projects can switch to another allowlisted terminal.
|
|
64
|
+
- Locked projects cannot be unlocked by the registry.
|
|
65
|
+
- Bad immutable router behavior means replacement infrastructure, not in-place edits.
|
|
66
|
+
- Quote-path weakness is usually mitigated operationally with better pool choice, external quoting, or replacement routing infrastructure, not with an owner-only hotfix.
|
|
81
67
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
The following values are set at deploy time and cannot be changed:
|
|
85
|
-
|
|
86
|
-
### JBRouterTerminal
|
|
87
|
-
|
|
88
|
-
| Property | Set At | Mutable? | Description |
|
|
89
|
-
|----------|--------|----------|-------------|
|
|
90
|
-
| `Trusted forwarder` | Constructor | No | ERC-2771 trusted forwarder for meta-transactions |
|
|
91
|
-
| `DIRECTORY` | Constructor | No | JB directory for terminal/controller lookups |
|
|
92
|
-
| `TOKENS` | Constructor | No | JB token manager for credit transfers and project token lookups |
|
|
93
|
-
| `FACTORY` | Constructor | No | Uniswap V3 factory for pool discovery and callback verification |
|
|
94
|
-
| `POOL_MANAGER` | Constructor | No | Uniswap V4 PoolManager (can be `address(0)` to disable V4) |
|
|
95
|
-
| `PERMIT2` | Constructor | No | Permit2 contract for gasless approvals |
|
|
96
|
-
| `WETH` | Constructor | No | Wrapped ETH contract |
|
|
97
|
-
| `BUYBACK_HOOK` | Constructor | No | Canonical buyback hook whose metadata this router understands |
|
|
98
|
-
| `UNIV4_HOOK` | Constructor | No | Canonical Uniswap V4 hook address searched during hooked-pool discovery |
|
|
99
|
-
| `DEFAULT_TWAP_WINDOW` | Compile-time constant | No | 10 minutes (600 seconds) |
|
|
100
|
-
| `SLIPPAGE_DENOMINATOR` | Compile-time constant | No | 10,000 (basis points) |
|
|
101
|
-
| `_FEE_TIERS` | Storage (initialized) | No | `[3000, 500, 10000, 100]` -- V3 fee tiers |
|
|
102
|
-
| `_V4_FEES` / `_V4_TICK_SPACINGS` | Storage (initialized) | No | V4 pool parameters |
|
|
103
|
-
| `_MAX_CASHOUT_ITERATIONS` | Compile-time constant | No | 20 iterations |
|
|
104
|
-
|
|
105
|
-
### JBRouterTerminalRegistry
|
|
106
|
-
|
|
107
|
-
| Property | Set At | Mutable? | Description |
|
|
108
|
-
|----------|--------|----------|-------------|
|
|
109
|
-
| `PERMISSIONS` | Constructor | No | JB permissions registry for permission checks |
|
|
110
|
-
| `Trusted forwarder` | Constructor | No | ERC-2771 trusted forwarder for meta-transactions |
|
|
111
|
-
| `PROJECTS` | Constructor | No | JB project NFT registry |
|
|
112
|
-
| `PERMIT2` | Constructor | No | Permit2 contract for gasless approvals |
|
|
113
|
-
|
|
114
|
-
### JBSwapLib
|
|
68
|
+
## Admin Boundaries
|
|
115
69
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| `SIGMOID_K` | 5e16 | Sigmoid curve shape parameter |
|
|
70
|
+
- The registry owner cannot unlock or override a locked project terminal.
|
|
71
|
+
- Project operators cannot set a terminal that the registry does not allow.
|
|
72
|
+
- Router maintainers cannot tune routing heuristics or constructor immutables post-deploy.
|
|
73
|
+
- There is no pause surface in the registry or router.
|
|
121
74
|
|
|
122
|
-
##
|
|
75
|
+
## Source Map
|
|
123
76
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
- **Override a project's locked terminal choice.** Once locked, the terminal is permanently stored in `_terminalOf[projectId]` and `hasLockedTerminal[projectId]` is permanently `true`.
|
|
129
|
-
- **Force a project to use a specific terminal.** Only the project owner (or delegate) can call `setTerminalFor`.
|
|
130
|
-
- **Access project funds.** The registry is a pass-through; it holds funds transiently during forwarding only.
|
|
131
|
-
- **Modify swap parameters, slippage, or routing logic.** These are controlled by the `JBRouterTerminal` contract, not the registry.
|
|
132
|
-
- **Pause payments.** There is no pause mechanism.
|
|
133
|
-
|
|
134
|
-
### Router Terminal Maintainers Cannot:
|
|
135
|
-
- **Modify swap slippage parameters.** The TWAP window, sigmoid constants, fee tiers, and max slippage are all immutable.
|
|
136
|
-
- **Redirect funds.** The terminal is stateless between transactions and routes payments to whichever terminal the JB directory specifies.
|
|
137
|
-
- **Change the Uniswap factory or PoolManager.** These are immutable constructor parameters.
|
|
138
|
-
- **Override user-provided quotes.** The `quoteForSwap` metadata is decoded and used as-is.
|
|
139
|
-
- **Prevent specific users from paying.** There is no blocklist mechanism.
|
|
140
|
-
- **Extract stuck funds.** There is no sweep or rescue function. The terminal relies on completing all token movements within a single transaction.
|
|
141
|
-
|
|
142
|
-
### Project Owner / Delegate Cannot:
|
|
143
|
-
- **Change the terminal after locking.** The `setTerminalFor` function reverts with `TerminalLocked` if the terminal is locked.
|
|
144
|
-
- **Set a disallowed terminal.** `setTerminalFor` reverts with `TerminalNotAllowed` if the terminal is not on the registry owner's allowlist.
|
|
145
|
-
- **Affect other projects' routing.** Permission checks are scoped to the specific `projectId`.
|
|
77
|
+
- `src/JBRouterTerminalRegistry.sol`
|
|
78
|
+
- `src/JBRouterTerminal.sol`
|
|
79
|
+
- `src/JBPayRouteResolver.sol`
|
|
80
|
+
- `test/`
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,67 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`nana-router-terminal-v6` lets a payer fund a Juicebox project with a token the project does not directly accept. It discovers the destination token, unwraps or wraps native assets when needed,
|
|
6
|
-
The router is intentionally heuristic: it does not exhaustively search every viable pool for the absolute best execution price.
|
|
5
|
+
`nana-router-terminal-v6` lets a payer fund a Juicebox project with a token the project does not directly accept. It discovers the destination token, unwraps or wraps native assets when needed, can recursively cash out upstream JB project tokens, and swaps through bounded Uniswap V3 or V4 routes before forwarding value to the destination terminal.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
The router is intentionally heuristic. It does not search every possible route for a globally optimal price.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
- It owns routing, swapping, quoting, and refund behavior.
|
|
12
|
-
- Final accounting still occurs at the destination terminal that actually accepts the routed token.
|
|
13
|
-
- Pool selection is optimized for simple, bounded route discovery, not full best-execution search across all candidate pools.
|
|
9
|
+
## System Overview
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
`JBRouterTerminal` is a terminal-shaped adapter, not an accounting source of truth. `JBRouterTerminalRegistry` is both a registry and a stable project-facing proxy surface: projects can point at the registry while the registry resolves, and can later lock, the actual router terminal implementation to use. `JBPayRouteResolver` expands preview candidates without forcing the main router contract to carry all preview complexity inline. Final accounting still occurs in the downstream terminal selected through `nana-core-v6`.
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
| --- | --- |
|
|
19
|
-
| `JBRouterTerminal` | Source-token intake, route discovery, swapping, and forwarding |
|
|
20
|
-
| `JBRouterTerminalRegistry` | Project-level selection and locking of router terminal instances |
|
|
21
|
-
| `JBPayRouteResolver` | Helper contract that evaluates pay-route preview candidates without bloating router runtime size |
|
|
22
|
-
| `JBSwapLib` | Pool discovery, quoting, and slippage helpers |
|
|
23
|
-
| `PoolInfo`, `CashOutPathCandidates`, and interfaces | Typed routing metadata and registry/payer integration surfaces |
|
|
13
|
+
## Core Invariants
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
- The router's own accounting context is synthetic and must not be treated as the project ledger.
|
|
16
|
+
- Preview route discovery and live execution must stay aligned.
|
|
17
|
+
- Refund behavior is part of correctness, not UX.
|
|
18
|
+
- Registry locking prevents silent migration to untrusted router implementations.
|
|
19
|
+
- Final terminal-facing ERC-20 hops only support standard, non-lossy transfers.
|
|
20
|
+
- Recursive project-token cashout routing is intentionally bounded; non-converging paths should fail instead of looping.
|
|
21
|
+
- Caller reclaim minima only apply to the first cashout hop, because later hops may change token units.
|
|
22
|
+
- Circular `router -> registry -> same router` forwarding remains blocked in the registry.
|
|
23
|
+
|
|
24
|
+
## Modules
|
|
25
|
+
|
|
26
|
+
| Module | Responsibility | Notes |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `JBRouterTerminal` | Intake, route discovery, swap execution, forwarding, and refunds | Main runtime surface |
|
|
29
|
+
| `JBRouterTerminalRegistry` | Project-level router selection, locking, and proxy forwarding to the resolved router terminal | Governance, safety, and proxy surface |
|
|
30
|
+
| `JBPayRouteResolver` | Preview candidate evaluation | Helper to keep runtime size bounded |
|
|
31
|
+
| `JBSwapLib` and routing structs | Pool discovery, quoting, and route metadata | Shared routing logic |
|
|
32
|
+
|
|
33
|
+
## Trust Boundaries
|
|
34
|
+
|
|
35
|
+
- Final accounting remains in the downstream terminal selected through `JBDirectory`.
|
|
36
|
+
- The router trusts Uniswap V3, Uniswap V4, Permit2, and optional payer trackers for routing-side behavior.
|
|
37
|
+
- Fee-on-transfer tokens are only tolerated on ingress where received-balance deltas can be reconciled.
|
|
38
|
+
- The registry is trusted to resolve and forward into the intended router terminal implementation for a project.
|
|
39
|
+
|
|
40
|
+
## Critical Flows
|
|
41
|
+
|
|
42
|
+
### Route And Pay
|
|
26
43
|
|
|
27
44
|
```text
|
|
28
45
|
router pay call
|
|
29
46
|
-> accept native, ERC-20, or JB-token-like input
|
|
30
47
|
-> if input is a project token, recursively cash it out first
|
|
31
|
-
-> resolve the destination token the project
|
|
32
|
-
->
|
|
33
|
-
-> execute the route and forward the result to the
|
|
34
|
-
->
|
|
48
|
+
-> resolve the destination token the project terminal actually accepts
|
|
49
|
+
-> choose the best direct, wrap/unwrap, or swap path under the router's bounded candidate-discovery heuristic
|
|
50
|
+
-> execute the route and forward the result to the downstream terminal
|
|
51
|
+
-> refund leftover input when possible
|
|
35
52
|
```
|
|
36
53
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
- The router's own accounting context is synthetic. Consumers should not treat it as the source of truth for project accounting.
|
|
40
|
-
- Pool discovery and quote logic must stay aligned between preview and execution paths.
|
|
41
|
-
- Refund resolution is part of correctness, not ergonomics. Partial fills without correct refunds create value leaks.
|
|
42
|
-
- Registry locking is a security feature; it prevents projects from being silently switched to untrusted router implementations.
|
|
43
|
-
- Final forwarded ERC-20 hops are only supported for standard tokens whose destination-terminal pull transfers the full nominal amount without transfer fees or burns.
|
|
44
|
-
- Circular `router -> registry -> same router` forwarding is blocked in the registry, not by teaching the router about registry internals.
|
|
54
|
+
## Accounting Model
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
The router does not own project balances. It owns transient route accounting: input reconciliation, swap execution, forwarded amount, and refund resolution.
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
- Native-asset handling and refund handling are the most failure-prone parts of the implementation.
|
|
50
|
-
- Liquidity discovery across V3 and V4 is simple to describe but easy to desynchronize between preview and live execution.
|
|
51
|
-
- V4 discovery intentionally searches both vanilla pools and pools using the canonical `UNIV4_HOOK`, because buyback-hook and LP-split integrations rely on that hook-backed oracle path.
|
|
52
|
-
- “Best route” in this system means the best route under the router's discovery heuristic, not a guarantee of globally optimal output across every live pool.
|
|
53
|
-
- Fee-on-transfer or otherwise lossy ERC-20s are only tolerated on ingress where the router can reconcile the received balance delta. They are rejected on the router's final terminal-facing hop. The registry does not perform independent receipt enforcement; it relies on the downstream router terminal to reject lossy transfers.
|
|
54
|
-
- The preview candidate fanout lives in `JBPayRouteResolver`, but downstream `previewPayFor(...)` calls still originate from the router so payer-sensitive previews match execution context.
|
|
58
|
+
Preview and execution share the same conceptual route shape: optional recursive cashout first, then destination-token resolution, then final conversion and forwarding. `JBPayRouteResolver` narrows candidate tokens and usable external terminals so the live router does not need to brute-force every possibility inline.
|
|
55
59
|
|
|
56
|
-
##
|
|
60
|
+
## Security Model
|
|
57
61
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
62
|
+
- Native-asset handling and refunds are the most failure-prone paths.
|
|
63
|
+
- V3 and V4 discovery must stay synchronized between preview and live execution.
|
|
64
|
+
- V4 discovery intentionally considers both vanilla pools and pools using the canonical `UNIV4_HOOK`.
|
|
65
|
+
- The router's “best route” claim is only as strong as its bounded discovery set and external-terminal safety checks. It is not a global optimizer.
|
|
66
|
+
- Recursive cashout behavior, preferred-token handling, and one-shot source overrides are tightly coupled; changing one can silently desynchronize preview from execution.
|
|
67
|
+
- “Best route” means best under the bounded discovery heuristic, not globally optimal routing.
|
|
61
68
|
|
|
62
69
|
## Safe Change Guide
|
|
63
70
|
|
|
64
|
-
- Keep route
|
|
65
|
-
- Be
|
|
66
|
-
- If
|
|
67
|
-
-
|
|
68
|
-
-
|
|
71
|
+
- Keep route discovery and route execution semantics paired.
|
|
72
|
+
- Be conservative with native wrapping, unwrapping, and refund behavior.
|
|
73
|
+
- If recursive cash-out logic changes, review hop limits and failure handling together.
|
|
74
|
+
- If metadata semantics change, re-check first-hop reclaim minima, one-shot source overrides, and preferred-token routing together.
|
|
75
|
+
- Do not turn the router into a persistent treasury layer.
|
|
76
|
+
|
|
77
|
+
## Canonical Checks
|
|
78
|
+
|
|
79
|
+
- bounded recursive cash-out behavior:
|
|
80
|
+
`test/regression/CashOutLoopLimit.t.sol`
|
|
81
|
+
- preview versus execution terminal alignment:
|
|
82
|
+
`test/audit/PreviewPrimaryTerminalMismatch.t.sol`
|
|
83
|
+
- router-wide route and refund invariants:
|
|
84
|
+
`test/invariant/RouterTerminalInvariant.t.sol`
|
|
85
|
+
|
|
86
|
+
## Source Map
|
|
87
|
+
|
|
88
|
+
- `src/JBRouterTerminal.sol`
|
|
89
|
+
- `src/JBRouterTerminalRegistry.sol`
|
|
90
|
+
- `src/JBPayRouteResolver.sol`
|
|
91
|
+
- `test/regression/CashOutLoopLimit.t.sol`
|
|
92
|
+
- `test/audit/PreviewPrimaryTerminalMismatch.t.sol`
|
|
93
|
+
- `test/invariant/RouterTerminalInvariant.t.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repo accepts one token and routes value into whatever token a destination project actually accepts. Audit it as a stateless router whose mistakes show up as lost value, bad slippage control, or wrong-route accounting.
|
|
4
4
|
|
|
5
|
-
## Objective
|
|
5
|
+
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
8
|
- route user funds through an incorrect pool or protocol path
|
|
@@ -25,7 +25,13 @@ Key dependencies:
|
|
|
25
25
|
- `nana-core-v6`
|
|
26
26
|
- Uniswap V3 and V4 integration surfaces
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Start Here
|
|
29
|
+
|
|
30
|
+
1. `src/JBRouterTerminal.sol`
|
|
31
|
+
2. `src/JBRouterTerminalRegistry.sol`
|
|
32
|
+
3. `src/libraries/JBSwapLib.sol`
|
|
33
|
+
|
|
34
|
+
## Security Model
|
|
29
35
|
|
|
30
36
|
The router terminal:
|
|
31
37
|
- discovers what token a project’s terminal accepts
|
|
@@ -35,6 +41,22 @@ The router terminal:
|
|
|
35
41
|
|
|
36
42
|
The registry chooses which router terminal instance a project uses and whether that choice is locked.
|
|
37
43
|
|
|
44
|
+
## Roles And Privileges
|
|
45
|
+
|
|
46
|
+
| Role | Powers | How constrained |
|
|
47
|
+
|------|--------|-----------------|
|
|
48
|
+
| User or relayer | Initiate routed payment with beneficiary and slippage intent | Must receive exact refund semantics requested |
|
|
49
|
+
| Registry controller | Set default or allowed router terminals | Must not redirect projects unexpectedly |
|
|
50
|
+
| Router terminal | Hold funds only transiently during routing | Must not retain leftovers across flows |
|
|
51
|
+
|
|
52
|
+
## Integration Assumptions
|
|
53
|
+
|
|
54
|
+
| Dependency | Assumption | What breaks if wrong |
|
|
55
|
+
|------------|------------|----------------------|
|
|
56
|
+
| `nana-core-v6` | Terminal discovery and pay semantics are accurate | Routed value lands in the wrong place |
|
|
57
|
+
| Uniswap V3 and V4 | Callback settlement and pool discovery are authentic | Slippage and final forwarded amount diverge |
|
|
58
|
+
| Permit2 | Allowances and deadlines reflect user intent | Unauthorized transfer or stuck routing behavior |
|
|
59
|
+
|
|
38
60
|
## Critical Invariants
|
|
39
61
|
|
|
40
62
|
1. User intent is preserved
|
|
@@ -49,16 +71,7 @@ The quoted path, callback settlement, and final forwarded amount must all descri
|
|
|
49
71
|
4. Registry controls stay narrow
|
|
50
72
|
Default terminals, allowed terminals, and lock semantics must not let an unexpected router instance take over project routing.
|
|
51
73
|
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
Prioritize:
|
|
55
|
-
- V3 or V4 callback reentrancy
|
|
56
|
-
- sandwiching around discovered pool liquidity
|
|
57
|
-
- beneficiary versus payer refund mismatches
|
|
58
|
-
- Permit2 allowance or deadline misuse
|
|
59
|
-
- races around registry terminal locking
|
|
60
|
-
|
|
61
|
-
## Hotspots
|
|
74
|
+
## Attack Surfaces
|
|
62
75
|
|
|
63
76
|
- payment entrypoints and refund logic
|
|
64
77
|
- V3 callback verification
|
|
@@ -66,18 +79,8 @@ Prioritize:
|
|
|
66
79
|
- pool discovery and best-path selection
|
|
67
80
|
- registry allowlist and lock behavior
|
|
68
81
|
|
|
69
|
-
##
|
|
82
|
+
## Verification
|
|
70
83
|
|
|
71
|
-
Standard workflow:
|
|
72
84
|
- `npm install`
|
|
73
85
|
- `forge build`
|
|
74
86
|
- `forge test`
|
|
75
|
-
|
|
76
|
-
Current tests focus on:
|
|
77
|
-
- refund edge cases
|
|
78
|
-
- payer tracking
|
|
79
|
-
- Permit2 failure handling
|
|
80
|
-
- cash-out-assisted routes
|
|
81
|
-
- reentrancy and sandwich-sensitive fork cases
|
|
82
|
-
|
|
83
|
-
Strong findings in this repo usually show the router holding onto value or satisfying user slippage checks with the wrong sign, recipient, or output token.
|
package/CHANGELOG.md
CHANGED
|
@@ -8,9 +8,14 @@ This file describes the verified change from `nana-swap-terminal-v5` to the curr
|
|
|
8
8
|
|
|
9
9
|
- `JBRouterTerminal`
|
|
10
10
|
- `JBRouterTerminalRegistry`
|
|
11
|
+
- `JBPayRouteResolver`
|
|
11
12
|
- `IJBRouterTerminal`
|
|
12
13
|
- `IJBRouterTerminalRegistry`
|
|
14
|
+
- `IJBPayRouteResolver`
|
|
15
|
+
- `IJBPayRoutePreviewer`
|
|
16
|
+
- `IJBForwardingTerminal`
|
|
13
17
|
- `JBSwapLib`
|
|
18
|
+
- `CashOutPathCandidates`
|
|
14
19
|
|
|
15
20
|
## Summary
|
|
16
21
|
|
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Juicebox Router Terminal
|
|
2
2
|
|
|
3
|
-
`@bananapus/router-terminal-v6` is a routing terminal for Juicebox V6. It accepts value in many input tokens, discovers what token the destination project actually accepts, and forwards the payment through the best
|
|
3
|
+
`@bananapus/router-terminal-v6` is a routing terminal for Juicebox V6. It accepts value in many input tokens, discovers what token the destination project actually accepts, and forwards the payment through the best previewed route it can resolve from the configured candidates.
|
|
4
4
|
|
|
5
|
-
Docs: <https://docs.juicebox.money>
|
|
6
|
-
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
5
|
+
Docs: <https://docs.juicebox.money>
|
|
6
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
7
|
+
User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
|
|
8
|
+
Skills: [SKILLS.md](./SKILLS.md)
|
|
9
|
+
Risks: [RISKS.md](./RISKS.md)
|
|
10
|
+
Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
|
|
11
|
+
Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
|
|
7
12
|
|
|
8
13
|
## Overview
|
|
9
14
|
|
|
@@ -16,7 +21,7 @@ It can route through:
|
|
|
16
21
|
- Uniswap V3 or V4 swaps
|
|
17
22
|
- recursive Juicebox token cash outs when the input itself is a project token
|
|
18
23
|
|
|
19
|
-
Projects can use the registry
|
|
24
|
+
Projects can use the registry to choose, and optionally lock, a project-specific router terminal or fall back to the registry's default terminal.
|
|
20
25
|
|
|
21
26
|
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.
|
|
22
27
|
|
|
@@ -28,6 +33,7 @@ This repo is best understood as an execution router attached to Juicebox, not as
|
|
|
28
33
|
| --- | --- |
|
|
29
34
|
| `JBRouterTerminal` | Main routing terminal that accepts many token types and forwards value to the destination terminal. |
|
|
30
35
|
| `JBRouterTerminalRegistry` | Registry and proxy surface that lets a project choose and optionally lock its preferred router terminal. |
|
|
36
|
+
| `JBPayRouteResolver` | Helper that evaluates pay-route candidates and selects the strongest route preview the router can resolve. |
|
|
31
37
|
|
|
32
38
|
## Mental Model
|
|
33
39
|
|
|
@@ -67,6 +73,14 @@ The shortest useful reading order is:
|
|
|
67
73
|
|
|
68
74
|
That separation is the reason a successful route can still end in a downstream terminal behavior you did not expect.
|
|
69
75
|
|
|
76
|
+
## High-Signal Tests
|
|
77
|
+
|
|
78
|
+
1. `test/RouterTerminal.t.sol`
|
|
79
|
+
2. `test/RouterTerminalPreviewFork.t.sol`
|
|
80
|
+
3. `test/RouterTerminalCashOutFork.t.sol`
|
|
81
|
+
4. `test/audit/PreviewPrimaryTerminalMismatch.t.sol`
|
|
82
|
+
5. `test/codex/CashOutCircularPrimaryTerminal.t.sol`
|
|
83
|
+
|
|
70
84
|
## Install
|
|
71
85
|
|
|
72
86
|
```bash
|
|
@@ -115,3 +129,9 @@ script/
|
|
|
115
129
|
- final terminal-facing ERC-20 hops must be standard tokens; lossy terminal pulls are rejected on both router and registry paths
|
|
116
130
|
|
|
117
131
|
The most common reader mistake here is to stop at the router and forget to inspect the terminal that actually receives the value.
|
|
132
|
+
|
|
133
|
+
## For AI Agents
|
|
134
|
+
|
|
135
|
+
- Do not claim the router is the accounting source of truth after forwarding.
|
|
136
|
+
- Read the preview, recursive cash-out, and registry tests before summarizing path selection behavior.
|
|
137
|
+
- If the route ends in surprising accounting, move to the downstream terminal in `nana-core-v6`.
|
package/RISKS.md
CHANGED
|
@@ -29,11 +29,11 @@ This file focuses on the routing, accounting-context, and liquidity-selection ri
|
|
|
29
29
|
|
|
30
30
|
## 2. Economic / Manipulation Risks
|
|
31
31
|
|
|
32
|
-
- **V4 price manipulation.** `_getV4Quote` first attempts a 30-second TWAP from the pool's oracle hook (e.g., `IGeomeanOracle.observe()`). If no oracle hook exists or the call fails, it falls back to the instantaneous `getSlot0` tick, which is manipulable via sandwich attacks or flash loans. The sigmoid slippage formula provides a floor (min 2%) but does NOT provide full MEV protection.
|
|
32
|
+
- **V4 price manipulation.** `_getV4Quote` first attempts a 30-second TWAP from the pool's oracle hook (e.g., `IGeomeanOracle.observe()`). If no oracle hook exists or the call fails, it falls back to the instantaneous `getSlot0` tick, which is manipulable via sandwich attacks or flash loans. The sigmoid slippage formula provides a floor (min 2%) but does NOT provide full MEV protection. This spot-fallback path is an accepted risk for integrations that cannot source external quotes, particularly when routing routine flow through sufficiently deep pools where manipulation cost is expected to dominate likely extractable value. Deep liquidity reduces practical risk but does not eliminate the same-block manipulation surface. Thin pools, fresh pools, and unusually large swaps should not rely on this fallback. Front-ends SHOULD supply `quoteForSwap` metadata for V4 swaps whenever possible. Note: `_getV4Quote` normalizes WETH to `address(0)` before calling OracleLibrary, since V4 uses `address(0)` for native ETH -- without this normalization, token sorting would mismatch the pool's currency ordering and produce inverted quotes.
|
|
33
33
|
- **Hooked V4 discovery scope.** Auto-discovery checks both vanilla V4 pools and pools using the configured canonical `UNIV4_HOOK`. That keeps buyback-hook and LP-split-hook routes discoverable, but it also means deployment misconfiguration of `UNIV4_HOOK` changes which hooked pools are even visible to the router.
|
|
34
34
|
- **V3 TWAP manipulation.** Short TWAP windows (falls back to `oldestObservation` if < 10 minutes) reduce manipulation resistance. A newly created pool with minimal history can be manipulated within the TWAP window.
|
|
35
35
|
- **Cashout loop value extraction.** `_cashOutLoop` iterates up to 20 times, cashing out JB project tokens recursively. Each cashout incurs bonding curve slippage. `cashOutMinReclaimed` is enforced only on the first concrete cashout hop. Later hops intentionally do not reuse or rescale that minimum, because multi-hop cashouts can change token units and a single metadata amount cannot be propagated safely across different assets. Gas cost: each cashout iteration involves `terminal.cashOutTokensOf` (external call, ~100-200k gas) plus token transfer and balance accounting. At 20 iterations maximum, the worst case is ~4M gas for the loop alone, leaving headroom within a 30M block but consuming a significant portion.
|
|
36
|
-
- **
|
|
36
|
+
- **Pre-existing balances are intentionally not swept into refunds.** `_handleSwap` snapshots a per-route refund baseline and only refunds the leftover input balance attributable to the current route. Tokens or ETH that were already sitting in the router before the route began are excluded from that refund calculation. This avoids gifting prior stray balances to the next caller, but it also means accidentally sent balances can remain stranded because the router has no sweep mechanism.
|
|
37
37
|
- **V4 native ETH settlement.** `_settleV4` unwraps WETH to native ETH when settling a `Currency.wrap(address(0))` debt with PoolManager. This is necessary because the router may hold WETH (from ERC-20 transfers or prior wrapping) but V4 native pools require `msg.value` settlement. If `address(this).balance` is already sufficient, no unwrap occurs.
|
|
38
38
|
- **Pool selection by liquidity.** `_discoverPool` selects the pool with the highest `liquidity()` value. An attacker can deploy a pool with high but concentrated (out-of-range) liquidity to win selection, then manipulate the actual swap execution at worse prices.
|
|
39
39
|
- **Heuristic route selection, not best execution.** Automatic routing chooses among discovered paths using bounded heuristics, and pool discovery prefers the deepest discovered pool rather than exhaustively quoting every viable pool. This is an intentional tradeoff to keep routing predictable and gas-bounded. Integrators should treat router-selected execution as best-effort convenience, not a guarantee of the globally best obtainable output. When execution quality matters more than convenience, frontends should supply `quoteForSwap` metadata.
|
|
@@ -65,9 +65,9 @@ This file focuses on the routing, accounting-context, and liquidity-selection ri
|
|
|
65
65
|
|
|
66
66
|
- **Registry default terminal change.** Projects without explicit terminal assignments use `defaultTerminal`. If the registry owner changes the default, all unlocked projects are silently migrated. `lockTerminalFor` mitigates this.
|
|
67
67
|
- **Locked bad-terminal risk.** `lockTerminalFor` protects projects from silent migrations, but it also freezes the current resolved terminal exactly as-is. If the locked terminal is malformed, recursively forwards, reverts on use, or otherwise cannot actually process routed payments, the project can be left permanently unroutable through the registry.
|
|
68
|
-
- **
|
|
68
|
+
- **forceApprove for terminal transfers.** `_beforeTransferFor` uses `forceApprove`, which resets the allowance to zero before setting the new value. This mitigates stale-allowance accumulation: even if a previous transaction left a non-zero allowance, `forceApprove` overwrites it rather than adding to it. The reset-then-set pattern prevents cumulative allowance drift.
|
|
69
69
|
- **Callback data trust.** `uniswapV3SwapCallback` validates the caller by reconstructing the pool address from `(tokenIn, tokenOut, fee)`. The factory `getPool` lookup makes spoofing infeasible in practice.
|
|
70
|
-
- **receive() function.** The contract accepts arbitrary ETH via `receive()`. This is necessary for WETH unwraps, cashout reclaims, and V4 PoolManager takes.
|
|
70
|
+
- **receive() function.** The contract accepts arbitrary ETH via `receive()`. This is necessary for WETH unwraps, cashout reclaims, and V4 PoolManager takes. Route-specific leftover refunds only return ETH attributable to the active route; arbitrary ETH already sitting in the router before that route is not swept out automatically and can remain stranded.
|
|
71
71
|
|
|
72
72
|
## 6. MEV Surface
|
|
73
73
|
|
|
@@ -77,7 +77,7 @@ This file focuses on the routing, accounting-context, and liquidity-selection ri
|
|
|
77
77
|
|
|
78
78
|
## 7. Invariants to Verify
|
|
79
79
|
|
|
80
|
-
- After any `pay()` or `addToBalanceOf()`, the contract should
|
|
80
|
+
- After any `pay()` or `addToBalanceOf()`, the contract should not retain balances attributable to the just-processed route. Pre-existing stray balances may still remain because refund logic intentionally excludes them.
|
|
81
81
|
- `minAmountOut` in swaps is never zero when TWAP/spot price is available (the sigmoid formula has a 2% floor).
|
|
82
82
|
- Callback validation: `uniswapV3SwapCallback` only transfers tokens to verified pool addresses.
|
|
83
83
|
- `unlockCallback` only executes when called by `POOL_MANAGER`.
|
|
@@ -95,7 +95,7 @@ The router terminal has no `ReentrancyGuard` or `_routing` flag. This is a consc
|
|
|
95
95
|
- **Each call uses its own funds.** The re-entrant call would need to supply its own tokens/ETH (via `_acceptFundsFor`). It cannot consume funds belonging to the outer call because those funds are already committed to the routing pipeline.
|
|
96
96
|
- **A reentrancy guard would block legitimate composition.** Projects may have terminal chains where terminal A routes through this router, which cashes out into terminal B, which itself routes through this router for a different project. A blanket reentrancy guard would break such flows.
|
|
97
97
|
|
|
98
|
-
Verified in `RouterTerminalReentrancy.t.sol`: re-entrant calls via both `pay()` and `addToBalanceOf()` succeed without corrupting the outer call's ETH forwarding. The invariant suite (`RouterTerminalInvariant.t.sol`)
|
|
98
|
+
Verified in `RouterTerminalReentrancy.t.sol`: re-entrant calls via both `pay()` and `addToBalanceOf()` succeed without corrupting the outer call's ETH forwarding. The invariant suite (`RouterTerminalInvariant.t.sol`) confirms that routed operations do not strand balances attributable to the active route, including operations that exercise the cashout recursion loop (`_cashOutLoop`, up to `_MAX_CASHOUT_ITERATIONS = 20`). That does not imply the router can never hold pre-existing stray balances, because refund logic intentionally excludes balances that were already present before the route began.
|
|
99
99
|
|
|
100
100
|
### 8.2 Router trusts `originalPayer()` from any `msg.sender` that implements it
|
|
101
101
|
|
|
@@ -112,3 +112,7 @@ The router and resolver no longer contain registry-specific circular-resolution
|
|
|
112
112
|
### 8.4 Liquidity-first pool selection is intentional
|
|
113
113
|
|
|
114
114
|
The router does not attempt full best-execution search across every viable V3 and V4 pool. `_discoverPool` prefers the deepest discovered pool, and the selected pool is then quoted and executed. This can underperform an alternative pool in some market states, but it is an accepted product tradeoff: bounded route discovery, lower complexity, and predictable behavior are prioritized over exhaustive output maximization. Users who need tighter execution guarantees should provide `quoteForSwap` metadata or route externally.
|
|
115
|
+
|
|
116
|
+
### 8.6 V4 spot fallback is an accepted risk for programmatic integrations
|
|
117
|
+
|
|
118
|
+
Automatic V4 quoting first tries a hook-provided oracle observation and only falls back to spot when no such quote is available. That fallback remains manipulable within the block and is not equivalent to an external quote or a trusted TWAP. It is nevertheless accepted in this terminal because some programmatic integrations cannot provide `quoteForSwap` metadata and still need a bounded on-chain quoting path. The intended operating envelope is routine routing through sufficiently deep pools with moderate swap sizes, where manipulation is possible in principle but expected to be uneconomic in practice. This is a risk reduction argument, not a safety proof: deep liquidity raises attack cost, but does not remove the vulnerability class.
|
package/SKILLS.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
5
|
- Use this file when the task involves routed payments or cash-outs, swap-path metadata, dynamic accepted-token discovery, route-registry selection, or router-terminal fee and slippage behavior.
|
|
6
|
-
- Start here, then
|
|
6
|
+
- Start here, then decide whether the problem is route discovery, swap execution, cash-out recursion, or registry selection. Those are distinct failure modes in this repo.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
|---|---|
|
|
12
12
|
| Repo overview and routing model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
13
|
| Terminal execution path | [`src/JBRouterTerminal.sol`](./src/JBRouterTerminal.sol) |
|
|
14
|
+
| Pay-route resolution helpers | [`src/JBPayRouteResolver.sol`](./src/JBPayRouteResolver.sol) |
|
|
14
15
|
| Registry behavior and terminal selection | [`src/JBRouterTerminalRegistry.sol`](./src/JBRouterTerminalRegistry.sol) |
|
|
15
16
|
| Shared libraries, interfaces, and metadata structs | [`src/libraries/`](./src/libraries/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
16
|
-
|
|
|
17
|
+
| Preview, cash-out, and buyback composition | [`test/RouterTerminalPreviewFork.t.sol`](./test/RouterTerminalPreviewFork.t.sol), [`test/RouterTerminalCashOutFork.t.sol`](./test/RouterTerminalCashOutFork.t.sol), [`test/RouterTerminalBuybackHookFork.t.sol`](./test/RouterTerminalBuybackHookFork.t.sol), [`test/RouterTerminalFeeCashOutFork.t.sol`](./test/RouterTerminalFeeCashOutFork.t.sol) |
|
|
18
|
+
| Registry, multihop, and adversarial coverage | [`test/RouterTerminalRegistry.t.sol`](./test/RouterTerminalRegistry.t.sol), [`test/RouterTerminalMultihopFork.t.sol`](./test/RouterTerminalMultihopFork.t.sol), [`test/RouterTerminalReentrancy.t.sol`](./test/RouterTerminalReentrancy.t.sol), [`test/RouterTerminalSandwichFork.t.sol`](./test/RouterTerminalSandwichFork.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
|
|
17
19
|
|
|
18
20
|
## Repo Map
|
|
19
21
|
|
|
@@ -36,6 +38,11 @@ Universal routing terminal for Juicebox V6. This repo accepts many input tokens,
|
|
|
36
38
|
## Working Rules
|
|
37
39
|
|
|
38
40
|
- Start in [`src/JBRouterTerminal.sol`](./src/JBRouterTerminal.sol) for execution behavior, but verify downstream semantics in the destination terminal before treating the router as the source of truth.
|
|
41
|
+
- The router intentionally synthesizes accounting contexts instead of storing a static accepted-token list. If token acceptance looks wrong, verify discovery logic before touching registry state.
|
|
39
42
|
- Treat preview behavior, quote selection, and execution callbacks as tightly coupled. Changes in one usually need verification in the others.
|
|
40
43
|
- When the input token is itself a Juicebox project token, follow the cash-out loop carefully. Recursive routing assumptions are where subtle bugs hide.
|
|
44
|
+
- Multi-hop and buyback-assisted routes are first-class behavior here, not edge cases. Verify them explicitly when changing route selection.
|
|
45
|
+
- Refund handling is route-specific state, not cleanup garnish. Baseline snapshots and partial-fill leftovers are part of correctness.
|
|
46
|
+
- Final terminal-facing receipt enforcement is a real boundary. If a terminal pull or forwarding model is non-standard, prove receipt semantics still hold before weakening guards.
|
|
47
|
+
- Callback guards and final-hop receipt checks are security boundaries. Do not weaken them to accommodate non-standard token paths.
|
|
41
48
|
- If you touch registry behavior, verify project-specific overrides, allowlisting, and terminal locking all still match the intended governance model.
|