From 38a8d6b901de5dbcc560ae4840250e5e2477e3a0 Mon Sep 17 00:00:00 2001 From: exception Date: Tue, 18 Jun 2024 15:08:14 -0300 Subject: [PATCH 1/4] chore: v3 forks --- contracts/callers/UniswapV3Caller.sol | 144 ++++++++++++++++++++++++++ package-lock.json | 100 ++++++++++++++++-- package.json | 6 +- 3 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 contracts/callers/UniswapV3Caller.sol diff --git a/contracts/callers/UniswapV3Caller.sol b/contracts/callers/UniswapV3Caller.sol new file mode 100644 index 00000000..c6591f80 --- /dev/null +++ b/contracts/callers/UniswapV3Caller.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.12; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import { IWETH9 } from "../interfaces/IWETH9.sol"; +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"; +// solhint-disable-next-line +import { UniswapV3Swap } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; + +contract UniswapV3Caller is TokensHandler, Weth { + address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + constructor(address _weth) Weth(_weth) {} + + function callBytes(bytes calldata callerCallData) external { + ( + address inputToken, + address outputToken, + address pool, + uint256 fixedSideAmount, + bool isExactInput + ) = abi.decode(callerCallData, (address, address, address, uint256, bool)); + + if (inputToken == ETH) { + depositEth(fixedSideAmount); + inputToken = getWeth(); + } + + if (isExactInput) { + exactInputSwap(inputToken, outputToken, pool, fixedSideAmount); + } else { + 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)); + } + + if (inputToken != ETH) { + Base.transfer(inputToken, msg.sender, Base.getBalance(inputToken)); + } + } + + function exactInputSwap( + address inputToken, + address outputToken, + address pool, + uint256 amountIn + ) internal { + IUniswapV3Pool v3Pool = IUniswapV3Pool(pool); + + SafeERC20.safeApprove(IERC20(inputToken), pool, amountIn); + + (int256 amount0, int256 amount1) = v3Pool.swap( + address(this), + inputToken < outputToken, + int256(amountIn), + inputToken < outputToken ? -TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + abi.encode(inputToken, outputToken) + ); + } + + function exactOutputSwap( + address inputToken, + address outputToken, + address pool, + uint256 amountOut + ) 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, + 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( + int256 amount0Delta, + 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); + + (uint160 sqrtRatioX96, , , , , , ) = v3Pool.slot0(); + uint256 price = (sqrtRatioX96 * sqrtRatioX96) / (2 ** 96); + + if (inputToken < outputToken) { + return (amountOut * price) / 1e18; + } else { + return (amountOut * 1e18) / price; + } + } + + function depositEth(uint256 amount) internal { + IWETH9(getWeth()).deposit{ value: amount }(); + } + + function withdrawEth() internal { + uint256 wethBalance = IERC20(getWeth()).balanceOf(address(this)); + if (wethBalance > uint256(0)) IWETH9(getWeth()).withdraw(wethBalance); + } +} diff --git a/package-lock.json b/package-lock.json index a28574d4..ccd2dc24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "hasInstallScript": true, "license": "LGPL-3.0-only", "dependencies": { - "@openzeppelin/contracts": "4.4.0" + "@openzeppelin/contracts": "4.4.0", + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.4" }, "devDependencies": { "@babel/core": "7.22.9", @@ -6016,6 +6018,50 @@ "@types/sinon": "*" } }, + "node_modules/@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", + "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz", + "integrity": "sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==", + "dependencies": { + "@openzeppelin/contracts": "3.4.2-solc-0.7", + "@uniswap/lib": "^4.0.1-alpha", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v3-core": "^1.0.0", + "base64-sol": "1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery/node_modules/@openzeppelin/contracts": { + "version": "3.4.2-solc-0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", + "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" + }, "node_modules/@vue/component-compiler-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", @@ -7457,6 +7503,11 @@ } ] }, + "node_modules/base64-sol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz", + "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==" + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -22496,7 +22547,6 @@ "resolved": "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.9.tgz", "integrity": "sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==", "dev": true, - "hasInstallScript": true, "dependencies": { "node-gyp-build": "4.3.0" } @@ -22534,7 +22584,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -22614,7 +22663,6 @@ "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", "dev": true, - "hasInstallScript": true, "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0" @@ -22625,7 +22673,6 @@ "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.0.tgz", "integrity": "sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==", "dev": true, - "hasInstallScript": true, "dependencies": { "abstract-leveldown": "^7.2.0", "napi-macros": "~2.0.0", @@ -22708,7 +22755,6 @@ "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", "dev": true, - "hasInstallScript": true, "dependencies": { "elliptic": "^6.5.2", "node-addon-api": "^2.0.0", @@ -22720,7 +22766,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -33003,7 +33048,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -33361,7 +33405,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -40482,6 +40525,40 @@ "@types/sinon": "*" } }, + "@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==" + }, + "@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==" + }, + "@uniswap/v3-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", + "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==" + }, + "@uniswap/v3-periphery": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz", + "integrity": "sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==", + "requires": { + "@openzeppelin/contracts": "3.4.2-solc-0.7", + "@uniswap/lib": "^4.0.1-alpha", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v3-core": "^1.0.0", + "base64-sol": "1.0.1" + }, + "dependencies": { + "@openzeppelin/contracts": { + "version": "3.4.2-solc-0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", + "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" + } + } + }, "@vue/component-compiler-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", @@ -41621,6 +41698,11 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "base64-sol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz", + "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", diff --git a/package.json b/package.json index 5db1dc85..58d95c4c 100755 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "license": "LGPL-3.0-only", "repository": "https://github.com/zeriontech/defi-sdk", "dependencies": { - "@openzeppelin/contracts": "4.4.0" + "@openzeppelin/contracts": "4.4.0", + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.4" }, "devDependencies": { "@babel/core": "7.22.9", @@ -42,8 +44,8 @@ "@matterlabs/hardhat-zksync-deploy": "^0.6.3", "@matterlabs/hardhat-zksync-solc": "^0.4.1", "@matterlabs/hardhat-zksync-verify": "^0.2.0", - "@nomiclabs/hardhat-ethers": "2.2.3", "@nomicfoundation/hardhat-verify": "1.1.1", + "@nomiclabs/hardhat-ethers": "2.2.3", "@nomiclabs/hardhat-waffle": "2.0.6", "@types/chai": "4.3.5", "@types/mocha": "10.0.1", From 73b9acf0109be4e559f76b50b8b4a3b16815905d Mon Sep 17 00:00:00 2001 From: exception Date: Thu, 5 Sep 2024 13:48:20 -0300 Subject: [PATCH 2/4] chore: uniswap death --- contracts/callers/UniswapV3Caller.sol | 78 +++---- .../callers/helpers/uniswap/BytesLib.sol | 104 +++++++++ .../helpers/uniswap/CallbackValidation.sol | 35 +++ contracts/callers/helpers/uniswap/Path.sol | 30 +++ .../callers/helpers/uniswap/PoolAddress.sol | 54 +++++ .../callers/helpers/uniswap/TickMath.sol | 209 ++++++++++++++++++ hardhat.config.ts | 3 +- test/callers/UniswapV3Caller.js | 107 +++++++++ 8 files changed, 569 insertions(+), 51 deletions(-) create mode 100644 contracts/callers/helpers/uniswap/BytesLib.sol create mode 100644 contracts/callers/helpers/uniswap/CallbackValidation.sol create mode 100644 contracts/callers/helpers/uniswap/Path.sol create mode 100644 contracts/callers/helpers/uniswap/PoolAddress.sol create mode 100644 contracts/callers/helpers/uniswap/TickMath.sol create mode 100644 test/callers/UniswapV3Caller.js 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 02326597..87bf4676 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)}`, + ); + }); +}); From 5be2212fbf1f7aef72e3a3c8e1b668feedfbb1f4 Mon Sep 17 00:00:00 2001 From: Igor Sobolev Date: Thu, 5 Sep 2024 23:11:16 +0100 Subject: [PATCH 3/4] finalize uni v3 caller --- .solhint.json | 11 +- contracts/callers/SimpleCaller.sol | 4 +- contracts/callers/UniswapV2Caller.sol | 5 +- contracts/callers/UniswapV3Caller.sol | 110 ++- .../callers/helpers/uniswap/BytesLib.sol | 104 --- .../helpers/uniswap/CallbackValidation.sol | 35 - contracts/callers/helpers/uniswap/Path.sol | 30 - .../callers/helpers/uniswap/PoolAddress.sol | 54 -- .../callers/helpers/uniswap/TickMath.sol | 209 ----- contracts/interfaces/ICaller.sol | 2 - contracts/interfaces/IDAIPermit.sol | 2 +- contracts/interfaces/IEIP2612.sol | 2 +- contracts/interfaces/IRouter.sol | 16 +- contracts/interfaces/ISignatureVerifier.sol | 3 - contracts/interfaces/IUniswapV2Pair.sol | 22 +- contracts/interfaces/IUniswapV2Router02.sol | 8 +- contracts/interfaces/IWETH9.sol | 4 +- contracts/interfaces/IYearnPermit.sol | 8 +- contracts/router/Router.sol | 91 +- contracts/router/SignatureVerifier.sol | 45 +- contracts/shared/Base.sol | 20 +- contracts/shared/Errors.sol | 2 +- contracts/shared/Weth.sol | 6 +- hardhat.config.ts | 2 +- package-lock.json | 791 ++++++++++++++++-- package.json | 6 +- scripts/4_deploy_uniswap_v2_caller copy.js | 23 + scripts/5_deploy_uniswap_v2_caller.js | 23 + scripts/{4_verify.js => 6_verify.js} | 6 + scripts/deployment.js | 14 + test/callers/UniswapV2Caller.js | 86 +- test/callers/UniswapV3Caller.js | 209 ++++- test/helpers/latestTime.js | 4 +- test/helpers/logger.js | 11 + test/router/Router.js | 17 + 35 files changed, 1198 insertions(+), 787 deletions(-) delete mode 100644 contracts/callers/helpers/uniswap/BytesLib.sol delete mode 100644 contracts/callers/helpers/uniswap/CallbackValidation.sol delete mode 100644 contracts/callers/helpers/uniswap/Path.sol delete mode 100644 contracts/callers/helpers/uniswap/PoolAddress.sol delete mode 100644 contracts/callers/helpers/uniswap/TickMath.sol create mode 100644 scripts/4_deploy_uniswap_v2_caller copy.js create mode 100644 scripts/5_deploy_uniswap_v2_caller.js rename scripts/{4_verify.js => 6_verify.js} (78%) create mode 100644 test/helpers/logger.js diff --git a/.solhint.json b/.solhint.json index 23c48a2c..d49667ba 100755 --- a/.solhint.json +++ b/.solhint.json @@ -5,7 +5,7 @@ "mark-callable-contracts": ["off"], "reason-string": ["error", { "maxLength": 50 }], "function-max-lines": ["error", 99], - "max-line-length": ["error", 99], + "max-line-length": ["off"], "compiler-version": ["error", "0.8.12"], "private-vars-leading-underscore": ["off"], "const-name-snakecase": ["off"], @@ -16,6 +16,13 @@ } ], "prettier/prettier": "warn", - "comprehensive-interface": ["off"] + "comprehensive-interface": ["off"], + "foundry-test-functions": ["off"], + "gas-small-strings": ["off"], + "gas-increment-by-one": ["off"], + "func-named-parameters": ["off"], + "gas-custom-errors": ["off"], + "named-parameters-mapping": ["off"], + "gas-indexed-events": ["off"] } } diff --git a/contracts/callers/SimpleCaller.sol b/contracts/callers/SimpleCaller.sol index 7c4c9568..b9a5362f 100644 --- a/contracts/callers/SimpleCaller.sol +++ b/contracts/callers/SimpleCaller.sol @@ -21,9 +21,7 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { ICaller } from "../interfaces/ICaller.sol"; import { Base } from "../shared/Base.sol"; -import { ActionType } from "../shared/Enums.sol"; -import { HighInputBalanceChange, ZeroTarget } from "../shared/Errors.sol"; -import { AbsoluteTokenAmount } from "../shared/Structs.sol"; +import { ZeroTarget } from "../shared/Errors.sol"; import { TokensHandler } from "../shared/TokensHandler.sol"; /** diff --git a/contracts/callers/UniswapV2Caller.sol b/contracts/callers/UniswapV2Caller.sol index 5593cfc7..bb60de2c 100644 --- a/contracts/callers/UniswapV2Caller.sol +++ b/contracts/callers/UniswapV2Caller.sol @@ -25,7 +25,7 @@ import { IUniswapV2Pair } from "../interfaces/IUniswapV2Pair.sol"; import { IWETH9 } from "../interfaces/IWETH9.sol"; import { Base } from "../shared/Base.sol"; import { SwapType } from "../shared/Enums.sol"; -import { BadToken, InconsistentPairsAndDirectionsLengths, InputSlippage, LowReserve, ZeroAmountIn, ZeroAmountOut, ZeroLength } from "../shared/Errors.sol"; +import { InconsistentPairsAndDirectionsLengths, InputSlippage, LowReserve, ZeroAmountIn, ZeroAmountOut, ZeroLength } from "../shared/Errors.sol"; import { TokensHandler } from "../shared/TokensHandler.sol"; import { Weth } from "../shared/Weth.sol"; @@ -68,8 +68,9 @@ contract UniswapV2Caller is ICaller, TokensHandler, Weth { uint256 length = pairs.length; if (length == uint256(0)) revert ZeroLength(); - if (directions.length != length) + if (directions.length != length) { revert InconsistentPairsAndDirectionsLengths(length, directions.length); + } uint256[] memory amounts = (swapType == SwapType.FixedInputs) ? getAmountsOut(fixedSideAmount, pairs, directions) diff --git a/contracts/callers/UniswapV3Caller.sol b/contracts/callers/UniswapV3Caller.sol index 1ef34ccf..28afc90f 100644 --- a/contracts/callers/UniswapV3Caller.sol +++ b/contracts/callers/UniswapV3Caller.sol @@ -2,48 +2,41 @@ pragma solidity 0.8.12; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import { IWETH9 } from "../interfaces/IWETH9.sol"; -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 "./helpers/uniswap/CallbackValidation.sol"; -import { TickMath } from "./helpers/uniswap/TickMath.sol"; -// solhint-disable-next-line import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; -import { Path } from "./helpers/uniswap/Path.sol"; +import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -contract UniswapV3Caller is TokensHandler, Weth { - using Path for bytes; +import { IWETH9 } from "./../interfaces/IWETH9.sol"; +import { Base } from "./../shared/Base.sol"; +import { TokensHandler } from "./../shared/TokensHandler.sol"; +import { Weth } from "./../shared/Weth.sol"; +contract UniswapV3Caller is TokensHandler, Weth, IUniswapV3SwapCallback { address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - struct SwapCallbackData { - bytes path; - address payer; + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /** + * @notice Sets Wrapped Ether address for the current chain + * @param weth Wrapped Ether address + */ + constructor(address weth) Weth(weth) { + // solhint-disable-previous-line no-empty-blocks } - constructor(address _weth) Weth(_weth) {} - function callBytes(bytes calldata callerCallData) external { ( address inputToken, address outputToken, address pool, + bool direction, uint256 fixedSideAmount, bool isExactInput - ) = abi.decode(callerCallData, (address, address, address, uint256, bool)); - - if (inputToken == ETH) { - depositEth(fixedSideAmount); - } + ) = abi.decode(callerCallData, (address, address, address, bool, uint256, bool)); if (isExactInput) { - exactInputSwap(inputToken, outputToken, pool, fixedSideAmount); + exactInputSwap(inputToken, pool, direction, fixedSideAmount); } else { - exactOutputSwap(inputToken, outputToken, pool, fixedSideAmount); + exactOutputSwap(inputToken, pool, direction, fixedSideAmount); } // Unwrap weth if necessary @@ -56,61 +49,56 @@ contract UniswapV3Caller is TokensHandler, Weth { Base.transfer(outputToken, msg.sender, Base.getBalance(outputToken)); } + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external { + address inputToken = abi.decode(data, (address)); + + if (amount0Delta > 0) { + if (inputToken == ETH) { + depositEth(uint256(amount0Delta)); + } + Base.transfer(IUniswapV3Pool(msg.sender).token0(), msg.sender, uint256(amount0Delta)); + } else { + if (inputToken == ETH) { + depositEth(uint256(amount1Delta)); + } + Base.transfer(IUniswapV3Pool(msg.sender).token1(), msg.sender, uint256(amount1Delta)); + } + } + function exactInputSwap( address inputToken, - address outputToken, address pool, + bool direction, uint256 amountIn ) internal { - IUniswapV3Pool v3Pool = IUniswapV3Pool(pool); - - SafeERC20.safeApprove(IERC20(inputToken), pool, amountIn); - - (int256 amount0, int256 amount1) = v3Pool.swap( + IUniswapV3Pool(pool).swap( address(this), - inputToken < outputToken, + direction, int256(amountIn), - inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, - abi.encode(inputToken, outputToken) + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(inputToken) ); } function exactOutputSwap( address inputToken, - address outputToken, address pool, + bool direction, uint256 amountOut ) internal { - IUniswapV3Pool v3Pool = IUniswapV3Pool(pool); - - (int256 amount0, int256 amount1) = v3Pool.swap( + IUniswapV3Pool(pool).swap( address(this), - inputToken < outputToken, + direction, -int256(amountOut), - inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, - abi.encode(inputToken, outputToken) + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(inputToken) ); } - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - SwapCallbackData memory callbackData = abi.decode(data, (SwapCallbackData)); - (address tokenIn, address tokenOut, ) = callbackData.path.decodeFirstPool(); - - (bool isExactInput, uint256 amountToPay) = amount0Delta > 0 - ? (tokenIn < tokenOut, uint256(amount0Delta)) - : (tokenOut < tokenIn, uint256(amount1Delta)); - - if (isExactInput) { - Base.transfer(tokenIn, msg.sender, amountToPay); - } else { - Base.transfer(tokenOut, msg.sender, amountToPay); - } - } - function depositEth(uint256 amount) internal { IWETH9(getWeth()).deposit{ value: amount }(); } diff --git a/contracts/callers/helpers/uniswap/BytesLib.sol b/contracts/callers/helpers/uniswap/BytesLib.sol deleted file mode 100644 index bb163e76..00000000 --- a/contracts/callers/helpers/uniswap/BytesLib.sol +++ /dev/null @@ -1,104 +0,0 @@ -// 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 deleted file mode 100644 index 9b1d8b8e..00000000 --- a/contracts/callers/helpers/uniswap/CallbackValidation.sol +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index c058c437..00000000 --- a/contracts/callers/helpers/uniswap/Path.sol +++ /dev/null @@ -1,30 +0,0 @@ -// 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 deleted file mode 100644 index ff542b0f..00000000 --- a/contracts/callers/helpers/uniswap/PoolAddress.sol +++ /dev/null @@ -1,54 +0,0 @@ -// 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 deleted file mode 100644 index 9d0d5f5b..00000000 --- a/contracts/callers/helpers/uniswap/TickMath.sol +++ /dev/null @@ -1,209 +0,0 @@ -// 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/contracts/interfaces/ICaller.sol b/contracts/interfaces/ICaller.sol index 39e6afc1..414e3e28 100644 --- a/contracts/interfaces/ICaller.sol +++ b/contracts/interfaces/ICaller.sol @@ -17,8 +17,6 @@ pragma solidity 0.8.12; -import { AbsoluteTokenAmount } from "../shared/Structs.sol"; - import { ITokensHandler } from "./ITokensHandler.sol"; interface ICaller is ITokensHandler { diff --git a/contracts/interfaces/IDAIPermit.sol b/contracts/interfaces/IDAIPermit.sol index f146f886..2ab20a8c 100644 --- a/contracts/interfaces/IDAIPermit.sol +++ b/contracts/interfaces/IDAIPermit.sol @@ -31,5 +31,5 @@ interface IDAIPermit is IERC20 { bytes32 s ) external; - function nonces(address holder) external view returns (uint256); + function nonces(address holder) external view returns (uint256 nonce); } diff --git a/contracts/interfaces/IEIP2612.sol b/contracts/interfaces/IEIP2612.sol index b74e17f2..35b0729d 100644 --- a/contracts/interfaces/IEIP2612.sol +++ b/contracts/interfaces/IEIP2612.sol @@ -30,5 +30,5 @@ interface IEIP2612 is IERC20 { bytes32 s ) external; - function nonces(address holder) external view returns (uint256); + function nonces(address holder) external view returns (uint256 nonce); } diff --git a/contracts/interfaces/IRouter.sol b/contracts/interfaces/IRouter.sol index 513e81bb..d9d33a57 100644 --- a/contracts/interfaces/IRouter.sol +++ b/contracts/interfaces/IRouter.sol @@ -17,17 +17,9 @@ pragma solidity 0.8.12; -import { - AbsoluteTokenAmount, - Input, - SwapDescription, - AccountSignature, - ProtocolFeeSignature, - Fee -} from "../shared/Structs.sol"; - -import { ITokensHandler } from "./ITokensHandler.sol"; +import { AbsoluteTokenAmount, Input, SwapDescription, AccountSignature, ProtocolFeeSignature } from "../shared/Structs.sol"; import { ISignatureVerifier } from "./ISignatureVerifier.sol"; +import { ITokensHandler } from "./ITokensHandler.sol"; interface IRouter is ITokensHandler, ISignatureVerifier { /** @@ -89,7 +81,9 @@ interface IRouter is ITokensHandler, ISignatureVerifier { SwapDescription calldata swapDescription, AccountSignature calldata accountSignature, ProtocolFeeSignature calldata protocolFeeSignature - ) external payable + ) + external + payable returns ( uint256 inputBalanceChange, uint256 actualOutputAmount, diff --git a/contracts/interfaces/ISignatureVerifier.sol b/contracts/interfaces/ISignatureVerifier.sol index e5c0e7ed..a379bb54 100644 --- a/contracts/interfaces/ISignatureVerifier.sol +++ b/contracts/interfaces/ISignatureVerifier.sol @@ -17,9 +17,6 @@ pragma solidity 0.8.12; -import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - import { AbsoluteTokenAmount, Input, SwapDescription } from "../shared/Structs.sol"; interface ISignatureVerifier { diff --git a/contracts/interfaces/IUniswapV2Pair.sol b/contracts/interfaces/IUniswapV2Pair.sol index 00496ef5..3b8fb277 100644 --- a/contracts/interfaces/IUniswapV2Pair.sol +++ b/contracts/interfaces/IUniswapV2Pair.sol @@ -23,27 +23,23 @@ pragma solidity 0.8.12; * github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Pair.sol. */ interface IUniswapV2Pair { - function mint(address) external returns (uint256); + function mint(address to) external returns (uint256 liquidity); - function burn(address) external returns (uint256, uint256); + function burn(address to) external returns (uint256 amount0, uint256 amount1); function swap( - uint256, - uint256, - address, - bytes calldata + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data ) external; function getReserves() external view - returns ( - uint112, - uint112, - uint32 - ); + returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); - function token0() external view returns (address); + function token0() external view returns (address token0); - function token1() external view returns (address); + function token1() external view returns (address token1); } diff --git a/contracts/interfaces/IUniswapV2Router02.sol b/contracts/interfaces/IUniswapV2Router02.sol index 0796174f..1b18cf07 100644 --- a/contracts/interfaces/IUniswapV2Router02.sol +++ b/contracts/interfaces/IUniswapV2Router02.sol @@ -24,9 +24,9 @@ pragma solidity 0.8.12; */ interface IUniswapV2Router02 { function swapExactETHForTokens( - uint256, - address[] calldata, - address, - uint256 + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline ) external payable; } diff --git a/contracts/interfaces/IWETH9.sol b/contracts/interfaces/IWETH9.sol index b4761ddf..75eb5a38 100644 --- a/contracts/interfaces/IWETH9.sol +++ b/contracts/interfaces/IWETH9.sol @@ -26,7 +26,7 @@ pragma solidity 0.8.12; interface IWETH9 { function deposit() external payable; - function withdraw(uint256) external; + function withdraw(uint256 amount) external; - function balanceOf(address) external view returns (uint256); + function balanceOf(address account) external view returns (uint256 balance); } diff --git a/contracts/interfaces/IYearnPermit.sol b/contracts/interfaces/IYearnPermit.sol index 3bfcebbd..3afc6c31 100644 --- a/contracts/interfaces/IYearnPermit.sol +++ b/contracts/interfaces/IYearnPermit.sol @@ -20,11 +20,5 @@ pragma solidity 0.8.12; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IYearnPermit is IERC20 { - function permit( - address, - address, - uint256, - uint256, - bytes calldata - ) external; + function permit(address, address, uint256, uint256, bytes calldata) external; } diff --git a/contracts/router/Router.sol b/contracts/router/Router.sol index 6164c08d..71afed55 100644 --- a/contracts/router/Router.sol +++ b/contracts/router/Router.sol @@ -18,8 +18,8 @@ pragma solidity 0.8.12; import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; @@ -30,39 +30,10 @@ import { IRouter } from "../interfaces/IRouter.sol"; import { IYearnPermit } from "../interfaces/IYearnPermit.sol"; import { Base } from "../shared/Base.sol"; import { AmountType, PermitType, SwapType } from "../shared/Enums.sol"; -import { - BadAmount, - BadAccount, - BadAccountSignature, - BadAmountType, - BadFeeAmount, - BadFeeBeneficiary, - BadFeeShare, - BadFeeSignature, - ExceedingDelimiterAmount, - ExceedingLimitFee, - HighInputBalanceChange, - InsufficientAllowance, - InsufficientMsgValue, - LowActualOutputAmount, - NoneAmountType, - NonePermitType, - NoneSwapType, - PassedDeadline -} from "../shared/Errors.sol"; +import { BadAmount, BadAccount, BadAccountSignature, BadAmountType, BadFeeAmount, BadFeeBeneficiary, BadFeeShare, BadFeeSignature, ExceedingDelimiterAmount, ExceedingLimitFee, HighInputBalanceChange, InsufficientAllowance, InsufficientMsgValue, LowActualOutputAmount, NoneAmountType, NonePermitType, NoneSwapType, PassedDeadline } from "../shared/Errors.sol"; import { Ownable } from "../shared/Ownable.sol"; -import { - AbsoluteTokenAmount, - AccountSignature, - Fee, - ProtocolFeeSignature, - Input, - Permit, - SwapDescription, - TokenAmount -} from "../shared/Structs.sol"; +import { AbsoluteTokenAmount, AccountSignature, Fee, ProtocolFeeSignature, Input, Permit, SwapDescription, TokenAmount } from "../shared/Structs.sol"; import { TokensHandler } from "../shared/TokensHandler.sol"; - import { ProtocolFee } from "./ProtocolFee.sol"; import { SignatureVerifier } from "./SignatureVerifier.sol"; @@ -86,8 +57,9 @@ contract Router is SwapDescription calldata swapDescription, AccountSignature calldata accountSignature ) external override nonReentrant { - if (msg.sender != swapDescription.account) + if (msg.sender != swapDescription.account) { revert BadAccount(msg.sender, swapDescription.account); + } validateAndExpireAccountSignature(input, output, swapDescription, accountSignature); } @@ -102,7 +74,10 @@ contract Router is AccountSignature calldata accountSignature, ProtocolFeeSignature calldata protocolFeeSignature ) - external payable override nonReentrant + external + payable + override + nonReentrant returns ( uint256 inputBalanceChange, uint256 actualOutputAmount, @@ -164,8 +139,9 @@ contract Router is uint256 outputBalanceChange = Base.getBalance(output.token) - initialOutputBalance; // Check input requirements, prevent the underflow - if (inputBalanceChange > absoluteInputAmount) + if (inputBalanceChange > absoluteInputAmount) { revert HighInputBalanceChange(inputBalanceChange, absoluteInputAmount); + } // Calculate the refund amount uint256 refundAmount = absoluteInputAmount - inputBalanceChange; @@ -180,8 +156,9 @@ contract Router is ); // Check output requirements, prevent revert on transfers - if (actualOutputAmount < output.absoluteAmount) + if (actualOutputAmount < output.absoluteAmount) { revert LowActualOutputAmount(actualOutputAmount, output.absoluteAmount); + } // Transfer the refund back to the user, // do nothing in zero input token case as `refundAmount` is zero @@ -267,8 +244,9 @@ contract Router is ) internal { uint256 allowance = IERC20(token).allowance(account, address(this)); if (allowance < amount) { - if (permit.permitCallData.length == uint256(0)) + if (permit.permitCallData.length == uint256(0)) { revert InsufficientAllowance(allowance, amount); + } Address.functionCall( token, @@ -327,8 +305,9 @@ contract Router is AccountSignature calldata accountSignature ) internal { if (accountSignature.signature.length == uint256(0)) { - if (msg.sender != swapDescription.account) + if (msg.sender != swapDescription.account) { revert BadAccount(msg.sender, swapDescription.account); + } return; } bytes32 hashedAccountSignatureData = hashAccountSignatureData( @@ -366,15 +345,18 @@ contract Router is Fee memory protocolFee = swapDescription.protocolFee; if (protocolFeeSignature.signature.length == uint256(0)) { - if (protocolFee.share != baseProtocolFee.share) + if (protocolFee.share != baseProtocolFee.share) { revert BadFeeShare(protocolFee.share, baseProtocolFee.share); - if (protocolFee.beneficiary != baseProtocolFee.beneficiary) + } + if (protocolFee.beneficiary != baseProtocolFee.beneficiary) { revert BadFeeBeneficiary(protocolFee.beneficiary, baseProtocolFee.beneficiary); + } return; } - if (protocolFee.share > baseProtocolFee.share) + if (protocolFee.share > baseProtocolFee.share) { revert ExceedingLimitFee(protocolFee.share, baseProtocolFee.share); + } bytes32 hashedProtocolFeeSignatureData = hashProtocolFeeSignatureData( input, @@ -392,8 +374,9 @@ contract Router is ) revert BadFeeSignature(); // solhint-disable not-rely-on-time - if (block.timestamp > protocolFeeSignature.deadline) + if (block.timestamp > protocolFeeSignature.deadline) { revert PassedDeadline(block.timestamp, protocolFeeSignature.deadline); + } // solhint-enable not-rely-on-time } @@ -405,11 +388,10 @@ contract Router is * @param account Address of the account to transfer token from * @return absoluteTokenAmount Absolute token amount */ - function getAbsoluteInputAmount(TokenAmount calldata tokenAmount, address account) - internal - view - returns (uint256 absoluteTokenAmount) - { + function getAbsoluteInputAmount( + TokenAmount calldata tokenAmount, + address account + ) internal view returns (uint256 absoluteTokenAmount) { AmountType amountType = tokenAmount.amountType; address token = tokenAmount.token; uint256 amount = tokenAmount.amount; @@ -420,8 +402,9 @@ contract Router is if (amountType == AmountType.Absolute) return amount; - if (token == ETH || token == address(0)) + if (token == ETH || token == address(0)) { revert BadAmountType(amountType, AmountType.Absolute); + } if (amount > DELIMITER) revert ExceedingDelimiterAmount(amount); @@ -456,18 +439,15 @@ contract Router is ) internal pure - returns ( - uint256 returnedAmount, - uint256 protocolFeeAmount, - uint256 marketplaceFeeAmount - ) + returns (uint256 returnedAmount, uint256 protocolFeeAmount, uint256 marketplaceFeeAmount) { if (swapType == SwapType.None) revert NoneSwapType(); uint256 outputAbsoluteAmount = output.absoluteAmount; if (output.token == address(0)) { - if (outputAbsoluteAmount > uint256(0)) + if (outputAbsoluteAmount > uint256(0)) { revert BadAmount(outputAbsoluteAmount, uint256(0)); + } return (uint256(0), uint256(0), uint256(0)); } @@ -490,8 +470,9 @@ contract Router is uint256 totalFeeAmount = outputBalanceChange - returnedAmount; // This check is important in fixed outputs case as we never actually check that // total fee amount is not too large and should always just pass in fixed inputs case - if (totalFeeAmount * DELIMITER > totalFeeShare * returnedAmount) + if (totalFeeAmount * DELIMITER > totalFeeShare * returnedAmount) { revert BadFeeAmount(totalFeeAmount, (returnedAmount * totalFeeShare) / DELIMITER); + } protocolFeeAmount = (totalFeeAmount * protocolFee.share) / totalFeeShare; marketplaceFeeAmount = totalFeeAmount - protocolFeeAmount; diff --git a/contracts/router/SignatureVerifier.sol b/contracts/router/SignatureVerifier.sol index 6d1695dc..dbdc01cf 100644 --- a/contracts/router/SignatureVerifier.sol +++ b/contracts/router/SignatureVerifier.sol @@ -21,16 +21,7 @@ import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/draft-EIP712. import { ISignatureVerifier } from "../interfaces/ISignatureVerifier.sol"; import { UsedHash } from "../shared/Errors.sol"; -import { - AbsoluteTokenAmount, - AccountSignature, - ProtocolFeeSignature, - Fee, - Input, - Permit, - SwapDescription, - TokenAmount -} from "../shared/Structs.sol"; +import { AbsoluteTokenAmount, Fee, Input, Permit, SwapDescription, TokenAmount } from "../shared/Structs.sol"; contract SignatureVerifier is ISignatureVerifier, EIP712 { mapping(bytes32 => bool) private isHashUsed_; @@ -175,7 +166,7 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { * @param output Outut described in `hashDada()` function * @param swapDescription Swap parameters described in `hashDada()` function * @param saltOrDeadline Salt/deadline parameter preventing double-spending - * @return `execute()` function data hashed + * @return hashedData `execute()` function data hashed */ function hash( bytes32 typehash, @@ -183,7 +174,7 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { AbsoluteTokenAmount memory output, SwapDescription memory swapDescription, uint256 saltOrDeadline - ) internal pure returns (bytes32) { + ) internal pure returns (bytes32 hashedData) { return keccak256( abi.encode( @@ -198,17 +189,19 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { /** * @param input Input struct to be hashed - * @return Hashed Input structs array + * @return hashedInput Hashed Input structs array */ - function hash(Input memory input) internal pure returns (bytes32) { + function hash(Input memory input) internal pure returns (bytes32 hashedInput) { return keccak256(abi.encode(INPUT_TYPEHASH, hash(input.tokenAmount), hash(input.permit))); } /** * @param tokenAmount TokenAmount struct to be hashed - * @return Hashed TokenAmount struct + * @return hashedTokenAmount Hashed TokenAmount struct */ - function hash(TokenAmount memory tokenAmount) internal pure returns (bytes32) { + function hash( + TokenAmount memory tokenAmount + ) internal pure returns (bytes32 hashedTokenAmount) { return keccak256( abi.encode( @@ -222,9 +215,9 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { /** * @param permit Permit struct to be hashed - * @return Hashed Permit struct + * @return hashedPermit Hashed Permit struct */ - function hash(Permit memory permit) internal pure returns (bytes32) { + function hash(Permit memory permit) internal pure returns (bytes32 hashedPermit) { return keccak256( abi.encode( @@ -237,9 +230,11 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { /** * @param absoluteTokenAmount AbsoluteTokenAmount struct to be hashed - * @return Hashed AbsoluteTokenAmount struct + * @return hashedAbsoluteTokenAmount Hashed AbsoluteTokenAmount struct */ - function hash(AbsoluteTokenAmount memory absoluteTokenAmount) internal pure returns (bytes32) { + function hash( + AbsoluteTokenAmount memory absoluteTokenAmount + ) internal pure returns (bytes32 hashedAbsoluteTokenAmount) { return keccak256( abi.encode( @@ -252,9 +247,11 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { /** * @param swapDescription SwapDescription struct to be hashed - * @return Hashed SwapDescription struct + * @return hashedSwapDescription Hashed SwapDescription struct */ - function hash(SwapDescription memory swapDescription) internal pure returns (bytes32) { + function hash( + SwapDescription memory swapDescription + ) internal pure returns (bytes32 hashedSwapDescription) { return keccak256( abi.encode( @@ -271,9 +268,9 @@ contract SignatureVerifier is ISignatureVerifier, EIP712 { /** * @param fee Fee struct to be hashed - * @return Hashed Fee struct + * @return hashedFee Hashed Fee struct */ - function hash(Fee memory fee) internal pure returns (bytes32) { + function hash(Fee memory fee) internal pure returns (bytes32 hashedFee) { return keccak256(abi.encode(FEE_TYPEHASH, fee.share, fee.beneficiary)); } } diff --git a/contracts/shared/Base.sol b/contracts/shared/Base.sol index ef0049cb..306e0aa9 100644 --- a/contracts/shared/Base.sol +++ b/contracts/shared/Base.sol @@ -17,11 +17,11 @@ pragma solidity 0.8.12; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { FailedEtherTransfer, ZeroReceiver } from "./Errors.sol"; +import { ZeroReceiver } from "./Errors.sol"; /** * @title Library unifying transfer, approval, and getting balance for ERC20 tokens and Ether @@ -38,11 +38,7 @@ library Base { * @dev Reverts on zero `receiver`, does nothing for zero amount * @dev Should not be used with zero token address */ - function transfer( - address token, - address receiver, - uint256 amount - ) internal { + function transfer(address token, address receiver, uint256 amount) internal { if (amount == uint256(0)) return; if (receiver == address(0)) revert ZeroReceiver(); @@ -60,11 +56,7 @@ library Base { * @param amount Tokens amount to be approved * @dev Should not be used with zero or `ETH` token address */ - function safeApproveMax( - address token, - address spender, - uint256 amount - ) internal { + function safeApproveMax(address token, address spender, uint256 amount) internal { uint256 allowance = IERC20(token).allowance(address(this), spender); if (allowance < amount) { if (allowance > uint256(0)) { @@ -80,7 +72,7 @@ library Base { * @param account Address of the account * @dev Should not be used with zero token address */ - function getBalance(address token, address account) internal view returns (uint256) { + function getBalance(address token, address account) internal view returns (uint256 balance) { if (token == ETH) return account.balance; return IERC20(token).balanceOf(account); @@ -91,7 +83,7 @@ library Base { * @param token Address of the token * @dev Returns `0` for zero token address in order to handle empty token case */ - function getBalance(address token) internal view returns (uint256) { + function getBalance(address token) internal view returns (uint256 balance) { if (token == address(0)) return uint256(0); return Base.getBalance(token, address(this)); diff --git a/contracts/shared/Errors.sol b/contracts/shared/Errors.sol index a347d7ab..b9731b00 100644 --- a/contracts/shared/Errors.sol +++ b/contracts/shared/Errors.sol @@ -17,7 +17,7 @@ pragma solidity 0.8.12; -import { ActionType, AmountType, PermitType, SwapType } from "./Enums.sol"; +import { AmountType } from "./Enums.sol"; import { Fee } from "./Structs.sol"; error BadAccount(address account, address expectedAccount); diff --git a/contracts/shared/Weth.sol b/contracts/shared/Weth.sol index eddfc63d..dc170715 100644 --- a/contracts/shared/Weth.sol +++ b/contracts/shared/Weth.sol @@ -21,14 +21,14 @@ pragma solidity 0.8.12; * @title Abstract contract storing Wrapped Ether address for the current chain */ abstract contract Weth { - address private immutable weth_; + address private immutable WETH; /** * @notice Sets Wrapped Ether address for the current chain * @param weth Wrapped Ether address */ constructor(address weth) { - weth_ = weth; + WETH = weth; } /** @@ -36,6 +36,6 @@ abstract contract Weth { * @return weth Wrapped Ether address */ function getWeth() public view returns (address weth) { - return weth_; + return WETH; } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 87bf4676..2cdf03eb 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -124,7 +124,7 @@ const config: HardhatUserConfig = { blast: process.env.BLAST_API_KEY ? process.env.BLAST_API_KEY.toString() : '', bsc: process.env.BSC_API_KEY ? process.env.BSC_API_KEY.toString() : '', celo: process.env.CELO_API_KEY ? process.env.CELO_API_KEY.toString() : '', - gnosis: process.env.GNOSIS_API_KEY ? process.env.GNOSIS_API_KEY.toString() : '', + xdai: process.env.GNOSIS_API_KEY ? process.env.GNOSIS_API_KEY.toString() : '', linea: process.env.LINEA_API_KEY ? process.env.LINEA_API_KEY.toString() : '', mainnet: process.env.ETHEREUM_API_KEY ? process.env.ETHEREUM_API_KEY.toString() : '', mantle: process.env.MANTLE_API_KEY ? process.env.MANTLE_API_KEY.toString() : '', diff --git a/package-lock.json b/package-lock.json index ccd2dc24..f5d6887a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,11 +45,11 @@ "lint-staged": "11.0.0", "mrm": "3.0.10", "pinst": "2.1.6", - "prettier": "2.8.8", + "prettier": "3.3.3", "prettier-plugin-solidity": "1.1.3", "regenerator-runtime": "0.13.11", - "solhint": "3.4.1", - "solhint-plugin-prettier": "0.0.5", + "solhint": "5.0.3", + "solhint-plugin-prettier": "0.1.0", "solidity-coverage": "0.8.4", "truffle": "5.11.1", "ts-node": "10.9.1", @@ -4721,6 +4721,53 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.4.0.tgz", "integrity": "sha512-dlKiZmDvJnGRLHojrDoFZJmsQVeltVeoiRN7RK+cf2FmkhASDEblE0RiaYdxPNsUZa6mRG8393b9bfyp+V5IAw==" }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@prettier/sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.3.0.tgz", + "integrity": "sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==", + "dev": true, + "funding": { + "url": "https://github.com/prettier/prettier-synchronized?sponsor=1" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -5809,9 +5856,9 @@ "dev": true }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/http-errors": { @@ -6114,6 +6161,22 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/@vue/component-compiler-utils/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler-utils/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6705,9 +6768,9 @@ "dev": true }, "node_modules/antlr4": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", - "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz", + "integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==", "dev": true, "engines": { "node": ">=16" @@ -8897,6 +8960,16 @@ "dev": true, "optional": true }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -23161,9 +23234,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "node_modules/graphemer": { @@ -23918,9 +23991,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "node_modules/http-errors": { @@ -29233,15 +29306,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -29373,6 +29446,12 @@ "extend": "^3.0.0" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -30950,14 +31029,14 @@ } }, "node_modules/solhint": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", - "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-5.0.3.tgz", + "integrity": "sha512-OLCH6qm/mZTCpplTXzXTJGId1zrtNuDYP5c2e6snIv/hdRVxPfBBz/bAlL91bY/Accavkayp2Zp2BaDSrLVXTQ==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.0", + "@solidity-parser/parser": "^0.18.0", "ajv": "^6.12.6", - "antlr4": "^4.11.0", + "antlr4": "^4.13.1-patch-1", "ast-parents": "^0.0.1", "chalk": "^4.1.2", "commander": "^10.0.0", @@ -30966,9 +31045,10 @@ "glob": "^8.0.3", "ignore": "^5.2.4", "js-yaml": "^4.1.0", + "latest-version": "^7.0.0", "lodash": "^4.17.21", "pluralize": "^8.0.0", - "semver": "^6.3.0", + "semver": "^7.5.2", "strip-ansi": "^6.0.1", "table": "^6.8.1", "text-table": "^0.2.0" @@ -30981,25 +31061,47 @@ } }, "node_modules/solhint-plugin-prettier": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz", - "integrity": "sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz", + "integrity": "sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==", "dev": true, "dependencies": { + "@prettier/sync": "^0.3.0", "prettier-linter-helpers": "^1.0.0" }, "peerDependencies": { - "prettier": "^1.15.0 || ^2.0.0", - "prettier-plugin-solidity": "^1.0.0-alpha.14" + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^1.0.0" + } + }, + "node_modules/solhint/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/solhint/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true + }, + "node_modules/solhint/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" } }, "node_modules/solhint/node_modules/argparse": { @@ -31017,6 +31119,33 @@ "balanced-match": "^1.0.0" } }, + "node_modules/solhint/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/solhint/node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, "node_modules/solhint/node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -31044,6 +31173,63 @@ "url": "https://github.com/sponsors/d-fischer" } }, + "node_modules/solhint/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/solhint/node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/solhint/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/solhint/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -31063,6 +31249,44 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/solhint/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/solhint/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/solhint/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -31075,6 +31299,60 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/solhint/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/solhint/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/solhint/node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/solhint/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -31087,6 +31365,115 @@ "node": ">=10" } }, + "node_modules/solhint/node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/solhint/node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/solhint/node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/solhint/node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/solhint/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/solidity-comments-extractor": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", @@ -33699,6 +34086,22 @@ "node": ">=4" } }, + "node_modules/ts-generator/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/ts-generator/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -39295,6 +39698,39 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.4.0.tgz", "integrity": "sha512-dlKiZmDvJnGRLHojrDoFZJmsQVeltVeoiRN7RK+cf2FmkhASDEblE0RiaYdxPNsUZa6mRG8393b9bfyp+V5IAw==" }, + "@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true + }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "requires": { + "graceful-fs": "4.2.10" + } + }, + "@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "requires": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, + "@prettier/sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.3.0.tgz", + "integrity": "sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==", + "dev": true, + "requires": {} + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -40316,9 +40752,9 @@ "dev": true }, "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "@types/http-errors": { @@ -40602,6 +41038,13 @@ "source-map": "^0.6.1" } }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -41081,9 +41524,9 @@ "dev": true }, "antlr4": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", - "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz", + "integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==", "dev": true }, "antlr4ts": { @@ -42829,6 +43272,16 @@ } } }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -54118,9 +54571,9 @@ } }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "graphemer": { @@ -54692,9 +55145,9 @@ } }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-errors": { @@ -58891,9 +59344,9 @@ "dev": true }, "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true }, "prettier-linter-helpers": { @@ -58997,6 +59450,12 @@ "extend": "^3.0.0" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -60243,14 +60702,14 @@ } }, "solhint": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", - "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-5.0.3.tgz", + "integrity": "sha512-OLCH6qm/mZTCpplTXzXTJGId1zrtNuDYP5c2e6snIv/hdRVxPfBBz/bAlL91bY/Accavkayp2Zp2BaDSrLVXTQ==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.16.0", + "@solidity-parser/parser": "^0.18.0", "ajv": "^6.12.6", - "antlr4": "^4.11.0", + "antlr4": "^4.13.1-patch-1", "ast-parents": "^0.0.1", "chalk": "^4.1.2", "commander": "^10.0.0", @@ -60259,22 +60718,35 @@ "glob": "^8.0.3", "ignore": "^5.2.4", "js-yaml": "^4.1.0", + "latest-version": "^7.0.0", "lodash": "^4.17.21", "pluralize": "^8.0.0", "prettier": "^2.8.3", - "semver": "^6.3.0", + "semver": "^7.5.2", "strip-ansi": "^6.0.1", "table": "^6.8.1", "text-table": "^0.2.0" }, "dependencies": { + "@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true + }, "@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "requires": { - "antlr4ts": "^0.5.0-alpha.4" + "defer-to-connect": "^2.0.1" } }, "argparse": { @@ -60292,6 +60764,27 @@ "balanced-match": "^1.0.0" } }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, "commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -60310,6 +60803,41 @@ "path-type": "^4.0.0" } }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, "glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -60323,6 +60851,35 @@ "once": "^1.3.0" } }, + "got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -60332,6 +60889,42 @@ "argparse": "^2.0.1" } }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "requires": { + "package-json": "^8.1.0" + } + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + }, "minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -60340,15 +60933,80 @@ "requires": { "brace-expansion": "^2.0.1" } + }, + "normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "dev": true + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "requires": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + } + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true + }, + "registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "requires": { + "@pnpm/npm-conf": "^2.1.0" + } + }, + "registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "requires": { + "rc": "1.2.8" + } + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true } } }, "solhint-plugin-prettier": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz", - "integrity": "sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz", + "integrity": "sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==", "dev": true, "requires": { + "@prettier/sync": "^0.3.0", "prettier-linter-helpers": "^1.0.0" } }, @@ -62335,6 +62993,13 @@ "dev": true, "peer": true }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "peer": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 58d95c4c..a64eca2c 100755 --- a/package.json +++ b/package.json @@ -66,11 +66,11 @@ "lint-staged": "11.0.0", "mrm": "3.0.10", "pinst": "2.1.6", - "prettier": "2.8.8", + "prettier": "3.3.3", "prettier-plugin-solidity": "1.1.3", "regenerator-runtime": "0.13.11", - "solhint": "3.4.1", - "solhint-plugin-prettier": "0.0.5", + "solhint": "5.0.3", + "solhint-plugin-prettier": "0.1.0", "solidity-coverage": "0.8.4", "truffle": "5.11.1", "ts-node": "10.9.1", diff --git a/scripts/4_deploy_uniswap_v2_caller copy.js b/scripts/4_deploy_uniswap_v2_caller copy.js new file mode 100644 index 00000000..520cb6ef --- /dev/null +++ b/scripts/4_deploy_uniswap_v2_caller copy.js @@ -0,0 +1,23 @@ +import deploymentAddresses from './deployment'; + +try { + (async () => { + console.log('Make sure 0x161b29D1919D4E06b53eE449376181B5082b30B9 is used and nonce is 3-6'); + + const chainIdHex = await hre.network.provider.request({ method: 'eth_chainId' }); + const chainId = parseInt(chainIdHex.toString(), 16).toString(); + + console.log(`Working with chainId ${chainId}`); + + // We get the contract to deploy + const Contract = await ethers.getContractFactory('UniswapV2Caller'); + const contract = await Contract.deploy(deploymentAddresses.weth[chainId]); + + console.log(`${'UniswapV2Caller'} deployed to: ${contract.address}`); + + return contract.address; + })(); +} catch (error) { + console.error(error); + process.exit(1); +} diff --git a/scripts/5_deploy_uniswap_v2_caller.js b/scripts/5_deploy_uniswap_v2_caller.js new file mode 100644 index 00000000..29561842 --- /dev/null +++ b/scripts/5_deploy_uniswap_v2_caller.js @@ -0,0 +1,23 @@ +import deploymentAddresses from './deployment'; + +try { + (async () => { + console.log('Make sure 0x161b29D1919D4E06b53eE449376181B5082b30B9 is used and nonce is 7'); + + const chainIdHex = await hre.network.provider.request({ method: 'eth_chainId' }); + const chainId = parseInt(chainIdHex.toString(), 16).toString(); + + console.log(`Working with chainId ${chainId}`); + + // We get the contract to deploy + const Contract = await ethers.getContractFactory('UniswapV3Caller'); + const contract = await Contract.deploy(deploymentAddresses.weth[chainId]); + + console.log(`${'UniswapV3Caller'} deployed to: ${contract.address}`); + + return contract.address; + })(); +} catch (error) { + console.error(error); + process.exit(1); +} diff --git a/scripts/4_verify.js b/scripts/6_verify.js similarity index 78% rename from scripts/4_verify.js rename to scripts/6_verify.js index 8782a48e..e3872d19 100644 --- a/scripts/4_verify.js +++ b/scripts/6_verify.js @@ -16,6 +16,12 @@ try { deploymentAddresses.weth[chainId], ], }); + await hre.run('verify:verify', { + address: deploymentAddresses.uniswapV3Caller[chainId], + constructorArguments: [ + deploymentAddresses.weth[chainId], + ], + }); })(); } catch (error) { console.error(error); diff --git a/scripts/deployment.js b/scripts/deployment.js index c564f661..e7660ef2 100644 --- a/scripts/deployment.js +++ b/scripts/deployment.js @@ -44,9 +44,16 @@ const deploymentAddresses = { 1313161554: '0xC629Bf86f02ef13E8F1f5F75adE8a8165587998F', }, uniswapV2Caller: { + 56: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', + 100: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', + 137: '0x53F5D89914327C3900FC2C9D297B9B30129F463e', + 250: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', 1101: '0xe76BA87E04555e1a5afcCb0c8c5AC4d0b29e3dBE', 5000: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', + 8453: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', 34443: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', + 42161: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', + 43114: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', 59144: '0xe76BA87E04555e1a5afcCb0c8c5AC4d0b29e3dBE', 81457: '0xC0AC45d01a64660629506b5889722C6dA25F4084', 167000: '0xE81B24bcD0a706ae3Ece36C42CF96c010EEeF37a', @@ -55,9 +62,16 @@ const deploymentAddresses = { 666666666: '0xe76BA87E04555e1a5afcCb0c8c5AC4d0b29e3dBE', }, weth: { + 56: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', + 100: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', + 137: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + 250: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', 1101: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', 5000: '0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111', + 8453: '0x4200000000000000000000000000000000000006', 34443: '0x4200000000000000000000000000000000000006', + 42161: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + 43114: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', 59144: '0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f', 81457: '0x4300000000000000000000000000000000000004', 167000: '0xA51894664A773981C6C112C43ce576f315d5b1B6', diff --git a/test/callers/UniswapV2Caller.js b/test/callers/UniswapV2Caller.js index 792fa3f2..322c02d8 100644 --- a/test/callers/UniswapV2Caller.js +++ b/test/callers/UniswapV2Caller.js @@ -1,4 +1,5 @@ import buyTokenOnUniswap from '../helpers/buyTokenOnUniswap'; +import logChange from '../helpers/logger'; import { wethAddress, ethAddress, daiAddress } from '../helpers/tokens'; const { expect } = require('chai'); @@ -28,6 +29,21 @@ describe('UniswapV2Caller', () => { const logger = new ethers.utils.Logger('1'); const abiCoder = new ethers.utils.AbiCoder(); + async function execute(i, out, sp, as, fs, opt = {}) { + const ethBefore = await owner.getBalance(); + const daiBefore = await dai.balanceOf(owner.address); + const wethBefore = await weth.balanceOf(owner.address); + + const tx = await router.functions.execute(i, out, sp, as, fs, opt); + const receipt = await tx.wait(); + + logger.info(`Called router for ${receipt.gasUsed} gas`); + + logChange(logger, 'eth', ethBefore, (await owner.getBalance()).add(receipt.gasUsed.mul(receipt.effectiveGasPrice))); + logChange(logger, 'dai', daiBefore, await dai.balanceOf(owner.address)); + logChange(logger, 'weth', wethBefore, await weth.balanceOf(owner.address)); + } + before(async () => { // await network.provider.request({ // method: "hardhat_reset", @@ -70,11 +86,11 @@ describe('UniswapV2Caller', () => { beforeEach(async () => { router = await Router.deploy(); + await router.setProtocolFeeDefault(protocolFeeDefault); }); it('should not do weth -> dai trade with high slippage', async () => { await weth.approve(router.address, ethers.utils.parseUnits('1', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -111,15 +127,8 @@ describe('UniswapV2Caller', () => { it('should do weth -> dai trade', async () => { await weth.approve(router.address, ethers.utils.parseUnits('1', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); - logger.info( - `weth balance is ${ethers.utils.formatUnits(await weth.balanceOf(owner.address), 18)}`, - ); - const tx = await router.functions.execute( + await execute( // input [[weth.address, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], // output @@ -148,22 +157,10 @@ describe('UniswapV2Caller', () => { // fee signature zeroSignature, ); - logger.info(`Called router for ${(await tx.wait()).gasUsed} gas`); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); - logger.info( - `weth balance is ${ethers.utils.formatUnits(await weth.balanceOf(owner.address), 18)}`, - ); }); it('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( + await execute( // input [[ethAddress, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], // output @@ -195,15 +192,9 @@ describe('UniswapV2Caller', () => { 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)}`, - ); }); it('should not do eth -> dai trade with too large marketplace fee', async () => { - await router.setProtocolFeeDefault(protocolFeeDefault); - await expect( router.functions.execute( // input @@ -241,8 +232,6 @@ describe('UniswapV2Caller', () => { }); it('should not do eth -> dai trade with high slippage', async () => { - await router.setProtocolFeeDefault(protocolFeeDefault); - await expect( router.functions.execute( // input @@ -279,9 +268,8 @@ describe('UniswapV2Caller', () => { ).to.be.reverted; }); - it('should do dai -> eth trade with 0 input', async () => { + it('should not do dai -> eth trade with 0 input', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -318,12 +306,8 @@ describe('UniswapV2Caller', () => { it.only('should do dai -> eth trade', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); - const tx = await router.functions.execute( + await execute( // input [[daiAddress, ethers.utils.parseUnits('500', 18), AMOUNT_ABSOLUTE], zeroPermit], // output @@ -352,15 +336,10 @@ describe('UniswapV2Caller', () => { // fee signature zeroSignature, ); - logger.info(`Called router for ${(await tx.wait()).gasUsed} gas`); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); }); - it('should do dai -> weth trade with 0 output', async () => { + it('should not do dai -> weth trade with 0 output', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -397,7 +376,6 @@ describe('UniswapV2Caller', () => { it('should not do dai -> weth trade with empty path', async () => { await dai.approve(router.address, ethers.utils.parseUnits('1', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -434,7 +412,6 @@ describe('UniswapV2Caller', () => { it('should not do dai -> weth trade with bad directions length', async () => { await dai.approve(router.address, ethers.utils.parseUnits('5000', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -471,7 +448,6 @@ describe('UniswapV2Caller', () => { it('should not do dai -> weth trade with huge fee', async () => { await dai.approve(router.address, ethers.utils.parseUnits('5000', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -508,15 +484,8 @@ describe('UniswapV2Caller', () => { it('should do dai -> weth trade', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); - logger.info( - `weth balance is ${ethers.utils.formatUnits(await weth.balanceOf(owner.address), 18)}`, - ); - const tx = await router.functions.execute( + await execute( // input [[daiAddress, ethers.utils.parseUnits('500', 18), AMOUNT_ABSOLUTE], zeroPermit], // output @@ -545,18 +514,10 @@ describe('UniswapV2Caller', () => { // fee signature zeroSignature, ); - logger.info(`Called router for ${(await tx.wait()).gasUsed} gas`); - logger.info( - `dai balance is ${ethers.utils.formatUnits(await dai.balanceOf(owner.address), 18)}`, - ); - logger.info( - `weth balance is ${ethers.utils.formatUnits(await weth.balanceOf(owner.address), 18)}`, - ); }); it('should not do dai -> weth trade with high token slippage', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( @@ -593,7 +554,6 @@ describe('UniswapV2Caller', () => { it('should not do dai -> weth trade with not enough liquidity', async () => { await dai.approve(router.address, ethers.utils.parseUnits('500', 18)); - await router.setProtocolFeeDefault(protocolFeeDefault); await expect( router.functions.execute( diff --git a/test/callers/UniswapV3Caller.js b/test/callers/UniswapV3Caller.js index 9a862683..6ede88bc 100644 --- a/test/callers/UniswapV3Caller.js +++ b/test/callers/UniswapV3Caller.js @@ -1,10 +1,12 @@ import buyTokenOnUniswap from '../helpers/buyTokenOnUniswap'; +import logChange from '../helpers/logger'; import { wethAddress, ethAddress, daiAddress } from '../helpers/tokens'; const { ethers } = require('hardhat'); const AMOUNT_ABSOLUTE = 2; const SWAP_FIXED_INPUTS = 1; +const SWAP_FIXED_OUTPUTS = 2; const EMPTY_BYTES = '0x'; const uniDaiWethAddress = '0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8'; @@ -19,12 +21,27 @@ describe('UniswapV3Caller', () => { let Router; let Caller; let router; - // let weth; + let weth; let dai; let protocolFeeDefault; const logger = new ethers.utils.Logger('1'); const abiCoder = new ethers.utils.AbiCoder(); + async function execute(i, out, sp, as, fs, opt = {}) { + const ethBefore = await owner.getBalance(); + const daiBefore = await dai.balanceOf(owner.address); + const wethBefore = await weth.balanceOf(owner.address); + + const tx = await router.functions.execute(i, out, sp, as, fs, opt); + const receipt = await tx.wait(); + + logger.info(`Called router for ${receipt.gasUsed} gas`); + + logChange(logger, 'eth', ethBefore, (await owner.getBalance()).add(receipt.gasUsed.mul(receipt.effectiveGasPrice))); + logChange(logger, 'dai', daiBefore, await dai.balanceOf(owner.address)); + logChange(logger, 'weth', wethBefore, await weth.balanceOf(owner.address)); + } + before(async () => { // await network.provider.request({ // method: "hardhat_reset", @@ -51,24 +68,20 @@ describe('UniswapV3Caller', () => { caller = await Caller.deploy(wethAddress); - // weth = await ethers.getContractAt('IERC20', wethAddress, owner); + 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]; + protocolFeeDefault = [ethers.utils.parseUnits('0', 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( + it('should do eth -> dai trade fixed inputs', async () => { + await execute( // input [[ethAddress, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], // output @@ -81,12 +94,116 @@ describe('UniswapV3Caller', () => { owner.address, caller.address, abiCoder.encode( - ['address', 'address', 'address', 'uint256', 'bool'], + ['address', 'address', 'address', 'bool', 'uint256', 'bool'], + [ + ethAddress, + daiAddress, + uniDaiWethAddress, + false, + ethers.utils.parseUnits('1', 18), + true, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, + { + value: ethers.utils.parseEther('1'), + }, + ); + }); + + it('should do dai -> eth trade fixed inputs', async () => { + await dai.approve(router.address, ethers.utils.parseUnits('1000', 18)); + + await execute( + // input + [[daiAddress, ethers.utils.parseUnits('1000', 18), AMOUNT_ABSOLUTE], zeroPermit], + // output + [ethAddress, ethers.utils.parseUnits('0.1', 18)], + // swap description + [ + SWAP_FIXED_INPUTS, + protocolFeeDefault, + protocolFeeDefault, + owner.address, + caller.address, + abiCoder.encode( + ['address', 'address', 'address', 'bool', 'uint256', 'bool'], [ + daiAddress, ethAddress, + uniDaiWethAddress, + true, + ethers.utils.parseUnits('1000', 18), + true, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, + ); + }); + + it('should do weth -> dai trade fixed inputs', async () => { + await weth.approve(router.address, ethers.utils.parseUnits('1', 18)); + + await execute( + // input + [[wethAddress, 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', 'bool', 'uint256', 'bool'], + [ + wethAddress, daiAddress, uniDaiWethAddress, + false, ethers.utils.parseUnits('1', 18), + true, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, + ); + }); + + it('should do eth -> dai trade fixed outputs', async () => { + await execute( + // input + [[ethAddress, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], + // output + [daiAddress, ethers.utils.parseUnits('1000', 18)], + // swap description + [ + SWAP_FIXED_OUTPUTS, + protocolFeeDefault, + protocolFeeDefault, + owner.address, + caller.address, + abiCoder.encode( + ['address', 'address', 'address', 'bool', 'uint256', 'bool'], + [ + ethAddress, + daiAddress, + uniDaiWethAddress, + false, + ethers.utils.parseUnits('1000', 18), false, ], ), @@ -99,9 +216,73 @@ describe('UniswapV3Caller', () => { 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)}`, + }); + + it('should do dai -> eth trade fixed outputs', async () => { + await dai.approve(router.address, ethers.utils.parseUnits('1000', 18)); + + await execute( + // input + [[daiAddress, ethers.utils.parseUnits('1000', 18), AMOUNT_ABSOLUTE], zeroPermit], + // output + [ethAddress, ethers.utils.parseUnits('0.1', 18)], + // swap description + [ + SWAP_FIXED_OUTPUTS, + protocolFeeDefault, + protocolFeeDefault, + owner.address, + caller.address, + abiCoder.encode( + ['address', 'address', 'address', 'bool', 'uint256', 'bool'], + [ + daiAddress, + ethAddress, + uniDaiWethAddress, + true, + ethers.utils.parseUnits('0.1', 18), + false, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, + ); + }); + + it('should do weth -> dai trade fixed outputs', async () => { + await weth.approve(router.address, ethers.utils.parseUnits('1', 18)); + + await execute( + // input + [[wethAddress, ethers.utils.parseUnits('1', 18), AMOUNT_ABSOLUTE], zeroPermit], + // output + [daiAddress, ethers.utils.parseUnits('1000', 18)], + // swap description + [ + SWAP_FIXED_OUTPUTS, + protocolFeeDefault, + protocolFeeDefault, + owner.address, + caller.address, + abiCoder.encode( + ['address', 'address', 'address', 'bool', 'uint256', 'bool'], + [ + wethAddress, + daiAddress, + uniDaiWethAddress, + false, + ethers.utils.parseUnits('1000', 18), + false, + ], + ), + ], + // account signature + zeroSignature, + // fee signature + zeroSignature, ); }); }); diff --git a/test/helpers/latestTime.js b/test/helpers/latestTime.js index 2f6e4cd1..ed312b26 100755 --- a/test/helpers/latestTime.js +++ b/test/helpers/latestTime.js @@ -1,7 +1,7 @@ // Returns the time of the last mined block in seconds -async function latestTime() { +const latestTime = async () => { const block = await web3.eth.getBlock('latest'); return block.timestamp; -} +}; export default latestTime; diff --git a/test/helpers/logger.js b/test/helpers/logger.js new file mode 100644 index 00000000..05e724f3 --- /dev/null +++ b/test/helpers/logger.js @@ -0,0 +1,11 @@ +const { ethers } = require('hardhat'); + +const logChange = (logger, name, before, after) => { + if (!before.eq(after)) { + logger.info( + `${name} balance change is ${before.gt(after) ? '-' : '+'}${ethers.utils.formatUnits(before.gt(after) ? before.sub(after) : after.sub(before), 18)}`, + ); + } +}; + +export default logChange; diff --git a/test/router/Router.js b/test/router/Router.js index 1f2bb983..af6d3abc 100644 --- a/test/router/Router.js +++ b/test/router/Router.js @@ -270,6 +270,23 @@ describe('Router', () => { ).to.be.reverted; }); + it('should not execute with bad amount type', async () => { + await expect( + router.functions.execute( + // input + [[AddressZero, ethers.utils.parseUnits('0', 18), '0'], zeroPermit], + // output + [ethAddress, '0'], + // swap description + [SWAP_FIXED_INPUTS, zeroFee, zeroFee, owner.address, mockCaller.address, EMPTY_BYTES], + // account signature + zeroSignature, + // fee signature + zeroSignature, + ), + ).to.be.reverted; + }); + it('should not execute with relative amount for ETH', async () => { await expect( router.functions.execute( From e6da1119b3ecebaff36a64deb9f653f88d2dbad7 Mon Sep 17 00:00:00 2001 From: Igor Sobolev Date: Thu, 5 Sep 2024 23:11:41 +0100 Subject: [PATCH 4/4] upd linters --- .prettierrc.json | 2 +- package-lock.json | 62 ++++++++++++++++------------------------------- package.json | 2 +- 3 files changed, 23 insertions(+), 43 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 7d95f61e..8f504e10 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,5 @@ { + "plugins": ["prettier-plugin-solidity"], "overrides": [ { "files": "*.sol", @@ -8,7 +9,6 @@ "useTabs": false, "singleQuote": false, "bracketSpacing": true, - "explicitTypes": "always", "trailingComma": "none" } }, diff --git a/package-lock.json b/package-lock.json index f5d6887a..d3ee4884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "mrm": "3.0.10", "pinst": "2.1.6", "prettier": "3.3.3", - "prettier-plugin-solidity": "1.1.3", + "prettier-plugin-solidity": "1.4.1", "regenerator-runtime": "0.13.11", "solhint": "5.0.3", "solhint-plugin-prettier": "0.1.0", @@ -29333,30 +29333,26 @@ } }, "node_modules/prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", + "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" + "@solidity-parser/parser": "^0.18.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=12" + "node": ">=16" }, "peerDependencies": { - "prettier": ">=2.3.0 || >=3.0.0-alpha.0" + "prettier": ">=2.3.0" } }, "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true }, "node_modules/prettier-plugin-solidity/node_modules/lru-cache": { "version": "6.0.0", @@ -31474,12 +31470,6 @@ "node": ">=10" } }, - "node_modules/solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", - "dev": true - }, "node_modules/solidity-coverage": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.4.tgz", @@ -59359,24 +59349,20 @@ } }, "prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", + "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" + "@solidity-parser/parser": "^0.18.0", + "semver": "^7.5.4" }, "dependencies": { "@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true }, "lru-cache": { "version": "6.0.0", @@ -61010,12 +60996,6 @@ "prettier-linter-helpers": "^1.0.0" } }, - "solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", - "dev": true - }, "solidity-coverage": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.4.tgz", diff --git a/package.json b/package.json index a64eca2c..f8a8e92c 100755 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "mrm": "3.0.10", "pinst": "2.1.6", "prettier": "3.3.3", - "prettier-plugin-solidity": "1.1.3", + "prettier-plugin-solidity": "1.4.1", "regenerator-runtime": "0.13.11", "solhint": "5.0.3", "solhint-plugin-prettier": "0.1.0",