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

#はじめに

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

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

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

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

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

#Proxy Patterns
<img width="512" alt="1Proxy0.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/2b12cf1b-bdd6-d66c-c328-410749a3f22c.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](https://remix.ethereum.org)を使用して確認できます。

```.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します。
<img width="500" alt="Proxy.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/8cc861c2-2311-7449-4ca8-0d6636ee4379.png">

## 2. TokenV0をdeployして、ProxyのupgradeTo()を実行します。
<img width="500" alt="TokenV0.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/326b0566-93d2-adf3-55bb-0a42d8f5d116.png">

## 3. Proxyのコントラクトアドレスを、At Addressへ入力して、TokenV0を読み込みます。
<img width="500" alt="TokenV0AtAddress.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/a1c648ce-f9c3-de9d-d461-bfe2272ae580.png">

## 4. setBalance()を実行します。
<img width="500" alt="TokenV0Balance.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/cd639cff-828d-9d7d-d901-c7df3b280462.png">

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

## 4. TokenV1をdeployします。
<img width="500" alt="TokenV1.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/ddd58e4b-6398-ec4a-f2ac-cb392162dc09.png">

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

<img width="500" alt="ProxyUpdate.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/b5394c70-2007-c356-ce19-e3dab625f9d1.png">

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

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

<img width="500" alt="TokenV1Balance.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/a59f4833-1b72-2274-2486-791ee44abcd8.png">

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

## 7. TokenV1のaddBalance()を実行します。
<img width="500" alt="TokenV1addBalance.png" src="https://qiita-image-store.s3.amazonaws.com/0/317035/d39ac81e-9718-e342-225d-c3e2633ae26f.png">

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

#まとめ

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

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