From 96af5429232a851a7e7144b8a30843a3e6dc980e Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 29 Jan 2025 10:20:24 -0500 Subject: [PATCH] feat: fixed USV3 Verification - We cannot use the regular v3-periphery library because of outdated solc versions. I've copied the v3-updated library which we have used in our previous project --- .gitmodules | 3 + foundry/foundry.toml | 1 + foundry/lib/v3-core | 1 + foundry/lib/v3-periphery | 1 - .../lib/v3-updated/CallbackValidationV2.sol | 39 ++++++++++++ foundry/lib/v3-updated/PoolAddressV2.sol | 59 +++++++++++++++++++ foundry/remappings.txt | 4 +- foundry/src/TychoRouter.sol | 24 ++++---- foundry/test/TychoRouterTestSetup.sol | 2 +- 9 files changed, 121 insertions(+), 13 deletions(-) create mode 160000 foundry/lib/v3-core delete mode 160000 foundry/lib/v3-periphery create mode 100644 foundry/lib/v3-updated/CallbackValidationV2.sol create mode 100644 foundry/lib/v3-updated/PoolAddressV2.sol diff --git a/.gitmodules b/.gitmodules index 61440eb..608f2c8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "foundry/lib/balancer-v2-monorepo"] path = foundry/lib/balancer-v2-monorepo url = https://github.com/balancer/balancer-v2-monorepo +[submodule "foundry/lib/v3-core"] + path = foundry/lib/v3-core + url = https://github.com/Uniswap/v3-core diff --git a/foundry/foundry.toml b/foundry/foundry.toml index a3c8d7b..b1f3da6 100644 --- a/foundry/foundry.toml +++ b/foundry/foundry.toml @@ -3,6 +3,7 @@ src = 'src' out = 'out' libs = ['lib'] solc = "0.8.28" +auto_detect_solc = true # Allows per-file version detection evm_version = 'shanghai' optimizer = true optimizer_runs = 1000 diff --git a/foundry/lib/v3-core b/foundry/lib/v3-core new file mode 160000 index 0000000..d8b1c63 --- /dev/null +++ b/foundry/lib/v3-core @@ -0,0 +1 @@ +Subproject commit d8b1c635c275d2a9450bd6a78f3fa2484fef73eb diff --git a/foundry/lib/v3-periphery b/foundry/lib/v3-periphery deleted file mode 160000 index 80f26c8..0000000 --- a/foundry/lib/v3-periphery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 80f26c86c57b8a5e4b913f42844d4c8bd274d058 diff --git a/foundry/lib/v3-updated/CallbackValidationV2.sol b/foundry/lib/v3-updated/CallbackValidationV2.sol new file mode 100644 index 0000000..b673dd2 --- /dev/null +++ b/foundry/lib/v3-updated/CallbackValidationV2.sol @@ -0,0 +1,39 @@ +// Updated v3 lib to solidity >=0.7.6 + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.6; + +import "./PoolAddressV2.sol"; +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; + +/// @notice Provides validation for callbacks from Uniswap V3 Pools +library CallbackValidationV2 { + /// @notice Returns the address of a valid Uniswap V3 Pool + /// @param factory The contract address of the Uniswap V3 factory + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The V3 pool contract address + function verifyCallback( + address factory, + address tokenA, + address tokenB, + uint24 fee + ) internal view returns (IUniswapV3Pool pool) { + return verifyCallback( + factory, PoolAddressV2.getPoolKey(tokenA, tokenB, fee) + ); + } + + /// @notice Returns the address of a valid Uniswap V3 Pool + /// @param factory The contract address of the Uniswap V3 factory + /// @param poolKey The identifying key of the V3 pool + /// @return pool The V3 pool contract address + function verifyCallback( + address factory, + PoolAddressV2.PoolKey memory poolKey + ) internal view returns (IUniswapV3Pool pool) { + pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey)); + require(msg.sender == address(pool), "CV"); + } +} diff --git a/foundry/lib/v3-updated/PoolAddressV2.sol b/foundry/lib/v3-updated/PoolAddressV2.sol new file mode 100644 index 0000000..c63aae7 --- /dev/null +++ b/foundry/lib/v3-updated/PoolAddressV2.sol @@ -0,0 +1,59 @@ +// Updated v3 lib to solidity >=0.7.6 + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee +library PoolAddressV2 { + bytes32 internal constant POOL_INIT_CODE_HASH = + 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + /// @notice The identifying key of the pool + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param tokenA The first token of a pool, unsorted + /// @param tokenB The second token of a pool, unsorted + /// @param fee The fee level of the pool + /// @return Poolkey The pool details with ordered token0 and token1 assignments + function getPoolKey(address tokenA, address tokenB, uint24 fee) + internal + pure + returns (PoolKey memory) + { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + /// @notice Deterministically computes the pool address given the factory and PoolKey + /// @param factory The Uniswap V3 factory contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddress(address factory, PoolKey memory key) + internal + pure + returns (address pool) + { + require(key.token0 < key.token1); + pool = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256( + abi.encode(key.token0, key.token1, key.fee) + ), + POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} diff --git a/foundry/remappings.txt b/foundry/remappings.txt index ce5c70e..2d2e7e6 100644 --- a/foundry/remappings.txt +++ b/foundry/remappings.txt @@ -3,4 +3,6 @@ @permit2/=lib/permit2/ @src/=src/ @uniswap-v2/=lib/v2-core/ -@balancer-labs/v2-interfaces=lib/balancer-v2-monorepo/pkg/interfaces \ No newline at end of file +@balancer-labs/v2-interfaces=lib/balancer-v2-monorepo/pkg/interfaces +@uniswap/v3-updated/=lib/v3-updated/ +@uniswap/v3-core/=lib/v3-core/ \ No newline at end of file diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 3ae63b0..52b00e1 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -10,6 +10,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@uniswap/v3-updated/CallbackValidationV2.sol"; import "./ExecutionDispatcher.sol"; import "./CallbackVerificationDispatcher.sol"; import {LibSwap} from "../lib/LibSwap.sol"; @@ -65,6 +66,10 @@ contract TychoRouter is permit2 = IAllowanceTransfer(_permit2); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _weth = IWETH(weth); + + if (usv3Factory == address(0)) { + revert TychoRouter__AddressZero(); + } _usv3Factory = usv3Factory; } @@ -131,7 +136,9 @@ contract TychoRouter is bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - require(receiver != address(0), "Invalid receiver address"); + if (receiver == address(0)) { + revert TychoRouter__AddressZero(); + } // For native ETH, assume funds already in our router. Else, transfer and handle approval. if (wrapEth) { @@ -173,8 +180,8 @@ contract TychoRouter is { uint256 currentAmountIn; uint256 currentAmountOut; - uint8 tokenInIndex; - uint8 tokenOutIndex; + uint8 tokenInIndex = 0; + uint8 tokenOutIndex = 0; uint24 split; bytes calldata swapData; @@ -362,17 +369,14 @@ contract TychoRouter is int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) - internal - pure - returns (uint256 amountOwed, address tokenOwed) - { + ) internal view returns (uint256 amountOwed, address tokenOwed) { address tokenIn = address(bytes20(data[0:20])); address tokenOut = address(bytes20(data[20:40])); - uint24 fee = uint24(bytes3(data[40:43])); + uint24 poolFee = uint24(bytes3(data[40:43])); + // slither-disable-next-line unused-return CallbackValidationV2.verifyCallback( - _usv3Factory, tokenIn, tokenOut, fee + _usv3Factory, tokenIn, tokenOut, poolFee ); amountOwed = diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 53b74b9..a4404c0 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -42,7 +42,7 @@ contract TychoRouterTestSetup is Test, Constants { vm.startPrank(ADMIN); tychoRouter = - new TychoRouterExposed(permit2Address, WETH_ADDR, address(0)); + new TychoRouterExposed(permit2Address, WETH_ADDR, address(1)); tychoRouterAddr = address(tychoRouter); tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER); tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER);