Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update EIP-2771: Add myself as an author and bring it up to date #5607

Merged
merged 4 commits into from
Oct 19, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 51 additions & 122 deletions EIPS/eip-2771.md
Original file line number Diff line number Diff line change
@@ -1,147 +1,110 @@
---
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)
description: A contract interface for receiving meta transactions through a trusted forwarder
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), Pandapip1 (@Pandapip1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defer to authors to decide.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I am fine with this change. I am glad someone took the lead to improve the wordings with the goal to make the EIP final

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussions-to: https://ethereum-magicians.org/t/erc-2771-secure-protocol-for-native-meta-transactions/4488
status: Stagnant
status: Review
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.
This EIP defines a contract-level protocol for `Recipient` contracts to accept meta-transactions through trusted `Forwarder` contracts. No protocol changes are made. `Recipient` contracts are sent the effective `msg.sender` (referred to as `_msgSender()`) and `msg.data` (referred to as `_msgData()`) by appending additional calldata.

## 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:
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. Solutions that allow for third parties to pay for gas costs are called meta transactions. For the purposes of this EIP, meta transactions are transactions that have been authorized by a **Transaction Signer** and relayed by an untrusted third party that pays for the gas (the **Gas Relay**).

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**)
## Specification

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

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**.
### Definitions

## Specification
**Transaction Signer**: Signs & sends transactions to a Gas Relay

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).
**Gas Relay**: Receives signed requests off-chain from Transaction Signers and pays gas to turn it into a valid transaction that goes through a Trusted Forwarder

Here is an example flow:
**Trusted Forwarder**: A contract trusted by the `Recipient` to correctly verify signatures and nonces before forwarding the request from Transaction Signers

![Example flow](../assets/eip-2771/example-flow.png)
**Recipient**: A contract that accepts meta-transactions through a Trusted Forwarder

### 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.
![Example flow](../assets/eip-2771/example-flow.png)

### 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.
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:
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.
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.
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.
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.
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:
To provide this discovery mechanism a **Recipient** contract MUST implement this function:

```solidity
function isTrustedForwarder(address forwarder) external returns(bool);
function isTrustedForwarder(address forwarder) external view 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.
`isTrustedForwarder` MUST return `true` if the forwarder is trusted by the Recipient, otherwise it MUST return `false`. `isTrustedForwarder` MUST NOT revert.

Internally, the **Recipient** MUST then accept a request from forwarder.

Internally, the **Recipient** MUST then accept a request from forwarder
`isTrustedForwarder` function MAY be called on-chain, and as such gas restrictions MUST be put in place. A Gas limit of 50k SHOULD be sufficient to making the decision either inside the contract, or delegating it to another contract and doing some memory access calculations, like querying a mapping.

That function can be called on-chain and as such gas restriction needs to be
put in place.
## Rationale

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.
* 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.
* `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**.
## Reference Implementation

### Recipient example
### Recipient Example

```solidity
contract RecipientExample {

function purchaseItem(uint256 itemId) external {
address sender = _msgSender();
... perform the purchase for sender
// ... perform the purchase for sender
}

address immutable _trustedForwarder;
Expand All @@ -165,45 +128,11 @@ contract RecipientExample {
}
```

## 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
A malicious forwarder may forge the value of `_msgSender()` and effectively send transactions from any address. Therefore, `Recipient` contracts must be very careful in trusting forwarders. If a forwarder is upgradeable, then one must also trust that the contract won't perform a malicious upgrade.

An implementation of a base class for a recipient: [BaseRelayRecipient.sol](https://github.com/opengsn/forwarder/blob/master/contracts/BaseRelayRecipient.sol)
In addition, modifying which forwarders are trusted must be restricted, since an attacker could "trust" their own address to forward transactions, and therefore be able to forge transactions. It is recommended to have the list of trusted forwarders be immutable, and if this is not feasible, then only trusted contract owners should be able to modify it.

## Copyright

Expand Down