PetShopTutorial
Truffle の PetShopTutorial をやる
PetShopTutorial (opens in a new tab)
準備
- Node と Git を入れる
- npm で truffle を入れる
- チュートリアル用のディレクトリを作成してそこに移動する
- pet-shop というパッケージを unbox で持ってくる
ディレクトリ構造
contracts/
は Solidity ソースファイルを入れておくところ。Migration.sol
もここにある。migrations/
にはスマートコントラクトをデプロイするときに Truffle が使うやつがおいてある。test/
にはスマートコントラクト用の JavaScript と Solidity のテストがある。truffle-config.js
は Truffle 用のコンフィグファイル。
スマートコントラクトを書こう
contracts/
にAdoption.sol
を新規作成する。
pragma solidity >=0.4.22 <0.6.2;
contract Adoption {
}
- pragma で Solidity のコンパイラバージョンを指定する。
- チュートリアルでは
pragma solidity ^0.5.0;
になってるけど、VSCode に怒られるので上記のようにいじっている。 pragma solidity ^0.5.0;
は 0.5.0 かそれ以上のバージョンと言う感じ。
- チュートリアルでは
- JavaScript のようにセミコロン;で終わりを指定する。
変数を用意しよう
pragma solidity >=0.4.22 <0.6.2;
contract Adoption {
address[16] public adopters;
}
- Solidity は静的型付言語なので、データタイプを str とか int とか指定しないといけない。
- また、アドレスという Solidity 固有の型がある。
- EthereumAccountAddress を 20byte の値としてストアするもの。
- Ethereum ブロックチェーン上では全てのアカウントとスマートコントラクトがアドレスを持っており、それぞれがそれぞれのアドレスを使用して ETH を送受信できる。
address[16] public adopters;
を{}
の中に入れ込む。adopters
は EthereumAddresses の array として用意した変数。- array はひとつの型を含み、固定長か可変長で持つことができる。
- 今回の場合は、型は
address
で、長さは16
にしてある。 public
となっているが、public 変数はブロックチェーンから自動取得することができる。- ただ配列の場合はキーを渡して値が返ってくることになるので、あとで配列全体を取得する関数を用意する。
最初の関数:ペットを飼う
contract
内のadopters
変数を定義したあとに次の関数を書いていく。
//Adopting pet
function adopt(uint256 petId) public returns (uint256) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}
- Solidity では関数での引数も返り値も型を定義しないといけない。
- この場合も
petId
を定義するときも返すときもuint256
を指定している。
- この場合も
petId
がadopters
の固定長の配列を越えないように、0 から 15 までをrequire()
を使って指定している。petId
がadopters
配列を越えないのが確認できたら、この関数を呼び出したアドレスを配列内に保存する。msg.sender
によってこの関数を呼び出したアドレスを示すことができる。
- 最終的に
petId
を返す。
2 つ目の関数:飼い主の取得
上で書いたとおり、配列の取得は与えられたキーに対応したひとつの値しか返さない。 なので配列全体を取得する関数を用意する。
//Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
contract
内に、上で書いたadopt()
の続きにgetAdopters()
を定義する。adopters
は既に宣言されているので、単に返すだけで OK.- 今回も返り値の型を指定したとおりに定義している。
memory
は変数のためのデータロケーションを与える。view
キーワードは関数がコントラクトの状態に変更を与えないことを示すもの。
スマートコントラクトをコンパイルしてマイグレートしよう
pragma solidity >=0.4.22 <0.6.2;
contract Adoption {
address[16] public adopters;
//Adopting pet
function adopt(uint256 petId) public returns (uint256) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}
//Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}
- Solidity はコンパイル言語なので、EthereumVirtualMachine で実行するためにはコンパイルしてバイトデータにするのが必要となる。
- 人間が読みやすい Solidity から、機械が理解しやすいものへ翻訳するイメージ。
- terminal で
truffle compile
を実行する。
マイグレートしよう
無事コンパイルできたら、それらをブロックチェーンにマイグレートする。
-
1_initial_migration.js
ファイルが既にmigrations/
にあるが、Migration.sol
を利用するtことで、以降変更されていないスマートコントラクトを重複してデプロイすることがないようになっている。 -
2_deploy_contracts.js
ファイルをmigrations/
に新規作成し、以下の内容を書き込む。
var Adoption = artifacts.require("Adoption");
module.exports = function (deployer) {
deployer.deploy(Adoption);
};
-
今回は、
Ganache
を利用してローカルブロックチェーンを立ち上げて、そこにデプロイしていく。 -
Ganache
をダウンロードして起動したら、クイックスタートを選択し、ローカルブロックチェーンを立ち上げる。 -
terminal に戻り、
truffle migrate
を実行する。 -
成功した場合、
Ganache
のブロック数0
から4
に増えており、一番上のイーサリアムアカウントも100ETH
から手数料が引かれているのがわかる。 -
これで、スマートコントラクトの作成からローカルブロックチェーンへのデプロイまでが完了した。次はスマートコントラクトにアクセスして操作してみよう。
スマートコントラクトをテストしよう
test/
にTestAdoption.sol
を新規作成し、以下の内容を追加する。
pragma solidity >=0.4.22 <0.6.2;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
// The address of the adoption contract to be tested
Adoption adoption = Adoption(DeployedAddresses.Adoption());
//The id of the pet that will be used for testing
uint expectedPetId = 8;
//The expected owner of adopted pet is this contract
address expectedAdopter = address(this);
}
Assert.sol
はテストで使用する Truffle のライブラリDeployedAddresses.sol
はテスト用にデプロイするスマートコントラクトのアドレスを取得する Truffle のライブラリAdoption.sol
はテストしたいスマートコントラクト- テスト用に想定される変数を用意している。
adopt()関数をテストしよう
// Testing the adopt() function
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(expectedPetId);
Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned.");
}
adopt()
はpetId
を返すので、返されたreturnedPetId
とexpectedPetId
とがイコールになるかを判定しています。- テストにクリアできなかった場合、与えられた文章が表示されます。
ペットの飼い主が合っているかテストしよう
// Testing retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
address adopter = adoption.adopters(expectedPetId);
Assert.equal(adopter, expectedAdopter,"Owner of the expected pet should be this contract");
}
adopters
配列の中からexpectedPetId
の飼い主を得て、expectedAdopter
とイコールになるかをテストします。
全てのペットの飼い主が合っているかテストしよう
// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
// Store adopters in memory rather than contract's storage
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
}
テストしよう
- terminal で
truffle test
を実行する。 - クリアできたら、3 つのチェックマークが表示される。
スマートコントラクトにアクセスするための UI を作成しよう
- 既に
src/
ディレクトリに用意してあるので、それを見ていく。
web3 をインスタンス化しよう
src/app.js
を開いて、initWeb3
の箇所に書き加えていく。- コメントブロックは消す。
//Modern dapp browsers...
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
//Request account access
await window.ethereum.enable();
} catch (error) {
console.error("User denied account access")
}
}
//Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
//If no injected web3 instance is detected, fall back to Ganache
else {
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);
- まず、新しいブラウザや、
Metamask
などを導入しているブラウザかどうかを判別して、web3 オブジェクトを作成する。アカウントへアクセスできなかった場合、エラーを返す処理も入れておく。 ethereum
が見つからなかった場合や、古い dapp ブラウザを使用している場合(旧バージョンの Metamask など)は使用している web3Provider を呼び出してオブジェクトを作成する。- ブラウザに web3 インスタンスが見つからない場合は、
Ganache
などのローカルのプロバイダーに基づいて作成するが、安全ではないので実運用には適していない。
コントラクトのインスタンス化
$.getJSON('Adoption.json', function (data) {
// Get the necessary contract artifact file and instantiate it with truffle-contract
var AdoptionArtifact = data;
App.contracts.Adoption = TruffleContract(AdoptionArtifact);
//Set the provider for our contract
App.contracts.Adoption.setProvider(App.web3Provider);
//Use our contract to retrieve and mark the adopted pets
return App.markAdopted();
});
- まずスマートコントラクトのアーティファクトを取得する。
- デプロイされたアドレスや ABI など、コントラクトに関する情報がアーティファクト。
TruffleContract()
にそれらの情報を渡す。- 先に作成した web3 オブジェクトをプロバイダーにセットする。