メインコンテンツへスキップ

DeFiアプリを作成してデプロイする

SolidityDeFiweb3.jsTruffleGanacheスマートコントラクト
中級
strykerin
github.com(opens in a new tab)
2020年12月31日
15 分の読書 minute read

このチュートリアルでは、Solidity を使用して DeFi アプリケーションを構築します。このアプリケーションでは、ERC20 トークンをスマートコントラクトに入金した上で、Farm Token を発行して転送することができます。 ユーザーはその後、スマートコントラクトで Farm Token をバーンすることで ERC-20 トークンを引き出すことができ、ERC-20 トークンは再度ユーザーに転送されます。

Truffle と Ganache をインストールする

はじめてスマートコントラクトを作成する場合は、環境設定が必要です。 Truffle(opens in a new tab)Ganache(opens in a new tab)という 2 つのツールを使用します。

Truffle はイーサリアムのスマートコントラクトを開発するための開発環境とテストフレームワークです。 Truffle を使用すると、簡単にスマートコントラクトを構築し、ブロックチェーンでデプロイできます。 Ganache は、スマートコントラクトをテストするために、ローカルのイーサリアムブロックチェーンを作成することができます。 Ganache は、実際のネットワークの機能をシミュレートし、最初の 10 アカウントに対しテスト用の 100ether が供給します。 これにより、無料で自由にスマートコントラクトをデプロイし、テストできるようになります。 Ganache は、デスクトップアプリケーションとコマンドラインツールの両方が提供されています。 この記事では、UI デスクトップアプリケーションを使用します。

Ganache UIデスクトップアプリケーション(opens in a new tab)Ganache の UI デスクトップアプリケーション

プロジェクトを作成するには、次のコマンドを実行します

mkdir your-project-name
cd your-project-name
truffle init

これにより、スマートコントラクトを開発し、デプロイするための空のプロジェクトが作成されます。 作成したプロジェクトの構造は以下のようになります:

  • contracts:Solidity で作成したスマートコントラクトのフォルダ

  • migrations:デプロイ用スクリプトのフォルダ

  • test:スマートコントラクトをテストするためのフォルダ

  • truffle-config.js:Truffle の設定ファイル

ERC-20 トークンを作成する

最初に、スマートコントラクトにステークするために使用する ERC-20 トークンを作成する必要があります。 代替可能なトークンを作成するには、まず OpenZeppelin ライブラリをインストールする必要があります。 このライブラリには、ERC-20 や ERC-721 のような標準の実装が含まれています。 インストールするには、以下のコマンドを実行します:

npm install @openzeppelin/contracts

OpenZeppelin ライブラリを使用して、contracts/MyToken.solに以下の Solidity コードを書き込むことで ERC-20 トークンを作成できます。

1pragma solidity ^0.8.0;
2
3import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4
5contract MyToken is ERC20 {
6 constructor() public ERC20("MyToken", "MTKN"){
7 _mint(msg.sender, 1000000000000000000000000);
8 }
9}
すべて表示
コピー

上記のコードでは:

  • 3 行目:このトークン標準の実装を含む OpenZeppelin から、ERC20.sol コントラクトをインポートします。

  • 5 行目:ERC20.sol コントラクトを継承します。

  • 6 行目:ERC20.sol コンストラクタを呼び出し、名前とシンボルパラメータを "MyToken""MTKN" として引き渡します。

  • 7 行目:スマートコントラクトをデプロイしているアカウントに、100 万トークンを発行して転送します(ERC-20 トークンでは、デフォルトである小数点以下 18 桁を使用します。 つまり、1 トークンをミントしたい場合は、1000000000000000000 と、1 の後に 18 個の 0 を付けて表します) 。

以下の ERC20.sol コンストラクタの実装では、 _decimalsフィールドが 18 に設定されているのが確認できます:

1string private _name;
2string private _symbol;
3uint8 private _decimals;
4
5constructor (string memory name_, string memory symbol_) public {
6 _name = name_;
7 _symbol = symbol_;
8 _decimals = 18;
9}
すべて表示
コピー

ERC-20 トークンをコンパイルする

スマートコントラクトをコンパイルするには、まず Solidity コンパイラのバージョンを確認してください。 バージョンを確認するには、以下のコマンドを実行します:

truffle version

デフォルトのバージョンは Solidity v0.5.16 です。 このトークンは Solidity のバージョン 0.6.2 を使って作成されているため、このコマンドを実行してコントラクトをコンパイルしようとするとエラーが発生します。 使用したい Solidity コンパイラのバージョンを指定するには、truffle-config.jsを開き、以下のように設定します。

