10
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Solidity コントラクトの更新(update/upgrade) Proxy Contract

Last updated at Posted at 2019-04-06

はじめに

「ブロックチェーンのサービス提供した後、バグを直せないと怖い。。。」
「コントラクトを更新したいけどどうすればいいの!?」
「コントラクトってそもそも更新できるんだろうか?」
「Proxy Contractって聞いたことあるけど何?」

などと思ったことは一度はあるのではないでしょうか?

Solidityでは、コントラクトをデプロイした後、通常は更新できません。機能を追加したコントラクトを再度デプロイすると別のアドレスになってしまいます。

Proxy Contractというコントラクトを使うことで、アップデート/アップグレードすることができます。

zeppelin OSの下記の記事を参考にしています。
https://blog.zeppelinos.org/proxy-patterns/

Proxy Patterns

1Proxy0.png

Proxy ContractとLogic Contractに分けられます。
メッセージ呼び出しがProxy Contractを通過して、それらが最新のデプロイされたLogic Contractにリダイレクトさされます。アップグレードするには、新しいバージョンのコントラクトを展開し、新しいコントラクトアドレスを参照するようにProxy Contractを更新します。

Proxy Contract

Proxy Contractは delegetecall関数を実行します。

ユーザはProxy Contractへトランザクションを送信し、delegetecallを実行することで更新可能なコントラクトを実行します。このProxyコントラクト自体は更新しません。そのため、コントラクトを更新しても、同じアドレスに対してトランザクションを送信することになります。

.java
pragma solidity ^0.5.0;

/**
 * @title Proxy
 */
contract Proxy {
  address public implementation;

  /**
  * @dev ロジックコントラクトのアドレスアドレスの設定
  */
  function upgradeTo(address _address) public {
    implementation = _address;
  }

  /**
  * @dev Fallback関数 ロジックコントラクトのdelegatecall()を実行する
  */
  function () payable external {
    // ロジックコントラクトのアドレス
    address _impl = implementation;
    require(_impl != address(0));
    
    assembly {
      // 0x40は次に利用可能な空きメモリポインタを格納する
      let ptr := mload(0x40)
      // ptrのメモリ領域に、calldataの0からcalldatasize分をコピーする
      calldatacopy(ptr, 0, calldatasize)
      /*
       gas:          関数を実行実行するために必要なgas
       _impl:        ロジックコントラクトのアドレス
       ptr:          データが始まる場所のメモリポインタ
       calldatasize: 渡すデータサイズ
       outdata:      delegatecallが実行された後に返却されるデータで使用されません
       outsize:      delegatecallが実行された後に返却されるデータのサイズで使用されません
      */
      let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
      // 返されたデータのサイズ
      let size := returndatasize
      // 空きメモリポインタに0からreturndata分をコピーする
      returndatacopy(ptr, 0, size)

      // 結果を返却する
      switch result
      // 0の場合:エラーのためrevert
      case 0 { revert(ptr, size) }
      // それ以外の場合:データを返す
      default { return(ptr, size) }
    }
  }
}

更新してみよう

TokenV0.sol / TokenV1.solのコントラクトを使って、更新できることを確認してみましょう。
Remixを使用して確認できます。

.java
pragma solidity ^0.5.0;

/**
 * @title Token Version 0
 */
contract TokenV0 {
    mapping (address => uint) balances;
    
    function balanceOf(address _address) public view returns(uint) {
        return balances[_address];
    }
    
    function setBalance(address to, uint256 _balance) public {
        balances[to] = _balance;
    }
}
.java
pragma solidity ^0.5.0;

import "./TokenV0.sol";

/**
 * @title Token Version 1
 */
contract TokenV1 is TokenV0 {

    function addBalance(address to, uint256 _balance) public {
        balances[to] += _balance;
    }
}

1. Proxyをdeployします。

Proxy.png

2. TokenV0をdeployして、ProxyのupgradeTo()を実行します。

TokenV0.png

3. Proxyのコントラクトアドレスを、At Addressへ入力して、TokenV0を読み込みます。

TokenV0AtAddress.png

4. setBalance()を実行します。

TokenV0Balance.png

残高を100に設定することができました。

4. TokenV1をdeployします。

TokenV1.png

5. ProxyのupgradeTo()を、TokenV1のコントラクトアドレスを入力して実行します。

ProxyUpdate.png

TokenV1へ更新することができました。

6. Proxyのコントラクトアドレスを、At Addressへ入力して、TokenV1を読み込みます。

TokenV1Balance.png

TokenV0からTokenV1へ更新した後も、残高は100になります。

7. TokenV1のaddBalance()を実行します。

TokenV1addBalance.png

50を追加して、残高を150にすることができました。

まとめ

  • Proxy Contractを使うことで、コントラクトをアップグレードすることができました。
  • storageはProxy Contractへ保存されます。

※Storageは Eternal Storageというkey-value方式で記載することもできます。

10
13
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
10
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?