Solidity metaTransaction

Solidity Openzeppelin metaTransaction implementation


foreword

Metatransactions is a way that allows users to complete transactions without paying gas (for example, a list of NFT in opensea uses metatransaction), the method is that after authorizing the contract, the user only needs to be responsible for signing the transaction data, and the latter The intermediate account (which can be called relayer or fowarder) takes the user signature and user transaction as input to call the contract, helps the user pay gas to complete the transaction, and the contract will verify the integrity and authenticity of the transaction and signature. This method is often used in the case of Opensea and other mint tokens.


1. Openzeppelin's implementation of metatx

metatx corresponds to Openzeppelin's ERC2771 proposal. The Openzeppelin library mainly implements the context contract ERC2771Context and a simple version of the forwarder, MinimalForwarder, for ERC2771.

1、ERC2771Context

ERC2771Context inherits from the Context contract. The purpose of the context contract is to rewrite its _msgSender function and _msgData function to make it return the sender and data you want to find instead of msg.sender and msg.data. This is very important in metatransaction, because what we often want to know is the address of the user who called the fowarder to complete the metatransaction, not the address of the fowarder. The Openzeppelin implementation of ERC2771Context is as follows:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)

pragma solidity ^0.8.9;

import "../utils/Context.sol";

/**
 * @dev Context variant with ERC2771 support.
 */
abstract contract ERC2771Context is Context {
    
    
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable _trustedForwarder;

    /// @custom:oz-upgrades-unsafe-allow constructor
    ///构造合约必须传入信任的fowarder的地址,让用户想要调用的metatransaction合约知道谁是合法的fowarder
    constructor(address trustedForwarder) {
    
    
        _trustedForwarder = trustedForwarder;
    }

    function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
    
    
        return forwarder == _trustedForwarder;
    }

    //判断msg.sender是否为fowarder,如果是则返回调用metatransaction的用户地址,如果不是,则返回msg.sender地址
    function _msgSender() internal view virtual override returns (address sender) {
    
    
        if (isTrustedForwarder(msg.sender)) {
    
    
            // The assembly code is more direct than the Solidity version using `abi.decode`.
            /// @solidity memory-safe-assembly
            assembly {
    
    
                sender := shr(96, calldataload(sub(calldatasize(), 20)))
            }
        } else {
    
    
            return super._msgSender();
        }
    }
    //判断msg.sender是否为fowarder,如果是则返回调用metatransaction的用户发送的msgdata,如果不是,则返回msg.data
    function _msgData() internal view virtual override returns (bytes calldata) {
    
    
        if (isTrustedForwarder(msg.sender)) {
    
    
            return msg.data[:msg.data.length - 20];
        } else {
    
    
            return super._msgData();
        }
    }
}

2.MinimalForwarder

MinimalForwarder is a simple implementation of fowarder for Openzeppelin

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/MinimalForwarder.sol)

pragma solidity ^0.8.0;

import "../utils/cryptography/ECDSA.sol";
import "../utils/cryptography/EIP712.sol";

/**fowarder合约的简易实现,包含功能不全
 * @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}.
 *
 * MinimalForwarder is mainly meant for testing, as it is missing features to be a good production-ready forwarder. This
 * contract does not intend to have all the properties that are needed for a sound forwarding system. A fully
 * functioning forwarding system with good properties requires more complexity. We suggest you look at other projects
 * such as the GSN which do have the goal of building a system like that.
 */
//EIP712提案定义了初始化domainseparator,以及通过data和domainseparator获得hashTypedData的方法,其中domainseparator用于防止一个 DApp 的签名被用在另一个 DApp 中。
contract MinimalForwarder is EIP712 {
    
    
    using ECDSA for bytes32;

    struct ForwardRequest {
    
    
        address from;
        address to;
        uint256 value;
        uint256 gas;
        uint256 nonce;
        bytes data;
    }

    bytes32 private constant _TYPEHASH =
        keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");

    mapping(address => uint256) private _nonces;

    constructor() EIP712("MinimalForwarder", "0.0.1") {
    
    }

    function getNonce(address from) public view returns (uint256) {
    
    
        return _nonces[from];
    }

    //使用签名和hash后的data可以恢复签名者的地址,判断该地址是否是metatx请求的发起者来验证签名的真实性,同时验证nonce已防止forward发起repay攻击
    function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
    
    
        address signer = _hashTypedDataV4(
            keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
        ).recover(signature);
        return _nonces[req.from] == req.nonce && signer == req.from;
    }

    //fowarder通过调用to合约发起metatransaction,同时令nonce+1
    function execute(ForwardRequest calldata req, bytes calldata signature)
        public
        payable
        returns (bool, bytes memory)
    {
    
    
        require(verify(req, signature), "MinimalForwarder: signature does not match request");
        _nonces[req.from] = req.nonce + 1;

        (bool success, bytes memory returndata) = req.to.call{
    
    gas: req.gas, value: req.value}(
            abi.encodePacked(req.data, req.from)
        );

        // Validate that the relayer has sent enough gas for the call.
        // See https://ronan.eth.limo/blog/ethereum-gas-dangers/
        if (gasleft() <= req.gas / 63) {
    
    
            // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since
            // neither revert or assert consume all gas since Solidity 0.8.0
            // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require
            /// @solidity memory-safe-assembly
            assembly {
    
    
                invalid()
            }
        }

        return (success, returndata);
    }
}

Guess you like

Origin blog.csdn.net/hhhhhhhhhjx/article/details/127317100
Recommended