[api] chore: refactor MultiQuoters to use abstract contract [LIT-910] (#167)
This commit is contained in:
committed by
GitHub
parent
09c8a22a62
commit
3cb7ea9980
@@ -21,6 +21,7 @@ pragma solidity >=0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IAlgebra.sol";
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
|
||||
contract AlgebraCommon {
|
||||
function toAlgebraPath(address[] memory tokenPath) internal pure returns (bytes memory algebraPath) {
|
||||
@@ -42,10 +43,10 @@ contract AlgebraCommon {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidTokenPath(IAlgebraFactory factory, address[] memory tokenPath) internal view returns (bool) {
|
||||
function isValidTokenPath(address factory, address[] memory tokenPath) internal view returns (bool) {
|
||||
for (uint256 i = 0; i < tokenPath.length - 1; ++i) {
|
||||
IAlgebraPool pool = factory.poolByPair(tokenPath[i], tokenPath[i + 1]);
|
||||
if (address(pool) == address(0)) {
|
||||
address pool = IAlgebraFactory(factory).poolByPair(tokenPath[i], tokenPath[i + 1]);
|
||||
if (pool == address(0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -23,13 +23,9 @@ import "@cryptoalgebra/core/contracts/libraries/LiquidityMath.sol";
|
||||
import "@cryptoalgebra/periphery/contracts/libraries/Path.sol";
|
||||
|
||||
import "./interfaces/IAlgebra.sol";
|
||||
import "./MultiQuoter.sol";
|
||||
|
||||
contract AlgebraMultiQuoter is IAlgebraMultiQuoter {
|
||||
// TODO: both quoteExactMultiInput and quoteExactMultiOutput revert at the end of the quoting logic
|
||||
// and return results encodied into a revert reason. The revert should be removed and replaced with
|
||||
// a normal return statement whenever UniswapV3Sampler and KyberElasticSampler stop having the
|
||||
// two pool filtering logic. AlgebraMultiQuoter has the same revert logic in order to have a common
|
||||
// interface across tick based AMM MultiQuoters.
|
||||
contract AlgebraMultiQuoter is MultiQuoter {
|
||||
using SafeCast for uint256;
|
||||
using LowGasSafeMath for int256;
|
||||
using Path for bytes;
|
||||
@@ -72,173 +68,37 @@ contract AlgebraMultiQuoter is IAlgebraMultiQuoter {
|
||||
uint256 gasBefore;
|
||||
}
|
||||
|
||||
// the result of multiswap
|
||||
struct MultiSwapResult {
|
||||
// the gas estimate for each of swap amounts
|
||||
uint256[] gasEstimates;
|
||||
// the token0 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts0;
|
||||
// the token1 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts1;
|
||||
}
|
||||
|
||||
/// @notice Quotes amounts out for a set of exact input swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing Algebra pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountsIn The amounts in of the first token to swap
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsOut, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiInput(
|
||||
IAlgebraFactory factory,
|
||||
/// @inheritdoc MultiQuoter
|
||||
function getFirstSwapDetails(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) public view override {
|
||||
for (uint256 i = 0; i < amountsIn.length - 1; ++i) {
|
||||
require(amountsIn[i] <= amountsIn[i + 1], "AlgebraMultiQuoter/amountsIn must be monotonically increasing");
|
||||
bool isExactInput
|
||||
) internal view override returns (address pool, bool zeroForOne) {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
if (isExactInput) {
|
||||
(tokenIn, tokenOut) = path.decodeFirstPool();
|
||||
} else {
|
||||
(tokenOut, tokenIn) = path.decodeFirstPool();
|
||||
}
|
||||
|
||||
uint256[] memory gasEstimates = new uint256[](amountsIn.length);
|
||||
|
||||
while (true) {
|
||||
(address tokenIn, address tokenOut) = path.decodeFirstPool();
|
||||
bool zeroForOne = tokenIn < tokenOut;
|
||||
IAlgebraPool pool = factory.poolByPair(tokenIn, tokenOut);
|
||||
|
||||
// multiswap only accepts int256[] for input amounts
|
||||
int256[] memory amounts = new int256[](amountsIn.length);
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amounts[i] = int256(amountsIn[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(
|
||||
pool,
|
||||
zeroForOne,
|
||||
amounts,
|
||||
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amountsIn[i] = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
|
||||
//decide whether to continue or terminate
|
||||
if (path.hasMultiplePools()) {
|
||||
path = path.skipToken();
|
||||
} else {
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to AlgebraMultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
bytes memory revertResult = abi.encodeWithSignature(
|
||||
"result(uint256[],uint256[])",
|
||||
amountsIn,
|
||||
gasEstimates
|
||||
);
|
||||
assembly {
|
||||
revert(add(revertResult, 0x20), mload(revertResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
zeroForOne = tokenIn < tokenOut;
|
||||
pool = IAlgebraFactory(factory).poolByPair(tokenIn, tokenOut);
|
||||
}
|
||||
|
||||
/// @notice Quotes amounts in a set of exact output swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing Algebra pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountsOut The amounts out of the last token to receive
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsIn, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiOutput(
|
||||
IAlgebraFactory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) public view override {
|
||||
uint256[] memory amountsIn = new uint256[](amountsOut.length);
|
||||
uint256[] memory gasEstimates = new uint256[](amountsOut.length);
|
||||
|
||||
for (uint256 i = 0; i < amountsOut.length - 1; ++i) {
|
||||
require(
|
||||
amountsOut[i] <= amountsOut[i + 1],
|
||||
"AlgebraMultiQuoter/amountsOut must be monotonically increasing"
|
||||
);
|
||||
}
|
||||
|
||||
uint256 nextAmountsLength = amountsOut.length;
|
||||
while (true) {
|
||||
(address tokenOut, address tokenIn) = path.decodeFirstPool();
|
||||
bool zeroForOne = tokenIn < tokenOut;
|
||||
IAlgebraPool pool = factory.poolByPair(tokenIn, tokenOut);
|
||||
|
||||
// multiswap only accepts int256[] for output amounts
|
||||
int256[] memory amounts = new int256[](nextAmountsLength);
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amounts[i] = -int256(amountsOut[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(
|
||||
pool,
|
||||
zeroForOne,
|
||||
amounts,
|
||||
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
uint256 amountReceived = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
if (amountReceived != amountsOut[i]) {
|
||||
// for exact output swaps we need to check whether we would receive the full amount due to
|
||||
// multiswap behavior when hitting the limit
|
||||
nextAmountsLength = i;
|
||||
break;
|
||||
} else {
|
||||
// populate amountsOut for the next pool
|
||||
amountsOut[i] = zeroForOne ? uint256(result.amounts0[i]) : uint256(result.amounts1[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (nextAmountsLength == 0 || !path.hasMultiplePools()) {
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amountsIn[i] = amountsOut[i];
|
||||
}
|
||||
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to UniswapV3MultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
bytes memory revertResult = abi.encodeWithSignature(
|
||||
"result(uint256[],uint256[])",
|
||||
amountsIn,
|
||||
gasEstimates
|
||||
);
|
||||
assembly {
|
||||
revert(add(revertResult, 0x20), mload(revertResult))
|
||||
}
|
||||
}
|
||||
|
||||
path = path.skipToken();
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice swap multiple amounts of token0 for token1 or token1 for token1
|
||||
/// @dev The results of multiswap includes slight rounding issues resulting from rounding up/rounding down in SqrtPriceMath library. Additionally,
|
||||
/// it should be noted that multiswap requires a monotonically increasing list of amounts for exact inputs and monotonically decreasing list of
|
||||
/// amounts for exact outputs.
|
||||
/// @param pool The Algebra pool to simulate each of the swap amounts for
|
||||
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
|
||||
/// @param amounts The amounts of the swaps, positive values indicate exactInput and negative values indicate exact output
|
||||
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
|
||||
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
|
||||
/// @return result The results of the swap as a MultiSwapResult struct with gas used, token0 and token1 deltas
|
||||
/// @inheritdoc MultiQuoter
|
||||
function multiswap(
|
||||
IAlgebraPool pool,
|
||||
address p,
|
||||
bool zeroForOne,
|
||||
int256[] memory amounts,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) private view returns (MultiSwapResult memory result) {
|
||||
int256[] memory amounts
|
||||
) internal view override returns (MultiSwapResult memory result) {
|
||||
result.gasEstimates = new uint256[](amounts.length);
|
||||
result.amounts0 = new int256[](amounts.length);
|
||||
result.amounts1 = new int256[](amounts.length);
|
||||
|
||||
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
||||
IAlgebraPool pool = IAlgebraPool(p);
|
||||
|
||||
(uint160 sqrtPriceX96Start, int24 tickStart, uint16 fee, , , , ) = pool.globalState();
|
||||
int24 tickSpacing = pool.tickSpacing();
|
||||
|
||||
@@ -463,4 +323,14 @@ contract AlgebraMultiQuoter is IAlgebraMultiQuoter {
|
||||
}
|
||||
return (getSingleSignificantBit(word));
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
|
||||
return path.hasMultiplePools();
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
|
||||
return path.skipToken();
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./AlgebraCommon.sol";
|
||||
import "./interfaces/IAlgebra.sol";
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
|
||||
contract AlgebraSampler is AlgebraCommon {
|
||||
/// @dev Sample sell quotes from Algebra.
|
||||
@@ -33,8 +33,8 @@ contract AlgebraSampler is AlgebraCommon {
|
||||
/// @return gasEstimates Estimated amount of gas used
|
||||
/// @return outputAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromAlgebra(
|
||||
IAlgebraMultiQuoter quoter,
|
||||
IAlgebraFactory factory,
|
||||
IMultiQuoter quoter,
|
||||
address factory,
|
||||
address[] memory tokenPath,
|
||||
uint256[] memory inputAmounts
|
||||
) public view returns (bytes memory path, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
|
||||
@@ -60,8 +60,8 @@ contract AlgebraSampler is AlgebraCommon {
|
||||
/// @return gasEstimates Estimated amount of gas used
|
||||
/// @return inputAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromAlgebra(
|
||||
IAlgebraMultiQuoter quoter,
|
||||
IAlgebraFactory factory,
|
||||
IMultiQuoter quoter,
|
||||
address factory,
|
||||
address[] memory tokenPath,
|
||||
uint256[] memory outputAmounts
|
||||
) public view returns (bytes memory path, uint256[] memory gasEstimates, uint256[] memory inputAmounts) {
|
||||
|
@@ -10,10 +10,10 @@ contract KyberElasticCommon {
|
||||
/// @dev Returns `poolPaths` to sample against. The caller is responsible for not using path involving zero address(es).
|
||||
function _getPoolPaths(
|
||||
IMultiQuoter quoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address[] memory path,
|
||||
uint256 inputAmount
|
||||
) internal view returns (IPool[][] memory poolPaths) {
|
||||
) internal view returns (IKyberElasticPool[][] memory poolPaths) {
|
||||
if (path.length == 2) {
|
||||
return _getPoolPathSingleHop(quoter, factory, path, inputAmount);
|
||||
}
|
||||
@@ -25,17 +25,17 @@ contract KyberElasticCommon {
|
||||
|
||||
function _getPoolPathSingleHop(
|
||||
IMultiQuoter quoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address[] memory path,
|
||||
uint256 inputAmount
|
||||
) internal view returns (IPool[][] memory poolPaths) {
|
||||
poolPaths = new IPool[][](2);
|
||||
(IPool[2] memory topPools, ) = _getTopTwoPools(quoter, factory, path[0], path[1], inputAmount);
|
||||
) internal view returns (IKyberElasticPool[][] memory poolPaths) {
|
||||
poolPaths = new IKyberElasticPool[][](2);
|
||||
(IKyberElasticPool[2] memory topPools, ) = _getTopTwoPools(quoter, factory, path[0], path[1], inputAmount);
|
||||
|
||||
uint256 pathCount = 0;
|
||||
for (uint256 i = 0; i < 2; i++) {
|
||||
IPool topPool = topPools[i];
|
||||
poolPaths[pathCount] = new IPool[](1);
|
||||
IKyberElasticPool topPool = topPools[i];
|
||||
poolPaths[pathCount] = new IKyberElasticPool[](1);
|
||||
poolPaths[pathCount][0] = topPool;
|
||||
pathCount++;
|
||||
}
|
||||
@@ -43,25 +43,31 @@ contract KyberElasticCommon {
|
||||
|
||||
function _getPoolPathTwoHop(
|
||||
IMultiQuoter quoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address[] memory path,
|
||||
uint256 inputAmount
|
||||
) internal view returns (IPool[][] memory poolPaths) {
|
||||
poolPaths = new IPool[][](4);
|
||||
(IPool[2] memory firstHopTopPools, uint256[2] memory firstHopAmounts) = _getTopTwoPools(
|
||||
) internal view returns (IKyberElasticPool[][] memory poolPaths) {
|
||||
poolPaths = new IKyberElasticPool[][](4);
|
||||
(IKyberElasticPool[2] memory firstHopTopPools, uint256[2] memory firstHopAmounts) = _getTopTwoPools(
|
||||
quoter,
|
||||
factory,
|
||||
path[0],
|
||||
path[1],
|
||||
inputAmount
|
||||
);
|
||||
(IPool[2] memory secondHopTopPools, ) = _getTopTwoPools(quoter, factory, path[1], path[2], firstHopAmounts[0]);
|
||||
(IKyberElasticPool[2] memory secondHopTopPools, ) = _getTopTwoPools(
|
||||
quoter,
|
||||
factory,
|
||||
path[1],
|
||||
path[2],
|
||||
firstHopAmounts[0]
|
||||
);
|
||||
|
||||
uint256 pathCount = 0;
|
||||
for (uint256 i = 0; i < 2; i++) {
|
||||
for (uint256 j = 0; j < 2; j++) {
|
||||
poolPaths[pathCount] = new IPool[](2);
|
||||
IPool[] memory currentPath = poolPaths[pathCount];
|
||||
poolPaths[pathCount] = new IKyberElasticPool[](2);
|
||||
IKyberElasticPool[] memory currentPath = poolPaths[pathCount];
|
||||
currentPath[0] = firstHopTopPools[i];
|
||||
currentPath[1] = secondHopTopPools[j];
|
||||
pathCount++;
|
||||
@@ -73,11 +79,11 @@ contract KyberElasticCommon {
|
||||
/// Addresses in `topPools` can be zero addresses when there are pool isn't available.
|
||||
function _getTopTwoPools(
|
||||
IMultiQuoter multiQuoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
uint256 inputAmount
|
||||
) internal view returns (IPool[2] memory topPools, uint256[2] memory outputAmounts) {
|
||||
) internal view returns (IKyberElasticPool[2] memory topPools, uint256[2] memory topOutputAmounts) {
|
||||
address[] memory tokenPath = new address[](2);
|
||||
tokenPath[0] = inputToken;
|
||||
tokenPath[1] = outputToken;
|
||||
@@ -87,33 +93,38 @@ contract KyberElasticCommon {
|
||||
|
||||
uint16[5] memory validPoolFees = [uint16(8), uint16(10), uint16(40), uint16(300), uint16(1000)]; // in bps
|
||||
for (uint256 i = 0; i < validPoolFees.length; ++i) {
|
||||
IPool pool = IPool(factory.getPool(inputToken, outputToken, validPoolFees[i]));
|
||||
IKyberElasticPool pool = IKyberElasticPool(
|
||||
IKyberElasticFactory(factory).getPool(inputToken, outputToken, validPoolFees[i])
|
||||
);
|
||||
if (!_isValidPool(pool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IPool[] memory poolPath = new IPool[](1);
|
||||
IKyberElasticPool[] memory poolPath = new IKyberElasticPool[](1);
|
||||
poolPath[0] = pool;
|
||||
bytes memory dexPath = _toPath(tokenPath, poolPath);
|
||||
|
||||
try
|
||||
multiQuoter.quoteExactMultiInput{gas: POOL_FILTERING_QUOTE_GAS}(factory, dexPath, inputAmounts)
|
||||
returns (uint256[] memory amountsOut, uint256[] memory /* gasEstimate */) {
|
||||
// Keeping track of the top 2 pools.
|
||||
if (amountsOut[0] > outputAmounts[0]) {
|
||||
outputAmounts[1] = outputAmounts[0];
|
||||
topPools[1] = topPools[0];
|
||||
outputAmounts[0] = amountsOut[0];
|
||||
topPools[0] = pool;
|
||||
} else if (amountsOut[0] > outputAmounts[1]) {
|
||||
outputAmounts[1] = amountsOut[0];
|
||||
topPools[1] = pool;
|
||||
{} catch (bytes memory reason) {
|
||||
(bool success, uint256[] memory outputAmounts, ) = catchKyberElasticMultiSwapResult(reason);
|
||||
if (success) {
|
||||
// Keeping track of the top 2 pools.
|
||||
if (outputAmounts[0] > topOutputAmounts[0]) {
|
||||
topOutputAmounts[1] = topOutputAmounts[0];
|
||||
topPools[1] = topPools[0];
|
||||
topOutputAmounts[0] = outputAmounts[0];
|
||||
topPools[0] = pool;
|
||||
} else if (outputAmounts[0] > topOutputAmounts[1]) {
|
||||
topOutputAmounts[1] = outputAmounts[0];
|
||||
topPools[1] = pool;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _isValidPool(IPool pool) internal view returns (bool isValid) {
|
||||
function _isValidPool(IKyberElasticPool pool) internal view returns (bool isValid) {
|
||||
// Check if it has been deployed.
|
||||
uint256 codeSize;
|
||||
assembly {
|
||||
@@ -122,7 +133,7 @@ contract KyberElasticCommon {
|
||||
return codeSize != 0;
|
||||
}
|
||||
|
||||
function _isValidPoolPath(IPool[] memory poolPaths) internal pure returns (bool) {
|
||||
function _isValidPoolPath(IKyberElasticPool[] memory poolPaths) internal pure returns (bool) {
|
||||
for (uint256 i = 0; i < poolPaths.length; i++) {
|
||||
if (address(poolPaths[i]) == address(0)) {
|
||||
return false;
|
||||
@@ -131,7 +142,10 @@ contract KyberElasticCommon {
|
||||
return true;
|
||||
}
|
||||
|
||||
function _toPath(address[] memory tokenPath, IPool[] memory poolPath) internal view returns (bytes memory path) {
|
||||
function _toPath(
|
||||
address[] memory tokenPath,
|
||||
IKyberElasticPool[] memory poolPath
|
||||
) internal view returns (bytes memory path) {
|
||||
require(tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1, "invalid path lengths");
|
||||
// paths are tightly packed as:
|
||||
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
|
||||
@@ -163,10 +177,33 @@ contract KyberElasticCommon {
|
||||
}
|
||||
}
|
||||
|
||||
function _reversePoolPath(IPool[] memory poolPath) internal pure returns (IPool[] memory reversed) {
|
||||
reversed = new IPool[](poolPath.length);
|
||||
function _reversePoolPath(
|
||||
IKyberElasticPool[] memory poolPath
|
||||
) internal pure returns (IKyberElasticPool[] memory reversed) {
|
||||
reversed = new IKyberElasticPool[](poolPath.length);
|
||||
for (uint256 i = 0; i < poolPath.length; ++i) {
|
||||
reversed[i] = poolPath[poolPath.length - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
function catchKyberElasticMultiSwapResult(
|
||||
bytes memory revertReason
|
||||
) internal pure returns (bool success, uint256[] memory amounts, uint256[] memory gasEstimates) {
|
||||
bytes4 selector;
|
||||
assembly {
|
||||
selector := mload(add(revertReason, 32))
|
||||
}
|
||||
|
||||
if (selector != bytes4(keccak256("result(uint256[],uint256[])"))) {
|
||||
return (false, amounts, gasEstimates);
|
||||
}
|
||||
|
||||
assembly {
|
||||
let length := sub(mload(revertReason), 4)
|
||||
revertReason := add(revertReason, 4)
|
||||
mstore(revertReason, length)
|
||||
}
|
||||
(amounts, gasEstimates) = abi.decode(revertReason, (uint256[], uint256[]));
|
||||
return (true, amounts, gasEstimates);
|
||||
}
|
||||
}
|
||||
|
@@ -20,8 +20,6 @@
|
||||
// transitive dependencies PathHelper.sol and BytesLib.sol require 0.8.9
|
||||
pragma solidity 0.8.9;
|
||||
|
||||
import "./libraries/Multiswap.sol";
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
|
||||
import "@kybernetwork/ks-elastic-sc/contracts/libraries/TickMath.sol";
|
||||
import "@kybernetwork/ks-elastic-sc/contracts/libraries/SwapMath.sol";
|
||||
@@ -29,12 +27,14 @@ import "@kybernetwork/ks-elastic-sc/contracts/libraries/Linkedlist.sol";
|
||||
import "@kybernetwork/ks-elastic-sc/contracts/libraries/LiqDeltaMath.sol";
|
||||
import "@kybernetwork/ks-elastic-sc/contracts/periphery/libraries/PathHelper.sol";
|
||||
|
||||
/// @title Provides quotes for multiple swap amounts
|
||||
/// @notice Allows getting the expected amount out or amount in for multiple given swap amounts without executing the swap
|
||||
contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
import "./interfaces/IKyberElastic.sol";
|
||||
import "./MultiQuoter.sol";
|
||||
|
||||
contract KyberElasticMultiQuoter is MultiQuoter {
|
||||
using SafeCast for uint256;
|
||||
using LowGasSafeMath for int256;
|
||||
using SafeCast for int128;
|
||||
using PathHelper for bytes;
|
||||
|
||||
// temporary swap variables, some of which will be used to update the pool state
|
||||
struct SwapData {
|
||||
@@ -44,7 +44,7 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
int24 currentTick; // the tick associated with the current price
|
||||
int24 nextTick; // the next initialized tick
|
||||
uint160 nextSqrtP; // the price of nextTick
|
||||
bool isToken0; // true if specifiedAmount is in token0, false if in token1
|
||||
bool zeroForOne; // true if swap is token0 for token 1
|
||||
bool isExactInput; // true = input qty, false = output qty
|
||||
uint128 baseL; // the cached base pool liquidity without reinvestment liquidity
|
||||
uint128 reinvestL; // the cached reinvestment liquidity
|
||||
@@ -53,94 +53,15 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
uint256 gasResidual; // amount of gas used between current tick and current price
|
||||
}
|
||||
|
||||
// TODO: same as uniswapv3 multiquoter logic. see if we can make generic.
|
||||
function quoteExactMultiInput(
|
||||
IFactory factory,
|
||||
/// @inheritdoc MultiQuoter
|
||||
function getFirstSwapDetails(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) public view returns (uint256[] memory amountsOut, uint256[] memory gasEstimate) {
|
||||
for (uint256 i = 1; i < amountsIn.length; ++i) {
|
||||
require(amountsIn[i] >= amountsIn[i - 1], "multiswap amounts must be monotonically increasing");
|
||||
}
|
||||
gasEstimate = new uint256[](amountsIn.length);
|
||||
while (true) {
|
||||
(address tokenIn, address tokenOut, uint24 fee) = PathHelper.decodeFirstPool(path);
|
||||
|
||||
// NOTE: this is equivalent to UniswapV3's zeroForOne.
|
||||
// if tokenIn < tokenOut, token input and specified token is token0, swap from 0 to 1
|
||||
bool isToken0 = tokenIn < tokenOut;
|
||||
IPool pool = IPool(factory.getPool(tokenIn, tokenOut, fee));
|
||||
|
||||
// multiswap only accepts int256[] for input amounts
|
||||
int256[] memory amounts = new int256[](amountsIn.length);
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amounts[i] = int256(amountsIn[i]);
|
||||
}
|
||||
|
||||
Multiswap.Result memory result = multiswap(
|
||||
pool,
|
||||
isToken0,
|
||||
amounts,
|
||||
isToken0 ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amountsIn[i] = isToken0 ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
gasEstimate[i] += result.gasEstimates[i];
|
||||
}
|
||||
|
||||
// decide whether to continue or terminate
|
||||
if (PathHelper.hasMultiplePools(path)) {
|
||||
path = PathHelper.skipToken(path);
|
||||
} else {
|
||||
return (amountsIn, gasEstimate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: same as uniswapv3 multiquoter logic. see if we can make generic.
|
||||
function quoteExactMultiOutput(
|
||||
IFactory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) public view returns (uint256[] memory amountsIn, uint256[] memory gasEstimate) {
|
||||
for (uint256 i = 1; i < amountsOut.length; ++i) {
|
||||
require(amountsOut[i] >= amountsOut[i - 1], "multiswap amounts must be monotonically inreasing");
|
||||
}
|
||||
gasEstimate = new uint256[](amountsOut.length);
|
||||
while (true) {
|
||||
(address tokenOut, address tokenIn, uint24 fee) = PathHelper.decodeFirstPool(path);
|
||||
|
||||
// NOTE: this is equivalent to UniswapV3's zeroForOne.
|
||||
// if tokenIn > tokenOut, output token and specified token is token0, swap from token1 to token0
|
||||
bool isToken0 = tokenIn > tokenOut;
|
||||
IPool pool = IPool(factory.getPool(tokenIn, tokenOut, fee));
|
||||
|
||||
// multiswap only accepts int256[] for input amounts
|
||||
int256[] memory amounts = new int256[](amountsOut.length);
|
||||
for (uint256 i = 0; i < amountsOut.length; ++i) {
|
||||
amounts[i] = -int256(amountsOut[i]);
|
||||
}
|
||||
|
||||
Multiswap.Result memory result = multiswap(
|
||||
pool,
|
||||
isToken0,
|
||||
amounts,
|
||||
isToken0 ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < amountsOut.length; ++i) {
|
||||
amountsOut[i] = isToken0 ? uint256(result.amounts0[i]) : uint256(result.amounts1[i]);
|
||||
gasEstimate[i] += result.gasEstimates[i];
|
||||
}
|
||||
|
||||
// decide whether to continue or terminate
|
||||
if (PathHelper.hasMultiplePools(path)) {
|
||||
path = PathHelper.skipToken(path);
|
||||
} else {
|
||||
return (amountsOut, gasEstimate);
|
||||
}
|
||||
}
|
||||
bool isExactInput
|
||||
) internal view override returns (address pool, bool zeroForOne) {
|
||||
(address tokenA, address tokenB, uint24 fee) = path.decodeFirstPool();
|
||||
zeroForOne = tokenA < tokenB;
|
||||
pool = IKyberElasticFactory(factory).getPool(tokenA, tokenB, fee);
|
||||
}
|
||||
|
||||
/// @dev Return initial data before swapping
|
||||
@@ -151,7 +72,7 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
/// @return currentTick current pool tick
|
||||
/// @return nextTick next tick to calculate data
|
||||
function _getInitialSwapData(
|
||||
IPool pool,
|
||||
IKyberElasticPool pool,
|
||||
bool willUpTick
|
||||
) internal view returns (uint128 baseL, uint128 reinvestL, uint160 sqrtP, int24 currentTick, int24 nextTick) {
|
||||
(sqrtP, currentTick, nextTick, ) = pool.getPoolState();
|
||||
@@ -163,25 +84,27 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice swap multiple amounts of token0 for token1 or token1 for token0
|
||||
/// @inheritdoc MultiQuoter
|
||||
function multiswap(
|
||||
IPool pool,
|
||||
bool isToken0,
|
||||
int256[] memory amounts,
|
||||
uint160 limitSqrtP
|
||||
) private view returns (Multiswap.Result memory result) {
|
||||
address p,
|
||||
bool zeroForOne,
|
||||
int256[] memory amounts
|
||||
) internal view override returns (MultiSwapResult memory result) {
|
||||
result.gasEstimates = new uint256[](amounts.length);
|
||||
result.amounts0 = new int256[](amounts.length);
|
||||
result.amounts1 = new int256[](amounts.length);
|
||||
uint256 gasBefore = gasleft();
|
||||
|
||||
IKyberElasticPool pool = IKyberElasticPool(p);
|
||||
|
||||
SwapData memory swapData;
|
||||
swapData.specifiedAmount = amounts[0];
|
||||
swapData.isToken0 = isToken0;
|
||||
swapData.zeroForOne = zeroForOne;
|
||||
swapData.isExactInput = swapData.specifiedAmount > 0;
|
||||
|
||||
// tick (token1Qty/token0Qty) will increase for swapping from token1 to token0
|
||||
bool willUpTick = (swapData.isExactInput != isToken0);
|
||||
bool willUpTick = (swapData.isExactInput != zeroForOne);
|
||||
uint160 limitSqrtP = willUpTick ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1;
|
||||
(
|
||||
swapData.baseL,
|
||||
swapData.reinvestL,
|
||||
@@ -235,7 +158,7 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
swapFeeUnits,
|
||||
swapData.specifiedAmount,
|
||||
swapData.isExactInput,
|
||||
swapData.isToken0
|
||||
swapData.zeroForOne
|
||||
);
|
||||
|
||||
swapData.specifiedAmount -= usedAmount;
|
||||
@@ -246,10 +169,6 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
// if price has not reached the next sqrt price (still liquidity in current tick)
|
||||
if (swapData.sqrtP != swapData.nextSqrtP) {
|
||||
swapData.currentTick = TickMath.getTickAtSqrtRatio(swapData.sqrtP);
|
||||
if (swapData.amountsIndex == amounts.length - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// use max since we want to over estimate
|
||||
swapData.gasResidual = max(swapData.gasResidual, gasBefore - gasleft());
|
||||
gasBefore = gasleft();
|
||||
@@ -270,7 +189,7 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
gasBefore = gasleft();
|
||||
}
|
||||
if (swapData.specifiedAmount == 0) {
|
||||
(result.amounts0[swapData.amountsIndex], result.amounts1[swapData.amountsIndex]) = isToken0 ==
|
||||
(result.amounts0[swapData.amountsIndex], result.amounts1[swapData.amountsIndex]) = zeroForOne ==
|
||||
swapData.isExactInput
|
||||
? (amounts[swapData.amountsIndex], swapData.returnedAmount)
|
||||
: (swapData.returnedAmount, amounts[swapData.amountsIndex]);
|
||||
@@ -287,12 +206,11 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
}
|
||||
|
||||
for (uint256 i = swapData.amountsIndex; i < amounts.length; ++i) {
|
||||
(result.amounts0[i], result.amounts1[i]) = isToken0 == swapData.isExactInput
|
||||
(result.amounts0[i], result.amounts1[i]) = zeroForOne == swapData.isExactInput
|
||||
? (amounts[i], swapData.returnedAmount)
|
||||
: (swapData.returnedAmount, amounts[i]);
|
||||
result.gasEstimates[i] = swapData.gasAggregate + swapData.gasResidual;
|
||||
}
|
||||
|
||||
result.gasEstimates[swapData.amountsIndex] = swapData.gasAggregate + swapData.gasResidual;
|
||||
}
|
||||
|
||||
function max(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
@@ -301,7 +219,7 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
|
||||
/// @dev Update liquidity net data and do cross tick
|
||||
function _updateLiquidityAndCrossTick(
|
||||
IPool pool,
|
||||
IKyberElasticPool pool,
|
||||
int24 nextTick,
|
||||
uint128 currentLiquidity,
|
||||
bool willUpTick
|
||||
@@ -319,4 +237,14 @@ contract KyberElasticMultiQuoter is IMultiQuoter {
|
||||
liquidityNet >= 0
|
||||
);
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
|
||||
return path.hasMultiplePools();
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
|
||||
return path.skipToken();
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
/// @return outputAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromKyberElastic(
|
||||
IMultiQuoter quoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address[] memory path,
|
||||
uint256[] memory inputAmounts
|
||||
) public view returns (bytes[] memory paths, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
|
||||
@@ -42,7 +42,12 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
paths = new bytes[](inputAmounts.length);
|
||||
gasEstimates = new uint256[](inputAmounts.length);
|
||||
|
||||
IPool[][] memory poolPaths = _getPoolPaths(quoter, factory, path, inputAmounts[inputAmounts.length - 1]);
|
||||
IKyberElasticPool[][] memory poolPaths = _getPoolPaths(
|
||||
quoter,
|
||||
factory,
|
||||
path,
|
||||
inputAmounts[inputAmounts.length - 1]
|
||||
);
|
||||
for (uint256 i = 0; i < poolPaths.length; ++i) {
|
||||
if (!_isValidPoolPath(poolPaths[i])) {
|
||||
continue;
|
||||
@@ -50,17 +55,30 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
|
||||
bytes memory dexPath = _toPath(path, poolPaths[i]);
|
||||
|
||||
(uint256[] memory amountsOut, uint256[] memory gasEstimate) = quoter.quoteExactMultiInput(
|
||||
factory,
|
||||
dexPath,
|
||||
inputAmounts
|
||||
);
|
||||
uint256[] memory amountsOut;
|
||||
uint256[] memory gasEstimatesTemp;
|
||||
|
||||
for (uint256 j = 0; j < outputAmounts.length; ++j) {
|
||||
if (outputAmounts[j] < amountsOut[j]) {
|
||||
outputAmounts[j] = amountsOut[j];
|
||||
paths[j] = dexPath;
|
||||
gasEstimates[j] = gasEstimate[j];
|
||||
try quoter.quoteExactMultiInput(factory, dexPath, inputAmounts) {} catch (bytes memory reason) {
|
||||
bool success;
|
||||
(success, amountsOut, gasEstimatesTemp) = catchKyberElasticMultiSwapResult(reason);
|
||||
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint256 j = 0; j < amountsOut.length; ++j) {
|
||||
if (amountsOut[j] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputAmounts[j] < amountsOut[j]) {
|
||||
outputAmounts[j] = amountsOut[j];
|
||||
paths[j] = dexPath;
|
||||
gasEstimates[j] = gasEstimatesTemp[j];
|
||||
} else if (outputAmounts[j] == amountsOut[j] && gasEstimates[j] > gasEstimatesTemp[j]) {
|
||||
paths[j] = dexPath;
|
||||
gasEstimates[j] = gasEstimatesTemp[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +93,7 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
/// @return outputAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromKyberElastic(
|
||||
IMultiQuoter quoter,
|
||||
IFactory factory,
|
||||
address factory,
|
||||
address[] memory path,
|
||||
uint256[] memory inputAmounts
|
||||
) public view returns (bytes[] memory paths, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
|
||||
@@ -84,7 +102,7 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
gasEstimates = new uint256[](inputAmounts.length);
|
||||
|
||||
address[] memory reversedPath = _reverseTokenPath(path);
|
||||
IPool[][] memory poolPaths = _getPoolPaths(
|
||||
IKyberElasticPool[][] memory poolPaths = _getPoolPaths(
|
||||
quoter,
|
||||
factory,
|
||||
reversedPath,
|
||||
@@ -98,17 +116,30 @@ contract KyberElasticSampler is KyberElasticCommon {
|
||||
|
||||
bytes memory poolPath = _toPath(reversedPath, poolPaths[i]);
|
||||
|
||||
(uint256[] memory amountsOut, uint256[] memory gasEstimate) = quoter.quoteExactMultiOutput(
|
||||
factory,
|
||||
poolPath,
|
||||
inputAmounts
|
||||
);
|
||||
uint256[] memory amountsIn;
|
||||
uint256[] memory gasEstimatesTemp;
|
||||
|
||||
for (uint256 j = 0; j < amountsOut.length; ++j) {
|
||||
if (amountsOut[j] > 0 && (outputAmounts[j] == 0 || outputAmounts[j] < amountsOut[j])) {
|
||||
outputAmounts[j] = amountsOut[j];
|
||||
paths[j] = _toPath(path, _reversePoolPath(poolPaths[i]));
|
||||
gasEstimates[j] = gasEstimate[j];
|
||||
try quoter.quoteExactMultiOutput(factory, poolPath, inputAmounts) {} catch (bytes memory reason) {
|
||||
bool success;
|
||||
(success, amountsIn, gasEstimatesTemp) = catchKyberElasticMultiSwapResult(reason);
|
||||
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint256 j = 0; j < amountsIn.length; ++j) {
|
||||
if (amountsIn[j] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputAmounts[j] == 0 || outputAmounts[j] > amountsIn[j]) {
|
||||
outputAmounts[j] = amountsIn[j];
|
||||
paths[j] = _toPath(path, _reversePoolPath(poolPaths[i]));
|
||||
gasEstimates[j] = gasEstimatesTemp[j];
|
||||
} else if (outputAmounts[j] == amountsIn[j] && outputAmounts[j] > gasEstimatesTemp[j]) {
|
||||
paths[j] = _toPath(path, _reversePoolPath(poolPaths[i]));
|
||||
gasEstimates[j] = gasEstimatesTemp[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
180
apps-node/api/contracts/src/MultiQuoter.sol
Normal file
180
apps-node/api/contracts/src/MultiQuoter.sol
Normal file
@@ -0,0 +1,180 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
pragma solidity >=0.7 <=0.8;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
|
||||
/// @title Provides quotes for multiple swap amounts
|
||||
/// @notice Allows getting the expected amount out or amount in for multiple given swap amounts without executing the swap
|
||||
abstract contract MultiQuoter is IMultiQuoter {
|
||||
// TODO: both quoteExactMultiInput and quoteExactMultiOutput revert at the end of the quoting logic
|
||||
// and return results encodied into a revert reason. The revert should be removed and replaced with
|
||||
// a normal return statement whenever the Tick Based AMM samplers stop having the two pool filtering
|
||||
// logic. The two pool filtering logic causes pool's storage slots to be warmed up, causing gas estimates
|
||||
// to be significantly below the gas used during settlement. Additionally, per the following EIP
|
||||
// this revert logic might not clear warm storage slots in the future: https://eips.ethereum.org/EIPS/eip-3978
|
||||
|
||||
// the result of multiswap
|
||||
struct MultiSwapResult {
|
||||
// the gas estimate for each of swap amounts
|
||||
uint256[] gasEstimates;
|
||||
// the token0 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts0;
|
||||
// the token1 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts1;
|
||||
}
|
||||
|
||||
// @inheritdoc IMultiQuoter
|
||||
function quoteExactMultiInput(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) external view override {
|
||||
for (uint256 i = 0; i < amountsIn.length - 1; ++i) {
|
||||
require(amountsIn[i] <= amountsIn[i + 1], "MultiQuoter/amountsIn must be monotonically increasing");
|
||||
}
|
||||
uint256[] memory gasEstimates = new uint256[](amountsIn.length);
|
||||
|
||||
while (true) {
|
||||
(address pool, bool zeroForOne) = getFirstSwapDetails(factory, path, true);
|
||||
|
||||
// multiswap only accepts int256[] for input amounts
|
||||
int256[] memory amounts = new int256[](amountsIn.length);
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amounts[i] = int256(amountsIn[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(pool, zeroForOne, amounts);
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amountsIn[i] = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
|
||||
// decide whether to continue or terminate
|
||||
if (pathHasMultiplePools(path)) {
|
||||
path = pathSkipToken(path);
|
||||
} else {
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to MultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
revertWithAmountsAndGas(amountsIn, gasEstimates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @inheritdoc IMultiQuoter
|
||||
function quoteExactMultiOutput(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) external view override {
|
||||
uint256[] memory amountsIn = new uint256[](amountsOut.length);
|
||||
uint256[] memory gasEstimates = new uint256[](amountsOut.length);
|
||||
|
||||
for (uint256 i = 0; i < amountsOut.length - 1; ++i) {
|
||||
require(amountsOut[i] <= amountsOut[i + 1], "MultiQuoter/amountsOut must be monotonically increasing");
|
||||
}
|
||||
|
||||
uint256 nextAmountsLength = amountsOut.length;
|
||||
while (true) {
|
||||
(address pool, bool zeroForOne) = getFirstSwapDetails(factory, path, false);
|
||||
|
||||
// multiswap only accepts int256[] for output amounts
|
||||
int256[] memory amounts = new int256[](nextAmountsLength);
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amounts[i] = -int256(amountsOut[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(pool, zeroForOne, amounts);
|
||||
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
uint256 amountReceived = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
if (amountReceived != amountsOut[i]) {
|
||||
// for exact output swaps we need to check whether we would receive the full amount due to
|
||||
// multiswap behavior when hitting the limit
|
||||
nextAmountsLength = i;
|
||||
break;
|
||||
} else {
|
||||
// populate amountsOut for the next pool
|
||||
amountsOut[i] = zeroForOne ? uint256(result.amounts0[i]) : uint256(result.amounts1[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (nextAmountsLength == 0 || !pathHasMultiplePools(path)) {
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amountsIn[i] = amountsOut[i];
|
||||
}
|
||||
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to MultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
revertWithAmountsAndGas(amountsIn, gasEstimates);
|
||||
}
|
||||
|
||||
path = pathSkipToken(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice reverts the transaction while encoding amounts and gas estimates into the revert reason
|
||||
/// @param amounts the amounts to encode as the first encoding in the revert reason
|
||||
/// @param gasEstimates the gas estimates to encode as the second encoding in the revert reason
|
||||
function revertWithAmountsAndGas(uint256[] memory amounts, uint256[] memory gasEstimates) private pure {
|
||||
bytes memory revertResult = abi.encodeWithSignature("result(uint256[],uint256[])", amounts, gasEstimates);
|
||||
assembly {
|
||||
revert(add(revertResult, 0x20), mload(revertResult))
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice get the details of the first swap including the pool to swap within and direction of the swap
|
||||
/// @param factory The factory contract managing the tick based AMM pools
|
||||
/// @param path The path of the swap as a combination of token pairs and/or pool fee
|
||||
/// @param isExactInput whether or not this wap is an exact input or exact output swap (this determines whether tokenIn is first or second in the path)
|
||||
/// @return pool The first pool derived from the path.
|
||||
/// @return zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
|
||||
function getFirstSwapDetails(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
bool isExactInput
|
||||
) internal view virtual returns (address pool, bool zeroForOne);
|
||||
|
||||
/// @notice swap multiple amounts of token0 for token1 or token1 for token1
|
||||
/// @dev The results of multiswap includes slight rounding issues resulting from rounding up/rounding down in SqrtPriceMath library. Additionally,
|
||||
/// it should be noted that multiswap requires a monotonically increasing list of amounts for exact inputs and monotonically decreasing list of
|
||||
/// amounts for exact outputs.
|
||||
/// @param pool The tick based AMM pool to simulate each of the swap amounts for
|
||||
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
|
||||
/// @param amounts The amounts of the swaps, positive values indicate exactInput and negative values indicate exact output
|
||||
/// @return result The results of the swap as a MultiSwapResult struct with gas used, token0 and token1 deltas
|
||||
function multiswap(
|
||||
address pool,
|
||||
bool zeroForOne,
|
||||
int256[] memory amounts
|
||||
) internal view virtual returns (MultiSwapResult memory result);
|
||||
|
||||
/// @notice Returns true iff the path contains two or more pools
|
||||
/// @param path The encoded swap path
|
||||
/// @return True if path contains two or more pools, otherwise false
|
||||
function pathHasMultiplePools(bytes memory path) internal pure virtual returns (bool);
|
||||
|
||||
/// @notice Skips a token and/or fee element from the path and returns the remainder
|
||||
/// @param path The swap path
|
||||
/// @return The remaining token + fee elements in the path
|
||||
function pathSkipToken(bytes memory path) internal pure virtual returns (bytes memory);
|
||||
}
|
@@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
import "./interfaces/IUniswapV3.sol";
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
|
||||
contract UniswapV3Common {
|
||||
/// @dev Gas limit for UniswapV3 calls
|
||||
@@ -79,8 +80,8 @@ contract UniswapV3Common {
|
||||
|
||||
/// @dev Returns `poolPaths` to sample against. The caller is responsible for not using path involinvg zero address(es).
|
||||
function getPoolPaths(
|
||||
IUniswapV3Factory factory,
|
||||
IUniswapV3MultiQuoter multiQuoter,
|
||||
address factory,
|
||||
IMultiQuoter multiQuoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256 inputAmount
|
||||
) internal view returns (IUniswapV3Pool[][] memory poolPaths) {
|
||||
@@ -94,8 +95,8 @@ contract UniswapV3Common {
|
||||
}
|
||||
|
||||
function getPoolPathSingleHop(
|
||||
IUniswapV3Factory factory,
|
||||
IUniswapV3MultiQuoter multiQuoter,
|
||||
address factory,
|
||||
IMultiQuoter multiQuoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256 inputAmount
|
||||
) private view returns (IUniswapV3Pool[][] memory poolPaths) {
|
||||
@@ -120,8 +121,8 @@ contract UniswapV3Common {
|
||||
}
|
||||
|
||||
function getPoolPathTwoHop(
|
||||
IUniswapV3Factory factory,
|
||||
IUniswapV3MultiQuoter multiQuoter,
|
||||
address factory,
|
||||
IMultiQuoter multiQuoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256 inputAmount
|
||||
) private view returns (IUniswapV3Pool[][] memory poolPaths) {
|
||||
@@ -159,8 +160,8 @@ contract UniswapV3Common {
|
||||
}
|
||||
|
||||
struct GetTopTwoPoolsParams {
|
||||
IUniswapV3Factory factory;
|
||||
IUniswapV3MultiQuoter multiQuoter;
|
||||
address factory;
|
||||
IMultiQuoter multiQuoter;
|
||||
IERC20TokenV06 inputToken;
|
||||
IERC20TokenV06 outputToken;
|
||||
uint256 inputAmount;
|
||||
@@ -180,10 +181,12 @@ contract UniswapV3Common {
|
||||
|
||||
uint24[4] memory validPoolFees = [uint24(0.0001e6), uint24(0.0005e6), uint24(0.003e6), uint24(0.01e6)];
|
||||
for (uint256 i = 0; i < validPoolFees.length; ++i) {
|
||||
IUniswapV3Pool pool = params.factory.getPool(
|
||||
address(params.inputToken),
|
||||
address(params.outputToken),
|
||||
validPoolFees[i]
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(
|
||||
IUniswapV3Factory(params.factory).getPool(
|
||||
address(params.inputToken),
|
||||
address(params.outputToken),
|
||||
validPoolFees[i]
|
||||
)
|
||||
);
|
||||
if (!isValidPool(pool)) {
|
||||
continue;
|
||||
@@ -200,7 +203,7 @@ contract UniswapV3Common {
|
||||
inputAmounts
|
||||
)
|
||||
{} catch (bytes memory reason) {
|
||||
(bool success, uint256[] memory outputAmounts, ) = catchMultiSwapResult(reason);
|
||||
(bool success, uint256[] memory outputAmounts, ) = catchUniswapV3MultiSwapResult(reason);
|
||||
if (success) {
|
||||
// Keeping track of the top 2 pools.
|
||||
if (outputAmounts[0] > topOutputAmounts[0]) {
|
||||
@@ -247,7 +250,7 @@ contract UniswapV3Common {
|
||||
return true;
|
||||
}
|
||||
|
||||
function catchMultiSwapResult(
|
||||
function catchUniswapV3MultiSwapResult(
|
||||
bytes memory revertReason
|
||||
) internal pure returns (bool success, uint256[] memory amounts, uint256[] memory gasEstimates) {
|
||||
bytes4 selector;
|
||||
|
@@ -24,16 +24,9 @@ import "@uniswap/v3-core/contracts/libraries/SafeCast.sol";
|
||||
import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
|
||||
|
||||
import "./interfaces/IUniswapV3.sol";
|
||||
import "./MultiQuoter.sol";
|
||||
|
||||
/// @title Provides quotes for multiple swap amounts
|
||||
/// @notice Allows getting the expected amount out or amount in for multiple given swap amounts without executing the swap
|
||||
contract UniswapV3MultiQuoter is IUniswapV3MultiQuoter {
|
||||
// TODO: both quoteExactMultiInput and quoteExactMultiOutput revert at the end of the quoting logic
|
||||
// and return results encodied into a revert reason. The revert should be removed and replaced with
|
||||
// a normal return statement whenever UniswapV3Sampler stops having the two pool filtering logic.
|
||||
// The two pool filtering logic causes pool's storage slots to be warmed up, causing gas estimates
|
||||
// to be significantly below the gas used during settlement. Additionally, per the following EIP
|
||||
// this revert logic might not clear warm storage slots in the future: https://eips.ethereum.org/EIPS/eip-3978
|
||||
contract UniswapV3MultiQuoter is MultiQuoter {
|
||||
using Path for bytes;
|
||||
using SafeCast for uint256;
|
||||
using LowGasSafeMath for int256;
|
||||
@@ -76,175 +69,38 @@ contract UniswapV3MultiQuoter is IUniswapV3MultiQuoter {
|
||||
uint256 gasBefore;
|
||||
}
|
||||
|
||||
// the result of multiswap
|
||||
struct MultiSwapResult {
|
||||
// the gas estimate for each of swap amounts
|
||||
uint256[] gasEstimates;
|
||||
// the token0 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts0;
|
||||
// the token1 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts1;
|
||||
}
|
||||
|
||||
/// @notice Quotes amounts out for a set of exact input swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountsIn The amounts in of the first token to swap
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsOut, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiInput(
|
||||
IUniswapV3Factory factory,
|
||||
/// @inheritdoc MultiQuoter
|
||||
function getFirstSwapDetails(
|
||||
address factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) public view override {
|
||||
for (uint256 i = 0; i < amountsIn.length - 1; ++i) {
|
||||
require(
|
||||
amountsIn[i] <= amountsIn[i + 1],
|
||||
"UniswapV3MultiQuoter/amountsIn must be monotonically increasing"
|
||||
);
|
||||
bool isExactInput
|
||||
) internal view override returns (address pool, bool zeroForOne) {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint24 fee;
|
||||
if (isExactInput) {
|
||||
(tokenIn, tokenOut, fee) = path.decodeFirstPool();
|
||||
} else {
|
||||
(tokenOut, tokenIn, fee) = path.decodeFirstPool();
|
||||
}
|
||||
uint256[] memory gasEstimates = new uint256[](amountsIn.length);
|
||||
|
||||
while (true) {
|
||||
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
|
||||
bool zeroForOne = tokenIn < tokenOut;
|
||||
IUniswapV3Pool pool = factory.getPool(tokenIn, tokenOut, fee);
|
||||
|
||||
// multiswap only accepts int256[] for input amounts
|
||||
int256[] memory amounts = new int256[](amountsIn.length);
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amounts[i] = int256(amountsIn[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(
|
||||
pool,
|
||||
zeroForOne,
|
||||
amounts,
|
||||
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
amountsIn[i] = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
|
||||
// decide whether to continue or terminate
|
||||
if (path.hasMultiplePools()) {
|
||||
path = path.skipToken();
|
||||
} else {
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to UniswapV3MultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
bytes memory revertResult = abi.encodeWithSignature(
|
||||
"result(uint256[],uint256[])",
|
||||
amountsIn,
|
||||
gasEstimates
|
||||
);
|
||||
assembly {
|
||||
revert(add(revertResult, 0x20), mload(revertResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
zeroForOne = tokenIn < tokenOut;
|
||||
pool = IUniswapV3Factory(factory).getPool(tokenIn, tokenOut, fee);
|
||||
}
|
||||
|
||||
/// @notice Quotes amounts in a set of exact output swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountsOut The amounts out of the last token to receive
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsIn, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiOutput(
|
||||
IUniswapV3Factory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) public view override {
|
||||
uint256[] memory amountsIn = new uint256[](amountsOut.length);
|
||||
uint256[] memory gasEstimates = new uint256[](amountsOut.length);
|
||||
|
||||
for (uint256 i = 0; i < amountsOut.length - 1; ++i) {
|
||||
require(
|
||||
amountsOut[i] <= amountsOut[i + 1],
|
||||
"UniswapV3MultiQuoter/amountsOut must be monotonically increasing"
|
||||
);
|
||||
}
|
||||
|
||||
uint256 nextAmountsLength = amountsOut.length;
|
||||
while (true) {
|
||||
(address tokenOut, address tokenIn, uint24 fee) = path.decodeFirstPool();
|
||||
bool zeroForOne = tokenIn < tokenOut;
|
||||
IUniswapV3Pool pool = factory.getPool(tokenIn, tokenOut, fee);
|
||||
|
||||
// multiswap only accepts int256[] for output amounts
|
||||
int256[] memory amounts = new int256[](nextAmountsLength);
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amounts[i] = -int256(amountsOut[i]);
|
||||
}
|
||||
|
||||
MultiSwapResult memory result = multiswap(
|
||||
pool,
|
||||
zeroForOne,
|
||||
amounts,
|
||||
zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1
|
||||
);
|
||||
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
uint256 amountReceived = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
|
||||
if (amountReceived != amountsOut[i]) {
|
||||
// for exact output swaps we need to check whether we would receive the full amount due to
|
||||
// multiswap behavior when hitting the limit
|
||||
nextAmountsLength = i;
|
||||
break;
|
||||
} else {
|
||||
// populate amountsOut for the next pool
|
||||
amountsOut[i] = zeroForOne ? uint256(result.amounts0[i]) : uint256(result.amounts1[i]);
|
||||
gasEstimates[i] += result.gasEstimates[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (nextAmountsLength == 0 || !path.hasMultiplePools()) {
|
||||
for (uint256 i = 0; i < nextAmountsLength; ++i) {
|
||||
amountsIn[i] = amountsOut[i];
|
||||
}
|
||||
|
||||
// quote results must be encoded into a revert because otherwise subsequent calls
|
||||
// to UniswapV3MultiQuoter result in multiswap hitting pool storage slots that are
|
||||
// already warm. This results in very inaccurate gas estimates when estimating gas
|
||||
// usage for settlement.
|
||||
bytes memory revertResult = abi.encodeWithSignature(
|
||||
"result(uint256[],uint256[])",
|
||||
amountsIn,
|
||||
gasEstimates
|
||||
);
|
||||
assembly {
|
||||
revert(add(revertResult, 0x20), mload(revertResult))
|
||||
}
|
||||
}
|
||||
|
||||
path = path.skipToken();
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice swap multiple amounts of token0 for token1 or token1 for token1
|
||||
/// @dev The results of multiswap includes slight rounding issues resulting from rounding up/rounding down in SqrtPriceMath library. Additionally,
|
||||
/// it should be noted that multiswap requires a monotonically increasing list of amounts for exact inputs and monotonically decreasing list of
|
||||
/// amounts for exact outputs.
|
||||
/// @param pool The UniswapV3 pool to simulate each of the swap amounts for
|
||||
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
|
||||
/// @param amounts The amounts of the swaps, positive values indicate exactInput and negative values indicate exact output
|
||||
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
|
||||
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
|
||||
/// @return result The results of the swap as a MultiSwapResult struct with gas used, token0 and token1 deltas
|
||||
/// @inheritdoc MultiQuoter
|
||||
function multiswap(
|
||||
IUniswapV3Pool pool,
|
||||
address p,
|
||||
bool zeroForOne,
|
||||
int256[] memory amounts,
|
||||
uint160 sqrtPriceLimitX96
|
||||
) private view returns (MultiSwapResult memory result) {
|
||||
int256[] memory amounts
|
||||
) internal view override returns (MultiSwapResult memory result) {
|
||||
result.gasEstimates = new uint256[](amounts.length);
|
||||
result.amounts0 = new int256[](amounts.length);
|
||||
result.amounts1 = new int256[](amounts.length);
|
||||
|
||||
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(p);
|
||||
|
||||
(uint160 sqrtPriceX96Start, int24 tickStart, , , , , ) = pool.slot0();
|
||||
int24 tickSpacing = pool.tickSpacing();
|
||||
uint24 fee = pool.fee();
|
||||
@@ -377,7 +233,7 @@ contract UniswapV3MultiQuoter is IUniswapV3MultiQuoter {
|
||||
int24 tick,
|
||||
int24 tickSpacing,
|
||||
bool lte
|
||||
) private view returns (int24 next, bool initialized) {
|
||||
) internal view returns (int24 next, bool initialized) {
|
||||
int24 compressed = tick / tickSpacing;
|
||||
if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity
|
||||
|
||||
@@ -417,4 +273,14 @@ contract UniswapV3MultiQuoter is IUniswapV3MultiQuoter {
|
||||
wordPos = int16(tick >> 8);
|
||||
bitPos = uint8(tick % 256);
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
|
||||
return path.hasMultiplePools();
|
||||
}
|
||||
|
||||
/// @inheritdoc MultiQuoter
|
||||
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
|
||||
return path.skipToken();
|
||||
}
|
||||
}
|
||||
|
@@ -23,11 +23,10 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
import "./UniswapV3Common.sol";
|
||||
import "./interfaces/IUniswapV3.sol";
|
||||
import "./interfaces/IMultiQuoter.sol";
|
||||
|
||||
contract UniswapV3Sampler is UniswapV3Common {
|
||||
IUniswapV3MultiQuoter private constant multiQuoter =
|
||||
IUniswapV3MultiQuoter(0x5555555555555555555555555555555555555556);
|
||||
IMultiQuoter private constant multiQuoter = IMultiQuoter(0x5555555555555555555555555555555555555556);
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV3.
|
||||
/// @param factory UniswapV3 Factory contract.
|
||||
@@ -37,7 +36,7 @@ contract UniswapV3Sampler is UniswapV3Common {
|
||||
/// @return uniswapGasUsed Estimated amount of gas used
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromUniswapV3(
|
||||
IUniswapV3Factory factory,
|
||||
address factory,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
@@ -69,7 +68,7 @@ contract UniswapV3Sampler is UniswapV3Common {
|
||||
bytes memory reason
|
||||
) {
|
||||
bool success;
|
||||
(success, amountsOut, gasEstimates) = catchMultiSwapResult(reason);
|
||||
(success, amountsOut, gasEstimates) = catchUniswapV3MultiSwapResult(reason);
|
||||
|
||||
if (!success) {
|
||||
continue;
|
||||
@@ -101,7 +100,7 @@ contract UniswapV3Sampler is UniswapV3Common {
|
||||
/// @return uniswapGasUsed Estimated amount of gas used
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromUniswapV3(
|
||||
IUniswapV3Factory factory,
|
||||
address factory,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
@@ -134,7 +133,7 @@ contract UniswapV3Sampler is UniswapV3Common {
|
||||
bytes memory reason
|
||||
) {
|
||||
bool success;
|
||||
(success, amountsIn, gasEstimates) = catchMultiSwapResult(reason);
|
||||
(success, amountsIn, gasEstimates) = catchUniswapV3MultiSwapResult(reason);
|
||||
|
||||
if (!success) {
|
||||
continue;
|
||||
|
@@ -1,43 +1,5 @@
|
||||
pragma solidity >=0.6;
|
||||
|
||||
interface IAlgebraQuoter {
|
||||
/// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair
|
||||
/// @param amountIn The amount of the first token to swap
|
||||
/// @return amountOut The amount of the last token that would be received
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
) external returns (uint256 amountOut, uint16[] memory fees);
|
||||
|
||||
/// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair. Path must be provided in reverse order
|
||||
/// @param amountOut The amount of the last token to receive
|
||||
/// @return amountIn The amount of first token required to be paid
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
) external returns (uint256 amountIn, uint16[] memory fees);
|
||||
}
|
||||
|
||||
interface IAlgebraMultiQuoter {
|
||||
// @notice Returns the amounts out received for a given set of exact input swaps without executing the swap
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountsIn The amounts in of the first token to swap
|
||||
function quoteExactMultiInput(IAlgebraFactory factory, bytes memory path, uint256[] memory amountsIn) external view;
|
||||
|
||||
/// @notice Returns the amounts in received for a given set of exact output swaps without executing the swap
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountsOut The amounts out of the last token to receive
|
||||
function quoteExactMultiOutput(
|
||||
IAlgebraFactory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) external view;
|
||||
}
|
||||
|
||||
interface IAlgebraFactory {
|
||||
/**
|
||||
* @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
|
||||
@@ -46,7 +8,7 @@ interface IAlgebraFactory {
|
||||
* @param tokenB The contract address of the other token
|
||||
* @return pool The pool address
|
||||
*/
|
||||
function poolByPair(address tokenA, address tokenB) external view returns (IAlgebraPool pool);
|
||||
function poolByPair(address tokenA, address tokenB) external view returns (address pool);
|
||||
}
|
||||
|
||||
interface IAlgebraPool {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
pragma solidity >=0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IFactory {
|
||||
interface IKyberElasticFactory {
|
||||
/// @notice Returns the pool address for a given pair of tokens and a swap fee
|
||||
/// @dev Token order does not matter
|
||||
/// @param tokenA Contract address of either token0 or token1
|
||||
@@ -9,14 +9,9 @@ interface IFactory {
|
||||
/// @param swapFeeUnits Fee to be collected upon every swap in the pool, in fee units
|
||||
/// @return pool The pool address. Returns null address if it does not exist
|
||||
function getPool(address tokenA, address tokenB, uint24 swapFeeUnits) external view returns (address pool);
|
||||
|
||||
function parameters()
|
||||
external
|
||||
view
|
||||
returns (address factory, address token0, address token1, uint24 swapFeeUnits, int24 tickDistance);
|
||||
}
|
||||
|
||||
interface IPool {
|
||||
interface IKyberElasticPool {
|
||||
function token0() external view returns (address);
|
||||
|
||||
function token1() external view returns (address);
|
||||
@@ -25,19 +20,6 @@ interface IPool {
|
||||
/// @return The swap fee in basis points
|
||||
function swapFeeUnits() external view returns (uint24);
|
||||
|
||||
/// @notice The pool tick distance
|
||||
/// @dev Ticks can only be initialized and used at multiples of this value
|
||||
/// It remains an int24 to avoid casting even though it is >= 1.
|
||||
/// e.g: a tickDistance of 5 means ticks can be initialized every 5th tick, i.e., ..., -10, -5, 0, 5, 10, ...
|
||||
/// @return The tick distance
|
||||
function tickDistance() external view returns (int24);
|
||||
|
||||
/// @notice Maximum gross liquidity that an initialized tick can have
|
||||
/// @dev This is to prevent overflow the pool's active base liquidity (uint128)
|
||||
/// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
|
||||
/// @return The max amount of liquidity per tick
|
||||
function maxTickLiquidity() external view returns (uint128);
|
||||
|
||||
/// @notice Look up information about a specific tick in the pool
|
||||
/// @param tick The tick to look up
|
||||
/// @return liquidityGross total liquidity amount from positions that uses this tick as a lower or upper tick
|
||||
@@ -61,15 +43,6 @@ interface IPool {
|
||||
/// @param tick The tick to look up
|
||||
function initializedTicks(int24 tick) external view returns (int24 previous, int24 next);
|
||||
|
||||
/// @notice Returns the information about a position by the position's key
|
||||
/// @return liquidity the liquidity quantity of the position
|
||||
/// @return feeGrowthInsideLast fee growth inside the tick range as of the last mint / burn action performed
|
||||
function getPositions(
|
||||
address owner,
|
||||
int24 tickLower,
|
||||
int24 tickUpper
|
||||
) external view returns (uint128 liquidity, uint256 feeGrowthInsideLast);
|
||||
|
||||
/// @notice Fetches the pool's prices, ticks and lock status
|
||||
/// @return sqrtP sqrt of current price: sqrt(token1/token0)
|
||||
/// @return currentTick pool's current tick
|
||||
@@ -86,90 +59,3 @@ interface IPool {
|
||||
/// @return reinvestLLast last cached value of reinvestL, used for calculating reinvestment token qty
|
||||
function getLiquidityState() external view returns (uint128 baseL, uint128 reinvestL, uint128 reinvestLLast);
|
||||
}
|
||||
|
||||
/// @title QuoterV2 Interface
|
||||
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps.
|
||||
/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap.
|
||||
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
|
||||
/// to compute the result. They are also not gas efficient and should not be called on-chain.
|
||||
interface IQuoterV2 {
|
||||
struct QuoteOutput {
|
||||
uint256 usedAmount;
|
||||
uint256 returnedAmount;
|
||||
uint160 afterSqrtP;
|
||||
uint32 initializedTicksCrossed;
|
||||
uint256 gasEstimate;
|
||||
}
|
||||
|
||||
/// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountIn The amount of the first token to swap
|
||||
/// @return amountOut The amount of the last token that would be received
|
||||
/// @return afterSqrtPList List of the sqrt price after the swap for each pool in the path
|
||||
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
/// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountOut,
|
||||
uint160[] memory afterSqrtPList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
struct QuoteExactInputSingleParams {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint256 amountIn;
|
||||
uint24 feeUnits;
|
||||
uint160 limitSqrtP;
|
||||
}
|
||||
|
||||
/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
|
||||
/// @param params The params for the quote, encoded as `QuoteExactInputSingleParams`
|
||||
/// tokenIn The token being swapped in
|
||||
/// tokenOut The token being swapped out
|
||||
/// fee The fee of the token pool to consider for the pair
|
||||
/// amountIn The desired input amount
|
||||
/// limitSqrtP The price limit of the pool that cannot be exceeded by the swap
|
||||
function quoteExactInputSingle(QuoteExactInputSingleParams memory params) external returns (QuoteOutput memory);
|
||||
|
||||
/// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountOut The amount of the last token to receive
|
||||
/// @return amountIn The amount of first token required to be paid
|
||||
/// @return afterSqrtPList List of the sqrt price after the swap for each pool in the path
|
||||
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
/// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountIn,
|
||||
uint160[] memory afterSqrtPList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
struct QuoteExactOutputSingleParams {
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint256 amount;
|
||||
uint24 feeUnits;
|
||||
uint160 limitSqrtP;
|
||||
}
|
||||
|
||||
/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
|
||||
/// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams`
|
||||
/// tokenIn The token being swapped in
|
||||
/// tokenOut The token being swapped out
|
||||
/// fee The fee of the token pool to consider for the pair
|
||||
/// amountOut The desired output amount
|
||||
/// limitSqrtP The price limit of the pool that cannot be exceeded by the swap
|
||||
function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) external returns (QuoteOutput memory);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2023 ZeroEx Intl.
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,18 +19,20 @@
|
||||
|
||||
pragma solidity >=0.6;
|
||||
|
||||
import "./IKyberElastic.sol";
|
||||
|
||||
interface IMultiQuoter {
|
||||
function quoteExactMultiInput(
|
||||
IFactory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) external view returns (uint256[] memory amountsOut, uint256[] memory gasEstimate);
|
||||
/// @notice Quotes amounts out for a set of exact input swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing the tick based AMM pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountsIn The amounts in of the first token to swap
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsOut, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiInput(address factory, bytes memory path, uint256[] memory amountsIn) external view;
|
||||
|
||||
function quoteExactMultiOutput(
|
||||
IFactory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) external view returns (uint256[] memory amountsIn, uint256[] memory gasEstimate);
|
||||
/// @notice Quotes amounts in a set of exact output swaps and provides results encoded into a revert reason
|
||||
/// @param factory The factory contract managing the tick based AMM pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountsOut The amounts out of the last token to receive
|
||||
/// @dev This function reverts at the end of the quoting logic and encodes (uint256[] amountsIn, uint256[] gasEstimates)
|
||||
/// into the revert reason. See additional documentation below.
|
||||
function quoteExactMultiOutput(address factory, bytes memory path, uint256[] memory amountsOut) external view;
|
||||
}
|
||||
|
@@ -1,48 +1,5 @@
|
||||
pragma solidity >=0.6;
|
||||
|
||||
interface IUniswapV3QuoterV2 {
|
||||
/// @return Returns the address of the Uniswap V3 factory
|
||||
function factory() external view returns (IUniswapV3Factory);
|
||||
|
||||
// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
// @param amountIn The amount of the first token to swap
|
||||
// @return amountOut The amount of the last token that would be received
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountOut,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
// @param amountOut The amount of the last token to receive
|
||||
// @return amountIn The amount of first token required to be paid
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountIn,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
}
|
||||
|
||||
interface IUniswapV3Pool {
|
||||
/// @notice The first of the two tokens of the pool, sorted by address
|
||||
/// @return The token contract address
|
||||
@@ -134,27 +91,5 @@ interface IUniswapV3Factory {
|
||||
/// @param b The contract address of the other token
|
||||
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
|
||||
/// @return pool The pool address
|
||||
function getPool(address a, address b, uint24 fee) external view returns (IUniswapV3Pool pool);
|
||||
}
|
||||
|
||||
interface IUniswapV3MultiQuoter {
|
||||
// @notice Returns the amounts out received for a given set of exact input swaps without executing the swap
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountsIn The amounts in of the first token to swap
|
||||
function quoteExactMultiInput(
|
||||
IUniswapV3Factory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsIn
|
||||
) external view;
|
||||
|
||||
/// @notice Returns the amounts in received for a given set of exact output swaps without executing the swap
|
||||
/// @param factory The factory contract managing UniswapV3 pools
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountsOut The amounts out of the last token to receive
|
||||
function quoteExactMultiOutput(
|
||||
IUniswapV3Factory factory,
|
||||
bytes memory path,
|
||||
uint256[] memory amountsOut
|
||||
) external view;
|
||||
function getPool(address a, address b, uint24 fee) external view returns (address pool);
|
||||
}
|
||||
|
@@ -1,27 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright 2023 ZeroEx Intl.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
pragma solidity >=0.6;
|
||||
|
||||
library Multiswap {
|
||||
// the result of multiswap
|
||||
struct Result {
|
||||
// the gas estimate for each of swap amounts
|
||||
uint256[] gasEstimates;
|
||||
// the token0 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts0;
|
||||
// the token1 delta for each swap amount, positive indicates sent and negative indicates receipt
|
||||
int256[] amounts1;
|
||||
}
|
||||
}
|
@@ -5,6 +5,26 @@ import "forge-std/Test.sol";
|
||||
import "../src/AlgebraMultiQuoter.sol";
|
||||
import "../src/AlgebraCommon.sol";
|
||||
|
||||
interface IAlgebraQuoter {
|
||||
/// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair
|
||||
/// @param amountIn The amount of the first token to swap
|
||||
/// @return amountOut The amount of the last token that would be received
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
) external returns (uint256 amountOut, uint16[] memory fees);
|
||||
|
||||
/// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair. Path must be provided in reverse order
|
||||
/// @param amountOut The amount of the last token to receive
|
||||
/// @return amountIn The amount of first token required to be paid
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
) external returns (uint256 amountIn, uint16[] memory fees);
|
||||
}
|
||||
|
||||
contract TestAlgebraMultiQuoter is Test, AlgebraCommon {
|
||||
/// @dev error threshold for comparison between MultiQuoter and UniswapV3's official QuoterV2.
|
||||
/// MultiQuoter results in some rounding errors due to SqrtPriceMath library.
|
||||
@@ -134,7 +154,7 @@ contract TestAlgebraMultiQuoter is Test, AlgebraCommon {
|
||||
) private returns (uint256 algebraQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
uint256[] memory multiQuoterAmountsOut;
|
||||
try multiQuoter.quoteExactMultiInput(factory, path, amountsIn) {} catch (bytes memory reason) {
|
||||
try multiQuoter.quoteExactMultiInput(address(factory), path, amountsIn) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsOut, ) = catchAlgebraMultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
@@ -162,7 +182,7 @@ contract TestAlgebraMultiQuoter is Test, AlgebraCommon {
|
||||
) private returns (uint256 algebraQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
uint256[] memory multiQuoterAmountsIn;
|
||||
try multiQuoter.quoteExactMultiOutput(factory, path, amountsOut) {} catch (bytes memory reason) {
|
||||
try multiQuoter.quoteExactMultiOutput(address(factory), path, amountsOut) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsIn, ) = catchAlgebraMultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
|
@@ -4,7 +4,52 @@ pragma experimental ABIEncoderV2;
|
||||
import "forge-std/Test.sol";
|
||||
import "../src/KyberElasticMultiQuoter.sol";
|
||||
import "../src/KyberElasticCommon.sol";
|
||||
import {IPool, IFactory} from "../src/interfaces/IKyberElastic.sol";
|
||||
import {IKyberElasticPool, IKyberElasticFactory} from "../src/interfaces/IKyberElastic.sol";
|
||||
|
||||
/// @title QuoterV2 Interface
|
||||
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps.
|
||||
/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap.
|
||||
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
|
||||
/// to compute the result. They are also not gas efficient and should not be called on-chain.
|
||||
interface IQuoterV2 {
|
||||
/// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
/// @param amountIn The amount of the first token to swap
|
||||
/// @return amountOut The amount of the last token that would be received
|
||||
/// @return afterSqrtPList List of the sqrt price after the swap for each pool in the path
|
||||
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
/// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountOut,
|
||||
uint160[] memory afterSqrtPList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
/// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
/// @param amountOut The amount of the last token to receive
|
||||
/// @return amountIn The amount of first token required to be paid
|
||||
/// @return afterSqrtPList List of the sqrt price after the swap for each pool in the path
|
||||
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
/// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountIn,
|
||||
uint160[] memory afterSqrtPList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
}
|
||||
|
||||
contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
// NOTE: Example test command: forge test --fork-url $ETH_RPC_URL --fork-block-number 16400073 --etherscan-api-key $ETHERSCAN_API_KEY --match-contract "KyberElastic"
|
||||
@@ -15,11 +60,11 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
address constant ETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
address constant LINK = 0x514910771AF9Ca656af840dff83E8264EcF986CA;
|
||||
|
||||
IPool constant ETH_KNC_POOL_100_BIP = IPool(0xB5e643250FF59311071C5008f722488543DD7b3C);
|
||||
IPool constant ETH_KNC_POOL_30_BIP = IPool(0xa38a0165e82B7a5E8650109E9e54087a34C93020);
|
||||
IPool constant LINK_ETH_POOL_30_BIP = IPool(0x8990b58Ab653C9954415f4544e8deB72c2b12ED8);
|
||||
IKyberElasticPool constant ETH_KNC_POOL_100_BIP = IKyberElasticPool(0xB5e643250FF59311071C5008f722488543DD7b3C);
|
||||
IKyberElasticPool constant ETH_KNC_POOL_30_BIP = IKyberElasticPool(0xa38a0165e82B7a5E8650109E9e54087a34C93020);
|
||||
IKyberElasticPool constant LINK_ETH_POOL_30_BIP = IKyberElasticPool(0x8990b58Ab653C9954415f4544e8deB72c2b12ED8);
|
||||
IQuoterV2 constant kyberQuoter = IQuoterV2(0x0D125c15D54cA1F8a813C74A81aEe34ebB508C1f);
|
||||
IFactory constant factory = IFactory(0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a);
|
||||
IKyberElasticFactory constant factory = IKyberElasticFactory(0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a);
|
||||
KyberElasticMultiQuoter multiQuoter;
|
||||
uint256[][] testAmounts;
|
||||
|
||||
@@ -61,7 +106,7 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
tokenPath[0] = ETH;
|
||||
tokenPath[1] = KNC;
|
||||
|
||||
IPool[] memory poolPath = new IPool[](1);
|
||||
IKyberElasticPool[] memory poolPath = new IKyberElasticPool[](1);
|
||||
poolPath[0] = ETH_KNC_POOL_100_BIP;
|
||||
|
||||
testAllAmountsAndPaths(tokenPath, poolPath);
|
||||
@@ -72,7 +117,7 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
tokenPath[0] = ETH;
|
||||
tokenPath[1] = KNC;
|
||||
|
||||
IPool[] memory poolPath = new IPool[](1);
|
||||
IKyberElasticPool[] memory poolPath = new IKyberElasticPool[](1);
|
||||
poolPath[0] = ETH_KNC_POOL_30_BIP;
|
||||
|
||||
testAllAmountsAndPaths(tokenPath, poolPath);
|
||||
@@ -84,7 +129,7 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
tokenPath[1] = KNC;
|
||||
tokenPath[2] = LINK;
|
||||
|
||||
IPool[] memory poolPath = new IPool[](2);
|
||||
IKyberElasticPool[] memory poolPath = new IKyberElasticPool[](2);
|
||||
poolPath[0] = ETH_KNC_POOL_100_BIP;
|
||||
poolPath[1] = LINK_ETH_POOL_30_BIP;
|
||||
|
||||
@@ -102,7 +147,7 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
tokenPath[0] = ETH;
|
||||
tokenPath[1] = KNC;
|
||||
|
||||
IPool[][] memory poolPaths = _getPoolPaths(multiQuoter, factory, tokenPath, 1 ether);
|
||||
IKyberElasticPool[][] memory poolPaths = _getPoolPaths(multiQuoter, address(factory), tokenPath, 1 ether);
|
||||
assert(poolPaths.length == 2);
|
||||
for (uint256 i; i < poolPaths.length; ++i) {
|
||||
for (uint256 j; j < poolPaths[i].length; ++j) {
|
||||
@@ -114,7 +159,7 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
}
|
||||
}
|
||||
|
||||
function testAllAmountsAndPaths(address[] memory tokenPath, IPool[] memory poolPath) private {
|
||||
function testAllAmountsAndPaths(address[] memory tokenPath, IKyberElasticPool[] memory poolPath) private {
|
||||
uint256 kyberQuoterGasUsage;
|
||||
uint256 multiQuoterGasUsage;
|
||||
|
||||
@@ -164,7 +209,10 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
uint256[] memory amountsIn
|
||||
) private returns (uint256 kyberQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
(uint256[] memory multiQuoterAmountsOut, ) = multiQuoter.quoteExactMultiInput(factory, path, amountsIn);
|
||||
uint256[] memory multiQuoterAmountsOut;
|
||||
try multiQuoter.quoteExactMultiInput(address(factory), path, amountsIn) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsOut, ) = catchKyberElasticMultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; ++i) {
|
||||
@@ -185,7 +233,10 @@ contract TestKyberElasticMultiQuoter is Test, KyberElasticCommon {
|
||||
uint256[] memory amountsOut
|
||||
) private returns (uint256 kyberQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
(uint256[] memory multiQuoterAmountsIn, ) = multiQuoter.quoteExactMultiOutput(factory, path, amountsOut);
|
||||
uint256[] memory multiQuoterAmountsIn;
|
||||
try multiQuoter.quoteExactMultiOutput(address(factory), path, amountsOut) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsIn, ) = catchKyberElasticMultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
|
||||
for (uint256 i = 0; i < amountsOut.length; ++i) {
|
||||
|
@@ -7,7 +7,50 @@ import "forge-std/Test.sol";
|
||||
import "../src/UniswapV3MultiQuoter.sol";
|
||||
import "../src/UniswapV3Common.sol";
|
||||
|
||||
contract TestUniswapV3Sampler is Test, UniswapV3Common {
|
||||
interface IUniswapV3QuoterV2 {
|
||||
/// @return Returns the address of the Uniswap V3 factory
|
||||
function factory() external view returns (IUniswapV3Factory);
|
||||
|
||||
// @notice Returns the amount out received for a given exact input swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee
|
||||
// @param amountIn The amount of the first token to swap
|
||||
// @return amountOut The amount of the last token that would be received
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactInput(
|
||||
bytes memory path,
|
||||
uint256 amountIn
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountOut,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
|
||||
// @notice Returns the amount in required for a given exact output swap without executing the swap
|
||||
// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
|
||||
// @param amountOut The amount of the last token to receive
|
||||
// @return amountIn The amount of first token required to be paid
|
||||
// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
|
||||
// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
|
||||
// @return gasEstimate The estimate of the gas that the swap consumes
|
||||
function quoteExactOutput(
|
||||
bytes memory path,
|
||||
uint256 amountOut
|
||||
)
|
||||
external
|
||||
returns (
|
||||
uint256 amountIn,
|
||||
uint160[] memory sqrtPriceX96AfterList,
|
||||
uint32[] memory initializedTicksCrossedList,
|
||||
uint256 gasEstimate
|
||||
);
|
||||
}
|
||||
|
||||
contract TestUniswapV3MultiQuoter is Test, UniswapV3Common {
|
||||
/// @dev error threshold in wei for comparison between MultiQuoter and UniswapV3's official QuoterV2.
|
||||
/// MultiQuoter results in some rounding errors due to SqrtPriceMath library.
|
||||
uint256 constant ERROR_THRESHOLD = 125;
|
||||
@@ -148,8 +191,8 @@ contract TestUniswapV3Sampler is Test, UniswapV3Common {
|
||||
) private returns (uint256 uniQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
uint256[] memory multiQuoterAmountsOut;
|
||||
try multiQuoter.quoteExactMultiInput(factory, path, amountsIn) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsOut, ) = catchMultiSwapResult(reason);
|
||||
try multiQuoter.quoteExactMultiInput(address(factory), path, amountsIn) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsOut, ) = catchUniswapV3MultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
|
||||
@@ -187,8 +230,8 @@ contract TestUniswapV3Sampler is Test, UniswapV3Common {
|
||||
) private returns (uint256 uniQuoterGasUsage, uint256 multiQuoterGasUsage) {
|
||||
uint256 gas0 = gasleft();
|
||||
uint256[] memory multiQuoterAmountsIn;
|
||||
try multiQuoter.quoteExactMultiOutput(factory, path, amountsOut) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsIn, ) = catchMultiSwapResult(reason);
|
||||
try multiQuoter.quoteExactMultiOutput(address(factory), path, amountsOut) {} catch (bytes memory reason) {
|
||||
(, multiQuoterAmountsIn, ) = catchUniswapV3MultiSwapResult(reason);
|
||||
}
|
||||
uint256 gas1 = gasleft();
|
||||
|
||||
@@ -264,8 +307,8 @@ contract TestUniswapV3Sampler is Test, UniswapV3Common {
|
||||
|
||||
{
|
||||
uint256[] memory mqGasEstimates;
|
||||
try multiQuoter.quoteExactMultiInput(factory, path, amounts) {} catch (bytes memory reason) {
|
||||
(, , mqGasEstimates) = catchMultiSwapResult(reason);
|
||||
try multiQuoter.quoteExactMultiInput(address(factory), path, amounts) {} catch (bytes memory reason) {
|
||||
(, , mqGasEstimates) = catchUniswapV3MultiSwapResult(reason);
|
||||
}
|
||||
for (uint256 i = 0; i < amounts.length; ++i) {
|
||||
console.log("MQ Gas Estimates: i=%d, MQ: %d", i, mqGasEstimates[i]);
|
||||
@@ -276,8 +319,8 @@ contract TestUniswapV3Sampler is Test, UniswapV3Common {
|
||||
|
||||
{
|
||||
uint256[] memory mqGasEstimates;
|
||||
try multiQuoter.quoteExactMultiInput(factory, path, amounts) {} catch (bytes memory reason) {
|
||||
(, , mqGasEstimates) = catchMultiSwapResult(reason);
|
||||
try multiQuoter.quoteExactMultiInput(address(factory), path, amounts) {} catch (bytes memory reason) {
|
||||
(, , mqGasEstimates) = catchUniswapV3MultiSwapResult(reason);
|
||||
}
|
||||
for (uint256 i = 0; i < amounts.length; ++i) {
|
||||
console.log("MQ Gas Estimates: i=%d, MQ: %d", i, mqGasEstimates[i]);
|
||||
|
Reference in New Issue
Block a user