mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-23 12:18:16 +00:00
212 lines
8.2 KiB
Markdown
212 lines
8.2 KiB
Markdown
---
|
|
eip: 2771
|
|
title: Secure Protocol for Native Meta Transactions
|
|
author: Ronan Sandford (@wighawag), Liraz Siri (@lirazsiri), Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Hadrien Croubois (@Amxx), Sachin Tomar (@tomarsachin2271), Patrick McCorry (@stonecoldpat), Nicolas Venturo (@nventuro), Fabian Vogelsteller (@frozeman)
|
|
discussions-to: https://ethereum-magicians.org/t/erc-2771-secure-protocol-for-native-meta-transactions/4488
|
|
status: Draft
|
|
type: Standards Track
|
|
category: ERC
|
|
created: 2020-07-01
|
|
---
|
|
|
|
## Simple Summary
|
|
|
|
A contract interface for receiving meta transactions through a trusted
|
|
forwarder.
|
|
|
|
## Abstract
|
|
|
|
This ERC defines a minimal contract-level protocol that a compliant Recipient
|
|
contract needs to support in order to be capable of accepting a meta
|
|
transaction through a compliant Forwarder contract that it trusts to help it
|
|
identify the address of the Transaction Signer.
|
|
|
|
No EVM-level protocol changes are proposed or required.
|
|
|
|
## 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:
|
|
|
|
1. Authorized by the **Transaction Signer**. For example, signed by an
|
|
externally owned account.
|
|
2. Relayed by an untrusted third party that pays for the gas (the **Gas
|
|
Relay**)
|
|
|
|
`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 securing
|
|
`msg.sender` is insufficient.
|
|
|
|
The problem is that for a contract that is not natively aware of meta
|
|
transactions, the `msg.sender` of the transaction will make it appear to be
|
|
coming from the **Gas Relay** and not the **Transaction Signer**. A secure
|
|
protocol for a contract to accept meta transactions needs to prevent the **Gas
|
|
Relay** from forging, modifying or duplicating requests by the **Transaction
|
|
Signer**.
|
|
|
|
## Specification
|
|
|
|
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
|
|
|
|
Here is an example flow:
|
|
|
|

|
|
|
|
|
|
* **Transaction Signer** - entity that signs & sends to request to **Gas
|
|
Relay**
|
|
* **Gas Relay** - receives a signed request off-chain from **Transaction
|
|
Signer** and pays gas to turn it into a valid transaction that goes through
|
|
**Trusted Forwarder**
|
|
* **Trusted Forwarder** - a contract that is trusted by the `Recipient` to
|
|
correctly verify the signature and nonce before forwarding the request from
|
|
**Transaction Signer**
|
|
* **Recipient** - a contract that can securely accept meta-transactions
|
|
through a **Trusted Forwarder** by being compliant with this standard.
|
|
|
|
### Extracting The Transaction Signer address
|
|
|
|
The **Trusted Forwarder** is responsible for calling the **Recipient** contract
|
|
and MUST append the address of the **Transaction Signer** (20 bytes of data) to
|
|
the end of the call data.
|
|
|
|
For example :
|
|
|
|
```solidity
|
|
(bool success, bytes memory returnData) = to.call.value(value)(abi.encodePacked(data, from));
|
|
```
|
|
|
|
The **Recipient** contract can then extract the **Transaction Signer** address
|
|
by performing 3 operations:
|
|
|
|
1. Check that the **Forwarder** is trusted. How this is implemented is out of
|
|
the scope of this proposal.
|
|
2. Extract the **Transaction Signer** address from the last 20 bytes of the
|
|
call data and use that as the original `sender` of the transaction (instead of `msg.sender`)
|
|
3. If the `msg.sender` is not a trusted forwarder (or if the msg.data is
|
|
shorter than 20 bytes), then return the original `msg.sender` as it is.
|
|
|
|
The **Recipient** MUST check that it trusts the Forwarder to prevent it from
|
|
extracting address data appended from an untrusted contract. This could result
|
|
in a forged address.
|
|
|
|
### Protocol Support Discovery Mechanism
|
|
|
|
Unless a **Recipient** contract is being used by a particular frontend that
|
|
knows that this contract has support for native meta transactions, it would not
|
|
be possible to offer the user the choice of using meta-transaction to interact
|
|
with the contract. We thus need a mechanism by which the **Recipient** can let
|
|
the world know that it supports meta transactions.
|
|
|
|
This is especially important for meta transactions to be supported at the Web3
|
|
wallet level. Such wallets may not necessarily know anything about the
|
|
**Recipient** contract users may wish to interact with.
|
|
|
|
As a **Recipient** could trust forwarders with different interfaces and
|
|
capabilities (e.g., transaction batching, different message signing formats),
|
|
we need to allow wallets to discover which Forwarder is trusted.
|
|
|
|
To provide this discovery mechanism a **Recipient** contract MUST implement
|
|
this function:
|
|
|
|
```solidity
|
|
function isTrustedForwarder(address forwarder) external returns(bool);
|
|
```
|
|
|
|
* That function MUST return true if the forwarder is trusted by the
|
|
Recipient.
|
|
* That function MUST return false if the forwarder is not trusted.
|
|
* That function MUST NOT throw a revert.
|
|
|
|
Internally, the **Recipient** MUST then accept a request from forwarder
|
|
|
|
That function can be called on-chain and as such gas restriction needs to be
|
|
put in place.
|
|
|
|
A Gas limit of 50k is enough for making the decision either inside the
|
|
contract, or delegating it to another contract and doing some memory access
|
|
calculations, like querying a mapping.
|
|
|
|
### Recipient example
|
|
|
|
```solidity
|
|
contract RecipientExample {
|
|
|
|
function purchaseItem(uint256 itemId) external {
|
|
address sender = _msgSender();
|
|
... perform the purchase for sender
|
|
}
|
|
|
|
address immutable _trustedForwarder;
|
|
constructor(address trustedForwarder) internal {
|
|
_trustedForwarder = trustedForwarder;
|
|
}
|
|
|
|
function isTrustedForwarder(address forwarder) public returns(bool) {
|
|
return forwarder == _trustedForwarder;
|
|
}
|
|
|
|
function _msgSender() internal view returns (address payable signer) {
|
|
signer = msg.sender;
|
|
if (msg.data.length>=20 && isTrustedForwarder(signer)) {
|
|
assembly {
|
|
signer := shr(96,calldataload(sub(calldatasize(),20)))
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
## Rationale
|
|
|
|
* Make it easy for contract developers to add support for meta
|
|
transactions by standardizing the simplest viable contract interface.
|
|
|
|
* Without support for meta transactions in the recipient contract, an externally owned
|
|
account can not use meta transactions to interact with the recipient contract.
|
|
|
|
* Without a standard contract interface, there is no standard way for a client
|
|
to discover whether a recipient supports meta transactions.
|
|
|
|
* Without a standard contract interface, there is no standard way to send a
|
|
meta transaction to a recipient.
|
|
|
|
* Without the ability to leverage a trusted forwarder every recipient contract
|
|
has to internally implement the logic required to accept meta transactions securely.
|
|
|
|
* Without a discovery protocol, there is no mechanism for a client to discover
|
|
whether a recipient supports a specific forwarder.
|
|
|
|
* Making the contract interface agnostic to the internal implementation
|
|
details of the trusted forwarder, makes it possible for a recipient contract
|
|
to support multiple forwarders with no change to code.
|
|
|
|
## Security Considerations
|
|
|
|
A bad forwarder may allow forgery of the `msg.sender` returned from
|
|
`_msgSender()` and allow transactions to appear to be coming from any address.
|
|
|
|
This means a recipient contract should be very careful which forwarder it
|
|
trusts and whether this can be modified. The power to change the forwarder
|
|
trusted by a recipient is equivalent to giving full control over the contract.
|
|
If this kind of control over the recipient is acceptable, it is recommended
|
|
that only the owner of the recipient contract be able to modify which forwarder
|
|
is trusted. Otherwise best to leave it unmodifiable, as in the example above.
|
|
|
|
## Implementations
|
|
|
|
An implementation of a base class for a recipient: [BaseRelayRecipient.sol](https://github.com/opengsn/forwarder/blob/master/contracts/BaseRelayRecipient.sol)
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via
|
|
[CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|