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

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)

最終編集者: @nhsz(opens in a new tab), 2024年5月3日

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