はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、Solidityのアップデートによる、ERC820のバグを修正する提案している規格であるERC1820についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
この規格は、Solidityのアップデートによって生じたERC820の問題を修正したものです。
内容はほとんどERC820と同じなため、この記事では以下の解説に止めようと思います。
- 提案の概要
- 修正部分の説明
- コントラクト部分の詳しく説明
ERC820については以下の記事を参考にしてください。
他にも様々なERCについてまとめています。
概要
この規格は、コントラクトやアカウントがどのインターフェースをサポートしているか、そしてその実装を担当するコントラクトがどれかを登録するためのレジストリコントラクトを提供します。
これにより、ERC165との互換性も維持されています。
このシステムでは、コントラクトや一般のアカウントが自分たちが提供する機能を公開できます。
この情報は、直接提供されるか、あるいはプロキシコントラクトを介して提供されます。
誰でもこのレジストリにアクセスして、特定のアドレスが特定のインターフェースを実装しているかどうか、そしてその実装がどのコントラクトによって行われているかを確認できます。
このレジストリは様々なブロックチェーン上にデプロイ可能で、すべてのチェーンで同じアドレスを持ちます。
また、最後の28
バイトが0
のインターフェースはERC165インターフェースとして認識され、この場合レジストリはそのコントラクトがインターフェースを実装しているかどうかを確認するために呼び出しを転送します。
加えて、このコントラクトはERC165のキャッシュとしても機能し、それによりガス消費を減らすことができます。
これは、特に頻繁にやり取りが行われる場合に有効で、実装の確認が簡単かつ効率的になります。
ERC-1820 has superseded ERC-820.
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.ERC-1820 MUST be used in lieu of ERC-820.
ERC1820はERC820を置き換える新しい規格です。Solidity 0.5のアップデートによって生じたERC165の互換性問題を解決するために導入されました。この問題に関する公式の発表や、バグとその修正についてのコメントを確認することが推奨されます。この修正以外では、ERC1820は機能的にはERC820と同等です。
ERC1820は、ERC820の代わりに使われなければなりません。
ERC820に代わって新しく導入されたERC1820は、Solidityのアップデートによって生じた問題を修正し、より安定した基盤を提供します。
開発者は以前のERC820を使用する代わりに、この新しい規格を採用する必要があり、これによりより互換性の高いシステムが保証されます。
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に問題がある可能性があるなど、さまざまな状況が考えられますが、少なくともこの問題に気づいておくことは重要です。また、この分析が完全に間違っている可能性もありますが、問題を意識してもらうために共有したいと思います。
Solidity 0.5を使っているとき、開発者はERC820とEIP165互換のコードに互換性の問題があることに気づきました。
ここでの問題は、新しいSolidityコンパイラが関数への引数のパディング(空白部分を埋めること)を厳格に要求することです。
これにより、予期せぬ動作が発生する可能性があります。
例として、TestERC165.sol
というファイルをテストコンパイルした場合を考えます。
このファイル内にあるsupportsInterface(bytes4 interfaceID)
関数は、特定のインターフェースをサポートしているかを確認するためのものです。
しかし、新しいコンパイラでは、この関数に渡される引数のサイズが期待されるサイズ(この場合は0x20
バイト)に満たないと、エラーを返してしまいます。
具体的には、noThrowCall
という関数がコールサイズを0x8
バイトに設定しているため、新しいコンパイラを使うと、呼び出されたコントラクトがエラー(リバート)を返す原因になっていました。
この問題を解決するために、開発者はnoThrowCall
関数を修正し、コールサイズを0x24
バイトに設定しました。
これにより、引数のサイズが期待値に達し、テストが正常にパスするようになりました。
Solidityの更新により、以前は問題なく動作していたコードがエラーを引き起こすようになりました。
このような問題は、特に新旧のコードが交じり合う大規模なプロジェクトで発生しやすく、注意深い対応が必要です。
コントラクト
以下はERC820のnoTrowCall
関数定義です。
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
関数定義です。
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によるパディング要件に準拠し、呼び出された関数がリバートする問題を解決しています。
- ここで
関数の目的
両方の関数は、存在しない関数を呼び出してもエラーを投げないように、静的なコールを使ってコントラクトに問い合わせを行うことを目的としています。
これは、特定のインターフェースがコントラクトによってサポートされているかどうかを確認するために使用されます。
ERC1820のnoThrowCall
関数は、Solidity 0.5の更新によって必要となった引数のパディング要件に対応するために、呼び出しの入力サイズを36
バイトに増やすという重要な修正を加えています。
これにより、新しいコンパイラに適合し、以前のバージョンで発生していたリバート問題を解決しています。
最後に
今回は「Solidityのアップデートによる、ERC820のバグを修正する提案している規格であるERC1820」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!