diff --git a/contracts/arbitrum/connectors/sushiswap/events.sol b/contracts/arbitrum/connectors/sushiswap/events.sol new file mode 100644 index 00000000..4697e44c --- /dev/null +++ b/contracts/arbitrum/connectors/sushiswap/events.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogDepositLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amtA, + uint256 amtB, + uint256 uniAmount, + uint256 getId, + uint256 setId + ); + + event LogWithdrawLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amountA, + uint256 amountB, + uint256 uniAmount, + uint256 getId, + uint256[] setId + ); + + 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 + ); +} \ No newline at end of file diff --git a/contracts/arbitrum/connectors/sushiswap/helpers.sol b/contracts/arbitrum/connectors/sushiswap/helpers.sol new file mode 100644 index 00000000..91a0ca11 --- /dev/null +++ b/contracts/arbitrum/connectors/sushiswap/helpers.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import {TokenInterface} from "../../common/interfaces.sol"; +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import {ISushiSwapRouter, ISushiSwapFactory} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev ISushiSwapRouter + */ + ISushiSwapRouter internal constant router = + ISushiSwapRouter(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506); + + function getExpectedBuyAmt(address[] memory paths, uint256 sellAmt) + internal + view + returns (uint256 buyAmt) + { + uint256[] memory amts = router.getAmountsOut(sellAmt, paths); + buyAmt = amts[1]; + } + + function getExpectedSellAmt(address[] memory paths, uint256 buyAmt) + internal + view + returns (uint256 sellAmt) + { + uint256[] memory amts = router.getAmountsIn(buyAmt, paths); + sellAmt = amts[0]; + } + + function checkPair(address[] memory paths) internal view { + address pair = ISushiSwapFactory(router.factory()).getPair( + paths[0], + paths[1] + ); + require(pair != address(0), "No-exchange-address"); + } + + function getPaths(address buyAddr, address sellAddr) + internal + pure + returns (address[] memory paths) + { + paths = new address[](2); + paths[0] = address(sellAddr); + paths[1] = address(buyAddr); + } + + function getMinAmount( + TokenInterface token, + uint256 amt, + uint256 slippage + ) internal view returns (uint256 minAmt) { + uint256 _amt18 = convertTo18(token.decimals(), amt); + minAmt = wmul(_amt18, sub(WAD, slippage)); + minAmt = convert18ToDec(token.decimals(), minAmt); + } + + function _addLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmt, + uint256 slippage + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _liquidity + ) + { + (TokenInterface _tokenA, TokenInterface _tokenB) = changeEthAddress( + tokenA, + tokenB + ); + + _amtA = _amt == uint256(-1) + ? getTokenBal(TokenInterface(tokenA)) + : _amt; + _amtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmt, convertTo18(_tokenA.decimals(), _amtA)) + ); + + bool isEth = address(_tokenA) == wethAddr; + convertEthToWeth(isEth, _tokenA, _amtA); + + isEth = address(_tokenB) == wethAddr; + convertEthToWeth(isEth, _tokenB, _amtB); + + approve(_tokenA, address(router), _amtA); + approve(_tokenB, address(router), _amtB); + + uint256 minAmtA = getMinAmount(_tokenA, _amtA, slippage); + uint256 minAmtB = getMinAmount(_tokenB, _amtB, slippage); + (_amtA, _amtB, _liquidity) = router.addLiquidity( + address(_tokenA), + address(_tokenB), + _amtA, + _amtB, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + function _removeLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmtA, + uint256 unitAmtB + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _uniAmt + ) + { + TokenInterface _tokenA; + TokenInterface _tokenB; + (_tokenA, _tokenB, _uniAmt) = _getRemoveLiquidityData( + tokenA, + tokenB, + _amt + ); + { + uint256 minAmtA = convert18ToDec( + _tokenA.decimals(), + wmul(unitAmtA, _uniAmt) + ); + uint256 minAmtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmtB, _uniAmt) + ); + (_amtA, _amtB) = router.removeLiquidity( + address(_tokenA), + address(_tokenB), + _uniAmt, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + bool isEth = address(_tokenA) == wethAddr; + convertWethToEth(isEth, _tokenA, _amtA); + + isEth = address(_tokenB) == wethAddr; + convertWethToEth(isEth, _tokenB, _amtB); + } + + function _getRemoveLiquidityData( + address tokenA, + address tokenB, + uint256 _amt + ) + internal + returns ( + TokenInterface _tokenA, + TokenInterface _tokenB, + uint256 _uniAmt + ) + { + (_tokenA, _tokenB) = changeEthAddress(tokenA, tokenB); + address exchangeAddr = ISushiSwapFactory(router.factory()).getPair( + address(_tokenA), + address(_tokenB) + ); + require(exchangeAddr != address(0), "pair-not-found."); + + TokenInterface uniToken = TokenInterface(exchangeAddr); + _uniAmt = _amt == uint256(-1) + ? uniToken.balanceOf(address(this)) + : _amt; + approve(uniToken, address(router), _uniAmt); + } +} diff --git a/contracts/arbitrum/connectors/sushiswap/interface.sol b/contracts/arbitrum/connectors/sushiswap/interface.sol new file mode 100644 index 00000000..b141c7b5 --- /dev/null +++ b/contracts/arbitrum/connectors/sushiswap/interface.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +interface ISushiSwapRouter { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + +interface ISushiSwapFactory { + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function createPair(address tokenA, address tokenB) external returns (address pair); +} diff --git a/contracts/arbitrum/connectors/sushiswap/main.sol b/contracts/arbitrum/connectors/sushiswap/main.sol new file mode 100644 index 00000000..f8b002eb --- /dev/null +++ b/contracts/arbitrum/connectors/sushiswap/main.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title SushiSwap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract SushiswapResolver is Helpers, Events { + /** + * @dev Deposit Liquidity. + * @notice Deposit Liquidity to a SushiSwap pool. + * @param tokenA The address of token A.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amtA The amount of A tokens to deposit. + * @param unitAmt The unit amount of of amtB/amtA with slippage. + * @param slippage Slippage amount. + * @param getId ID to retrieve amtA. + * @param setId ID stores the amount of pools tokens received. + */ + function deposit( + address tokenA, + address tokenB, + uint256 amtA, + uint256 unitAmt, + uint256 slippage, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amtA); + + (uint _amtA, uint _amtB, uint _uniAmt) = _addLiquidity( + tokenA, + tokenB, + _amt, + unitAmt, + slippage + ); + setUint(setId, _uniAmt); + + _eventName = "LogDepositLiquidity(address,address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setId); + } + + /** + * @dev Withdraw Liquidity. + * @notice Withdraw Liquidity from a SushiSwap pool. + * @param tokenA The address of token A.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param uniAmt The amount of pool tokens to withdraw. + * @param unitAmtA The unit amount of amtA/uniAmt with slippage. + * @param unitAmtB The unit amount of amtB/uniAmt with slippage. + * @param getId ID to retrieve uniAmt. + * @param setIds Array of IDs to store the amount tokens received. + */ + function withdraw( + address tokenA, + address tokenB, + uint256 uniAmt, + uint256 unitAmtA, + uint256 unitAmtB, + uint256 getId, + uint256[] calldata setIds + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, uniAmt); + + (uint _amtA, uint _amtB, uint _uniAmt) = _removeLiquidity( + tokenA, + tokenB, + _amt, + unitAmtA, + unitAmtB + ); + + setUint(setIds[0], _amtA); + setUint(setIds[1], _amtB); + + _eventName = "LogWithdrawLiquidity(address,address,uint256,uint256,uint256,uint256,uint256[])"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setIds); + } + + /** + * @dev Buy ETH/ERC20_Token. + * @notice Buy a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param buyAmt The amount of tokens to buy. + * @param unitAmt The unit amount of sellAmt/buyAmt with slippage. + * @param getId ID to retrieve buyAmt. + * @param setId ID to store the amount of tokens sold. + */ + function buy( + address buyAddr, + address sellAddr, + uint256 buyAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _buyAmt = getUint(getId, buyAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + uint _slippageAmt = convert18ToDec(_sellAddr.decimals(), + wmul(unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedSellAmt(paths, _buyAmt); + require(_slippageAmt >= _expectedAmt, "Too much slippage"); + + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _expectedAmt); + approve(_sellAddr, address(router), _expectedAmt); + + uint _sellAmt = router.swapTokensForExactTokens( + _buyAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[0]; + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } + + /** + * @dev Sell ETH/ERC20_Token. + * @notice Sell a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The unit amount of buyAmt/sellAmt with slippage. + * @param getId ID to retrieve sellAmt. + * @param setId ID stores the amount of token brought. + */ + function sell( + address buyAddr, + address sellAddr, + uint256 sellAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _sellAmt = getUint(getId, sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeEthAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + if (_sellAmt == uint(-1)) { + _sellAmt = sellAddr == ethAddr ? + address(this).balance : + _sellAddr.balanceOf(address(this)); + } + + uint _slippageAmt = convert18ToDec(_buyAddr.decimals(), + wmul(unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedBuyAmt(paths, _sellAmt); + require(_slippageAmt <= _expectedAmt, "Too much slippage"); + + bool isEth = address(_sellAddr) == wethAddr; + convertEthToWeth(isEth, _sellAddr, _sellAmt); + approve(_sellAddr, address(router), _sellAmt); + + uint _buyAmt = router.swapExactTokensForTokens( + _sellAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[1]; + + isEth = address(_buyAddr) == wethAddr; + convertWethToEth(isEth, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } +} + +contract ConnectV2SushiswapArbitrum is SushiswapResolver { + string public constant name = "Sushiswap-v1"; +} diff --git a/contracts/avalanche/connectors/sushiswap/events.sol b/contracts/avalanche/connectors/sushiswap/events.sol new file mode 100644 index 00000000..4697e44c --- /dev/null +++ b/contracts/avalanche/connectors/sushiswap/events.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogDepositLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amtA, + uint256 amtB, + uint256 uniAmount, + uint256 getId, + uint256 setId + ); + + event LogWithdrawLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amountA, + uint256 amountB, + uint256 uniAmount, + uint256 getId, + uint256[] setId + ); + + 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 + ); +} \ No newline at end of file diff --git a/contracts/avalanche/connectors/sushiswap/helpers.sol b/contracts/avalanche/connectors/sushiswap/helpers.sol new file mode 100644 index 00000000..04149201 --- /dev/null +++ b/contracts/avalanche/connectors/sushiswap/helpers.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import {TokenInterface} from "../../common/interfaces.sol"; +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import {ISushiSwapRouter, ISushiSwapFactory} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev ISushiSwapRouter + */ + ISushiSwapRouter internal constant router = + ISushiSwapRouter(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506); + + function getExpectedBuyAmt(address[] memory paths, uint256 sellAmt) + internal + view + returns (uint256 buyAmt) + { + uint256[] memory amts = router.getAmountsOut(sellAmt, paths); + buyAmt = amts[1]; + } + + function getExpectedSellAmt(address[] memory paths, uint256 buyAmt) + internal + view + returns (uint256 sellAmt) + { + uint256[] memory amts = router.getAmountsIn(buyAmt, paths); + sellAmt = amts[0]; + } + + function checkPair(address[] memory paths) internal view { + address pair = ISushiSwapFactory(router.factory()).getPair( + paths[0], + paths[1] + ); + require(pair != address(0), "No-exchange-address"); + } + + function getPaths(address buyAddr, address sellAddr) + internal + pure + returns (address[] memory paths) + { + paths = new address[](2); + paths[0] = address(sellAddr); + paths[1] = address(buyAddr); + } + + function getMinAmount( + TokenInterface token, + uint256 amt, + uint256 slippage + ) internal view returns (uint256 minAmt) { + uint256 _amt18 = convertTo18(token.decimals(), amt); + minAmt = wmul(_amt18, sub(WAD, slippage)); + minAmt = convert18ToDec(token.decimals(), minAmt); + } + + function _addLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmt, + uint256 slippage + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _liquidity + ) + { + (TokenInterface _tokenA, TokenInterface _tokenB) = changeAvaxAddress( + tokenA, + tokenB + ); + + _amtA = _amt == uint256(-1) + ? getTokenBal(TokenInterface(tokenA)) + : _amt; + _amtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmt, convertTo18(_tokenA.decimals(), _amtA)) + ); + + bool isAvax = address(_tokenA) == wavaxAddr; + convertAvaxToWavax(isAvax, _tokenA, _amtA); + + isAvax = address(_tokenB) == wavaxAddr; + convertAvaxToWavax(isAvax, _tokenB, _amtB); + + approve(_tokenA, address(router), _amtA); + approve(_tokenB, address(router), _amtB); + + uint256 minAmtA = getMinAmount(_tokenA, _amtA, slippage); + uint256 minAmtB = getMinAmount(_tokenB, _amtB, slippage); + (_amtA, _amtB, _liquidity) = router.addLiquidity( + address(_tokenA), + address(_tokenB), + _amtA, + _amtB, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + function _removeLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmtA, + uint256 unitAmtB + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _uniAmt + ) + { + TokenInterface _tokenA; + TokenInterface _tokenB; + (_tokenA, _tokenB, _uniAmt) = _getRemoveLiquidityData( + tokenA, + tokenB, + _amt + ); + { + uint256 minAmtA = convert18ToDec( + _tokenA.decimals(), + wmul(unitAmtA, _uniAmt) + ); + uint256 minAmtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmtB, _uniAmt) + ); + (_amtA, _amtB) = router.removeLiquidity( + address(_tokenA), + address(_tokenB), + _uniAmt, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + bool isAvax = address(_tokenA) == wavaxAddr; + convertWavaxToAvax(isAvax, _tokenA, _amtA); + + isAvax = address(_tokenB) == wavaxAddr; + convertWavaxToAvax(isAvax, _tokenB, _amtB); + } + + function _getRemoveLiquidityData( + address tokenA, + address tokenB, + uint256 _amt + ) + internal + returns ( + TokenInterface _tokenA, + TokenInterface _tokenB, + uint256 _uniAmt + ) + { + (_tokenA, _tokenB) = changeAvaxAddress(tokenA, tokenB); + address exchangeAddr = ISushiSwapFactory(router.factory()).getPair( + address(_tokenA), + address(_tokenB) + ); + require(exchangeAddr != address(0), "pair-not-found."); + + TokenInterface uniToken = TokenInterface(exchangeAddr); + _uniAmt = _amt == uint256(-1) + ? uniToken.balanceOf(address(this)) + : _amt; + approve(uniToken, address(router), _uniAmt); + } +} diff --git a/contracts/avalanche/connectors/sushiswap/interface.sol b/contracts/avalanche/connectors/sushiswap/interface.sol new file mode 100644 index 00000000..b141c7b5 --- /dev/null +++ b/contracts/avalanche/connectors/sushiswap/interface.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +interface ISushiSwapRouter { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + +interface ISushiSwapFactory { + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function createPair(address tokenA, address tokenB) external returns (address pair); +} diff --git a/contracts/avalanche/connectors/sushiswap/main.sol b/contracts/avalanche/connectors/sushiswap/main.sol new file mode 100644 index 00000000..f8ed4847 --- /dev/null +++ b/contracts/avalanche/connectors/sushiswap/main.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title SushiSwap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract SushiswapResolver is Helpers, Events { + /** + * @dev Deposit Liquidity. + * @notice Deposit Liquidity to a SushiSwap pool. + * @param tokenA The address of token A.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amtA The amount of A tokens to deposit. + * @param unitAmt The unit amount of of amtB/amtA with slippage. + * @param slippage Slippage amount. + * @param getId ID to retrieve amtA. + * @param setId ID stores the amount of pools tokens received. + */ + function deposit( + address tokenA, + address tokenB, + uint256 amtA, + uint256 unitAmt, + uint256 slippage, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amtA); + + (uint _amtA, uint _amtB, uint _uniAmt) = _addLiquidity( + tokenA, + tokenB, + _amt, + unitAmt, + slippage + ); + setUint(setId, _uniAmt); + + _eventName = "LogDepositLiquidity(address,address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setId); + } + + /** + * @dev Withdraw Liquidity. + * @notice Withdraw Liquidity from a SushiSwap pool. + * @param tokenA The address of token A.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param uniAmt The amount of pool tokens to withdraw. + * @param unitAmtA The unit amount of amtA/uniAmt with slippage. + * @param unitAmtB The unit amount of amtB/uniAmt with slippage. + * @param getId ID to retrieve uniAmt. + * @param setIds Array of IDs to store the amount tokens received. + */ + function withdraw( + address tokenA, + address tokenB, + uint256 uniAmt, + uint256 unitAmtA, + uint256 unitAmtB, + uint256 getId, + uint256[] calldata setIds + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, uniAmt); + + (uint _amtA, uint _amtB, uint _uniAmt) = _removeLiquidity( + tokenA, + tokenB, + _amt, + unitAmtA, + unitAmtB + ); + + setUint(setIds[0], _amtA); + setUint(setIds[1], _amtB); + + _eventName = "LogWithdrawLiquidity(address,address,uint256,uint256,uint256,uint256,uint256[])"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setIds); + } + + /** + * @dev Buy Avax/ERC20_Token. + * @notice Buy a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param buyAmt The amount of tokens to buy. + * @param unitAmt The unit amount of sellAmt/buyAmt with slippage. + * @param getId ID to retrieve buyAmt. + * @param setId ID to store the amount of tokens sold. + */ + function buy( + address buyAddr, + address sellAddr, + uint256 buyAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _buyAmt = getUint(getId, buyAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeAvaxAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + uint _slippageAmt = convert18ToDec(_sellAddr.decimals(), + wmul(unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedSellAmt(paths, _buyAmt); + require(_slippageAmt >= _expectedAmt, "Too much slippage"); + + bool isAvax = address(_sellAddr) == wavaxAddr; + convertAvaxToWavax(isAvax, _sellAddr, _expectedAmt); + approve(_sellAddr, address(router), _expectedAmt); + + uint _sellAmt = router.swapTokensForExactTokens( + _buyAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[0]; + + isAvax = address(_buyAddr) == wavaxAddr; + convertWavaxToAvax(isAvax, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } + + /** + * @dev Sell Avax/ERC20_Token. + * @notice Sell a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For Avax: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The unit amount of buyAmt/sellAmt with slippage. + * @param getId ID to retrieve sellAmt. + * @param setId ID stores the amount of token brought. + */ + function sell( + address buyAddr, + address sellAddr, + uint256 sellAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _sellAmt = getUint(getId, sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeAvaxAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + if (_sellAmt == uint(-1)) { + _sellAmt = sellAddr == avaxAddr ? + address(this).balance : + _sellAddr.balanceOf(address(this)); + } + + uint _slippageAmt = convert18ToDec(_buyAddr.decimals(), + wmul(unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedBuyAmt(paths, _sellAmt); + require(_slippageAmt <= _expectedAmt, "Too much slippage"); + + bool isAvax = address(_sellAddr) == wavaxAddr; + convertAvaxToWavax(isAvax, _sellAddr, _sellAmt); + approve(_sellAddr, address(router), _sellAmt); + + uint _buyAmt = router.swapExactTokensForTokens( + _sellAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[1]; + + isAvax = address(_buyAddr) == wavaxAddr; + convertWavaxToAvax(isAvax, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } +} + +contract ConnectV2SushiswapAvalanche is SushiswapResolver { + string public constant name = "Sushiswap-v1"; +} diff --git a/contracts/mainnet/connectors/sushiswap/events.sol b/contracts/mainnet/connectors/sushiswap/events.sol index 2717b0a3..4697e44c 100644 --- a/contracts/mainnet/connectors/sushiswap/events.sol +++ b/contracts/mainnet/connectors/sushiswap/events.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; contract Events { diff --git a/contracts/mainnet/connectors/sushiswap/helpers.sol b/contracts/mainnet/connectors/sushiswap/helpers.sol index ea29a03f..9260b080 100644 --- a/contracts/mainnet/connectors/sushiswap/helpers.sol +++ b/contracts/mainnet/connectors/sushiswap/helpers.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import {TokenInterface} from "../../common/interfaces.sol"; diff --git a/contracts/mainnet/connectors/sushiswap/interface.sol b/contracts/mainnet/connectors/sushiswap/interface.sol index bd3be483..b141c7b5 100644 --- a/contracts/mainnet/connectors/sushiswap/interface.sol +++ b/contracts/mainnet/connectors/sushiswap/interface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface ISushiSwapRouter { diff --git a/contracts/mainnet/connectors/sushiswap/main.sol b/contracts/mainnet/connectors/sushiswap/main.sol index bd0078bc..bd559688 100644 --- a/contracts/mainnet/connectors/sushiswap/main.sol +++ b/contracts/mainnet/connectors/sushiswap/main.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** @@ -9,7 +10,7 @@ import { TokenInterface } from "../../common/interfaces.sol"; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -abstract contract SushipswapResolver is Helpers, Events { +abstract contract SushiswapResolver is Helpers, Events { /** * @dev Deposit Liquidity. * @notice Deposit Liquidity to a SushiSwap pool. @@ -191,6 +192,6 @@ abstract contract SushipswapResolver is Helpers, Events { } } -contract ConnectV2Sushiswap is SushipswapResolver { - string public constant name = "Sushipswap-v1.1"; +contract ConnectV2Sushiswap is SushiswapResolver { + string public constant name = "Sushiswap-v1"; } diff --git a/contracts/polygon/connectors/sushiswap/events.sol b/contracts/polygon/connectors/sushiswap/events.sol new file mode 100644 index 00000000..4697e44c --- /dev/null +++ b/contracts/polygon/connectors/sushiswap/events.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogDepositLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amtA, + uint256 amtB, + uint256 uniAmount, + uint256 getId, + uint256 setId + ); + + event LogWithdrawLiquidity( + address indexed tokenA, + address indexed tokenB, + uint256 amountA, + uint256 amountB, + uint256 uniAmount, + uint256 getId, + uint256[] setId + ); + + 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 + ); +} \ No newline at end of file diff --git a/contracts/polygon/connectors/sushiswap/helpers.sol b/contracts/polygon/connectors/sushiswap/helpers.sol new file mode 100644 index 00000000..5a03a061 --- /dev/null +++ b/contracts/polygon/connectors/sushiswap/helpers.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import {TokenInterface} from "../../common/interfaces.sol"; +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import {ISushiSwapRouter, ISushiSwapFactory} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev ISushiSwapRouter + */ + ISushiSwapRouter internal constant router = + ISushiSwapRouter(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506); + + function getExpectedBuyAmt(address[] memory paths, uint256 sellAmt) + internal + view + returns (uint256 buyAmt) + { + uint256[] memory amts = router.getAmountsOut(sellAmt, paths); + buyAmt = amts[1]; + } + + function getExpectedSellAmt(address[] memory paths, uint256 buyAmt) + internal + view + returns (uint256 sellAmt) + { + uint256[] memory amts = router.getAmountsIn(buyAmt, paths); + sellAmt = amts[0]; + } + + function checkPair(address[] memory paths) internal view { + address pair = ISushiSwapFactory(router.factory()).getPair( + paths[0], + paths[1] + ); + require(pair != address(0), "No-exchange-address"); + } + + function getPaths(address buyAddr, address sellAddr) + internal + pure + returns (address[] memory paths) + { + paths = new address[](2); + paths[0] = address(sellAddr); + paths[1] = address(buyAddr); + } + + function getMinAmount( + TokenInterface token, + uint256 amt, + uint256 slippage + ) internal view returns (uint256 minAmt) { + uint256 _amt18 = convertTo18(token.decimals(), amt); + minAmt = wmul(_amt18, sub(WAD, slippage)); + minAmt = convert18ToDec(token.decimals(), minAmt); + } + + function _addLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmt, + uint256 slippage + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _liquidity + ) + { + (TokenInterface _tokenA, TokenInterface _tokenB) = changeMaticAddress( + tokenA, + tokenB + ); + + _amtA = _amt == uint256(-1) + ? getTokenBal(TokenInterface(tokenA)) + : _amt; + _amtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmt, convertTo18(_tokenA.decimals(), _amtA)) + ); + + bool isMatic = address(_tokenA) == wmaticAddr; + convertMaticToWmatic(isMatic, _tokenA, _amtA); + + isMatic = address(_tokenB) == wmaticAddr; + convertMaticToWmatic(isMatic, _tokenB, _amtB); + + approve(_tokenA, address(router), _amtA); + approve(_tokenB, address(router), _amtB); + + uint256 minAmtA = getMinAmount(_tokenA, _amtA, slippage); + uint256 minAmtB = getMinAmount(_tokenB, _amtB, slippage); + (_amtA, _amtB, _liquidity) = router.addLiquidity( + address(_tokenA), + address(_tokenB), + _amtA, + _amtB, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + function _removeLiquidity( + address tokenA, + address tokenB, + uint256 _amt, + uint256 unitAmtA, + uint256 unitAmtB + ) + internal + returns ( + uint256 _amtA, + uint256 _amtB, + uint256 _uniAmt + ) + { + TokenInterface _tokenA; + TokenInterface _tokenB; + (_tokenA, _tokenB, _uniAmt) = _getRemoveLiquidityData( + tokenA, + tokenB, + _amt + ); + { + uint256 minAmtA = convert18ToDec( + _tokenA.decimals(), + wmul(unitAmtA, _uniAmt) + ); + uint256 minAmtB = convert18ToDec( + _tokenB.decimals(), + wmul(unitAmtB, _uniAmt) + ); + (_amtA, _amtB) = router.removeLiquidity( + address(_tokenA), + address(_tokenB), + _uniAmt, + minAmtA, + minAmtB, + address(this), + block.timestamp + 1 + ); + } + + bool isMatic = address(_tokenA) == wmaticAddr; + convertWmaticToMatic(isMatic, _tokenA, _amtA); + + isMatic = address(_tokenB) == wmaticAddr; + convertWmaticToMatic(isMatic, _tokenB, _amtB); + } + + function _getRemoveLiquidityData( + address tokenA, + address tokenB, + uint256 _amt + ) + internal + returns ( + TokenInterface _tokenA, + TokenInterface _tokenB, + uint256 _uniAmt + ) + { + (_tokenA, _tokenB) = changeMaticAddress(tokenA, tokenB); + address exchangeAddr = ISushiSwapFactory(router.factory()).getPair( + address(_tokenA), + address(_tokenB) + ); + require(exchangeAddr != address(0), "pair-not-found."); + + TokenInterface uniToken = TokenInterface(exchangeAddr); + _uniAmt = _amt == uint256(-1) + ? uniToken.balanceOf(address(this)) + : _amt; + approve(uniToken, address(router), _uniAmt); + } +} diff --git a/contracts/polygon/connectors/sushiswap/interface.sol b/contracts/polygon/connectors/sushiswap/interface.sol new file mode 100644 index 00000000..b141c7b5 --- /dev/null +++ b/contracts/polygon/connectors/sushiswap/interface.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +interface ISushiSwapRouter { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + +interface ISushiSwapFactory { + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function createPair(address tokenA, address tokenB) external returns (address pair); +} diff --git a/contracts/polygon/connectors/sushiswap/main.sol b/contracts/polygon/connectors/sushiswap/main.sol new file mode 100644 index 00000000..5f80f946 --- /dev/null +++ b/contracts/polygon/connectors/sushiswap/main.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title SushiSwap. + * @dev Decentralized Exchange. + */ + +import { TokenInterface } from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract SushiswapResolver is Helpers, Events { + /** + * @dev Deposit Liquidity. + * @notice Deposit Liquidity to a SushiSwap pool. + * @param tokenA The address of token A.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amtA The amount of A tokens to deposit. + * @param unitAmt The unit amount of of amtB/amtA with slippage. + * @param slippage Slippage amount. + * @param getId ID to retrieve amtA. + * @param setId ID stores the amount of pools tokens received. + */ + function deposit( + address tokenA, + address tokenB, + uint256 amtA, + uint256 unitAmt, + uint256 slippage, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amtA); + + (uint _amtA, uint _amtB, uint _uniAmt) = _addLiquidity( + tokenA, + tokenB, + _amt, + unitAmt, + slippage + ); + setUint(setId, _uniAmt); + + _eventName = "LogDepositLiquidity(address,address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setId); + } + + /** + * @dev Withdraw Liquidity. + * @notice Withdraw Liquidity from a SushiSwap pool. + * @param tokenA The address of token A.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenB The address of token B.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param uniAmt The amount of pool tokens to withdraw. + * @param unitAmtA The unit amount of amtA/uniAmt with slippage. + * @param unitAmtB The unit amount of amtB/uniAmt with slippage. + * @param getId ID to retrieve uniAmt. + * @param setIds Array of IDs to store the amount tokens received. + */ + function withdraw( + address tokenA, + address tokenB, + uint256 uniAmt, + uint256 unitAmtA, + uint256 unitAmtB, + uint256 getId, + uint256[] calldata setIds + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, uniAmt); + + (uint _amtA, uint _amtB, uint _uniAmt) = _removeLiquidity( + tokenA, + tokenB, + _amt, + unitAmtA, + unitAmtB + ); + + setUint(setIds[0], _amtA); + setUint(setIds[1], _amtB); + + _eventName = "LogWithdrawLiquidity(address,address,uint256,uint256,uint256,uint256,uint256[])"; + _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setIds); + } + + /** + * @dev Buy Matic/ERC20_Token. + * @notice Buy a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param buyAmt The amount of tokens to buy. + * @param unitAmt The unit amount of sellAmt/buyAmt with slippage. + * @param getId ID to retrieve buyAmt. + * @param setId ID to store the amount of tokens sold. + */ + function buy( + address buyAddr, + address sellAddr, + uint256 buyAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _buyAmt = getUint(getId, buyAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeMaticAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + uint _slippageAmt = convert18ToDec(_sellAddr.decimals(), + wmul(unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedSellAmt(paths, _buyAmt); + require(_slippageAmt >= _expectedAmt, "Too much slippage"); + + bool isMatic = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isMatic, _sellAddr, _expectedAmt); + approve(_sellAddr, address(router), _expectedAmt); + + uint _sellAmt = router.swapTokensForExactTokens( + _buyAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[0]; + + isMatic = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(isMatic, _buyAddr, _buyAmt); + + setUint(setId, _sellAmt); + + _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } + + /** + * @dev Sell Matic/ERC20_Token. + * @notice Sell a token using a SushiSwap + * @param buyAddr The address of the token to buy.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For Matic: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The unit amount of buyAmt/sellAmt with slippage. + * @param getId ID to retrieve sellAmt. + * @param setId ID stores the amount of token brought. + */ + function sell( + address buyAddr, + address sellAddr, + uint256 sellAmt, + uint256 unitAmt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _sellAmt = getUint(getId, sellAmt); + (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeMaticAddress(buyAddr, sellAddr); + address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr)); + + if (_sellAmt == uint(-1)) { + _sellAmt = sellAddr == maticAddr ? + address(this).balance : + _sellAddr.balanceOf(address(this)); + } + + uint _slippageAmt = convert18ToDec(_buyAddr.decimals(), + wmul(unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + checkPair(paths); + uint _expectedAmt = getExpectedBuyAmt(paths, _sellAmt); + require(_slippageAmt <= _expectedAmt, "Too much slippage"); + + bool isMatic = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isMatic, _sellAddr, _sellAmt); + approve(_sellAddr, address(router), _sellAmt); + + uint _buyAmt = router.swapExactTokensForTokens( + _sellAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[1]; + + isMatic = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(isMatic, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId); + } +} + +contract ConnectV2SushiswapPolygon is SushiswapResolver { + string public constant name = "Sushiswap-v1"; +} diff --git a/hardhat.config.ts b/hardhat.config.ts index d0d1000c..d3bd78b9 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -122,8 +122,8 @@ const config: HardhatUserConfig = { sources: "./contracts", tests: "./test", }, - etherscan: { - apiKey: getScanApiKey(String(process.env.networkType)), + etherscan: { + apiKey: getScanApiKey(String(process.env.networkType)), }, typechain: { outDir: "typechain", diff --git a/test/arbitrum/sushiswap/sushiswap.test.ts b/test/arbitrum/sushiswap/sushiswap.test.ts new file mode 100644 index 00000000..68da8d66 --- /dev/null +++ b/test/arbitrum/sushiswap/sushiswap.test.ts @@ -0,0 +1,148 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider } = 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 { constants } from "../../../scripts/constant/constant"; +import { addresses } from "../../../scripts/tests/arbitrum/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2SushiswapArbitrum__factory } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; + +const DAI_ADDR = "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"; + +describe("Sushiswap", function () { + const connectorName = "Sushiswap-v1"; + + let dsaWallet0: Contract; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: 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: 13005785 + } + } + ] + }); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2SushiswapArbitrum__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 aeth & 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("10000")); + }); + + it("Deposit aeth & 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("10000")); + }); + }); + + describe("Main", function () { + it("Should deposit successfully", async function () { + const aethAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const aethAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [aethAddress, DAI_ADDR, aethAmount, daiUnitAmount, "500000000000000000", getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }).timeout(10000000000); + + it("Should withdraw successfully", async function () { + const aethAmount = ethers.utils.parseEther("0.1"); + const aethAddress = constants.native_address; + + const getId = "0"; + const setIds = ["0", "0"]; + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [aethAddress, DAI_ADDR, aethAmount, 0, 0, getId, setIds] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + + it("Should buy successfully", async function () { + const aethAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const aethAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [aethAddress, DAI_ADDR, aethAmount, daiUnitAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + }); +}); diff --git a/test/avalanche/sushiswap/sushiswap.test.ts b/test/avalanche/sushiswap/sushiswap.test.ts new file mode 100644 index 00000000..c2ef92fe --- /dev/null +++ b/test/avalanche/sushiswap/sushiswap.test.ts @@ -0,0 +1,148 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider } = 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 { constants } from "../../../scripts/constant/constant"; +import { addresses } from "../../../scripts/tests/avalanche/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2SushiswapAvalanche__factory, ConnectV2SushiswapAvalanche } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; + +const DAI_ADDR = "0xd586e7f844cea2f87f50152665bcbc2c279d8d70"; + +describe("Sushiswap", function () { + const connectorName = "Sushiswap-v1"; + + let dsaWallet0: Contract; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: 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: 13005785 + } + } + ] + }); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2SushiswapAvalanche__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 avax & 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("10000")); + }); + + it("Deposit avax & 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("10000")); + }); + }); + + describe("Main", function () { + it("Should deposit successfully", async function () { + const avaxAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const avaxAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [avaxAddress, DAI_ADDR, avaxAmount, daiUnitAmount, "500000000000000000", getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }).timeout(10000000000); + + it("Should withdraw successfully", async function () { + const avaxAmount = ethers.utils.parseEther("0.1"); + const avaxAddress = constants.native_address; + + const getId = "0"; + const setIds = ["0", "0"]; + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [avaxAddress, DAI_ADDR, avaxAmount, 0, 0, getId, setIds] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + + it("Should buy successfully", async function () { + const avaxAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const avaxAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [avaxAddress, DAI_ADDR, avaxAmount, daiUnitAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + }); +}); diff --git a/test/polygon/sushiswap/sushiswap.test.ts b/test/polygon/sushiswap/sushiswap.test.ts new file mode 100644 index 00000000..4314e1b8 --- /dev/null +++ b/test/polygon/sushiswap/sushiswap.test.ts @@ -0,0 +1,148 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider } = 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 { constants } from "../../../scripts/constant/constant"; +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2SushiswapPolygon__factory, ConnectV2SushiswapPolygon } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; + +const DAI_ADDR = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"; + +describe("Sushiswap", function () { + const connectorName = "Sushiswap-v1"; + + let dsaWallet0: Contract; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: 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: 13005785 + } + } + ] + }); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2SushiswapPolygon__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("10000")); + }); + + it("Deposit MATIC & 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("10000")); + }); + }); + + describe("Main", function () { + it("Should deposit successfully", async function () { + const maticAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const maticAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [maticAddress, DAI_ADDR, maticAmount, daiUnitAmount, "500000000000000000", getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }).timeout(10000000000); + + it("Should withdraw successfully", async function () { + const maticAmount = ethers.utils.parseEther("0.1"); + const maticAddress = constants.native_address; + + const getId = "0"; + const setIds = ["0", "0"]; + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [maticAddress, DAI_ADDR, maticAmount, 0, 0, getId, setIds] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + + it("Should buy successfully", async function () { + const maticAmount = ethers.utils.parseEther("0.1"); + const daiUnitAmount = ethers.utils.parseEther("4000"); + const maticAddress = constants.native_address; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [maticAddress, DAI_ADDR, maticAmount, daiUnitAmount, getId, setId] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + }); +});