Merge pull request #114 from Instadapp/uniswap-arbitrum

feat: simple uniswap connector on arbitrum
This commit is contained in:
Samyak Jain 2021-11-20 23:08:01 +05:30 committed by GitHub
commit b2e1060e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1187 additions and 278 deletions

View File

@ -0,0 +1,10 @@
pragma solidity ^0.7.0;
contract Events {
event LogSell(
uint24 fee,
uint256 amountIn,
uint256 amountOut,
uint256 amountOutMinimum
);
}

View File

@ -0,0 +1,123 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
import {UniswapV3Pool, ISwapRouter} from "./interface.sol";
import {SqrtPriceMath} from "./libraries/SqrtPriceMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract Helpers {
using SafeERC20 for IERC20;
ISwapRouter internal constant router =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
bytes32 internal constant POOL_INIT_CODE_HASH =
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
struct PoolKey {
address token0;
address token1;
uint24 fee;
}
function getPoolAddress(
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (address pool) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return
computeAddress(
0x1F98431c8aD98523631AE4a59f267346ea31F984,
PoolKey({token0: tokenA, token1: tokenB, fee: fee})
);
}
function computeAddress(address factory, PoolKey memory key)
internal
pure
returns (address pool)
{
require(key.token0 < key.token1);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(
abi.encode(key.token0, key.token1, key.fee)
),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
function getPriceLimit(
uint256 amountIn,
bool zeroForOne,
address tokenA,
address tokenB,
uint24 fee
) internal view returns (uint160) {
UniswapV3Pool state = UniswapV3Pool(
getPoolAddress(tokenA, tokenB, fee)
);
return (
SqrtPriceMath.getNextSqrtPriceFromInput(
state.slot0().sqrtPriceX96,
state.liquidity(),
amountIn,
zeroForOne
)
);
}
function getParams(
address tokenIn,
address tokenOut,
address recipient,
uint24 fee,
uint256 amountIn,
uint256 amountOutMinimum
) internal view returns (ISwapRouter.ExactInputSingleParams memory params) {
params = ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: recipient,
deadline: block.timestamp + 1,
amountIn: amountIn,
amountOutMinimum: amountOutMinimum,
sqrtPriceLimitX96: getPriceLimit(
amountIn,
tokenIn < tokenOut,
tokenIn,
tokenOut,
fee
)
});
}
function SwapTokens(
address tokenIn,
address tokenOut,
bool zeroForOne
) internal pure returns (address, address) {
if (!zeroForOne) return (tokenOut, tokenIn);
else return (tokenIn, tokenOut);
}
function swapSingleInput(ISwapRouter.ExactInputSingleParams memory params)
internal
returns (uint256)
{
return (uint256(router.exactInputSingle(params)));
}
}

View File

@ -0,0 +1,36 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
interface UniswapV3Pool {
struct Slot0 {
uint160 sqrtPriceX96;
int24 tick;
uint16 observationIndex;
uint16 observationCardinality;
uint16 observationCardinalityNext;
uint8 feeProtocol;
bool unlocked;
}
function liquidity() external view returns (uint128);
function slot0() external view returns (Slot0 memory);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256);
}

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.4.0;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}

View File

@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = -denominator & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.0;
/// @title Optimized overflow and underflow safe math operations
/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost
library LowGasSafeMath {
/// @notice Returns x + y, reverts if sum overflows uint256
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x);
}
/// @notice Returns x - y, reverts if underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x);
}
/// @notice Returns x * y, reverts if overflows
/// @param x The multiplicand
/// @param y The multiplier
/// @return z The product of x and y
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(x == 0 || (z = x * y) / x == y);
}
/// @notice Returns x + y, reverts if overflows or underflows
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x + y) >= x == (y >= 0));
}
/// @notice Returns x - y, reverts if overflows or underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x - y) <= x == (y >= 0));
}
}

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
/// @notice Cast a uint256 to a uint160, revert on overflow
/// @param y The uint256 to be downcasted
/// @return z The downcasted integer, now type uint160
function toUint160(uint256 y) internal pure returns (uint160 z) {
require((z = uint160(y)) == y);
}
/// @notice Cast a int256 to a int128, revert on overflow or underflow
/// @param y The int256 to be downcasted
/// @return z The downcasted integer, now type int128
function toInt128(int256 y) internal pure returns (int128 z) {
require((z = int128(y)) == y);
}
/// @notice Cast a uint256 to a int256, revert on overflow
/// @param y The uint256 to be casted
/// @return z The casted integer, now type int256
function toInt256(uint256 y) internal pure returns (int256 z) {
require(y < 2**255);
z = int256(y);
}
}