1// Configure your compilers
2compilers: {
3 solc: {
4 version: "^0.8.0", // Fetch exact version from solc-bin (default: truffle's version)
5 // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
6 // settings: { // See the solidity docs for advice about optimization and evmVersion
7 // optimizer: {
8 // enabled: false,
9 // runs: 200
10 // },
11 // evmVersion: "byzantium"
12 // }
13 }
14}
すべて表示

以下のコマンドを実行すると、スマートコントラクトがコンパイルされます。

truffle compile

ERC-20 トークンをデプロイする

コンパイルが完了すると、トークンをデプロイできるようになります。

migrationsフォルダに、 2_deploy_Tokens.jsという名前のファイルを作成します。 このファイルでは、ERC-20 トークンと Farm Token スマートコントラクトの両方をデプロイします。 以下のコードは、MyToken.sol コントラクトをデプロイするために使用します:

1const MyToken = artifacts.require("MyToken")
2
3module.exports = async function (deployer, network, accounts) {
4 // Deploy MyToken
5 await deployer.deploy(MyToken)
6 const myToken = await MyToken.deployed()
7}

Ganache を開いて「QuickStart」のオプションを選択すると、ローカルのイーサリアムブロックチェーンが開始されます。 コントラクトをデプロイするには、以下を実行します:

truffle migrate

コントラクトのデプロイに使用するアドレスは、Ganache で表示されるアドレスリストの一番上のアドレスです。 これを確認するには、Ganache のデスクトップアプリケーションを開きます。スマートコントラクトをデプロイするためにコストが発生したため、リストの一番上のアカウントでは ether の残高が減少していることを確認してください。

Ganacheデスクトップアプリケーション(opens in a new tab)Ganache デスクトップアプリケーション

デプロイ先アドレスに「MyToken」トークンが 100 万個送信されたことを確認するには、Truffle Console を使ってデプロイ済みのスマートコントラクトと接続します。

Truffle Console は、あらゆるイーサリアムクライアントに接続できる基本的な対話型コンソールです。(opens in a new tab)

スマートコントラクトとのやりとりを実行するには、以下のコマンドを実行します:

truffle console

ターミナル上で、以下のコマンドが入力可能になりました:

  • スマートコントラクトの取得: myToken = await MyToken.deployed()

  • Ganache からアカウント情報を取得する:accounts = await web3.eth.getAccounts()

  • 一番上のアカウントの残高を取得する: balance = await myToken.balanceOf(accounts[0])

  • 残高のフォーマットを、小数点以下 18 桁に設定する:web3.utils.fromWei(balance.toString())

上記のコマンドを実行すると、最初のアドレスに実際に 100 万個の MyTokens が含まれていることが確認できます。

一番上のアドレスに、100万MyTokenが含まれています(opens in a new tab)

最初のアドレスに、100 万 MyTokens が含まれています。

Farm Token のスマートコントラクトを作成する

Farm Token のスマートコントラクトは、以下の 3 つの機能を持ちます:

  • balance():Farm Token スマートコントラクトにおける MyToken の残高を取得します。

  • deposit(uint256 _amount):ユーザーを代理して MyToken を Farm Token スマートコントラクトに転送し、Farm Token を作成した上でユーザーに返送します。

  • withdraw(uint256 _amount):ユーザーの Farm Token をバーンし、MyToken をユーザーのアドレスに転送します。

以下の Farm Token コンストラクタを確認してください:

