diff --git a/contracts/arbitrum/connectors/uniswap/v3_swap/events.sol b/contracts/arbitrum/connectors/uniswap/v3_swap/events.sol new file mode 100644 index 00000000..bb4dffed --- /dev/null +++ b/contracts/arbitrum/connectors/uniswap/v3_swap/events.sol @@ -0,0 +1,22 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogBuy( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); + + event LogSell( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/arbitrum/connectors/uniswap/v3_swap/helpers.sol b/contracts/arbitrum/connectors/uniswap/v3_swap/helpers.sol new file mode 100644 index 00000000..fc4ead42 --- /dev/null +++ b/contracts/arbitrum/connectors/uniswap/v3_swap/helpers.sol @@ -0,0 +1,149 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev uniswap v3 Swap Router + */ + ISwapRouter02 constant swapRouter = + ISwapRouter02(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); + + struct BuyInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of sellAmt/buyAmt with slippage + uint256 buyAmt; //amount of token to be bought + } + + struct SellInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of buyAmt/sellAmt with slippage. + uint256 sellAmt; //amount of token to sell + } + + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param buyData Data input for the buy action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _buy( + BuyInfo memory buyData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _buyAmt = getUint(getId, buyData.buyAmt); + + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + buyData.buyAddr, + buyData.sellAddr + ); + + uint256 _slippageAmt = convert18ToDec( + _sellAddr.decimals(), + wmul(buyData.unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _slippageAmt); + approve(_sellAddr, address(swapRouter), _slippageAmt); + + ExactOutputSingleParams memory params = ExactOutputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: buyData.fee, + recipient: address(this), + amountOut: _buyAmt, + amountInMaximum: _slippageAmt, //require(_sellAmt <= amountInMaximum) + sqrtPriceLimitX96: 0 + }); + + uint256 _sellAmt = swapRouter.exactOutputSingle(params); + require(_slippageAmt >= _sellAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + buyData.buyAddr, + buyData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), to get max buy tokens + * @param sellData Data input for the sell action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _sell( + SellInfo memory sellData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _sellAmt = getUint(getId, sellData.sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + sellData.buyAddr, + sellData.sellAddr + ); + + if (_sellAmt == uint256(-1)) { + _sellAmt = sellData.sellAddr == ethAddr + ? address(this).balance + : _sellAddr.balanceOf(address(this)); + } + + uint256 _slippageAmt = convert18ToDec( + _buyAddr.decimals(), + wmul(sellData.unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _sellAmt); + approve(_sellAddr, address(swapRouter), _sellAmt); + ExactInputSingleParams memory params = ExactInputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: sellData.fee, + recipient: address(this), + amountIn: _sellAmt, + amountOutMinimum: _slippageAmt, //require(_buyAmt >= amountOutMinimum) + sqrtPriceLimitX96: 0 + }); + + uint256 _buyAmt = swapRouter.exactInputSingle(params); + require(_slippageAmt <= _buyAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + sellData.buyAddr, + sellData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } +} diff --git a/contracts/arbitrum/connectors/uniswap/v3_swap/interface.sol b/contracts/arbitrum/connectors/uniswap/v3_swap/interface.sol new file mode 100644 index 00000000..a120e112 --- /dev/null +++ b/contracts/arbitrum/connectors/uniswap/v3_swap/interface.sol @@ -0,0 +1,134 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; + +struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; +} + +struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; +} + +struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; +} + +struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; +} + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + +interface IV3SwapRouter is IUniswapV3SwapCallback { + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + returns (uint256 amountIn); +} + +interface IApproveAndCall {} + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + +} + +/// @title MulticallExtended interface +/// @notice Enables calling multiple methods in a single call to the contract with optional validation +interface IMulticallExtended is IMulticall { + +} + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + +} + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V2 +interface IV2SwapRouter { + +} + +interface ISwapRouter02 is + IV2SwapRouter, + IV3SwapRouter, + IApproveAndCall, + IMulticallExtended, + ISelfPermit +{} diff --git a/contracts/arbitrum/connectors/uniswap/v3_swap/main.sol b/contracts/arbitrum/connectors/uniswap/v3_swap/main.sol new file mode 100644 index 00000000..7368f266 --- /dev/null +++ b/contracts/arbitrum/connectors/uniswap/v3_swap/main.sol @@ -0,0 +1,95 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Uniswap v3 swap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import "./interface.sol"; + +abstract contract UniswapResolver is Helpers, Events { + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of sellAmt/buyAmt with slippage + * @param _buyAmt amount of token to be bought + * @param _getId Id to get buyAmt + * @param _setId Id to store sellAmt + */ + function buy( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _buyAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _buy( + BuyInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + buyAmt: _buyAmt + }), + _getId, + _setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of buyAmt/sellAmt with slippage + * @param _sellAmt amount of token to be sold + * @param _getId Id to get sellAmt + * @param _setId Id to store buyAmt + */ + function sell( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _sellAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _sell( + SellInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + sellAmt: _sellAmt + }), + _getId, + _setId + ); + } +} + +contract ConnectV2UniswapV3SwapArbitrum is UniswapResolver { + string public constant name = "UniswapV3-Swap-v1"; +} diff --git a/contracts/mainnet/connectors/uniswap/v3_swap/events.sol b/contracts/mainnet/connectors/uniswap/v3_swap/events.sol new file mode 100644 index 00000000..bb4dffed --- /dev/null +++ b/contracts/mainnet/connectors/uniswap/v3_swap/events.sol @@ -0,0 +1,22 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogBuy( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); + + event LogSell( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/mainnet/connectors/uniswap/v3_swap/helpers.sol b/contracts/mainnet/connectors/uniswap/v3_swap/helpers.sol new file mode 100644 index 00000000..27e7cb88 --- /dev/null +++ b/contracts/mainnet/connectors/uniswap/v3_swap/helpers.sol @@ -0,0 +1,147 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev uniswap v3 Swap Router + */ + ISwapRouter02 constant swapRouter = + ISwapRouter02(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); + + struct BuyInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of sellAmt/buyAmt with slippage + uint256 buyAmt; //amount of token to be bought + } + + struct SellInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of buyAmt/sellAmt with slippage. + uint256 sellAmt; //amount of token to be bought + } + + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param buyData Data input for the buy action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _buy( + BuyInfo memory buyData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _buyAmt = getUint(getId, buyData.buyAmt); + + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + buyData.buyAddr, + buyData.sellAddr + ); + + uint256 _slippageAmt = convert18ToDec( + _sellAddr.decimals(), + wmul(buyData.unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _slippageAmt); + approve(_sellAddr, address(swapRouter), _slippageAmt); + + ExactOutputSingleParams memory params = ExactOutputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: buyData.fee, + recipient: address(this), + amountOut: _buyAmt, + amountInMaximum: _slippageAmt, //require(_sellAmt <= amountInMaximum) + sqrtPriceLimitX96: 0 + }); + uint256 _sellAmt = swapRouter.exactOutputSingle(params); + require(_slippageAmt >= _sellAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + buyData.buyAddr, + buyData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), to get max buy tokens + * @param sellData Data input for the sell action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _sell( + SellInfo memory sellData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _sellAmt = getUint(getId, sellData.sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + sellData.buyAddr, + sellData.sellAddr + ); + + if (_sellAmt == uint256(-1)) { + _sellAmt = sellData.sellAddr == ethAddr + ? address(this).balance + : _sellAddr.balanceOf(address(this)); + } + + uint256 _slippageAmt = convert18ToDec( + _buyAddr.decimals(), + wmul(sellData.unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _sellAmt); + approve(_sellAddr, address(swapRouter), _sellAmt); + ExactInputSingleParams memory params = ExactInputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: sellData.fee, + recipient: address(this), + amountIn: _sellAmt, + amountOutMinimum: _slippageAmt, //require(_buyAmt >= amountOutMinimum) + sqrtPriceLimitX96: 0 + }); + uint256 _buyAmt = swapRouter.exactInputSingle(params); + require(_slippageAmt <= _buyAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + sellData.buyAddr, + sellData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } +} diff --git a/contracts/mainnet/connectors/uniswap/v3_swap/interface.sol b/contracts/mainnet/connectors/uniswap/v3_swap/interface.sol new file mode 100644 index 00000000..a120e112 --- /dev/null +++ b/contracts/mainnet/connectors/uniswap/v3_swap/interface.sol @@ -0,0 +1,134 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; + +struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; +} + +struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; +} + +struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; +} + +struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; +} + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + +interface IV3SwapRouter is IUniswapV3SwapCallback { + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + returns (uint256 amountIn); +} + +interface IApproveAndCall {} + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + +} + +/// @title MulticallExtended interface +/// @notice Enables calling multiple methods in a single call to the contract with optional validation +interface IMulticallExtended is IMulticall { + +} + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + +} + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V2 +interface IV2SwapRouter { + +} + +interface ISwapRouter02 is + IV2SwapRouter, + IV3SwapRouter, + IApproveAndCall, + IMulticallExtended, + ISelfPermit +{} diff --git a/contracts/mainnet/connectors/uniswap/v3_swap/main.sol b/contracts/mainnet/connectors/uniswap/v3_swap/main.sol new file mode 100644 index 00000000..749b6857 --- /dev/null +++ b/contracts/mainnet/connectors/uniswap/v3_swap/main.sol @@ -0,0 +1,95 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Uniswap v3 swap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import "./interface.sol"; + +abstract contract UniswapResolver is Helpers, Events { + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of sellAmt/buyAmt with slippage + * @param _buyAmt amount of token to be bought + * @param _getId Id to get buyAmt + * @param _setId Id to store sellAmt + */ + function buy( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _buyAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _buy( + BuyInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + buyAmt: _buyAmt + }), + _getId, + _setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of buyAmt/sellAmt with slippage + * @param _sellAmt amount of token to be sold + * @param _getId Id to get sellAmt + * @param _setId Id to store buyAmt + */ + function sell( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _sellAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _sell( + SellInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + sellAmt: _sellAmt + }), + _getId, + _setId + ); + } +} + +contract ConnectV2UniswapV3Swap is UniswapResolver { + string public constant name = "UniswapV3-Swap-v1"; +} diff --git a/contracts/optimism/connectors/uniswap/v3_swap/events.sol b/contracts/optimism/connectors/uniswap/v3_swap/events.sol new file mode 100644 index 00000000..107e5f77 --- /dev/null +++ b/contracts/optimism/connectors/uniswap/v3_swap/events.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogBuy( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); + + event LogSell( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/optimism/connectors/uniswap/v3_swap/helpers.sol b/contracts/optimism/connectors/uniswap/v3_swap/helpers.sol new file mode 100644 index 00000000..622a338c --- /dev/null +++ b/contracts/optimism/connectors/uniswap/v3_swap/helpers.sol @@ -0,0 +1,149 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev uniswap v3 Swap Router + */ + ISwapRouter02 constant swapRouter = + ISwapRouter02(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); + + struct BuyInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of sellAmt/buyAmt with slippage + uint256 buyAmt; //amount of token to be bought + } + + struct SellInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of sellAmt/buyAmt with slippage + uint256 sellAmt; //amount of token to be bought + } + + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param buyData Data input for the buy action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _buy( + BuyInfo memory buyData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _buyAmt = getUint(getId, buyData.buyAmt); + + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + buyData.buyAddr, + buyData.sellAddr + ); + + uint256 _slippageAmt = convert18ToDec( + _sellAddr.decimals(), + wmul(buyData.unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _slippageAmt); + approve(_sellAddr, address(swapRouter), _slippageAmt); + + ExactOutputSingleParams memory params = ExactOutputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: buyData.fee, + recipient: address(this), + amountOut: _buyAmt, + amountInMaximum: _slippageAmt, //require(_sellAmt <= amountInMaximum) + sqrtPriceLimitX96: 0 + }); + + uint256 _sellAmt = swapRouter.exactOutputSingle(params); + require(_slippageAmt >= _sellAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + buyData.buyAddr, + buyData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), to get max buy tokens + * @param sellData Data input for the sell action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _sell( + SellInfo memory sellData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _sellAmt = getUint(getId, sellData.sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress( + sellData.buyAddr, + sellData.sellAddr + ); + + if (_sellAmt == uint256(-1)) { + _sellAmt = sellData.sellAddr == ethAddr + ? address(this).balance + : _sellAddr.balanceOf(address(this)); + } + + uint256 _slippageAmt = convert18ToDec( + _buyAddr.decimals(), + wmul(sellData.unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _sellAmt); + approve(_sellAddr, address(swapRouter), _sellAmt); + ExactInputSingleParams memory params = ExactInputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: sellData.fee, + recipient: address(this), + amountIn: _sellAmt, + amountOutMinimum: _slippageAmt, //require(_buyAmt >= amountOutMinimum) + sqrtPriceLimitX96: 0 + }); + + uint256 _buyAmt = swapRouter.exactInputSingle(params); + require(_slippageAmt <= _buyAmt, "Too much slippage"); + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + sellData.buyAddr, + sellData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } +} diff --git a/contracts/optimism/connectors/uniswap/v3_swap/interface.sol b/contracts/optimism/connectors/uniswap/v3_swap/interface.sol new file mode 100644 index 00000000..a120e112 --- /dev/null +++ b/contracts/optimism/connectors/uniswap/v3_swap/interface.sol @@ -0,0 +1,134 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; + +struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; +} + +struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; +} + +struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; +} + +struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; +} + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + +interface IV3SwapRouter is IUniswapV3SwapCallback { + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + returns (uint256 amountIn); +} + +interface IApproveAndCall {} + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + +} + +/// @title MulticallExtended interface +/// @notice Enables calling multiple methods in a single call to the contract with optional validation +interface IMulticallExtended is IMulticall { + +} + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + +} + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V2 +interface IV2SwapRouter { + +} + +interface ISwapRouter02 is + IV2SwapRouter, + IV3SwapRouter, + IApproveAndCall, + IMulticallExtended, + ISelfPermit +{} diff --git a/contracts/optimism/connectors/uniswap/v3_swap/main.sol b/contracts/optimism/connectors/uniswap/v3_swap/main.sol new file mode 100644 index 00000000..5873f07e --- /dev/null +++ b/contracts/optimism/connectors/uniswap/v3_swap/main.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Uniswap v3 swap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import "./interface.sol"; + +abstract contract UniswapResolver is Helpers, Events { + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of sellAmt/buyAmt with slippage + * @param _buyAmt amount of token to be bought + * @param _getId Id to get buyAmt + * @param _setId Id to store sellAmt + */ + function buy( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _buyAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _buy( + BuyInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + buyAmt: _buyAmt + }), + _getId, + _setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of buyAmt/sellAmt with slippage + * @param _sellAmt amount of token to be sold + * @param _getId Id to get sellAmt + * @param _setId Id to store buyAmt + */ + function sell( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _sellAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _sell( + SellInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + sellAmt: _sellAmt + }), + _getId, + _setId + ); + } +} + +contract ConnectV2UniswapV3SwapOptimism is UniswapResolver { + string public constant name = "UniswapV3-Swap-v1"; +} diff --git a/contracts/polygon/connectors/uniswap/v3_swap/events.sol b/contracts/polygon/connectors/uniswap/v3_swap/events.sol new file mode 100644 index 00000000..bb4dffed --- /dev/null +++ b/contracts/polygon/connectors/uniswap/v3_swap/events.sol @@ -0,0 +1,22 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogBuy( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); + + event LogSell( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/polygon/connectors/uniswap/v3_swap/helpers.sol b/contracts/polygon/connectors/uniswap/v3_swap/helpers.sol new file mode 100644 index 00000000..30911e8e --- /dev/null +++ b/contracts/polygon/connectors/uniswap/v3_swap/helpers.sol @@ -0,0 +1,148 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev uniswap v3 Swap Router + */ + ISwapRouter02 constant swapRouter = + ISwapRouter02(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); + + struct BuyInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of sellAmt/buyAmt with slippage + uint256 buyAmt; //amount of token to be bought + } + + struct SellInfo { + address buyAddr; //token to be bought + address sellAddr; //token to be sold + uint24 fee; //pool fees for buyAddr-sellAddr token pair + uint256 unitAmt; //The unit amount of buyAmt/sellAmt with slippage + uint256 sellAmt; //amount of token to be bought + } + + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param buyData Data input for the buy action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _buy( + BuyInfo memory buyData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _buyAmt = getUint(getId, buyData.buyAmt); + ( + TokenInterface _buyAddr, + TokenInterface _sellAddr + ) = changeMaticAddress(buyData.buyAddr, buyData.sellAddr); + + uint256 _slippageAmt = convert18ToDec( + _sellAddr.decimals(), + wmul(buyData.unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + + bool isMatic = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isMatic, _sellAddr, _slippageAmt); + approve(_sellAddr, address(swapRouter), _slippageAmt); + ExactOutputSingleParams memory params = ExactOutputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: buyData.fee, + recipient: address(this), + amountOut: _buyAmt, + amountInMaximum: _slippageAmt, //require(_sellAmt <= amountInMaximum) + sqrtPriceLimitX96: 0 + }); + + uint256 _sellAmt = swapRouter.exactOutputSingle(params); + require(_slippageAmt >= _sellAmt, "Too much slippage"); + + isMatic = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(isMatic, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + buyData.buyAddr, + buyData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), to get max buy tokens + * @param sellData Data input for the sell action + * @param getId Id to get buyAmt + * @param setId Id to store sellAmt + */ + function _sell( + SellInfo memory sellData, + uint256 getId, + uint256 setId + ) internal returns (string memory _eventName, bytes memory _eventParam) { + uint256 _sellAmt = getUint(getId, sellData.sellAmt); + ( + TokenInterface _buyAddr, + TokenInterface _sellAddr + ) = changeMaticAddress(sellData.buyAddr, sellData.sellAddr); + + if (_sellAmt == uint256(-1)) { + _sellAmt = sellData.sellAddr == maticAddr + ? address(this).balance + : _sellAddr.balanceOf(address(this)); + } + + uint256 _slippageAmt = convert18ToDec( + _buyAddr.decimals(), + wmul(sellData.unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + bool isMatic = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isMatic, _sellAddr, _sellAmt); + approve(_sellAddr, address(swapRouter), _sellAmt); + ExactInputSingleParams memory params = ExactInputSingleParams({ + tokenIn: address(_sellAddr), + tokenOut: address(_buyAddr), + fee: sellData.fee, + recipient: address(this), + amountIn: _sellAmt, + amountOutMinimum: _slippageAmt, //require(_buyAmt >= amountOutMinimum) + sqrtPriceLimitX96: 0 + }); + + uint256 _buyAmt = swapRouter.exactInputSingle(params); + require(_slippageAmt <= _buyAmt, "Too much slippage"); + + isMatic = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(isMatic, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + sellData.buyAddr, + sellData.sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } +} diff --git a/contracts/polygon/connectors/uniswap/v3_swap/interface.sol b/contracts/polygon/connectors/uniswap/v3_swap/interface.sol new file mode 100644 index 00000000..a120e112 --- /dev/null +++ b/contracts/polygon/connectors/uniswap/v3_swap/interface.sol @@ -0,0 +1,134 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; + +struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; +} + +struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; +} + +struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; +} + +struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; +} + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + +interface IV3SwapRouter is IUniswapV3SwapCallback { + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, + /// and swap the entire amount, enabling contracts to send tokens before calling this function. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// that may remain in the router after the swap. + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + returns (uint256 amountIn); +} + +interface IApproveAndCall {} + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + +} + +/// @title MulticallExtended interface +/// @notice Enables calling multiple methods in a single call to the contract with optional validation +interface IMulticallExtended is IMulticall { + +} + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + +} + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V2 +interface IV2SwapRouter { + +} + +interface ISwapRouter02 is + IV2SwapRouter, + IV3SwapRouter, + IApproveAndCall, + IMulticallExtended, + ISelfPermit +{} diff --git a/contracts/polygon/connectors/uniswap/v3_swap/main.sol b/contracts/polygon/connectors/uniswap/v3_swap/main.sol new file mode 100644 index 00000000..5d88bfa5 --- /dev/null +++ b/contracts/polygon/connectors/uniswap/v3_swap/main.sol @@ -0,0 +1,93 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Uniswap v3 swap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract UniswapResolver is Helpers, Events { + /** + * @dev Buy Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of sellAmt/buyAmt with slippage + * @param _buyAmt amount of token to be bought + * @param _getId Id to get buyAmt + * @param _setId Id to store sellAmt + */ + function buy( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _buyAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + return + _buy( + BuyInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + buyAmt: _buyAmt + }), + _getId, + _setId + ); + } + + /** + * @dev Sell Function + * @notice Swap token(sellAddr) with token(buyAddr), buy token with minimum sell token + * @param _buyAddr token to be bought + * @param _sellAddr token to be sold + * @param _fee pool fees for buyAddr-sellAddr token pair + * @param _unitAmt The unit amount of buyAmt/sellAmt with slippage + * @param _sellAmt amount of token to be sold + * @param _getId Id to get sellAmt + * @param _setId Id to store buyAmt + */ + function sell( + address _buyAddr, + address _sellAddr, + uint24 _fee, + uint256 _unitAmt, + uint256 _sellAmt, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + _sell( + SellInfo({ + buyAddr: _buyAddr, + sellAddr: _sellAddr, + fee: _fee, + unitAmt: _unitAmt, + sellAmt: _sellAmt + }), + _getId, + _setId + ); + } +} + +contract ConnectV2UniswapV3SwapPolygon is UniswapResolver { + string public constant name = "UniswapV3-Swap-v1"; +} \ No newline at end of file diff --git a/scripts/tests/addLiquidity.ts b/scripts/tests/addLiquidity.ts index fd969222..057f384b 100644 --- a/scripts/tests/addLiquidity.ts +++ b/scripts/tests/addLiquidity.ts @@ -5,6 +5,7 @@ import { tokenMapping as mainnetMapping } from "./mainnet/tokens"; import { tokenMapping as polygonMapping } from "./polygon/tokens"; import { tokenMapping as avalancheMapping } from "./avalanche/tokens"; import { tokenMapping as optimismMapping } from "./optimism/tokens"; +import { tokenMapping as arbitrumMapping } from "./arbitrum/tokens"; const mineTx = async (tx: any) => { await (await tx).wait(); @@ -14,7 +15,8 @@ const tokenMapping: Record> = { mainnet: mainnetMapping, polygon: polygonMapping, avalanche: avalancheMapping, - optimism: optimismMapping + optimism: optimismMapping, + arbitrum: arbitrumMapping }; export async function addLiquidity(tokenName: string, address: any, amt: any) { diff --git a/scripts/tests/arbitrum/tokens.ts b/scripts/tests/arbitrum/tokens.ts index fc679ab4..a3218314 100644 --- a/scripts/tests/arbitrum/tokens.ts +++ b/scripts/tests/arbitrum/tokens.ts @@ -1,23 +1,53 @@ +import { Provider } from "@ethersproject/abstract-provider"; +import { Signer } from "@ethersproject/abstract-signer"; +import { ethers } from "hardhat"; + +const mineTx = async (tx: any) => { + await (await tx).wait(); +}; + export const tokens = { eth: { type: "token", symbol: "ETH", name: "Ethereum", address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - decimals: 18, + decimals: 18 }, dai: { type: "token", symbol: "DAI", name: "DAI Stable", address: "0xd586e7f844cea2f87f50152665bcbc2c279d8d70", - decimals: 18, + decimals: 18 }, usdc: { type: "token", symbol: "USDC", name: "USD Coin", address: "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664", - decimals: 6, - }, + decimals: 6 + } +}; + +export const tokenMapping: Record = { + usdc: { + impersonateSigner: "0xce2cc46682e9c6d5f174af598fb4931a9c0be68e", + address: "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664", + abi: ["function mint(address _to, uint256 _amount) external returns (bool);"], + process: async function (owner: Signer | Provider, to: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + + await mineTx(contract.mint(to, amt)); + } + }, + dai: { + impersonateSigner: "0xc5ed2333f8a2c351fca35e5ebadb2a82f5d254c3", + abi: ["function transfer(address to, uint value)"], + address: "0xd586e7f844cea2f87f50152665bcbc2c279d8d70", + process: async function (owner: Signer | Provider, to: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + await mineTx(contract.transfer(to, amt)); + } + } }; diff --git a/scripts/tests/mainnet/tokens.ts b/scripts/tests/mainnet/tokens.ts index 4fa7a6b1..0e00fec7 100644 --- a/scripts/tests/mainnet/tokens.ts +++ b/scripts/tests/mainnet/tokens.ts @@ -12,36 +12,34 @@ export const tokens = { symbol: "ETH", name: "Ethereum", address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - decimals: 18, + decimals: 18 }, dai: { type: "token", symbol: "DAI", name: "DAI Stable", address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", - decimals: 18, + decimals: 18 }, usdc: { type: "token", symbol: "USDC", name: "USD Coin", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - decimals: 6, - }, + decimals: 6 + } }; export const tokenMapping: Record = { usdc: { impersonateSigner: "0xfcb19e6a322b27c06842a71e8c725399f049ae3a", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - abi: [ - "function mint(address _to, uint256 _amount) external returns (bool);", - ], + abi: ["function mint(address _to, uint256 _amount) external returns (bool);"], process: async function (owner: Signer | Provider, to: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.mint(to, amt)); - }, + } }, dai: { impersonateSigner: "0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503", @@ -50,21 +48,18 @@ export const tokenMapping: Record = { process: async function (owner: Signer | Provider, to: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.transfer(to, amt)); - }, + } }, usdt: { impersonateSigner: "0xc6cde7c39eb2f0f0095f41570af89efc2c1ea828", address: "0xdac17f958d2ee523a2206206994597c13d831ec7", - abi: [ - "function issue(uint amount)", - "function transfer(address to, uint value)", - ], + abi: ["function issue(uint amount)", "function transfer(address to, uint value)"], process: async function (owner: Signer | Provider, address: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.issue(amt)); await mineTx(contract.transfer(address, amt)); - }, + } }, wbtc: { impersonateSigner: "0xCA06411bd7a7296d7dbdd0050DFc846E95fEBEB7", @@ -73,7 +68,7 @@ export const tokenMapping: Record = { process: async function (owner: Signer | Provider, address: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.mint(address, amt)); - }, + } }, inst: { impersonateSigner: "0x75e89d5979E4f6Fba9F97c104c2F0AFB3F1dcB88", @@ -82,6 +77,6 @@ export const tokenMapping: Record = { process: async function (owner: Signer | Provider, address: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.transfer(address, amt)); - }, - }, + } + } }; diff --git a/scripts/tests/optimism/tokens.ts b/scripts/tests/optimism/tokens.ts index d12cd92c..8d72f1d3 100644 --- a/scripts/tests/optimism/tokens.ts +++ b/scripts/tests/optimism/tokens.ts @@ -12,59 +12,60 @@ export const tokens = { symbol: "ETH", name: "Eth", address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - decimals: 18, + decimals: 18 }, dai: { type: "token", symbol: "DAI", name: "DAI Stable", address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", - decimals: 18, + decimals: 18 }, usdc: { type: "token", symbol: "USDC", name: "USD Coin", address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", - decimals: 6, + decimals: 6 }, + usdt: { + type: "token", + symbol: "USDT", + name: "Tether USD Coin", + address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", + decimals: 6 + } }; export const tokenMapping: Record = { usdc: { impersonateSigner: "0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09", address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", - abi: [ - "function mint(address _to, uint256 _amount) external returns (bool);", - ], + abi: ["function mint(address _to, uint256 _amount) external returns (bool);"], process: async function (owner: Signer | Provider, to: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.mint(to, amt)); - }, + } }, dai: { - impersonateSigner: "0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09", + impersonateSigner: "0x360537542135943E8Fc1562199AEA6d0017F104B", address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", abi: ["function transfer(address to, uint value)"], process: async function (owner: Signer | Provider, to: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.transfer(to, amt)); - }, + } }, usdt: { - impersonateSigner: "0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09", + impersonateSigner: "0xc858a329bf053be78d6239c4a4343b8fbd21472b", address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", - abi: [ - "function issue(uint amount)", - "function transfer(address to, uint value)", - ], + abi: ["function issue(uint amount)", "function transfer(address to, uint value)"], process: async function (owner: Signer | Provider, address: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); - await mineTx(contract.issue(amt)); await mineTx(contract.transfer(address, amt)); - }, + } }, wbtc: { impersonateSigner: "0x3aa76aa74bdfa09d68d9ebeb462c5f40d727283f", @@ -73,8 +74,8 @@ export const tokenMapping: Record = { process: async function (owner: Signer | Provider, address: any, amt: any) { const contract = new ethers.Contract(this.address, this.abi, owner); await mineTx(contract.mint(address, amt)); - }, - }, + } + } // inst: { // impersonateSigner: "0xf1f22f25f748f79263d44735198e023b72806ab1", // address: "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb", diff --git a/test/arbitrum/uniswap/uniswap-swap.test.ts b/test/arbitrum/uniswap/uniswap-swap.test.ts new file mode 100644 index 00000000..a915ea73 --- /dev/null +++ b/test/arbitrum/uniswap/uniswap-swap.test.ts @@ -0,0 +1,138 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import { addresses } from "../../../scripts/tests/arbitrum/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import type { Signer, Contract } from "ethers"; + +import { abi } from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; +import { ConnectV2UniswapV3SwapArbitrum__factory } from "../../../typechain"; + +const FeeAmount = { + LOW: 500, + MEDIUM: 3000, + HIGH: 10000 +}; + +const TICK_SPACINGS: Record = { + 500: 10, + 3000: 60, + 10000: 200 +}; + +const DAI_ADDR = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"; + +let tokenIds: any[] = []; +let liquidities: any[] = []; +const abiCoder = ethers.utils.defaultAbiCoder; + +describe("UniswapV3 [Arbitrum]", function () { + const connectorName = "UniswapV3-Swap-v1"; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + let nftManager: Contract; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 11201500 + } + } + ] + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2UniswapV3SwapArbitrum__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH & DAI into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await addLiquidity("dai", dsaWallet0.address, ethers.utils.parseEther("100000")); + }); + }); + + describe("Main", function () { + it("Should buy successfully", async function () { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("0.000359232717483266"); + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [DAI_ADDR, ethAddress, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + + it("Should sell successfully", async function () { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("2770.23"); + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "sell", + args: [DAI_ADDR, ethAddress, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + }); +}); + +const getMinTick = (tickSpacing: number) => Math.ceil(-887272 / tickSpacing) * tickSpacing; +const getMaxTick = (tickSpacing: number) => Math.floor(887272 / tickSpacing) * tickSpacing; diff --git a/test/mainnet/uniswap/uniswap-swap.test.ts b/test/mainnet/uniswap/uniswap-swap.test.ts new file mode 100644 index 00000000..21946ddc --- /dev/null +++ b/test/mainnet/uniswap/uniswap-swap.test.ts @@ -0,0 +1,150 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import type { Signer, Contract } from "ethers"; + +import { abi } from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; +import { ConnectV2UniswapV3Swap__factory } from "../../../typechain"; + +const FeeAmount = { + LOW: 500, + MEDIUM: 3000, + HIGH: 10000 +}; + +const TICK_SPACINGS: Record = { + 500: 10, + 3000: 60, + 10000: 200 +}; + +const USDT_ADDR = "0xdac17f958d2ee523a2206206994597c13d831ec7"; +const DAI_ADDR = "0x6b175474e89094c44da98b954eedeac495271d0f"; + +let tokenIds: any[] = []; +let liquidities: any[] = []; +const abiCoder = ethers.utils.defaultAbiCoder; + +describe("UniswapV3 [Mainnet]", function () { + const connectorName = "UniswapV3-Swap-v1"; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + let nftManager: Contract; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 14674358 + } + } + ] + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + nftManager = await ethers.getContractAt(abi, "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2UniswapV3Swap__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH & DAI into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await addLiquidity("dai", dsaWallet0.address, ethers.utils.parseEther("100000")); + }); + + it("Deposit ETH & USDT into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await addLiquidity("usdt", dsaWallet0.address, ethers.utils.parseEther("100000")); + }); + }); + + describe("Main", function () { + it("Should buy successfully", async function () { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("2994.474881115"); + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [ethAddress, DAI_ADDR, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + + it("Should sell successfully", async function () { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("2693.010801400"); + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "sell", + args: [DAI_ADDR, ethAddress, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + }); +}); + +const getMinTick = (tickSpacing: number) => Math.ceil(-887272 / tickSpacing) * tickSpacing; +const getMaxTick = (tickSpacing: number) => Math.floor(887272 / tickSpacing) * tickSpacing; diff --git a/test/optimism/uniswap/uniswap-swap.test.ts b/test/optimism/uniswap/uniswap-swap.test.ts new file mode 100644 index 00000000..c80ab154 --- /dev/null +++ b/test/optimism/uniswap/uniswap-swap.test.ts @@ -0,0 +1,157 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import { addresses } from "../../../scripts/tests/optimism/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import type { Signer, Contract } from "ethers"; + +import { abi } from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; +import { ConnectV2UniswapV3SwapOptimism__factory } from "../../../typechain"; + +const FeeAmount = { + LOW: 500, + MEDIUM: 3000, + HIGH: 10000, +}; + +const TICK_SPACINGS: Record = { + 500: 10, + 3000: 60, + 10000: 200, +}; + +const DAI_ADDR = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"; + +let tokenIds: any[] = []; +let liquidities: any[] = []; +const abiCoder = ethers.utils.defaultAbiCoder; + +describe("UniswapV3 [Optimism]", function() { + const connectorName = "UniswapV3-Swap-v1"; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + let nftManager: Contract; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 7093500, + }, + }, + ], + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt( + abis.core.connectorsV2, + addresses.core.connectorsV2 + ); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2UniswapV3SwapOptimism__factory, + signer: masterSigner, + connectors: instaConnectorsV2, + }); + console.log("Connector address", connector.address); + }); + + it("Should have contracts deployed.", async function() { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function() { + it("Should build DSA v2", async function() { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH & DAI into DSA wallet", async function() { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10"), + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + ethers.utils.parseEther("10") + ); + + await addLiquidity( + "dai", + dsaWallet0.address, + ethers.utils.parseEther("100000") + ); + }); + }); + + describe("Main", function() { + + it("Should buy successfully", async function() { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("3078") + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [ethAddress, DAI_ADDR, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId], + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + + it("Should sell successfully", async function() { + const ethAmount = ethers.utils.parseEther("0.1"); + const unitAmt = ethers.utils.parseEther("2784") + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "sell", + args: [DAI_ADDR, ethAddress, FeeAmount.MEDIUM, unitAmt, ethAmount, getId, setId], + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + }); +}); + +const getMinTick = (tickSpacing: number) => + Math.ceil(-887272 / tickSpacing) * tickSpacing; +const getMaxTick = (tickSpacing: number) => + Math.floor(887272 / tickSpacing) * tickSpacing; + + + \ No newline at end of file diff --git a/test/polygon/uniswap/uniswap-swap.test.ts b/test/polygon/uniswap/uniswap-swap.test.ts new file mode 100644 index 00000000..29b480c9 --- /dev/null +++ b/test/polygon/uniswap/uniswap-swap.test.ts @@ -0,0 +1,139 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import type { Signer, Contract } from "ethers"; + +import { abi } from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; +import { ConnectV2UniswapV3SwapPolygon__factory } from "../../../typechain"; + +const FeeAmount = { + LOW: 500, + MEDIUM: 3000, + HIGH: 10000 +}; + +const TICK_SPACINGS: Record = { + 500: 10, + 3000: 60, + 10000: 200 +}; + +const DAI_ADDR = "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063"; + +let tokenIds: any[] = []; +let liquidities: any[] = []; +const abiCoder = ethers.utils.defaultAbiCoder; + +describe("UniswapV3 [Polygon]", function () { + const connectorName = "UniswapV3-Swap-v1"; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + let nftManager: Contract; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 27946900 + } + } + ] + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + nftManager = await ethers.getContractAt(abi, "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2UniswapV3SwapPolygon__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit MATIC & DAI into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await addLiquidity("dai", dsaWallet0.address, ethers.utils.parseEther("100000")); + }); + }); + + describe("Main", function () { + it("Should buy successfully", async function () { + const maticAmount = ethers.utils.parseUnits("0.1", 18); + const unitAmt = ethers.utils.parseEther("1.21"); + const maticAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [maticAddress, DAI_ADDR, FeeAmount.MEDIUM, unitAmt, maticAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + + it("Should sell successfully", async function () { + const maticAmount = ethers.utils.parseUnits("0.1", 18); + const unitAmt = ethers.utils.parseEther("1.093"); + const maticAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "sell", + args: [DAI_ADDR, maticAddress, FeeAmount.MEDIUM, unitAmt, maticAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + // console.log(receipt); + }); + }); +}); + +const getMinTick = (tickSpacing: number) => Math.ceil(-887272 / tickSpacing) * tickSpacing; +const getMaxTick = (tickSpacing: number) => Math.floor(887272 / tickSpacing) * tickSpacing;