diff --git a/EIPS/eip-777.md b/EIPS/eip-777.md index 460482f2c61b6..298dd755ce9cf 100644 --- a/EIPS/eip-777.md +++ b/EIPS/eip-777.md @@ -67,7 +67,10 @@ interface ERC777Token { function granularity() external view returns (uint256); function defaultOperators() external view returns (address[] memory); - function isOperatorFor(address operator, address holder) external view returns (bool); + function isOperatorFor( + address operator, + address holder + ) external view returns (bool); function authorizeOperator(address operator) external; function revokeOperator(address operator) external; @@ -81,7 +84,12 @@ interface ERC777Token { ) external; function burn(uint256 amount, bytes calldata data) external; - function operatorBurn(address from, uint256 amount, bytes calldata data, bytes calldata operatorData) external; + function operatorBurn( + address from, + uint256 amount, + bytes calldata data, + bytes calldata operatorData + ) external; event Sent( address indexed operator, @@ -91,9 +99,24 @@ interface ERC777Token { bytes data, bytes operatorData ); - event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); - event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); - event AuthorizedOperator(address indexed operator, address indexed holder); + event Minted( + address indexed operator, + address indexed to, + uint256 amount, + bytes data, + bytes operatorData + ); + event Burned( + address indexed operator, + address indexed from, + uint256 amount, + bytes data, + bytes operatorData + ); + event AuthorizedOperator( + address indexed operator, + address indexed holder + ); event RevokedOperator(address indexed operator, address indexed holder); } ``` @@ -101,9 +124,11 @@ The token contract MUST implement the above interface. The implementation MUST follow the specifications described below. The token contract MUST register the `ERC777Token` interface with its own address via [ERC1820]. -This is done by calling the `setInterfaceImplementer` function on the ERC1820 registry -with the token contract address as both the address and the implementer -and the `keccak256` hash of `ERC777Token` as the interface hash. + +> This is done by calling the `setInterfaceImplementer` function on the [ERC1820] registry +> with the token contract address as both the address and the implementer +> and the `keccak256` hash of `ERC777Token` (`0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054`) +> as the interface hash. If the contract has a switch to enable or disable ERC777 functions, every time the switch is triggered, the token MUST register or unregister the `ERC777Token` interface for its own address accordingly via ERC1820. @@ -112,7 +137,7 @@ the `keccak256` hash of `ERC777Token` as the interface hash and `0x0` as the imp (See [Set An Interface For An Address][erc1820-set] in [ERC1820] for more details.) When interacting with the token contract, all amounts and balances MUST be unsigned integers. -I.e. Internally, all values are stored as a denomination of 1E-18 of a token. +I.e. internally, all values are stored as a denomination of 1E-18 of a token. The display denomination—to display any amount to the end user—MUST be 1018 of the internal denomination. @@ -204,7 +229,7 @@ The following rules MUST be applied regarding the *granularity*: - The *granularity* value MUST NOT be changed, ever. -- The *granularity* value MUST be greater or equal to `1`. +- The *granularity* value MUST be greater than or equal to `1`. - All balances MUST be a multiple of the granularity. @@ -214,7 +239,7 @@ The following rules MUST be applied regarding the *granularity*: - Any operation that would result in a balance that's not a multiple of the *granularity* value MUST be considered invalid, and the transaction MUST `revert`. -*NOTE*: Most of the tokens SHOULD be fully partition-able. +*NOTE*: Most tokens SHOULD be fully partition-able. I.e., this function SHOULD return `1` unless there is a good reason for not allowing any fraction of the token. > **identifier:** `556f0dc7` @@ -258,7 +283,7 @@ The rules below apply to *default operators*: - `AuthorizedOperator` events MUST NOT be emitted when defining *default operators*. -- A *holder* MUST be allowed revoke a *default operator* +- A *holder* MUST be allowed to revoke a *default operator* (unless the *holder* is the *default operator* in question). - A *holder* MUST be allowed to re-authorize a previously revoked *default operator*. @@ -371,7 +396,10 @@ as an *operator* for itself (i.e., if `operator` is equal to `msg.sender`). **`isOperatorFor` function** ``` solidity -function isOperatorFor(address operator, address holder) external view returns (bool) +function isOperatorFor( + address operator, + address holder +) external view returns (bool) ``` Indicate whether the `operator` address is an *operator* of the `holder` address. @@ -427,6 +455,10 @@ The token contract MUST `revert` when sending in any of the following cases: - Any of the resulting balances becomes negative, i.e. becomes less than zero (`0`). +- The `tokensToSend` hook of the *holder* `revert`s. + +- The `tokensReceived` hook of the *recipient* `revert`s. + The token contract MAY send tokens from many *holders*, to many *recipients*, or both. In this case: - The previous send rules MUST apply to all the *holders* and all the *recipients*. @@ -438,7 +470,7 @@ The token contract MAY send tokens from many *holders*, to many *recipients*, or *NOTE*: Mechanisms such as applying a fee on a send is considered as a send to multiple *recipients*: the intended *recipient* and the fee *recipient*. -*NOTE*: Transfer of tokens MAY be chained. +*NOTE*: Movements of tokens MAY be chained. For example, if a contract upon receiving tokens sends them further to another address. In this case, the previous send rules apply to each send, in order. @@ -468,7 +500,14 @@ In most of the cases the recipient would ignore the `operatorData`, or at most, **`Sent` event** ``` solidity -event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData) +event Sent( + address indexed operator, + address indexed from, + address indexed to, + uint256 amount, + bytes data, + bytes operatorData +) ``` Indicate a send of `amount` of tokens from the `from` address to the `to` address by the `operator` address. @@ -505,7 +544,13 @@ The *operator* and the *holder* MUST both be the `msg.sender`. **`operatorSend` function** ``` solidity -function operatorSend(address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external +function operatorSend( + address from, + address to, + uint256 amount, + bytes calldata data, + bytes calldata operatorData +) external ``` Send the `amount` of tokens on behalf of the address `from` to the address `to`. @@ -536,7 +581,7 @@ as the minting process is generally specific for every token. Nonetheless, the rules below MUST be respected when minting for a *recipient*: -- Tokens MAY be minted for any *recipient* address. +- Tokens MAY be minted for any *recipient* address (except `0x0`). - The total supply MUST be increased by the amount of tokens minted. @@ -557,6 +602,7 @@ The token contract MUST `revert` when minting in any of the following cases: - The resulting *recipient* balance after the mint is not a multiple of the *granularity* defined by the token contract. - The *recipient* is a contract, and it does not implement the `ERC777TokensRecipient` interface via [ERC1820]. - The address of the *recipient* is `0x0`. +- The `tokensReceived` hook of the *recipient* `revert`s. *NOTE*: The initial token supply at the creation of the token contract MUST be considered as minting for the amount of the initial supply to the address(es) receiving the initial supply. @@ -588,7 +634,13 @@ The `tokensReceived()` hooks MAY use the information to decide if it wish to rej **`Minted` event** ``` solidity -event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData) +event Minted( + address indexed operator, + address indexed to, + uint256 amount, + bytes data, + bytes operatorData +) ``` Indicate the minting of `amount` of tokens to the `to` address by the `operator` address. @@ -612,7 +664,7 @@ The token contract MAY also define other functions to burn tokens. The rules below MUST be respected when burning the tokens of a *holder*: -- Tokens MAY be burned from any *holder* address. +- Tokens MAY be burned from any *holder* address (except `0x0`). - The total supply MUST be decreased by the amount of tokens burned. @@ -640,6 +692,8 @@ The token contract MUST `revert` when burning in any of the following cases: - The address of the *holder* is `0x0`. +- The `tokensToSend` hook of the *holder* `revert`s. + *[ERC20] compatibility requirement*: While a `Sent` event MUST NOT be emitted when burning; if the token contract is [ERC20] enabled, a `Transfer` event with the `to` parameter set to `0x0` SHOULD be emitted. @@ -664,7 +718,13 @@ MAY use the information to decide if they wish to reject the transaction. **`Burned` event** ``` solidity -event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); +event Burned( + ddress indexed operator, + address indexed from, + uint256 amount, + bytes data, + bytes operatorData +); ``` Indicate the burning of `amount` of tokens from the `from` address by the `operator` address. @@ -674,7 +734,7 @@ Indicate the burning of `amount` of tokens from the `from` address by the `opera > **parameters** > `operator`: Address which triggered the burn. > `from`: *Holder* whose tokens were burned. -> `amount`: Number of tokens burned. +> `amount`: Number of tokens burned. > `data`: Information provided by the *holder*. > `operatorData`: Information provided by the *operator*. @@ -693,13 +753,18 @@ The *operator* and the *holder* MUST both be the `msg.sender`. > **identifier:** `fe9d9303` > **parameters** -> `amount`: Number of tokens to burn. +> `amount`: Number of tokens to burn. > `data`: Information provided by the *holder*. **`operatorBurn` function** ``` solidity -function operatorBurn(address from, uint256 amount, bytes calldata data, bytes calldata operatorData) external +function operatorBurn( + address from, + uint256 amount, + bytes calldata data, + bytes calldata operatorData +) external ``` Burn the `amount` of tokens on behalf of the address `from`. @@ -729,6 +794,12 @@ The `tokensToSend` hook notifies of any request to decrement the balance (send a Any address (regular or contract) wishing to be notified of token debits from their address MAY register the address of a contract implementing the `ERC777TokensSender` interface described below via [ERC1820]. +> This is done by calling the `setInterfaceImplementer` function on the [ERC1820] registry +> with the *holder* address as the address, +> the `keccak256` hash of `ERC777TokensSender` +> (`0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895`) as the interface hash, +> and the address of the contract implementing the `ERC777TokensSender` as the implementer. + ``` solidity interface ERC777TokensSender { function tokensToSend( @@ -750,7 +821,14 @@ but said address MUST implement the interface on its behalf. **`tokensToSend`** ``` solidity -function tokensToSend(address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData) external +function tokensToSend( + address operator, + address from, + address to, + uint256 amount, + bytes calldata userData, + bytes calldata operatorData +) external ``` Notify a request to send or burn (if `to` is `0x0`) an `amount` tokens from the `from` address to the `to` address @@ -769,26 +847,26 @@ by the `operator` address. The following rules apply when calling the `tokensToSend` hook: -- The `tokensToSend` hook MUST be called every time the balance is decremented. +- The `tokensToSend` hook MUST be called for every send and burn processes. - The `tokensToSend` hook MUST be called *before* the state is updated—i.e. *before* the balance is decremented. -- `operator` MUST be the address which triggered the decrease of the balance. +- `operator` MUST be the address which triggered the send or burn process. -- `from` MUST be the address of the *holder* whose balance is decreased. +- `from` MUST be the address of the *holder* whose tokens are sent or burned. -- `to` MUST be the address of the *recipient* whose balance is increased for a send. +- `to` MUST be the address of the *recipient* which receives the tokens for a send. - `to` MUST be `0x0` for a burn. -- `amount` MUST be the number of tokens the *holder* balance is decreased by. +- `amount` MUST be the number of tokens the *holder* sent or burned. -- `data` MUST contain the extra information provided by the *holder* (if any) for a send. +- `data` MUST contain the extra information (if any) provided to the send or the burn process. - `operatorData` MUST contain the extra information provided by the address which triggered the decrease of the balance (if any). -- The *holder* MAY block a decrease of its balance by `revert`ing. +- The *holder* MAY block a send or burn process by `revert`ing. (I.e., reject the withdrawal of tokens from its account.) *NOTE*: Multiple *holders* MAY use the same implementation of `ERC777TokensSender`. @@ -809,6 +887,12 @@ The `tokensReceived` hook notifies of any increment of the balance (send and min Any address (regular or contract) wishing to be notified of token credits to their address MAY register the address of a contract implementing the `ERC777TokensSender` interface described below via [ERC1820]. +> This is done by calling the `setInterfaceImplementer` function on the [ERC1820] registry +> with the *recipient* address as the address, +> the `keccak256` hash of `ERC777TokensRecipient` +> (`0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b`) as the interface hash, +> and the address of the contract implementing the `ERC777TokensRecipient` as the implementer. + ``` solidity interface ERC777TokensRecipient { function tokensReceived( @@ -838,7 +922,14 @@ but said address MUST implement the interface on its behalf. **`tokensReceived`** ``` solidity -function tokensReceived(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external +function tokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes calldata data, + bytes calldata operatorData +) external ``` Notify a send or mint (if `from` is `0x0`) of `amount` tokens from the `from` address to the `to` address @@ -852,28 +943,32 @@ by the `operator` address. > `from`: *Holder* whose tokens were sent (or `0x0` for a mint). > `to`: Recipient of the tokens. > `amount`: Number of tokens the *recipient* balance is increased by. -> `data`: Information provided by the *holder*. +> `data`: Information provided by the *holder*. > `operatorData`: Information provided by the *operator*. The following rules apply when calling the `tokensReceived` hook: -- The `tokensReceived` hook MUST be called every time the balance is incremented. +- The `tokensReceived` hook MUST be called for every send and mint processes. - The `tokensReceived` hook MUST be called *after* the state is updated—i.e. *after* the balance is incremented. -- `operator` MUST be the address which triggered the increase of the balance. +- `operator` MUST be the address which triggered the send or mint process. -- `from` MUST be the address of the *holder* whose balance is decreased for a send. +- `from` MUST be the address of the *holder* whose tokens are sent for a send. - `from` MUST be `0x0` for a mint. -- `to` MUST be the address of the *recipient* whose balance is increased. +- `to` MUST be the address of the *recipient* which receives the tokens. + +- `amount` MUST be the number of tokens the *recipient* sent or minted. -- `amount` MUST be the number of tokens the *recipient* balance is increased by. +- `data` MUST contain the extra information (if any) provided to the send or the mint process. - `operatorData` MUST contain the extra information provided by the address which triggered the increase of the balance (if any). -- The *holder* MAY block an increase of its balance by `revert`ing. (I.e., reject the reception of tokens.) + +- The *holder* MAY block a send or mint process by `revert`ing. + (I.e., reject the reception of tokens.) *NOTE*: Multiple *holders* MAY use the same implementation of `ERC777TokensRecipient`. @@ -913,14 +1008,123 @@ If needed, other sizes MAY be created by converting from `SVG` into `PNG`. ## Rationale -This standard solves some of the shortcomings of [ERC20] while maintaining backward compatibility with [ERC20]. -It avoids the problems and vulnerabilities of [EIP223]. - -It goes a step further by allowing *operators* (generally contracts) -which can manage the tokens in the same way that the [ERC20] with infinite `approve` was allowed. -Finally, it adds hooks to provide further control to *holders* over their tokens. -Note that, the usage of [ERC1820] provides backward compatibility with wallets and existing contracts -without having to be redeployed thanks proxy contracts implementing the hooks. +The principal intent for this standard is +to solve some of the shortcomings of [ERC20] while maintaining backward compatibility with [ERC20], +and avoiding the problems and vulnerabilities of [EIP223]. + +Below are the rationales for the decisions regarding the main aspects of the standards. + +*NOTE*: Jacques Dafflon ([0xjac]), one of the authors of the standard, +conjointly wrote his [master thesis] on the standard, +which goes in more details than could reasonably fit directly within the standard, +and can provide further clarifications regarding certain aspects or decisions. + +### Lifecycle + +More than just sending tokens, [ERC777] defines the entire lifecycle of a token, +starting with the minting process, followed by the sending process and terminating with the burn process. + +Having a lifecycle clearly defined is important for consistency and accuracy, +especially when value is derived from scarcity. +In contrast when looking at some [ERC20] tokens, a discrepancy can be observed +between the value returned by the `totalSupply` and the actual circulating supply, +as the standard does not clearly define a process to create and destroy tokens. + +### Data + +The mint, send and burn processes can all make use of a `data` and `operatorData` fields +which are passed to any movement (mint, send or burn). +Those fields may be empty for simple use cases, +or they may contain valuable information related to the movement of tokens, +similar to information attached to a bank transfer by the sender or the bank itself. + +The use of a `data` field is equally present in other standard proposals such as [EIP223], +and was requested by multiple members of the community who reviewed this standard. + +### Hooks + +In most cases, [ERC20] requires two calls to safely transfer tokens to a contract without locking them. +A call from the sender, using the `approve` function +and a call from the recipient using `transferFrom`. +Furthermore, this requires extra communication between the parties which is not clearly defined. +Finally, holders can get confused between `transfer` and `approve`/`transferFrom`. +Using the former to transfer tokens to a contract will most likely result in locked tokens. + +Hooks allow streamlining of the sending process and offer a single way to send tokens to any recipient. +Thanks to the `tokensReceived` hook, contracts are able to react and prevent locking tokens upon reception. + +#### **Greater Control For Holders** + +The `tokensReceived` hook also allows holders to reject the reception of some tokens. +This gives greater control to holders who can accept or reject incoming tokens based on some parameters, +for example located in the `data` or `operatorData` fields. + +Following the same intentions and based on suggestions from the community, +the `tokensToSend` hook was added to give control over and prevent the movement of outgoing tokens. + +#### **[ERC1820] Registry** + +The [ERC1820] Registry allows holders to register their hooks. +Other alternatives were examined beforehand to link hooks and holders. + +The first was for hooks to be defined at the sender's or recipient's address. +This approach is similar to [EIP223] which proposes a `tokenFallback` function on recipient contracts +to be called when receiving tokens, +but improves on it by relying on [ERC165] for interface detection. +While straightforward to implement, this approach imposes several limitations. +In particular, the sender and recipient must be contracts in order to provide their implementation of the hooks. +Preventing externally owned addresses to benefit from hooks. +Existing contracts have a strong probability not to be compatible, +as they undoubtedly were unaware and do not define the new hooks. +Consequently existing smart contract infrastructure such as multisig wallets +which potentially hold large amounts of ether and tokens would need to be migrated to new updated contracts. + +The second approach considered was to use [ERC672] which offered pseudo-introspection for addresses using reverse-ENS. +However, this approach relied heavily on ENS, on top of which reverse lookup would need to be implemented. +Analysis of this approach promptly revealed a certain degree of complexity and security concerns +which would transcend the benefits of approach. + +The third solution—used in this standard—is to rely on a unique registry +where any address can register the addresses of contracts implementing the hooks on its behalf. +This approach has the advantage that externally owned accounts and contracts can benefit from hooks, +including existing contracts which can rely on hooks deployed on proxy contracts. + +The decision was made to keep this registry in a separate EIP, +as to not over complicate this standard. +More importantly, the registry is designed in a flexible fashion, +such that other EIPs and smart contract infrastructures can benefit from it +for their own use cases, outside the realm of [ERC777] and tokens. +The first proposal for this registry was [ERC820]. +Unfortunately, issues emanating from upgrades in the Solidity language to versions 0.5 and above +resulted in a bug in a separated part of the registry, which required changes. +This was discovered right after the last call period. +Attempts made to avoid creating a separate EIP, such as [ERC820a], were rejected. +Hence the standard for the registry used for [ERC777] became [ERC1820]. +[ERC1820] and [ERC820] are functionally equivalent. [ERC1820] simply contains the fix for newer versions of Solidity. + +### Operators + +The standard defines the concept of operators as any address which moves tokens. +While intuitively every address moves its own tokens, +separating the concepts of holder and operator allows for greater flexibility. +Primarily, this originates from the fact that the standard defines a mechanism for holders +to let other addresses become their operators. +Moreover, unlike the approve calls in [ERC20] where the role of an approved address is not clearly defined, +[ERC777] details the intent of and interactions with operators, +including an obligation for operators to be approved, +and an irrevocable right for any holder to revoke operators. + +#### **Default Operators** + +Default operators were added based on community demand for pre-approved operators. +That is operators which are approved for all holders by default. +For obvious security reasons, the list of default operators is defined at the token contract creation time, +and cannot be changed. +Any holder still has the right to revoke default operators. +One of the obvious advantages of default operators is to allow ether-less movements of tokens. +Default operators offer other usability advantages, +such as allowing token providers to offer functionality in a modular way, +and to reduce the complexity for holders to use features provided through operators. ## Backward Compatibility @@ -949,7 +1153,8 @@ If the token implements [ERC20], it MUST register the `ERC20Token` interface with its own address via [ERC1820]. This is done by calling the `setInterfaceImplementer` function on the ERC1820 registry with the token contract address as both the address and the implementer -and the `keccak256` hash of `ERC20Token` as the interface hash. +and the `keccak256` hash of `ERC20Token` (`0xaea199e31a596269b42cdafd93407f14436db6e4cad65417994c2eb37381e05a`) +as the interface hash. If the contract has a switch to enable or disable ERC20 functions, every time the switch is triggered, the token MUST register or unregister the `ERC20Token` interface for its own address accordingly via ERC1820. @@ -992,17 +1197,23 @@ when sending, minting and transferring token via [ERC777] and [ERC20]: ERC777TokensRecipient
not registered regular address - continue - continue + continue contract MUST revert + SHOULD continue1 +> 1. +> The transaction SHOULD continue for clarity as ERC20 is not aware of hooks. +> However, this can result in accidentally locked tokens. +> If avoiding accidentally locked tokens is paramount, the transaction MAY revert. + + There is no particular action to take if `tokensToSend` is not implemented. -The transfer MUST proceed and only be canceled if another condition is not respected +The movement MUST proceed and only be canceled if another condition is not respected such as lack of funds or a `revert` in `tokensReceived` (if present). During a send, mint and burn, the respective `Sent`, `Minted` and `Burned` events MUST be emitted. @@ -1035,10 +1246,16 @@ Copyright and related rights waived via [CC0]. [operators]: #operators [ERC20]: https://eips.ethereum.org/EIPS/eip-20 +[ERC165]: https://eips.ethereum.org/EIPS/eip-165 +[ERC672]: https://github.com/ethereum/EIPs/issues/672 [ERC777]: https://eips.ethereum.org/EIPS/eip-777 +[ERC820]: https://eips.ethereum.org/EIPS/eip-820 +[ERC820a]: https://github.com/ethereum/EIPs/pull/1758 [ERC1820]: https://eips.ethereum.org/EIPS/eip-1820 [erc1820-set]: https://eips.ethereum.org/EIPS/eip-1820#set-an-interface-for-an-address +[0xjac]: https://github.com/0xjac [0xjac/ERC777]: https://github.com/0xjac/ERC777 +[master thesis]: https://github.com/0xjac/master-thesis [npm/erc777]: https://www.npmjs.com/package/erc777 [ref tests]: https://github.com/0xjac/ERC777/blob/master/test/ReferenceToken.test.js [reference implementation]: https://github.com/0xjac/ERC777/blob/master/contracts/examples/ReferenceToken.sol