DCIPs/EIPS/eip-2770.md

208 lines
7.9 KiB
Markdown
Raw Normal View History

---
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).