ERC777 解体して分かりやすく

# はじめに
ERC20の拡張、改善案について調べていたとき、ERC777はどうもよくわからなかったので、関数を一つ一つ分解して理解していこう。

# ERC777の概要
ERC20との下位互換性を維持しつつ、トークン契約と対話する新しい方法を定義しています。

トークンとの対話のための高度な機能を定義しています。すなわち、オペレータが別のアドレス契約または通常のアカウントに代わってトークンを送信したり、トークン保有者がトークンをよりコントロールできるように送受信フックを提供したりすることです。

ERC1820を利用して、コントラクトや通常のアドレスがトークンを受け取るときに、どこに通知するかを調べたり、すでに展開されているコントラクトとの互換性を可能にしたりしています。

トークンはsend(dest, value, data)で送信されるという点で、Etherと同じ仕組みを使用しています。(ERC20にはTransfer)

ERC223 + ERC677 = ERC777

# 解体(Openzeppelin/token/ERC777)
importするpackages

- using SafeMath for uint256;
- using Address for address;

## 用語

- Operator
 トークン送信者の代わりに送信する第三者。全てのアカウントは自分のオペレーターである。

- mapping (address => mapping (address => uint256))
 二次元配列。 
 ex. allowances[from][msg.sender] = 10

## コンストラクタ

```js

constructor(
        string memory name,
        string memory symbol,
        address[] memory defaultOperators
    ) public {
        _name = name;
        _symbol = symbol;

        _defaultOperatorsArray = defaultOperators;
        for (uint256 i = 0; i < _defaultOperatorsArray.length; i++) {
            _defaultOperators[_defaultOperatorsArray[i]] = true;
        }

        // register interfaces
        // ERC1820にコントラクトの実装形式を記録する
        _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this));
        _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this));
    }
```

## 属性
|  属性  |  データ型  | 説明         |
| -------------- | ------------ | ----------- |
| _ERC1820_REGISTRY | IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24) |  |
|  _balances  |  mapping(address => uint256)|   アドレスをキーとしトークン保有量をバリューとする   |
|  _totalSupply  |   uint256   | トークンの最大供給料 |
|  _name   |  string     | 　トークン名　|
| _symbol | string | "BTC"などのトークンシンボル |
| _TOKENS_SENDER_INTERFACE_HASH | bytes32 | keccak256("ERC777TokensSender")のハッシュ値 |
| _TOKENS_RECIPIENT_INTERFACE_HASH | bytes32 | keccak256("ERC777TokensRecipient") |
| _defaultOperatorsArray | address[] |   |
| _defaultOperators | mapping(address => bool)  |    |
| _operators | mapping(address => mapping(address => bool)) |  |
| _revokedDefaultOperators | mapping(address => mapping(address => bool)) |  |
| _allowances | mapping (address => mapping (address => uint256)) |  |



## 関数
|  関数  |  戻り値  | 説明         |
| -------------- | ------------ | ----------- |
| name() | string memory | トークン名を返す |
| symbol() | string memory | シンボル名を返す |
| decimals() | uint8 | 単位を返す。常に18を返す。 |
| granularity() | uint256 | 粒度(?)を返す。常に1を返す。 |
| totalSupply() | uint256 | 最大供給量を返す |
| balanceOf(address tokenHolder) | uint256 | 与えられたアドレスの保有するトークン量を返す |
| send(address recipient, uint256 amount, bytes memory data) |  | トークンを送信する(ERC777) |
| transfer(address recipient, uint256 amount) | bool | トークンを送信する(ERC777) |
| burn(uint256 amount, bytes memory data) |  | amount分のトークンを焼却する。 |
| isOperatorFor(address operator, address tokenHolder) | bool | tokenHolderがoperatorの場合trueを返す |
| authorizeOperator(address operator) |  | operatorに任命する |
| revokeOperator(address operator) |  | operatorを剥奪する |
| defaultOperators() | address[] memory | _defaultOperatorsArrayを返す |
| operatorSend(address sender, address recipient, uint256 amount, bytes memory data, bytes memory operatorData) |  | operatorとして第三者のトークンを送信する |
| operatorBurn(address account, uint256 amount, bytes memory data, bytes memory operatorData) |  | operatorとして第三者のトークンを焼却する |
| allowance(address holder, address spender) | uint256 | holderが承認したspenderに送信可能なトークン量を返す |
| approve(address spender, uint256 value) | bool | msg.senderからspenderへ転送を許可させる |
| transferFrom(address holder, address recipient, uint256 amount) | bool | holderがrecipientに許可した量以下のトークンを送信する。 |
| _mint(address account, uint256 amount, bytes memory userData, bytes memory operatorData)|  | トークンを発行 |
| _send(address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck) |  | トークンの送信 |
| _burn(address from, uint256 amount, bytes memory data,bytes memory operatorData) |  | トークンを焼却する |
| _move(address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData) |  | トークンの移動 |
| _approve(address holder, address spender, uint256 value) |  |  |
| _callTokensToSend(address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData) |  |  |
| _callTokensReceived( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck)|  |  |
| _beforeTokenTransfer(address operator, address from, address to, uint256 amount) |  | 転送前に処理する。mintやburn,send,Transfer,OperatorTransferをする。 |



