1Inch Swap error, via Solidity. "Transaction reverted without a reason"

Hi, I’m trying to integrate with 1Inch on-chain, using solidity. I’m using the mainnet forking capabilities of Hardhat to test the integration. I have successfully been able to swap two tokens if I call the one inch contract from an EOA in my tests. However, calling the same function from my contract, under what I believe is the same circumstances, is resulting in Transaction reverted without a reason, which is maddening.

I am first calling the “getExpectedReturn” function, as this article and the 1inch docs seem to indicate. And indeed that works for my test EOA. I have approved the one inch contract for the ERC20’s in question from my smart contract. And I’m also fairly sure the swap part within the one inch contract itself is working, because I was at one point getting the “the return amount is < min specified”, but I just set the min (what they call "return amount) to be 1, and that is when I started getting the “transaction reverted without a reason” bug.

I have also tried changing my gas settings within Hardhat, to no avail, and tried verbose logging, which doesn’t seem to help much.

Any ideas would be very much appreciated! Thanks so much!

:computer: Environment

Hardhat: ^2.02
Using truffle contracts in my tests.

:memo:Details

:1234: Code to reproduce

Here is the relevant smart contract code:

  function payWithSwap(
    address creditLineAddress,
    uint256 amount,
    IERC20 fromToken,
    uint256 returnAmount,
    uint256[] memory distribution
  ) external onlyAdmin {
    address oneInch = config.oneInchAddress();
    fromToken.approve(oneInch, uint256(-1));
    fromToken.transferFrom(msg.sender, address(this), amount);
    swap(fromToken, IERC20(config.usdcAddress()), amount, returnAmount, distribution);
  }

  function swap(
    IERC20 fromToken,
    IERC20 toToken,
    uint256 amount,
    uint256 returnAmount,
    uint256[] memory distribution
  ) internal {
    config.getExchange().swap(
        fromToken,
        toToken,
        amount,
        10,
        distribution,
        0
    );
  }

And my test code looks like the following:

      const expectedReturn = await oneSplit.getExpectedReturn(busd.address, usdc.address, bigVal(rawAmount), 10, 0, {from: bwr})
      await expectAction(() => {
        return bwrCon.payWithSwap(
          cl.address,
          bigVal(rawAmount),
          busdAddress,
          expectedReturn.returnAmount,
          expectedReturn.distribution,
          {from: bwr}
        )
      }).to.succeed()
2 Likes

It looks fine, could you please paste the failed transaction hash and which test net do you use?

1 Like

I’m not doing this on a testnet. I’m doing it on a “local” network (so thus no transaction hash to share). But I’m doing it using Hardhat’s mainnet forking, which automatically pulls data from mainnet in order to simulate interacting with those contracts. I have confirmed that this works as expected, by using Tenderly to simulate my “getExpectedReturn” call at the specific block number that I am pinning my Hardhat local network to. I got identical results in both Tenderly and on my forked local mainnet. Also, as mentioned, doing a swap works when using a local EOA, but just does not seem to work for my smart contract. Thanks for any help or ideas!

1 Like

Hi @blakewest,

You may want to ask in the 1Inch Telegram chat: https://1inch.exchange/

Thanks. Yeah I thought of that myself yesterday, and asked around. No real help yet, but will continue to investigate!

2 Likes

@blakewest, did you find a solution to this? I’m having the same issue.

1 Like

Actually, yes, I did find a solution. I had to do the low-level abi.encodeSignature call. So my ending code looked like this…

  function swapOnOneInch(
    address fromToken,
    address toToken,
    uint256 originAmount,
    uint256 minTargetAmount,
    uint256[] memory exchangeDistribution
  ) internal {
    bytes memory _data = abi.encodeWithSignature(
      "swap(address,address,uint256,uint256,uint256[],uint256)",
      fromToken,
      toToken,
      originAmount,
      // Set to 99% of the minTargetAmount, to give us a
      // 1% price/slippage buffer
      minTargetAmount.mul(99).div(100),
      exchangeDistribution,
      0
    );
    invoke(config.oneInchAddress(), _data);
  }

  /**
   * @notice Performs a generic transaction.
   * @param _target The address for the transaction.
   * @param _data The data of the transaction.
   * Mostly copied from Argent:
   * https://github.com/argentlabs/argent-contracts/blob/develop/contracts/wallet/BaseWallet.sol#L111
   */
  function invoke(address _target, bytes memory _data) internal returns (bytes memory) {
    // External contracts can be compiled with different Solidity versions
    // which can cause "revert without reason" when called through,
    // for example, a standard IERC20 ABI compiled on the latest version.
    // This low-level call avoids that issue.

    bool success;
    bytes memory _res;
    // solhint-disable-next-line avoid-low-level-calls
    (success, _res) = _target.call(_data);
    if (!success && _res.length > 0) {
      // solhint-disable-next-line no-inline-assembly
      assembly {
        returndatacopy(0, 0, returndatasize())
        revert(0, returndatasize())
      }
    } else if (!success) {
      revert("VM: wallet invoke reverted");
    }
    return _res;
  }

Note: I also had to do these low level calls for transferring tokens through Tether. I don’t know for sure, but my hunch is that it has to do with the fact that my contracts are compiled with 0.6.12, but Tether, OneInch and others are compiled with 0.5.0.

Hope that’s helpful!

2 Likes

Thank you very much!

1 Like