1pragma solidity ^0.6.2;
2
3import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
4import "@openzeppelin/contracts/utils/Address.sol";
5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
7
8contract FarmToken is ERC20 {
9 using Address for address;
10 using SafeMath for uint256; // As of Solidity v0.8.0, mathematical operations can be done safely without the need for SafeMath
11 using SafeERC20 for IERC20;
12
13 IERC20 public token;
14
15 constructor(address _token)
16 public
17 ERC20("FarmToken", "FRM")
18 {
19 token = IERC20(_token);
20 }
すべて表示
コピー
  • 3 ~ 6 行目: OpenZeppelin から、以下のコントラクト(IERC20.sol、Address.sol、SafeERC20.sol、ERC20.sol)をインポートします。

  • 8 行目:この Farm Token は、ERC-20 コントラクトを継承します。

  • 14 ~ 19 行目:Farm Token コンストラクタは、MyToken コントラクトのアドレスをパラメータとして受け取り、このコントラクトにtokenの public 変数を割り当てます。

それでは、balance()関数を実装しましょう。 この関数は、パラメータを受け取らず、スマートコントラクトの MyToken 残高を返します。 以下のように実装します:

1function balance() public view returns (uint256) {
2 return token.balanceOf(address(this));
3}
コピー

deposit(uint256 _amount)関数は、ユーザーが入金したい金額をパラメータとして受け取り、Farm Token を作成した上でユーザーに転送します。

1function deposit(uint256 _amount) public {
2 // Amount must be greater than zero
3 require(_amount > 0, "amount cannot be 0");
4
5 // Transfer MyToken to smart contract
6 token.safeTransferFrom(msg.sender, address(this), _amount);
7
8 // Mint FarmToken to msg sender
9 _mint(msg.sender, _amount);
10}
すべて表示
コピー

withdraw(uint256 _amount) 関数は、ユーザーがバーンしたい Farm Token の金額をパラメータして受け取り、同額の My Token をユーザーに転送します。

1function withdraw(uint256 _amount) public {
2 // Burn FarmTokens from msg sender
3 _burn(msg.sender, _amount);
4
5 // Transfer MyTokens from this smart contract to msg sender
6 token.safeTransfer(msg.sender, _amount);
7}
コピー

次に、スマートコントラクトを実装します。 2_deploy_Tokens.jsファイルに戻り、デプロイする新しいコントラクトを追加してください:

1const MyToken = artifacts.require("MyToken")
2const FarmToken = artifacts.require("FarmToken")
3
4module.exports = async function (deployer, network, accounts) {
5 // Deploy MyToken
6 await deployer.deploy(MyToken)
7 const myToken = await MyToken.deployed()
8
9 // Deploy Farm Token
10 await deployer.deploy(FarmToken, myToken.address)
11 const farmToken = await FarmToken.deployed()
12}
すべて表示

Farm Token をデプロイする時は、デプロイ済みの MyToken コントラクトのアドレスがパラメータとして提供される点に注意してください。

次に、truffle compiletruffle migrateを実行して、コントラクトをデプロイします。

次に、スマートコントラクトのテストを実行します。 スマートコントラクトとのやり取りには、truffle consoleを使用するのではなく、テストプロセスを自動化するスクリプトを作成します。 scriptsというフォルダを作成し、getMyTokenBalance.jsファイルを追加してください。 このファイルで、Farm Token スマートコントラクトにおける MyToken 残高を確認できます。

1const MyToken = artifacts.require("MyToken")
2const FarmToken = artifacts.require("FarmToken")
3
4module.exports = async function (callback) {
5 myToken = await MyToken.deployed()
6 farmToken = await FarmToken.deployed()
7 balance = await myToken.balanceOf(farmToken.address)
8 console.log(web3.utils.fromWei(balance.toString()))
9 callback()
10}
すべて表示

このスクリプトを実行するには、以下の cli コマンドを実行します:

truffle exec .\scripts\getMyTokenBalance.js

ここでは、予想通り「0」が表示されます。 Farm Token がデプロイされていないというエラーが表示された場合、Truffle ネットワークに最新バージョンのコントラクトコードが提供されていないことを意味します。 Ganache を閉じて再度 quickstart を実行してから、確実にtruffle migrateを実行してください。

次に、MyToken をこのスマートコントラクト上でステーキングしましょう。 deposit(uint256 _amount) は ERC-20 のsafeTransferFrom関数を呼び出す関数ですから、ユーザーはまず、スマートコントラクトがユーザーの代理として MyToken を送信するのを承認する必要があります。 このため以下のスクリプトでは、まずこのステップを承認し、その上で関数を呼び出します。

1const MyToken = artifacts.require("MyToken")
2const FarmToken = artifacts.require("FarmToken")
3
4module.exports = async function (callback) {
5 const accounts = await new web3.eth.getAccounts()
6 const myToken = await MyToken.deployed()
7 const farmToken = await FarmToken.deployed()
8
9 // Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom.
10 // This is zero by default.
11 const allowanceBefore = await myToken.allowance(
12 accounts[0],
13 farmToken.address
14 )
15 console.log(
16 "Amount of MyToken FarmToken is allowed to transfer on our behalf Before: " +
17 allowanceBefore.toString()
18 )
19
20 // In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf,
21 // we must explicitly allow it.
22 // We allow farmToken to transfer x amount of MyToken on our behalf
23 await myToken.approve(farmToken.address, web3.utils.toWei("100", "ether"))
24
25 // Validate that the farmToken can now move x amount of MyToken on our behalf
26 const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address)
27 console.log(
28 "Amount of MyToken FarmToken is allowed to transfer on our behalf After: " +
29 allowanceAfter.toString()
30 )
31
32 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer
33 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])
34 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)
35 console.log("*** My Token ***")
36 console.log(
37 "Balance MyToken Before accounts[0] " +
38 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())
39 )
40 console.log(
41 "Balance MyToken Before TokenFarm " +
42 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())
43 )
44
45 console.log("*** Farm Token ***")
46 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])
47 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)
48 console.log(
49 "Balance FarmToken Before accounts[0] " +
50 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())
51 )
52 console.log(
53 "Balance FarmToken Before TokenFarm " +
54 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())
55 )
56 // Call Deposit function from FarmToken
57 console.log("Call Deposit Function")
58 await farmToken.deposit(web3.utils.toWei("100", "ether"))
59 console.log("*** My Token ***")
60 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])
61 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)
62 console.log(
63 "Balance MyToken After accounts[0] " +
64 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())
65 )
66 console.log(
67 "Balance MyToken After TokenFarm " +
68 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())
69 )
70
71 console.log("*** Farm Token ***")
72 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])
73 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)
74 console.log(
75 "Balance FarmToken After accounts[0] " +
76 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())
77 )
78 console.log(
79 "Balance FarmToken After TokenFarm " +
80 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())
81 )
82
83 // End function
84 callback()
85}
すべて表示

