diff --git a/contracts/callers/UniswapV3Caller.sol b/contracts/callers/UniswapV3Caller.sol index c6591f80..1ef34ccf 100644 --- a/contracts/callers/UniswapV3Caller.sol +++ b/contracts/callers/UniswapV3Caller.sol @@ -9,14 +9,22 @@ import { Base } from "../shared/Base.sol"; import { TokensHandler } from "../shared/TokensHandler.sol"; import { Weth } from "../shared/Weth.sol"; // solhint-disable-next-line -import { CallbackValidation } from "@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol"; -import { TickMath } from "@uniswap/v3-core/contracts/libraries/TickMath.sol"; +import { CallbackValidation } from "./helpers/uniswap/CallbackValidation.sol"; +import { TickMath } from "./helpers/uniswap/TickMath.sol"; // solhint-disable-next-line -import { UniswapV3Swap } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; +import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; +import { Path } from "./helpers/uniswap/Path.sol"; contract UniswapV3Caller is TokensHandler, Weth { + using Path for bytes; + address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + struct SwapCallbackData { + bytes path; + address payer; + } + constructor(address _weth) Weth(_weth) {} function callBytes(bytes calldata callerCallData) external { @@ -30,7 +38,6 @@ contract UniswapV3Caller is TokensHandler, Weth { if (inputToken == ETH) { depositEth(fixedSideAmount); - inputToken = getWeth(); } if (isExactInput) { @@ -39,16 +46,14 @@ contract UniswapV3Caller is TokensHandler, Weth { exactOutputSwap(inputToken, outputToken, pool, fixedSideAmount); } - if (outputToken == ETH) { - withdrawEth(); - Base.transfer(ETH, msg.sender, Base.getBalance(ETH)); - } else { - Base.transfer(outputToken, msg.sender, Base.getBalance(outputToken)); - } + // Unwrap weth if necessary + if (outputToken == ETH) withdrawEth(); - if (inputToken != ETH) { - Base.transfer(inputToken, msg.sender, Base.getBalance(inputToken)); - } + // In case of non-zero input token, transfer the remaining amount back to `msg.sender` + Base.transfer(inputToken, msg.sender, Base.getBalance(inputToken)); + + // In case of non-zero output token, transfer the total balance to `msg.sender` + Base.transfer(outputToken, msg.sender, Base.getBalance(outputToken)); } function exactInputSwap( @@ -65,7 +70,7 @@ contract UniswapV3Caller is TokensHandler, Weth { address(this), inputToken < outputToken, int256(amountIn), - inputToken < outputToken ? -TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, abi.encode(inputToken, outputToken) ); } @@ -78,27 +83,13 @@ contract UniswapV3Caller is TokensHandler, Weth { ) internal { IUniswapV3Pool v3Pool = IUniswapV3Pool(pool); - int256 amountInMaximum = int256( - calculateMaxInput(inputToken, outputToken, pool, amountOut) - ); - - SafeERC20.safeApprove(IERC20(inputToken), pool, uint256(amountInMaximum)); - (int256 amount0, int256 amount1) = v3Pool.swap( address(this), inputToken < outputToken, -int256(amountOut), - inputToken < outputToken ? -TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, abi.encode(inputToken, outputToken) ); - - // Refund any excess tokens - uint256 refundAmount = uint256( - amountInMaximum - (inputToken < outputToken ? amount0 : amount1) - ); - if (refundAmount > 0) { - SafeERC20.safeTransfer(IERC20(inputToken), msg.sender, refundAmount); - } } function uniswapV3SwapCallback( @@ -106,30 +97,17 @@ contract UniswapV3Caller is TokensHandler, Weth { int256 amount1Delta, bytes calldata data ) external { - (address inputToken, address outputToken) = abi.decode(data, (address, address)); - - if (amount0Delta > 0) { - SafeERC20.safeTransfer(IERC20(inputToken), msg.sender, uint256(amount0Delta)); - } else { - SafeERC20.safeTransfer(IERC20(outputToken), msg.sender, uint256(amount1Delta)); - } - } - - function calculateMaxInput( - address inputToken, - address outputToken, - address pool, - uint256 amountOut - ) internal view returns (uint256 memory maxInput) { - IUniswapV3Pool v3Pool = IUniswapV3Pool(pool); + SwapCallbackData memory callbackData = abi.decode(data, (SwapCallbackData)); + (address tokenIn, address tokenOut, ) = callbackData.path.decodeFirstPool(); - (uint160 sqrtRatioX96, , , , , , ) = v3Pool.slot0(); - uint256 price = (sqrtRatioX96 * sqrtRatioX96) / (2 ** 96); + (bool isExactInput, uint256 amountToPay) = amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); - if (inputToken < outputToken) { - return (amountOut * price) / 1e18; + if (isExactInput) { + Base.transfer(tokenIn, msg.sender, amountToPay); } else { - return (amountOut * 1e18) / price; + Base.transfer(tokenOut, msg.sender, amountToPay); } } diff --git a/contracts/callers/helpers/uniswap/BytesLib.sol b/contracts/callers/helpers/uniswap/BytesLib.sol new file mode 100644 index 00000000..bb163e76 --- /dev/null +++ b/contracts/callers/helpers/uniswap/BytesLib.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity 0.8.12; + +library BytesLib { + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) internal pure returns (bytes memory) { + require(_length + 31 >= _length, "slice_overflow"); + require(_start + _length >= _start, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add( + add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), + _start + ) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_start + 20 >= _start, "toAddress_overflow"); + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { + require(_start + 3 >= _start, "toUint24_overflow"); + require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } +} diff --git a/contracts/callers/helpers/uniswap/CallbackValidation.sol b/contracts/callers/helpers/uniswap/CallbackValidation.sol new file mode 100644 index 00000000..9b1d8b8e --- /dev/null +++ b/contracts/callers/helpers/uniswap/CallbackValidation.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.12; + +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import "./PoolAddress.sol"; + +/// @notice Provides validation for callbacks from Uniswap V3 Pools +library CallbackValidation { + /// @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, PoolAddress.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, + PoolAddress.PoolKey memory poolKey + ) internal view returns (IUniswapV3Pool pool) { + pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + require(msg.sender == address(pool)); + } +} diff --git a/contracts/callers/helpers/uniswap/Path.sol b/contracts/callers/helpers/uniswap/Path.sol new file mode 100644 index 00000000..c058c437 --- /dev/null +++ b/contracts/callers/helpers/uniswap/Path.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.12; + +import "./BytesLib.sol"; + +/// @title Functions for manipulating path data for multihop swaps +library Path { + using BytesLib for bytes; + + /// @dev The length of the bytes encoded address + uint256 private constant ADDR_SIZE = 20; + /// @dev The length of the bytes encoded fee + uint256 private constant FEE_SIZE = 3; + + /// @dev The offset of a single token address and pool fee + uint256 private constant NEXT_OFFSET = ADDR_SIZE + FEE_SIZE; + + /// @notice Decodes the first pool in path + /// @param path The bytes encoded swap path + /// @return tokenA The first token of the given pool + /// @return tokenB The second token of the given pool + /// @return fee The fee level of the pool + function decodeFirstPool( + bytes memory path + ) internal pure returns (address tokenA, address tokenB, uint24 fee) { + tokenA = path.toAddress(0); + fee = path.toUint24(ADDR_SIZE); + tokenB = path.toAddress(NEXT_OFFSET); + } +} diff --git a/contracts/callers/helpers/uniswap/PoolAddress.sol b/contracts/callers/helpers/uniswap/PoolAddress.sol new file mode 100644 index 00000000..ff542b0f --- /dev/null +++ b/contracts/callers/helpers/uniswap/PoolAddress.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.12; + +/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee +library PoolAddress { + 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/contracts/callers/helpers/uniswap/TickMath.sol b/contracts/callers/helpers/uniswap/TickMath.sol new file mode 100644 index 00000000..9d0d5f5b --- /dev/null +++ b/contracts/callers/helpers/uniswap/TickMath.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.12; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(uint24(MAX_TICK)), "T"); + + uint256 ratio = absTick & 0x1 != 0 + ? 0xfffcb933bd6fad37aa2d162d1a594001 + : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R"); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 + ? tickHi + : tickLow; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index d545b371..aec6e004 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -36,7 +36,8 @@ const config: HardhatUserConfig = { // }, hardhat: { forking: { - url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, + // url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, + url: "https://rpc.zerion.io/v1/ethereum" }, blockGasLimit: 10000000, gas: 10000000, diff --git a/test/callers/UniswapV3Caller.js b/test/callers/UniswapV3Caller.js new file mode 100644 index 00000000..9a862683 --- /dev/null +++ b/test/callers/UniswapV3Caller.js @@ -0,0 +1,107 @@ +import buyTokenOnUniswap from '../helpers/buyTokenOnUniswap'; +import { wethAddress, ethAddress, daiAddress } from '../helpers/tokens'; + +const { ethers } = require('hardhat'); + +const AMOUNT_ABSOLUTE = 2; +const SWAP_FIXED_INPUTS = 1; +const EMPTY_BYTES = '0x'; + +const uniDaiWethAddress = '0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8'; + +const zeroPermit = ['0', EMPTY_BYTES]; +const zeroSignature = ['0', EMPTY_BYTES]; + +describe('UniswapV3Caller', () => { + let owner; + let notOwner; + let caller; + let Router; + let Caller; + let router; + // let weth; + let dai; + let protocolFeeDefault; + const logger = new ethers.utils.Logger('1'); + const abiCoder = new ethers.utils.AbiCoder(); + + before(async () => { + // await network.provider.request({ + // method: "hardhat_reset", + // params: [ + // { + // forking: { + // jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, + // }, + // }, + // ], + // }); + + Caller = await ethers.getContractFactory('UniswapV3Caller'); + Router = await ethers.getContractFactory('Router'); + + [owner, notOwner] = await ethers.getSigners(); + + const weth9 = await ethers.getContractAt('IWETH9', wethAddress); + + await weth9.deposit({ + value: ethers.utils.parseEther('2'), + gasLimit: 1000000, + }); + + caller = await Caller.deploy(wethAddress); + + // weth = await ethers.getContractAt('IERC20', wethAddress, owner); + dai = await ethers.getContractAt('IERC20', daiAddress, owner); + + await buyTokenOnUniswap(owner, daiAddress); + protocolFeeDefault = [ethers.utils.parseUnits('0.01', 18), notOwner.address]; + }); + + beforeEach(async () => { + router = await Router.deploy(); + }); + + it.only('should do eth -> dai trade', async () => { + await router.setProtocolFeeDefault(protocolFeeDefault); + + logger.info( + `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, + ); + const tx = await router.functions.execute( + // input + [[ethAddress, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], + // output + [daiAddress, ethers.utils.parseUnits('1000', 18)], + // swap description + [ + SWAP_FIXED_INPUTS, + protocolFeeDefault, + protocolFeeDefault, + owner.address, + caller.address, + abiCoder.encode( + ['address', 'address', 'address', 'uint256', 'bool'], + [ + ethAddress, + daiAddress, + uniDaiWethAddress, + ethers.utils.parseUnits('1', 18), + false, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, + { + value: ethers.utils.parseEther('1'), + }, + ); + logger.info(`Called router for ${(await tx.wait()).gasUsed} gas`); + logger.info( + `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, + ); + }); +});