LoginSignup
25
18

More than 5 years have passed since last update.

Smart ContractをUpgradableにする方法

Last updated at Posted at 2018-05-13

Contract を Updateできない問題への解決策

ご存知の通り、ブロックチェーン技術を用いたスマートコントラクトは、良くも悪くもソースコードをアップデートできないという特徴があります。実際アプリケーションを運用するにあたって、アップデートができないというのはかなり厄介な訳ですが、ストレージとロジックを分離することによって、コントラクトも一部アップデートすることができるようになります。

contractアップデートのために使われる手法

僕が知ってる範囲では、アップデート可能なコントラクトを作るために利用される手法には以下があります。

  1. Interfaceの導入。抽象コントラクトを利用することで、参照先のコントラクトの関数内部を書き換える方法。cryptokittiesのgeneScienceコントラクトとかでも使われてます。
  2. Eternal Storageの導入。ロジックとデータストレージ用のコントラクトを分けることによって、ロジック部分をupdate可能にする。
  3. Ethereum name serviceを利用。これは単純にコントラクトアドレスをENSでマスキングしてユーザーから見たときに同じにしよう、というものでアップデート可能になっているかと言われるとビミョいですね。

1,2の考え方としては、ストレージとして利用するコントラクトと、ロジックを置くコントラクトを分離することで、後からロジック(関数の中身)をアップデートできる状態にしておこうというものです。

Interface

抽象コントラクト(abstract contract)を利用することで、関数のロジック部分を置き換えることができるようになります。

抽象コントラクトとは
例えば、普通のコントラクトは以下のようになります。めっちゃ短いサンプルですが。

contract Feline {
    function utterance() public returns (bytes32) { return "miaow"; }
}

一方抽象コントラクトは以下のようになります。

contract FelineInterface {
    function utterance() public returns (bytes32);
}

下の例のように、関数の名前やアクセス制限(visibility)、返り値と引数の型を定義することで、後から関数の中身を変更できるようになります。
例えば、Cat contract のなかでFelineInterfaceを利用することで、

contract Cat{
    FelineInterface feline;

    constructor(address _felineAddr) public {
        feline = FelineInterface(_felineAddr);
    }    

    function utterance() public returns (bytes32) { 
        feline.utterance();
    }
}

と、utterance関数を持ったコントラクトのアドレスをFelineInterfaceに渡すことで、utterance関数を利用できるようになります。

こうすることで、Cat contract自体をアップデートすることなく、utterance関数を持ったコントラクトをデプロイし、そのアドレスを設定することで関数の中身を更新することができるようになります。

Eternal Storage

Eternal Storageは、コントラクトのストレージ部分を切り出す手法です。
こちらの記事では、sha3でhash化したkeyとvalueを保持できるストレージ contractを作っています。こんな感じでkey-valueで実装すると拡張性が非常に高そうですね。

 contract EternalStorage{ 
     mapping(bytes32 => uint) UIntStorage; 
     function getUIntValue(bytes32 record) constant returns (uint){ 
         return UIntStorage[record]; 
     } 
     function setUIntValue(bytes32 record, uint value) 
     { 
         UIntStorage[record] = value; 
    } 
    mapping(bytes32 => string) StringStorage; 

    function getStringValue(bytes32 record) constant returns (string){ 
         return StringStorage[record]; 
   } 

    function setStringValue(bytes32 record, string value) 
    { 
         StringStorage[record] = value; 
     } 

     mapping(bytes32 => address) AddressStorage; 

     function getAddressValue(bytes32 record) constant returns (address){
         return AddressStorage[record]; 
     } 

     function setAddressValue(bytes32 record, address value) 
     { 
         AddressStorage[record] = value; 
     } 

     mapping(bytes32 => bytes) BytesStorage; 

     function getBytesValue(bytes32 record) constant returns (bytes){
         return BytesStorage[record]; 
     } 
     function setBytesValue(bytes32 record, bytes value) 
     { 
         BytesStorage[record] = value; 
     } 

     mapping(bytes32 => bool) BooleanStorage; 
     function getBooleanValue(bytes32 record) constant returns (bool){ 
         return BooleanStorage[record]; 
     } 
     function setBooleanValue(bytes32 record, bool value) 
     { 
         BooleanStorage[record] = value; 
     } 

   mapping(bytes32 => int) IntStorage; 

   function getIntValue(bytes32 record) constant returns (int){ 
        return IntStorage[record]; 
    } 
    function setIntValue(bytes32 record, int value) 
    { 
        IntStorage[record] = value; 
    } 
 } 

このようにstate変数と値をセットするexternal(or public)アクセスの関数を定義することで、ストレージとして利用するコントラクトを用意します。

それを例えば以下のように、別コントラクトとから呼び出すことで、ロジックとストレージを分離することができます。
*ここでは、EternalStorageのInterfaceを利用することでストレージの関数を呼び出しています。

 import "EternalStorageInterface.sol"; 
 contract Proposals{ 
 function getProposalCount(address _storageContract) constant 
    returns(uint256) 
 { 
     return 

    EternalStorageInterface(_storageContract).getUIntValue(sha3("ProposalCount"));
 } 
function addProposal(address _storageContract, bytes32 _name)
{
   var idx = getProposalCount(_storageContract);

   EternalStorageInterface(_storageContract).setBytes32Value(sha3("proposal_name", idx), _name); 
   EternalStorageInterface(_storageContract).setUIntValue(sha3("proposal_eth", idx), 0);
   EternalStorageInterface(_storageContract).setUIntValue(sha3("ProposalCount"), idx 
  + 1);
  } 
} 

こんな感じで、EternalStorageコントラクトに値をセットすることで、Proposal Contract(ロジック部分)と、Eternal Storage(ストレージ部分)を完全に分離することができ、ロジック部分を完全にアップデートすることができるようになります。

一応補足で、今回の例で書いたStorageコントラクトにはアクセス制限をつけていませんでしたが、任意の人がアクセスできる状態はよくないと思うので、setter関数には特定のアドレスからのみアクセスできるなど制限をつけた方が良さそうですね。

ざっくりこんな感じ。Ethereumを使ったコントラクトでもアップデートできるようにする方法はあるよ、という話でした。

Eternal Storageのデータ型をうまいこと設定すればゲーム用のストレージコントラクトの雛形とか作れそうですね。

うんこうんこ。

25
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
18