0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FiatTokenを開発環境にデプロイ

Posted at

はじめに(Introduction)

JPYC や USDC などのステーブルコインで使用されている、FiatToken を開発環境で使用したい場合の手順に関する記事です。

本記事では、開発環境を Hardhat 、IDE を VSCode としています。

FiatToken生成

FiatToken のソースコードは以下の JPYCv2 を利用します。
開発環境と JPYCv2 では Hardhat を利用していますが、 JPYCv2Hardhat が最新でない為、コンパイル済みのファイルを開発環境へ移動することとします。

git clone

ワークフォルダ配下にて以下のコマンドを実行してソースコードを取得します。

git clone https://github.com/jcam1/JPYCv2.git

ソースコードを取得したら JPYCv2 フォルダに移動します。

cd JPYCv2        

npm install

パッケージをインストールします。

npm install

hardhat.config.js修正

以下のコマンドで VSCode を起動します。

code .

hardhat.config.js を修正します。
修正箇所は、networksrinkebyブロックをコメントアウトします。

【修正例】hardhat.config.js
  networks: {
    hardhat: {
      chainId: 1337,
      allowUnlimitedContractSize: false,
    },
    // rinkeby: {
    //   url: process.env.INFRA_API_KEY,
    //   accounts: [process.env.PRIVATE_KEY],
    // },
  },

npx hardhat compile

以下のコマンドを実行してコンパイルを行います。

npx hardhat compile

以下のような結果となります。
※:状況によりコンパイラ(solc)がダウンロードされる場合があります。

【コンパイル結果例】
Solidity 0.8.11 is not fully supported yet. You can still use Hardhat, but some features, like stack traces, might not work correctly.

Learn more at https://hardhat.org/reference/solidity-support

Compiling 1 file with 0.4.24
Compiling 30 files with 0.8.11
Compilation finished successfully

ファイル取得

以下の3つのファイルを取得します。

  • artifacts\contracts\v1\FiatTokenV1.sol\FiatTokenV1.json
  • artifacts\contracts\v2\FiatTokenV2.sol\FiatTokenV2.json
  • artifacts\contracts\proxy\ERC1967Proxy.sol\ERC1967Proxy.json

開発環境

開発環境に FiatToken をデプロイします。

開発環境構築

※:すでに開発環境が存在する場合は割愛してください。

npm の初期化をします。

npm init -y

hardhatをインストールします。

npm install hardhat@latest

hardhat を初期化します。(TypeScript を選択しています。)

npx hardhat init

ファイルの設置

開発環境に FiatToken フォルダを作成し、コンパイルで作成した3つの json ファイルを配置します。

【設置例】
work
│  .gitignore
│  hardhat.config.ts
│  package-lock.json
│  package.json
│  README.md
│  tree.txt
│  tsconfig.json
│  
├─contracts
├─FiatToken
│      ERC1967Proxy.json
│      FiatTokenV1.json
│      FiatTokenV2.json
├─ignition
├─node_modules
└─test

deployFixture

Hardhat のテストにおいてFiatToken のデプロイと初期化をする deployFixture を作成します。

deployFixture

ヘッダ

import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { ethers } from "hardhat";
import type { Signer, Contract } from "ethers";
import { expect } from "chai";

import FiatTokenV1Json from "../FiatToken/FiatTokenV1.json";
import FiatTokenV2Json from "../FiatToken/FiatTokenV2.json";
import ERC1967ProxyJson from "../FiatToken/ERC1967Proxy.json";

const TOKEN_NAME = "Dummy Fiat Token";
const TOKEN_SYMBOL = "DFT";
const TOKEN_CURRENCY = "DJPY";
const TOKEN_DECIMALS = 18;