View File

@ -0,0 +1,227 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import './LowGasSafeMath.sol';
import './SafeCast.sol';
import './FullMath.sol';
import './UnsafeMath.sol';
import './FixedPoint96.sol';
/// @title Functions based on Q64.96 sqrt price and liquidity
/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas
library SqrtPriceMath {
using LowGasSafeMath for uint256;
using SafeCast for uint256;
/// @notice Gets the next sqrt price given a delta of token0
/// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the
/// price less in order to not send too much output.
/// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96),
/// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount).
/// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token0 to add or remove from virtual reserves
/// @param add Whether to add or remove the amount of token0
/// @return The price after adding or removing amount, depending on add
function getNextSqrtPriceFromAmount0RoundingUp(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price
if (amount == 0) return sqrtPX96;
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
if (add) {
uint256 product;
if ((product = amount * sqrtPX96) / amount == sqrtPX96) {
uint256 denominator = numerator1 + product;
if (denominator >= numerator1)
// always fits in 160 bits
return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator));
}
return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount)));
} else {
uint256 product;
// if the product overflows, we know the denominator underflows
// in addition, we must check that the denominator does not underflow
require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
uint256 denominator = numerator1 - product;
return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160();
}
}
/// @notice Gets the next sqrt price given a delta of token1
/// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the
/// price less in order to not send too much output.
/// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity
/// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token1 to add, or remove, from virtual reserves
/// @param add Whether to add, or remove, the amount of token1
/// @return The price after adding or removing `amount`
function getNextSqrtPriceFromAmount1RoundingDown(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// if we're adding (subtracting), rounding down requires rounding the quotient down (up)
// in both cases, avoid a mulDiv for most inputs
if (add) {
uint256 quotient =
(
amount <= type(uint160).max
? (amount << FixedPoint96.RESOLUTION) / liquidity
: FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity)
);
return uint256(sqrtPX96).add(quotient).toUint160();
} else {
uint256 quotient =
(
amount <= type(uint160).max
? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
: FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
);
require(sqrtPX96 > quotient);
// always fits 160 bits
return uint160(sqrtPX96 - quotient);
}
}
/// @notice Gets the next sqrt price given an input amount of token0 or token1
/// @dev Throws if price or liquidity are 0, or if the next price is out of bounds
/// @param sqrtPX96 The starting price, i.e., before accounting for the input amount
/// @param liquidity The amount of usable liquidity
/// @param amountIn How much of token0, or token1, is being swapped in
/// @param zeroForOne Whether the amount in is token0 or token1
/// @return sqrtQX96 The price after adding the input amount to token0 or token1
function getNextSqrtPriceFromInput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountIn,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we don't pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true)
: getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true);
}
/// @notice Gets the next sqrt price given an output amount of token0 or token1
/// @dev Throws if price or liquidity are 0 or the next price is out of bounds
/// @param sqrtPX96 The starting price before accounting for the output amount
/// @param liquidity The amount of usable liquidity
/// @param amountOut How much of token0, or token1, is being swapped out
/// @param zeroForOne Whether the amount out is token0 or token1
/// @return sqrtQX96 The price after removing the output amount of token0 or token1
function getNextSqrtPriceFromOutput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountOut,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false)
: getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false);
}
/// @notice Gets the amount0 delta between two prices
/// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper),
/// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up or down
/// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount0) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
require(sqrtRatioAX96 > 0);
return
roundUp
? UnsafeMath.divRoundingUp(
FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96),
sqrtRatioAX96
)
: FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96;
}
/// @notice Gets the amount1 delta between two prices
/// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up, or down
/// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return
roundUp
? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96)
: FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
}
/// @notice Helper that gets signed token0 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount0 delta
/// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount0) {
return
liquidity < 0
? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
/// @notice Helper that gets signed token1 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount1 delta
/// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount1) {
return
liquidity < 0
? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
}

View File

@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Math functions that do not check inputs or outputs
/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks
library UnsafeMath {
/// @notice Returns ceil(x / y)
/// @dev division by 0 has unspecified behavior, and must be checked externally
/// @param x The dividend
/// @param y The divisor
/// @return z The quotient, ceil(x / y)
function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := add(div(x, y), gt(mod(x, y), 0))
}
}
}