truffle exec .\scripts\transferMyTokenToFarmToken.jsで、このスクリプトを実行できます。 コンソールには、次のように表示されます。

transferMyTokenToFarmToken.jsの出力(opens in a new tab)

transferMyTokenToFarmToken.js の出力

一番上のアカウントに Farm Token が入金されたことが確認できるので、スマートコントラクトに MyToken を入金することができました。

出金は、以下の方法で行います:

1const MyToken = artifacts.require("MyToken")
2const FarmToken = artifacts.require("FarmToken")
3
4module.exports = async function (callback) {
5 const accounts = await new web3.eth.getAccounts()
6 const myToken = await MyToken.deployed()
7 const farmToken = await FarmToken.deployed()
8
9 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer
10 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])
11 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)
12 console.log("*** My Token ***")
13 console.log(
14 "Balance MyToken Before accounts[0] " +
15 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())
16 )
17 console.log(
18 "Balance MyToken Before TokenFarm " +
19 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())
20 )
21
22 console.log("*** Farm Token ***")
23 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])
24 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)
25 console.log(
26 "Balance FarmToken Before accounts[0] " +
27 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())
28 )
29 console.log(
30 "Balance FarmToken Before TokenFarm " +
31 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())
32 )
33
34 // Call Deposit function from FarmToken
35 console.log("Call Withdraw Function")
36 await farmToken.withdraw(web3.utils.toWei("100", "ether"))
37
38 console.log("*** My Token ***")
39 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])
40 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)
41 console.log(
42 "Balance MyToken After accounts[0] " +
43 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())
44 )
45 console.log(
46 "Balance MyToken After TokenFarm " +
47 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())
48 )
49
50 console.log("*** Farm Token ***")
51 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])
52 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)
53 console.log(
54 "Balance FarmToken After accounts[0] " +
55 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())
56 )
57 console.log(
58 "Balance FarmToken After TokenFarm " +
59 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())
60 )
61
62 // End function
63 callback()
64}
すべて表示

truffle exec .\scripts\drawMyTokenFromTokenFarm.jsで、このスクリプトを実行できます。 以下の出力では、再度 My Token を取得できており、Farm Token がバーンされたことを確認できます。

withdrawMyTokenFromTokenFarm.jsの出力(opens in a new tab)

withdrawMyTokenFromTokenFarm.js の出力

参考文献

コントラクト - OpenZeppelin 関連文書(opens in a new tab)

スマートコントラクトに最適なツール | Truffle Suite(opens in a new tab)

Ganache | Truffle Suite(opens in a new tab)

分散型金融(DeFi)とは 初心者向けガイド(2021 年更新版)(99bitcoins.com)(opens in a new tab)

DeFi - DeFi Llama の分散型金融リーダーボード(opens in a new tab)

最終編集者: @mfujimori1018(opens in a new tab), 2023年8月15日

このチュートリアルは役に立ちましたか?