diff --git a/contracts/mainnet/common/basic.sol b/contracts/mainnet/common/basic.sol index febeb784..c0650dfb 100644 --- a/contracts/mainnet/common/basic.sol +++ b/contracts/mainnet/common/basic.sol @@ -41,6 +41,10 @@ abstract contract Basic is DSMath, Stores { _sell = sell == ethAddr ? TokenInterface(wethAddr) : TokenInterface(sell); } + function changeEthAddrToWethAddr(address token) internal pure returns(address tokenAddr){ + tokenAddr = token == ethAddr ? wethAddr : token; + } + function convertEthToWeth(bool isEth, TokenInterface token, uint amount) internal { if(isEth) token.deposit{value: amount}(); } diff --git a/contracts/mainnet/common/interfaces.sol b/contracts/mainnet/common/interfaces.sol index 24a4eb47..53e848d3 100644 --- a/contracts/mainnet/common/interfaces.sol +++ b/contracts/mainnet/common/interfaces.sol @@ -8,6 +8,7 @@ interface TokenInterface { function withdraw(uint) external; function balanceOf(address) external view returns (uint); function decimals() external view returns (uint); + function totalSupply() external view returns (uint); } interface MemoryInterface { diff --git a/contracts/mainnet/connectors/sushi-incentive/events.sol b/contracts/mainnet/connectors/sushi-incentive/events.sol new file mode 100644 index 00000000..aa1c890d --- /dev/null +++ b/contracts/mainnet/connectors/sushi-incentive/events.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + uint256 indexed pid, + uint256 indexed version, + uint256 amount + ); + event LogWithdraw( + uint256 indexed pid, + uint256 indexed version, + uint256 amount + ); + event LogEmergencyWithdraw( + uint256 indexed pid, + uint256 indexed version, + uint256 lpAmount, + uint256 rewardsAmount + ); + event LogHarvest( + uint256 indexed pid, + uint256 indexed version, + uint256 amount + ); + event LogWithdrawAndHarvest( + uint256 indexed pid, + uint256 indexed version, + uint256 widrawAmount, + uint256 harvestAmount + ); +} diff --git a/contracts/mainnet/connectors/sushi-incentive/helpers.sol b/contracts/mainnet/connectors/sushi-incentive/helpers.sol new file mode 100644 index 00000000..97522f22 --- /dev/null +++ b/contracts/mainnet/connectors/sushi-incentive/helpers.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import "./interface.sol"; + +contract Helpers is DSMath, Basic { + IMasterChefV2 immutable masterChefV2 = + IMasterChefV2(0xEF0881eC094552b2e128Cf945EF17a6752B4Ec5d); + IMasterChef immutable masterChef = + IMasterChef(0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd); + ISushiSwapFactory immutable factory = + ISushiSwapFactory(0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac); + + function _deposit(uint256 _pid, uint256 _amount, uint256 _version) internal { + if(_version == 2) + masterChefV2.deposit(_pid, _amount, address(this)); + else + masterChef.deposit(_pid, _amount); + } + + function _withdraw(uint256 _pid, uint256 _amount, uint256 _version) internal { + if(_version == 2) + masterChefV2.withdraw(_pid, _amount, address(this)); + else + masterChef.withdraw(_pid, _amount); + } + + function _harvest(uint256 _pid) internal { + masterChefV2.harvest(_pid, address(this)); + } + + function _withdrawAndHarvest(uint256 _pid, uint256 _amount, uint256 _version) internal { + if(_version == 2) + masterChefV2.withdrawAndHarvest(_pid, _amount, address(this)); + else _withdraw(_pid, _amount, _version); + } + + function _emergencyWithdraw(uint256 _pid, uint256 _version) internal { + if(_version == 2) + masterChefV2.emergencyWithdraw(_pid, address(this)); + else + masterChef.emergencyWithdraw(_pid, address(this)); + } + + function _getPoolId(address tokenA, address tokenB) + internal + view + returns (uint256 poolId, uint256 version, address lpToken) + { + address pair = factory.getPair(tokenA, tokenB); + uint256 length = masterChefV2.poolLength(); + version = 2; + poolId = uint256(-1); + + for (uint256 i = 0; i < length; i++) { + lpToken = masterChefV2.lpToken(i); + if (pair == lpToken) { + poolId = i; + break; + } + } + + uint256 lengthV1 = masterChef.poolLength(); + for (uint256 i = 0; i < lengthV1; i++) { + (lpToken, , , ) = masterChef.poolInfo(i); + if (pair == lpToken) { + poolId = i; + version = 1; + break; + } + } + } + + function _getUserInfo(uint256 _pid, uint256 _version) + internal + view + returns (uint256 lpAmount, uint256 rewardsAmount) + { + if(_version == 2) + (lpAmount, rewardsAmount) = masterChefV2.userInfo(_pid, address(this)); + else + (lpAmount, rewardsAmount) = masterChef.userInfo(_pid, address(this)); + } +} diff --git a/contracts/mainnet/connectors/sushi-incentive/interface.sol b/contracts/mainnet/connectors/sushi-incentive/interface.sol new file mode 100644 index 00000000..ba3b1c6c --- /dev/null +++ b/contracts/mainnet/connectors/sushi-incentive/interface.sol @@ -0,0 +1,117 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +struct UserInfo { + uint256 amount; + uint256 rewardDebt; +} + +struct PoolInfo { + IERC20 lpToken; // Address of LP token contract. + uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block. + uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs. + uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below. +} + +interface IERC20 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + /// @notice EIP 2612 + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} + +interface IMasterChef { + function poolLength() external view returns (uint256); + + function updatePool(uint256 pid) external returns (PoolInfo memory); + + function poolInfo(uint256 pid) external view returns (address, uint256, uint256, uint256); + + function userInfo(uint256 _pid, address _user) + external + view + returns (uint256, uint256); + + function deposit( + uint256 pid, + uint256 amount + ) external; + + function withdraw( + uint256 pid, + uint256 amount + ) external; + + function emergencyWithdraw(uint256 pid, address to) external; +} + +interface IMasterChefV2 { + function poolLength() external view returns (uint256); + + function updatePool(uint256 pid) external returns (PoolInfo memory); + + function lpToken(uint256 pid) external view returns (address); + + function userInfo(uint256 _pid, address _user) + external + view + returns (uint256, uint256); + + function deposit( + uint256 pid, + uint256 amount, + address to + ) external; + + function withdraw( + uint256 pid, + uint256 amount, + address to + ) external; + + function emergencyWithdraw(uint256 pid, address to) external; + + function harvest(uint256 pid, address to) external; + + function withdrawAndHarvest( + uint256 pid, + uint256 amount, + address to + ) external; +} + +interface ISushiSwapFactory { + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + 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/mainnet/connectors/sushi-incentive/main.sol b/contracts/mainnet/connectors/sushi-incentive/main.sol new file mode 100644 index 00000000..cc448434 --- /dev/null +++ b/contracts/mainnet/connectors/sushi-incentive/main.sol @@ -0,0 +1,174 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title SushiSwap Double Incentive. + * @dev Decentralized Exchange. + */ + +import {TokenInterface} from "../../common/interfaces.sol"; +import {Helpers} from "./helpers.sol"; +import {Events} from "./events.sol"; + +abstract contract SushipswapIncentiveResolver is Helpers, Events { + /** + * @dev deposit LP token to masterChef + * @notice deposit LP token to masterChef + * @param token1 token1 of LP token + * @param token2 token2 of LP token + * @param amount amount of LP token + * @param getId ID to retrieve amount + * @param setId ID stores Pool ID + */ + function deposit( + address token1, + address token2, + uint256 amount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + token1 = changeEthAddrToWethAddr(token1); + token2 = changeEthAddrToWethAddr(token2); + amount = getUint(getId, amount); + (uint256 _pid, uint256 _version, address lpTokenAddr) = _getPoolId( + token1, + token2 + ); + setUint(setId, _pid); + require(_pid != uint256(-1), "pool-does-not-exist"); + TokenInterface lpToken = TokenInterface(lpTokenAddr); + lpToken.approve(address(masterChef), amount); + _deposit(_pid, amount, _version); + _eventName = "LogDeposit(uint256,uint256,uint256)"; + _eventParam = abi.encode(_pid, _version, amount); + } + + /** + * @dev withdraw LP token from masterChef + * @notice withdraw LP token from masterChef + * @param token1 token1 of LP token + * @param token2 token2 of LP token + * @param amount amount of LP token + * @param getId ID to retrieve amount + * @param setId ID stores Pool ID + */ + function withdraw( + address token1, + address token2, + uint256 amount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + token1 = changeEthAddrToWethAddr(token1); + token2 = changeEthAddrToWethAddr(token2); + amount = getUint(getId, amount); + (uint256 _pid, uint256 _version, ) = _getPoolId(token1, token2); + setUint(setId, _pid); + require(_pid != uint256(-1), "pool-does-not-exist"); + _withdraw(_pid, amount, _version); + _eventName = "LogWithdraw(uint256,uint256,uint256)"; + _eventParam = abi.encode(_pid, _version, amount); + } + + /** + * @dev harvest from masterChef + * @notice harvest from masterChef + * @param token1 token1 deposited of LP token + * @param token2 token2 deposited LP token + * @param setId ID stores Pool ID + */ + function harvest( + address token1, + address token2, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + token1 = changeEthAddrToWethAddr(token1); + token2 = changeEthAddrToWethAddr(token2); + (uint256 _pid, uint256 _version, ) = _getPoolId(token1, token2); + setUint(setId, _pid); + require(_pid != uint256(-1), "pool-does-not-exist"); + (, uint256 rewardsAmount) = _getUserInfo(_pid, _version); + if (_version == 2) _harvest(_pid); + else _withdraw(_pid, 0, _version); + _eventName = "LogHarvest(uint256,uint256,uint256)"; + _eventParam = abi.encode(_pid, _version, rewardsAmount); + } + + /** + * @dev withdraw LP token and harvest from masterChef + * @notice withdraw LP token and harvest from masterChef + * @param token1 token1 of LP token + * @param token2 token2 of LP token + * @param amount amount of LP token + * @param getId ID to retrieve amount + * @param setId ID stores Pool ID + */ + function withdrawAndHarvest( + address token1, + address token2, + uint256 amount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + token1 = changeEthAddrToWethAddr(token1); + token2 = changeEthAddrToWethAddr(token2); + amount = getUint(getId, amount); + (uint256 _pid, uint256 _version, ) = _getPoolId(token1, token2); + setUint(setId, _pid); + require(_pid != uint256(-1), "pool-does-not-exist"); + (, uint256 rewardsAmount) = _getUserInfo(_pid, _version); + _withdrawAndHarvest(_pid, amount, _version); + _eventName = "LogWithdrawAndHarvest(uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(_pid, _version, amount, rewardsAmount); + } + + /** + * @dev emergency withdraw from masterChef + * @notice emergency withdraw from masterChef + * @param token1 token1 deposited of LP token + * @param token2 token2 deposited LP token + * @param setId ID stores Pool ID + */ + function emergencyWithdraw( + address token1, + address token2, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + token1 = changeEthAddrToWethAddr(token1); + token2 = changeEthAddrToWethAddr(token2); + (uint256 _pid, uint256 _version, ) = _getPoolId(token1, token2); + setUint(setId, _pid); + require(_pid != uint256(-1), "pool-does-not-exist"); + (uint256 lpAmount, uint256 rewardsAmount) = _getUserInfo( + _pid, + _version + ); + _emergencyWithdraw(_pid, _version); + _eventName = "LogEmergencyWithdraw(uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(_pid, _version, lpAmount, rewardsAmount); + } +} + +contract ConnectV2SushiswapIncentive is SushipswapIncentiveResolver { + string public constant name = "SushipswapIncentive-v1.1"; +} diff --git a/contracts/mainnet/connectors/sushiswap/events.sol b/contracts/mainnet/connectors/sushiswap/events.sol new file mode 100644 index 00000000..2717b0a3 --- /dev/null +++ b/contracts/mainnet/connectors/sushiswap/events.sol @@ -0,0 +1,41 @@ +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/mainnet/connectors/sushiswap/helpers.sol b/contracts/mainnet/connectors/sushiswap/helpers.sol new file mode 100644 index 00000000..a1baa4c5 --- /dev/null +++ b/contracts/mainnet/connectors/sushiswap/helpers.sol @@ -0,0 +1,184 @@ +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(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); + + 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, + minAmtA, + 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/mainnet/connectors/sushiswap/interface.sol b/contracts/mainnet/connectors/sushiswap/interface.sol new file mode 100644 index 00000000..bd3be483 --- /dev/null +++ b/contracts/mainnet/connectors/sushiswap/interface.sol @@ -0,0 +1,57 @@ +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/mainnet/connectors/sushiswap/main.sol b/contracts/mainnet/connectors/sushiswap/main.sol new file mode 100644 index 00000000..bd0078bc --- /dev/null +++ b/contracts/mainnet/connectors/sushiswap/main.sol @@ -0,0 +1,196 @@ +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 SushipswapResolver 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 ConnectV2Sushiswap is SushipswapResolver { + string public constant name = "Sushipswap-v1.1"; +} diff --git a/contracts/polygon/connectors/quickswap/events.sol b/contracts/polygon/connectors/quickswap/events.sol new file mode 100644 index 00000000..c8fc41d6 --- /dev/null +++ b/contracts/polygon/connectors/quickswap/events.sol @@ -0,0 +1,41 @@ +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 + ); +} diff --git a/contracts/polygon/connectors/quickswap/helpers.sol b/contracts/polygon/connectors/quickswap/helpers.sol new file mode 100644 index 00000000..07316a2c --- /dev/null +++ b/contracts/polygon/connectors/quickswap/helpers.sol @@ -0,0 +1,184 @@ +pragma solidity ^0.7.0; + +import {TokenInterface} from "../../common/interfaces.sol"; +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import {IQuickSwapRouter, IQuickSwapFactory} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev IQuickSwapRouter + */ + IQuickSwapRouter internal constant router = + IQuickSwapRouter(0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff); + + 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 = IQuickSwapFactory(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, + minAmtA, + 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 = IQuickSwapFactory(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/quickswap/interface.sol b/contracts/polygon/connectors/quickswap/interface.sol new file mode 100644 index 00000000..438d89c3 --- /dev/null +++ b/contracts/polygon/connectors/quickswap/interface.sol @@ -0,0 +1,97 @@ +pragma solidity ^0.7.0; + +interface IQuickSwapRouter { + function factory() external pure returns (address); + + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function quote( + uint256 amountA, + uint256 reserveA, + uint256 reserveB + ) external pure returns (uint256 amountB); + + function getAmountOut( + uint256 amountIn, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountOut); + + function getAmountIn( + uint256 amountOut, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountIn); + + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + returns (uint256[] memory amounts); +} + +interface IQuickSwapFactory { + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + 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/quickswap/main.sol b/contracts/polygon/connectors/quickswap/main.sol new file mode 100644 index 00000000..2cc07049 --- /dev/null +++ b/contracts/polygon/connectors/quickswap/main.sol @@ -0,0 +1,256 @@ +pragma solidity ^0.7.0; + +/** + * @title QuickSwap. + * @dev Decentralized Exchange. + */ + +import {TokenInterface} from "../../common/interfaces.sol"; +import {Helpers} from "./helpers.sol"; +import {Events} from "./events.sol"; + +abstract contract QuickpswapResolver is Helpers, Events { + /** + * @dev Deposit Liquidity. + * @notice Deposit Liquidity to a QuickSwap 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) + { + uint256 _amt = getUint(getId, amtA); + + (uint256 _amtA, uint256 _amtB, uint256 _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 QuickSwap 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) + { + uint256 _amt = getUint(getId, uniAmt); + + (uint256 _amtA, uint256 _amtB, uint256 _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 QuickSwap + * @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) + { + uint256 _buyAmt = getUint(getId, buyAmt); + ( + TokenInterface _buyAddr, + TokenInterface _sellAddr + ) = changeMaticAddress(buyAddr, sellAddr); + address[] memory paths = getPaths( + address(_buyAddr), + address(_sellAddr) + ); + + uint256 _slippageAmt = convert18ToDec( + _sellAddr.decimals(), + wmul(unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt)) + ); + + checkPair(paths); + uint256 _expectedAmt = getExpectedSellAmt(paths, _buyAmt); + require(_slippageAmt >= _expectedAmt, "Too much slippage"); + + bool isEth = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isEth, _sellAddr, _expectedAmt); + approve(_sellAddr, address(router), _expectedAmt); + + uint256 _sellAmt = router.swapTokensForExactTokens( + _buyAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[0]; + + isEth = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(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 QuickSwap + * @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) + { + uint256 _sellAmt = getUint(getId, sellAmt); + ( + TokenInterface _buyAddr, + TokenInterface _sellAddr + ) = changeMaticAddress(buyAddr, sellAddr); + address[] memory paths = getPaths( + address(_buyAddr), + address(_sellAddr) + ); + + if (_sellAmt == uint256(-1)) { + _sellAmt = sellAddr == maticAddr + ? address(this).balance + : _sellAddr.balanceOf(address(this)); + } + + uint256 _slippageAmt = convert18ToDec( + _buyAddr.decimals(), + wmul(unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt)) + ); + + checkPair(paths); + uint256 _expectedAmt = getExpectedBuyAmt(paths, _sellAmt); + require(_slippageAmt <= _expectedAmt, "Too much slippage"); + + bool isEth = address(_sellAddr) == wmaticAddr; + convertMaticToWmatic(isEth, _sellAddr, _sellAmt); + approve(_sellAddr, address(router), _sellAmt); + + uint256 _buyAmt = router.swapExactTokensForTokens( + _sellAmt, + _expectedAmt, + paths, + address(this), + block.timestamp + 1 + )[1]; + + isEth = address(_buyAddr) == wmaticAddr; + convertWmaticToMatic(isEth, _buyAddr, _buyAmt); + + setUint(setId, _buyAmt); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + buyAddr, + sellAddr, + _buyAmt, + _sellAmt, + getId, + setId + ); + } +} + +contract ConnectV2Quickswap is QuickpswapResolver { + string public constant name = "Quickpswap-v1.1"; +} diff --git a/test/polygon/quickswap/quickswap.test.ts b/test/polygon/quickswap/quickswap.test.ts new file mode 100644 index 00000000..b831dfe5 --- /dev/null +++ b/test/polygon/quickswap/quickswap.test.ts @@ -0,0 +1,177 @@ +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 { addresses } from "../../../scripts/tests/polygon/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2Quickswap__factory, ConnectV2Quickswap } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; + +const DAI_ADDR = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"; + +describe("Quickswap", function() { + const connectorName = "Quickpswap-v1.1"; + + 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: ConnectV2Quickswap__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 deposit successfully", async function() { + const ethAmount = ethers.utils.parseEther("100"); // 1 ETH + const daiUnitAmount = ethers.utils.parseUnits("4", 6); // 1 ETH + const usdtAmount = Number(ethers.utils.parseEther("400")) / Math.pow(10, 12); // 1 ETH + const ethAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [ + ethAddress, + DAI_ADDR, + ethAmount, + 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 ethAmount = ethers.utils.parseEther("0.1"); // 1 ETH + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + + const getId = "0"; + const setIds = ["0", "0"]; + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [ethAddress, DAI_ADDR, ethAmount, 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 ethAmount = ethers.utils.parseEther("0.1"); // 1 ETH + const daiUnitAmount = ethers.utils.parseEther("4000"); // 1 ETH + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + + const getId = "0"; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "buy", + args: [ethAddress, DAI_ADDR, ethAmount, daiUnitAmount, getId, setId], + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + let receipt = await tx.wait(); + }); + }); +});