deployFixture

  async function deployFixture() {
    // アカウントを取得
    const [
      deployer,
      minterAdmin,
      minter,
      pauser,
      blocklister,
      rescuer,
      owner,
      user1,
      user2,
      user3,
    ] = await ethers.getSigners();

    // FiatTokenV1コントラクトをデプロイ
    const FiatTokenV1 = new ethers.ContractFactory(
      FiatTokenV1Json.abi,
      FiatTokenV1Json.bytecode,
      deployer
    );
    const fiatTokenV1 = await FiatTokenV1.deploy();
    await fiatTokenV1.waitForDeployment();

    // FiatTokenV2コントラクトをデプロイ
    const FiatTokenV2 = new ethers.ContractFactory(
      FiatTokenV2Json.abi,
      FiatTokenV2Json.bytecode,
      deployer
    );
    const fiatTokenV2 = await FiatTokenV2.deploy();
    await fiatTokenV2.waitForDeployment();

    // ERC1967Proxyコントラクトをデプロイ、初期化
    const ERC1967Proxy = new ethers.ContractFactory(
      ERC1967ProxyJson.abi,
      ERC1967ProxyJson.bytecode,
      deployer
    );
    const erc1967Proxy = await ERC1967Proxy.deploy(
      fiatTokenV1.target, // 初期実装コントラクトのアドレス
      fiatTokenV1.interface.encodeFunctionData(
        "initialize", // 初期化関数
        [
          TOKEN_NAME, // トークン名
          TOKEN_SYMBOL, // トークンシンボル
          TOKEN_CURRENCY, // 通貨名
          TOKEN_DECIMALS, // 小数点以下の桁数
          minterAdmin.address, // minter管理者アドレス
          pauser.address, // pauserアドレス
          blocklister.address, // blocklisterアドレス
          rescuer.address, // rescuerアドレス
          owner.address, // オーナーアドレス
        ]
      )
    );
    await erc1967Proxy.waitForDeployment();

    // Proxy経由でFiatTokenV1コントラクトを操作するためのインスタンスを作成
    const fiatTokenV1Proxy = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV1Json.abi,
      owner
    );

    // Proxyコントラクトの実装をFiatTokenV2にアップグレードし、initializeV2を実行
    let tx = await fiatTokenV1Proxy.upgradeToAndCall(
      fiatTokenV2.target, // 新しい実装コントラクトのアドレス
      fiatTokenV2.interface.encodeFunctionData("initializeV2")
    );
    await tx.wait();

    // Proxy経由でFiatTokenV2コントラクトを操作するためのインスタンスを作成
    const fiatTokenV2Proxy = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      minterAdmin
    );

    // Minterの初期供給量を設定
    tx = await fiatTokenV2Proxy.configureMinter(
      minter.address,
      ethers.parseUnits("100000000", TOKEN_DECIMALS)
    );
    await tx.wait();

    const token = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      minter
    );

    tx = await token.mint(
      user1.address,
      ethers.parseUnits("10000", TOKEN_DECIMALS)
    );
    await tx.wait();

    return {
      deployer,
      minterAdmin,
      minter,
      pauser,
      blocklister,
      rescuer,
      owner,
      user1,
      user2,
      user3,
      fiatTokenV1,
      fiatTokenV2,
      erc1967Proxy,
    };
  }

アカウント

各アカウントの説明です。

Account Description
deployer コントラクトを deploy するアカウント
minterAdmin mint 実行が可能なアカウントを設定するアカウント
minter minterAdmin が設定する mint 実行が可能なアカウント
pauser pause 実行が可能なアカウント
blocklister ブロックリストの追加、削除が可能なアカウント
rescuer rescueERC20 実行が可能なアカウント
owner 各アカウントを更新可能なアカウント
user1 テスト内で使用するユーザー1
user2 テスト内で使用するユーザー2
user3 テスト内で使用するユーザー3

デプロイ

ERC1967Proxy に実装として FiatTokenV1 を設定し、
その後 upgradeToAndCall にて実装を FiatTokenV2 に変更します。

import

配置した3つの json ファイルをインポートします。
また、トークンに設定する値を定数として定義します。

