From add4f7261137b6f448b2a493c02c2ecd387ba0e1 Mon Sep 17 00:00:00 2001 From: Tim Olson <> Date: Sun, 5 Nov 2023 16:50:06 -0400 Subject: [PATCH] price constraints working --- bin/build.sh | 2 +- src/OrderLib.sol | 7 +++++++ src/QueryHelper.sol | 35 ++++++++++++++++++++++++----------- src/UniswapSwapper.sol | 18 ++++++++++++++++-- test/MockEnv.sol | 7 ++++--- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/bin/build.sh b/bin/build.sh index 05217b1..02e7a7a 100755 --- a/bin/build.sh +++ b/bin/build.sh @@ -2,7 +2,7 @@ # this script requires the jq command $(sudo apt install jq) # first-pass build -forge build "$@" || exit 1 +forge build --force "$@" || exit 1 # calculate the Vault init code hash using the bytecode generated for Vault # shellcheck disable=SC2046 diff --git a/src/OrderLib.sol b/src/OrderLib.sol index 0421d84..d90588d 100644 --- a/src/OrderLib.sol +++ b/src/OrderLib.sol @@ -220,11 +220,14 @@ library OrderLib { // c.valueSqrtX96 = uint160(price * c.valueSqrtX96 / 2**96); int256 limit256 = int256(uint256(lc.valueSqrtX96)); if( lc.slopeSqrtX96 != 0 ) { + // todo cannot add square roots. limit256 += int256(block.timestamp - lc.time) * lc.slopeSqrtX96 / 2**96; if( limit256 < 0 ) limit256 = 0; } console2.log(limit256); + console2.log(price); + console2.log(lc.isAbove); uint160 limit = uint160(uint256(limit256)); // use <= and >= here because trading AT the limit results in 0 volume. price must exceed the limit. if( lc.isAbove && price <= limit || !lc.isAbove && price >= limit ) @@ -254,6 +257,10 @@ library OrderLib { - (status.order.amountIsInput ? status.trancheFilledIn[trancheIndex] : status.trancheFilledOut[trancheIndex]); // minus tranche fills console2.log('amount'); console2.log(amount); + console2.log('limit'); + console2.log(sqrtPriceLimitX96); + console2.log('price'); + console2.log(sqrtPriceX96); // order amount remaining require( (status.order.amountIsInput ? status.filledIn : status.filledOut) <= status.order.amount, 'OVERFILL' ); uint256 remaining = status.order.amount - (status.order.amountIsInput ? status.filledIn : status.filledOut); diff --git a/src/QueryHelper.sol b/src/QueryHelper.sol index 4de0d90..72bd94c 100644 --- a/src/QueryHelper.sol +++ b/src/QueryHelper.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: UNLICENSED -//pragma solidity =0.7.6; pragma solidity >=0.8.0; pragma abicoder v2; @@ -9,6 +8,8 @@ import "./OrderLib.sol"; import "./Vault.sol"; import "./VaultDeployer.sol"; import "./Factory.sol"; +import "forge-std/console2.sol"; + contract QueryHelper { uint8 constant public version = 1; @@ -47,28 +48,40 @@ contract QueryHelper { function getRoutes( address tokenA, address tokenB ) public view returns(RoutesResult[] memory routes) { // todo discover all supported pools + console2.log('getRoutes'); + console2.log(tokenA); + console2.log(tokenB); // here we find the highest liquidity pool for v2 and for v3 uint24[4] memory fees = [uint24(100),500,3000,10000]; uint24 uniswapV2Fee = 0; // uint128 uniswapV2Liquidity = 0; // address uniswapV2Pool = address(0); uint24 uniswapV3Fee = 0; - uint128 uniswapV3Liquidity = 0; + uint256 uniswapV3Liquidity = 0; address uniswapV3Pool = address(0); + IERC20 ercA = IERC20(tokenA); for( uint8 f=0; f<4; f++ ) { - IUniswapV3Pool pool = IUniswapV3Pool(Constants.uniswapV3Factory.getPool(tokenA, tokenB, fees[f])); - try pool.liquidity() returns (uint128 liquidity) { - // todo v2 - if( liquidity > uniswapV3Liquidity ) { - uniswapV3Fee = fees[f]; - uniswapV3Liquidity = liquidity; - uniswapV3Pool = address(pool); - } + console2.log('getPool..'); + uint24 fee = fees[f]; + IUniswapV3Pool pool = IUniswapV3Pool(Constants.uniswapV3Factory.getPool(tokenA, tokenB, fee)); + if( address(pool) == address(0) ) { + console2.log('no pool'); + continue; } - catch { + console2.log('gotPool.'); + console2.log(address(pool)); + // NOTE: pool.liquidity() is only the current tick's liquidity, so we look at the pool's balance + // of one of the tokens as a measure of liquidity + uint256 liquidity = ercA.balanceOf(address(pool)); + if( liquidity > uniswapV3Liquidity ) { + uniswapV3Fee = fee; + uniswapV3Liquidity = liquidity; + uniswapV3Pool = address(pool); } } uint8 routesCount = uniswapV3Fee > 0 ? 1 : 0 + uniswapV2Fee > 0 ? 1 : 0; + console2.log(uniswapV3Pool); + console2.log(uint(routesCount)); routes = new QueryHelper.RoutesResult[](routesCount); uint8 i = 0; // todo v2 diff --git a/src/UniswapSwapper.sol b/src/UniswapSwapper.sol index d292afa..5c73d92 100644 --- a/src/UniswapSwapper.sol +++ b/src/UniswapSwapper.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: UNLICENSED -//pragma solidity =0.7.6; pragma solidity >=0.8.0; pragma abicoder v2; @@ -15,7 +14,7 @@ library UniswapSwapper { address pool; address tokenIn; address tokenOut; - address recipient; + address recipient; // todo refactor back into bool outputToOwner to save space and constrain the money path uint24 fee; uint256 amount; uint160 sqrtPriceLimitX96; @@ -135,4 +134,19 @@ library UniswapSwapper { TransferHelper.safeApprove(params.tokenIn, address(Constants.uniswapV3SwapRouter), 0); } + // from https://github.com/ethereum/dapp-bin/pull/50/files + // the same logic as UniswapV2's version of sqrt + function sqrt(uint x) internal pure returns (uint y) { + // todo overflow is not possible in this algorithm, correct? we map wrap it in unchecked {} + if (x == 0) return 0; + else if (x <= 3) return 1; + uint z = (x + 1) / 2; + y = x; + while (z < y) + { + y = z; + z = (x / z + z) / 2; + } + } + } diff --git a/test/MockEnv.sol b/test/MockEnv.sol index 38887e0..1a4998a 100644 --- a/test/MockEnv.sol +++ b/test/MockEnv.sol @@ -41,7 +41,8 @@ contract MockEnv { inverted = address(COIN) > address(USD); token0 = inverted ? address(USD) : address(COIN); token1 = inverted ? address(COIN) : address(USD); - uint160 initialPrice = uint160(79228162514264337593543); // price 1e-12 = sqrt price 1e-6 = 2**96 / 10**6 +// uint160 initialPrice = uint160(79228162514264337593543); // price 1e-12 = sqrt price 1e-6 = 2**96 / 10**6 + uint160 initialPrice = uint160(79228162514264337593543950336000000); // $1.00 console2.log('if this is the last line before a revert then make sure to run forge with --rpc-url'); // if this reverts here make sure Anvil is started and you are running forge with --rpc-url pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, initialPrice)); @@ -138,8 +139,8 @@ contract MockEnv { return swapper.exactInputSingle(params); } - function price() public view returns (uint160 sqrtPrice) { - (sqrtPrice,,,,,,) = pool.slot0(); + function price() public view returns (uint160 sqrtPriceX96) { + (sqrtPriceX96,,,,,,) = pool.slot0(); } function swapToPrice(uint160 sqrtPriceLimitX96) public {