@bananapus/router-terminal-v6 0.0.43 → 0.0.45
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/package.json +4 -4
- package/script/Deploy.s.sol +4 -4
- package/src/JBPayRouteResolver.sol +23 -15
- package/src/JBRouterTerminal.sol +48 -47
- package/src/JBRouterTerminalRegistry.sol +21 -7
- package/src/interfaces/IJBPayRoutePreviewer.sol +1 -1
- package/src/structs/DefaultTerminalSegment.sol +10 -4
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.45",
|
|
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.
|
|
30
|
-
"@bananapus/core-v6": "^0.0.
|
|
29
|
+
"@bananapus/buyback-hook-v6": "^0.0.47",
|
|
30
|
+
"@bananapus/core-v6": "^0.0.54",
|
|
31
31
|
"@bananapus/permission-ids-v6": "^0.0.22",
|
|
32
|
-
"@bananapus/univ4-router-v6": "^0.0.
|
|
32
|
+
"@bananapus/univ4-router-v6": "^0.0.32",
|
|
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/script/Deploy.s.sol
CHANGED
|
@@ -186,10 +186,10 @@ contract DeployScript is Script, Sphinx {
|
|
|
186
186
|
deployer: safeAddress()
|
|
187
187
|
});
|
|
188
188
|
terminal.setChainSpecificConstants({
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
newWrappedNativeToken: IWETH9(weth),
|
|
190
|
+
newFactory: IUniswapV3Factory(factory),
|
|
191
|
+
newPoolManager: IPoolManager(poolManager),
|
|
192
|
+
newUniv4Hook: address(univ4Router.hook)
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
// Set the terminal as the default for the registry.
|
|
@@ -366,7 +366,9 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
366
366
|
pure
|
|
367
367
|
returns (bool exists, bytes memory data)
|
|
368
368
|
{
|
|
369
|
-
return JBMetadataResolver.getDataFor({
|
|
369
|
+
return JBMetadataResolver.getDataFor({
|
|
370
|
+
id: JBMetadataResolver.getId({purpose: key, target: address(router)}), metadata: metadata
|
|
371
|
+
});
|
|
370
372
|
}
|
|
371
373
|
|
|
372
374
|
/// @notice Check whether two tokens share the same routing representation for the router.
|
|
@@ -918,17 +920,17 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
918
920
|
if (_isCircularTerminal({router: router, projectId: projectId, terminal: candidateTerminal})) continue;
|
|
919
921
|
|
|
920
922
|
// Isolate each candidate preview so one broken route does not brick the whole search.
|
|
921
|
-
try self.previewPayRouteForCandidate(
|
|
922
|
-
router,
|
|
923
|
-
wrappedNativeToken,
|
|
924
|
-
projectId,
|
|
925
|
-
tokenIn,
|
|
926
|
-
amount,
|
|
927
|
-
beneficiary,
|
|
928
|
-
metadata,
|
|
929
|
-
candidateTokens[i],
|
|
930
|
-
candidateTerminal
|
|
931
|
-
) returns (
|
|
923
|
+
try self.previewPayRouteForCandidate({
|
|
924
|
+
router: router,
|
|
925
|
+
wrappedNativeToken: wrappedNativeToken,
|
|
926
|
+
projectId: projectId,
|
|
927
|
+
tokenIn: tokenIn,
|
|
928
|
+
amount: amount,
|
|
929
|
+
beneficiary: beneficiary,
|
|
930
|
+
metadata: metadata,
|
|
931
|
+
tokenOut: candidateTokens[i],
|
|
932
|
+
destTerminal: candidateTerminal
|
|
933
|
+
}) returns (
|
|
932
934
|
IJBTerminal candidateDestTerminal,
|
|
933
935
|
address candidateTokenOut,
|
|
934
936
|
uint256 candidateAmountOut,
|
|
@@ -977,9 +979,15 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
|
|
|
977
979
|
// No candidate token could be scored — fall back to the router's generic route resolution.
|
|
978
980
|
// Uses an external self-call (`self.previewFallbackRoute`) so Solidity's try/catch can isolate
|
|
979
981
|
// reverts from broken terminals or price feeds without bricking the entire best-route preview.
|
|
980
|
-
try self.previewFallbackRoute(
|
|
981
|
-
router,
|
|
982
|
-
|
|
982
|
+
try self.previewFallbackRoute({
|
|
983
|
+
routePreviewer: router,
|
|
984
|
+
wrappedNativeToken: wrappedNativeToken,
|
|
985
|
+
destProjectId: projectId,
|
|
986
|
+
tokenIn: tokenIn,
|
|
987
|
+
amountIn: amount,
|
|
988
|
+
beneficiary: beneficiary,
|
|
989
|
+
metadata: metadata
|
|
990
|
+
}) returns (
|
|
983
991
|
IJBTerminal fallbackDestTerminal,
|
|
984
992
|
address fallbackTokenOut,
|
|
985
993
|
uint256 fallbackAmountOut,
|
package/src/JBRouterTerminal.sol
CHANGED
|
@@ -145,7 +145,7 @@ contract JBRouterTerminal is
|
|
|
145
145
|
|
|
146
146
|
/// @notice The helper contract used to resolve best pay-route previews without bloating router runtime size.
|
|
147
147
|
/// @dev Deployed in the constructor with chain-same inputs (just `directory` — the resolver does NOT cache
|
|
148
|
-
/// `
|
|
148
|
+
/// `wrappedNativeToken` locally; the router passes it in on every external resolver call as a parameter to
|
|
149
149
|
/// avoid an extra external call on each normalization step). Because this router's address is chain-same via
|
|
150
150
|
/// CREATE2 and the resolver is deployed at the router's nonce 1, the resolver's address is chain-same too.
|
|
151
151
|
IJBPayRouteResolver internal immutable _PAY_ROUTE_RESOLVER;
|
|
@@ -167,23 +167,23 @@ contract JBRouterTerminal is
|
|
|
167
167
|
/// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
|
|
168
168
|
/// constructor inputs are byte-identical on every chain (Uniswap V3 deploys to a different factory address per
|
|
169
169
|
/// chain).
|
|
170
|
-
IUniswapV3Factory public
|
|
170
|
+
IUniswapV3Factory public factory;
|
|
171
171
|
|
|
172
172
|
/// @notice The Uniswap V4 PoolManager. Can be `address(0)` if V4 is not deployed on this chain.
|
|
173
173
|
/// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
|
|
174
174
|
/// constructor inputs are byte-identical on every chain.
|
|
175
|
-
IPoolManager public
|
|
175
|
+
IPoolManager public poolManager;
|
|
176
176
|
|
|
177
177
|
/// @notice The canonical Uniswap V4 router hook address used by supported hooked pools.
|
|
178
178
|
/// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable because
|
|
179
179
|
/// `JBUniswapV4Hook` inherits Uniswap's `BaseHook -> ImmutableState`, which forces a chain-specific PoolManager
|
|
180
180
|
/// immutable inside the hook itself — making the hook chain-different by design.
|
|
181
|
-
address public
|
|
181
|
+
address public univ4Hook;
|
|
182
182
|
|
|
183
183
|
/// @notice The ERC-20 wrapper for the native token.
|
|
184
184
|
/// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
|
|
185
185
|
/// constructor inputs are byte-identical on every chain (WETH/WCELO/etc. differ per chain).
|
|
186
|
-
IWETH9 public override
|
|
186
|
+
IWETH9 public override wrappedNativeToken;
|
|
187
187
|
|
|
188
188
|
//*********************************************************************//
|
|
189
189
|
// ---------------------- internal stored properties ----------------- //
|
|
@@ -393,29 +393,30 @@ contract JBRouterTerminal is
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/// @notice One-shot setter for the chain-specific Uniswap and wrapped-native addresses.
|
|
396
|
-
/// @dev Callable only by `_DEPLOYER` and only once (when `
|
|
396
|
+
/// @dev Callable only by `_DEPLOYER` and only once (when `wrappedNativeToken` is still `address(0)`). After this
|
|
397
397
|
/// call all four values are effectively immutable for the contract's lifetime. Mirrors the
|
|
398
398
|
/// `JBOptimismSuckerDeployer.setChainSpecificConstants` pattern so the contract's CREATE2 inputs stay
|
|
399
399
|
/// byte-identical across chains and its deployed address is unified.
|
|
400
|
-
/// @param
|
|
400
|
+
/// @param newWrappedNativeToken The ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum,
|
|
401
401
|
/// WCELO on Celo).
|
|
402
|
-
/// @param
|
|
403
|
-
/// @param
|
|
404
|
-
///
|
|
402
|
+
/// @param newFactory The Uniswap V3 factory for pool discovery on this chain.
|
|
403
|
+
/// @param newPoolManager The Uniswap V4 PoolManager on this chain (may be `address(0)` if V4 is not deployed
|
|
404
|
+
/// there).
|
|
405
|
+
/// @param newUniv4Hook The canonical Uniswap V4 router hook on this chain.
|
|
405
406
|
function setChainSpecificConstants(
|
|
406
|
-
IWETH9
|
|
407
|
-
IUniswapV3Factory
|
|
408
|
-
IPoolManager
|
|
409
|
-
address
|
|
407
|
+
IWETH9 newWrappedNativeToken,
|
|
408
|
+
IUniswapV3Factory newFactory,
|
|
409
|
+
IPoolManager newPoolManager,
|
|
410
|
+
address newUniv4Hook
|
|
410
411
|
)
|
|
411
412
|
external
|
|
412
413
|
{
|
|
413
414
|
if (msg.sender != _DEPLOYER) revert JBRouterTerminal_Unauthorized({caller: msg.sender});
|
|
414
|
-
if (address(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
415
|
+
if (address(wrappedNativeToken) != address(0)) revert JBRouterTerminal_AlreadyConfigured();
|
|
416
|
+
wrappedNativeToken = newWrappedNativeToken;
|
|
417
|
+
factory = newFactory;
|
|
418
|
+
poolManager = newPoolManager;
|
|
419
|
+
univ4Hook = newUniv4Hook;
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
/// @notice The Uniswap v3 pool callback where the token transfer is expected to happen.
|
|
@@ -451,7 +452,7 @@ contract JBRouterTerminal is
|
|
|
451
452
|
/// @param data Encoded swap parameters.
|
|
452
453
|
/// @return Encoded output amount.
|
|
453
454
|
function unlockCallback(bytes calldata data) external override returns (bytes memory) {
|
|
454
|
-
if (msg.sender != address(
|
|
455
|
+
if (msg.sender != address(poolManager)) revert JBRouterTerminal_CallerNotPoolManager(msg.sender);
|
|
455
456
|
|
|
456
457
|
// Decode the swap parameters.
|
|
457
458
|
(
|
|
@@ -464,7 +465,7 @@ contract JBRouterTerminal is
|
|
|
464
465
|
) = abi.decode(data, (PoolKey, bool, int256, uint160, uint256, bool));
|
|
465
466
|
|
|
466
467
|
// Execute the swap.
|
|
467
|
-
BalanceDelta delta =
|
|
468
|
+
BalanceDelta delta = poolManager.swap({
|
|
468
469
|
key: key,
|
|
469
470
|
params: SwapParams({
|
|
470
471
|
zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
@@ -895,7 +896,7 @@ contract JBRouterTerminal is
|
|
|
895
896
|
// it on every normalization step.
|
|
896
897
|
return _PAY_ROUTE_RESOLVER.previewBestPayRoute({
|
|
897
898
|
router: IJBPayRoutePreviewer(address(this)),
|
|
898
|
-
wrappedNativeToken: address(
|
|
899
|
+
wrappedNativeToken: address(wrappedNativeToken),
|
|
899
900
|
projectId: projectId,
|
|
900
901
|
tokenIn: tokenIn,
|
|
901
902
|
amount: amount,
|
|
@@ -1389,7 +1390,7 @@ contract JBRouterTerminal is
|
|
|
1389
1390
|
returns (uint256 amountOut)
|
|
1390
1391
|
{
|
|
1391
1392
|
// Convert wrapped-native-token addresses to V4's native representation (address(0)) for currency comparison.
|
|
1392
|
-
address v4In = normalizedTokenIn == address(
|
|
1393
|
+
address v4In = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
|
|
1393
1394
|
|
|
1394
1395
|
// Determine the V4 swap direction by comparing the input token to currency0 in the pool key.
|
|
1395
1396
|
bool zeroForOne = Currency.unwrap(key.currency0) == v4In;
|
|
@@ -1403,7 +1404,7 @@ contract JBRouterTerminal is
|
|
|
1403
1404
|
// The router only reaches this path with uint256 amounts that fit the signed exact-input convention.
|
|
1404
1405
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1405
1406
|
int256 exactInputAmount = -int256(amount);
|
|
1406
|
-
bytes memory result =
|
|
1407
|
+
bytes memory result = poolManager.unlock(
|
|
1407
1408
|
abi.encode(key, zeroForOne, exactInputAmount, sqrtPriceLimitX96, minAmountOut, canUseExistingNativeBalance)
|
|
1408
1409
|
);
|
|
1409
1410
|
|
|
@@ -1465,7 +1466,7 @@ contract JBRouterTerminal is
|
|
|
1465
1466
|
(bool success,) = refundTo.call{value: refundAmount}("");
|
|
1466
1467
|
if (!success) {
|
|
1467
1468
|
_wrapNativeToken(refundAmount);
|
|
1468
|
-
IERC20(address(
|
|
1469
|
+
IERC20(address(wrappedNativeToken)).safeTransfer({to: refundTo, value: refundAmount});
|
|
1469
1470
|
}
|
|
1470
1471
|
} else {
|
|
1471
1472
|
_transferFrom({from: address(this), to: refundTo, token: tokenIn, amount: refundAmount});
|
|
@@ -1508,7 +1509,7 @@ contract JBRouterTerminal is
|
|
|
1508
1509
|
// Resolve what token the destination project accepts and which terminal to use.
|
|
1509
1510
|
(tokenOut, destTerminal) = _PAY_ROUTE_RESOLVER.resolveTokenOut({
|
|
1510
1511
|
router: IJBPayRoutePreviewer(address(this)),
|
|
1511
|
-
wrappedNativeToken: address(
|
|
1512
|
+
wrappedNativeToken: address(wrappedNativeToken),
|
|
1512
1513
|
projectId: destProjectId,
|
|
1513
1514
|
tokenIn: tokenIn,
|
|
1514
1515
|
metadata: metadata
|
|
@@ -1661,13 +1662,13 @@ contract JBRouterTerminal is
|
|
|
1661
1662
|
_unwrapNativeToken(amount);
|
|
1662
1663
|
}
|
|
1663
1664
|
// Native settlement uses `msg.value` because PoolManager expects ETH to accompany the settle call.
|
|
1664
|
-
|
|
1665
|
+
poolManager.settle{value: amount}();
|
|
1665
1666
|
} else {
|
|
1666
1667
|
// ERC20 settlement requires PoolManager to observe the token first (`sync`), then receive the transfer,
|
|
1667
1668
|
// then finalize the accounting with `settle`.
|
|
1668
|
-
|
|
1669
|
-
IERC20(Currency.unwrap(currency)).safeTransfer({to: address(
|
|
1670
|
-
|
|
1669
|
+
poolManager.sync(currency);
|
|
1670
|
+
IERC20(Currency.unwrap(currency)).safeTransfer({to: address(poolManager), value: amount});
|
|
1671
|
+
poolManager.settle();
|
|
1671
1672
|
}
|
|
1672
1673
|
}
|
|
1673
1674
|
|
|
@@ -1676,7 +1677,7 @@ contract JBRouterTerminal is
|
|
|
1676
1677
|
/// @param amount The amount of `currency` to take.
|
|
1677
1678
|
function _takeV4(Currency currency, uint256 amount) internal {
|
|
1678
1679
|
// Pull the owed output asset into the router before any later wrapping/unwrapping or forwarding logic runs.
|
|
1679
|
-
|
|
1680
|
+
poolManager.take({currency: currency, to: address(this), amount: amount});
|
|
1680
1681
|
|
|
1681
1682
|
// If native token output, wrap it (downstream _handleSwap unwraps if needed).
|
|
1682
1683
|
if (Currency.unwrap(currency) == address(0)) _wrapNativeToken(amount);
|
|
@@ -1726,13 +1727,13 @@ contract JBRouterTerminal is
|
|
|
1726
1727
|
if (token == preferredToken) return (token, amount);
|
|
1727
1728
|
|
|
1728
1729
|
// Wrap native tokens when the preferred token is the ERC-20 wrapper.
|
|
1729
|
-
if (token == JBConstants.NATIVE_TOKEN && preferredToken == address(
|
|
1730
|
+
if (token == JBConstants.NATIVE_TOKEN && preferredToken == address(wrappedNativeToken)) {
|
|
1730
1731
|
_wrapNativeToken(amount);
|
|
1731
1732
|
return (preferredToken, amount);
|
|
1732
1733
|
}
|
|
1733
1734
|
|
|
1734
1735
|
// Unwrap wrapped native tokens when the preferred token is the native-token sentinel.
|
|
1735
|
-
if (token == address(
|
|
1736
|
+
if (token == address(wrappedNativeToken) && preferredToken == JBConstants.NATIVE_TOKEN) {
|
|
1736
1737
|
_unwrapNativeToken(amount);
|
|
1737
1738
|
return (preferredToken, amount);
|
|
1738
1739
|
}
|
|
@@ -1744,13 +1745,13 @@ contract JBRouterTerminal is
|
|
|
1744
1745
|
/// @notice Wrap native tokens into their ERC-20 wrapped representation.
|
|
1745
1746
|
/// @param amount The amount of native tokens to wrap.
|
|
1746
1747
|
function _wrapNativeToken(uint256 amount) internal {
|
|
1747
|
-
|
|
1748
|
+
wrappedNativeToken.deposit{value: amount}();
|
|
1748
1749
|
}
|
|
1749
1750
|
|
|
1750
1751
|
/// @notice Unwrap wrapped native tokens into native tokens.
|
|
1751
1752
|
/// @param amount The amount of wrapped native tokens to unwrap.
|
|
1752
1753
|
function _unwrapNativeToken(uint256 amount) internal {
|
|
1753
|
-
|
|
1754
|
+
wrappedNativeToken.withdraw(amount);
|
|
1754
1755
|
}
|
|
1755
1756
|
|
|
1756
1757
|
//*********************************************************************//
|
|
@@ -1763,14 +1764,14 @@ contract JBRouterTerminal is
|
|
|
1763
1764
|
/// @param fee The fee tier to query.
|
|
1764
1765
|
/// @return The pool address, or address(0) if none exists.
|
|
1765
1766
|
function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (address) {
|
|
1766
|
-
return
|
|
1767
|
+
return factory.getPool({tokenA: tokenA, tokenB: tokenB, fee: fee});
|
|
1767
1768
|
}
|
|
1768
1769
|
|
|
1769
1770
|
/// @notice Look up the in-range liquidity of a V4 pool.
|
|
1770
1771
|
/// @param id The pool ID to query.
|
|
1771
1772
|
/// @return The pool's current in-range liquidity.
|
|
1772
1773
|
function _getLiquidity(PoolId id) internal view returns (uint128) {
|
|
1773
|
-
return
|
|
1774
|
+
return poolManager.getLiquidity(id);
|
|
1774
1775
|
}
|
|
1775
1776
|
|
|
1776
1777
|
/// @notice Read slot0 from a V4 pool.
|
|
@@ -1784,7 +1785,7 @@ contract JBRouterTerminal is
|
|
|
1784
1785
|
view
|
|
1785
1786
|
returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
|
|
1786
1787
|
{
|
|
1787
|
-
return
|
|
1788
|
+
return poolManager.getSlot0(id);
|
|
1788
1789
|
}
|
|
1789
1790
|
|
|
1790
1791
|
/// @notice Look up the primary terminal for a project and token.
|
|
@@ -1942,11 +1943,11 @@ contract JBRouterTerminal is
|
|
|
1942
1943
|
updatedBestPool = bestPool;
|
|
1943
1944
|
|
|
1944
1945
|
// Exit early on chains where V4 is not deployed.
|
|
1945
|
-
if (address(
|
|
1946
|
+
if (address(poolManager) == address(0)) return updatedBestPool;
|
|
1946
1947
|
|
|
1947
1948
|
// Convert wrapped native token -> address(0) for V4 native representation.
|
|
1948
|
-
address v4In = normalizedTokenIn == address(
|
|
1949
|
-
address v4Out = normalizedTokenOut == address(
|
|
1949
|
+
address v4In = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
|
|
1950
|
+
address v4Out = normalizedTokenOut == address(wrappedNativeToken) ? address(0) : normalizedTokenOut;
|
|
1950
1951
|
|
|
1951
1952
|
// Sort currencies (currency0 < currency1).
|
|
1952
1953
|
(address sorted0, address sorted1) = v4In < v4Out ? (v4In, v4Out) : (v4Out, v4In);
|
|
@@ -1954,7 +1955,7 @@ contract JBRouterTerminal is
|
|
|
1954
1955
|
for (uint256 i; i < 4;) {
|
|
1955
1956
|
for (uint256 j; j < 2;) {
|
|
1956
1957
|
// Probe vanilla pools first, then the configured hooked-pool family if one exists.
|
|
1957
|
-
IHooks hooks = j == 0 ? IHooks(address(0)) : IHooks(
|
|
1958
|
+
IHooks hooks = j == 0 ? IHooks(address(0)) : IHooks(univ4Hook);
|
|
1958
1959
|
if (j != 0 && address(hooks) == address(0)) {
|
|
1959
1960
|
unchecked {
|
|
1960
1961
|
++j;
|
|
@@ -2245,7 +2246,7 @@ contract JBRouterTerminal is
|
|
|
2245
2246
|
/// This fallback is an accepted product risk for programmatic integrations that cannot provide an external quote,
|
|
2246
2247
|
/// but it should be understood as a bounded-convenience path rather than a fully manipulation-resistant one.
|
|
2247
2248
|
///
|
|
2248
|
-
/// SECURITY NOTE: The spot price read from `
|
|
2249
|
+
/// SECURITY NOTE: The spot price read from `poolManager.getSlot0(id)` is an instantaneous value
|
|
2249
2250
|
/// that can be manipulated within the same block (e.g. via sandwich attacks or flash loans). Unlike V3 pools,
|
|
2250
2251
|
/// V4 vanilla pools do not expose a built-in TWAP oracle, so there is no manipulation-resistant price source
|
|
2251
2252
|
/// available on-chain for automatic quoting.
|
|
@@ -2303,7 +2304,7 @@ contract JBRouterTerminal is
|
|
|
2303
2304
|
secondsAgos[1] = 0; // End of the window (current block).
|
|
2304
2305
|
|
|
2305
2306
|
// Ask the hook for cumulative tick data over the window. Silently catch if it doesn't support it.
|
|
2306
|
-
try IGeomeanOracle(address(key.hooks)).observe(key, secondsAgos) returns (
|
|
2307
|
+
try IGeomeanOracle(address(key.hooks)).observe({key: key, secondsAgos: secondsAgos}) returns (
|
|
2307
2308
|
int56[] memory tickCumulatives, uint160[] memory
|
|
2308
2309
|
) {
|
|
2309
2310
|
// Guard against malicious/broken hooks returning fewer elements than requested.
|
|
@@ -2335,8 +2336,8 @@ contract JBRouterTerminal is
|
|
|
2335
2336
|
|
|
2336
2337
|
// V4 uses address(0) for native tokens; map the wrapped native token so OracleLibrary token sorting matches the
|
|
2337
2338
|
// pool.
|
|
2338
|
-
normalizedTokenIn = normalizedTokenIn == address(
|
|
2339
|
-
normalizedTokenOut = normalizedTokenOut == address(
|
|
2339
|
+
normalizedTokenIn = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
|
|
2340
|
+
normalizedTokenOut = normalizedTokenOut == address(wrappedNativeToken) ? address(0) : normalizedTokenOut;
|
|
2340
2341
|
|
|
2341
2342
|
if (!usedTwap) {
|
|
2342
2343
|
// Without TWAP, instantaneous liquidity and spot price are both JIT-manipulable.
|
|
@@ -2403,7 +2404,7 @@ contract JBRouterTerminal is
|
|
|
2403
2404
|
/// @return normalizedToken The normalized token address.
|
|
2404
2405
|
function _normalize(address token) internal view returns (address) {
|
|
2405
2406
|
// Replace the native-token sentinel with the wrapped native token so both share one routing representation.
|
|
2406
|
-
return token == JBConstants.NATIVE_TOKEN ? address(
|
|
2407
|
+
return token == JBConstants.NATIVE_TOKEN ? address(wrappedNativeToken) : token;
|
|
2407
2408
|
}
|
|
2408
2409
|
|
|
2409
2410
|
/// @notice Discover a pool and compute the minimum acceptable output for a swap. Uses a user-provided quote if
|
|
@@ -358,14 +358,19 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
358
358
|
function _defaultTerminalFor(uint256 projectId) internal view returns (IJBTerminal terminal) {
|
|
359
359
|
// New projects (created after the most recent setDefaultTerminal) get the current default.
|
|
360
360
|
if (projectId > defaultTerminalProjectIdThreshold) return defaultTerminal;
|
|
361
|
-
|
|
361
|
+
|
|
362
|
+
// Older projects walk the history. Each segment covers a half-open range
|
|
363
|
+
// `(minProjectIdExclusive, maxProjectId]` — exactly the cohort that was issued while that segment's terminal
|
|
364
|
+
// was the active default. Projects whose IDs pre-date every recorded default (the cold-start cohort)
|
|
365
|
+
// do not match any segment and resolve to `address(0)` — preserving the documented "the default only applies
|
|
366
|
+
// to projects created AFTER it was set" property at registry cold-start.
|
|
362
367
|
uint256 len = _defaultTerminalHistory.length;
|
|
363
368
|
for (uint256 i; i < len; ++i) {
|
|
364
|
-
|
|
365
|
-
|
|
369
|
+
DefaultTerminalSegment storage segment = _defaultTerminalHistory[i];
|
|
370
|
+
if (projectId > segment.minProjectIdExclusive && projectId <= segment.maxProjectId) {
|
|
371
|
+
return segment.terminal;
|
|
366
372
|
}
|
|
367
373
|
}
|
|
368
|
-
// Falls here only if projectId predates the first setDefaultTerminal call (no default ever applied).
|
|
369
374
|
return IJBTerminal(address(0));
|
|
370
375
|
}
|
|
371
376
|
|
|
@@ -626,10 +631,19 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
626
631
|
// default is what unconfigured (and all-future) projects will resolve to.
|
|
627
632
|
_requireNonCircularTerminalFor({projectId: count, terminal: terminal});
|
|
628
633
|
|
|
629
|
-
// Snapshot the OUTGOING default so
|
|
630
|
-
//
|
|
634
|
+
// Snapshot the OUTGOING default so projects whose IDs were issued while it was active keep resolving to it.
|
|
635
|
+
// The segment encodes the half-open range `(prevThreshold, currentCount]` — i.e. exactly the cohort that was
|
|
636
|
+
// assigned the outgoing default at creation time. The first call ever has `defaultTerminal == 0` and nothing
|
|
637
|
+
// to snapshot; in that case projects whose IDs already existed remain unaffected by the new default, keeping
|
|
638
|
+
// the cold-start cohort outside any retroactive routing change.
|
|
631
639
|
if (address(defaultTerminal) != address(0)) {
|
|
632
|
-
_defaultTerminalHistory.push(
|
|
640
|
+
_defaultTerminalHistory.push(
|
|
641
|
+
DefaultTerminalSegment({
|
|
642
|
+
minProjectIdExclusive: defaultTerminalProjectIdThreshold,
|
|
643
|
+
maxProjectId: count,
|
|
644
|
+
terminal: defaultTerminal
|
|
645
|
+
})
|
|
646
|
+
);
|
|
633
647
|
}
|
|
634
648
|
|
|
635
649
|
defaultTerminal = terminal;
|
|
@@ -24,7 +24,7 @@ interface IJBPayRoutePreviewer {
|
|
|
24
24
|
|
|
25
25
|
/// @notice The ERC-20 wrapper for the chain's native token, used for router token normalization.
|
|
26
26
|
/// @return weth The ERC-20 wrapper for the chain's native token.
|
|
27
|
-
function
|
|
27
|
+
function wrappedNativeToken() external view returns (IWETH9 weth);
|
|
28
28
|
|
|
29
29
|
/// @notice Preview the recursive cashout loop the router would use for a project-token input.
|
|
30
30
|
/// @param destProjectId The destination project the router is trying to pay.
|
|
@@ -3,11 +3,17 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
5
5
|
|
|
6
|
-
/// @
|
|
7
|
-
///
|
|
8
|
-
///
|
|
9
|
-
///
|
|
6
|
+
/// @notice A segment of the default-terminal history. Each segment pins a previous default to the half-open range of
|
|
7
|
+
/// project IDs that were created while it was active. Resolution returns the segment whose
|
|
8
|
+
/// `(minProjectIdExclusive, maxProjectId]` window contains the project's ID.
|
|
9
|
+
/// @custom:member minProjectIdExclusive The threshold that was active when this segment's terminal first became the
|
|
10
|
+
/// default. Project IDs strictly greater than this fall within this segment.
|
|
11
|
+
/// @custom:member maxProjectId The threshold that was set when this segment's terminal was overwritten. Project IDs
|
|
12
|
+
/// less than or equal to this fall within this segment.
|
|
13
|
+
/// @custom:member terminal The default terminal that was active for project IDs in
|
|
14
|
+
/// `(minProjectIdExclusive, maxProjectId]`.
|
|
10
15
|
struct DefaultTerminalSegment {
|
|
16
|
+
uint256 minProjectIdExclusive;
|
|
11
17
|
uint256 maxProjectId;
|
|
12
18
|
IJBTerminal terminal;
|
|
13
19
|
}
|