Actual revert error message being swallowed by relayer

Hi,
When a transaction is being reverted due some failure, the revert message is not being sent. Instead there is this standard error which is being sent in all cases. It will be extremely useful to see the error message.

2021-09-22T09:52:35.510Z        0a63919d-7840-4d11-b3d6-d91b66a28991    INFO    Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"code":3,"data":"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000"}, method="call", transaction={"from":"0xcF2487870ed6f53d59984E70FD6ae76c50BA4c7F","to":"0x39dc7228A29Bf242F2a05f920a82b548733078ff","data":"0xdd    at processTicksAndRejections (internal/process/task_queues.js:95:5)on-rpc-provider.js:21:65):53)pc-provider.js:633:47)version=providers/5.4.5)
2021-09-22T09:52:35.511Z        0a63919d-7840-4d11-b3d6-d91b66a28991    ERROR   Invoke Error    {"errorType":"Error","errorMessage":"Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={\"code\":3,\"data\":\"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000\"}, method=\"call\", transaction={\"from\":\"0xcF2487870ed6f53d59984E70FD6ae76c50BA4c7F\",\"to\":\"0x39dc7228A29Bf242F2a05f920a82b548733078ff\",\"data\":\"0xddc632620000000000000000000000000000000000000000000000000000000000000000\",\"accessList\":null}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.4.5)\n    at Logger.makeError (/var/task/node_modules/@ethersproject/logger/lib/index.js:199:21)\n    at Logger.throwError (/var/task/node_modules/@ethersproject/logger/lib/index.js:208:20)\n    at checkError (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:118:16)\n    at DefenderRelayProvider.<anonymous> (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:633:47)\n    at step (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23)\n    at Object.throw (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:29:53)\n    at rejected (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:21:65)\n    at processTicksAndRejections (internal/process/task_queues.js:95:5)","stack":["Error: Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={\"code\":3,\"data\":\"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000\"}, method=\"call\", transaction={\"from\":\"0xcF2487870ed6f53d59984E70FD6ae76c50BA4c7F\",\"to\":\"0x39dc7228A29Bf242F2a05f920a82b548733078ff\",\"data\":\"0xddc632620000000000000000000000000000000000000000000000000000000000000000\",\"accessList\":null}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.4.5)","    at Logger.makeError (/var/task/node_modules/@ethersproject/logger/lib/index.js:199:21)","    at Logger.throwError (/var/task/node_modules/@ethersproject/logger/lib/index.js:208:20)","    at checkError (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:118:16)","    at DefenderRelayProvider.<anonymous> (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:633:47)","    at step (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23)","    at Object.throw (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:29:53)","    at rejected (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:21:65)","    at processTicksAndRejections (internal/process/task_queues.js:95:5)","    at processEvent (/var/task/index.js:73:11)","    at processTicksAndRejections (internal/process/task_queues.js:95:5)","    at async Runtime.exports.handler (/var/task/index.js:51:20)"]}
{"errorType":"Error","errorMessage":"Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={\"code\":3,\"data\":\"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000\"}, method=\"call\", transaction={\"from\":\"0xcF2487870ed6f53d59984E70FD6ae76c50BA4c7F\",\"to\":\"0x39dc7228A29Bf242F2a05f920a82b548733078ff\",\"data\":\"0xddc632620000000000000000000000000000000000000000000000000000000000000000\",\"accessList\":null}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.4.5)\n    at Logger.makeError (/var/task/node_modules/@ethersproject/logger/lib/index.js:199:21)\n    at Logger.throwError (/var/task/node_modules/@ethersproject/logger/lib/index.js:208:20)\n    at checkError (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:118:16)\n    at DefenderRelayProvider.<anonymous> (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:633:47)\n    at step (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23)\n    at Object.throw (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:29:53)\n    at rejected (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:21:65)\n    at processTicksAndRejections (internal/process/task_queues.js:95:5)","trace":["Error: Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={\"code\":3,\"data\":\"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000\"}, method=\"call\", transaction={\"from\":\"0xcF2487870ed6f53d59984E70FD6ae76c50BA4c7F\",\"to\":\"0x39dc7228A29Bf242F2a05f920a82b548733078ff\",\"data\":\"0xddc632620000000000000000000000000000000000000000000000000000000000000000\",\"accessList\":null}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.4.5)","    at Logger.makeError (/var/task/node_modules/@ethersproject/logger/lib/index.js:199:21)","    at Logger.throwError (/var/task/node_modules/@ethersproject/logger/lib/index.js:208:20)","    at checkError (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:118:16)","    at DefenderRelayProvider.<anonymous> (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:633:47)","    at step (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23)","    at Object.throw (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:29:53)","    at rejected (/var/task/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:21:65)","    at processTicksAndRejections (internal/process/task_queues.js:95:5)","    at processEvent (/var/task/index.js:73:11)","    at processTicksAndRejections (internal/process/task_queues.js:95:5)","    at async Runtime.exports.handler (/var/task/index.js:51:20)"]}END RequestId: 0a63919d-7840-4d11-b3d6-d91b66a28991
REPORT RequestId: 0a63919d-7840-4d11-b3d6-d91b66a28991  Init Duration: 0.08 ms  Duration: 10351.81 ms   Billed Duration: 10400 ms       Memory Size: 128 MB     Max Memory Used: 128 MB

:computer: Environment
Invoking a simple transaction via Relayer

:1234: Code to reproduce
Invoke harvest with incorrect strategist in the following smart contract.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Simulator {
    event Harvest(uint256 lpTokens);
     address private strategist;
    mapping(address => bool) private Owners;
    // Access restriction to registered rollover
    modifier onlyStrategist() {
        require(
        msg.sender == strategist,
        "Invalid caller"
        );
        _;
    }
    modifier onlyOwner() {
        require(
        Owners[msg.sender] == true,
        "Invalid caller"
        );
        _;
    }    
    constructor()
    {
        Owners[msg.sender] = true;
    }
    function harvest(uint256 _minLp) onlyStrategist public returns (uint256)
    {
        uint256 lpAmount = _minLp + 1;
        require(lpAmount >= _minLp, "Exceeds maximum slippage");
        emit Harvest(lpAmount);
        return lpAmount;
    }
    function setStrategist(address _strategist) external onlyOwner{
        strategist = _strategist;
    }
    function setOwner(address _owner) external onlyOwner{
        Owners[_owner] = true;
    }
}

Normally it shows the error message 'Invalid caller' as below. However, when called via relayer this error message is lost.

Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
execution reverted: Invalid caller { "originalError": { "code": 3, "data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e496e76616c69642063616c6c6572000000000000000000000000000000000000", "message": "execution reverted: Invalid caller" } }

Hey @Bhanu_Ratnakaram! Thanks for reporting this. Can you share whether you're using the vanilla Relay class from defender-relay-client, or the ethers or web3js integration? Also, if you're using ethers, are you using the DefenderRelaySigner, or also the DefenderRelayProvider? If you are willing to share your Autotask code (at least the setup), that would help!

sure, here you go

  gasLimit = GAS_LIMIT / 1;
  relayer = { apiKey: API_KEY, apiSecret: apiSecretDecrypted };
  provider = new DefenderRelayProvider(relayer);
  signer = new DefenderRelaySigner(relayer, provider, { speed: TXN_SPEED });
  ethAdapter = new EthersAdapter({
    ethers,
    signer
  });  
  strategyContract = new ethers.Contract(
    STRATEGY_ADDRESS,
    StrategyABI,
    provider
  );
  await strategyContract.connect(signer).harvest(0, {gasLimit});

Thanks @Bhanu_Ratnakaram! We'll take a look and get back to you. Could you clarify what is the EthersAdapter class in your snippet?

Also, what network is this in?

Also, could you clarify how you are sending the tx in this case? And does the tx get sent in either case?

Hi @spalladino , thanks for your response.
I faced this issue with rinkeby and mainnet networks. EthersAdapter is the class provided by gnosis. We are using gnosis multisig wallet for strategist.

i am sending this on remix. It give the error even before siging the transaction with metamask

1 Like

I'm facing a similar issue.

I'm using Defender Autotasks to relay meta-transactions.

My forwarding contract verification view function is using EIP-3668 OffchainLookup custom errors.

I'm treating verification like a preflight - if the meta-transaction signature is valid, the contract reverts with a custom error per the spec that includes args that tell the client how to get an offchain proof and how to then submit the transaction.

    /// @notice Submit a meta-tx request and signature to check validity and receive
    ///         a response with data useful for fetching a trusted proof per EIP-3668.
    /// @dev Per EIP-3668, a valid signature will cause a revert with useful error params.
    function preflight(IForwardRequest.ForwardRequest calldata req, bytes calldata signature) public view {
        // If the signature is valid for the request and state, the client will receive
        // the OffchainLookup error with parameters suitable for an https call to a JSON
        // RPC server.

        if (verifyRequest(req, signature)) {
            revert OffchainLookup(
                address(this),
                urls,
                abi.encode(req.from, _nonces[req.from], req.nftContract, req.tokenId),
                this.executeWithProof.selector,
                abi.encode(req, signature)
            );
        }
    }

autotask uses ethers with defender relayer:

  const credentials = { ...event };
  const provider = new DefenderRelayProvider(credentials);
  const signer = new DefenderRelaySigner(credentials, provider, {});

  const forwarder = new Contract(Forwarder.address, Forwarder.abi, signer);

  // Preflight transaction
  const { sender, url, callData, callbackFunction, extraData } =
    await preflight(forwarder, request, signature);

here's the key function:

async function preflight(forwarder: Contract, request: any, signature: string) {
  // Validate request on the forwarder contract

  try {
    await forwarder.preflight(request, signature, {
      gasLimit: utils.hexlify(2100000),
    });
  } catch (e) {
    console.warn('CAUGHT', e);
    if (e.code === utils.Logger.errors.CALL_EXCEPTION) {
      if (e.errorName === 'OffchainLookup') {
        const args = {
          sender: e.args.sender,
          url: e.args.urls[0],
          callData: e.args.callData,
          callbackFunction: e.args.callbackFunction,
          extraData: e.args.extraData,
        };

        return args;
      }
    }
  }
  throw new Error(`Preflight did not revert`);
}

I'm providing a gas limit, have tried BigNumber and number.

I also have made other calls to the contract from the autotask, so I know address / abi / network are correct.

I also have some integrative tests in hardhat that suggest ethers should support this.

Happy to try one of the other approaches, but get a little lost in the weeds for populating a transaction to use with send thats already a populated transaction for the implementation contract?

My autotask logs report this for the above code. Instead of catching the custom revert error, it gets swallowed up. We expect an OffchainLookup error in a revert here and need access to it.

Error
Preflight did not revert
Logs
AUTOTASK START
2022-02-22T04:22:59.273Z	INFO	HANDLING=================================
2022-02-22T04:23:01.898Z	WARN	CAUGHT Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"code":3,"data":"0x556f183000000000000000000000000049a3611559b

I seem to have this working by using a JsonRpcProvider via Infura in place of the Defender Relayer Provider.

Hey OZ frens! am back hoping to simplify my autotask code with some recent ethers updates.

Previously, the DefenderRelayerProvider would seemingly not catch a custom OffchainLookup error.

To work around that, I set up my own InfuraProvider with an API key from secrets. This provider makes the initial read call that results in an OffchainLookup error.

Currently we handle that error ourselves, and use the error params to make an HTTPS API call, later submitting a transaction with that call's result via DefenderRelayerProvider.

ethers@5.6.1 adds native support for CCIP Read / EIP3668. My goal now is to replace our error handling functionality and instead use the standards-based OffchainLookup that's now available in ethers.

I think my main issue is not really understanding whether the DefenderRelayerProvider uses its own flavor of ethers? I of course want to use that provider so i can manage keys and funds via Defender.

But if that provider doesn't support CCIP Read then I think I need to maintain my current approach?

Here's our current autotask source code - https://github.com/0xEssential/essential-autotasks/blob/4a75e4d308d76e0a7f45a4afdaade719ee5a1e5b/essential-nft-meta-tx/src/index.ts

Hi @sbauch

I think my main issue is not really understanding whether the DefenderRelayerProvider uses its own flavor of ethers? I of course want to use that provider so i can manage keys and funds via Defender.

DefenderRelayProvider imports from ethersproject/providers. The current version is set to ^5.0.5.
You can find the source code and version over here:

I'm still to dive into EIP3668. Meanwhile, I hope that answers your question.