import FiatTokenV1Json from "../FiatToken/FiatTokenV1.json";
import FiatTokenV2Json from "../FiatToken/FiatTokenV2.json";
import ERC1967ProxyJson from "../FiatToken/ERC1967Proxy.json";

const TOKEN_NAME = "Dummy Fiat Token";
const TOKEN_SYMBOL = "DFT";
const TOKEN_CURRENCY = "DJPY";
const TOKEN_DECIMALS = 18;

①FiatTokenV1のデプロイ

FiatTokenV1 を実装(implementation)としてデプロイします。

    // FiatTokenV1コントラクトをデプロイ
    const FiatTokenV1 = new ethers.ContractFactory(
      FiatTokenV1Json.abi,
      FiatTokenV1Json.bytecode,
      deployer
    );
    const fiatTokenV1 = await FiatTokenV1.deploy();
    await fiatTokenV1.waitForDeployment();

②FiatTokenV2のデプロイ

FiatTokenV2 を実装(implementation)としてデプロイします。

    // FiatTokenV2コントラクトをデプロイ
    const FiatTokenV2 = new ethers.ContractFactory(
      FiatTokenV2Json.abi,
      FiatTokenV2Json.bytecode,
      deployer
    );
    const fiatTokenV2 = await FiatTokenV2.deploy();
    await fiatTokenV2.waitForDeployment();

③ERC1967Proxyデプロイ、初期化

ERC1967Proxy をデプロイします。
実装(implementation)を FiatTokenV1 とし、initialize(初期化関数)で初期値を設定しています。

    // ERC1967Proxyコントラクトをデプロイ、初期化
    const ERC1967Proxy = new ethers.ContractFactory(
      ERC1967ProxyJson.abi,
      ERC1967ProxyJson.bytecode,
      deployer
    );
    const erc1967Proxy = await ERC1967Proxy.deploy(
      fiatTokenV1.target, // 初期実装コントラクトのアドレス
      fiatTokenV1.interface.encodeFunctionData(
        "initialize", // 初期化関数
        [
          TOKEN_NAME, // トークン名
          TOKEN_SYMBOL, // トークンシンボル
          TOKEN_CURRENCY, // 通貨名
          TOKEN_DECIMALS, // 小数点以下の桁数
          minterAdmin.address, // minter管理者アドレス
          pauser.address, // pauserアドレス
          blocklister.address, // blocklisterアドレス
          rescuer.address, // rescuerアドレス
          owner.address, // オーナーアドレス
        ]
      )
    );
    await erc1967Proxy.waitForDeployment();

④アップグレード

実装(implementation)を FiatTokenV2 にアップグレードします。
FiatTokenV1upgradeToAndCall を利用します。
新しい実装(implementation)を FiatTokenV2 とし、initializeV2(初期化関数)で新しい実装の初期化をしています。

    // Proxy経由でFiatTokenV1コントラクトを操作するためのインスタンスを作成
    const fiatTokenV1Proxy = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV1Json.abi,
      owner
    );

    // Proxyコントラクトの実装をFiatTokenV2にアップグレードし、initializeV2を実行
    let tx = await fiatTokenV1Proxy.upgradeToAndCall(
      fiatTokenV2.target, // 新しい実装コントラクトのアドレス
      fiatTokenV2.interface.encodeFunctionData("initializeV2")
    );
    await tx.wait();

FiatTokenmint

FiatTokenmint するには以下の手順が必要です。

configureMinter

mintAminmint 可能なアカウント(minter)と初期供給量(100000000)を設定します。

    // Proxy経由でFiatTokenV2コントラクトを操作するためのインスタンスを作成
    const fiatTokenV2Proxy = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      minterAdmin
    );

    // Minterの初期供給量を設定
    tx = await fiatTokenV2Proxy.configureMinter(
      minter.address,
      ethers.parseUnits("100000000", TOKEN_DECIMALS)
    );
    await tx.wait();

mint

