mirror of
https://github.com/Instadapp/dsa-connectors.git
synced 2024-07-29 22:37:00 +00:00
Merge pull request #70 from Instadapp/feat/uniswap-staker
Add Uniswap-v3 staker connector
This commit is contained in:
commit
2ef29190e9
|
@ -1,8 +1,8 @@
|
||||||
pragma solidity ^0.7.0;
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import { TokenInterface } from "../../common/interfaces.sol";
|
import { TokenInterface } from "../../../common/interfaces.sol";
|
||||||
import { DSMath } from "../../common/math.sol";
|
import { DSMath } from "../../../common/math.sol";
|
||||||
import { Basic } from "../../common/basic.sol";
|
import { Basic } from "../../../common/basic.sol";
|
||||||
import { IUniswapV2Router02, IUniswapV2Factory } from "./interface.sol";
|
import { IUniswapV2Router02, IUniswapV2Factory } from "./interface.sol";
|
||||||
|
|
||||||
abstract contract Helpers is DSMath, Basic {
|
abstract contract Helpers is DSMath, Basic {
|
|
@ -5,7 +5,7 @@ pragma solidity ^0.7.0;
|
||||||
* @dev Decentralized Exchange.
|
* @dev Decentralized Exchange.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TokenInterface } from "../../common/interfaces.sol";
|
import { TokenInterface } from "../../../common/interfaces.sol";
|
||||||
import { Helpers } from "./helpers.sol";
|
import { Helpers } from "./helpers.sol";
|
||||||
import { Events } from "./events.sol";
|
import { Events } from "./events.sol";
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
pragma solidity ^0.7.6;
|
pragma solidity ^0.7.6;
|
||||||
pragma abicoder v2;
|
pragma abicoder v2;
|
||||||
|
|
||||||
import {TokenInterface} from "../../common/interfaces.sol";
|
import {TokenInterface} from "../../../common/interfaces.sol";
|
||||||
import {DSMath} from "../../common/math.sol";
|
import {DSMath} from "../../../common/math.sol";
|
||||||
import {Basic} from "../../common/basic.sol";
|
import {Basic} from "../../../common/basic.sol";
|
||||||
import "./interface.sol";
|
import "./interface.sol";
|
||||||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||||
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
|
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
|
|
@ -6,7 +6,7 @@ pragma abicoder v2;
|
||||||
* @dev Decentralized Exchange.
|
* @dev Decentralized Exchange.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TokenInterface} from "../../common/interfaces.sol";
|
import {TokenInterface} from "../../../common/interfaces.sol";
|
||||||
import {Helpers} from "./helpers.sol";
|
import {Helpers} from "./helpers.sol";
|
||||||
import {Events} from "./events.sol";
|
import {Events} from "./events.sol";
|
||||||
|
|
27
contracts/mainnet/connectors/uniswap/v3_staker/events.sol
Normal file
27
contracts/mainnet/connectors/uniswap/v3_staker/events.sol
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
|
contract Events {
|
||||||
|
event LogDeposit(uint256 tokenId);
|
||||||
|
|
||||||
|
event LogWithdraw(uint256 indexed tokenId);
|
||||||
|
|
||||||
|
event LogDepositTransfer(uint256 indexed tokenId, address to);
|
||||||
|
|
||||||
|
event LogStake(uint256 indexed tokenId, bytes32 incentiveId);
|
||||||
|
|
||||||
|
event LogUnstake(uint256 indexed tokenId, bytes32 incentiveId);
|
||||||
|
|
||||||
|
event LogRewardClaimed(
|
||||||
|
address indexed rewardToken,
|
||||||
|
uint256 amount
|
||||||
|
);
|
||||||
|
|
||||||
|
event LogIncentiveCreated(
|
||||||
|
bytes32 incentiveId,
|
||||||
|
address poolAddr,
|
||||||
|
address refundee,
|
||||||
|
uint256 startTime,
|
||||||
|
uint256 endTime,
|
||||||
|
uint256 reward
|
||||||
|
);
|
||||||
|
}
|
87
contracts/mainnet/connectors/uniswap/v3_staker/helpers.sol
Normal file
87
contracts/mainnet/connectors/uniswap/v3_staker/helpers.sol
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
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 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})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
183
contracts/mainnet/connectors/uniswap/v3_staker/interface.sol
Normal file
183
contracts/mainnet/connectors/uniswap/v3_staker/interface.sol
Normal file
|
@ -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);
|
||||||
|
}
|
214
contracts/mainnet/connectors/uniswap/v3_staker/main.sol
Normal file
214
contracts/mainnet/connectors/uniswap/v3_staker/main.sol
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
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 Deposit NFT token
|
||||||
|
* @notice Transfer deposited NFT token
|
||||||
|
* @param _tokenId NFT LP Token ID
|
||||||
|
*/
|
||||||
|
function deposit(uint256 _tokenId)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
if (_tokenId == 0) _tokenId = _getLastNftId(address(this));
|
||||||
|
nftManager.safeTransferFrom(
|
||||||
|
address(this),
|
||||||
|
address(staker),
|
||||||
|
_tokenId,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
_eventName = "LogDeposit(uint256)";
|
||||||
|
_eventParam = abi.encode(_tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Deposit Transfer
|
||||||
|
* @notice Transfer deposited NFT token
|
||||||
|
* @param _tokenId NFT LP Token ID
|
||||||
|
* @param _to address to transfer
|
||||||
|
*/
|
||||||
|
function transferDeposit(uint256 _tokenId, address _to)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
if (_tokenId == 0) _tokenId = _getLastNftId(address(this));
|
||||||
|
staker.transferDeposit(_tokenId, _to);
|
||||||
|
|
||||||
|
_eventName = "LogDepositTransfer(uint256,address)";
|
||||||
|
_eventParam = abi.encode(_tokenId, _to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Withdraw NFT LP token
|
||||||
|
* @notice Withdraw NFT LP token from staking pool
|
||||||
|
* @param _tokenId NFT LP Token ID
|
||||||
|
*/
|
||||||
|
function withdraw(uint256 _tokenId)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
if (_tokenId == 0) _tokenId = _getLastNftId(address(this));
|
||||||
|
staker.withdrawToken(_tokenId, address(this), "");
|
||||||
|
|
||||||
|
_eventName = "LogWithdraw(uint256)";
|
||||||
|
_eventParam = abi.encode(_tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
if (_tokenId == 0) _tokenId = _getLastNftId(address(this));
|
||||||
|
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,bytes32)";
|
||||||
|
_eventParam = abi.encode(_tokenId, keccak256(abi.encode(_key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
if (_tokenId == 0) _tokenId = _getLastNftId(address(this));
|
||||||
|
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, keccak256(abi.encode(_key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Claim rewards
|
||||||
|
* @notice Claim rewards
|
||||||
|
* @param _rewardToken _rewardToken address
|
||||||
|
* @param _amount requested amount
|
||||||
|
*/
|
||||||
|
function claimRewards(
|
||||||
|
address _rewardToken,
|
||||||
|
uint256 _amount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
uint256 rewards = _claimRewards(
|
||||||
|
IERC20Minimal(_rewardToken),
|
||||||
|
address(this),
|
||||||
|
_amount
|
||||||
|
);
|
||||||
|
|
||||||
|
_eventName = "LogRewardClaimed(address,uint256)";
|
||||||
|
_eventParam = abi.encode(_rewardToken, rewards);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Create incentive
|
||||||
|
* @notice Create incentive
|
||||||
|
* @param _rewardToken _rewardToken address
|
||||||
|
* @param _length incentive length
|
||||||
|
* @param _refundee refundee address
|
||||||
|
* @param _poolAddr Uniswap V3 Pool address
|
||||||
|
* @param _reward reward amount
|
||||||
|
*/
|
||||||
|
function createIncentive(
|
||||||
|
address _rewardToken,
|
||||||
|
uint256 _length,
|
||||||
|
address _refundee,
|
||||||
|
address _poolAddr,
|
||||||
|
uint256 _reward
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
IUniswapV3Pool pool = IUniswapV3Pool(_poolAddr);
|
||||||
|
uint256 _startTime = block.timestamp;
|
||||||
|
uint256 _endTime = _startTime + _length;
|
||||||
|
IUniswapV3Staker.IncentiveKey memory _key = IUniswapV3Staker
|
||||||
|
.IncentiveKey(
|
||||||
|
IERC20Minimal(_rewardToken),
|
||||||
|
pool,
|
||||||
|
_startTime,
|
||||||
|
_endTime,
|
||||||
|
_refundee
|
||||||
|
);
|
||||||
|
if (_rewardToken != ethAddr) {
|
||||||
|
IERC20Minimal(_rewardToken).approve(address(staker), _reward);
|
||||||
|
}
|
||||||
|
staker.createIncentive(_key, _reward);
|
||||||
|
|
||||||
|
_eventName = "LogIncentiveCreated(bytes32,address,address,uint256,uint256,uint256)";
|
||||||
|
_eventParam = abi.encode(keccak256(abi.encode(_key)), _poolAddr, _refundee, _startTime, _endTime, _reward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract ConnectV2UniswapV3Staker is UniswapResolver {
|
||||||
|
string public constant name = "Uniswap-V3-Staker-v1";
|
||||||
|
}
|
|
@ -57,7 +57,7 @@ module.exports = {
|
||||||
hardhat: {
|
hardhat: {
|
||||||
forking: {
|
forking: {
|
||||||
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
|
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
|
||||||
blockNumber: 13005785,
|
blockNumber: 13180514,
|
||||||
},
|
},
|
||||||
blockGasLimit: 12000000,
|
blockGasLimit: 12000000,
|
||||||
gasPrice: parseInt(utils.parseUnits("300", "gwei"))
|
gasPrice: parseInt(utils.parseUnits("300", "gwei"))
|
||||||
|
|
|
@ -50,6 +50,15 @@ const tokenMapping = {
|
||||||
await mineTx(contract.mint(address, amt));
|
await mineTx(contract.mint(address, amt));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
inst: {
|
||||||
|
impersonateSigner: "0x75e89d5979E4f6Fba9F97c104c2F0AFB3F1dcB88",
|
||||||
|
address: "0x6f40d4a6237c257fff2db00fa0510deeecd303eb",
|
||||||
|
abi: ["function transfer(address to, uint value)"],
|
||||||
|
process: async function(owner, address, amt) {
|
||||||
|
const contract = new ethers.Contract(this.address, this.abi, owner);
|
||||||
|
await mineTx(contract.transfer(address, amt));
|
||||||
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = async (tokenName, address, amt) => {
|
module.exports = async (tokenName, address, amt) => {
|
||||||
|
|
342
test/uniswapStake/uniswapStake.test.js
Normal file
342
test/uniswapStake/uniswapStake.test.js
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
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/uniswap/v3_staker/main.sol/ConnectV2UniswapV3Staker.json");
|
||||||
|
const connectV2UniswapV3Artifacts = require("../../artifacts/contracts/mainnet/connectors/uniswap/v3/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"
|
||||||
|
const INST_ADDR = "0x6f40d4a6237c257fff2db00fa0510deeecd303eb"
|
||||||
|
|
||||||
|
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 & INST 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"));
|
||||||
|
await addLiquidity("inst", dsaWallet0.address, ethers.utils.parseEther("10000"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Main", function () {
|
||||||
|
const ethAmount = ethers.utils.parseEther("0.1") // 1 ETH
|
||||||
|
const daiAmount = ethers.utils.parseEther("400") // 1 ETH
|
||||||
|
const instAmount = ethers.utils.parseEther("50")
|
||||||
|
|
||||||
|
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
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorUniswap,
|
||||||
|
method: "mint",
|
||||||
|
args: [
|
||||||
|
INST_ADDR,
|
||||||
|
ethAddress,
|
||||||
|
FeeAmount.MEDIUM,
|
||||||
|
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
|
||||||
|
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
|
||||||
|
instAmount,
|
||||||
|
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]);
|
||||||
|
const params1 = abiCoder.decode(["uint256", "uint256", "uint256", "uint256", "int24", "int24"], eventParams[1]);
|
||||||
|
tokenIds.push(params[0]);
|
||||||
|
tokenIds.push(params1[0]);
|
||||||
|
event.removeListener();
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
eventNames,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('timeout'));
|
||||||
|
}, 60000)
|
||||||
|
});
|
||||||
|
|
||||||
|
let event = await castEvent
|
||||||
|
|
||||||
|
let balance = await nftManager.connect(wallet0).balanceOf(dsaWallet0.address)
|
||||||
|
console.log("Balance", balance)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should create incentive successfully", async function () {
|
||||||
|
const spells = [
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "createIncentive",
|
||||||
|
args: [
|
||||||
|
ethAddress,
|
||||||
|
"1000",
|
||||||
|
dsaWallet0.address,
|
||||||
|
"0xc2e9f25be6257c210d7adf0d4cd6e3e881ba25f8",
|
||||||
|
ethers.utils.parseEther("0.01")
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "createIncentive",
|
||||||
|
args: [
|
||||||
|
INST_ADDR,
|
||||||
|
"50",
|
||||||
|
dsaWallet0.address,
|
||||||
|
"0xcba27c8e7115b4eb50aa14999bc0866674a96ecb",
|
||||||
|
ethers.utils.parseEther("0.01")
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet0.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(["bytes32","address","address","uint256","uint256","uint256"], eventParams[0]);
|
||||||
|
const params1 = abiCoder.decode(["bytes32","address","address","uint256","uint256","uint256"], eventParams[1]);
|
||||||
|
event.removeListener();
|
||||||
|
|
||||||
|
resolve({ start: [params[3], params1[3]], end: [params[4], params1[4]] });
|
||||||
|
});
|
||||||
|
|
||||||
|
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: "deposit",
|
||||||
|
args: [
|
||||||
|
tokenIds[0]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "stake",
|
||||||
|
args: [
|
||||||
|
ethAddress,
|
||||||
|
startTime[0],
|
||||||
|
endTime[0],
|
||||||
|
dsaWallet0.address,
|
||||||
|
tokenIds[0]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "deposit",
|
||||||
|
args: [
|
||||||
|
tokenIds[1]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "stake",
|
||||||
|
args: [
|
||||||
|
INST_ADDR,
|
||||||
|
startTime[1],
|
||||||
|
endTime[1],
|
||||||
|
dsaWallet0.address,
|
||||||
|
tokenIds[1]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
|
||||||
|
let receipt = await tx.wait()
|
||||||
|
|
||||||
|
let balance = await nftManager.connect(wallet0).balanceOf(dsaWallet0.address)
|
||||||
|
console.log("Balance", balance)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should claim rewards successfully", async function () {
|
||||||
|
const spells = [
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "claimRewards",
|
||||||
|
args: [
|
||||||
|
ethAddress,
|
||||||
|
"1000",
|
||||||
|
],
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "claimRewards",
|
||||||
|
args: [
|
||||||
|
INST_ADDR,
|
||||||
|
"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[0],
|
||||||
|
endTime[0],
|
||||||
|
dsaWallet0.address,
|
||||||
|
tokenIds[0]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "withdraw",
|
||||||
|
args: [
|
||||||
|
tokenIds[0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "unstake",
|
||||||
|
args: [
|
||||||
|
INST_ADDR,
|
||||||
|
startTime[1],
|
||||||
|
endTime[1],
|
||||||
|
dsaWallet0.address,
|
||||||
|
tokenIds[1]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: connectorStaker,
|
||||||
|
method: "withdraw",
|
||||||
|
args: [
|
||||||
|
tokenIds[1]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
|
||||||
|
let receipt = await tx.wait()
|
||||||
|
|
||||||
|
let balance = await nftManager.connect(wallet0).balanceOf(dsaWallet0.address)
|
||||||
|
console.log("Balance", balance)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const getMinTick = (tickSpacing) => Math.ceil(-887272 / tickSpacing) * tickSpacing
|
||||||
|
const getMaxTick = (tickSpacing) => Math.floor(887272 / tickSpacing) * tickSpacing
|
Loading…
Reference in New Issue
Block a user