[ERC1820] SolidityのアップデートによるERC820のバグ修正の仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、Solidityのアップデートによる、**ERC820**のバグを修正する提案している規格である**ERC1820**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-1820

:::note info
この規格は、Solidityのアップデートによって生じた**ERC820**の問題を修正したものです。
内容はほとんど**ERC820**と同じなため、この記事では以下の解説に止めようと思います。
- **提案の概要**
- **修正部分の説明**
- **コントラクト部分の詳しく説明**

**ERC820**については以下の記事を参考にしてください。
:::



他にも様々なERCについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

この規格は、コントラクトやアカウントがどのインターフェースをサポートしているか、そしてその実装を担当するコントラクトがどれかを登録するためのレジストリコントラクトを提供します。
これにより、**ERC165**との互換性も維持されています。

このシステムでは、コントラクトや一般のアカウントが自分たちが提供する機能を公開できます。
この情報は、直接提供されるか、あるいはプロキシコントラクトを介して提供されます。
誰でもこのレジストリにアクセスして、特定のアドレスが特定のインターフェースを実装しているかどうか、そしてその実装がどのコントラクトによって行われているかを確認できます。

このレジストリは様々なブロックチェーン上にデプロイ可能で、すべてのチェーンで同じアドレスを持ちます。
また、最後の`28`バイトが`0`のインターフェースは**ERC165**インターフェースとして認識され、この場合レジストリはそのコントラクトがインターフェースを実装しているかどうかを確認するために呼び出しを転送します。

加えて、このコントラクトは**ERC165**のキャッシュとしても機能し、それによりガス消費を減らすことができます。
これは、特に頻繁にやり取りが行われる場合に有効で、実装の確認が簡単かつ効率的になります。

> :information_source: ERC-1820 has superseded ERC-820. :information_source:
ERC-1820 fixes the incompatibility in the ERC-165 logic which was introduced by the Solidity 0.5 update.
Have a look at the official announcement, and the comments about the bug and the fix.
Apart from this fix, ERC-1820 is functionally equivalent to ERC-820.
> 
> :warning: ERC-1820 MUST be used in lieu of ERC-820. :warning:

> **ERC1820**は**ERC820**を置き換える新しい規格です。Solidity 0.5のアップデートによって生じた**ERC165**の互換性問題を解決するために導入されました。この問題に関する公式の発表や、バグとその修正についてのコメントを確認することが推奨されます。この修正以外では、**ERC1820**は機能的には**ERC820**と同等です。
> 
> :warning: **ERC1820**は、**ERC820**の代わりに使われなければなりません。:warning:

**ERC820**に代わって新しく導入された**ERC1820**は、Solidityのアップデートによって生じた問題を修正し、より安定した基盤を提供します。
開発者は以前の**ERC820**を使用する代わりに、この新しい規格を採用する必要があり、これによりより互換性の高いシステムが保証されます。

