address.functionCallWithValue erroring out despite accurate abi data values

Hey folks,
I've been experimenting with calling uniswapV3 using encoded data and the functionCallWithValue() function doesn't seem to be working properly for me. I'm sure I'm doing something wrong, but could someone please help me out?

I've deployed 2 mock tokens called Weth and USDC on Arbitrum testnet, and initialized the uniswap pool and added liquidity for those two specific tokens. I can provide additonal information on how I initialized and added liquidity to those pools if necessary, but I don't think that's the problem.

I'm experimenting using the functionCallWithValue because eventually in my project I need the simpleSwap contract to cause another contract to call the uniswap trade function
I also have the transaction hash of the failed transaction on arbiscan here: https://goerli.arbiscan.io/tx/0x6f12b37f2683ff73ccf6efde529195e5f22b40d98032af49bdfcc2e5fa024b0e (the affected contract is simpleSwapTest.sol)

:1234: Code to reproduce

SimpleSwapTest.sol

pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";

import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol";
import { IWETH } from "../../../interfaces/external/IWETH.sol";
import { ISwapRouter } from  "../../../interfaces/external/ISwapRouter.sol";


import "hardhat/console.sol";

contract SimpleSwapTest {
    using Address for address;

    string internal constant SWAP_EXACT_OUTPUT = "exactOutputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))";
    string internal constant SWAP_EXACT_INPUT = "exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))";

    IWETH public weth;                                      // Weth contract address
    IERC20 public usdc;

    address public uniswapRouter;

    constructor(
        IWETH _weth,
        IERC20 _usdc,
        address _uniswapRouter
    )
        public
    {
        weth = _weth;
        usdc = _usdc;
        uniswapRouter = _uniswapRouter;
    }

    function trade(
        address _sellComponent,
        address _buyComponent,
        uint256 _amount
    ) public {
        uint256 approveAmount = 2**256 - 1;
        IERC20(weth).approve(uniswapRouter, approveAmount);
        IERC20(usdc).approve(uniswapRouter, approveAmount);
            bytes memory callData = abi.encodeWithSignature(
                SWAP_EXACT_OUTPUT,
                ISwapRouter.ExactInputSingleParams(
                    _sellComponent,
                    _buyComponent,
                    3000,
                    address(this),
                    block.timestamp,
                    _amount,
                    1000,
                    0
                )
            );
            uniswapRouter.functionCallWithValue(callData, 0);
        
    }
}

JS testing script

const simpleSwapAddress = "0x807B64A0F3dBD76B6D36EE0C809cE8C175a815Bf"; //arbitrum goerli
const simpleSwapModuleInterface = await hre.ethers.getContractAt("SimpleSwapTest", simpleSwapAddress);
await simpleSwapModuleInterface.trade(wethAddress, usdcAddress, true, 
        ethers.utils.parseEther("1"));
console.log('simple swap trade completed');

:computer: Environment

Using: Hardhat on Arbitrum Goerli (testnet).

I'm pretty sure that you don't need the "double parenthesis" here.

At least in terms of a proper function-selector string, there should be a single parenthesis on each side.

1 Like

I've tried it with just the single parenthesis but it's still giving me the same error unfortunately. Also the 2 parentheses are needed because the function call takes in a struct and structs are represented with two parentheses

There are a bunch of other things that look way off in your code:

  1. Why would you approve both tokens, when only the token that goes in needs to be approved?
  2. Why are you approving weth and usdc rather than _sellComponent and _buyComponent?
  3. Why are you approving an amount of 2**256 - 1 rather than your amountInMaximum?
  4. Why are you limiting your amountInMaximum to such a low value as 1000?
  5. Are you sure that passing 0 for sqrtPriceLimitX96 is considered valid?

Note that 1000 wei is equivalent to 0.01 usdc (~1 cent) and 1e-14 weth (way less than 1 cent).
So if your _amount value (desired output amount) is even remotely large, then this transaction will likely revert on exceeding the maximum input amount permitted.

Yeah, this is a test contract that I just want to be able to get working with Uniswap. Right now it's not swapping at all, which is concerning. So for 1 and 2 I was just trying to make sure that there was no approval issues. Currently, I'm trying this to see if omitting the functionCallWithValue would work, and if the issues are with the params. It's still not working:

IERC20(_sellComponent).approve(uniswapRouter, approveAmount);
ISwapRouter swapRouter =
                ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
            ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
            .ExactInputSingleParams({
                tokenIn: _sellComponent,
                tokenOut: _buyComponent,
                // pool fee 0.3%
                fee: 3000,
                recipient: msg.sender,
                deadline: block.timestamp + 15,
                amountIn: 1000000000000,
                amountOutMinimum: 1,
                // NOTE: In production, this value can be used to set the limit
                // for the price the swap will push the pool to,
                // which can help protect against price impact
                sqrtPriceLimitX96: 0
            });
            amountOut = swapRouter.exactInputSingle(params);

So the problem is not related to your original question (as depicted in the title), correct?

It is. Because calling the function directly works, but calling it with functionCallWithValue doesn't, which confuses me

IV3SwapRouter swapRouter02 = IV3SwapRouter(uniswapRouter);
            IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter
            .ExactInputSingleParams({
                tokenIn: _sellComponent,
                tokenOut: _buyComponent,
                fee: 3000,
                recipient: address(this),
                amountIn: 10**16,
                amountOutMinimum: 1,
                sqrtPriceLimitX96: 0
            });
            //this is working
            //amountOut = swapRouter02.exactInputSingle(params);

            bytes memory callData = abi.encodeWithSignature(
                SWAP_EXACT_INPUT,
                params
            );

            //this is not
            address(swapRouter02).functionCallWithValue(callData, 0);