# OpenZeppelin ERC-777

## EIP-777

{% embed url="https://eips.ethereum.org/EIPS/eip-777" %}
EIP-777
{% endembed %}

### Simple Summary

This EIP defines standard interfaces and behaviors for token contracts.

### Abstract

This standard defines a new way to interact with a token contract while remaining <mark style="color:red;">**backward compatible**</mark> with [ERC-20](https://eips.ethereum.org/EIPS/eip-20).

It defines advanced features to interact with tokens. Namely, <mark style="color:red;">**operators**</mark> to send tokens on behalf of another address—contract or regular account—and send/receive <mark style="color:red;">**hooks**</mark> to offer token holders more control over their tokens.

It takes advantage of [ERC-1820](https://eips.ethereum.org/EIPS/eip-1820) to find out whether and where to notify contracts and regular addresses when they receive tokens as well as to allow compatibility with already-deployed contracts.

### Motivation

This standard tries to improve upon the widely used [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token standard. The main advantages of this standard are:

1. Uses the same philosophy as Ether in that tokens are sent with `send(dest, value, data)`.
2. Both contracts and regular addresses can control and reject which token they send by registering a `tokensToSend` hook. (Rejection is done by `revert`ing in the hook function.)
3. Both contracts and regular addresses can control and reject which token they receive by registering a `tokensReceived` hook. (Rejection is done by `revert`ing in the hook function.)
4. The `tokensReceived` hook allows to send tokens to a contract and notify it in a single transaction, unlike [ERC-20](https://eips.ethereum.org/EIPS/eip-20) which requires a double call (`approve`/`transferFrom`) to achieve this.
5. The holder can “authorize” and “revoke” operators which can send tokens on their behalf. These operators are intended to be verified contracts such as an exchange, a cheque processor or an automatic charging system.
6. Every token transaction contains `data` and `operatorData` bytes fields to be used freely to pass data from the holder and the operator, respectively.
7. It is backward compatible with wallets that do not contain the `tokensReceived` hook function by deploying a proxy contract implementing the `tokensReceived` hook for the wallet.

## OpenZeppelin ERC777 Doc

{% embed url="https://docs.openzeppelin.com/contracts/3.x/erc777" %}
ERC777 Doc
{% endembed %}

Like [ERC20](https://docs.openzeppelin.com/contracts/3.x/erc20), ERC777 is a standard for [_fungible_ tokens](https://docs.openzeppelin.com/contracts/3.x/tokens#different-kinds-of-tokens), and is focused around allowing more complex interactions when trading tokens. <mark style="color:red;">**More generally, it brings tokens and Ether closer together by providing the equivalent of a**</mark> `msg.value` <mark style="color:red;">**field, but for tokens.**</mark>

The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is <mark style="color:red;">**receive hooks**</mark>. A hook is simply a function in a contract that is called when tokens are sent to it, meaning <mark style="color:red;">**accounts and contracts can react to receiving tokens**</mark>.

{% hint style="info" %}
**My comment**\
Callbacks are potentially vulnerable to reentrancy attacks, be careful.
{% endhint %}

This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do `approve` and `transferFrom` in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how [`PaymentSplitter`](https://docs.openzeppelin.com/contracts/3.x/api/payment#PaymentSplitter) does it), among many others.

Furthermore, <mark style="color:red;">**since contracts are required to implement these hooks in order to receive tokens, no tokens can get stuck in a contract that is unaware of the ERC777 protocol**</mark>**,** as has happened countless times when using ERC20s.

### What If I Already Use ERC20? <a href="#what_if_i_already_use_erc20" id="what_if_i_already_use_erc20"></a>

The standard has you covered! The ERC777 standard is <mark style="color:red;">**backwards compatible with ERC20**</mark>, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the [EIP’s Backwards Compatibility section](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility) to learn more.

### Constructing an ERC777 Token Contract <a href="#constructing_an_erc777_token_contract" id="constructing_an_erc777_token_contract"></a>

We will replicate the `GLD` example of the [ERC20 guide](https://docs.openzeppelin.com/contracts/3.x/erc20#constructing-an-erc20-token-contract), this time using ERC777. As always, check out the [`API reference`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777) to learn more about the details of each function.

```solidity
// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC777/ERC777.sol";

contract GLDToken is ERC777 {
    constructor(uint256 initialSupply, address[] memory defaultOperators)
        public
        ERC777("Gold", "GLD", defaultOperators)
    {
        _mint(msg.sender, initialSupply, "", "");
    }
}
```

In this case, we’ll be extending from the [`ERC777`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777) contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of [`ERC777`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777), and we’ll once again make use of [`_mint`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777-_mint-address-address-uint256-bytes-bytes-) to assign the `initialSupply` to the deployer account. Unlike [ERC20’s `_mint`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC20#ERC20-_mint-address-uint256-), this one includes some extra parameters, but you can safely ignore those for now.

You’ll notice both [`name`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#IERC777-name--) and [`symbol`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#IERC777-symbol--) are assigned, but not [`decimals`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777-decimals--). The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include [`ERC20Detailed`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC20#ERC20Detailed)), <mark style="color:red;">**but also mandates that**</mark> `decimals` <mark style="color:red;">**always returns a fixed value of**</mark> `18`, so there’s no need to set it ourselves. For a review of `decimals`'s role and importance, refer back to our [ERC20 guide](https://docs.openzeppelin.com/contracts/3.x/erc20#a-note-on-decimals).

Finally, we’ll need to set the [`defaultOperators`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#IERC777-defaultOperators--): special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you’re not planning on using operators in your token, you can simply pass an empty array. _Stay tuned for an upcoming in-depth guide on ERC777 operators!_

That’s it for a basic token contract! We can now deploy it, and use the same [`balanceOf`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#IERC777-balanceOf-address-) method to query the deployer’s balance:

```solidity
> GLDToken.balanceOf(deployerAddress)
1000
```

To move tokens from one account to another, we can use both [`ERC20`'s `transfer`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777-transfer-address-uint256-) method, or the new [`ERC777`'s `send`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777-send-address-uint256-bytes-), which fulfills a very similar role, but adds an optional `data` field:

```solidity
> GLDToken.transfer(otherAddress, 300)
> GLDToken.send(otherAddress, 300, "")
> GLDToken.balanceOf(otherAddress)
600
> GLDToken.balanceOf(deployerAddress)
400
```

### Sending Tokens to Contracts <a href="#sending_tokens_to_contracts" id="sending_tokens_to_contracts"></a>

A key difference when using [`send`](https://docs.openzeppelin.com/contracts/3.x/api/token/ERC777#ERC777-send-address-uint256-bytes-) is that token transfers to other contracts may revert with the following message:

```
ERC777: token recipient contract has no implementer for ERC777TokensRecipient
```

This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 protocol, so transfers to it are disabled to <mark style="color:red;">**prevent tokens from being locked forever**</mark>. As an example, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error.

_An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!_

## How to Set Implementer

{% embed url="https://docs.openzeppelin.com/contracts/4.x/api/token/erc777" %}
OpenZeppelin ERC777 API Doc
{% endembed %}

Interface of the ERC777Token standard as defined in the EIP.

This contract uses the [ERC1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let token holders and recipients react to token movements by using setting implementers for the associated interfaces in said registry. See [`IERC1820Registry`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Registry) and [`ERC1820Implementer`](https://docs.openzeppelin.com/contracts/4.x/api/utils#ERC1820Implementer).

The implementer setter function can be found in IERC1820Registry:

{% embed url="https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-" %}
IERC1820Registry
{% endembed %}

**Function definition:**

```solidity
setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer)
```

**Description:**

Sets the `implementer` contract as `account`'s implementer for `interfaceHash`.

`account` being the zero address is an alias for the caller’s address. The zero address can also be used in `implementer` to remove an old one.

See [`interfaceHash`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Registry-interfaceHash-string-) to learn how these are created.

Emits an [`InterfaceImplementerSet`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) event.

Requirements:

* the caller must be the current manager for `account`.
* `interfaceHash` must not be an [`IERC165`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC165) interface id (i.e. it must not end in 28 zeroes).
* `implementer` must implement [`IERC1820Implementer`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Implementer) and return true when queried for support, unless `implementer` is the caller. See [`IERC1820Implementer.canImplementInterfaceForAddress`](https://docs.openzeppelin.com/contracts/4.x/api/utils#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-).

## OpenZeppelin Implementation

{% embed url="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC777/ERC777.sol" %}
OpenZeppelin ERC777
{% endembed %}

We are going to discover the "new" functions that ERC20 does not have.

### send()

The `send()` method is similar to `transfer()` but with an additional function parameter `data`. If we `send()` to a contract, the recipient contract MUST implment the `tokensReceived()` callback, otherwise the transaction would revert. This design makes sure that the recipient contract is aware of the ERC777 protocol.

Diving into the code, `send()` will invoke the `_callTokensToSend()` and `_callTokensReceived()` callbacks:

```solidity
function send(address recipient, uint256 amount, bytes memory data) public virtual override {
    _send(_msgSender(), recipient, amount, data, "", true);
}
```

```solidity
function _send(
    address from,
    address to,
    uint256 amount,
    bytes memory userData,
    bytes memory operatorData,
    bool requireReceptionAck
) internal virtual {
    require(from != address(0), "ERC777: transfer from the zero address");
    require(to != address(0), "ERC777: transfer to the zero address");

    address operator = _msgSender();

    _callTokensToSend(operator, from, to, amount, userData, operatorData);

    _move(operator, from, to, amount, userData, operatorData);

    _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
}
```

### Callbacks

#### \_callTokensToSend()

```solidity
function _callTokensToSend(
    address operator,
    address from,
    address to,
    uint256 amount,
    bytes memory userData,
    bytes memory operatorData
) private {
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH);
    if (implementer != address(0)) {
        IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
    }
}
```

#### \_callTokensReceived()

```solidity
function _callTokensReceived(
    address operator,
    address from,
    address to,
    uint256 amount,
    bytes memory userData,
    bytes memory operatorData,
    bool requireReceptionAck
) private {
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH);
    if (implementer != address(0)) {
        IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
    } else if (requireReceptionAck) {
        require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
    }
}
```

`implementer` must be registered in ERC1820 registry in order to use these two callbacks.
