From efad0956ad5abed678204e51b679cbfd7f075cf4 Mon Sep 17 00:00:00 2001 From: exception Date: Tue, 18 Jun 2024 15:08:14 -0300 Subject: [PATCH] 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 77596718..7e7a24ca 100755 --- a/package.json +++ b/package.json @@ -30,7 +30,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", @@ -41,8 +43,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",