2.スマートコントラクトのアップグレードついて
はじめに
本連載では、スマートコントラクトのUpgradeabilityに焦点を当てています。スマートコントラクトは、普通のアプリケーションとは違い、一度デプロイしたコントラクトのコードは修正できないという不変性を持っています。この不変性を特殊な方法を用いて、回避することで、Developer Experienceの向上を図ることができる、「Upgradeability」のコンセプトをわかりやすく解説するのが本連載の目的です。
今回は、「2.スマートコントラクトのアップグレードついて」です。Proxy Contractのより細かい3つのデザインパターンとアップグレードを行える2つツールについて紹介していきます。
関連する記事
第1回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
第3回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
第4回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
三つのアップグレードパターン
Proxy Contractの実装には以下の有力なパターンが存在します。OpenZeppelinSDKではUnstructured Storageが採用されており、一番有力なパターンであると言えるでしょう。
- Inherited Storage
- Eternal Storage
- Unstructured Storage
1.Inherited Storage Pattern
出典 : proxy pattern
Proxy Patterns
UpgradeabilityProxyコントラクトとLogic Contractが同じUpgradeabilityStorageコントラクトを継承しています。UpgradeabilityStorageコントラクトは、共通の状態(slot)を2つのコントラクトに提供します。このコントラクトは、主にProxy Contractのadminアドレス(Proxy Contractの関数の実行権限を唯一持つアドレス)とLogic Contractのアドレスを保存することになります。これによりLogic Contract内で定義されるslotとStorage Contractで定義が必要だったslotの衝突を回避することができます。以下より具体的な実装を確認することが可能です。
upgradeability_using_inherited_storage
https://github.com/OpenZeppelin/openzeppelin-labs/tree/master/upgradeability_using_inherited_storage
2.Eternal Storage Pattern
出典 : proxy pattern
Proxy Patterns
このパターンで大事なのは、まずEternalStorageコントラクトです。このコントラクトではLogicに必要な全ての変数タイプをmappingを利用して事前に定義しています。mappingによって定義された変数を利用するためには、Logic Contract側でsetter関数とgetter関数を用意する必要があります。UpgradeablityStorageコントラクトには、Logic Contractのアドレスが、UpgradeabilityOwnerStorageコントラクトには、EternalStorageProxyコントラクトのadminアドレスを保存する変数が定義されています。この3つのStorage Contractを継承することでEternalStorageProxyコントラクト(Storage Contract)はslotを衝突させずにすみます。名前の通り(直訳すると永久ストレージ)このパターンでは、状態変数を変更することはできず、EternalStorageコントラクトに脆弱性があった場合、アップグレードを行うことはできません。
実装は以下より確認することが可能です。
upgradeability_using_eternal_storage
https://github.com/OpenZeppelin/openzeppelin-labs/tree/master/upgradeability_using_eternal_storage
3.Unstructured Storage Pattern
このパターンではストレージの衝突の回避します。
出典 : OpenZeppelin SDK Upgrades Pattern
https://docs.openzeppelin.com/sdk/2.5/pattern#transparent-proxies-and-function-clashes
ストレージの衝突とは上記のように、まず利用者がProxy Contractからdelegatecallを介して、Logic Contractの関数を利用した時に、Proxy Contract(上記の図ではProxyの列) のslot 1の部分に格納されている「address _implementation」が、Logic Contract(上記の図ではImplementationの列)のslot 1 の部分に格納されている「address _owner」と重複していることによって起こる問題です。利用者が呼び出した関数の処理が「address _owner」を利用することを意図していても、実際には、Storage Contractの役目を担っているProxy Contractの「address _implementation」を使ってしまうからです。
出典 : OpenZeppelin SDK Upgrades Pattern
https://docs.openzeppelin.com/sdk/2.5/pattern#transparent-proxies-and-function-clashes
そこで、Proxy Contractが保持する状態のslotの場所をランダムに決めることで、Logic Contractの状態との衝突を回避します。これがUnstructured Storage Pattern(非構造化ストレージパターン)です。1つのコントラクトが持つslotの数は、2の256乗個あるため、ランダムに決めても衝突は起きません。
実装例
bytes32 private constant implementationPosition = keccak256("org.zeppelinos.proxy.implementation");
function setImplementation(address newImplementation) internal {
bytes32 position = implementationPosition;
assembly {
sstore(position, newImplementation)
}
}
まずメモリ位置をハッシュ関数を使ってランダムに決めます。変数implementationPositionはconstantを使っているため、slotを使用しません。assembly内で、sstoreを利用してLogic Contractをランダムなメモリ位置(positionで指定している)に保存します。
出典 : proxy pattern
Proxy Patterns
このパターンでは、UpgradeablityProxyコントラクトにLogic Contractのアドレスを格納する変数が、OwnedUpgradeablityStorageコントラクトにOwnedUpgradeabilityコントラクトのadminアドレスを格納する変数がそれぞれ定義されています。この2つのコントラクトを継承するOwnedUpgradeablityProxyコントラクトは2つのslotをランダムな場所(2の256乗個のうちから)で、保持しています。これによりLogic Contractに安全に新規の状態変数を追加することができます。
upgradeability_using_unstructured_storage
https://github.com/OpenZeppelin/openzeppelin-labs/tree/master/upgradeability_using_unstructured_storage
OpenZeppelin SDK Upgrades Pattern
https://docs.openzeppelin.com/sdk/2.5/pattern#transparent-proxies-and-function-clashes
関連するEIPの紹介
現在3つのUpgradeabilityに関するEIPが提案されていてどれもdraft状態です。
EIP 897: ERC DelegateProxy
Proxy Contractの基本メソッドのインターフェースの標準化を目指しています。
EIP 1822: Universal Upgradeable Proxy Standard (UUPS)
Proxy Contractの具体的な実装の標準化を目指しています。
EIP 1967: Standard Proxy Storage Slots
unstructured proxy patternに関する実装方法の標準化を目指しています。
アップグレードツールの紹介
アップグレーダブルなコントラクトの設計は、高度なSolidityやEVMの知識を必要とするため、簡単には行えません。そこでコントラクトのアップグレードをサポートしてくれるツールを以下で簡単に紹介します。
OpenZeppelinSDKとは
OpenZeppelinSDKは、OpenZeppelinが提供するSmart Contract開発ツールです。Truffleを利用せずにSmart Contractのcompile、upgrade、deployが簡単に行えます。またCLIが充実しており、関数の実行も簡単に行えます。またアップグレード可能なコントラクトのパッケージであるopenzeppelin-contracts-ethereum-packageを利用することで、簡単にアップグレーダブルなコントラクトを作成できます。本連載の次回より使い方を扱っていきます。
OpenZeppelin SDK
https://openzeppelin.com/sdk/
OpenZeppelin SDK document v2.5
https://docs.openzeppelin.com/sdk/2.5/
AragonOSとは
AragonOSは、DAO、DApps、プロトコルを構築するためのSmart Contractのフレームワークです。OSである理由は、アプリやユーザーがDAOやプロトコルのリソースにアクセスする権限を管理するからです。アップグレードが可能な分散型アプリケーションとプロトコルを作成することが可能です。
Aragon ホームページ
https://aragon.org/discover/
Aragon Developer Portal
https://hack.aragon.org/
Aragon OS
https://hack.aragon.org/docs/aragonos-intro.html
まとめ
『第2回 UpgradeabilityとOpenZeppelinSDKとは』では、3つのアップグレードパターンを掘り下げて解説し、アップグレードを簡単に行うことが可能になる2つのツールについて、紹介しました。次回は、「3.openzeppelinSDKの基本」です。コントラクトのアップグレードツールであるopenzeppelinSDKを利用していくための、環境構築・利用の仕方・注意点について解説していきます。