priceToSqrtPriceX96 seems to behave in unintended ways when the price goes above uint64
mediumI have adapted code that I wrote from here: https://github.com/GalloDaSballo/Lp-Script
I have ran into similar issues when setting up an LP for UniV3
solidity// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test} from "forge-std/Test.sol"; import {console2} from "forge-std/console2.sol"; import {LiquidityProvider, UniV3Translator} from "../src/LiquidityProvider.sol"; import {IUniV3Factory, IV3NFTManager, IUnIV3Pool} from "../src/interfaces/IUni.sol"; import {ICurveFactory, ICurvePool} from "../src/interfaces/ICurve.sol"; import {ERC20} from "../src/mocks/ERC20.sol"; import {UniV3Translator} from "ebtc-amm-comparer/UniV3Translator.sol"; import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol"; contract TranslatorTests is Test { uint256 constant DECIMAL_PRECISION = 1e18; function priceToSqrtPriceX96(uint256 _price) public pure returns (uint160 sqrtPriceX96) { if (_price > (1 << 64)) { sqrtPriceX96 = uint160(Math.sqrt(_price / DECIMAL_PRECISION) << 96); } else { /// @audit this assumes the token 1 is Bold / ETH and that it is price is below that sqrtPriceX96 = uint160(Math.sqrt((_price << 192) / DECIMAL_PRECISION)); } } function test_equivalence(uint256 token1, uint256 token0) public { deployer = new LiquidityProvider(); UniV3Translator translator = deployer.translator(); // This ensures we always hit the "below u64 on getSqrtPriceX96GivenRatio" // But once we add more precision (1e18), this can change, so the other test should compare values properly require(token1 < type(uint64).max); require(token0 < type(uint64).max); require(token0 < 10e18); require(token1 < 200_000e18); uint256 price = token1 * 1e18 / token0; uint256 referenceVal = translator.getSqrtPriceX96GivenRatio(token1, token0); uint256 other = priceToSqrtPriceX96(price); if(referenceVal > other) { optimize_test_equivalence_reference = int256(referenceVal - other); } else { optimize_test_equivalence_other = int256(other - referenceVal); } } int256 public optimize_test_equivalence_reference; int256 public optimize_test_equivalence_other; }
Results
pythonā¾ [PASSED] Optimization Test: TranslatorTests.optimize_test_equivalence_reference() Test for method "TranslatorTests.optimize_test_equivalence_reference()" resulted in the maximum value: 79228162511981885646111530437 [Call Sequence] 1) TranslatorTests.test_equivalence(uint256,uint256)(2249999999999999999, 589824) (block=2, time=2, gas=12500000, gasprice=1, value=0, sender=0x10000) [Execution Trace] => [call] TranslatorTests.test_equivalence(uint256,uint256)(2249999999999999999, 589824) (addr=0xA647ff3c36cFab592509E13860ab8c4F28781a66, value=0, sender=0x10000) => [creation] LiquidityProvider.constructor() (addr=0x54919A19522Ce7c842E25735a9cFEcef1c0a06dA, value=0, sender=0xA647ff3c36cFab592509E13860ab8c4F28781a66) => [creation] UniV3Translator.constructor() (addr=0x8085Fe5dF5e137F474201f8E79b65FcB9D4334ED, value=0, sender=0x54919A19522Ce7c842E25735a9cFEcef1c0a06dA) => [return ()] => [return ()] => [call] LiquidityProvider.translator()() (addr=0x54919A19522Ce7c842E25735a9cFEcef1c0a06dA, value=<nil>, sender=0xA647ff3c36cFab592509E13860ab8c4F28781a66) => [return (0x8085Fe5dF5e137F474201f8E79b65FcB9D4334ED)] => [call] UniV3Translator.getSqrtPriceX96GivenRatio(uint256,uint256)(2249999999999999999, 589824) (addr=0x8085Fe5dF5e137F474201f8E79b65FcB9D4334ED, value=<nil>, sender=0xA647ff3c36cFab592509E13860ab8c4F28781a66) => [return (154742504910672534328003304686517214)] => [return ()] [Optimization Test Execution Trace] [Execution Trace] => [call] TranslatorTests.optimize_test_equivalence_reference()() (addr=0xA647ff3c36cFab592509E13860ab8c4F28781a66, value=0, sender=0x10000) => [return (79228162514229950370230467550)]
Mitigation
Ensure that every price you pass to the function is below uint64.max
