Invalid request in MinimalForwarder

I am having exact issues as this post:

After following the metatransactions workshop and having xDAI network successfully submit a transaction, I tried using mumbai testnet as the network instead. I ended up getting the same error as the post above: "the autotask fails verification on the forwarder."

:computer: Environment

:memo:Details

I am aware of the solution that was given in the post above, and although its been resolved in https://github.com/OpenZeppelin/openzeppelin-contracts/blob/1b27c13096d6e4389d62e7b0766a1db53fbb3f1b/contracts/metatx/MinimalForwarder.sol
I am still getting the same error.

:1234: Code to reproduce

Cloned entire repo and did not touch anything except:

  • created a mumbai relay.
  • changed the network in package.json.
  • and also changed athe network id in the app as suggested.

Maybe is there a way to check if the ERC712 struct is incompatible with what we have in the signer?
Thanks!

Hey @Richard_Zhang, can I ask you to try with any of the Ethereum testnets instead (goerli, ropsten, rinkeby, or kovan)? I want to rule out any issues related to the size of the chain ID, since for xDAI and the testnets mentioned above the v value according to EIP155 fits within a byte, as opposed to with Mumbai.

Hey @spalladino , thanks for the reply. I tried with rinkeby testnet with a rinkeby relayer. Still didn't work.

Here was what I did step by step:

  1. Changed all parameters listed above
  2. ran yarn
  3. yarn deploy
  4. yarn sign (needed to create a new folder tmp)
  5. yarn relay (presented the Invalid request error)

I presented a github repo so feel free to reproduce it. I left the .env and everything in (except my private key and Rinkeby Node URL), so you can use the exact relayer to reproduce the error.

My only other clue is maybe my node url is making the transaction request invalid? I am using Alchemy as my node service. Thanks!

Hello @spalladino , I have tried xDAI and it worked, but rinkeby and mumbai did not. Gave this error:

Error: call revert exception (method="verify((address,address,uint256,uint256,uint256,bytes),bytes)", errorSignature=null, errorArgs=[null], reason=null, code=CALL_EXCEPTION, version=abi/5.0.12)

Does anyone know what this error is concerning? Thanks!

Hey @Richard_Zhang, that CALL_EXCEPTION from ethers.js most likely means that the eth_call to verify on the MinimalForwarder reverted. It could be due to a malformed signature, or due to an error in the contract you're calling.

I take it that 0x289A975C4Ce1FF6307B229FDC2018cCfC3F34bf5 is the address where you deployed the minimal forwarder on Rinkeby or Mumbai, right?

Could you verify the contract, to check whether there are any issues with the deployed source? Also, can you share a payload/signature you're testing with, so I can try to reproduce it? Thanks!

Hello @spalladino, apologies, I deployed a fresh set of contracts.

MinimalForwarder: https://mumbai.polygonscan.com/address/0xa5d6c82b0cB53D8Fd4cD7205d63e55b07A570799

Registry:

Here is the payload in tmp/request.json:

{
  "signature": "0xdb3525cee752ce0ec4219eb92039da44efdf61bb7f3502afaceb87e6329cb6411f858d648b8b19b7886d55a286ed7d489c271d2175295a6ec7fb419a5345cfcf1b",
  "request": {
    "value": 0,
    "gas": 1000000,
    "nonce": "0",
    "to": "0x8F090f675CdB6fE3CfCB2Adf488D4E9437D676E1",
    "from": "0x751146151EB6eAa2F723fc11060C0A8e5fEA3976",
    "data": "0xf2c298be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000097369676e2d746573740000000000000000000000000000000000000000000000"
  }
}

after making commands "yarn deploy", "yarn sign", I get this error after "yarn relay",

Error: call revert exception (method="verify((address,address,uint256,uint256,uint256,bytes),bytes)", errorSignature=null, errorArgs=[null], reason=null, code=CALL_EXCEPTION, version=abi/5.0.12)

I pushed the latest to github so hopefully its enough to reproduce the error! Thanks!

Hey @Richard_Zhang, I found the issue: there are two copies of OpenZeppelin Contracts on the repo you shared.

The MinimalForwarder being imported by the Registry contract was the old-ish openzeppelin-solidity, but that one defines is EIP712 domain separator using the domain name GSNv2 Forwarder (see here) as opposed to MinimalForwarder (as expected by the signer here). This was causing the verification step of the signature to fail.

This issue is carried over from the OpenZeppelin/workshops repo, where I updated the 712 domain name in the script but failed to bump the Contracts library version in the deps to account for it. I've just pushed a fix, along with a new verify script that will test the signature in the local tmp/request.json against the deployed minimal forwarder.

Thanks for your help in locating this issue!

Works! @spalladino thanks for pointing that out! I guess the issue was with the contract itself instead of how i was setting up the scripts, or that it was mumbai.

