Forgeを使ってupgradableコントラクトを作成し、thirdweb上でデプロイしてみよう!

2022/12/03に公開

こんにちは、CryptoGamesの高橋です。

本日は、こちらの記事を元にして、upgradableのコントラクトを作っていきます。
https://blog.thirdweb.com/guides/how-to-upgrade-smart-contracts-upgradeable-smart-contracts/

では、やっていきます。

0.始める前に

今回は、Hardhatではなく、Forgeを利用します。
初めて実施する場合は、こちらの記事などもご参照ください。
https://zenn.dev/yuki2020/articles/ddd308a8e2e6cc

1.upgradableのコントラクトの仕組みとは

upgradableコントラクトは図のように二つのコントラクトから構成されています。

proxyコントラクトに値が入っており、implementationコントラクトにロジックが格納されています。

ロジックに誤りがあった場合などに、implementationコントラクトを別のimplementationコントラクトに変更することができます。

2.コントラクトのテンプレートを作成する

まずは、下のようにして、今回はForge用のテンプレートを作成します。

npx thirdweb@latest create --contract

これでできました。

3.implementationコントラクト(ロジック部分)を作成する

では、implementationコントラクトを作成していきます。

「src」フォルダに「Number.sol」というファイルを作り、次の公式にあるコードをコピペします。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@thirdweb-dev/contracts/extension/Upgradeable.sol";
import "@thirdweb-dev/contracts/extension/Initializable.sol";

contract Number is Upgradeable, Initializable {
    
	uint256 public num;

	address public deployer;

	function initialize(uint256 _initialValue) external initializer {
		require(_initialValue > 0);
		num = _initialValue;
		deployer = msg.sender;
	}

	function doubleNumber() external {
		num += 2; // Notice that we use the `+=` operation instead of
							// the "*=" operation, as the function name would suggest.
							// This is what we'll fix in our upgrade.
	}

	function _authorizeUpgrade(address) internal view override {
		require(msg.sender == deployer);
	}

}

このようになりました。

4.implementationコントラクト(ロジック部分)を確認する

1.値を入れる変数について

下のように、数字を入れるための、numがあります。

ただ、値は、proxyコントラクトだったはずです。(第1章参照)

そのため、ここではproxyコントラクトの値のエイリアス(別名)として、設定しています。

これにより、proxyコントラクトのnumを操作することができます。

2.ロジック(関数)について

ここでは、下のように、doubleNumber関数を定義しています。

想定では、特定の数字を2倍にするものですが、誤って、+2をするというロジックになっています。

なお、まさにこのようなロジックがimplementationコントラクトに格納されます。

3.初期化設定について

初期化設定は通常使われる「constructor」ではなく、「initialize関数」が使われます。

再確認ですが、値を格納するのはproxyコントラクトです。

そのため、値の初期化を行いたいのはproxyコントラクトです。

そのため、proxyコントラクトから呼び出せるように、関数である、「initialize関数」で設定します。

4.修飾子initializerについて

通常のconstructorであれば、コントラクト作成時に1度だけ実施するというような制約がされています。

一方、関数でそのような制約を行うためにはmodifier(修飾子)などで指定をすることが必要です。

そのため、図のようにinitializerが使用されています。

5.authorizeUpgradeについて

誰でもupgradableができてしまうと困りますので、開発者しかできないように下のように設定しています。

5.proxyコントラクト(値部分)を作成する

下のように、「src」フォルダ内に「Proxy.sol」を作成し、次の公式のコードをコピペします。

pragma solidity ^0.8.0;

import "@thirdweb-dev/contracts/extension/ProxyForUpgradeable.sol";

contract ProxyForNumber is ProxyForUpgradeable {

    constructor(address _logic, bytes memory _data) payable ProxyForUpgradeable(_logic, _data) {}
}

下のように貼り付けました。

初期化の際に渡す「_logic」にはimplementationコントラクトのアドレスを渡します。

一方、「_data」には初期化のために必要なinitialize関数の呼び出しをbytesにしたものを渡します。

これで、implementationコントラクトのinitialize関数を使って、proxyコントラクトの値を初期化することができます。

6.implementationコントラクト(ロジック部分)をデプロイする

では、中身の確認ができたので、デプロイを行ってみましょう。

まずは、下のようにして、implementationコントラクトである、Numberを実行します。

npx thirdweb@latest deploy

thirdwebが立ち上がるので、チェーンを確認した上で、「Deploy Now」を実施します。

すると、このようにコントラクトが作成できました。

7.initialize関数の呼び出し部分をbytesで取得する

では、proxyコントラクトにinitialize関数の呼び出し部分をbytesを取得していきます。

「Test」フォルダに「Initialize.t.sol」を作成し、公式のコードをコピペします。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "src/Number.sol";

contract InitializeTest is Test {
    function setUp() public {}

    function testInitializeData() public {

        uint256 initialValue = 5; // Use a non-zero initial value of choice.

        emit log_bytes(
            abi.encodeCall(
                Number.initialize,
                (initialValue)
            )
        );
    }
}

なお、下のように、ここでは初期値を5として設定しています。

次のコマンドでテストを実行すると、bytesが取得できます。

forge test --match-contract Initialize -vv

このように、取得ができました。

8.proxyコントラクト(値部分)をデプロイする

では、引数の準備が整ったので、proxyコントラクトをデプロイしていきます。

次のコマンドで実施していきます。

npx thirdweb@latest deploy

すると、thirdwebが立ち上がりますので、先ほど第6章で作成したimplementationコントラクトのアドレスと第7章で作成したbytesを入れて、「Deploy Now」

すると、このように、デプロイされました。
名前も「Number」となっていることもご注目ください。

9.proxyコントラクト(値部分)を触ってみる

では、今できたコントラクトを触ってみましょう。

「Explorer」タブから「num」を選択すると、初期値の「5」が確認できました。

では「doubleNumber」を実行してみましょう。

(あえて)間違って、2倍ではなく「+2」になっていることが確認できます。
(私は2回押したので、9になっています。)

10.更新用のimplementationコントラクトを作成する

では、implementationコントラクトを修正しましょう。

下のように、掛け算に直し、デプロイを行います。

npx thirdweb@latest deploy

チェーンを確認した上で、「Deploy Now」

すると、下のように、新しいimplementationコントラクトができました。

11.proxyコントラクトから更新する

では、proxyコントラクトの「upgradeTo」関数から今できたアドレスを設定して、実行していきます。

下のように実行できました。

では、doubleNumber関数を実行します。

下のように、無事に2倍されるようになりました。

今回は以上です。

ぜひやってみてください。

Discussion