Skip to content

Latest commit

 

History

History
198 lines (158 loc) · 11.4 KB

eip-2612.md

File metadata and controls

198 lines (158 loc) · 11.4 KB
Error in user YAML: (<unknown>): mapping values are not allowed in this context at line 2 column 14
---
eip: 2612
title: permit: 712-signed approvals
author: Martin Lundfall (@Mrchico)
discussions-to: https://github.com/ethereum/EIPs/issues/2613
status: Draft
type: Standards Track
category: ERC
created: 2020-04-13
requires: 20, 712
---

Simple Summary

A function permit extending ERC-20 which allows for approvals to be made via secp256k1 signatures. This kind of "account abstraction for ERC-20" brings about two main benefits:

  • transactions involving ERC-20 operations can be paid using the token itself rather than ETH,
  • approve and pull operations can happen in a single transaction instead of two consecutive transactions,

while adding as little as possible over the existing ERC-20 standard.

Abstract

Arguably one of the main reasons for the success of ERC-20 tokens lies in the interplay between approve and transferFrom, which allows for tokens to not only be transferred between externally owned accounts (EOA), but to be used in other contracts under application specific conditions by abstracting away msg.sender as the defining mechanism for token access control.

However, a limiting factor in this design stems from the fact that the ERC-20 approve function itself is defined in terms of msg.sender. This means that user's initial action involving ERC-20 tokens must be performed by an EOA [1]. If the user needs to interact with a smart contract, then they need to make 2 transactions (approve and the smart contract call which will internally call transferFrom). Even in the simple use case of paying another person, they need to hold ETH to pay for transaction gas costs.

This ERC extends the ERC-20 standard with a new function permit, which allows users to modify the allowance mapping using a signed message, instead of through msg.sender.

For an improved user experience, the signed data is structured following ERC-712, which already has wide spread adoption in major RPC providers.

Motivation

While ERC-20 tokens have become ubiquotous in the Ethereum ecosystem, their status remains that of second class tokens from the perspective of the protocol. The ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many EIPs.

So far, many of these proposals have seen very little adoption, and the ones that have been adopted (such as ERC-777), introduce a lot of additional functionality, causing unexpected behavior in mainstream contracts.

This ERC proposes an alternative solution which is designed to be as minimal as possible and to only address one problem: the lack of abstraction in the ERC-20 approve method.

While it may be tempting to introduce *_by_signature counterparts for every ERC-20 function, they are intentionally left out of this ERC-20 for two reasons:

  • the desired specifics of such functions, such as decision regarding fees for transfer_by_signature, possible batching algorithms, varies depending on the use case, and,
  • they can be implemented using a combination of permit and additional helper contracts without loss of generality.

Specification

A new method

function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

and a new storage item

mapping(address=>uint256) nonces;

with accompanying getter function are introduced, the semantics of which are as follows:

For all addresses owner, spender, uint256s value, deadline and nonce, uint8 v, bytes32 r and s, a call to permit(owner, spender, value, deadline, v, r, s) will set approval[owner][spender] to value, increment nonces[owner] by 1, and emit a corresponding Approval event, if and only if the following conditions are met:

  • The current blocktime is less than or equal to deadline.
  • owner is not the zero address.
  • nonces[owner] (before the state update) is equal to nonce.
  • r, s and v is a valid secp256k1 signature from owner of the message:
keccak256(abi.encodePacked(
   hex"1901",
   keccak256(abi.encodePacked(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256(bytes(erc20name)),
            keccak256(bytes(version)),
            chainid,
            tokenAddress)),
   keccak256(abi.encodePacked(
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
            owner,
            spender,
            value,
            nonce,
            deadline))
))

where tokenAddress is the address of the token contract, chainid is the chain id of the network it is deployed to and erc20name is the name of the token as defined by ERC-20. version is a string defined at contract deployment which remains constant throughout the lifetime of the contract, but is otherwise unconstrained.

In other words, the message is the ERC-712 typed structure:

{
  "types": {
    "EIP712Domain": [
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "version",
        "type": "string"
      },
      {
        "name": "chainId",
        "type": "uint256"
      },
      {
        "name": "verifyingContract",
        "type": "address"
      }
    ],
    "Permit": [{
      "name": "owner",
      "type": "address"
      },
      {
        "name": "spender",
        "type": "address"
      },
      {
        "name": "value",
        "type": "uint256"
      },
      {
        "name": "nonce",
        "type": "uint256"
      },
      {
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "primaryType": "Permit",
    "domain": {
      "name": erc20name,
      "version": version,
      "chainId": chainid,
      "verifyingContract": tokenAddress
  },
  "message": {
    "owner": owner,
    "spender": spender,
    "value": value,
    "nonce": nonce,
    "deadline": deadline
  }
}}

Note that nowhere in this definition we refer to msg.sender. The caller of the permit function can be any address.

Rationale

The permit function is sufficient for enabling any operation involving ERC-20 tokens to be paid for using the token itself, rather than using ETH. An example of a contract which enables gasless token transactions can be found here.

It avoids any calls to unknown code.

The nonces mapping is given for replay protection.

A common use case of permit has a relayer submit a Permit on behalf of the owner. In this scenario, the relaying party is essentially given a free option to submit or withhold the Permit. If this is a cause of concern, the owner can limit the time a Permit is valid for by setting deadline to a value in the near future. The deadline argument can be set to uint(-1) to create Permits that effectively never expire.

ERC-712 typed messages are included because of its wide spread adoption in many wallet providers.

Backwards Compatibility

There are currently two slightly differing implementations of this ERC, and we are forced to make a choice here for specificity. This implies that the given ERC deviates from the implementation given in the Dai and Chai ERC-20 contracts. There, the permit method takes nonce as an additional argument, and the uint256 value argument is exchanged for bool approval, admitting binary approvals only. There is also a slight difference in argument names. The specification presented here is in line with the implementation in Uniswap-v2. This mismatch is a little unfortunate, but not very different from the variations found in ERC-20 contracts.

Test Cases

Some basic test vector can be found here https://github.com/Uniswap/uniswap-v2-core/blob/master/test/UniswapV2ERC20.spec.ts. Additional test vectors for the Dai/Chai-like implementation are given at chai.t.sol.

Implementation

UniswapV2ERC20.sol Dai.sol Chai.sol

Note that the latter two implementations differ slightly from the presentation given here.

Security Considerations

Though the signer of a Permit may have a certain party in mind to submit their transaction, another party can always front run this transaction and call permit before the intended party. The end result is the same for the Permit signer, however.

Signed Permit messages are censorable. The relaying party can always choose to not submit the Permit after having received it, withholding the option to submit it. The deadline parameter is one mitigation to this. If the signing party holds ETH they can also just submit the Permit themselves, which can render previously signed Permits invalid.

The standard ERC-20 race condition for approvals applies to permit as well.

Copyright

Copyright and related rights waived via CC0.

[1] - Unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this EIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA owner of the contract wallet from the contract wallet itself (which is meant to carry out actions on the owners behalf and holds all of their funds), user interfaces need to be specifically designed to support them. The permit pattern reaps many of the same benefits while requiring little to no change in user interfaces.