OpenZeppelin / openzeppelin-upgrades

Plugins for Hardhat and Foundry to deploy and manage upgradeable contracts on Ethereum.
MIT License
627 stars 267 forks source link

【Wanted Help・ ERC2771 Forwarder】 write code Metatrasactions'S test code #968

Closed HarukiKondo closed 9 months ago

HarukiKondo commented 9 months ago

I would like to prepare a test code for a smart contract using @openzeppelin/contracts/metatx/ERC2771Forwarder.sol.

I have created a test code referring to the file under the test folder, but an error occurs in the verify method of the forwarder contract. I believe there is a problem with the signature data, but I would like to know if you know of a specific fix.

My Forwarder Contract code is here

import "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol";

contract ERC2771ForwarderMock is ERC2771Forwarder {

    struct ForwardRequest {
        address from;
        address to;
        uint256 value;
        uint256 gas;
        uint256 nonce;
        uint48 deadline;
        bytes data;
    }
    constructor(string memory name) ERC2771Forwarder(name) {}

    function structHash(ForwardRequest calldata request) external view returns (bytes32) {
        return
            _hashTypedDataV4(
                keccak256(
                    abi.encode(
                        _FORWARD_REQUEST_TYPEHASH,
                        request.from,
                        request.to,
                        request.value,
                        request.nonce,
                        request.gas,
                        request.deadline,
                        keccak256(request.data)
                    )
                )
            );
    }
}

My Test code is here

describe("MetaTransaction", function () {

   async function relay(
    forwarder: any, 
    request: any, 
  ) {
    console.log("request:", request)
    // まずトランザクションデータを検証する。
    // const valid = await forwarder.verify(request);
    //if (!valid) throw new Error(`Invalid request`);

    // 検証して問題なければトランザクションを実行して Recipientコントラクトの処理を呼び出す。
    const gasLimit = (parseInt(request.gas) + 50000).toString();
    // forward コントラクトのexecuteメソッドを呼び出す。
    await forwarder.execute(request, { gasLimit });
    return 
  }

    it("Gasless tranfer NFT", async function () {
      const { mockNFTV1, forwarder, owner, addr1 } = await loadFixture(deployTokenFixture);
      // mint NFT
      await mintNft(mockNFTV1, owner, addr1.address, "mock", 1);

      const beforeBalance = await mockNFTV1.balanceOf(owner.address);
      const beforeBalance2 = await mockNFTV1.balanceOf(addr1.address);

      const beforeEthBalance = await addr1.provider.getBalance(addr1.address);

      const data = mockNFTV1.interface.encodeFunctionData('safeTransferFrom(address,address,uint256)', [addr1.address, owner.address, 0]);

      const {
        request,
        signature,
      } = await signMetaTxRequest(owner, forwarder, {
        to: await mockNFTV1.getAddress(), 
        from: addr1.address, 
        data
      });

      request.signature = signature;

      await relay(forwarder, request);

      const afterBalance = await mockNFTV1.balanceOf(owner.address);
      const afterBalance2 = await mockNFTV1.balanceOf(addr1.address);
      const afterEthBalance = await addr1.provider.getBalance(addr1.address);

      expect(beforeBalance).to.equal(0);
      expect(beforeBalance2).to.equal(1);
      expect(afterBalance).to.equal(1);
      expect(afterBalance2).to.equal(0);
      expect(beforeEthBalance).to.equal(afterEthBalance);
    });

helper's code is here

import ethSigUtil from 'eth-sig-util';
import { ethers } from "ethers";

const EIP712Domain = [
  { name: 'name', type: 'string' },
  { name: 'version', type: 'string' },
  { name: 'chainId', type: 'uint256' },
  { name: 'verifyingContract', type: 'address' }
];

const ForwardRequest = [
  { name: 'from', type: 'address' },
  { name: 'to', type: 'address' },
  { name: 'value', type: 'uint256' },
  { name: 'gas', type: 'uint256' },
  { name: 'nonce', type: 'uint256' },
  { name: 'deadline', type: 'uint48' },
  { name: 'data', type: 'bytes' },
];

export function getMetaTxTypeData(
  chainId: any, 
  verifyingContract: any
) {
  return {
    types: {
      ForwardRequest,
    },
    domain: {
      name: 'ERC2771ForwarderMock',
      version: '0.0.1',
      chainId,
      verifyingContract,
    },
    primaryType: 'ForwardRequest',
  }
};

export async function signTypedData(
  signer: any, 
  data: any
) {
  return await signer.signTypedData(data.domain, data.types, data.request)
}

export async function buildRequest(
  forwarder: any, 
  input: any
) {
  const nonce = await forwarder.nonces(input.from).then((nonce: any) => nonce.toString());
  const numberNonce = Number(nonce);
  const currentTimestamp = Math.floor(new Date().getTime() / 1000);
  const oneWeekInSeconds = 60;
  const futureTimestamp = currentTimestamp + oneWeekInSeconds;
  const uint48Value = ethers.toNumber(futureTimestamp);
  return { 
    data: input.data,
    deadline: uint48Value,
    from: input.from,
    gas: 100000, 
    nonce: numberNonce, 
    to: input.to,
    value: 0, 
  };
}

export async function buildTypedData(
  forwarder: any, 
  request: any  
) {
  const chainId = 31337;
  const typeData = getMetaTxTypeData(chainId, await forwarder.getAddress());
  return { ...typeData, request };
}

export async function signMetaTxRequest(
  signer: any, 
  forwarder: any, 
  input: any
) {
  const request = await buildRequest(forwarder, input);
  const toSign = await buildTypedData(forwarder, request);
  const signature = await signTypedData(signer, toSign);
  return { signature, request };
}

test code's result is here

 24 passing (7s)
  1 failing

  1) Upgradable NFT contract's test
       MetaTransaction
         Gasless tranfer NFT:
     Error: VM Exception while processing transaction: reverted with custom error 'ERC2771ForwarderInvalidSigner("0x6e1B8e2C47a6ae080eEdaA9264055c20cc414744", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8")'
    at ERC2771ForwarderMock.executeBatch (@openzeppelin/contracts/metatx/ERC2771Forwarder.sol:182)
    at ERC2771ForwarderMock.execute (@openzeppelin/contracts/metatx/ERC2771Forwarder.sol:134)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Why from address & recovered address are not match??

ericglau commented 9 months ago

This repository is for upgrades plugins, which does not look related to your question.

Please consider asking your question on the forum https://forum.openzeppelin.com/

HarukiKondo commented 9 months ago

OK! Thank you !!