Skip to content

Commit

Permalink
super optimised ETH for USDC swap (#325)
Browse files Browse the repository at this point in the history
* super optimised ETH for USDC swap

* updated v3 math versions

* add ETH wrap

---------

Co-authored-by: hensha256 <[email protected]>
  • Loading branch information
haydenadams and hensha256 authored Oct 27, 2023
1 parent 6c8b844 commit c6614f7
Show file tree
Hide file tree
Showing 7 changed files with 572 additions and 3 deletions.
37 changes: 36 additions & 1 deletion contracts/CalldataOptRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.17;

import {V2SwapRouter} from './modules/uniswap/v2/V2SwapRouter.sol';
import {V3SwapRouter} from './modules/uniswap/v3/V3SwapRouter.sol';
import {OracleLibrary} from './modules/uniswap/v3/OracleLibrary.sol';

import {UniswapParameters, UniswapImmutables} from './modules/uniswap/UniswapImmutables.sol';
import {PaymentsParameters, PaymentsImmutables} from './modules/PaymentsImmutables.sol';
import {V3Path} from './modules/uniswap/v3/V3Path.sol';
Expand Down Expand Up @@ -51,12 +53,15 @@ contract CalldataOptRouter is V2SwapRouter, V3SwapRouter {
UNSUPPORTED_PROTOCOL
);

constructor(UniswapParameters memory uniswapParameters, PaymentsParameters memory paymentsParameters)
address immutable localUSDC;

constructor(UniswapParameters memory uniswapParameters, PaymentsParameters memory paymentsParameters, address _USDC)
UniswapImmutables(uniswapParameters)
PaymentsImmutables(paymentsParameters)
{
DEADLINE_OFFSET = block.timestamp;
END_OF_TIME = DEADLINE_OFFSET + (DEADLINE_GRANULARITY * type(uint16).max);
localUSDC = _USDC;
}

/// @notice Thrown when executing commands with an expired deadline
Expand All @@ -72,6 +77,36 @@ contract CalldataOptRouter is V2SwapRouter, V3SwapRouter {
_;
}

// can be unsafe if you don't know what you're doing
// slippage tolerance set to 2% from the 1 minute average
function swapETHForUSDCOptimized() public payable {
uint24 _feeTier = 500;
address _poolAddress = computePoolAddress(address(WETH9), localUSDC, _feeTier);
uint32 _period = uint32(block.timestamp - 1 minutes);
(int24 arithmeticMeanTick,) = OracleLibrary.consult(_poolAddress, _period);

uint256 _quoteAmount = OracleLibrary.getQuoteAtTick(
arithmeticMeanTick,
uint128(msg.value),
address(WETH9),
localUSDC
);

uint256 _minOutput = _quoteAmount * 49 / 50;

bytes memory _path = abi.encodePacked(address(WETH9), _feeTier, localUSDC);

WETH9.deposit{value: msg.value}();

v3SwapExactInput(
msg.sender,
msg.value,
_minOutput,
_path,
address(this)
);
}

function v3SwapExactTokenForToken(bytes calldata swapInfo) external checkDeadline(swapInfo) {
uint256 amountIn;
uint256 amountOutMinimum;
Expand Down
186 changes: 186 additions & 0 deletions contracts/modules/uniswap/v3/OracleLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// pragma solidity >=0.5.0 <0.8.0
pragma solidity ^0.8.17;


import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import './updated/updatedTickMath.sol';
import './updated/updatedFullMath.sol';

// import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
// import {V2SwapRouter} from './modules/uniswap/v2/V2SwapRouter.sol';

/// @title Oracle library
/// @notice Provides functions to integrate with V3 pool oracle
library OracleLibrary {
/// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool
/// @param pool Address of the pool that we want to observe
/// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
/// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp
/// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp
function consult(address pool, uint32 secondsAgo)
internal
view
returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity)
{
require(secondsAgo != 0, 'BP');

uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = secondsAgo;
secondsAgos[1] = 0;

(int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) =
IUniswapV3Pool(pool).observe(secondsAgos);

int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
uint160 secondsPerLiquidityCumulativesDelta =
secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];

arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(secondsAgo)));
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(secondsAgo)) != 0)) arithmeticMeanTick--;

// We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128
uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max;
harmonicMeanLiquidity = uint128(secondsAgoX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32));
}

/// @notice Given a tick and a token amount, calculates the amount of token received in exchange
/// @param tick Tick value used to calculate the quote
/// @param baseAmount Amount of token to be converted
/// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
/// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
/// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
function getQuoteAtTick(
int24 tick,
uint128 baseAmount,
address baseToken,
address quoteToken
) internal pure returns (uint256 quoteAmount) {
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);

// Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
if (sqrtRatioX96 <= type(uint128).max) {
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
quoteAmount = baseToken < quoteToken
? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
: FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
} else {
uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
quoteAmount = baseToken < quoteToken
? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
: FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
}
}

/// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation
/// @param pool Address of Uniswap V3 pool that we want to observe
/// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool
function getOldestObservationSecondsAgo(address pool) internal view returns (uint32 secondsAgo) {
(, , uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0();
require(observationCardinality > 0, 'NI');

(uint32 observationTimestamp, , , bool initialized) =
IUniswapV3Pool(pool).observations((observationIndex + 1) % observationCardinality);

// The next index might not be initialized if the cardinality is in the process of increasing
// In this case the oldest observation is always in index 0
if (!initialized) {
(observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0);
}

secondsAgo = uint32(block.timestamp) - observationTimestamp;
}

/// @notice Given a pool, it returns the tick value as of the start of the current block
/// @param pool Address of Uniswap V3 pool
/// @return The tick that the pool was in at the start of the current block
function getBlockStartingTickAndLiquidity(address pool) internal view returns (int24, uint128) {
(, int24 tick, uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0();

// 2 observations are needed to reliably calculate the block starting tick
require(observationCardinality > 1, 'NEO');

// If the latest observation occurred in the past, then no tick-changing trades have happened in this block
// therefore the tick in `slot0` is the same as at the beginning of the current block.
// We don't need to check if this observation is initialized - it is guaranteed to be.
(uint32 observationTimestamp, int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128, ) =
IUniswapV3Pool(pool).observations(observationIndex);
if (observationTimestamp != uint32(block.timestamp)) {
return (tick, IUniswapV3Pool(pool).liquidity());
}

uint256 prevIndex = (uint256(observationIndex) + observationCardinality - 1) % observationCardinality;
(
uint32 prevObservationTimestamp,
int56 prevTickCumulative,
uint160 prevSecondsPerLiquidityCumulativeX128,
bool prevInitialized
) = IUniswapV3Pool(pool).observations(prevIndex);

require(prevInitialized, 'ONI');

uint32 delta = observationTimestamp - prevObservationTimestamp;

tick = int24((tickCumulative - prevTickCumulative) / int56(uint56(delta)));
uint128 liquidity =
uint128(
(uint192(delta) * type(uint160).max) /
(uint192(secondsPerLiquidityCumulativeX128 - prevSecondsPerLiquidityCumulativeX128) << 32)
);
return (tick, liquidity);
}

/// @notice Information for calculating a weighted arithmetic mean tick
struct WeightedTickData {
int24 tick;
uint128 weight;
}

/// @notice Given an array of ticks and weights, calculates the weighted arithmetic mean tick
/// @param weightedTickData An array of ticks and weights
/// @return weightedArithmeticMeanTick The weighted arithmetic mean tick
/// @dev Each entry of `weightedTickData` should represents ticks from pools with the same underlying pool tokens. If they do not,
/// extreme care must be taken to ensure that ticks are comparable (including decimal differences).
/// @dev Note that the weighted arithmetic mean tick corresponds to the weighted geometric mean price.
function getWeightedArithmeticMeanTick(WeightedTickData[] memory weightedTickData)
internal
pure
returns (int24 weightedArithmeticMeanTick)
{
// Accumulates the sum of products between each tick and its weight
int256 numerator;

// Accumulates the sum of the weights
uint256 denominator;

// Products fit in 152 bits, so it would take an array of length ~2**104 to overflow this logic
for (uint256 i; i < weightedTickData.length; i++) {
numerator += weightedTickData[i].tick * int256(uint256(weightedTickData[i].weight));
denominator += weightedTickData[i].weight;
}

weightedArithmeticMeanTick = int24(numerator / int256(denominator));
// Always round to negative infinity
if (numerator < 0 && (numerator % int256(denominator) != 0)) weightedArithmeticMeanTick--;
}

/// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last
/// @dev Useful for calculating relative prices along routes.
/// @dev There must be one tick for each pairwise set of tokens.
/// @param tokens The token contract addresses
/// @param ticks The ticks, representing the price of each token pair in `tokens`
/// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens`
function getChainedPrice(address[] memory tokens, int24[] memory ticks)
internal
pure
returns (int256 syntheticTick)
{
require(tokens.length - 1 == ticks.length, 'DL');
for (uint256 i = 1; i <= ticks.length; i++) {
// check the tokens for address sort order, then accumulate the
// ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out"
tokens[i - 1] < tokens[i] ? syntheticTick += ticks[i - 1] : syntheticTick -= ticks[i - 1];
}
}
}
2 changes: 1 addition & 1 deletion contracts/modules/uniswap/v3/V3SwapRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3
);
}

function computePoolAddress(address tokenA, address tokenB, uint24 fee) private view returns (address pool) {
function computePoolAddress(address tokenA, address tokenB, uint24 fee) internal view returns (address pool) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
pool = address(
uint160(
Expand Down
128 changes: 128 additions & 0 deletions contracts/modules/uniswap/v3/updated/updatedFullMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}

// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}

// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);

///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////

// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}

// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}

// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;

// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256

// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}

/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}
}
Loading

0 comments on commit c6614f7

Please sign in to comment.