Permit method – execution reverted

:1234: Code to reproduce

Hello!

I've created test contract with just erc20 and permit:

// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract MyToken is ERC20, ERC20Permit {
    constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
        _mint(msg.sender, 10000 * 10 ** decimals());
    }
}

And deployed it to Arbitrum Sepolia network:

Next It try to call permit method:

// Define Signature
    const domain = {
      name: name,
      version: '1',
      chainId: "421614",
      verifyingContract: GRN_TOKEN,
    };

    // Define types
    const types = {
      Permit: [
        { name: 'owner', type: 'address' },
        { name: 'spender', type: 'address' },
        { name: 'nonce', type: 'uint256' },
        { name: 'deadline', type: 'uint256' },
        { name: 'value', type: 'uint256' },
      ],
    };

  // Define transaction
    const values = {
      owner: tokenOwner.address,
      spender: tokenReceiver.address,
      nonce: nonce,
      deadline: deadline,
      value: value,
    };

 // Sign data
    const signature = await tokenOwner._signTypedData(domain, types, values);

    const r = signature.substring(0, 66);
    const s = '0x' + signature.substring(66, 130);
    const v = parseInt(signature.substring(130, 132), 16);

    // Split signature
    const sig = ethers.utils.splitSignature(signature);

 return await Token.connect(tokenReceiver).permit(
      tokenOwner.address,
      tokenReceiver.address,
      value,
      deadline,
      v,
      r,
      s,
      {
        gasPrice: gas,
        gasLimit: 5000000,
      },
    );

But I get error always:
Warning! Error encountered during contract execution [execution reverted ]

What could be the problem and how to debug it? I already have checked all the internet and nothing helps(((

Hi @Enflow-io

My first impression is that it may related to how you're decoding the signature, did you try using sig.v, sig.r and sig.s in the permit parameters?

Aside from that, I don't see why it's not working. My best advice would be to make sure the domain values are correct

Yes it's the same as s, r and v. Ive checked twice. What could I test next?

This is the issue!

You must respect the order defined in the ERC otherwize the data will not be hashed/prepared correctly.

The correct order is

    const types = {
      Permit: [
        { name: 'owner', type: 'address' },
        { name: 'spender', type: 'address' },
        { name: 'value', type: 'uint256' },
        { name: 'nonce', type: 'uint256' },
        { name: 'deadline', type: 'uint256' },
      ],
    };

Note: I was able to get a permit operation to pass: https://sepolia.arbiscan.io/tx/0x303d8342e6c4d28c26897728aaa3e456fec159d52f6b0040b70a9d780a37918e

Using this code (ethers v6)

const {ethers} = require("ethers");

const { abi } = require("/home/amxx/Work/OZ/openzeppelin-contracts/artifacts/contracts/token/ERC20/extensions/ERC20Permit.sol/ERC20Permit.json");

const NODE = "https://arbitrum-sepolia.blockpi.network/v1/rpc/public";
const KEY = process.env.PRIVATE_KEY;

(async () => {
    const types = {
        Permit: [
            { name: 'owner', type: 'address' },
            { name: 'spender', type: 'address' },
            { name: 'value', type: 'uint256' },
            { name: 'nonce', type: 'uint256' },
            { name: 'deadline', type: 'uint256' },
        ],
    };

    const domain = {
        name: 'MyToken',
        version: '1',
        chainId: 421614n,
        verifyingContract: '0x4CC9e4f21CB783Fa87C60C448d535197D95DfED2'
    };

    const signer = new ethers.Wallet(KEY, ethers.getDefaultProvider(NODE));
    const instance = new ethers.Contract(domain.verifyingContract, abi, signer);

    // Check domain is correct
    if (await instance.DOMAIN_SEPARATOR() != ethers.TypedDataEncoder.hashDomain(domain)) {
        throw new Error("Invalid domain");
    }

    // Prepare permit
    const value = {
        owner: signer.address,
        spender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        value: 1337n,
        nonce: await instance.nonces(signer.address),
        deadline: ethers.MaxUint256,
    };

    const { r, s, v } = await signer.signTypedData(domain, types, value).then(ethers.Signature.from);

    // console.log(await instance.getFunction("permit").staticCall(value.owner, value.spender, value.value, value.deadline, v, r, s));
    const tx = await instance.permit(value.owner, value.spender, value.value, value.deadline, v, r, s);
    console.log(tx);

})().catch(console.error);