Truffle(トリュフ)は、スマートコントラクトの開発に必要となる、コンパイラ、リンク、デプロイ、バイナリ管理の機能を持つ統合開発管理フレームワークです。
#Truffleフレームワークの特徴
- スマートコントラクトのコンパイル、リンク、デプロイ、バイナリ管理
- 迅速な開発を目的とするスマートコントラクトのテスト
- スクリプトで記述できる拡張可能なデプロイとマイグレーションのフレームワーク
- パブリックネットとプライベートネットにデプロイできるネットワーク管理
- ERC190規格を使用したEthPMとNPMによるパッケージ管理
- インテグレーションをサポートする設定変更可能なビルドパイプライン
- Truffle環境内でスクリプトを実行する外部スクリプトランナー
このTruffleフレームワークには、独自コインを作成してアカウント間でやり取りするコントラクトのサンプルがあります。
今回はその中の一つ、「MetaCoin」の実装をやっていきます。
#Truffleの環境構築
macOS環境での環境構築をやっていきます。まずは、npmをインストールしていない方はインストールします。node.jsにアクセスして、[macOS Installer]をクリックし、pkgファイルをダウンロードし、インストールします。
また、Homebrewでもインストール可能です。npmのインストールが完了したら、npmでTruffleをインストールします。
# npm_install
$ brew install npm
# truffle_install
$ npm install -g truffle
#MetaCoinのサンプルコードをダウンロード
下記のコマンドを入力し、ディレクトリを作成してからその中でtruffle unboxコマンドを実行します。
# Create a new directory for your Truffle project
$ mkdir metacoin
$ cd metacoin
# Download ("unbox") the MetaCoin box
$ truffle unbox metacoin
✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box
Unbox successful. Sweet!
Commands:
Compile contracts: truffle compile
Migrate contracts: truffle migrate
Test contracts: truffle test
truffle unboxコマンドはTruffleフレームワークが提供するサンプルをローカル環境にに展開するコマンドです。MetaCoin以外にもさまざまなサンプルがTruffle Boxesとして用意されているのでそちらも試してみてください。
#コントラクトの解説
ここではダウンロードしたcontractsディレクトリにダウンロードされたMetaCoin.solを確認します。
とにかく実装だけしたい方はここは飛ばしても大丈夫です。
pragma solidity >=0.4.25 <0.6.0;
import "./ConvertLib.sol";
contract MetaCoin {
mapping (address => uint) balances; //①
event Transfer(address indexed _from, address indexed _to, uint256 _value); //②
//③
constructor() public {
balances[tx.origin] = 10000;
}
//④
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount);
return true;
}
//⑤
function getBalanceInEth(address addr) public view returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
//⑥
function getBalance(address addr) public view returns(uint) {
return balances[addr];
}
}
①では、mapping型のbalancesを宣言します。keyがaddress型(ユーザーアカウントのアドレス)でvalueがuint型です。MetaCoinをいくら保持しているのかをここで管理します。また、値変化の過程はブロックチェーンに保存されますが値そのものはブロックチェーン外のState Treeに保存されます。
②では、event型のTransferを宣言します。Transferはアドレス(_from)からアドレス(_to)にいくら(_value)が送金されたか記録します。このログでウォレットなどがイベントを追跡可能になります。
③にあるコントラクトと同じ名前の関数は、Solidity0.5.0時点でのコンストラクタ関数の定義です。コントラクトが展開されて初期化される際に実行される関数です。①で定義したBalancesに、key = tx.originで10,000を入れます。tx.originはコントラクトを呼び出したアドレスです。このコントラクトを最初に作成したアカウントは無条件に10,000MetaCoinを入手することになります。
④のsendCoinは、送り先アドレスreceiverと送る数量amountを受け取ります。実行するアカウントmsg.senderが保持するMetaCoinをbalancesで確認します。amountよりも少ない場合はfalseを返し、そうでなければ次の処理に続きます。msg.senderのbalancesからamountを引き、receiverのbalancesにamountを加えたのち、Transferイベントを実行します。実際にsenderからreceiverへamountのMetaCoin増減が発生したことをログに記録します。処理が成功したらtrueを返します。
⑥ではアドレス型のaddrを取得して、balancesに保持するMetaCoinの数量をuintで返しています。その1つ前の⑤でもアドレス型のaddrを取得して、getBalanceInEthでもConvertLibのconvertメソッドを投げてgetBalance(addr)を呼び出しています。
つぎは、呼び出しているConvertLibの内容を確認します。
pragma solidity >=0.4.25 <0.6.0;
library ConvertLib{
//①
function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
libraryとして宣言することで、処理自体はライブラリに存在していても、呼び出し元の状態変数を参照可能になります。
convert関数は、uint型のamountとuint型のconversionRateを取り、それらを掛け合わせてuint型のconvertedAmountとして返す関数です。
getBalanceInEthを呼び出す際にgetBalance(addr)と2を渡しているので単純にMetaCoinの数量を2倍したものが返されます。
#テストの実行
ターミナルでテストを実行します。SolidityテストとJavaScriptテストをそれぞれ実行します。
# On a terminal, run the Solidity test
$ truffle test ./test/TestMetacoin.sol
TestMetacoin
✓ testInitialBalanceUsingDeployedContract (91ms)
✓ testInitialBalanceWithNewMetaCoin (83ms)
2 passing (8s)
# Run the JavaScript test
$ truffle test ./test/metacoin.js
Contract: MetaCoin
✓ should put 10000 MetaCoin in the first account (50ms)
✓ should call a function that depends on a linked library (80ms)
✓ should send coin correctly (227ms)
3 passing (392ms)
これらのテストはコントラクトに対して実行されて、テストの実行内容が表示されています。
#コンパイルとマイグレーションの実行
スマートコントラクトをコンパイルします。
# Compile the smart contracts
$ truffle compile
Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts
Truffleにはテストをのみ使用できる組み込みのパーソナルブロックチェーンがあります。
そのブロックチェーンを作成して、truffle developを使ってインタラクトできます。
# Run Truffle Develop
$ truffle develop
Truffle Develop started at http://127.0.0.1:9545/
Accounts:
(0) 0x67c5482d26ebea433fefa3ccea1bc3ffd1c44070
(1) 0xb3158425f5141ec5828b960f8b9030931ae489bc
(2) 0x1db2d7bd10f651bc35b4c56f15081e608c559ad6
(3) 0x35049260e26b0caf5fcb6c741014c7c7c37e09a4
(4) 0x124837f7f8f7a527fc55a230f25d45895b337bb0
(5) 0xb59f6fdcc374a7fd926a7e0242836ee9a716e0d6
(6) 0x053d25e65b44066059e2da4cf5f111ea4961a0b4
(7) 0x4675a1f004481981a4ab5dbde9a7ee4b74f99f64
(8) 0xd1cb500094b93704bc6894322b9ffb350b554be6
(9) 0x941d677400833f47de2b9adaa2dc2c47448a1696
Private Keys:
(0) 17d05a9f36d794c10e5aa4e842a21e741675333c20b65aebaf10eac103363d8b
(1) 4628f8780a246c00d900197b8703d3835f73954578c8b521267383e2b31094e7
(2) 74cad5e628aa2d22ff8f7978e53159fb0bf8ed5b025061a1c9ddb84779a02a04
(3) 6acb184c9895e3220cefc76834b44e011d2cb99855223cd7a3bf90c2e93b7c53
(4) 526ba4f93a9aa32394ef21eee8397e8dcce9aceb2c4300869dd0249354243086
(5) 530226519f68cf4b4f31d0992e5f63dde1765a182ae8e6a4ce317c53da2a9128
(6) f8b371b2a82a6c1061f7a6230428a4d54c99d309970a68ab70344438475bbd4d
(7) 1175c5a3a0cd0d902b404ca1bf6e68645b9fc85dae22c98521e260a53b7aab14
(8) 1e3a5fee24a55b7ff834d35fb6fa211a8d259234d4fda64d6246c76274f3bd1c
(9) 452bf0d346b18c38c6ef868a9bd9dc193865c7a6f346d9c1fd3c681f486736ec
Mnemonic: mad train like neutral attend utility online cherry eight mammal eager bonus
⚠️ Important ⚠️ : This mnemonic was created for you by Truffle. It is not secure.
Ensure you do not use it on production blockchains, or else you risk losing funds.
ブロックチェーンとやり取りするときに使用できる10個のアカウント(およびそれらの秘密鍵)を示しています。
次にマイグレーションを行います。
truffle(develop)> migrate
⚠️ Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.
Starting migrations...
======================
> Network name: 'develop'
> Network id: 4447
> Block gas limit: 6721975
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x54a1e77cd3a06a8cb2f21f2c1f2099a4733ea467608c5335f57c201f7b409e52
> Blocks: 0 Seconds: 0
> contract address: 0xd235811f8b6eDc0400e5087A6fdb9E5409E68316
> account: 0x67C5482d26EbeA433fEFa3cCeA1Bc3ffd1c44070
> balance: 99.99430184
> gas used: 284908
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00569816 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.00569816 ETH
2_deploy_contracts.js
=====================
Deploying 'ConvertLib'
----------------------
> transaction hash: 0x505b7a93c2d2d0b7ffde4ee9cf3e2bf2d6d13a66b85d76c79391f5ee3eb78a7a
> Blocks: 0 Seconds: 0
> contract address: 0xAdDF111686006eA4E47D4BD77ac2757368e390f3
> account: 0x67C5482d26EbeA433fEFa3cCeA1Bc3ffd1c44070
> balance: 99.99206602
> gas used: 111791
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00223582 ETH
Linking
-------
* Contract: MetaCoin <--> Library: ConvertLib (at address: 0xAdDF111686006eA4E47D4BD77ac2757368e390f3)
Deploying 'MetaCoin'
--------------------
> transaction hash: 0x92cafb19d927c744161933c8163900d60cbc54e4bce30a27c094c5e6e07d6ca2
> Blocks: 0 Seconds: 0
> contract address: 0xA7f2Ec94195752A57D9A2550aaC54F7c96b80d6a
> account: 0x67C5482d26EbeA433fEFa3cCeA1Bc3ffd1c44070
> balance: 99.98489586
> gas used: 358508
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00717016 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.00940598 ETH
Summary
=======
> Total deployments: 3
> Final cost: 0.01510414 ETH
これには、展開した契約のトランザクションIDとアドレスが表示されます。また、コストの概要とリアルタイムのステータス更新も含まれています。
#デプロイの確認
以上で、Metacoinがデプロイされたので、正常にデプロイされているか確認していきます。
まず、getBalanceを呼び出して、アカウント0が10,000MetaCoinを保持していることを確認します。下記の通り、変数に保存することでMetaCoinのコントラクトを参照できます。
truffle(develop)> let instance = await MetaCoin.deployed()
undefined
truffle(develop)> let accounts = await web3.eth.getAccounts()
undefined
getBalanceを呼びだします。
truffle(develop)> let balance = await instance.getBalance(accounts[0])
undefined
truffle(develop)> balance.toNumber()
10000
次のコマンドを入力するとMetaCoinをいくら保有しているかをイーサリアムに変換して確認できます。
truffle(develop)> let ether = await instance.getBalanceInEth(accounts[0])
undefined
truffle(develop)> ether.toNumber()
20000
アカウント0からアカウント1にMetaCoinを送ります。
truffle(develop)> instance.sendCoin(accounts[1], 500)
{ tx:
'0x16064590f4c193f40a60f67e04abb3d1d3786e86a12d7d4f6738eb7eecf617ad',
receipt:
{ transactionHash:
'0x16064590f4c193f40a60f67e04abb3d1d3786e86a12d7d4f6738eb7eecf617ad',
transactionIndex: 0,
blockHash:
'0xb23d745a7c11da21c99ca5fe3e8ffdddd765fb82401009948064f5f1b7ed823f',
blockNumber: 4,
from: '0x67c5482d26ebea433fefa3ccea1bc3ffd1c44070',
to: '0xa7f2ec94195752a57d9a2550aac54f7c96b80d6a',
gasUsed: 51083,
cumulativeGasUsed: 51083,
contractAddress: null,
logs: [ [Object] ],
status: true,
logsBloom:
'0x00000000000000000000080000000000000020000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000',
v: '0x1b',
r:
'0x392b784ab10874d6b67d81fdd85ee8e0279764177a50fe9596b6c73e5553cde5',
s:
'0x5d76a0393eb36c5888f56c35554580bffdcbaddb55f5d5c054980fe770914970',
rawLogs: [ [Object] ] },
logs:
[ { logIndex: 0,
transactionIndex: 0,
transactionHash:
'0x16064590f4c193f40a60f67e04abb3d1d3786e86a12d7d4f6738eb7eecf617ad',
blockHash:
'0xb23d745a7c11da21c99ca5fe3e8ffdddd765fb82401009948064f5f1b7ed823f',
blockNumber: 4,
address: '0xA7f2Ec94195752A57D9A2550aaC54F7c96b80d6a',
type: 'mined',
id: 'log_7b0b4816',
event: 'Transfer',
args: [Result] } ] }
送金後に、アカウント0とアカウント1の残高を確認します。
truffle(develop)> let balance1 = await instance.getBalance(accounts[1])
undefined
truffle(develop)> balance1.toNumber()
500
truffle(develop)> let balance0 = await instance.getBalance(accounts[0])
undefined
truffle(develop)> balance0.toNumber()
9500
#まとめ
今回はTruffleフレームワークの公式サンプルMetaCoinのコントラクト内容を簡単に把握し、デプロイ確認まで行いました。