--- eip: 2770 title: Meta-Transactions Forwarder Contract author: Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh) discussions-to: https://ethereum-magicians.org/t/erc-2770-meta-transactions-forwarder-contract/5391 status: Stagnant type: Standards Track category: ERC created: 2020-07-01 requires: 712, 2771 --- ## Simple Summary Standardized contract interface for extensible meta-transaction forwarding. ## Abstract This proposal defines an external API of an extensible Forwarder whose responsibility is to validate transaction signatures on-chain and expose the signer to the destination contract, that is expected to accommodate all use-cases. The ERC-712 structure of the forwarding request can be extended allowing wallets to display readable data even for types not known during the Forwarder contract deployment. ## Motivation There is a growing interest in making it possible for Ethereum contracts to accept calls from externally owned accounts that do not have ETH to pay for gas. This can be accomplished with meta-transactions, which are transactions that have been signed as plain data by one externally owned account first and then wrapped into an Ethereum transaction by a different account. `msg.sender` is a transaction parameter that can be inspected by a contract to determine who signed the transaction. The integrity of this parameter is guaranteed by the Ethereum EVM, but for a meta-transaction verifying `msg.sender` is insufficient, and signer address must be recovered as well. The Forwarder contract described here allows multiple Gas Relays and Relay Recipient contracts to rely on a single instance of the signature verifying code, improving reliability and security of any participating meta-transaction framework, as well as avoiding on-chain code duplication. ## Specification The Forwarder contract operates by accepting a signed typed data together with it's ERC-712 signature, performing signature verification of incoming data, appending the signer address to the data field and performing a call to the target. ### Forwarder data type registration Request struct MUST contain the following fields in this exact order: ``` struct ForwardRequest { address from; address to; uint256 value; uint256 gas; uint256 nonce; bytes data; uint256 validUntil; } ``` `from` - an externally-owned account making the request \ `to` - a destination address, normally a smart-contract\ `value` - an amount of Ether to transfer to the destination\ `gas` - an amount of gas limit to set for the execution\ `nonce` - an on-chain tracked nonce of a transaction\ `data` - the data to be sent to the destination\ `validUntil` - the highest block number the request can be forwarded in, or 0 if request validity is not time-limited The request struct MAY include any other fields, including nested structs, if necessary. In order for the Forwarder to be able to enforce the names of the fields of this struct, only registered types are allowed. Registration MUST be performed in advance by a call to the following method: ``` function registerRequestType(string typeName, string typeSuffix) ``` `typeName` - a name of a type being registered\ `typeSuffix` - an ERC-712 compatible description of a type For example, after calling ``` registerRequestType("ExtendedRequest", "uint256 x,bytes z,ExtraData extraData)ExtraData(uint256 a,uint256 b,uint256 c)") ``` the following ERC-712 type will be registered with forwarder: ``` /* primary type */ struct ExtendedRequest { address from; address to; uint256 value; uint256 gas; uint256 nonce; bytes data; uint256 validUntil; uint256 x; bytes z; ExtraData extraData; } /* subtype */ struct ExtraData { uint256 a; uint256 b; uint256 c; } ``` ### Signature verification The following method performs an ERC-712 signature check on a request: ``` function verify( ForwardRequest forwardRequest, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature ) view; ``` `forwardRequest` - an instance of the `ForwardRequest` struct `domainSeparator` - caller-provided domain separator to prevent signature reuse across dapps (refer to ERC-712) `requestTypeHash` - hash of the registered relay request type `suffixData` - RLP-encoding of the remainder of the request struct `signature` - an ERC-712 signature on the concatenation of `forwardRequest` and `suffixData` ### Command execution In order for the Forwarder to perform an operation, the following method is to be called: ``` function execute( ForwardRequest forwardRequest, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature ) public payable returns ( bool success, bytes memory ret ) ``` Performs the ‘verify’ internally and if it succeeds performs the following call: ``` bytes memory data = abi.encodePacked(forwardRequest.data, forwardRequest.from); ... (success, ret) = forwardRequest.to.call{gas: forwardRequest.gas, value: forwardRequest.value}(data); ``` Regardless of whether the inner call succeeds or reverts, the nonce is incremented, invalidating the signature and preventing a replay of the request. Note that `gas` parameter behaves according to EVM rules, specifically EIP-150. The forwarder validates internally that there is enough gas for the inner call. In case the `forwardRequest` specifies non-zero value, extra `40000 gas` is reserved in case inner call reverts or there is a remaining Ether so there is a need to transfer value from the `Forwarder`: ```solidity uint gasForTransfer = 0; if ( req.value != 0 ) { gasForTransfer = 40000; // buffer in case we need to move Ether after the transaction. } ... require(gasleft()*63/64 >= req.gas + gasForTransfer, "FWD: insufficient gas"); ``` In case there is not enough `value` in the Forwarder the execution of the inner call fails.\ Be aware that if the inner call ends up transferring Ether to the `Forwarder` in a call that did not originally have `value`, this Ether will remain inside `Forwarder` after the transaction is complete. ### ERC-712 and 'suffixData' parameter `suffixData` field must provide a valid 'tail' of an ERC-712 typed data. For instance, in order to sign on the `ExtendedRequest` struct, the data will be a concatenation of the following chunks: * `forwardRequest` fields will be RLP-encoded as-is, and variable-length `data` field will be hashed * `uint256 x` will be appended entirely as-is * `bytes z` will be hashed first * `ExtraData extraData` will be hashed as a typed data So a valid `suffixData` is calculated as following: ``` function calculateSuffixData(ExtendedRequest request) internal pure returns (bytes) { return abi.encode(request.x, keccak256(request.z), hashExtraData(request.extraData)); } function hashExtraData(ExtraData extraData) internal pure returns (bytes32) { return keccak256(abi.encode( keccak256("ExtraData(uint256 a,uint256 b,uint256 c)"), extraData.a, extraData.b, extraData.c )); } ``` ### Accepting Forwarded calls In order to support calls performed via the Forwarder, the Recipient contract must read the signer address from the last 20 bytes of `msg.data`, as described in ERC-2771. ## Rationale Further relying on `msg.sender` to authenticate end users by their externally-owned accounts is taking the Ethereum dapp ecosystem to a dead end. A need for users to own Ether before they can interact with any contract has made a huge portion of use-cases for smart contracts non-viable, which in turn limits the mass adoption and enforces this vicious cycle. `validUntil` field uses a block number instead of timestamp in order to allow for better precision and integration with other common block-based timers. ## Security Considerations All contracts introducing support for the Forwarded requests thereby authorize this contract to perform any operation under any account. It is critical that this contract has no vulnerabilities or centralization issues. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).