PetShopTutorial
TruffleのPetShopTutorialをやる
準備
- 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オブジェクトをプロバイダーにセットする。