From 30eb39378fb32da235e1a294cd7007d74d43271e Mon Sep 17 00:00:00 2001 From: cryptoDev222 Date: Tue, 24 Aug 2021 14:04:17 -0500 Subject: [PATCH] implement uniswap v3 staker --- .../connectors/uniswapStaker/events.sol | 34 +++ .../connectors/uniswapStaker/helpers.sol | 230 ++++++++++++++++ .../connectors/uniswapStaker/interface.sol | 183 +++++++++++++ .../mainnet/connectors/uniswapStaker/main.sol | 237 +++++++++++++++++ test/uniswapStake/uniswapStake.test.js | 245 ++++++++++++++++++ 5 files changed, 929 insertions(+) create mode 100644 contracts/mainnet/connectors/uniswapStaker/events.sol create mode 100644 contracts/mainnet/connectors/uniswapStaker/helpers.sol create mode 100644 contracts/mainnet/connectors/uniswapStaker/interface.sol create mode 100644 contracts/mainnet/connectors/uniswapStaker/main.sol create mode 100644 test/uniswapStake/uniswapStake.test.js diff --git a/contracts/mainnet/connectors/uniswapStaker/events.sol b/contracts/mainnet/connectors/uniswapStaker/events.sol new file mode 100644 index 00000000..8618e151 --- /dev/null +++ b/contracts/mainnet/connectors/uniswapStaker/events.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + uint256 indexed tokenId, + uint256 liquidity, + uint256 amountA, + uint256 amountB + ); + + event LogWithdraw( + uint256 indexed tokenId, + uint256 liquidity, + uint256 amountA, + uint256 amountB + ); + + event LogStake(uint256 tokenId, address refundee); + + event LogUnstake(uint256 tokenId, bytes32 incentiveId); + + event LogRewardClaimed( + address rewardToken, + address receiver, + uint256 amount + ); + + event LogIncentiveCreated( + uint256 tokenId, + uint256 startTime, + uint256 endTime, + uint256 reward + ); +} diff --git a/contracts/mainnet/connectors/uniswapStaker/helpers.sol b/contracts/mainnet/connectors/uniswapStaker/helpers.sol new file mode 100644 index 00000000..e7b8e7af --- /dev/null +++ b/contracts/mainnet/connectors/uniswapStaker/helpers.sol @@ -0,0 +1,230 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +import {TokenInterface} from "../../common/interfaces.sol"; +import {DSMath} from "../../common/math.sol"; +import {Basic} from "../../common/basic.sol"; +import "./interface.sol"; +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import "@uniswap/v3-core/contracts/libraries/TickMath.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev uniswap v3 NFT Position Manager & Swap Router + */ + INonfungiblePositionManager constant nftManager = + INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + IUniswapV3Staker constant staker = + IUniswapV3Staker(0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d); + + /** + * @dev Get Last NFT Index + * @param user: User address + */ + function _getLastNftId(address user) + internal + view + returns (uint256 tokenId) + { + uint256 len = nftManager.balanceOf(user); + tokenId = nftManager.tokenOfOwnerByIndex(user, len - 1); + } + + 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 getNftTokenPairAddresses(uint256 _tokenId) + internal + view + returns (address token0, address token1) + { + (bool success, bytes memory data) = address(nftManager).staticcall( + abi.encodeWithSelector(nftManager.positions.selector, _tokenId) + ); + require(success, "fetching positions failed"); + { + (, , token0, token1, , , , ) = abi.decode( + data, + ( + uint96, + address, + address, + address, + uint24, + int24, + int24, + uint128 + ) + ); + } + } + + function getPoolAddress(uint256 _tokenId) + internal + view + returns (address pool) + { + (bool success, bytes memory data) = address(nftManager).staticcall( + abi.encodeWithSelector(nftManager.positions.selector, _tokenId) + ); + require(success, "fetching positions failed"); + { + (, , address token0, address token1, uint24 fee, , , ) = abi.decode( + data, + ( + uint96, + address, + address, + address, + uint24, + int24, + int24, + uint128 + ) + ); + + pool = PoolAddress.computeAddress( + nftManager.factory(), + PoolAddress.PoolKey({token0: token0, token1: token1, fee: fee}) + ); + } + } + + /** + * @dev Check if token address is etherAddr and convert it to weth + */ + function _checkETH( + address _token0, + address _token1, + uint256 _amount0, + uint256 _amount1 + ) internal { + bool isEth0 = _token0 == wethAddr; + bool isEth1 = _token1 == wethAddr; + convertEthToWeth(isEth0, TokenInterface(_token0), _amount0); + convertEthToWeth(isEth1, TokenInterface(_token1), _amount1); + approve(TokenInterface(_token0), address(nftManager), _amount0); + approve(TokenInterface(_token1), address(nftManager), _amount1); + } + + /** + * @dev addLiquidityWrapper function wrapper of _addLiquidity + */ + function _addLiquidityWrapper( + uint256 tokenId, + uint256 amountA, + uint256 amountB, + uint256 slippage + ) + internal + returns ( + uint256 liquidity, + uint256 amtA, + uint256 amtB + ) + { + (address token0, address token1) = getNftTokenPairAddresses(tokenId); + + (liquidity, amtA, amtB) = _addLiquidity( + tokenId, + token0, + token1, + amountA, + amountB, + slippage + ); + } + + /** + * @dev addLiquidity function which interact with Uniswap v3 + */ + function _addLiquidity( + uint256 _tokenId, + address _token0, + address _token1, + uint256 _amount0, + uint256 _amount1, + uint256 _slippage + ) + internal + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ) + { + _checkETH(_token0, _token1, _amount0, _amount1); + uint256 _amount0Min = getMinAmount( + TokenInterface(_token0), + _amount0, + _slippage + ); + uint256 _amount1Min = getMinAmount( + TokenInterface(_token1), + _amount1, + _slippage + ); + INonfungiblePositionManager.IncreaseLiquidityParams + memory params = INonfungiblePositionManager.IncreaseLiquidityParams( + _tokenId, + _amount0, + _amount1, + _amount0Min, + _amount1Min, + block.timestamp + ); + + (liquidity, amount0, amount1) = nftManager.increaseLiquidity(params); + } + + /** + * @dev decreaseLiquidity function which interact with Uniswap v3 + */ + function _decreaseLiquidity( + uint256 _tokenId, + uint128 _liquidity, + uint256 _amount0Min, + uint256 _amount1Min + ) internal returns (uint256 amount0, uint256 amount1) { + INonfungiblePositionManager.DecreaseLiquidityParams + memory params = INonfungiblePositionManager.DecreaseLiquidityParams( + _tokenId, + _liquidity, + _amount0Min, + _amount1Min, + block.timestamp + ); + (amount0, amount1) = nftManager.decreaseLiquidity(params); + } + + function _stake( + uint256 _tokenId, + IUniswapV3Staker.IncentiveKey memory _incentiveId + ) internal { + staker.stakeToken(_incentiveId, _tokenId); + } + + function _unstake( + IUniswapV3Staker.IncentiveKey memory _key, + uint256 _tokenId + ) internal { + staker.unstakeToken(_key, _tokenId); + } + + function _claimRewards( + IERC20Minimal _rewardToken, + address _to, + uint256 _amountRequested + ) internal returns (uint256 rewards) { + rewards = staker.claimReward(_rewardToken, _to, _amountRequested); + } +} diff --git a/contracts/mainnet/connectors/uniswapStaker/interface.sol b/contracts/mainnet/connectors/uniswapStaker/interface.sol new file mode 100644 index 00000000..5c537e43 --- /dev/null +++ b/contracts/mainnet/connectors/uniswapStaker/interface.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity =0.7.6; +pragma abicoder v2; + +import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; + +import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol'; +import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; +import '@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol'; + +import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol'; +import '@uniswap/v3-periphery/contracts/interfaces/IMulticall.sol'; + +/// @title Uniswap V3 Staker Interface +/// @notice Allows staking nonfungible liquidity tokens in exchange for reward tokens +interface IUniswapV3Staker is IERC721Receiver, IMulticall { + /// @param rewardToken The token being distributed as a reward + /// @param pool The Uniswap V3 pool + /// @param startTime The time when the incentive program begins + /// @param endTime The time when rewards stop accruing + /// @param refundee The address which receives any remaining reward tokens when the incentive is ended + struct IncentiveKey { + IERC20Minimal rewardToken; + IUniswapV3Pool pool; + uint256 startTime; + uint256 endTime; + address refundee; + } + + /// @notice The Uniswap V3 Factory + function factory() external view returns (IUniswapV3Factory); + + /// @notice The nonfungible position manager with which this staking contract is compatible + function nonfungiblePositionManager() external view returns (INonfungiblePositionManager); + + /// @notice The max duration of an incentive in seconds + function maxIncentiveDuration() external view returns (uint256); + + /// @notice The max amount of seconds into the future the incentive startTime can be set + function maxIncentiveStartLeadTime() external view returns (uint256); + + /// @notice Represents a staking incentive + /// @param incentiveId The ID of the incentive computed from its parameters + /// @return totalRewardUnclaimed The amount of reward token not yet claimed by users + /// @return totalSecondsClaimedX128 Total liquidity-seconds claimed, represented as a UQ32.128 + /// @return numberOfStakes The count of deposits that are currently staked for the incentive + function incentives(bytes32 incentiveId) + external + view + returns ( + uint256 totalRewardUnclaimed, + uint160 totalSecondsClaimedX128, + uint96 numberOfStakes + ); + + /// @notice Returns information about a deposited NFT + /// @return owner The owner of the deposited NFT + /// @return numberOfStakes Counter of how many incentives for which the liquidity is staked + /// @return tickLower The lower tick of the range + /// @return tickUpper The upper tick of the range + function deposits(uint256 tokenId) + external + view + returns ( + address owner, + uint48 numberOfStakes, + int24 tickLower, + int24 tickUpper + ); + + /// @notice Returns information about a staked liquidity NFT + /// @param tokenId The ID of the staked token + /// @param incentiveId The ID of the incentive for which the token is staked + /// @return secondsPerLiquidityInsideInitialX128 secondsPerLiquidity represented as a UQ32.128 + /// @return liquidity The amount of liquidity in the NFT as of the last time the rewards were computed + function stakes(uint256 tokenId, bytes32 incentiveId) + external + view + returns (uint160 secondsPerLiquidityInsideInitialX128, uint128 liquidity); + + /// @notice Returns amounts of reward tokens owed to a given address according to the last time all stakes were updated + /// @param rewardToken The token for which to check rewards + /// @param owner The owner for which the rewards owed are checked + /// @return rewardsOwed The amount of the reward token claimable by the owner + function rewards(IERC20Minimal rewardToken, address owner) external view returns (uint256 rewardsOwed); + + /// @notice Creates a new liquidity mining incentive program + /// @param key Details of the incentive to create + /// @param reward The amount of reward tokens to be distributed + function createIncentive(IncentiveKey memory key, uint256 reward) external; + + /// @notice Ends an incentive after the incentive end time has passed and all stakes have been withdrawn + /// @param key Details of the incentive to end + /// @return refund The remaining reward tokens when the incentive is ended + function endIncentive(IncentiveKey memory key) external returns (uint256 refund); + + /// @notice Transfers ownership of a deposit from the sender to the given recipient + /// @param tokenId The ID of the token (and the deposit) to transfer + /// @param to The new owner of the deposit + function transferDeposit(uint256 tokenId, address to) external; + + /// @notice Withdraws a Uniswap V3 LP token `tokenId` from this contract to the recipient `to` + /// @param tokenId The unique identifier of an Uniswap V3 LP token + /// @param to The address where the LP token will be sent + /// @param data An optional data array that will be passed along to the `to` address via the NFT safeTransferFrom + function withdrawToken( + uint256 tokenId, + address to, + bytes memory data + ) external; + + /// @notice Stakes a Uniswap V3 LP token + /// @param key The key of the incentive for which to stake the NFT + /// @param tokenId The ID of the token to stake + function stakeToken(IncentiveKey memory key, uint256 tokenId) external; + + /// @notice Unstakes a Uniswap V3 LP token + /// @param key The key of the incentive for which to unstake the NFT + /// @param tokenId The ID of the token to unstake + function unstakeToken(IncentiveKey memory key, uint256 tokenId) external; + + /// @notice Transfers `amountRequested` of accrued `rewardToken` rewards from the contract to the recipient `to` + /// @param rewardToken The token being distributed as a reward + /// @param to The address where claimed rewards will be sent to + /// @param amountRequested The amount of reward tokens to claim. Claims entire reward amount if set to 0. + /// @return reward The amount of reward tokens claimed + function claimReward( + IERC20Minimal rewardToken, + address to, + uint256 amountRequested + ) external returns (uint256 reward); + + /// @notice Calculates the reward amount that will be received for the given stake + /// @param key The key of the incentive + /// @param tokenId The ID of the token + /// @return reward The reward accrued to the NFT for the given incentive thus far + function getRewardInfo(IncentiveKey memory key, uint256 tokenId) + external + returns (uint256 reward, uint160 secondsInsideX128); + + /// @notice Event emitted when a liquidity mining incentive has been created + /// @param rewardToken The token being distributed as a reward + /// @param pool The Uniswap V3 pool + /// @param startTime The time when the incentive program begins + /// @param endTime The time when rewards stop accruing + /// @param refundee The address which receives any remaining reward tokens after the end time + /// @param reward The amount of reward tokens to be distributed + event IncentiveCreated( + IERC20Minimal indexed rewardToken, + IUniswapV3Pool indexed pool, + uint256 startTime, + uint256 endTime, + address refundee, + uint256 reward + ); + + /// @notice Event that can be emitted when a liquidity mining incentive has ended + /// @param incentiveId The incentive which is ending + /// @param refund The amount of reward tokens refunded + event IncentiveEnded(bytes32 indexed incentiveId, uint256 refund); + + /// @notice Emitted when ownership of a deposit changes + /// @param tokenId The ID of the deposit (and token) that is being transferred + /// @param oldOwner The owner before the deposit was transferred + /// @param newOwner The owner after the deposit was transferred + event DepositTransferred(uint256 indexed tokenId, address indexed oldOwner, address indexed newOwner); + + /// @notice Event emitted when a Uniswap V3 LP token has been staked + /// @param tokenId The unique identifier of an Uniswap V3 LP token + /// @param liquidity The amount of liquidity staked + /// @param incentiveId The incentive in which the token is staking + event TokenStaked(uint256 indexed tokenId, bytes32 indexed incentiveId, uint128 liquidity); + + /// @notice Event emitted when a Uniswap V3 LP token has been unstaked + /// @param tokenId The unique identifier of an Uniswap V3 LP token + /// @param incentiveId The incentive in which the token is staking + event TokenUnstaked(uint256 indexed tokenId, bytes32 indexed incentiveId); + + /// @notice Event emitted when a reward token has been claimed + /// @param to The address where claimed rewards were sent to + /// @param reward The amount of reward tokens claimed + event RewardClaimed(address indexed to, uint256 reward); +} diff --git a/contracts/mainnet/connectors/uniswapStaker/main.sol b/contracts/mainnet/connectors/uniswapStaker/main.sol new file mode 100644 index 00000000..da0db209 --- /dev/null +++ b/contracts/mainnet/connectors/uniswapStaker/main.sol @@ -0,0 +1,237 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Uniswap v3. + * @dev Decentralized Exchange. + */ + +import {TokenInterface} from "../../common/interfaces.sol"; +import "./interface.sol"; +import {Helpers} from "./helpers.sol"; +import {Events} from "./events.sol"; + +abstract contract UniswapResolver is Helpers, Events { + /** + * @dev Increase Liquidity + * @notice Increase Liquidity of NFT Position + * @param tokenId NFT LP Token ID. + * @param amountA tokenA amounts. + * @param amountB tokenB amounts. + * @param slippage slippage. + * @param getIds IDs to retrieve token amounts + * @param setId stores the liquidity amount + */ + function deposit( + uint256 tokenId, + uint256 amountA, + uint256 amountB, + uint256 slippage, + uint256[] calldata getIds, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (tokenId == 0) tokenId = _getLastNftId(address(this)); + amountA = getUint(getIds[0], amountA); + amountB = getUint(getIds[1], amountB); + ( + uint256 _liquidity, + uint256 _amtA, + uint256 _amtB + ) = _addLiquidityWrapper(tokenId, amountA, amountB, slippage); + setUint(setId, _liquidity); + + _eventName = "LogDeposit(uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(tokenId, _liquidity, _amtA, _amtB); + } + + /** + * @dev Decrease Liquidity + * @notice Decrease Liquidity of NFT Position + * @param tokenId NFT LP Token ID. + * @param liquidity LP Token amount. + * @param amountAMin Min amount of tokenA. + * @param amountBMin Min amount of tokenB. + * @param getId ID to retrieve LP token amounts + * @param setIds stores the amount of output tokens + */ + function withdraw( + uint256 tokenId, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + uint256 getId, + uint256[] calldata setIds + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (tokenId == 0) tokenId = _getLastNftId(address(this)); + uint128 _liquidity = uint128(getUint(getId, liquidity)); + + (uint256 _amtA, uint256 _amtB) = _decreaseLiquidity( + tokenId, + _liquidity, + amountAMin, + amountBMin + ); + + setUint(setIds[0], _amtA); + setUint(setIds[1], _amtB); + + _eventName = "LogWithdraw(uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(tokenId, _liquidity, _amtA, _amtB); + } + + /** + * @dev Stake NFT LP token + * @notice Stake NFT LP Position + * @param _rewardToken _rewardToken address + * @param _startTime stake start time + * @param _endTime stake end time + * @param _refundee refundee address + * @param _tokenId NFT LP token id + */ + function stake( + address _rewardToken, + uint256 _startTime, + uint256 _endTime, + address _refundee, + uint256 _tokenId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + nftManager.safeTransferFrom( + address(this), + address(staker), + _tokenId, + "" + ); + address poolAddr = getPoolAddress(_tokenId); + + IUniswapV3Pool pool = IUniswapV3Pool(poolAddr); + IUniswapV3Staker.IncentiveKey memory _key = IUniswapV3Staker + .IncentiveKey( + IERC20Minimal(_rewardToken), + pool, + _startTime, + _endTime, + _refundee + ); + _stake(_tokenId, _key); + + _eventName = "LogStake(uint256, address)"; + _eventParam = abi.encode(_tokenId, _refundee); + } + + /** + * @dev Unstake NFT LP token + * @notice Unstake NFT LP Position + * @param _rewardToken _rewardToken address + * @param _startTime stake start time + * @param _endTime stake end time + * @param _refundee refundee address + * @param _tokenId NFT LP token id + */ + function unstake( + address _rewardToken, + uint256 _startTime, + uint256 _endTime, + address _refundee, + uint256 _tokenId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + address poolAddr = getPoolAddress(_tokenId); + + IUniswapV3Pool pool = IUniswapV3Pool(poolAddr); + IUniswapV3Staker.IncentiveKey memory _key = IUniswapV3Staker + .IncentiveKey( + IERC20Minimal(_rewardToken), + pool, + _startTime, + _endTime, + _refundee + ); + _unstake(_key, _tokenId); + _eventName = "LogUnstake(uint256,bytes32)"; + _eventParam = abi.encode(_tokenId, _key); + } + + /** + * @dev Claim rewards + * @notice Claim rewards + * @param _rewardToken _rewardToken address + * @param _to address to receive + * @param _amountRequested requested amount + */ + function claimRewards( + address _rewardToken, + address _to, + uint256 _amountRequested + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 rewards = _claimRewards( + IERC20Minimal(_rewardToken), + _to, + _amountRequested + ); + + _eventName = "LogRewardClaimed(address,address,uint256)"; + _eventParam = abi.encode(_rewardToken, _to, rewards); + } + + /** + * @dev Create incentive + * @notice Create incentive + * @param _rewardToken _rewardToken address + * @param _length incentive length + * @param _refundee refundee address + * @param _tokenId NFT LP token id + * @param _reward reward amount + */ + function createIncentive( + address _rewardToken, + uint256 _length, + address _refundee, + uint256 _tokenId, + uint256 _reward + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + address poolAddr = getPoolAddress(_tokenId); + + IUniswapV3Pool pool = IUniswapV3Pool(poolAddr); + uint256 _startTime = block.timestamp; + uint256 _endTime = _startTime + _length; + IUniswapV3Staker.IncentiveKey memory _key = IUniswapV3Staker + .IncentiveKey( + IERC20Minimal(_rewardToken), + pool, + _startTime, + _endTime, + _refundee + ); + staker.createIncentive(_key, _reward); + + _eventName = "LogIncentiveCreated(uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(_tokenId, _startTime, _endTime, _reward); + } +} + +contract ConnectV2UniswapV3Staker is UniswapResolver { + string public constant name = "UniswapStaker-v1"; +} diff --git a/test/uniswapStake/uniswapStake.test.js b/test/uniswapStake/uniswapStake.test.js new file mode 100644 index 00000000..0ca88ae0 --- /dev/null +++ b/test/uniswapStake/uniswapStake.test.js @@ -0,0 +1,245 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") +const addLiquidity = require("../../scripts/addLiquidity"); + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const { abi: nftManagerAbi } = require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json") + +const connectV2UniswapStakerArtifacts = require("../../artifacts/contracts/mainnet/connectors/uniswapStaker/main.sol/ConnectV2UniswapV3Staker.json"); +const connectV2UniswapV3Artifacts = require("../../artifacts/contracts/mainnet/connectors/uniswapV3/main.sol/ConnectV2UniswapV3.json"); + +const FeeAmount = { + LOW: 500, + MEDIUM: 3000, + HIGH: 10000, +} + +const TICK_SPACINGS = { + 500: 10, + 3000: 60, + 10000: 200 +} + +const DAI_ADDR = "0x6b175474e89094c44da98b954eedeac495271d0f" +const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + +let tokenIds = [] +const abiCoder = ethers.utils.defaultAbiCoder + +describe("UniswapV3", function () { + const connectorStaker = "UniswapStaker-v1" + const connectorUniswap = "UniswapV3-v1" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + let startTime, endTime; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + before(async () => { + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + nftManager = await ethers.getContractAt(nftManagerAbi, "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"); + connector = await deployAndEnableConnector({ + connectorName: connectorStaker, + contractArtifact: connectV2UniswapStakerArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + console.log("Connector address", connector.address) + + uniswapConnector = await deployAndEnableConnector({ + connectorName: connectorUniswap, + contractArtifact: connectV2UniswapV3Artifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + }) + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).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("dai", dsaWallet0.address, ethers.utils.parseEther("100000")); + await addLiquidity("usdt", dsaWallet0.address, ethers.utils.parseEther("100000")); + }); + }); + + describe("Main", function () { + const ethAmount = ethers.utils.parseEther("0.1") // 1 ETH + const daiAmount = ethers.utils.parseEther("400") // 1 ETH + + it("Should mint successfully", async function () { + const getIds = ["0", "0"] + const setId = "0" + + const spells = [ + { + connector: connectorUniswap, + method: "mint", + args: [ + DAI_ADDR, + ethAddress, + FeeAmount.MEDIUM, + getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + daiAmount, + ethAmount, + "500000000000000000", + getIds, + setId + ], + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + + let castEvent = new Promise((resolve, reject) => { + dsaWallet0.on('LogCast', (origin, sender, value, targetNames, targets, eventNames, eventParams, event) => { + const params = abiCoder.decode(["uint256", "uint256", "uint256", "uint256", "int24", "int24"], eventParams[0]); + tokenIds.push(params[0]); + event.removeListener(); + + resolve({ + eventNames, + }); + }); + + setTimeout(() => { + reject(new Error('timeout')); + }, 60000) + }); + + let event = await castEvent + }); + + it("Should create incentive successfully", async function () { + const spells = [ + { + connector: connectorStaker, + method: "createIncentive", + args: [ + ethAddress, + "1000", + dsaWallet0.address, + tokenIds[0], + ethers.utils.parseEther("0.01") + ], + }] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + + let castEvent = new Promise((resolve, reject) => { + dsaWallet0.on('LogCast', (origin, sender, value, targetNames, targets, eventNames, eventParams, event) => { + const params = abiCoder.decode(["uint256", "uint256", "uint256", "uint256"], eventParams[0]); + event.removeListener(); + + resolve({ start: params[1], end: params[2] }); + }); + + setTimeout(() => { + reject(new Error('timeout')); + }, 60000) + }); + + let event = await castEvent + startTime = event.start; + endTime = event.end; + }); + + it("Should stake successfully", async function () { + const spells = [ + { + connector: connectorStaker, + method: "stake", + args: [ + ethAddress, + startTime, + endTime, + dsaWallet0.address, + tokenIds[0] + ], + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + }); + + it("Should claim rewards successfully", async function () { + const spells = [ + { + connector: connectorStaker, + method: "claimRewards", + args: [ + ethAddress, + dsaWallet0.address, + "1000", + ], + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + }); + + it("Should unstake successfully", async function () { + const spells = [ + { + connector: connectorStaker, + method: "unstake", + args: [ + ethAddress, + startTime, + endTime, + dsaWallet0.address, + tokenIds[0] + ], + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + }); + }) +}) + +const getMinTick = (tickSpacing) => Math.ceil(-887272 / tickSpacing) * tickSpacing +const getMaxTick = (tickSpacing) => Math.floor(887272 / tickSpacing) * tickSpacing