From 12821d3a7d59367530805c373f692f942c3ecafa Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 10:40:46 -0700 Subject: [PATCH 01/14] add multi-token std --- EIPS/eip-n.md | 520 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 EIPS/eip-n.md diff --git a/EIPS/eip-n.md b/EIPS/eip-n.md new file mode 100644 index 0000000000000..2386e91e78ac0 --- /dev/null +++ b/EIPS/eip-n.md @@ -0,0 +1,520 @@ +--- +title: Multi-Token Standard +description: A minimal specification for managing multiple tokens by their id in a single contract. +author: Joshua Trujillo (@jtriley) +discussions-to: TODO +status: Draft +type: Standards Track +category: ERC +created: 2023-04-19 +requires: 165 +--- + +## Abstract + +The following standard specifies a multi-token contract as a simplified alternative to the [ERC-1155 Multi-Token Standard](./eip-1155.md). + +## Motivation + +The ERC-1155 standard includes unnecessary features such as requiring recipient accounts with code to implement callbacks returning specific values and batch-calls in the specification. In addition, the single operator permission scheme grants unlimited allowance on every token ID in the contract. Simplification of the transfer scheme, the addition of a more granular and familiar permission scheme, and minor changes to the token URI specification from the ERC-1155 standard are key features of the following specification. Backwards compatibility is deliberately removed only where necessary. Additional features such as batch calls, increase and decrease allowance methods, and other user experience improvements are deliberately omitted in the specification to minimize the required external interface. + +## Rationale + +### Granular Approvals + +While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from ERC-20 allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. + +### Removal of Batching + +While batching operations is useful, its place should not be in the standard itself, but rather on a case-by-case basis. This allows for different tradeoffs to be made in terms of calldata layout, which may be especially useful for specific applications such as roll-ups that commit calldata to global storage. + +### Removal of Required Callbacks + +Callbacks MAY be used within a multi-token compliant contract, but it is not required. This allows for more gas efficient methods by reducing external calls and additional checks. + +### Removal of "Safe" Naming + +The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and ERC-721 standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. + +## 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. + +### Definitions + +- infinite: The maximum value for a uint256 (`2 ** 256 - 1`). +- caller: The caller of the current context (`msg.sender`). +- spender: An account that transfers tokens on behalf of another account. +- operator: An account that has unlimited transfer permissions on all token ids for another account. +- mint: The creation of an amount of tokens. This MAY happen in a mint method or as a transfer from the zero address. +- burn: The removal an amount of tokens. This MAY happen in a burn method or as a transfer from the zero address. + +### Methods + +#### totalSupply + +The total `amount` of a token `id` that exists. + +MUST be equal to the sum of the `balanceOf` of all accounts of the token `id`. + +```yaml +- name: totalSupply + type: function + stateMutability: view + + inputs: + - name: id + type: uint256 + + outputs: + - name: amount + type: uint256 +``` + +#### decimals + +The `amount` of decimals for a token `id`. + +SHOULD be ignored if not explicitly set to a non-zero value. + +```yaml +- name: decimals + type: function + stateMutability: view + + inputs: + - name: id + type: uint256 + + outputs: + - name: amount + type: uint8 +``` + +#### balanceOf + +The total `amount` of a token `id` that an `owner` owns. + +```yaml +- name: balanceOf + type: function + stateMutability: view + + inputs: + - name: owner + type: address + - name: id + type: uint256 + + outputs: + - name: amount + type: uint256 +``` + +#### allowance + +The total `amount` of a token id that a spender is permitted to transfer on behalf of an owner. + +```yaml +- name: allowance + type: function + stateMutability: view + + inputs: + - name: owner + type: address + - name: spender + type: address + - name: id + type: uint256 + + outputs: + - name: amount + type: uint256 +``` + +#### transfer + +Transfers an `amount` of a token `id` from the caller to the `receiver`. + +MUST revert when the caller's balance for the token `id` is insufficient. + +MUST log the `Transfer` event. + +```yaml +- name: transfer + type: function + stateMutability: nonpayable + + inputs: + - name: receiver + type: address + - name: id + type: uint256 + - name: amount + type: uint256 + + outputs: [] +``` + +#### transferFrom + +Transfers an `amount` of a token `id` from a `sender` to a `receiver` by the caller. + +MUST revert when the caller is not an operator for the `sender` and the caller's allowance for the token `id` for the `sender` is insufficient. + +MUST revert when the `sender`'s balance for the token id is insufficient. + +MUST log the `Transfer` event. + +MUST decrease the caller's `allowance` by the same `amount` of the `sender`'s balance decrease if the caller's `allowance` is not infinite. + +SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the `allowance` is infinite. + +SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the caller is an operator. + +```yaml +- name: transferFrom + type: function + stateMutability: nonpayable + + inputs: + - name: sender + type: address + - name: receiver + type: address + - name: id + type: uint256 + - name: amount + type: uint256 + + outputs: [] +``` + +#### approve + +Approves an `amount` of a token `id` that a `spender` is permitted to transfer on behalf of the caller. + +MUST set the `allowance` of the `spender` of the token `id` for the caller to the `amount`. + +MUST log the `Approval` event. + +```yaml +- name: approve + type: function + stateMutability: nonpayable + + inputs: + - name: spender + type: address + - name: id + type: uint256 + - name: amount + type: uint256 + + outputs: [] +``` + +#### setOperator + +Grants or revokes unlimited transfer permissions for a `spender` for any token `id` on behalf of the caller. + +MUST set the operator status to the `approved` value. + +MUST log the `OperatorSet` event. + +```yaml +- name: setOperator + type: function + stateMutability: nonpayable + + inputs: + - name: spender + type: address + - name: approved + type: bool +``` + +### Events + +#### Transfer + +The `sender` has transferred an `amount` of a token `id` to a `receiver`. + +MUST be logged when an `amount` of a token `id` is transferred from one account to another. + +SHOULD be logged with the `sender` address as the zero address when an `amount` of a token `id` is minted. + +SHOULD be logged with the `receiver` address as the zero address when an `amount` of a token `id` is burned. + +```yaml +- name: Transfer + type: event + + inputs: + - name: sender + indexed: true + type: address + - name: receiver + indexed: true + type: address + - name: id + indexed: true + type: address + - name: amount + indexed: false + type: address +``` + +#### OperatorSet + +The `owner` has set the `approved` status to a `spender`. + +MUST be logged when the operator status is set. + +MAY be logged when the operator status is set to the same status it was before the current call. + +```yaml +- name: OperatorSet + type: event + + inputs: + - name: owner + indexed: true + type: address + - name: spender + indexed: true + type: address + - name: approved + indexed: false + type: bool +``` + +#### Approval + +The `owner` has approved a `spender` to transfer an `amount` of a token `id` to be transferred on the owner's behalf. + +MUST be logged when the `allowance` is set by an `owner`. + +```yaml +- name: Approval + type: event + + inputs: + - name: owner + indexed: true + type: address + - name: spender + indexed: true + type: address + - name: id + indexed: true + type: uint256 + - name: amount + indexed: false + type: uint256 +``` + +## Interface ID + +The interface ID is `0x8da179e8`. + +## Extensions + +### ERCNMetadata + +#### Methods + +##### name + +The `name` of the contract. + +```yaml +- name: name + type: function + stateMutability: view + + inputs: [] + + outputs: + - name: name + type: string +``` + +##### symbol + +The ticker `symbol` of the contract. + +```yaml +- name: symbol + type: function + stateMutability: view + + inputs: [] + + outputs: + - name: symbol + type: string +``` + +##### tokenURI + +The `URI` for a token `id`. + +MAY revert if the token `id` does not exist. + +```yaml +- name: tokenURI + type: function + stateMutability: view + + inputs: + - name: id + type: uint256 + + outputs: + - name: uri + type: string +``` + +#### Metadata Structure + +The metadata specification closely follows that of the ERC-721 JSON schema. + +```json +{ + "title": "Asset Metadata", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Identifies the token" + }, + "description": { + "type": "string", + "description": "Describes the token" + }, + "image": { + "type": "string", + "description": "A URI pointing to an image resource." + } + } +} +``` + +## Backwards Compatibility + +This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. + +## Reference Implementation + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +/// @title ERCN Multi-Token Reference Implementation +/// @author jtriley.eth +contract ERCN { + /// @dev Thrown when owner balance for id is insufficient. + /// @param owner The address of the owner. + /// @param id The id of the token. + error InsufficientBalance(address owner, uint256 id); + + /// @dev Thrown when spender allowance for id is insufficient. + /// @param spender The address of the spender. + /// @param id The id of the token. + error InsufficientPermission(address spender, uint256 id); + + /// @notice The event emitted when a transfer occurs. + /// @param sender The address of the sender. + /// @param receiver The address of the receiver. + /// @param id The id of the token. + /// @param amount The amount of the token. + event Transfer(address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount); + + /// @notice The event emitted when an operator is set. + /// @param owner The address of the owner. + /// @param spender The address of the spender. + /// @param approved The approval status. + event OperatorSet(address indexed owner, address indexed spender, bool approved); + + /// @notice The event emitted when an approval occurs. + /// @param owner The address of the owner. + /// @param spender The address of the spender. + /// @param id The id of the token. + /// @param amount The amount of the token. + event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount); + + /// @notice The total supply of each id. + mapping(uint256 id => uint256 amount) public totalSupply; + + /// @notice The number of decimals for each id. + mapping(uint256 id => uint8 amount) public decimals; + + /// @notice Owner balance of an id. + mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf; + + /// @notice Spender allowance of an id. + mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance; + + /// @notice Checks if a spender is approved by an owner as an operator. + mapping(address owner => mapping(address spender => bool)) public isOperator; + + /// @notice Transfers an amount of an id from the caller to a receiver. + /// @param receiver The address of the receiver. + /// @param id The id of the token. + /// @param amount The amount of the token. + function transfer(address receiver, uint256 id, uint256 amount) public { + if (balanceOf[msg.sender][id] < amount) revert InsufficientBalance(msg.sender, id); + balanceOf[msg.sender][id] -= amount; + balanceOf[receiver][id] += amount; + emit Transfer(msg.sender, receiver, id, amount); + } + + /// @notice Transfers an amount of an id from a sender to a receiver. + /// @param sender The address of the sender. + /// @param receiver The address of the receiver. + /// @param id The id of the token. + /// @param amount The amount of the token. + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public { + if (sender != msg.sender && !isOperator[sender][msg.sender]) { + if (allowance[sender][msg.sender][id] < amount) { + revert InsufficientPermission(msg.sender, id); + } + allowance[sender][msg.sender][id] -= amount; + } + if (balanceOf[sender][id] < amount) revert InsufficientBalance(sender, id); + balanceOf[sender][id] -= amount; + balanceOf[receiver][id] += amount; + emit Transfer(sender, receiver, id, amount); + } + + /// @notice Approves an amount of an id to a spender. + /// @param spender The address of the spender. + /// @param id The id of the token. + /// @param amount The amount of the token. + function approve(address spender, uint256 id, uint256 amount) public { + allowance[msg.sender][spender][id] = amount; + emit Approval(msg.sender, spender, id, amount); + } + + /// @notice Sets or removes a spender as an operator for the caller. + /// @param spender The address of the spender. + /// @param approved The approval status. + function setOperator(address spender, bool approved) public { + isOperator[msg.sender][spender] = approved; + emit OperatorSet(msg.sender, spender, approved); + } + + /// @notice Checks if a contract implements an interface. + /// @param interfaceId The interface identifier, as specified in ERC-165. + /// @return supported True if the contract implements `interfaceId` and + function supportsInterface(bytes4 interfaceId) public pure returns (bool supported) { + return interfaceId == 0x8da179e8 || interfaceId == 0x01ffc9a7; + } +} +``` + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 126a4b04a41ea9494a769cce15feff199defffa5 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 10:45:11 -0700 Subject: [PATCH 02/14] update eip # --- EIPS/{eip-n.md => eip-6909.md} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename EIPS/{eip-n.md => eip-6909.md} (99%) diff --git a/EIPS/eip-n.md b/EIPS/eip-6909.md similarity index 99% rename from EIPS/eip-n.md rename to EIPS/eip-6909.md index 2386e91e78ac0..b829df89e9aaf 100644 --- a/EIPS/eip-n.md +++ b/EIPS/eip-6909.md @@ -1,4 +1,5 @@ --- +eip: 6909 title: Multi-Token Standard description: A minimal specification for managing multiple tokens by their id in a single contract. author: Joshua Trujillo (@jtriley) @@ -321,7 +322,7 @@ The interface ID is `0x8da179e8`. ## Extensions -### ERCNMetadata +### ERC6909Metadata #### Methods @@ -412,9 +413,9 @@ This is not backwards compatible with ERC-1155 as some methods are removed. howe // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -/// @title ERCN Multi-Token Reference Implementation +/// @title ERC6909 Multi-Token Reference Implementation /// @author jtriley.eth -contract ERCN { +contract ERC6909 { /// @dev Thrown when owner balance for id is insufficient. /// @param owner The address of the owner. /// @param id The id of the token. From 9a72e07f21b0f21e6c7ae6295cb3b4966b44aebc Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 10:47:10 -0700 Subject: [PATCH 03/14] add eth magician link --- EIPS/eip-6909.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index b829df89e9aaf..2e0a5f58103d2 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -3,7 +3,7 @@ eip: 6909 title: Multi-Token Standard description: A minimal specification for managing multiple tokens by their id in a single contract. author: Joshua Trujillo (@jtriley) -discussions-to: TODO +discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 status: Draft type: Standards Track category: ERC From d033b01dbf19caf88cf08257a0ffb63e83bcd48f Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 11:16:54 -0700 Subject: [PATCH 04/14] burn typo --- EIPS/eip-6909.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 2e0a5f58103d2..368e803274214 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -48,7 +48,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - spender: An account that transfers tokens on behalf of another account. - operator: An account that has unlimited transfer permissions on all token ids for another account. - mint: The creation of an amount of tokens. This MAY happen in a mint method or as a transfer from the zero address. -- burn: The removal an amount of tokens. This MAY happen in a burn method or as a transfer from the zero address. +- burn: The removal an amount of tokens. This MAY happen in a burn method or as a transfer to the zero address. ### Methods From b4fee8d1b375e615f0b9c73779df69841e26957f Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 12:16:56 -0700 Subject: [PATCH 05/14] format, nits --- EIPS/eip-6909.md | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 368e803274214..e1fc1696e96f8 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -1,6 +1,6 @@ --- eip: 6909 -title: Multi-Token Standard +title: Multi-Token description: A minimal specification for managing multiple tokens by their id in a single contract. author: Joshua Trujillo (@jtriley) discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 @@ -13,30 +13,12 @@ requires: 165 ## Abstract -The following standard specifies a multi-token contract as a simplified alternative to the [ERC-1155 Multi-Token Standard](./eip-1155.md). +The following standard specifies a multi-token contract as a simplified alternative to the [ERC-1155](./eip-1155.md) Multi-Token Standard. ## Motivation The ERC-1155 standard includes unnecessary features such as requiring recipient accounts with code to implement callbacks returning specific values and batch-calls in the specification. In addition, the single operator permission scheme grants unlimited allowance on every token ID in the contract. Simplification of the transfer scheme, the addition of a more granular and familiar permission scheme, and minor changes to the token URI specification from the ERC-1155 standard are key features of the following specification. Backwards compatibility is deliberately removed only where necessary. Additional features such as batch calls, increase and decrease allowance methods, and other user experience improvements are deliberately omitted in the specification to minimize the required external interface. -## Rationale - -### Granular Approvals - -While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from ERC-20 allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. - -### Removal of Batching - -While batching operations is useful, its place should not be in the standard itself, but rather on a case-by-case basis. This allows for different tradeoffs to be made in terms of calldata layout, which may be especially useful for specific applications such as roll-ups that commit calldata to global storage. - -### Removal of Required Callbacks - -Callbacks MAY be used within a multi-token compliant contract, but it is not required. This allows for more gas efficient methods by reducing external calls and additional checks. - -### Removal of "Safe" Naming - -The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and ERC-721 standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. - ## 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. @@ -316,13 +298,29 @@ MUST be logged when the `allowance` is set by an `owner`. type: uint256 ``` -## Interface ID +## Rationale -The interface ID is `0x8da179e8`. +### Granular Approvals -## Extensions +While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from ERC-20 allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. + +### Removal of Batching + +While batching operations is useful, its place should not be in the standard itself, but rather on a case-by-case basis. This allows for different tradeoffs to be made in terms of calldata layout, which may be especially useful for specific applications such as roll-ups that commit calldata to global storage. + +### Removal of Required Callbacks + +Callbacks MAY be used within a multi-token compliant contract, but it is not required. This allows for more gas efficient methods by reducing external calls and additional checks. + +### Removal of "Safe" Naming + +The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and [ERC-721](./eip-721.md) standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. + +### Interface ID + +The interface ID is `0x8da179e8`. -### ERC6909Metadata +### ERC-6909Metadata Extension #### Methods @@ -405,7 +403,7 @@ The metadata specification closely follows that of the ERC-721 JSON schema. ## Backwards Compatibility -This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. +This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the [ERC-20](./eip-20.md), ERC-721, and ERC-1155 standards. ## Reference Implementation From dc569d41c35ff60da9a4a3735dea2e7c1cceb027 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 12:33:35 -0700 Subject: [PATCH 06/14] add isOperator --- EIPS/eip-6909.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index e1fc1696e96f8..918bfb3508608 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -116,6 +116,26 @@ The total `amount` of a token id that a spender is permitted to transfer on beha type: uint256 ``` +#### isOperator + +Returns `true` if the `spender` is approved as an operator for an `owner`. + +```yaml +- name: isOperator + type: function + stateMutability: view + + inputs: + - name: owner + type: address + - name: spender + type: address + + outputs: + - name: status + type: bool +``` + #### transfer Transfers an `amount` of a token `id` from the caller to the `receiver`. From 39947a9e5f248ecd0ac61580dd9416f7459e973a Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 12:50:07 -0700 Subject: [PATCH 07/14] update metadata extension header --- EIPS/eip-6909.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 918bfb3508608..83e247b2c1aff 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -340,7 +340,7 @@ The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, esp The interface ID is `0x8da179e8`. -### ERC-6909Metadata Extension +### Metadata Extension #### Methods From c9292dc18aae39122d42247c2ccbe658e9ed4856 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 12:52:06 -0700 Subject: [PATCH 08/14] mv eip-20 link --- EIPS/eip-6909.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 83e247b2c1aff..4b87bd7f499d6 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -322,7 +322,7 @@ MUST be logged when the `allowance` is set by an `owner`. ### Granular Approvals -While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from ERC-20 allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. +While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from [ERC-20](./eip-20.md) allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. ### Removal of Batching @@ -423,7 +423,7 @@ The metadata specification closely follows that of the ERC-721 JSON schema. ## Backwards Compatibility -This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the [ERC-20](./eip-20.md), ERC-721, and ERC-1155 standards. +This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. ## Reference Implementation From b8892fed40b51c4124d3428a5dd9d8bb7e08ed15 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 19 Apr 2023 18:56:00 -0700 Subject: [PATCH 09/14] mv decimals to metadata; add security considerations --- EIPS/eip-6909.md | 89 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 4b87bd7f499d6..ea4abd67cf5a6 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -54,26 +54,6 @@ MUST be equal to the sum of the `balanceOf` of all accounts of the token `id`. type: uint256 ``` -#### decimals - -The `amount` of decimals for a token `id`. - -SHOULD be ignored if not explicitly set to a non-zero value. - -```yaml -- name: decimals - type: function - stateMutability: view - - inputs: - - name: id - type: uint256 - - outputs: - - name: amount - type: uint8 -``` - #### balanceOf The total `amount` of a token `id` that an `owner` owns. @@ -170,7 +150,7 @@ MUST revert when the `sender`'s balance for the token id is insufficient. MUST log the `Transfer` event. -MUST decrease the caller's `allowance` by the same `amount` of the `sender`'s balance decrease if the caller's `allowance` is not infinite. +MUST decrease the caller's `allowance` by the same `amount` of the `sender`'s balance decrease if the caller is not an operator for the `sender` and the caller's `allowance` is not infinite. SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the `allowance` is infinite. @@ -376,6 +356,26 @@ The ticker `symbol` of the contract. type: string ``` +#### decimals + +The `amount` of decimals for a token `id`. + +MAY be ignored if not explicitly set to a non-zero value. + +```yaml +- name: decimals + type: function + stateMutability: view + + inputs: + - name: id + type: uint256 + + outputs: + - name: amount + type: uint8 +``` + ##### tokenURI The `URI` for a token `id`. @@ -467,9 +467,6 @@ contract ERC6909 { /// @notice The total supply of each id. mapping(uint256 id => uint256 amount) public totalSupply; - /// @notice The number of decimals for each id. - mapping(uint256 id => uint8 amount) public decimals; - /// @notice Owner balance of an id. mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf; @@ -534,6 +531,50 @@ contract ERC6909 { } ``` +## Security Considerations + +### Approvals and Operators + +The specification includes two token transfer permission systems, the "allowance" and "operator" +models. There are two security considerations in regards to delegating permission to transfer. + +The first consideration is consistent with all delegated permission models. Any account with an allowance may transfer the full allowance for any reason at any time until the allowance is revoked. Any account with operator permissions may transfer any amount of any token id on behalf of the owner until the operator permission is revoked. + +The second consideration is unique to systems with both delegated permission models. In accordance with the `transferFrom` method, spenders with operator permission are not subject to allowance restrictions, spenders with infinite approvals SHOULD NOT have their allowance deducted on delegated transfers, but spenders with non-infinite approvals MUST have their balance deducted on delegated transfers. A spender with both operator permission and a non-infinite approval may introduce functional ambiguity. If the operator permission takes precedence, that is, the allowance is never deducted when a spender has operator permissions, there is no ambiguity. However, in the event the allowance takes precedence over the operator permissions, an additional branch may be necessary to ensure an allowance underflow does not occur. The following is an example of such an issue. + +```solidity +contract ERC6909OperatorPrecedence { + // -- snip -- + + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public { + // check if `isOperator` first + if (msg.sender != sender && !isOperator[sender][msg.sender]) { + require(allowance[sender][msg.sender][id] >= amount, "insufficient allowance"); + allowance[sender][msg.sender][id] -= amount; + } + + // -- snip -- + } +} + +contract ERC6909AllowancePrecedence { + // -- snip -- + + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public { + // check if allowance is sufficient first + if (msg.sender != sender && allowance[sender][msg.sender][id] < amount) { + require(isOperator[sender][msg.sender], "insufficient allowance"); + } + + // ERROR: when allowance is insufficient, this panics due to arithmetic underflow, regardless of + // whether the caller has operator permissions. + allowance[sender][msg.sender][id] -= amount; + + // -- snip + } +} +``` + ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). From b5a78073afa68e70b04bd3d5129cc7566278bc71 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Thu, 20 Apr 2023 12:24:41 -0700 Subject: [PATCH 10/14] decimals wording, interfaceId update --- EIPS/eip-6909.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index ea4abd67cf5a6..074dbae829f58 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -318,7 +318,7 @@ The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, esp ### Interface ID -The interface ID is `0x8da179e8`. +The interface ID is `0xb2e69f8a`. ### Metadata Extension @@ -360,8 +360,6 @@ The ticker `symbol` of the contract. The `amount` of decimals for a token `id`. -MAY be ignored if not explicitly set to a non-zero value. - ```yaml - name: decimals type: function From 74c991a1b9a9a16a59d445c848b3645eee601f7c Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Mon, 24 Apr 2023 09:56:20 -0700 Subject: [PATCH 11/14] add mint/burn, update interfaceid --- EIPS/eip-6909.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 074dbae829f58..c1c755585de00 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -524,7 +524,21 @@ contract ERC6909 { /// @param interfaceId The interface identifier, as specified in ERC-165. /// @return supported True if the contract implements `interfaceId` and function supportsInterface(bytes4 interfaceId) public pure returns (bool supported) { - return interfaceId == 0x8da179e8 || interfaceId == 0x01ffc9a7; + return interfaceId == 0xb2e69f8a || interfaceId == 0x01ffc9a7; + } + + function _mint(address receiver, uint256 id, uint256 amount) internal { + // WARNING: important safety checks should preceed calls to this method. + balanceOf[receiver][id] += amount; + totalSupply[id] += amount; + emit Transfer(address(0), receiver, id, amount); + } + + function _burn(address sender, uint256 id, uint256 amount) internal { + // WARNING: important safety checks should preceed calls to this method. + balanceOf[sender][id] -= amount; + totalSupply[id] -= amount; + emit Transfer(sender, address(0), id, amount); } } ``` From a250546116a8eee5d1a2b02cc363c1277d6b8704 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Wed, 10 May 2023 20:32:26 -0700 Subject: [PATCH 12/14] breakout metadata uri --- EIPS/eip-6909.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index c1c755585de00..6ad2ee50c6de2 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -356,7 +356,7 @@ The ticker `symbol` of the contract. type: string ``` -#### decimals +##### decimals The `amount` of decimals for a token `id`. @@ -374,12 +374,18 @@ The `amount` of decimals for a token `id`. type: uint8 ``` +### Metadata URI Extension + +#### Methods + ##### tokenURI The `URI` for a token `id`. MAY revert if the token `id` does not exist. +MUST replace occurrences of `{id}` in the returned URI string by the client. + ```yaml - name: tokenURI type: function @@ -398,6 +404,8 @@ MAY revert if the token `id` does not exist. The metadata specification closely follows that of the ERC-721 JSON schema. +MUST replace occurrences of `{id}` in the returned URI string by the client. + ```json { "title": "Asset Metadata", From 826c38e46f56bc9f92c35c124c44a88539f754e0 Mon Sep 17 00:00:00 2001 From: Riley Date: Tue, 30 May 2023 10:12:00 -0500 Subject: [PATCH 13/14] typo Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> --- EIPS/eip-6909.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 6ad2ee50c6de2..284934bd70224 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -429,7 +429,7 @@ MUST replace occurrences of `{id}` in the returned URI string by the client. ## Backwards Compatibility -This is not backwards compatible with ERC-1155 as some methods are removed. however, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. +This is not backwards compatible with ERC-1155 as some methods are removed. However, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. ## Reference Implementation From 83835fb3c22a33f3cd05f3b0c15013dfb09dc7f7 Mon Sep 17 00:00:00 2001 From: Joshua Trujillo Date: Tue, 30 May 2023 11:49:19 -0400 Subject: [PATCH 14/14] title, license, nits --- EIPS/eip-6909.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 284934bd70224..b265e4b5b746c 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -1,6 +1,6 @@ --- eip: 6909 -title: Multi-Token +title: Minimal Multi-Token Interface description: A minimal specification for managing multiple tokens by their id in a single contract. author: Joshua Trujillo (@jtriley) discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 @@ -17,7 +17,13 @@ The following standard specifies a multi-token contract as a simplified alternat ## Motivation -The ERC-1155 standard includes unnecessary features such as requiring recipient accounts with code to implement callbacks returning specific values and batch-calls in the specification. In addition, the single operator permission scheme grants unlimited allowance on every token ID in the contract. Simplification of the transfer scheme, the addition of a more granular and familiar permission scheme, and minor changes to the token URI specification from the ERC-1155 standard are key features of the following specification. Backwards compatibility is deliberately removed only where necessary. Additional features such as batch calls, increase and decrease allowance methods, and other user experience improvements are deliberately omitted in the specification to minimize the required external interface. +The ERC-1155 standard includes unnecessary features such as requiring recipient accounts with code to implement callbacks returning specific values and batch-calls in the specification. In addition, the single operator permission scheme grants unlimited allowance on every token ID in the contract. Backwards compatibility is deliberately removed only where necessary. Additional features such as batch calls, increase and decrease allowance methods, and other user experience improvements are deliberately omitted in the specification to minimize the required external interface. + +According to ERC-1155, callbacks are required for each transfer and batch transfer to contract accounts. This requires potentially unnecessary external calls to the recipient when the recipient account is a contract account. While this behavior may be desirable in some cases, there is no option to opt-out of this behavior, as is the case for [ERC-721](./eip-721.md) having both `transferFrom` and `safeTransferFrom`. In addition to runtime performance of the token contract itself, it also impacts the runtime performance and codesize of recipient contract accounts, requiring multiple callback functions and return values to recieve the tokens. + +Batching transfers, while useful, are excluded from this standard to allow for opinionated batch transfer operations on different implementations. For example, a different ABI encoding may provide different benefits in different environments such as calldata size optimization for rollups with calldata storage commitments or runtime performance for environments with expensive gas fees. + +A hybrid allowance-operator permission scheme enables granular yet scalable controls on token approvals. Allowances enable an external account to transfer tokens of a single token ID on a user's behalf w by their ID while operators are granted full transfer permission for all token IDs for the user. ## Specification @@ -34,7 +40,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S ### Methods -#### totalSupply +#### `totalSupply` The total `amount` of a token `id` that exists. @@ -54,7 +60,7 @@ MUST be equal to the sum of the `balanceOf` of all accounts of the token `id`. type: uint256 ``` -#### balanceOf +#### `balanceOf` The total `amount` of a token `id` that an `owner` owns. @@ -74,7 +80,7 @@ The total `amount` of a token `id` that an `owner` owns. type: uint256 ``` -#### allowance +#### `allowance` The total `amount` of a token id that a spender is permitted to transfer on behalf of an owner. @@ -96,7 +102,7 @@ The total `amount` of a token id that a spender is permitted to transfer on beha type: uint256 ``` -#### isOperator +#### `isOperator` Returns `true` if the `spender` is approved as an operator for an `owner`. @@ -116,7 +122,7 @@ Returns `true` if the `spender` is approved as an operator for an `owner`. type: bool ``` -#### transfer +#### `transfer` Transfers an `amount` of a token `id` from the caller to the `receiver`. @@ -140,7 +146,7 @@ MUST log the `Transfer` event. outputs: [] ``` -#### transferFrom +#### `transferFrom` Transfers an `amount` of a token `id` from a `sender` to a `receiver` by the caller. @@ -174,7 +180,7 @@ SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` outputs: [] ``` -#### approve +#### `approve` Approves an `amount` of a token `id` that a `spender` is permitted to transfer on behalf of the caller. @@ -198,7 +204,7 @@ MUST log the `Approval` event. outputs: [] ``` -#### setOperator +#### `setOperator` Grants or revokes unlimited transfer permissions for a `spender` for any token `id` on behalf of the caller. @@ -220,7 +226,7 @@ MUST log the `OperatorSet` event. ### Events -#### Transfer +#### `Transfer` The `sender` has transferred an `amount` of a token `id` to a `receiver`. @@ -249,7 +255,7 @@ SHOULD be logged with the `receiver` address as the zero address when an `amount type: address ``` -#### OperatorSet +#### `OperatorSet` The `owner` has set the `approved` status to a `spender`. @@ -273,7 +279,7 @@ MAY be logged when the operator status is set to the same status it was before t type: bool ``` -#### Approval +#### `Approval` The `owner` has approved a `spender` to transfer an `amount` of a token `id` to be transferred on the owner's behalf. @@ -314,7 +320,7 @@ Callbacks MAY be used within a multi-token compliant contract, but it is not req ### Removal of "Safe" Naming -The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and [ERC-721](./eip-721.md) standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. +The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and ERC-721 standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. ### Interface ID @@ -434,7 +440,7 @@ This is not backwards compatible with ERC-1155 as some methods are removed. Howe ## Reference Implementation ```solidity -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: CC0-1.0 pragma solidity 0.8.19; /// @title ERC6909 Multi-Token Reference Implementation