Antoher roadblock I ran into was when I tried verifying the signature of my own UpgradeableProxy Contract function call. I am getting an invalid request yet again with this change. Do you think EIP712 struct is different from upgradeable contracts in @openzeppelin/upgradeable-contracts package? Seems UpgradeableMinimalForwarder also has the same domain separator MinimalForwarder (https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/metatx/MinimalForwarderUpgradeable.sol). Is it even possible to use meta transactions for upgradeable contracts?

Heres my package.json, where I just added @openzeppelin/upgradeable-contracts:

{
  "name": "workshop-meta-txs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "local:start": "hardhat node",
    "local:deploy": "hardhat run scripts/deploy.js --network local",
    "local:sign": "hardhat run scripts/sign.js --network local",
    "deploy": "hardhat run scripts/deploy.js --network mumbai",
    "sign": "hardhat run scripts/sign.js --network mumbai",
    "events": "hardhat run scripts/events.js --network mumbai",
    "verify": "hardhat run scripts/verify.js --network mumbai",
    "build": "rollup -c",
    "invoke": "bash ./scripts/invoke.sh",
    "upload": "yarn build && node scripts/upload.js",
    "relay": "node scripts/relay.js",
    "test": "NODE_ENV=test hardhat test"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.1",
    "@nomiclabs/hardhat-waffle": "^2.0.1",
    "@openzeppelin/contracts": "^4.4.1",
    "@rollup/plugin-commonjs": "^17.1.0",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^11.1.1",
    "builtin-modules": "^3.2.0",
    "chai": "^4.3.0",
    "chai-as-promised": "^7.1.1",
    "create-react-app": "^4.0.2",
    "defender-autotask-client": "^1.4.1",
    "dotenv": "^8.2.0",
    "ethereum-waffle": "^3.2.2",
    "ethers": "^5.0.30",
    "hardhat": "^2.0.8",
    "rollup": "^2.38.5"
  },
  "dependencies": {
    "@openzeppelin/contracts-upgradeable": "^4.4.1",
    "defender-relay-client": "^1.3.1",
    "eth-sig-util": "^3.0.1"
  }
}

I also updated the branch master in my repo for reproducing purposes. Thanks so much!

Yep, an upgradeable minimal forwarder should work just as a regular one. Note, however, that you need to manually initialize the contract, otherwise it won't work, since the initializer takes care of setting up the domain separator. You'll need to extend from the contract and expose the initializer as an external function, so you can call it during deployment.

This is not needed in non-upgradeable contracts because the constructor runs automatically upon deployment, but that's not the case with initializers.

Hey @spalladino, I've tried extending my contract with MinimalForwarderUpgradeable, here is my initialize function:

    function initialize(
        string memory baseURI,
        address forwarder,
        address _admin
    ) public initializer {
        __Ownable_init_unchained();
        __ERC1155_init_unchained(baseURI);
        __ERC2771Context_init_unchained(forwarder);
        __MinimalForwarder_init();
        admin = _admin;
        _currentTokenID = 0;
    }

And I believe __MinimalForwarder_init(); initializes the EIP712 initializer. But it still gives an invalid error :confused:. Here is how I deploy my contracts as well:

async function main() {
  const forwarder = await deploy('MinimalForwarderUpgradeable');
  const MinimalForwarder = forwarder.address
  console.log('Deploying ActiveBatch...');
  const activeBatch = await ethers.getContractFactory('ActiveBatch');
  console.log(activeBatch);
  const Proxycontract = await upgrades.deployProxy(activeBatch, ["uri", MinimalForwarder, "0x8422B7530f55E3F9FDb3f13950B24895c56E63a2"], { initializer: 'initialize' });
  await Proxycontract.deployed();
  console.log(Proxycontract)
  const provider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.g.alchemy.com/v2/{KEY}", 80001);
  const currentImplAddress = await getImplementationAddress(provider, Proxycontract.address);
  writeFileSync('deploy.json', JSON.stringify({
    MinimalForwarder: MinimalForwarder,
    activeBatch: currentImplAddress,
    ActiveBatch: Proxycontract.address
  }, null, 2));
  console.log('Proxycontract Address:', Proxycontract.address);
  console.log('ActiveBatch Address:', currentImplAddress);
  console.log('Forwarder Address:', MinimalForwarder);
}

Please let me know if there is anything else I am missing! I've updated the github so it is reproducible thanks!

Thanks for keeping the Github updated, it helps a lot in troubleshooting! The issue here is that, while you've added the initialize function to the MinimalForwarderUpgradeable, you are not calling it, so the contract (and the ERC712 domain) remains uninitialized.

If you want to deploy the forwarder as an upgradeable contract, you should be using upgrades.deployProxy including the call to initialize with { initializer: 'initialize' } as you do with the ActiveBatch contract.

If not, you should directly deploy the MinimalForwarder from openzeppelin-contracts (the vanilla version of the library), and just rely on the constructor.

@spalladino, Thank you! Worked out finally, and time to end this post. Thank you so much for the help!

1 Like

On the contrary, thank you for your assistance in troubleshooting the issue @Richard_Zhang! This also helps a lot in identifying some common pain points when using upgradeable contracts.