🐕

Solidity で Factory Contract の作成

2020/12/17に公開

概要

Solidity で Factory Contract (スマートコントラクトを作成するスマートコントラクト) を簡易的に実装して動作を確認する。

注意事項

開発環境

  • Truffle: v5.1.56
  • Ganache: v2.5.3
  • Solidity Compiler: v0.7.5

実装

まず Factory Contract に必要なものは二つです。

  1. スマートコントラクトを作るスマートコントラクト (Factory.sol)
  2. スマートコントラクトに作られるスマートコントラクト (Product.sol)

初めに Factory.sol の方から見てみましょう。

Factory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "./Product.sol";

contract Factory {
  address[] public products;

  function deploy(uint _data) public {
    Product newProduct = new Product(_data);
    products.push(address(newProduct));
  }

  function getAddress(uint _index) external view returns (address) {
    return products[_index];
  }

  function getLength() external view returns (uint) {
    return products.length;
  }
}

Factory.sol では以下6つのことが行われています。

  • Product.sol の import
  • Factory コントラクトの定義
  • 変数定義
    • address[] products: 作成されたコントラクトのアドレスを格納する
  • function 定義
    • deploy(uint _data): _data を引数としてコントラクトを作成する
    • getAddress(uint _index)[1]: _index 番目に作成されたコントラクトのアドレスを取得する
    • getLength(): これまでに作成されたコントラクトの総数を取得する

次に Product.sol を見ましょう。

Product.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

contract Product {
  uint public immutable data;

  constructor(uint _data) {
    data = _data;
  }
}

今回はコントラクトが存在していればよいので中身は必要ないのですが、動作の確認のために追加しています。
Product.sol では以下3つのことが行われています。

  • Product コントラクトの定義
  • 変数定義
    • uint data: コンストラクタに与えられた値を格納する
  • function 定義
    • constructor(uint _data): コンストラクタ作成時に一度だけ実行され、引数の _datadata 変数に格納する

実行 & 確認

必要なスマートコントラクトを理解したところで、実際にテスト環境で実行して確認してみましょう。
確認に必要なステップは概ね以下の8つです。

  1. truffle init
  2. Factory.sol & Product.sol の作成
  3. migration ファイル作成
  4. ganache との接続設定
  5. ganache 起動 & 接続
  6. truffle compile
  7. truffle migrate
  8. 確認

この記事では上記の内手順8のみを説明します。その他の手順はこの記事の本質とは関係ないので説明しません。(追記する可能性はありますが・・・)


そういうわけで手順8です。
truffle console を用いて ganache ネットワークへ接続している場合、

factory = await Factory.deployed()

console 上で上記のコマンドを打つことで ganache ネットワークにデプロイされている Factory コントラクトのインスタンスを取得できます。
undefined が返ってきましたら取得は成功しています。

取得できましたら、

factory.getLength()

を実行して products が空であることを確認しましょう。

BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }

返答は上記のように BigNumber という大きな数を扱うためのオブジェクト形式で帰ってきます。
詳細は省きますが、欲しいデータは words: [ 0, <1 empty item> ] 要素の第一要素です。つまり 0 です。

空であることを確認できたので、Product コントラクトを 1984 を引数としてデプロイし、値が変わることを確認します。

factory.deploy(1984)

上記コマンドが正常に処理された場合、長いので記載はしませんが生成されたトランザクションの情報が帰ってきます。

BN { negative: 0, words: [ 1, <1 empty item> ], length: 1, red: null }

再度 products の長さを取得しますと、上記のように今度は値が 1 になっていることを確認できると思います。

Product コントラクトが無事にデプロイ出来たようなので、そのアドレスを取得しましょう。

productAddress = factory.getAddress(0)

返答されるアドレスは環境によって異なりますが、

'0x50b7Dced96faf4E84ce656DD2Eae5038d77a476E'

私の実行時にはこのような返答が来ました。以降でこのアドレスが使用される場合は自身の環境で取得されたアドレスと読み替えてください。

簡単のため、生成されたコントラクト専用のインスタンスを作成します。

product_0 = await new web3.eth.Contract(Product.abi, '0x50b7Dced96faf4E84ce656DD2Eae5038d77a476E')

product_0 のデプロイ時には 1984 が引数として渡されているので、data には 1984 が格納されているはずです。確認しましょう。

product_0.methods.data().call()
'1984'

上記のように帰ってきましたら無事終了です。

最後に

読みづらい/分かりづらい箇所ですとか、誤りなど発見されましたら教えていただけると非常にありがたいです。

脚注
  1. 変数が public で定義されているので、本来はこの実装では getter は必要ありません。 ↩︎

Discussion