View File

@ -0,0 +1,41 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
import "./helpers.sol";
import {Events} from "./events.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract uniswapSellBeta is Helpers, Events {
using SafeERC20 for IERC20;
function sell(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint256 amountOutMinimum
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
IERC20(tokenIn).safeApprove(address(router), amountIn);
uint256 amountOut = swapSingleInput(
getParams(
tokenIn,
tokenOut,
address(this),
fee,
amountIn,
amountOutMinimum
)
);
_eventName = "LogSell(uint24,uint256,uint256,uint256)";
_eventParam = abi.encode(fee, amountIn, amountOut, amountOutMinimum);
}
}
contract ConnectV2UniswapSellBeta is uniswapSellBeta {
string public constant name = "Uniswap-Sell-Beta";
}

View File

@ -80,12 +80,12 @@ module.exports = {
gasPrice: parseInt(utils.parseUnits("2", "gwei")),
},
avax: {
url: 'https://api.avax.network/ext/bc/C/rpc',
url: "https://api.avax.network/ext/bc/C/rpc",
chainId: 43114,
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("225", "gwei"))
}
gasPrice: parseInt(utils.parseUnits("225", "gwei")),
},
},
etherscan: {
apiKey: ETHERSCAN_API_KEY,
@ -97,4 +97,4 @@ module.exports = {
mocha: {
timeout: 100 * 1000,
},
};
};

80
test/sample/uniswap.js Normal file
View File

@ -0,0 +1,80 @@
const { expect } = require("chai");
const hre = require("hardhat");
const { web3, deployments, 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 addresses = require("../../scripts/constant/addresses");
const abis = require("../../scripts/constant/abis");
const FeeAmount = {
LOW: 500,
MEDIUM: 3000,
HIGH: 10000,
};
const TICK_SPACINGS = {
500: 10,
3000: 60,
10000: 200,
};
const USDT_ADDR = "0xdac17f958d2ee523a2206206994597c13d831ec7";
const DAI_ADDR = "0x6b175474e89094c44da98b954eedeac495271d0f";
describe("UniswapV3", function() {
const connectorName = "UniswapV3-v1";
let dsaWallet0;
let masterSigner;
let instaConnectorsV2;
let connector;
let nftManager;
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
);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: connectV2UniswapV3Artifacts,
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(!!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("Perfrom a swap", 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")
);
});
});
});

View File

@ -0,0 +1,128 @@
const { expect } = require("chai");
const hre = require("hardhat");
const { web3, deployments, waffle, ethers } = hre;
const { provider, deployContract } = waffle;
const USDC_ADDR = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8";
const WETH_ADDR = "0x82af49447d8a07e3bd95bd0d56f35241523fbab1";
describe("Uniswap-sell-beta", function() {
let UniswapSellBeta, uniswapSellBeta;
async function setBalance(address) {
await network.provider.send("hardhat_setBalance", [
address,
ethers.utils.parseEther("10.0").toHexString(),
]);
}
async function impersonate(owner, account, token0, decimals) {
const tokenArtifact = await artifacts.readArtifact(
"@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20"
);
setBalance(owner);
setBalance(account);
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [account],
});
const signer = await ethers.getSigner(account);
const token = new ethers.Contract(
token0,
tokenArtifact.abi,
ethers.provider
);
// console.log((await token.balanceOf(account)).toString());
await token
.connect(signer)
.transfer(owner, ethers.utils.parseUnits("10", decimals));
await hre.network.provider.request({
method: "hardhat_stopImpersonatingAccount",
params: [account],
});
}
beforeEach(async () => {
const account0 = "0x36cc7B13029B5DEe4034745FB4F24034f3F2ffc6";
const account1 = "0xce2cc46682e9c6d5f174af598fb4931a9c0be68e";
const [owner, add1, add2] = await ethers.getSigners();
await impersonate(owner.address, account1, USDC_ADDR, 6);
await impersonate(owner.address, account0, WETH_ADDR, 18);
UniswapSellBeta = await ethers.getContractFactory(
"ConnectV2UniswapSellBeta"
);
uniswapSellBeta = await UniswapSellBeta.deploy();
await uniswapSellBeta.deployed();
});
it("Should have contracts deployed.", async function() {
expect(uniswapSellBeta.address).to.exist;
});
it("Should swap WETH with USDC", async () => {
const [owner, add1, add2] = await ethers.getSigners();
const tokenArtifact = await artifacts.readArtifact(
"@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20"
);
const token = new ethers.Contract(
WETH_ADDR,
tokenArtifact.abi,
ethers.provider
);
const signer = await ethers.getSigner(owner.address);
await token
.connect(signer)
.transfer(uniswapSellBeta.address, ethers.utils.parseUnits("10.0", 18));
const tx = await uniswapSellBeta.sell(
WETH_ADDR,
USDC_ADDR,
3000,
ethers.utils.parseUnits("10.0", 18),
0
);
// console.log(tx);
});
it("Should swap USDC with WETH", async () => {
const [owner, add1, add2] = await ethers.getSigners();
const tokenArtifact = await artifacts.readArtifact(
"@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20"
);
const token = new ethers.Contract(
USDC_ADDR,
tokenArtifact.abi,
ethers.provider
);
const signer = await ethers.getSigner(owner.address);
await token
.connect(signer)
.transfer(uniswapSellBeta.address, ethers.utils.parseUnits("10.0", 6));
const tx = await uniswapSellBeta.sell(
USDC_ADDR,
WETH_ADDR,
3000,
ethers.utils.parseUnits("10.0", 6),
0
);
// console.log(tx);
});
});