# 関数実行流れ
## send(address recipient, uint256 amount, bytes memory data)

1 send関数を実行

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

2 _send関数を実行

```js

function _send(
        address from,
        address to,
        uint256 amount,
        bytes memory userData,
        bytes memory operatorData,
        bool requireReceptionAck
    )
        internal
    {
        require(from != address(0), "ERC777: send from the zero address");
        require(to != address(0), "ERC777: send 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);
    }
```
3 _callTokensToSend(operator, from, to, amount, userData, operatorData)を実行

```js

    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);
        }
    }
```
implementer

```js

 IERC1820Registry constant internal _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
```
## _callTokensReceived

> 受信者がERC1820経由でERC777TokensRecipient実装を登録している場合、受信者のtokensReceivedフックを呼び出さなければならない

ERC1820に何のインターフェースを実装しているかを問い合わせ、もしERC777であればtokenToSendフックを呼び出す。
トランザクションを拒否するかどうかを設定しても良い

> トークンコントラクトは、状態を更新する前にtokensToSendフックを呼び出さなければならない。
トークンコントラクトは、状態を更新した後に tokensReceived フックを呼び出さなければならない。
つまり、tokensToSend は最初に呼び出されなければならず、次に残高は送信を反映して更新されなければならず、最後にtokensReceived はその後に呼び出されなければならない。したがって、tokensToSend内のbalanceOfコールは送信前のアドレスの残高を返し、tokensReceived内のbalanceOfコールは送信後のアドレスの残高を返します。

```js

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(0), "ERC777: transfer to the zero address");
        address from = _msgSender();
        // tokensToSendフックの呼び出し
        _callTokensToSend(from, from, recipient, amount, "", "");
        _move(from, from, recipient, amount, "", "");
        // tokensToReceivedフックの呼び出し
        _callTokensReceived(from, from, recipient, amount, "", "", false);
        return true;
    }
```


```js

    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);
        }
    }
```

```js

    // ERC1820
    // @param _addrの実装しているインターフェースを取得
    // 実装している場合はコントラクトアドレスを返す
    // 実装していない場合は0アドレスを返す
    function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
        address addr = _addr == address(0) ? msg.sender : _addr;
        if (isERC165Interface(_interfaceHash)) {
            bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
            return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
        }
        return interfaces[addr][_interfaceHash];
    }

    // @param _addrが実装しているインターフェースを設定する
    function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
        address addr = _addr == address(0) ? msg.sender : _addr;
        require(getManager(addr) == msg.sender, "Not the manager");

        require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
        if (_implementer != address(0) && _implementer != msg.sender) {
            require(
                ERC1820ImplementerInterface(_implementer)
                    .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
                "Does not implement the interface"
            );
        }
        interfaces[addr][_interfaceHash] = _implementer;
        emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
    }
```

# 参考文献
[Science | What is the ERC-777 token contract?](https://blocking.net/23536/science-what-is-the-erc-777-token-contract/)