@bananapus/router-terminal-v6 0.0.26 → 0.0.28
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 -126
- package/ARCHITECTURE.md +71 -45
- package/AUDIT_INSTRUCTIONS.md +36 -32
- package/README.md +34 -15
- package/RISKS.md +66 -75
- package/SKILLS.md +13 -8
- package/USER_JOURNEYS.md +115 -32
- package/package.json +4 -4
- package/references/operations.md +5 -2
- package/references/runtime.md +4 -2
- package/src/JBPayRouteResolver.sol +18 -3
- package/src/JBRouterTerminal.sol +133 -27
- package/test/RouterTerminalBuybackHookFork.t.sol +1 -1
- package/test/RouterTerminalCashOutFork.t.sol +1 -1
- 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/audit/MultiHopForwardCycle.t.sol +185 -0
- package/test/audit/RevertingTerminalRouteDiscovery.t.sol +463 -0
- package/test/fork/V4QuoteAndSettlementFork.t.sol +1 -1
package/ADMINISTRATION.md
CHANGED
|
@@ -1,152 +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
|
|
15
|
-
|
|
16
|
-
- Keep the registry allowlist limited to router terminal implementations you actually want projects to choose from.
|
|
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.
|
|
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 |
|
|
20
11
|
|
|
21
|
-
##
|
|
12
|
+
## Purpose
|
|
22
13
|
|
|
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.
|
|
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`.
|
|
25
15
|
|
|
26
|
-
##
|
|
16
|
+
## Control Model
|
|
27
17
|
|
|
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.
|
|
50
|
-
|
|
51
|
-
## Terminal Resolution
|
|
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 |
|
|
52
31
|
|
|
53
|
-
|
|
32
|
+
## Privileged Surfaces
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 |
|
|
58
39
|
|
|
59
|
-
|
|
40
|
+
## Immutable And One-Way
|
|
60
41
|
|
|
61
|
-
|
|
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
|
|
62
45
|
|
|
63
|
-
##
|
|
46
|
+
## Operational Notes
|
|
64
47
|
|
|
65
|
-
|
|
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
|
|
52
|
+
- distinguish configuration risk from quote-quality risk
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|----------|--------------|---------------|-------|--------------|
|
|
69
|
-
| `allowTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Adds a terminal to the allowlist (`isTerminalAllowed[terminal] = true`). Projects can only use allowlisted terminals. |
|
|
70
|
-
| `disallowTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Removes a terminal from the allowlist. Reverts if `terminal` is the current `defaultTerminal`, so the owner must move the default first. Does NOT affect projects that have already locked their terminal or explicitly set another terminal. |
|
|
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.** |
|
|
54
|
+
## Machine Notes
|
|
74
55
|
|
|
75
|
-
|
|
56
|
+
- do not treat registry ownership as authority to override a locked project choice
|
|
57
|
+
- inspect `src/JBRouterTerminalRegistry.sol` and `src/JBRouterTerminal.sol` separately; they govern different control boundaries
|
|
58
|
+
- if effective terminal resolution and the documented default differ, resolve 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
|
|
76
60
|
|
|
77
|
-
|
|
78
|
-
|-----------|------------|------------|--------------|
|
|
79
|
-
| Credit cashout via `cashOutSource` metadata | Payer | `TRANSFER_CREDITS` (13) granted to router terminal | `TOKENS.transferCreditsFrom()` pulls credits from payer. Reverts if payer has not granted the permission. |
|
|
80
|
-
| Cashout execution | Router terminal (as holder) | None (terminal holds the tokens) | `IJBCashOutTerminal.cashOutTokensOf()` is called with the router as the holder. The router already holds the tokens from the credit transfer or prior cashout step. |
|
|
61
|
+
## Recovery
|
|
81
62
|
|
|
82
|
-
|
|
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
|
|
83
67
|
|
|
84
|
-
|
|
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
|
-
### JBPayRouteResolver
|
|
115
|
-
|
|
116
|
-
| Property | Set At | Mutable? | Description |
|
|
117
|
-
|----------|--------|----------|-------------|
|
|
118
|
-
| `DIRECTORY` | Constructor | No | JB directory for terminal and accounting-context lookups, cached from the router at construction time |
|
|
119
|
-
| `WETH` | Constructor | No | Wrapped native token used for token normalization, cached from the router at construction time |
|
|
120
|
-
|
|
121
|
-
### JBSwapLib
|
|
68
|
+
## Admin Boundaries
|
|
122
69
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| `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
|
|
128
74
|
|
|
129
|
-
##
|
|
75
|
+
## Source Map
|
|
130
76
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
- **Override a project's locked terminal choice.** Once locked, the terminal is permanently stored in `_terminalOf[projectId]` and `hasLockedTerminal[projectId]` is permanently `true`.
|
|
136
|
-
- **Force a project to use a specific terminal.** Only the project owner (or delegate) can call `setTerminalFor`.
|
|
137
|
-
- **Access project funds.** The registry is a pass-through; it holds funds transiently during forwarding only.
|
|
138
|
-
- **Modify swap parameters, slippage, or routing logic.** These are controlled by the `JBRouterTerminal` contract, not the registry.
|
|
139
|
-
- **Pause payments.** There is no pause mechanism.
|
|
140
|
-
|
|
141
|
-
### Router Terminal Maintainers Cannot:
|
|
142
|
-
- **Modify swap slippage parameters.** The TWAP window, sigmoid constants, fee tiers, and max slippage are all immutable.
|
|
143
|
-
- **Redirect funds.** The terminal is stateless between transactions and routes payments to whichever terminal the JB directory specifies.
|
|
144
|
-
- **Change the Uniswap factory or PoolManager.** These are immutable constructor parameters.
|
|
145
|
-
- **Override user-provided quotes.** The `quoteForSwap` metadata is decoded and used as-is.
|
|
146
|
-
- **Prevent specific users from paying.** There is no blocklist mechanism.
|
|
147
|
-
- **Extract stuck funds.** There is no sweep or rescue function. The terminal relies on completing all token movements within a single transaction.
|
|
148
|
-
|
|
149
|
-
### Project Owner / Delegate Cannot:
|
|
150
|
-
- **Change the terminal after locking.** The `setTerminalFor` function reverts with `TerminalLocked` if the terminal is locked.
|
|
151
|
-
- **Set a disallowed terminal.** `setTerminalFor` reverts with `TerminalNotAllowed` if the terminal is not on the registry owner's allowlist.
|
|
152
|
-
- **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,93 @@
|
|
|
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,
|
|
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, wraps or unwraps 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.
|
|
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
|
+
Final accounting still happens in the downstream terminal selected through `nana-core-v6`.
|
|
24
14
|
|
|
25
|
-
##
|
|
15
|
+
## Core Invariants
|
|
16
|
+
|
|
17
|
+
- the router's own accounting context is synthetic and must not be treated as the project ledger
|
|
18
|
+
- preview route discovery and live execution must stay aligned
|
|
19
|
+
- refund behavior is part of correctness, not just UX
|
|
20
|
+
- registry locking prevents silent migration to untrusted router implementations
|
|
21
|
+
- final terminal-facing ERC-20 hops only support standard, non-lossy transfers
|
|
22
|
+
- recursive project-token cashout routing is intentionally bounded
|
|
23
|
+
- caller reclaim minima only apply to the first cashout hop, because later hops may change token units
|
|
24
|
+
- circular `router -> registry -> same router` forwarding remains blocked in the registry
|
|
25
|
+
|
|
26
|
+
## Modules
|
|
27
|
+
|
|
28
|
+
| Module | Responsibility | Notes |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `JBRouterTerminal` | Intake, route discovery, swap execution, forwarding, and refunds | Main runtime surface |
|
|
31
|
+
| `JBRouterTerminalRegistry` | Project-level router selection, locking, and proxy forwarding to the resolved router terminal | Governance, safety, and proxy surface |
|
|
32
|
+
| `JBPayRouteResolver` | Preview candidate evaluation | Helper to keep runtime size bounded |
|
|
33
|
+
| `JBSwapLib` and routing structs | Pool discovery, quoting, and route metadata | Shared routing logic |
|
|
34
|
+
|
|
35
|
+
## Trust Boundaries
|
|
36
|
+
|
|
37
|
+
- final accounting remains in the downstream terminal selected through `JBDirectory`
|
|
38
|
+
- the router trusts Uniswap V3, Uniswap V4, Permit2, and optional payer trackers for routing-side behavior
|
|
39
|
+
- fee-on-transfer tokens are only tolerated on ingress where received-balance deltas can be reconciled
|
|
40
|
+
- the registry is trusted to resolve and forward into the intended router implementation for a project
|
|
41
|
+
|
|
42
|
+
## Critical Flows
|
|
43
|
+
|
|
44
|
+
### Route And Pay
|
|
26
45
|
|
|
27
46
|
```text
|
|
28
47
|
router pay call
|
|
29
48
|
-> accept native, ERC-20, or JB-token-like input
|
|
30
49
|
-> 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
|
-
->
|
|
50
|
+
-> resolve the destination token the project terminal actually accepts
|
|
51
|
+
-> choose the best direct, wrap/unwrap, or swap path under the router's bounded candidate-discovery heuristic
|
|
52
|
+
-> execute the route and forward the result to the downstream terminal
|
|
53
|
+
-> refund leftover input when possible
|
|
35
54
|
```
|
|
36
55
|
|
|
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.
|
|
56
|
+
## Accounting Model
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
The router does not own project balances. It owns transient route accounting: input reconciliation, swap execution, forwarded amount, and refund resolution.
|
|
47
59
|
|
|
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.
|
|
60
|
+
Preview and execution share the same conceptual route shape: optional recursive cashout first, then destination-token resolution, then final conversion and forwarding.
|
|
55
61
|
|
|
56
|
-
##
|
|
62
|
+
## Security Model
|
|
57
63
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
64
|
+
- native-asset handling and refunds are the most failure-prone paths
|
|
65
|
+
- V3 and V4 discovery must stay synchronized between preview and live execution
|
|
66
|
+
- V4 discovery intentionally considers both vanilla pools and pools using the canonical `UNIV4_HOOK`
|
|
67
|
+
- the router's "best route" claim is only as strong as its bounded discovery set and external-terminal safety checks
|
|
68
|
+
- recursive cashout behavior, preferred-token handling, and one-shot source overrides are tightly coupled
|
|
61
69
|
|
|
62
70
|
## Safe Change Guide
|
|
63
71
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
72
|
+
- keep route discovery and route execution semantics paired
|
|
73
|
+
- be conservative with native wrapping, unwrapping, and refund behavior
|
|
74
|
+
- if recursive cash-out logic changes, review hop limits and failure handling together
|
|
75
|
+
- if metadata semantics change, re-check first-hop reclaim minima, one-shot source overrides, and preferred-token routing together
|
|
76
|
+
- do not turn the router into a persistent treasury layer
|
|
77
|
+
|
|
78
|
+
## Canonical Checks
|
|
79
|
+
|
|
80
|
+
- bounded recursive cash-out behavior:
|
|
81
|
+
`test/regression/CashOutLoopLimit.t.sol`
|
|
82
|
+
- preview versus execution terminal alignment:
|
|
83
|
+
`test/audit/PreviewPrimaryTerminalMismatch.t.sol`
|
|
84
|
+
- router-wide route and refund invariants:
|
|
85
|
+
`test/invariant/RouterTerminalInvariant.t.sol`
|
|
86
|
+
|
|
87
|
+
## Source Map
|
|
88
|
+
|
|
89
|
+
- `src/JBRouterTerminal.sol`
|
|
90
|
+
- `src/JBRouterTerminalRegistry.sol`
|
|
91
|
+
- `src/JBPayRouteResolver.sol`
|
|
92
|
+
- `test/regression/CashOutLoopLimit.t.sol`
|
|
93
|
+
- `test/audit/PreviewPrimaryTerminalMismatch.t.sol`
|
|
94
|
+
- `test/invariant/RouterTerminalInvariant.t.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
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
9
|
- route user funds through an incorrect pool or protocol path
|
|
9
10
|
- under-deliver relative to quoted or minimum-return semantics
|
|
10
11
|
- refund leftovers to the wrong party or trap them in the router
|
|
@@ -14,6 +15,7 @@ Find issues that:
|
|
|
14
15
|
## Scope
|
|
15
16
|
|
|
16
17
|
In scope:
|
|
18
|
+
|
|
17
19
|
- `src/JBRouterTerminal.sol`
|
|
18
20
|
- `src/JBRouterTerminalRegistry.sol`
|
|
19
21
|
- `src/interfaces/`
|
|
@@ -22,43 +24,55 @@ In scope:
|
|
|
22
24
|
- deployment scripts in `script/`
|
|
23
25
|
|
|
24
26
|
Key dependencies:
|
|
27
|
+
|
|
25
28
|
- `nana-core-v6`
|
|
26
29
|
- Uniswap V3 and V4 integration surfaces
|
|
27
30
|
|
|
28
|
-
##
|
|
31
|
+
## Start Here
|
|
32
|
+
|
|
33
|
+
1. `src/JBRouterTerminal.sol`
|
|
34
|
+
2. `src/JBRouterTerminalRegistry.sol`
|
|
35
|
+
3. `src/libraries/JBSwapLib.sol`
|
|
36
|
+
|
|
37
|
+
## Security Model
|
|
29
38
|
|
|
30
39
|
The router terminal:
|
|
31
|
-
|
|
40
|
+
|
|
41
|
+
- discovers what token a project's terminal accepts
|
|
32
42
|
- decides whether to route via wrap/unwrap, V3, V4, Juicebox token cash-out, or a combination
|
|
33
43
|
- forwards value into the destination terminal
|
|
34
44
|
- optionally handles Permit2-funded transfers
|
|
35
45
|
|
|
36
46
|
The registry chooses which router terminal instance a project uses and whether that choice is locked.
|
|
37
47
|
|
|
38
|
-
##
|
|
39
|
-
|
|
40
|
-
1. User intent is preserved
|
|
41
|
-
The actual destination project, beneficiary, minimum output semantics, and refund recipient must match the request and metadata.
|
|
48
|
+
## Roles And Privileges
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
| Role | Powers | How constrained |
|
|
51
|
+
|------|--------|-----------------|
|
|
52
|
+
| User or relayer | Initiate routed payment with beneficiary and slippage intent | Must receive exact refund semantics requested |
|
|
53
|
+
| Registry controller | Set default or allowed router terminals | Must not redirect projects unexpectedly |
|
|
54
|
+
| Router terminal | Hold funds only transiently during routing | Must not retain leftovers across flows |
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
The quoted path, callback settlement, and final forwarded amount must all describe the same trade.
|
|
56
|
+
## Integration Assumptions
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
| Dependency | Assumption | What breaks if wrong |
|
|
59
|
+
|------------|------------|----------------------|
|
|
60
|
+
| `nana-core-v6` | Terminal discovery and pay semantics are accurate | Routed value lands in the wrong place |
|
|
61
|
+
| Uniswap V3 and V4 | Callback settlement and pool discovery are authentic | Slippage and final forwarded amount diverge |
|
|
62
|
+
| Permit2 | Allowances and deadlines reflect user intent | Unauthorized transfer or stuck routing behavior |
|
|
51
63
|
|
|
52
|
-
##
|
|
64
|
+
## Critical Invariants
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
1. User intent is preserved.
|
|
67
|
+
The actual destination project, beneficiary, minimum output semantics, and refund recipient must match the request and metadata.
|
|
68
|
+
2. No leftover value disappears.
|
|
69
|
+
Partial fills, failed paths, and overfunded inputs must either be forwarded or refunded to the intended party.
|
|
70
|
+
3. Pool discovery and settlement agree.
|
|
71
|
+
The quoted path, callback settlement, and final forwarded amount must all describe the same trade.
|
|
72
|
+
4. Registry controls stay narrow.
|
|
73
|
+
Default terminals, allowed terminals, and lock semantics must not let an unexpected router instance take over project routing.
|
|
60
74
|
|
|
61
|
-
##
|
|
75
|
+
## Attack Surfaces
|
|
62
76
|
|
|
63
77
|
- payment entrypoints and refund logic
|
|
64
78
|
- V3 callback verification
|
|
@@ -66,18 +80,8 @@ Prioritize:
|
|
|
66
80
|
- pool discovery and best-path selection
|
|
67
81
|
- registry allowlist and lock behavior
|
|
68
82
|
|
|
69
|
-
##
|
|
83
|
+
## Verification
|
|
70
84
|
|
|
71
|
-
Standard workflow:
|
|
72
85
|
- `npm install`
|
|
73
86
|
- `forge build`
|
|
74
87
|
- `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/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 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
|
|
|
@@ -14,9 +19,9 @@ It can route through:
|
|
|
14
19
|
- direct forwarding when the destination already accepts the input token
|
|
15
20
|
- wrapping or unwrapping native ETH and WETH
|
|
16
21
|
- Uniswap V3 or V4 swaps
|
|
17
|
-
- recursive Juicebox token cash outs when the input itself
|
|
22
|
+
- recursive Juicebox token cash outs when the input is itself 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,7 +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. |
|
|
31
|
-
| `JBPayRouteResolver` | Helper that evaluates pay-route candidates and selects the
|
|
36
|
+
| `JBPayRouteResolver` | Helper that evaluates pay-route candidates and selects the strongest route preview the router can resolve. |
|
|
32
37
|
|
|
33
38
|
## Mental Model
|
|
34
39
|
|
|
@@ -55,18 +60,26 @@ The shortest useful reading order is:
|
|
|
55
60
|
|
|
56
61
|
## Integration Traps
|
|
57
62
|
|
|
58
|
-
- projects that expose a router terminal still settle into ordinary Juicebox terminals underneath
|
|
59
|
-
- route discovery and route execution are related but not identical, especially when liquidity or
|
|
63
|
+
- projects that expose a router terminal still settle into ordinary Juicebox terminals underneath
|
|
64
|
+
- route discovery and route execution are related but not identical, especially when liquidity or caller-supplied quote data moves
|
|
60
65
|
- using JB project tokens as router input creates recursive path complexity that frontends and integrators should model explicitly
|
|
61
|
-
- the registry
|
|
66
|
+
- the registry changes which router a project uses, but not what downstream terminal ultimately settles the payment
|
|
62
67
|
|
|
63
68
|
## Where State Lives
|
|
64
69
|
|
|
65
|
-
- route-selection logic
|
|
66
|
-
- per-project router choice and lock status
|
|
67
|
-
- accepted-token accounting and final balance changes
|
|
70
|
+
- route-selection logic: `JBRouterTerminal`
|
|
71
|
+
- per-project router choice and lock status: `JBRouterTerminalRegistry`
|
|
72
|
+
- accepted-token accounting and final balance changes: the downstream terminal, usually in `nana-core-v6`
|
|
68
73
|
|
|
69
|
-
That separation is
|
|
74
|
+
That separation is why a successful route can still end in downstream terminal behavior you did not expect.
|
|
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`
|
|
70
83
|
|
|
71
84
|
## Install
|
|
72
85
|
|
|
@@ -112,7 +125,13 @@ script/
|
|
|
112
125
|
- the router synthesizes accounting context for discovery and should not be treated as an accounting-truth surface
|
|
113
126
|
- swap previews are best-effort estimates and depend on current pool state plus caller-supplied quote data
|
|
114
127
|
- recursive cash-out routing increases complexity when the input token is itself a Juicebox project token
|
|
115
|
-
- slippage and sandwich resistance depend on the quality of the quote path
|
|
116
|
-
- final terminal-facing ERC-20 hops must be standard tokens; lossy terminal pulls are rejected
|
|
128
|
+
- slippage and sandwich resistance depend on the quality of the chosen quote path
|
|
129
|
+
- final terminal-facing ERC-20 hops must be standard tokens; lossy terminal pulls are rejected
|
|
117
130
|
|
|
118
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`.
|