- [公式の発表](https://github.com/ethereum/EIPs/issues/820)
- [バグ](https://github.com/ethereum/EIPs/issues/820#issuecomment-452465748)

> I was trying to use this with Solidity 0.5, and discovered what may be an incompatibility between the EIP165-compatible code here and the new Solidity compiler.
It looks like the new solidity compiler enforces padding of call arguments, so a test-compile of e.g. TestERC165.sol results in code for supportsInterface(bytes4 interfaceID) which checks lt(sub(calldatasize,0x4),0x20) or reverts. The problem seems to be that noThrowCall sets the call size to be 0x8 bytes, which is technically sufficient, but makes the called-contract revert with the new compiler. I tried modifying noThrowCall locally to set the call size to 0x24 bytes, and then the test passes.
I don't use the ERC165 compatibility, we're past the last call date, this is arguably a problem with Solidity more than this specification, and it's entirely possible my analysis is completely wrong, but I wanted to at least bring awareness to the issue.

> Solidity 0.5を使用している際に、ここにある**EIP165**互換のコードと新しいSolidityコンパイラとの間に互換性の問題があることが発見されました。新しいSolidityコンパイラはコール引数のパディングを強制するため、例えば`TestERC165.sol`のテストコンパイルを行うと、`supportsInterface(bytes4 interfaceID)`について、引数が期待される長さに満たない場合にリバートするコードが生成されます。問題は`noThrowCall`がコールサイズを0x8バイトに設定していることで、これは技術的には十分ですが、新しいコンパイラを使用すると呼び出されたコントラクトがリバートしてしまいます。ローカルで`noThrowCall`を修正してコールサイズを0x24バイトに設定すると、テストが通過することが確認されました。
> 
> **ERC165**の互換性を使用していない場合や、この仕様よりもSolidityに問題がある可能性があるなど、さまざまな状況が考えられますが、少なくともこの問題に気づいておくことは重要です。また、この分析が完全に間違っている可能性もありますが、問題を意識してもらうために共有したいと思います。

:::note info
Solidity 0.5を使っているとき、開発者は**ERC820**と**EIP165**互換のコードに互換性の問題があることに気づきました。
ここでの問題は、新しいSolidityコンパイラが関数への引数のパディング（空白部分を埋めること）を厳格に要求することです。
これにより、予期せぬ動作が発生する可能性があります。

例として、`TestERC165.sol`というファイルをテストコンパイルした場合を考えます。
このファイル内にある`supportsInterface(bytes4 interfaceID)`関数は、特定のインターフェースをサポートしているかを確認するためのものです。
しかし、新しいコンパイラでは、この関数に渡される引数のサイズが期待されるサイズ（この場合は`0x20`バイト）に満たないと、エラーを返してしまいます。
具体的には、`noThrowCall`という関数がコールサイズを`0x8`バイトに設定しているため、新しいコンパイラを使うと、呼び出されたコントラクトがエラー（リバート）を返す原因になっていました。

この問題を解決するために、開発者は`noThrowCall`関数を修正し、コールサイズを`0x24`バイトに設定しました。
これにより、引数のサイズが期待値に達し、テストが正常にパスするようになりました。

Solidityの更新により、以前は問題なく動作していたコードがエラーを引き起こすようになりました。
このような問題は、特に新旧のコードが交じり合う大規模なプロジェクトで発生しやすく、注意深い対応が必要です。
:::

# コントラクト

以下は**ERC820**の`noTrowCall`関数定義です。

```javascript
function noThrowCall(address _contract, bytes4 _interfaceId)
        internal view returns (uint256 success, uint256 result)
    {
        bytes4 erc165ID = ERC165ID;

        assembly {
                let x := mload(0x40)               // Find empty storage location using "free memory pointer"
                mstore(x, erc165ID)                // Place signature at beginning of empty storage
                mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature

                success := staticcall(
                    30000,                         // 30k gas
                    _contract,                     // To addr
                    x,                             // Inputs are stored at location x
                    0x08,                          // Inputs are 8 bytes long
                    x,                             // Store output over input (saves space)
                    0x20                           // Outputs are 32 bytes long
                )

                result := mload(x)                 // Load the result
        }
    }
```

以下は**ERC1820**の`noTrowCall`関数定義です。

```javascript
function noThrowCall(address _contract, bytes4 _interfaceId)
        internal view returns (uint256 success, uint256 result)
    {
        bytes4 erc165ID = ERC165ID;

        assembly {
            let x := mload(0x40)               // Find empty storage location using "free memory pointer"
            mstore(x, erc165ID)                // Place signature at beginning of empty storage
            mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature

            success := staticcall(
                30000,                         // 30k gas
                _contract,                     // To addr
                x,                             // Inputs are stored at location x
                0x24,                          // Inputs are 36 (4 + 32) bytes long
                x,                             // Store output over input (saves space)
                0x20                           // Outputs are 32 bytes long
            )

            result := mload(x)                 // Load the result
        }
    }
```

**ERC820**と**ERC1820**の`noThrowCall`関数の主な違いは、関数への入力引数の長さを指定する部分です。
この違いは、Solidity 0.5の更新によって引き起こされた互換性の問題を解決するためのものです。

## 入力引数の長さ

### ERC820
- `0x08`
    - これは8バイト長を意味します。
    - `erc165ID`（4バイト）と`_interfaceId`（4バイト）があり、合計で`8`バイトになります。
    - しかし、Solidity 0.5の更新後、引数がこの長さでは不足していると見なされ、関数がリバートする原因となっていました。

### ERC1820
- `0x24`
    - ここで`36`バイト（`4` + `32`バイト）が指定されています。
    - これにより、Solidity 0.5によるパディング要件に準拠し、呼び出された関数がリバートする問題を解決しています。

## 関数の目的
両方の関数は、存在しない関数を呼び出してもエラーを投げないように、静的なコールを使ってコントラクトに問い合わせを行うことを目的としています。
これは、特定のインターフェースがコントラクトによってサポートされているかどうかを確認するために使用されます。

:::note info
**ERC1820**の`noThrowCall`関数は、Solidity 0.5の更新によって必要となった引数のパディング要件に対応するために、呼び出しの入力サイズを`36`バイトに増やすという重要な修正を加えています。
これにより、新しいコンパイラに適合し、以前のバージョンで発生していたリバート問題を解決しています。
:::

# 最後に

今回は「Solidityのアップデートによる、**ERC820**のバグを修正する提案している規格である**ERC1820**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4