minter が ユーザー(user1)にトークン(10000)を mint します。

    // Proxy経由でFiatTokenV2コントラクトを操作するためのインスタンスを作成
    const token = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      minter
    );

    // ミント
    tx = await token.mint(
      user1.address,
      ethers.parseUnits("10000", TOKEN_DECIMALS)
    );
    await tx.wait();

FiatTokenの転送

ユーザーがトークンを転送する方法をいくつか試してみます。

transfer

ユーザー(user1)が自身のトークンを他のユーザー(user2)に transfer(転送)します。
トランザクション送信者はユーザー(user1)となります。

コード)

  it("transfer", async function () {
    // デプロイされたコントラクトとユーザーアカウントを取得
    const { erc1967Proxy, user1, user2 } = await loadFixture(deployFixture);
    // FiatTokenV2コントラクトを操作するためのインスタンスを作成
    let token = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      user1
    );

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("10000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("0", TOKEN_DECIMALS)
    );

    // ユーザー1からユーザー2にトークンを転送
    let tx = await token.transfer(
      user2.address,
      ethers.parseUnits("1000", TOKEN_DECIMALS)
    );
    let receipt = await tx.wait();
    expect(receipt.status).to.equal(1);

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("9000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("1000", TOKEN_DECIMALS)
    );
  });

transferWithAuthorization

ユーザー(user1)が自身のトークンを他のユーザー(user2)に transferWithAuthorization(転送)します。
ユーザー(user1)は、送信を許可する署名値をトランザクション送信者(user3)に渡します。
トランザクション送信者(user3)は署名値を使用して transferWithAuthorization(転送)を実行します。

コード)

  it("transferWithAuthorization", async function () {
    // デプロイされたコントラクトとユーザーアカウントを取得
    const { erc1967Proxy, user1, user2, user3 } = await loadFixture(
      deployFixture
    );
    // FiatTokenV2コントラクトを操作するためのインスタンスを作成
    let token = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      user3
    );

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("10000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("0", TOKEN_DECIMALS)
    );

    // EIP712のドメインを設定
    const domain = {
      name: await token.name(), // ドメイン名
      version: "2", // ドメインバージョン
      verifyingContract: token.target as string, // コントラクトアドレス
      chainId: (await ethers.provider.getNetwork()).chainId, // チェーンID
    };
    // TransferWithAuthorizationのタイプを定義
    const TYPES = {
      TransferWithAuthorization: [
        { name: "from", type: "address" }, // 支払アドレス (承認者)
        { name: "to", type: "address" }, // 受取アドレス
        { name: "value", type: "uint256" }, // 送金金額
        { name: "validAfter", type: "uint256" }, // この時間の後で有効 (UNIX 時間)
        { name: "validBefore", type: "uint256" }, // この時間の前で有効 (UNIX 時間)
        { name: "nonce", type: "bytes32" }, // 一意の nonce (32 バイト)
      ],
    };
    // TransferWithAuthorizationの値を定義
    const value = {
      from: user1.address, // 支払アドレス (承認者)
      to: user2.address, // 受取アドレス
      value: ethers.parseUnits("1000", TOKEN_DECIMALS), // 送金金額
      validAfter: 0n, // この時間の後で有効 (UNIX 時間)
      validBefore: BigInt(Math.floor(Date.now() / 1000)) + 3600n, // この時間の前で有効 (UNIX 時間)
      nonce: ethers.randomBytes(32), // 一意の nonce (32 バイト)
    };
    // EIP712の署名を取得
    const sign = ethers.Signature.from(
      await user1.signTypedData(domain, TYPES, value)
    );

    // ユーザー1からユーザー2にトークンを転送
    let tx = await token.transferWithAuthorization(
      value.from, // 支払アドレス (承認者)
      value.to, // 受取アドレス
      value.value, // 送金金額
      value.validAfter, // この時間の後で有効 (UNIX 時間)
      value.validBefore, // この時間の前で有効 (UNIX 時間)
      value.nonce, // 一意の nonce (32 バイト)
      sign.v, // 署名の v 値
      sign.r, // 署名の r 値
      sign.s // 署名の s 値
    );
    let receipt = await tx.wait();
    expect(receipt.status).to.equal(1);

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("9000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("1000", TOKEN_DECIMALS)
    );
  });

