123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- :github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
- :xref-ERC2771Context-constructor-address-: xref:metatx.adoc#ERC2771Context-constructor-address-
- :xref-ERC2771Context-trustedForwarder--: xref:metatx.adoc#ERC2771Context-trustedForwarder--
- :xref-ERC2771Context-isTrustedForwarder-address-: xref:metatx.adoc#ERC2771Context-isTrustedForwarder-address-
- :xref-ERC2771Context-_msgSender--: xref:metatx.adoc#ERC2771Context-_msgSender--
- :xref-ERC2771Context-_msgData--: xref:metatx.adoc#ERC2771Context-_msgData--
- :xref-ERC2771Context-_contextSuffixLength--: xref:metatx.adoc#ERC2771Context-_contextSuffixLength--
- :ERC2771Context: pass:normal[xref:metatx.adoc#ERC2771Context[`ERC2771Context`]]
- :xref-ERC2771Forwarder-constructor-string-: xref:metatx.adoc#ERC2771Forwarder-constructor-string-
- :xref-ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-
- :xref-ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-
- :xref-ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-: xref:metatx.adoc#ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-
- :xref-ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-
- :xref-ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-
- :xref-ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-: xref:metatx.adoc#ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-
- :xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address-
- :xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address-
- :xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256-
- :xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4--
- :xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32-
- :xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain--
- :xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name--
- :xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version--
- :xref-ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-: xref:metatx.adoc#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-
- :xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged--
- :xref-ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-
- :xref-ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-
- :xref-ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-
- :xref-ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-: xref:metatx.adoc#ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-
- :xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256-
- :EIP712-constructor: pass:normal[xref:utils.adoc#EIP712-constructor-string-string-[`EIP712.constructor`]]
- :ECDSA-tryRecover: pass:normal[xref:utils.adoc#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-[`ECDSA.tryRecover`]]
- = Meta Transactions
- [.readme-notice]
- NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/metatx
- == Core
- :constructor: pass:normal[xref:#ERC2771Context-constructor-address-[`++constructor++`]]
- :trustedForwarder: pass:normal[xref:#ERC2771Context-trustedForwarder--[`++trustedForwarder++`]]
- :isTrustedForwarder: pass:normal[xref:#ERC2771Context-isTrustedForwarder-address-[`++isTrustedForwarder++`]]
- :_msgSender: pass:normal[xref:#ERC2771Context-_msgSender--[`++_msgSender++`]]
- :_msgData: pass:normal[xref:#ERC2771Context-_msgData--[`++_msgData++`]]
- :_contextSuffixLength: pass:normal[xref:#ERC2771Context-_contextSuffixLength--[`++_contextSuffixLength++`]]
- [.contract]
- [[ERC2771Context]]
- === `++ERC2771Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/metatx/ERC2771Context.sol[{github-icon},role=heading-link]
- [.hljs-theme-light.nopadding]
- ```solidity
- import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
- ```
- Context variant with ERC2771 support.
- WARNING: 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`.
- WARNING: 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.
- [.contract-index]
- .Functions
- --
- * {xref-ERC2771Context-constructor-address-}[`++constructor(trustedForwarder_)++`]
- * {xref-ERC2771Context-trustedForwarder--}[`++trustedForwarder()++`]
- * {xref-ERC2771Context-isTrustedForwarder-address-}[`++isTrustedForwarder(forwarder)++`]
- * {xref-ERC2771Context-_msgSender--}[`++_msgSender()++`]
- * {xref-ERC2771Context-_msgData--}[`++_msgData()++`]
- * {xref-ERC2771Context-_contextSuffixLength--}[`++_contextSuffixLength()++`]
- --
- [.contract-item]
- [[ERC2771Context-constructor-address-]]
- ==== `[.contract-item-name]#++constructor++#++(address trustedForwarder_)++` [.item-kind]#internal#
- [.contract-item]
- [[ERC2771Context-trustedForwarder--]]
- ==== `[.contract-item-name]#++trustedForwarder++#++() → address++` [.item-kind]#public#
- Returns the address of the trusted forwarder.
- [.contract-item]
- [[ERC2771Context-isTrustedForwarder-address-]]
- ==== `[.contract-item-name]#++isTrustedForwarder++#++(address forwarder) → bool++` [.item-kind]#public#
- Indicates whether any particular address is the trusted forwarder.
- [.contract-item]
- [[ERC2771Context-_msgSender--]]
- ==== `[.contract-item-name]#++_msgSender++#++() → address++` [.item-kind]#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).
- [.contract-item]
- [[ERC2771Context-_msgData--]]
- ==== `[.contract-item-name]#++_msgData++#++() → bytes++` [.item-kind]#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).
- [.contract-item]
- [[ERC2771Context-_contextSuffixLength--]]
- ==== `[.contract-item-name]#++_contextSuffixLength++#++() → uint256++` [.item-kind]#internal#
- ERC-2771 specifies the context as being a single address (20 bytes).
- == Utils
- :ForwardRequestData: pass:normal[xref:#ERC2771Forwarder-ForwardRequestData[`++ForwardRequestData++`]]
- :_FORWARD_REQUEST_TYPEHASH: pass:normal[xref:#ERC2771Forwarder-_FORWARD_REQUEST_TYPEHASH-bytes32[`++_FORWARD_REQUEST_TYPEHASH++`]]
- :ExecutedForwardRequest: pass:normal[xref:#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-[`++ExecutedForwardRequest++`]]
- :ERC2771ForwarderInvalidSigner: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-[`++ERC2771ForwarderInvalidSigner++`]]
- :ERC2771ForwarderMismatchedValue: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-[`++ERC2771ForwarderMismatchedValue++`]]
- :ERC2771ForwarderExpiredRequest: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-[`++ERC2771ForwarderExpiredRequest++`]]
- :ERC2771UntrustfulTarget: pass:normal[xref:#ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-[`++ERC2771UntrustfulTarget++`]]
- :constructor: pass:normal[xref:#ERC2771Forwarder-constructor-string-[`++constructor++`]]
- :verify: pass:normal[xref:#ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-[`++verify++`]]
- :execute: pass:normal[xref:#ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-[`++execute++`]]
- :executeBatch: pass:normal[xref:#ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-[`++executeBatch++`]]
- :_validate: pass:normal[xref:#ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-[`++_validate++`]]
- :_recoverForwardRequestSigner: pass:normal[xref:#ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-[`++_recoverForwardRequestSigner++`]]
- :_execute: pass:normal[xref:#ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-[`++_execute++`]]
- [.contract]
- [[ERC2771Forwarder]]
- === `++ERC2771Forwarder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/metatx/ERC2771Forwarder.sol[{github-icon},role=heading-link]
- [.hljs-theme-light.nopadding]
- ```solidity
- 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.
- NOTE: 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.
- [.contract-index]
- .Functions
- --
- * {xref-ERC2771Forwarder-constructor-string-}[`++constructor(name)++`]
- * {xref-ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-}[`++verify(request)++`]
- * {xref-ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-}[`++execute(request)++`]
- * {xref-ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-}[`++executeBatch(requests, refundReceiver)++`]
- * {xref-ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-}[`++_validate(request)++`]
- * {xref-ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-}[`++_recoverForwardRequestSigner(request)++`]
- * {xref-ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-}[`++_execute(request, requireValidRequest)++`]
- [.contract-subindex-inherited]
- .Nonces
- * {xref-Nonces-nonces-address-}[`++nonces(owner)++`]
- * {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`]
- * {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`]
- [.contract-subindex-inherited]
- .EIP712
- * {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`]
- * {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`]
- * {xref-EIP712-eip712Domain--}[`++eip712Domain()++`]
- * {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`]
- * {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`]
- [.contract-subindex-inherited]
- .IERC5267
- --
- [.contract-index]
- .Events
- --
- * {xref-ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-}[`++ExecutedForwardRequest(signer, nonce, success)++`]
- [.contract-subindex-inherited]
- .Nonces
- [.contract-subindex-inherited]
- .EIP712
- [.contract-subindex-inherited]
- .IERC5267
- * {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`]
- --
- [.contract-index]
- .Errors
- --
- * {xref-ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-}[`++ERC2771ForwarderInvalidSigner(signer, from)++`]
- * {xref-ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-}[`++ERC2771ForwarderMismatchedValue(requestedValue, msgValue)++`]
- * {xref-ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-}[`++ERC2771ForwarderExpiredRequest(deadline)++`]
- * {xref-ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-}[`++ERC2771UntrustfulTarget(target, forwarder)++`]
- [.contract-subindex-inherited]
- .Nonces
- * {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`]
- [.contract-subindex-inherited]
- .EIP712
- [.contract-subindex-inherited]
- .IERC5267
- --
- [.contract-item]
- [[ERC2771Forwarder-constructor-string-]]
- ==== `[.contract-item-name]#++constructor++#++(string name)++` [.item-kind]#public#
- See {EIP712-constructor}.
- [.contract-item]
- [[ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-]]
- ==== `[.contract-item-name]#++verify++#++(struct ERC2771Forwarder.ForwardRequestData request) → bool++` [.item-kind]#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.
- NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund
- receiver is provided.
- [.contract-item]
- [[ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-]]
- ==== `[.contract-item-name]#++execute++#++(struct ERC2771Forwarder.ForwardRequestData request)++` [.item-kind]#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}.
- [.contract-item]
- [[ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-]]
- ==== `[.contract-item-name]#++executeBatch++#++(struct ERC2771Forwarder.ForwardRequestData[] requests, address payable refundReceiver)++` [.item-kind]#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.
- NOTE: 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.
- [.contract-item]
- [[ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-]]
- ==== `[.contract-item-name]#++_validate++#++(struct ERC2771Forwarder.ForwardRequestData request) → bool isTrustedForwarder, bool active, bool signerMatch, address signer++` [.item-kind]#internal#
- Validates if the provided request can be executed at current block timestamp with
- the given `request.signature` on behalf of `request.signer`.
- [.contract-item]
- [[ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-]]
- ==== `[.contract-item-name]#++_recoverForwardRequestSigner++#++(struct ERC2771Forwarder.ForwardRequestData request) → bool, address++` [.item-kind]#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.
- NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it.
- [.contract-item]
- [[ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-]]
- ==== `[.contract-item-name]#++_execute++#++(struct ERC2771Forwarder.ForwardRequestData request, bool requireValidRequest) → bool success++` [.item-kind]#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.
- IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially
- leaving value stuck in the contract.
- [.contract-item]
- [[ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-]]
- ==== `[.contract-item-name]#++ExecutedForwardRequest++#++(address indexed signer, uint256 nonce, bool success)++` [.item-kind]#event#
- Emitted when a `ForwardRequest` is executed.
- NOTE: 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.
- [.contract-item]
- [[ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-]]
- ==== `[.contract-item-name]#++ERC2771ForwarderInvalidSigner++#++(address signer, address from)++` [.item-kind]#error#
- The request `from` doesn't match with the recovered `signer`.
- [.contract-item]
- [[ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-]]
- ==== `[.contract-item-name]#++ERC2771ForwarderMismatchedValue++#++(uint256 requestedValue, uint256 msgValue)++` [.item-kind]#error#
- The `requestedValue` doesn't match with the available `msgValue`.
- [.contract-item]
- [[ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-]]
- ==== `[.contract-item-name]#++ERC2771ForwarderExpiredRequest++#++(uint48 deadline)++` [.item-kind]#error#
- The request `deadline` has expired.
- [.contract-item]
- [[ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-]]
- ==== `[.contract-item-name]#++ERC2771UntrustfulTarget++#++(address target, address forwarder)++` [.item-kind]#error#
- The request target doesn't trust the `forwarder`.
|