View File

@ -1,335 +1,374 @@
const { expect } = require("chai");
const hre = require("hardhat");
const { web3, deployments, waffle, ethers } = hre;
const { provider, deployContract } = waffle
const { provider, deployContract } = waffle;
const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js")
const buildDSAv2 = require("../../scripts/buildDSAv2")
const encodeSpells = require("../../scripts/encodeSpells.js")
const encodeFlashcastData = require("../../scripts/encodeFlashcastData.js")
const getMasterSigner = require("../../scripts/getMasterSigner")
const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js");
const buildDSAv2 = require("../../scripts/buildDSAv2");
const encodeSpells = require("../../scripts/encodeSpells.js");
const encodeFlashcastData = require("../../scripts/encodeFlashcastData.js");
const getMasterSigner = require("../../scripts/getMasterSigner");
const addLiquidity = require("../../scripts/addLiquidity");
const addresses = require("../../scripts/constant/addresses");
const abis = require("../../scripts/constant/abis");
const constants = require("../../scripts/constant/constant");
const tokens = require("../../scripts/constant/tokens");
const { abi: nftManagerAbi } = require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json")
const {
abi: nftManagerAbi,
} = require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json");
const connectV2UniswapV3Artifacts = require("../../artifacts/contracts/mainnet/connectors/uniswap/v3/main.sol/ConnectV2UniswapV3.json");
const { eth } = require("../../scripts/constant/tokens");
const { BigNumber } = require("ethers");
const FeeAmount = {
LOW: 500,
MEDIUM: 3000,
HIGH: 10000,
}
LOW: 500,
MEDIUM: 3000,
HIGH: 10000,
};
const TICK_SPACINGS = {
500: 10,
3000: 60,
10000: 200
}
500: 10,
3000: 60,
10000: 200,
};
const USDT_ADDR = "0xdac17f958d2ee523a2206206994597c13d831ec7"
const DAI_ADDR = "0x6b175474e89094c44da98b954eedeac495271d0f"
const USDT_ADDR = "0xdac17f958d2ee523a2206206994597c13d831ec7";
const DAI_ADDR = "0x6b175474e89094c44da98b954eedeac495271d0f";
let tokenIds = []
let liquidities = []
const abiCoder = ethers.utils.defaultAbiCoder
let tokenIds = [];
let liquidities = [];
const abiCoder = ethers.utils.defaultAbiCoder;
describe("UniswapV3", function () {
const connectorName = "UniswapV3-v1"
describe("UniswapV3", function() {
const connectorName = "UniswapV3-v1";
let dsaWallet0
let masterSigner;
let instaConnectorsV2;
let connector;
let nftManager;
let dsaWallet0;
let masterSigner;
let instaConnectorsV2;
let connector;
let nftManager;
const wallets = provider.getWallets()
const [wallet0, wallet1, wallet2, wallet3] = wallets
before(async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 13005785,
},
},
],
});
masterSigner = await getMasterSigner(wallet3)
instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2);
nftManager = await ethers.getContractAt(nftManagerAbi, "0xC36442b4a4522E871399CD717aBDD847Ab11FE88");
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: connectV2UniswapV3Artifacts,
signer: masterSigner,
connectors: instaConnectorsV2
})
console.log("Connector address", connector.address)
})
const wallets = provider.getWallets();
const [wallet0, wallet1, wallet2, wallet3] = wallets;
before(async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 13005785,
},
},
],
});
masterSigner = await getMasterSigner(wallet3);
instaConnectorsV2 = await ethers.getContractAt(
abis.core.connectorsV2,
addresses.core.connectorsV2
);
nftManager = await ethers.getContractAt(
nftManagerAbi,
"0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: connectV2UniswapV3Artifacts,
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(!!masterSigner.address).to.be.true;
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;
});
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")
);
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"));
});
await addLiquidity(
"dai",
dsaWallet0.address,
ethers.utils.parseEther("100000")
);
});
describe("Main", function () {
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")
);
it("Should mint successfully", async function () {
const ethAmount = ethers.utils.parseEther("0.1") // 1 ETH
const daiAmount = ethers.utils.parseEther("400") // 1 ETH
const usdtAmount = ethers.utils.parseEther("400") / Math.pow(10, 12) // 1 ETH
const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
await addLiquidity(
"usdt",
dsaWallet0.address,
ethers.utils.parseEther("100000")
);
});
});
const getIds = ["0", "0"]
const setId = "0"
describe("Main", function() {
it("Should mint successfully", async function() {
const ethAmount = ethers.utils.parseEther("0.1"); // 1 ETH
const daiAmount = ethers.utils.parseEther("400"); // 1 ETH
const usdtAmount = ethers.utils.parseEther("400") / Math.pow(10, 12); // 1 ETH
const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const spells = [
{
connector: connectorName,
method: "mint",
args: [
ethAddress,
DAI_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
ethAmount,
daiAmount,
"500000000000000000",
getIds,
setId
],
},
{
connector: connectorName,
method: "mint",
args: [
DAI_ADDR,
USDT_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
daiAmount,
usdtAmount,
"300000000000000000",
getIds,
setId
],
},
{
connector: connectorName,
method: "mint",
args: [
ethAddress,
USDT_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
ethAmount,
usdtAmount,
"300000000000000000",
getIds,
setId
],
}
]
const getIds = ["0", "0"];
const setId = "0";
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[2]);
tokenIds.push(params[0]);
tokenIds.push(params1[0]);
liquidities.push(params[1]);
event.removeListener();
const spells = [
{
connector: connectorName,
method: "mint",
args: [
ethAddress,
DAI_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
ethAmount,
daiAmount,
"500000000000000000",
getIds,
setId,
],
},
{
connector: connectorName,
method: "mint",
args: [
DAI_ADDR,
USDT_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
daiAmount,
usdtAmount,
"300000000000000000",
getIds,
setId,
],
},
{
connector: connectorName,
method: "mint",
args: [
ethAddress,
USDT_ADDR,
FeeAmount.MEDIUM,
getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
ethAmount,
usdtAmount,
"300000000000000000",
getIds,
setId,
],
},
];
resolve({
eventNames,
});
});
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[2]
);
tokenIds.push(params[0]);
tokenIds.push(params1[0]);
liquidities.push(params[1]);
event.removeListener();
setTimeout(() => {
reject(new Error('timeout'));
}, 60000)
resolve({
eventNames,
});
}
);
let event = await castEvent
setTimeout(() => {
reject(new Error("timeout"));
}, 60000);
});
const data = await nftManager.positions(tokenIds[0])
let event = await castEvent;
expect(data.liquidity).to.be.equals(liquidities[0]);
}).timeout(10000000000);
const data = await nftManager.positions(tokenIds[0]);
it("Should deposit successfully", async function () {
const daiAmount = ethers.utils.parseEther("400") // 1 ETH
const ethAmount = ethers.utils.parseEther("0.1") // 1 ETH
const usdtAmount = ethers.utils.parseEther("400") / Math.pow(10, 12) // 1 ETH
const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
expect(data.liquidity).to.be.equals(liquidities[0]);
}).timeout(10000000000);
const getIds = ["0", "0"]
const setId = "0"
it("Should deposit successfully", async function() {
const daiAmount = ethers.utils.parseEther("400"); // 1 ETH
const ethAmount = ethers.utils.parseEther("0.1"); // 1 ETH
const usdtAmount = ethers.utils.parseEther("400") / Math.pow(10, 12); // 1 ETH
const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const spells = [
{
connector: connectorName,
method: "deposit",
args: [
tokenIds[0],
daiAmount,
ethAmount,
"500000000000000000",
getIds,
setId
],
}
]
const getIds = ["0", "0"];
const setId = "0";
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
const receipt = await tx.wait()
const spells = [
{
connector: connectorName,
method: "deposit",
args: [
tokenIds[0],
daiAmount,
ethAmount,
"500000000000000000",
getIds,
setId,
],
},
];
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]);
liquidities[0] = liquidities[0].add(params[1]);
event.removeListener();
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.address);
const receipt = await tx.wait();
resolve({
eventNames,
});
});
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]
);
liquidities[0] = liquidities[0].add(params[1]);
event.removeListener();
setTimeout(() => {
reject(new Error('timeout'));
}, 60000)
resolve({
eventNames,
});
}
);
let event = await castEvent
setTimeout(() => {
reject(new Error("timeout"));
}, 60000);
});
const data = await nftManager.positions(tokenIds[0])
expect(data.liquidity).to.be.equals(liquidities[0]);
})
let event = await castEvent;
it("Should withdraw successfully", async function () {
const data = await nftManager.positions(tokenIds[0]);
expect(data.liquidity).to.be.equals(liquidities[0]);
});
const getId = "0"
const setIds = ["0", "0"]
it("Should withdraw successfully", async function() {
const getId = "0";
const setIds = ["0", "0"];
const data = await nftManager.positions(tokenIds[0])
let data1 = await nftManager.positions(tokenIds[1])
const data = await nftManager.positions(tokenIds[0]);
let data1 = await nftManager.positions(tokenIds[1]);
const spells = [
{
connector: connectorName,
method: "withdraw",
args: [
tokenIds[0],
data.liquidity,
0,
0,
getId,
setIds
],
},
{
connector: connectorName,
method: "withdraw",
args: [
0,
data1.liquidity,
0,
0,
getId,
setIds
],
},
]
const spells = [
{
connector: connectorName,
method: "withdraw",
args: [tokenIds[0], data.liquidity, 0, 0, getId, setIds],
},
{
connector: connectorName,
method: "withdraw",
args: [0, data1.liquidity, 0, 0, getId, setIds],
},
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
const receipt = await tx.wait()
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.address);
const receipt = await tx.wait();
data1 = await nftManager.positions(tokenIds[1])
expect(data1.liquidity.toNumber()).to.be.equals(0);
})
data1 = await nftManager.positions(tokenIds[1]);
expect(data1.liquidity.toNumber()).to.be.equals(0);
});
it("Should collect successfully", async function () {
it("Should collect successfully", async function() {
const ethAmount = ethers.utils.parseEther("0.2"); // 1 ETH
const daiAmount = ethers.utils.parseEther("800"); // 1 ETH
const getIds = ["0", "0"];
const setIds = ["0", "0"];
const ethAmount = ethers.utils.parseEther("0.2") // 1 ETH
const daiAmount = ethers.utils.parseEther("800") // 1 ETH
const getIds = ["0", "0"]
const setIds = ["0", "0"]
const spells = [
{
connector: connectorName,
method: "collect",
args: [tokenIds[0], daiAmount, ethAmount, getIds, setIds],
},
];
const spells = [
{
connector: connectorName,
method: "collect",
args: [
tokenIds[0],
daiAmount,
ethAmount,
getIds,
setIds
],
}
]
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.address);
const receipt = await tx.wait();
});
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
const receipt = await tx.wait()
})
it("Should burn successfully", async function() {
const spells = [
{
connector: connectorName,
method: "burn",
args: [tokenIds[0]],
},
];
it("Should burn successfully", async function () {
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.address);
const receipt = await tx.wait();
});
});
});
const spells = [
{
connector: connectorName,
method: "burn",
args: [
tokenIds[0]
],
}
]
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)
const receipt = await tx.wait()
})
})
})
const getMinTick = (tickSpacing) => Math.ceil(-887272 / tickSpacing) * tickSpacing
const getMaxTick = (tickSpacing) => Math.floor(887272 / tickSpacing) * tickSpacing
const getMinTick = (tickSpacing) =>
Math.ceil(-887272 / tickSpacing) * tickSpacing;
const getMaxTick = (tickSpacing) =>
Math.floor(887272 / tickSpacing) * tickSpacing;