@bananapus/router-terminal-v6 0.0.37 → 0.0.39
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/CHANGELOG.md +19 -1
- package/README.md +4 -4
- package/package.json +5 -5
- package/references/operations.md +2 -2
- package/references/runtime.md +1 -1
- package/src/JBPayRouteResolver.sol +12 -19
- package/src/JBRouterTerminal.sol +101 -106
- package/src/JBRouterTerminalRegistry.sol +121 -42
- package/src/interfaces/IJBPayRoutePreviewer.sol +3 -3
- package/src/interfaces/IJBRouterTerminalRegistry.sol +31 -0
- package/src/interfaces/IWETH9.sol +5 -5
- package/src/libraries/JBForwardingCheck.sol +0 -1
- package/src/structs/DefaultTerminalSegment.sol +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Scope
|
|
4
4
|
|
|
5
|
-
This file describes the verified change from `nana-swap-terminal-v5` to the current `nana-router-terminal-v6` repo.
|
|
5
|
+
This file describes the verified change from `nana-swap-terminal-v5` to the current `nana-router-terminal-v6` repo. In-v6 behavior changes that have semantic implications for integrators are also captured here.
|
|
6
|
+
|
|
7
|
+
## In-v6 changes
|
|
8
|
+
|
|
9
|
+
### Threshold-protected `setDefaultTerminal`
|
|
10
|
+
|
|
11
|
+
The registry owner's `setDefaultTerminal(IJBTerminal)` call now applies only to projects created AFTER the call. Existing projects without an explicit `setTerminalFor` override keep resolving to the default that was current when their project-ID cohort was active. The outgoing default is snapshotted into an append-only `_defaultTerminalHistory` array on every `setDefaultTerminal` call.
|
|
12
|
+
|
|
13
|
+
- New view: `defaultTerminalFor(uint256 projectId)` returns the default applicable to a specific project (walks history if the project is in a legacy cohort).
|
|
14
|
+
- New view: `defaultTerminalProjectIdThreshold()` returns `PROJECTS.count()` at the time of the most recent `setDefaultTerminal`.
|
|
15
|
+
- New views: `defaultTerminalHistoryAt(uint256 index)` and `defaultTerminalHistoryLength()` expose the snapshot history.
|
|
16
|
+
- New struct: `DefaultTerminalSegment { uint256 maxProjectId; IJBTerminal terminal; }` in `src/structs/`.
|
|
17
|
+
- `lockTerminalFor` now snapshots the *cohort-correct* default into `_terminalOf` (via `_defaultTerminalFor(projectId)`) when locking a project that has no explicit terminal — not the current registry-wide default.
|
|
18
|
+
|
|
19
|
+
Indexer impact: read `defaultTerminalFor(projectId)` rather than `defaultTerminal()` when computing the effective default for any specific project.
|
|
20
|
+
|
|
21
|
+
Admin impact: the registry owner can no longer silently reroute payments for already-deployed projects by changing the default. See `ADMINISTRATION.md` for the updated boundary description.
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
|
|
7
25
|
## Current v6 surface
|
|
8
26
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ It can route through:
|
|
|
21
21
|
- Uniswap V3 or V4 swaps
|
|
22
22
|
- recursive Juicebox token cash outs when the input is itself a project token
|
|
23
23
|
|
|
24
|
-
Projects can use the registry to choose, and optionally lock, a project-specific router terminal or fall back to the registry's default
|
|
24
|
+
Projects can use the registry to choose, and optionally lock, a project-specific router terminal or fall back to the registry's default. The default is cohort-pinned: when the registry owner calls `setDefaultTerminal` again, the new default applies only to projects created after that call; existing projects continue to resolve against the default that was current when their ID range was active (see `defaultTerminalFor(projectId)`).
|
|
25
25
|
|
|
26
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.
|
|
27
27
|
|
|
@@ -78,8 +78,8 @@ That separation is why a successful route can still end in downstream terminal b
|
|
|
78
78
|
1. `test/RouterTerminal.t.sol`
|
|
79
79
|
2. `test/RouterTerminalPreviewFork.t.sol`
|
|
80
80
|
3. `test/RouterTerminalCashOutFork.t.sol`
|
|
81
|
-
4. `test/
|
|
82
|
-
5. `test/
|
|
81
|
+
4. `test/regression/PreviewPrimaryTerminalMismatch.t.sol`
|
|
82
|
+
5. `test/regression/CashOutCircularPrimaryTerminal.t.sol`
|
|
83
83
|
|
|
84
84
|
## Install
|
|
85
85
|
|
|
@@ -114,7 +114,7 @@ src/
|
|
|
114
114
|
libraries/
|
|
115
115
|
structs/
|
|
116
116
|
test/
|
|
117
|
-
unit, fork, registry,
|
|
117
|
+
unit, fork, registry, review, invariant, and regression coverage
|
|
118
118
|
script/
|
|
119
119
|
Deploy.s.sol
|
|
120
120
|
helpers/
|
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.39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-router-terminal-v6'"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bananapus/buyback-hook-v6": "0.0.36",
|
|
30
|
-
"@bananapus/core-v6": "0.0.
|
|
31
|
-
"@bananapus/permission-ids-v6": "0.0.22",
|
|
32
|
-
"@bananapus/univ4-router-v6": "0.0.22",
|
|
29
|
+
"@bananapus/buyback-hook-v6": "^0.0.36",
|
|
30
|
+
"@bananapus/core-v6": "^0.0.48",
|
|
31
|
+
"@bananapus/permission-ids-v6": "^0.0.22",
|
|
32
|
+
"@bananapus/univ4-router-v6": "^0.0.22",
|
|
33
33
|
"@openzeppelin/contracts": "5.6.1",
|
|
34
34
|
"@uniswap/permit2": "github:Uniswap/permit2#cc56ad0f3439c502c246fc5cfcc3db92bb8b7219",
|
|
35
35
|
"@uniswap/v3-core": "github:Uniswap/v3-core#6562c52e8f75f0c10f9deaf44861847585fc8129",
|
package/references/operations.md
CHANGED
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
- [`test/RouterTerminalRegistry.t.sol`](../test/RouterTerminalRegistry.t.sol) for registry rules.
|
|
29
29
|
- [`test/RouterTerminalERC2771.t.sol`](../test/RouterTerminalERC2771.t.sol) for trusted-forwarder behavior.
|
|
30
30
|
- [`test/RouterTerminalSandwichFork.t.sol`](../test/RouterTerminalSandwichFork.t.sol) and [`test/RouterTerminalFeeCashOutFork.t.sol`](../test/RouterTerminalFeeCashOutFork.t.sol) for adversarial routing conditions.
|
|
31
|
-
- [`test/
|
|
32
|
-
- [`test/
|
|
31
|
+
- [`test/regression/LeftoverRefund.t.sol`](../test/regression/LeftoverRefund.t.sol), [`test/regression/PreviewPrimaryTerminalMismatch.t.sol`](../test/regression/PreviewPrimaryTerminalMismatch.t.sol), and [`test/regression/CashOutCircularPrimaryTerminal.t.sol`](../test/regression/CashOutCircularPrimaryTerminal.t.sol) for the route-selection and refund traps most likely to regress.
|
|
32
|
+
- [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for pinned edge cases.
|
package/references/runtime.md
CHANGED
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
- [`test/RouterTerminalCashOutFork.t.sol`](../test/RouterTerminalCashOutFork.t.sol) and [`test/RouterTerminalCreditCashout.t.sol`](../test/RouterTerminalCreditCashout.t.sol) for cash-out routing.
|
|
32
32
|
- [`test/RouterTerminalReentrancy.t.sol`](../test/RouterTerminalReentrancy.t.sol) for callback and reentrancy-sensitive behavior.
|
|
33
33
|
- [`test/RouterTerminalFork.t.sol`](../test/RouterTerminalFork.t.sol), [`test/RouterTerminalMultihopFork.t.sol`](../test/RouterTerminalMultihopFork.t.sol), and [`test/invariant/RouterTerminalInvariant.t.sol`](../test/invariant/RouterTerminalInvariant.t.sol) for live routing assumptions.
|
|
34
|
-
- [`test/
|
|
34
|
+
- [`test/regression/CashOutCircularPrimaryTerminal.t.sol`](../test/regression/CashOutCircularPrimaryTerminal.t.sol), [`test/regression/CashOutFallbackPrefersRecursiveLoop.t.sol`](../test/regression/CashOutFallbackPrefersRecursiveLoop.t.sol), [`test/regression/LeftoverRefund.t.sol`](../test/regression/LeftoverRefund.t.sol), and [`test/regression/PreviewPrimaryTerminalMismatch.t.sol`](../test/regression/PreviewPrimaryTerminalMismatch.t.sol) for the misdiagnosis-prone edge cases.
|
|
@@ -32,18 +32,18 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
32
32
|
/// @notice The directory storing project terminal relationships, cached from the router at construction time.
|
|
33
33
|
IJBDirectory public immutable DIRECTORY;
|
|
34
34
|
|
|
35
|
-
/// @notice The
|
|
36
|
-
IWETH9 public immutable
|
|
35
|
+
/// @notice The ERC-20 wrapper for the chain's native token, cached from the router at construction time.
|
|
36
|
+
IWETH9 public immutable WRAPPED_NATIVE_TOKEN;
|
|
37
37
|
|
|
38
38
|
//*********************************************************************//
|
|
39
39
|
// -------------------------- constructor ---------------------------- //
|
|
40
40
|
//*********************************************************************//
|
|
41
41
|
|
|
42
42
|
/// @param directory The directory storing project terminal relationships.
|
|
43
|
-
/// @param weth The
|
|
43
|
+
/// @param weth The ERC-20 wrapper for the chain's native token, used for router token normalization.
|
|
44
44
|
constructor(IJBDirectory directory, IWETH9 weth) {
|
|
45
45
|
DIRECTORY = directory;
|
|
46
|
-
|
|
46
|
+
WRAPPED_NATIVE_TOKEN = weth;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
//*********************************************************************//
|
|
@@ -56,7 +56,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
56
56
|
/// @param terminals The terminal list already fetched for the destination project.
|
|
57
57
|
/// @return tokens The unique candidate tokens that can be paid into the project.
|
|
58
58
|
/// @return count The number of populated entries in `tokens`.
|
|
59
|
-
// slither-disable-next-line calls-loop
|
|
60
59
|
function _candidatePayRouteTokens(
|
|
61
60
|
IJBDirectory directory,
|
|
62
61
|
uint256 projectId,
|
|
@@ -145,7 +144,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
145
144
|
/// @param tokenIn The input token to find a route from.
|
|
146
145
|
/// @return tokenOut The best accepted token found.
|
|
147
146
|
/// @return destTerminal The terminal that accepts `tokenOut`.
|
|
148
|
-
// slither-disable-next-line calls-loop
|
|
149
147
|
function _discoverAcceptedToken(
|
|
150
148
|
IJBPayRoutePreviewer router,
|
|
151
149
|
uint256 projectId,
|
|
@@ -188,7 +186,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
188
186
|
// Pull the candidate token out of the accounting context being inspected.
|
|
189
187
|
address candidateToken = contexts[j].token;
|
|
190
188
|
|
|
191
|
-
// Normalize the candidate so native-vs-
|
|
189
|
+
// Normalize the candidate so native-vs-wrapped comparisons behave the same as the router.
|
|
192
190
|
address normalizedCandidate = _normalizedTokenOf(candidateToken);
|
|
193
191
|
|
|
194
192
|
// Skip tokens that are equivalent to the input token because they do not require route discovery.
|
|
@@ -364,8 +362,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
364
362
|
pure
|
|
365
363
|
returns (bool exists, bytes memory data)
|
|
366
364
|
{
|
|
367
|
-
// slither-disable-next-line unused-return
|
|
368
|
-
// slither-disable-next-line unused-return
|
|
369
365
|
return JBMetadataResolver.getDataFor({id: JBMetadataResolver.getId(key, address(router)), metadata: metadata});
|
|
370
366
|
}
|
|
371
367
|
|
|
@@ -377,7 +373,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
377
373
|
// Treat exact-token matches as the same routing asset without extra normalization work.
|
|
378
374
|
if (tokenA == tokenB) return true;
|
|
379
375
|
|
|
380
|
-
// Otherwise compare normalized representations so
|
|
376
|
+
// Otherwise compare normalized representations so native and wrapped native tokens share one routing identity.
|
|
381
377
|
return _normalizedTokenOf(tokenA) == _normalizedTokenOf(tokenB);
|
|
382
378
|
}
|
|
383
379
|
|
|
@@ -404,7 +400,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
404
400
|
/// @param token The token to normalize.
|
|
405
401
|
/// @return normalizedToken The normalized token address.
|
|
406
402
|
function _normalizedTokenOf(address token) internal view returns (address normalizedToken) {
|
|
407
|
-
return token == JBConstants.NATIVE_TOKEN ? address(
|
|
403
|
+
return token == JBConstants.NATIVE_TOKEN ? address(WRAPPED_NATIVE_TOKEN) : token;
|
|
408
404
|
}
|
|
409
405
|
|
|
410
406
|
/// @notice Preview the amount that would be routed into a specific destination token.
|
|
@@ -602,7 +598,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
602
598
|
if (sourceProjectId == 0) return (resolvedTerminal, tokenIn, amount);
|
|
603
599
|
|
|
604
600
|
// Otherwise reuse the router's own preview cashout loop so preview and execution stay aligned.
|
|
605
|
-
// slither-disable-next-line unused-return
|
|
606
601
|
return router.previewCashOutLoopOf({
|
|
607
602
|
destProjectId: destProjectId,
|
|
608
603
|
token: tokenIn,
|
|
@@ -647,7 +642,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
647
642
|
address(destTerminal) == address(0)
|
|
648
643
|
|| _isCircularTerminal({router: router, projectId: projectId, terminal: destTerminal})
|
|
649
644
|
) {
|
|
650
|
-
revert JBRouterTerminal_NoRouteFound(projectId, tokenIn);
|
|
645
|
+
revert JBRouterTerminal_NoRouteFound({projectId: projectId, tokenIn: tokenIn});
|
|
651
646
|
}
|
|
652
647
|
return (tokenOut, destTerminal);
|
|
653
648
|
}
|
|
@@ -663,8 +658,8 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
663
658
|
}
|
|
664
659
|
|
|
665
660
|
// Then try the native-token and wrapped-native-token equivalent form before falling back to pool discovery.
|
|
666
|
-
if (tokenIn == JBConstants.NATIVE_TOKEN || tokenIn == address(
|
|
667
|
-
tokenOut = tokenIn == JBConstants.NATIVE_TOKEN ? address(
|
|
661
|
+
if (tokenIn == JBConstants.NATIVE_TOKEN || tokenIn == address(WRAPPED_NATIVE_TOKEN)) {
|
|
662
|
+
tokenOut = tokenIn == JBConstants.NATIVE_TOKEN ? address(WRAPPED_NATIVE_TOKEN) : JBConstants.NATIVE_TOKEN;
|
|
668
663
|
destTerminal = directory.primaryTerminalOf({projectId: projectId, token: tokenOut});
|
|
669
664
|
if (
|
|
670
665
|
address(destTerminal) != address(0)
|
|
@@ -682,7 +677,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
682
677
|
address(destTerminal) == address(0)
|
|
683
678
|
|| _isCircularTerminal({router: router, projectId: projectId, terminal: destTerminal})
|
|
684
679
|
) {
|
|
685
|
-
revert JBRouterTerminal_NoRouteFound(projectId, tokenIn);
|
|
680
|
+
revert JBRouterTerminal_NoRouteFound({projectId: projectId, tokenIn: tokenIn});
|
|
686
681
|
}
|
|
687
682
|
}
|
|
688
683
|
|
|
@@ -828,7 +823,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
828
823
|
returns (IJBTerminal candidateTerminal)
|
|
829
824
|
{
|
|
830
825
|
// Resolve the primary terminal for the candidate token so fallback discovery agrees with preview/execution.
|
|
831
|
-
// slither-disable-next-line calls-loop
|
|
832
826
|
candidateTerminal = directory.primaryTerminalOf({projectId: projectId, token: candidateToken});
|
|
833
827
|
|
|
834
828
|
// Drop candidates whose primary terminal disappeared or would route straight back into the router.
|
|
@@ -845,7 +839,6 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
845
839
|
//*********************************************************************//
|
|
846
840
|
|
|
847
841
|
/// @inheritdoc IJBPayRouteResolver
|
|
848
|
-
// slither-disable-next-line calls-loop
|
|
849
842
|
function previewBestPayRoute(
|
|
850
843
|
IJBPayRoutePreviewer router,
|
|
851
844
|
uint256 projectId,
|
|
@@ -885,7 +878,7 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
885
878
|
address(destTerminal) == address(0)
|
|
886
879
|
|| _isCircularTerminal({router: router, projectId: projectId, terminal: destTerminal})
|
|
887
880
|
) {
|
|
888
|
-
revert JBRouterTerminal_NoRouteFound(projectId, tokenIn);
|
|
881
|
+
revert JBRouterTerminal_NoRouteFound({projectId: projectId, tokenIn: tokenIn});
|
|
889
882
|
}
|
|
890
883
|
|
|
891
884
|
// Score the explicitly requested route directly instead of scanning every accepted token.
|