Light ModeLight
Light ModeDark

One Bug Per Day

One H/M every day from top Wardens

Checkmark

Join over 1125 wardens!

Checkmark

Receive the email at any hour!

Ad

priceToSqrtPriceX96 seems to behave in unintended ways when the price goes above uint64

mediumRecon Audits

I 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