DeFiアプリを作成してデプロイする
このチュートリアルでは、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デスクトップアプリケーションを使用します。
(opens in a new tab)GanacheのUI デスクトップアプリケーション
プロジェクトを作成するには、次のコマンドを実行します
mkdir your-project-namecd your-project-nametruffle 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;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";45contract 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;45constructor (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 compilers2compilers: {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 evmVersion7 // optimizer: {8 // enabled: false,9 // runs: 20010 // },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")23module.exports = async function (deployer, network, accounts) {4 // Deploy MyToken5 await deployer.deploy(MyToken)6 const myToken = await MyToken.deployed()7}
Ganacheを開いて「QuickStart」のオプションを選択すると、ローカルのイーサリアムブロックチェーンが開始されます。 コントラクトをデプロイするには、以下を実行します:
truffle migrate
コントラクトのデプロイに使用するアドレスは、Ganacheで表示されるアドレスリストの一番上のアドレスです。 これを確認するには、Ganacheのデスクトップアプリケーションを開きます。スマートコントラクトをデプロイするためにコストが発生したため、リストの一番上のアカウントではetherの残高が減少していることを確認してください。
(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万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;23import "@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";78contract 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 SafeMath11 using SafeERC20 for IERC20;1213 IERC20 public token;1415 constructor(address _token)16 public17 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 zero3 require(_amount > 0, "amount cannot be 0");45 // Transfer MyToken to smart contract6 token.safeTransferFrom(msg.sender, address(this), _amount);78 // Mint FarmToken to msg sender9 _mint(msg.sender, _amount);10}すべて表示コピー
withdraw(uint256 _amount)
関数は、ユーザーがバーンしたいFarm Tokenの金額をパラメータして受け取り、同額のMy Tokenをユーザーに転送します。
1function withdraw(uint256 _amount) public {2 // Burn FarmTokens from msg sender3 _burn(msg.sender, _amount);45 // Transfer MyTokens from this smart contract to msg sender6 token.safeTransfer(msg.sender, _amount);7}コピー
次に、スマートコントラクトを実装します。 2_deploy_Tokens.js
ファイルに戻り、デプロイする新しいコントラクトを追加してください:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (deployer, network, accounts) {5 // Deploy MyToken6 await deployer.deploy(MyToken)7 const myToken = await MyToken.deployed()89 // Deploy Farm Token10 await deployer.deploy(FarmToken, myToken.address)11 const farmToken = await FarmToken.deployed()12}すべて表示
Farm Tokenをデプロイする時は、デプロイ済みのMyTokenコントラクトのアドレスがパラメータとして提供される点に注意してください。
次に、truffle compile
と truffle migrate
を実行して、コントラクトをデプロイします。
次に、スマートコントラクトのテストを実行します。 スマートコントラクトとのやり取りには、truffle console
を使用するのではなく、テストプロセスを自動化するスクリプトを作成します。 scripts
というフォルダを作成し、getMyTokenBalance.js
ファイルを追加してください。 このファイルで、Farm TokenスマートコントラクトにおけるMyToken残高を確認できます。
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.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")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // 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.address14 )15 console.log(16 "Amount of MyToken FarmToken is allowed to transfer on our behalf Before: " +17 allowanceBefore.toString()18 )1920 // 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 behalf23 await myToken.approve(farmToken.address, web3.utils.toWei("100", "ether"))2425 // Validate that the farmToken can now move x amount of MyToken on our behalf26 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 )3132 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer33 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 )4445 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 FarmToken57 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 )7071 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 )8283 // End function84 callback()85}すべて表示
truffle exec .\scripts\transferMyTokenToFarmToken.js
で、このスクリプトを実行できます。 コンソールには、次のように表示されます。
transferMyTokenToFarmToken.js の出力
一番上のアカウントにFarm Tokenが入金されたことが確認できるので、スマートコントラクトに MyTokenを入金することができました。
出金は、以下の方法で行います:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer10 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 )2122 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 )3334 // Call Deposit function from FarmToken35 console.log("Call Withdraw Function")36 await farmToken.withdraw(web3.utils.toWei("100", "ether"))3738 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 )4950 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 )6162 // End function63 callback()64}すべて表示
truffle exec .\scripts\drawMyTokenFromTokenFarm.js
で、このスクリプトを実行できます。 以下の出力では、再度My Tokenを取得できており、Farm Tokenがバーンされたことを確認できます。
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), 2023年8月15日