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

Add ERC: ERC-1155 Permit Approvals #223

Merged
merged 27 commits into from
May 27, 2024
Merged
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
202 changes: 202 additions & 0 deletions ERCS/erc-7604.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
---
eip: 7604
title: ERC-1155 Permit Approvals
description: Permit approvals for ERC-1155 tokens
author: calvbore (@calvbore)
discussions-to: https://ethereum-magicians.org/t/proposal-for-a-new-eip-erc-2612-style-permits-for-erc1155-nfts/15504
status: Draft
type: Standards Track
category: ERC
created: 2024-01-27
requires: 165, 712, 1155, 5216
calvbore marked this conversation as resolved.
Show resolved Hide resolved
---

## Abstract

The "permit" approval flow for both [ERC-20](./erc-20.md) and [ERC-721](./erc-721.md) are large improvements for the existing UX of the token underlying each ERC. This ERC extends the "permit" pattern to [ERC-1155](./erc-20.md) tokens, borrowing heavily upon both [ERC-4494](./erc-4494.md) and [ERC-2612](./erc-2612.md).

The structure of [ERC-1155](./erc-1155.md) tokens requires a new ERC to account for the token standard's use of both token IDs and balances (also why this ERC requires [ERC-5216](./erc-5216.md)). An additional field of arbitrary `bytes` is added for extra data to be added to the signatures in the case that the permit function would be extended in some way.
Copy link

Choose a reason for hiding this comment

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

I am personally not a fan of the inclusion of an arbitrary bytes data field.
I feel it's unnecessary and out of scope of the requirements of a permit function - 99.99% of the time it will be a waste of gas (about 350 units).
It's also not present in any other permit standards.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am open to removing this, but I would also like to hear some more feedback on possible usecases, or if it is mostly useless, before making a final decision.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@emiliolanzalaco I think I have removed all the references to the additional bytes data paramater. Can you give the doc a look over to make sure I didn't miss any?


## Motivation

The permit structures outlined in both [ERC-4494](./erc-4494) and [ERC-2612](./erc-2612) allows a signed message to create an approval, but are only applicable to their respective underlying tokens ([ERC-721](./erc-721) and [ERC-20](./erc-20)).

While the permit flow described in the above ERCs greatly improves UX and contract interactions they do not allow for any extensibility to the permit flow. It may be that implementors would like to verify additional data than what is strictly required by the permit signature.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Three new functions must be added to ERC-1155 and ERC-5216.

```solidity
interface IERC1155Permit {
function permit(address owner, address operator, uint256 tokenId, uint256 value, uint256 deadline, bytes memory data, bytes memory sig) external;
function nonces(address owner, uint256 tokenId) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
```

The semantics of which are as follows:

For all addresses `owner`, `spender`, uint256's `tokenId`, `value`, `deadline`, and `nonce`, bytes `data` and `sig`, a call to `permit(owner, spender, tokenId, value, deadline, data, sig)` MUST set `allownace(owner, spender, tokenId)` to `value`, increment `nonces(owner, tokenId)` by 1, and emit a corresponding `ApprovalByAmount` event, if and only if the following conditions are met:
calvbore marked this conversation as resolved.
Show resolved Hide resolved
- The current blocktime is less than or equal to `deadline`
- `owner` is not the zero address
- `nonces[owner][tokenId]` (before state update) is equal to `nonce`
- `sig` is a valid `secp256k1`, [ERC-2098](./erc-2098.md), or [ERC-1271](./erc-1271.md) signature from `owner` of the message:
```
keccak256(abi.encodePacked(
hex"1901",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
keccak256("Permit(address owner,address spender,uint256 tokenId,uint256 value,uint256 nonce,uint256 deadline,bytes data)"),
owner,
spender,
tokenId,
value,
nonce,
deadline,
data))
));
```

If any of these conditions are not met the `permit` call MUST revert.

Where `DOMAIN_SEPARATOR` MUST be defined according to [EIP-712](./eip-712.md). The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, and satisfy the requirements of EIP-712, but is otherwise unconstrained. A common choice for `DOMAIN_SEPARATOR` is:
```
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainid,
address(this)
));
```

In other words, the message is the following EIP-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": "tokenId",
"type": "uint256"
},
{
"name": "value",
"type": "uint256"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "data",
"type": "bytes"
}
],
"primaryType": "Permit",
"domain": {
"name": erc1155name,
"version": version,
"chainId": chainid,
"verifyingContract": tokenAddress
},
"message": {
"owner": owner,
"spender": spender,
"tokenId": tokenId,
"value": value,
"nonce": nonce,
"deadline": deadline,
"data": data
}
}}
```

The `permit` function MUST check that the signer is not the zero address.

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

This EIP requires [ERC-165](./erc-165.md). ERC-165 is already required in [ERC-1155](./erc-1155.md), but is further necessary here in order to register the interface of this ERC. Doing so will allow easy verification if an NFT contract has implemented this ERC or not, enabling them to interact accordingly. The ERC-165 interface of this ERC is `0x7409106d`. Contracts implementing this ERC MUST have the `supportsInterface` function return `true` when called with `0x7409106d`.

## Rationale

The `permit` function is sufficient for enabling a `safeTransferFrom` transaction to be made without the need for an additional transaction.

The format 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. Likewise, the `value` argument can be set to `uint(-1)` to create Permits with effectively unlimited allowances.

EIP-712 typed messages are included because of its use in [ERC-4494](./erc-4494.md) and [ERC-2612](./erc-2612.md), which in turn cites widespread adoption in many wallet providers.

This ERC focuses on both the `value` and `tokenId` being approved, ERC-4494 focuses only on the `tokenId`, while ERC-2612 focuses primarily on the `value`. ERC-1155 does not natively support approvals by amount, thus this ERC requires ERC-5216, otherwise a `permit` would grant approval for an account's entire `tokenId` balance.

Whereas ERC-2612 splits signatures into their `v,r,s` components, this ERC opts to instead take a `bytes` array of variable length in order to support [ERC-2098](./erc-1271.md) signatures, which may not be easily separated or reconstructed from `r,s,v` components (65 bytes).

An additional parameter `data` is added to the `permit` in order to offer extensibility to implementations of this ERC.

## Backwards Compatibility

No backward compatibility issues found.

## Test Cases

## Reference Implementation

calvbore marked this conversation as resolved.
Show resolved Hide resolved
## Security Considerations

Special attention should be held to any extra `data` being verified.

The below considerations have been copied from ERC-4494.

Extra care should be taken when creating transfer functions in which `permit` and a transfer function can be used in one function to make sure that invalid permits cannot be used in any way. This is especially relevant for automated NFT platforms, in which a careless implementation can result in the compromise of a number of user assets.

The remaining considerations have been copied from [ERC-2612](./erc-2612.md) with minor adaptation, and are equally relevant here:

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.

Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `ownerOf(tokenId) != address(0)` to avoid `permit` from creating an approval to any `tokenId` which does not have an approval set.

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 `Permit`s invalid.

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

If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split..

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).