Meta Transactions

This document is better viewed at https://docs.openzeppelin.com/contracts/api/metatx

This directory includes contracts for adding meta-transaction capabilities (i.e. abstracting the execution context from the transaction origin) following the ERC-2771 specification.

  • ERC2771Context: Provides a mechanism to override the sender and calldata of the execution context (msg.sender and msg.data) with a custom value specified by a trusted forwarder.

  • ERC2771Forwarder: A production-ready forwarder that relays operation requests signed off-chain by an EOA.

Core

ERC2771Context

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

Context variant with ERC2771 support.

Avoid using this pattern in contracts that rely in a specific calldata length as they’ll be affected by any forwarder whose msg.data is suffixed with the from address according to the ERC2771 specification adding the address size in bytes (20) to the calldata size. An example of an unexpected behavior could be an unintended fallback (or another function) invocation while trying to invoke the receive function only accessible if msg.data.length == 0.
The usage of delegatecall in this contract is dangerous and may result in context corruption. Any forwarded request to this contract triggering a delegatecall to itself will result in an invalid _msgSender recovery.

constructor(address trustedForwarder_) internal

trustedForwarder() → address public

Returns the address of the trusted forwarder.

isTrustedForwarder(address forwarder) → bool public

Indicates whether any particular address is the trusted forwarder.

_msgSender() → address internal

Override for msg.sender. Defaults to the original msg.sender whenever a call is not performed by the trusted forwarder or the calldata length is less than 20 bytes (an address length).

_msgData() → bytes internal

Override for msg.data. Defaults to the original msg.data whenever a call is not performed by the trusted forwarder or the calldata length is less than 20 bytes (an address length).

_contextSuffixLength() → uint256 internal

ERC-2771 specifies the context as being a single address (20 bytes).

Utils

ERC2771Forwarder

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

A forwarder compatible with ERC2771 contracts. See ERC2771Context.

This forwarder operates on forward requests that include:

  • from: An address to operate on behalf of. It is required to be equal to the request signer.

  • to: The address that should be called.

  • value: The amount of native token to attach with the requested call.

  • gas: The amount of gas limit that will be forwarded with the requested call.

  • nonce: A unique transaction ordering identifier to avoid replayability and request invalidation.

  • deadline: A timestamp after which the request is not executable anymore.

  • data: Encoded msg.data to send with the requested call.

Relayers are able to submit batches if they are processing a high volume of requests. With high throughput, relayers may run into limitations of the chain such as limits on the number of transactions in the mempool. In these cases the recommendation is to distribute the load among multiple accounts.

Batching requests includes an optional refund for unused msg.value that is achieved by performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance, if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly handle msg.data.length == 0. ERC2771Context in OpenZeppelin Contracts versions prior to 4.9.3 do not handle this properly.

Security Considerations

If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount specified in the request. This contract does not implement any kind of retribution for this gas, and it is assumed that there is an out of band incentive for relayers to pay for execution on behalf of signers. Often, the relayer is operated by a project that will consider it a user acquisition cost.

By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward some other purpose that is not aligned with the expected out of band incentives. If you operate a relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or ERC-1155 transfers specifically, consider rejecting the use of the data field, since it can be used to execute arbitrary code.

constructor(string name) public

verify(struct ERC2771Forwarder.ForwardRequestData request) → bool public

Returns true if a request is valid for a provided signature at the current block timestamp.

A transaction is considered valid when the target trusts this forwarder, the request hasn’t expired (deadline is not met), and the signer matches the from parameter of the signed request.

A request may return false here but it won’t cause executeBatch to revert if a refund receiver is provided.

execute(struct ERC2771Forwarder.ForwardRequestData request) public

Executes a request on behalf of `signature’s signer using the ERC-2771 protocol. The gas provided to the requested call may not be exactly the amount requested, but the call will not run out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed.

Requirements:

  • The request value should be equal to the provided msg.value.

  • The request should be valid according to verify.

executeBatch(struct ERC2771Forwarder.ForwardRequestData[] requests, address payable refundReceiver) public

Batch version of execute with optional refunding and atomic execution.

In case a batch contains at least one invalid request (see verify), the request will be skipped and the refundReceiver parameter will receive back the unused requested value at the end of the execution. This is done to prevent reverting the entire batch when a request is invalid or has already been submitted.

If the refundReceiver is the address(0), this function will revert when at least one of the requests was not valid instead of skipping it. This could be useful if a batch is required to get executed atomically (at least at the top-level). For example, refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids including reverted transactions.

Requirements:

  • The sum of the requests' values should be equal to the provided msg.value.

  • All of the requests should be valid (see verify) when refundReceiver is the zero address.

Setting a zero refundReceiver guarantees an all-or-nothing requests execution only for the first-level forwarded calls. In case a forwarded request calls to a contract with another subcall, the second-level call may revert without the top-level call reverting.

_validate(struct ERC2771Forwarder.ForwardRequestData request) → bool isTrustedForwarder, bool active, bool signerMatch, address signer internal

Validates if the provided request can be executed at current block timestamp with the given request.signature on behalf of request.signer.

_recoverForwardRequestSigner(struct ERC2771Forwarder.ForwardRequestData request) → bool, address internal

Returns a tuple with the recovered the signer of an EIP712 forward request message hash and a boolean indicating if the signature is valid.

The signature is considered valid if ECDSA.tryRecover indicates no recover error for it.

_execute(struct ERC2771Forwarder.ForwardRequestData request, bool requireValidRequest) → bool success internal

Validates and executes a signed request returning the request call success value.

Internal function without msg.value validation.

Requirements:

  • The caller must have provided enough gas to forward with the call.

  • The request must be valid (see verify) if the requireValidRequest is true.

Emits an ExecutedForwardRequest event.

Using this function doesn’t check that all the msg.value was sent, potentially leaving value stuck in the contract.

ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success) event

Emitted when a ForwardRequest is executed.

An unsuccessful forward request could be due to an invalid signature, an expired deadline, or simply a revert in the requested call. The contract guarantees that the relayer is not able to force the requested call to run out of gas.

ERC2771ForwarderInvalidSigner(address signer, address from) error

The request from doesn’t match with the recovered signer.

ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue) error

The requestedValue doesn’t match with the available msgValue.

ERC2771ForwarderExpiredRequest(uint48 deadline) error

The request deadline has expired.

ERC2771UntrustfulTarget(address target, address forwarder) error

The request target doesn’t trust the forwarder.