I am currently trying to perform a low-level call in Solidity using staticcall to invoke a function in another contract. My code looks like this:


    struct QuoteExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint24 fee;
        uint160 sqrtPriceLimitX96;
    function getQuote(address quoterV2, QuoteExactInputSingleParams memory params) internal view returns (uint256 amountOut) {
        bytes4 functionSignature = bytes4(keccak256("quoteExactInputSingle(address,address,uint256,uint24,uint160)"));
        bytes memory data = abi.encodeWithSelector(functionSignature, params);
        (bool success, bytes memory returnData) = quoterV2.staticcall(data); // Using `staticcall` as it's a view-only call
        if (!success) {
            // Decode the error message from the low-level call and revert with it
            revert(string(abi.decode(returnData, (bytes))));
        (amountOut, , , ) = abi.decode(returnData, (uint256, uint160, uint160, uint160));

    function getAmountsOut(
        uint256[] memory dexIdents,
        address[] memory path,
        uint24[] memory poolFees,
        uint256 amountIn
    ) public view returns (uint256[] memory amounts) {
        require(path.length >= 2, "getSwapPath: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[0] = amountIn;
        for (uint256 i; i < path.length - 1; i++) {
            amounts[i + 1] = getQuote(
                    tokenIn: path[i],
                    tokenOut: path[i + 1],
                    amountIn: amounts[i],
                    fee: poolFees[i],
                    sqrtPriceLimitX96: 0

When I call the getQuote function, I encounter the error: "Transaction reverted without a reason string."

So far, I've tried:

Checking the quoterV2 address to ensure it's correct. Verifying the function signature derived with keccak256 to make sure it's accurate.

Ensuring the ABI encoding of the parameters is correct, especially since I'm using a struct.

Yet, I still get the same error. I'm starting to wonder if the issue might be related to state changes in the QuoterContract when using staticcall.


  • It should revert with the quote. It will use call instead of static call, since it calls swap which is state modifying. See Quoter Limitation. uniswapv3book.com/docs/milestone_2/quoter-contract
    – Maka
    Commented Sep 2, 2023 at 11:56
  • thanks @Maka, As per the Quoter Limitation on uniswapv3book.com, it's clear that state modification is a possibility, which means a standard call is necessary.(Thank you for pointing this out!) However, with call, the view-only nature isn't guaranteed.
    – R4C0N
    Commented Sep 2, 2023 at 12:07
  • Uniswap docs say: These functions are not gas efficient and should not be called on chain. Instead, optimistically execute the swap and check the amounts in the callback.
    – Maka
    Commented Sep 2, 2023 at 12:14

2 Answers 2


Yes, if you try to call something in another contract with staticcall, and that contract attempts to change state then an exception gets thrown and the call fails

An exception will be thrown if the code:

  • tries to deploy a new contract
  • emits logs
  • makes state changes
  • anything that uses call() with a value (sending eth)

Thank you to everyone who offered guidance and suggestions! I've successfully written the getAmountsOut function, and it's functioning as expected. Interestingly, it provides about 1-2% more output compared to the V2 version, which aligns with the behavior of the GetAmountsOut from V2, lol.

    function getAmountsOut(
        address[] memory pool,
        address[] memory path,
        uint256 amountIn
    ) public view returns (uint256[] memory amounts) {
        require(path.length >= 2, "getSwapPath: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[0] = amountIn;
        for (uint256 i; i < pool.length; i++) {
            IUniswapV3Pool IPool = IUniswapV3Pool(pool[i]);
            (uint160 sqrtPriceX96, , , , , ,bool unlocked ) = IPool.slot0();
            require(unlocked, "Pool is Locked!");
            uint256 squaredPrice = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96);
            amounts[i + 1] = FullMath.mulDiv(
                (path[i] == IPool.token0()) ? squaredPrice :FixedPoint96.Q96, 
                (path[i] == IPool.token0()) ? FixedPoint96.Q96 : squaredPrice
            require(amounts[i + 1] != 0, "Output Zero");
        return amounts;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.