receiveWithAuthorization

ユーザー(user1)が自身のトークンを他のユーザー(user2)に receiveWithAuthorization(転送)します。
ユーザー(user1)は、送信を許可する署名値をトランザクション送信者(user2)に渡します。
トランザクション送信者(user2)は署名値を使用して receiveWithAuthorization(転送)を実行します。

※:transferWithAuthorizationとの違いは、トランザクション送信者(user2)がトークンの受取ユーザー(user2)と同じ必要があることです。

コード)

  it("receiveWithAuthorization", async function () {
    // デプロイされたコントラクトとユーザーアカウントを取得
    const { erc1967Proxy, user1, user2, user3 } = await loadFixture(
      deployFixture
    );
    // FiatTokenV2コントラクトを操作するためのインスタンスを作成
    let token = new ethers.Contract(
      erc1967Proxy.target,
      FiatTokenV2Json.abi,
      user2
    );

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("10000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("0", TOKEN_DECIMALS)
    );

    // EIP712のドメインを設定
    const domain = {
      name: await token.name(), // ドメイン名
      version: "2", // ドメインバージョン
      verifyingContract: token.target as string, // コントラクトアドレス
      chainId: (await ethers.provider.getNetwork()).chainId, // チェーンID
    };
    // ReceiveWithAuthorizationのタイプを定義
    const TYPES = {
      ReceiveWithAuthorization: [
        { name: "from", type: "address" }, // 支払アドレス (承認者)
        { name: "to", type: "address" }, // 受取アドレス
        { name: "value", type: "uint256" }, // 送金金額
        { name: "validAfter", type: "uint256" }, // この時間の後で有効 (UNIX 時間)
        { name: "validBefore", type: "uint256" }, // この時間の前で有効 (UNIX 時間)
        { name: "nonce", type: "bytes32" }, // 一意の nonce (32 バイト)
      ],
    };
    // ReceiveWithAuthorizationの値を定義
    const value = {
      from: user1.address, // 支払アドレス (承認者)
      to: user2.address, // 受取アドレス
      value: ethers.parseUnits("1000", TOKEN_DECIMALS), // 送金金額
      validAfter: 0n, // この時間の後で有効 (UNIX 時間)
      validBefore: BigInt(Math.floor(Date.now() / 1000)) + 3600n, // この時間の前で有効 (UNIX 時間)
      nonce: ethers.randomBytes(32), // 一意の nonce (32 バイト)
    };
    // EIP712の署名を取得
    const sign = ethers.Signature.from(
      await user1.signTypedData(domain, TYPES, value)
    );

    // ユーザー1からユーザー2にトークンを転送
    let tx = await token.receiveWithAuthorization(
      value.from, // 支払アドレス (承認者)
      value.to, // 受取アドレス
      value.value, // 送金金額
      value.validAfter, // この時間の後で有効 (UNIX 時間)
      value.validBefore, // この時間の前で有効 (UNIX 時間)
      value.nonce, // 一意の nonce (32 バイト)
      sign.v, // 署名の v 値
      sign.r, // 署名の r 値
      sign.s // 署名の s 値
    );
    let receipt = await tx.wait();
    expect(receipt.status).to.equal(1);

    // ユーザー1とユーザー2の残高を確認
    expect(await token.balanceOf(user1.address)).to.equal(
      ethers.parseUnits("9000", TOKEN_DECIMALS)
    );
    expect(await token.balanceOf(user2.address)).to.equal(
      ethers.parseUnits("1000", TOKEN_DECIMALS)
    );
  });

まとめ

FiatToken を利用したコントラクトやアプリを作成するための準備ができると思います。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?