最近ETHを買いあさりながらサーバサイドを担当していますsyroheiです。最近Ethereumの開発もだいぶ楽になってきていて生産性があっという間に向上してきています。この爆速でDappsを構築するシリーズでは何回かに分けてEthereumの開発について紹介していきます。
今回は最初なので ①Tutorial です。
TruffleはConsensysが開発したパブリック・プライベートEthereumのSmartContract開発フレームワークです。
フロントエンド開発からMainNet Deployまでスムーズに開発できるのが特徴です。2016年4月に正式リリースされました。
※ 2016年7月19日にver 2.0 がリリースされました。
https://github.com/ConsenSys/truffle/releases/tag/v2.0.0
Ðappとは
ÐappはDecentralized Appliction の略で非中央集権的実行環境Ethereum Virtual Machine(EVM)上で走るスマートコントラクトで実現できるアプリケーションのこと。
- サーバーレス(サーバーが世界中に分散しており、すべてが無料(マイニングや寄付)で運営されている、SPFがほとんどない)。
- アプリケーションのデプロイと実行にはgas(Ethereum , Ethereum Classic)が必要。
スマートコントラクトとは
ここでいうスマートコントラクトはEVM上でSolidityやsurpent, mutanでコンパイルされたbytecodeのことです。広義的な意味はありません。
手順
- gethを入れる
- ethereum-testrpcを入れる
- truffleを入れる
- truffleでプロジェクトを作成
- truffleでコントラクトを作成
- truffleでコントラクトをテスト
- truffleでフロントエンド開発
- private-netにデプロイ
- main-netにデプロイ
環境
- OS X El Capitan (10.11)
- Node.js v6.0.0 ( v5.0以上推奨)
- geth v1.5.0
- eth v1.2.9
1. gethを入れる
$ brew tap ethereum/ethereum
$ brew install ethereum
2. ethereum-testrpcを入れる
$ npm install -g ethereumjs-testrpc
3. truffleを入れる
$ npm install -g truffle
$ truffle
Truffle v2.0.7 - a development framework for Ethereum
Usage: truffle [command] [options]
Commands:
build => Build development version of app
compile => Compile contracts
console => Run a console with deployed contracts instantiated and available (REPL)
create:contract => Create a basic contract
create:test => Create a basic test
exec => Execute a JS file within truffle environment
init => Initialize new Ethereum project, including example contracts and tests
list => List all available tasks
migrate => Run migrations
networks => Show addresses for deployed contracts on each network
serve => Serve app on localhost and rebuild changes as needed
test => Run tests
version => Show version number and exit
watch => Watch filesystem for changes and rebuild the project automatically
4. truffleでプロジェクトを作成
$ mkdir myproject
$ cd myproject
$ truffle init
成功すると下記のような構成になる
├── app
│ ├── images
│ ├── index.html
│ ├── javascripts
│ │ └── app.js
│ └── stylesheets
│ └── app.css
├── build
│ ├── app.css
│ ├── app.js
│ ├── contracts
│ │ ├── ConvertLib.sol.js
│ │ ├── MetaCoin.sol.js
│ │ └── Migrations.sol.js
│ ├── images
│ └── index.html
├── contracts
│ ├── ConvertLib.sol
│ ├── MetaCoin.sol
│ └── Migrations.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
│ └── metacoin.js
└── truffle.js
- app/ - フロントエンドアプリケーションの場所
- contracts/ - コントラクトの配置場所
- migrations/ - デプロイ動作などを定義
- test/ - テストファイル配置場所
- truffle.js - コンフィグ
5. truffleでコントラクトを作成
truffleは最初にConvertLib.solとMetaCoin.solとMigration.solがある。
スマートコントラクトは現状Solidityで書かれることが多く、デファクトスタンダードになりつつあります。
solidityの詳しい仕様についてはまたの機会に解説するつもりなのでここでは割愛します。
最初のチュートリアルではMetaCoin
という新しいトークンを作ってみます。
トークンのデザインは自由に決めていいです。
たとえば
- このトークンはオーナーのみ発行できる
- このトークンのトークンホールダーは互いに送受信を行うことができる
- オーナーはこのコントラクトを消滅させることができる
- このトークンを使って投票を行うことができる
- トークンホールダーは自身のトークンを燃やすことができる
といったことが簡単にコントラクトで作ることができます。
MetaCoinの仕様は
- MetaCoinをデプロイしたアドレスはトークンを10000発行する
- MetaCoinのトークンホールダーは互いに送信することができる
それではコードを見ていきましょう。
MetaCoinには二つのコントラクトがあります。ConvertLibとMetaCoinです。
Migrationsはここでは使いません。
library ConvertLib{
function convert(uint amount,uint conversionRate) returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
```
```MetaCoin.sol
import "ConvertLib.sol";
// This is just a simple example of a coin-like contract.
// It is not standards compatible and cannot be expected to talk to other
// coin/token contracts. If you want to create a standards-compliant
// token, see: https://github.com/ConsenSys/Tokens. Cheers!
contract MetaCoin {
mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
function MetaCoin() {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Transfer(msg.sender, receiver, amount);
return true;
}
function getBalanceInEth(address addr) returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) returns(uint) {
return balances[addr];
}
}
```
順に解説していきます。
```
mapping (address => uint) balances;
```
EVM上にstorage領域を定義します。key => value になっていて address : uint 型でarray balancesを作ります。
```
event Transfer(address indexed _from, address indexed _to, uint256 _value);
```
Transfer eventlogを定義します。
```
function MetaCoin() {
balances[tx.origin] = 10000;
}
```
コンストラクタを定義します。
```
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Transfer(msg.sender, receiver, amount);
return true;
}
```
sendCoin methodを定義します。送信が成功した場合Transfer Eventが呼ばれます。
```
function getBalanceInEth(address addr) returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
```
addressの持っているMetaCoin balanceを2倍にして返します。この時ConvertLib contractが呼ばれます。
```
function getBalance(address addr) returns(uint) {
return balances[addr];
}
```
addressの持っているMetaCoin balanceを返します。
#### 6. truffleでコントラクトをテスト
コントラクトをテストするために testrpcを立ち上げます。
```
$ testrpc -b 1
```
```
EthereumJS TestRPC v2.0.9
Available Accounts
==================
(0) 0x44b222577305fd8c392806cccc99ad246be89c62
(1) 0x0b4a08c18bd40a61d9d42a707030c40503e3d6f4
(2) 0x77f33610c00957790ec592b14cc756a92803b8e2
(3) 0x5be53e9eb6742489267ee8e2d81916e6ce7d7e33
(4) 0xaf97130ff3f702588c16a2e1a916fd9b921c21de
(5) 0x3193ff53b128964023da4ec4e3747479bf72732f
(6) 0xf2ced2841032724a86205b31d40ee371c1cb3962
(7) 0x71444b68ba8ec78a7ba930a9ef23dad70c9cce8a
(8) 0x2328077ae8c2f62834b723ef2527f51ff7aede31
(9) 0x4b29c3d47710480b234139fd53904a4fe55861c3
Private Keys
==================
(0) 15da35cd6999bf253bd42bb66afa8077e7ef3b1b90d083ccb0f780f1e2a46231
(1) d8434ee1f473b246a24e12aa7dfe9976dc8420a7a20e85f0c00c20a91003eae0
(2) edf9d021ee1bd7a626f6346960fac3884953325e847fe983f0d8e91f9835ccd1
(3) 55551cee6361cfdfb32694d1f5d0f1fd31e6ce2375d883aaf7b594e14e9ee7e1
(4) a924735357b12ce956777bc939415a9e3da8f63357ab6d4faab3e499092314bf
(5) c17375424dae58eab4293446bab375dd1fc3f9a65338fad18a57bf55d42123f7
(6) ebab687f14f5e3d0a3fcaca23ec4563700df37cc4158eb25d1af60e4db4cbc30
(7) 6746a454c66aa6782f700c5589de25c981443e46e27ab37520a37540bb93aadc
(8) 93443e3639c900b3745f7ed128a229177053171d49e2dc0b893af6da14de72e3
(9) 27ec7e1267982c0d034a0dd5b1823970364893a6991a3b60908808b465d01552
HD Wallet
==================
Mnemonic: diamond infant two shoot curious address shift anxiety sight move swap emerge
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
```
testrpc はMnemonic,hdwallet (BIP 39, BIP 44) に対応しています。
コントラクトをテストします。
```metacoin.js
contract('MetaCoin', function(accounts) {
it("should put 10000 MetaCoin in the first account", function() {
var meta = MetaCoin.deployed();
return meta.getBalance.call(accounts[0]).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
});
});
it("should call a function that depends on a linked library ", function(){
var meta = MetaCoin.deployed();
var metaCoinBalance;
var metaCoinEthBalance;
return meta.getBalance.call(accounts[0]).then(function(outCoinBalance){
metaCoinBalance = outCoinBalance.toNumber();
return meta.getBalanceInEth.call(accounts[0]);
}).then(function(outCoinBalanceEth){
metaCoinEthBalance = outCoinBalanceEth.toNumber();
}).then(function(){
assert.equal(metaCoinEthBalance,2*metaCoinBalance,"Library function returned unexpeced function, linkage may be broken");
});
});
it("should send coin correctly", function() {
var meta = MetaCoin.deployed();
// Get initial balances of first and second account.
var account_one = accounts[0];
var account_two = accounts[1];
var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;
var amount = 10;
return meta.getBalance.call(account_one).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();
assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
});
});
});
```
```
$ truffle test
```
```
Compiling ConvertLib.sol...
Compiling MetaCoin.sol...
Contract: MetaCoin
✓ should put 10000 MetaCoin in the first account (57ms)
✓ should call a function that depends on a linked library (129ms)
✓ should send coin correctly (250ms)
3 passing (2s)
```
自動的にsolcが呼ばれてコンパイルされ、testrpcにデプロイされてテストされます。
#### 7. truffleでフロントエンド開発
truffleのいいところは標準でweb3がbuildと同時にimportされるため、いちいちethereum.jsを読み込む必要がなく手間が省けてよいです。またコントラクトのinstanceが作られるためそれ用のコードも省くことができます。なお標準でReact.jsとJSXに対応しています。
```
var meta = MetaCoin.deployed();
```
またgulpのようにtrackingも可能です。
```
$ truffle serve
```
gulpのように自動的に開かれないので手動でアクセスしないといけません。
http://localhost:8080
![image](https://qiita-image-store.s3.amazonaws.com/0/100763/8ca2385c-90f5-8218-f288-48922734086b.png)
最後に
```
$ truffle build
```
すればbuildされます。
#### 8. private-netにデプロイ
Ethereumのprivate-netは`networkid`を変えるだけで簡単にprivate-chainを作ることができます。しかしこのままだと`difficulty`と`gaslimit`がpublic-chainと同じ設定になっているのでprivate-chainはもっと高速にマイニングしたい場合は現状`genesis.json`を作るのがデファクトスタンダードになっています。
```genesis.json
{
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x0001",
"alloc": {
"0xdc6de66342a74c3bb5483dab98958cfd1d5d2dde": {
"balance": "100000000000000000000000000000"
},
"0x908cfe80cfeadf9831bce22f3bc8ba061c94c3d9": {
"balance": "100000000000000000000000000000"
},
"0x71a6688b1b59dedbdd0e6fa90d1af93781aa6f0b": {
"balance": "100000000000000000000000000000"
}
},
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "Genesis Block",
"gasLimit": "0xffffffffffff"
}
```
最初にEthereum private-chain用のディレクトリ./dataに`genesis.json`を読ませます。
```
geth --datadir ./private init ./genesis.json
```
Ethereum accountを作ります。
```
$ geth --datadir ./private account new
```
geth をプライベートネットで立ち上げます。
```
geth --datadir ./private --networkid 123456 --nodiscover --maxpeers 0 --rpc --rpcapi eth,net,web3,personal,admin,miner --minerthreads 1 --rpccorsdomain "*" --mine
```
accountをunlockします。
```
$ geth attach http://localhost:8545
> personal.unlockAccount(eth.accounts[0],"password", 2000000) //time 2000000sec
true
```
deployします。
```
$ truffle migrate
```
```
Running migration: 1_initial_migration.js
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
Replacing ConvertLib...
ConvertLib: 0xeb4cf9aed40387c19952b369579541314ce8dcd3
Linking ConvertLib to MetaCoin
Replacing MetaCoin...
MetaCoin: 0x1f156e6134a9eb6ffb8804363e9326f6d429a278
Saving successful migration to network...
Saving artifacts...
```
```
$ truffle serve
```
#### 9. main-netにデプロイ
ethereum-mainnetではtheDAOのHardfork後二つのchainが**マイナーの合意**によって存在しています。俗に言うEthereum vs Ethereum Classicです。どちらのchainでプロジェクトを進めるかはプロジェクトの思想とセキュリティをもとに判断すると良いでしょう。ただし前者ではETH,後者ではETCという暗号通貨を**deploy時のaccounts[0]にデポジットする**必要があり。持っていなければ各取引所で交換することができます。
デプロイプロセスはprivate-chainとほとんど同じです。
genesis.jsonを作る必要がないので1ステップ減らせます。
```
$ geth --datadir ./main account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {d366b902518d90c4e896c582209f313cfce003ec}
```
すでに取引所にETH,ETCがある場合は上記で作成したaddress宛に各取引所から送金すると準備が完了です。 **※絶対に間違えないこと**
geth をmain-netで起動(eth)
```
$ geth --datadir ./main --rpc --rpcaddr "0.0.0.0" --rpcapi eth,net,web3,personal,admin,miner --rpccorsdomain "*" --support-dao-fork
```
geth をmain-netで起動(etc)
```
$ geth --datadir ./main --rpc --rpcaddr "0.0.0.0" --rpcapi eth,net,web3,personal,admin,miner --rpccorsdomain "*" --oppose-dao-fork
```
以下privateと同様。
コードはこちら
https://github.com/syrohei/truffle-tutorial
## まとめ
初回なので簡単なコントラクトを実装してみました。少しはしょった感ですがとりあえずチュートリアルということで今回はここまでにします。次回からはいろんなコントラクトの紹介と実装の解説を予定しています。
ここ最近Ethereum系のプロジェクトが再び活気を帯びてきています。9月には上海でdevcon2が開催されますのもあり、アジアでEthereumの存在感はどんどん大きくなってきています。truffleは自分含めそのディベロッパーたちの生産性向上のために非常に重要な役割を果たしているように思います。4月のリリースからあっという間にver2.0がリリースされて、スピード感が半端ないです。truffleと使ってみて体感的にも今までと比べて100倍くらい生産性が向上しました。最近React.js始めたのですがtruffleでもシンプルに書けるのがとても気に入っています。
Ethereumのエコシステムが成功するかどうかは自分達ディベロッパー達に掛かっていると感じていて, theDAOの問題もとてもポジティブに生かされていますし、ガンガン生産性を高めることが重要だと感じています。
Ethereumの基本的なアーキテクチャと思想は革新的でとても面白いと感じています。特に暗号通貨デザインがとても良くできていてbitcoinと違って価値の流動が起きやすく柔軟で扱いやすいようになっているように感じます。イノベーションのサイクルが起こりやすいのではないでしょうか。ただ技術的にまだまだ未熟なのは間違いないのでOSSらしい成長を期待しています。
余談ですが、
筆者はHardfok時にETCを売ってしまい、ETCでテストするために**最高値で**ETCを買うはめになりました。暗号通貨の購入はテクニカル分析やファンダメンタルズを参考にすることをお勧めします。