はじめに
Ethereumを使ったDApp開発をこれまで少しづつしてきた中で、知見だな〜と思ったことを上げてきます。
Remixをローカルで使う
基本的にローカル環境のTruffleで作業することが多いんですが、デバッグ中とかテスト中に思った挙動になってなくてイラッとする時ありますよね。
そういうときは細かいデバッグができる Remix を使うと原因を追いやすくなります。
ブレークポイントで処理止めながら追えるので。
このRemix、オンラインエディタとしても使えますが、ローカルの開発環境としても持てて、私はそちらを使います。開きやすいので。
remix-ide のページに従ってインストールするだけです。
npm install remix-ide -g
remix-ide
あとは開発中によくわかんねってなったときに remix-ide .
するだけでローカル環境をRemixエディタで開いてデバッグできます。っょぃ。
ERC721トークンburn時の注意
ERC721トークン、ありますよね。
NFTトークンとして管理されているので、トークンの削除が可能です。
例えばTwitterみたいなDAppを作るとして、「これまでに発行されたトークン(=ツイート)をすべて見たい、けどburnされてるやつ(=削除ツイート)は見えないようにしたい。」みたいなことをしたいとしますね。
さっくり考えるとtotalSupply
でこれまでのトークン発行数を取得して、それぞれtokenURI
で確認する、みたいなことをしたいのですが、totalSupply
はburnされた数分減算されて帰ってきます。
(ex: 4回ツイートして、3つ目のツイートをburnするとtotalSupply
は3になる。)
なのでどうするかですが、ERC721ではmint時にTransfer
イベントを発行していて、過去発行されたイベントがすべて残っています。
なので下記のように過去Transferイベントを取得し、最後に発行された第一パラメータのaddressが0のtokenIdを取得すれば良いです。
var tokenIdLength = 0;
await contract.getPastEvents('Transfer', {
filter: { myIndexedParam: [20, 23], myOtherIndexedParam: '0x123456789...' }, // Using an array means OR: e.g. 20 or 23
fromBlock: 0,
toBlock: 'latest'
}, (error, events) => {
events.map(event => {
const returnVal = event.returnValues;
if (returnVal.from === '0x0000000000000000000000000000000000000000') {
tokenIdLength = parseInt(returnVal.tokenId, 10)
}
});
});
あとは取得したLength分tokenURIを確認するだけです。
burnされているトークンはownerOfしたときにnullが帰ってくるので、それで判別してください。
for (let i = 0; i <= tokenIdLength; i += 1) {
const owner = await contract.methods.ownerOf(i).call();
// ignore burned token
if (owner === null) continue;
const token = await contract.methods.tokenURI(i).call();
// ~~~~~~~~~~~~~~~~~
}
solidityとテストダブル
solidityの単体テストの時、別コントラクトの呼び出しをテストダブルしたい時、ありますよね?
(そもそもスマートコントラクトでテストダブル使ったUnitテストを是とするのかはおいておいて、、)
例えばCallerがCalleeを呼び出して利用する時、想定どおりにCalleeを呼び出せていることをテストしたいとします。
pragma solidity ^0.5.1;
import './Callee.sol';
contract Caller{
address public callee;
uint256 public num = 0;
constructor (address _callee) public {
callee = _callee;
}
function exec(uint256 arg) public {
num = Callee(callee).multiple5(arg);
}
}
pragma solidity ^0.5.1;
contract Callee{
function multiple5(uint256 _temp) public returns (uint256) {
return _temp * 5;
}
}
その際にCalleeの実装に依存せずテストしたい場合、テストスタブとしてSpyStubCallee
を作ってインジェクションすることで、呼び出しを担保したり、返り値を固定的にコントロールすることができます。
pragma solidity ^0.5.1;
// テスト時にはこっちのアドレスをCallerのコンストラクタに入れる。
contract SpyStubCallee{
uint public arg_temp = 0;
function multiple5(uint256 _temp) public returns (uint256) {
arg_temp = _temp;
return 100;
}
}
ただし、pure・view関数はストレージ書き込みができないので、
どうにかしましょう。
ストレージ書き込みを伴う関数のテストで返り値?を使う
下記みたいに、コントラクト内部で別コントラクトを生成、そのアドレスを取得したいとします。
pragma solidity ^0.5.0;
import './Some.sol'
contract SomeFactory {
event Create(address indexed _from, address _property);
function createSome(string memory _name, string memory _symbol)
public
returns (address)
{
Some some = new Some(
msg.sender,
_name,
_symbol,
);
emit Create(msg.sender, address(some));
return address(some);
}
}
この場合、テストコードで関数の返り値を受け取っても、ストレージ書き込みがある関数の場合、txログが帰ってくるだけで期待通りアドレスを取得できません。
ストレージ書き込みがある関数のoutputを取得したい場合、内部でイベントをemitして、txログから発行されたイベントログから取得できます。
// ここのresultはアドレスではなく関数実行時のtxログ
const result = await SomeFactory.createSome('sample', 'SAMPLE', {
from: deployer
})
expectedPropertyAddress = await result.logs.filter(
e => e.event === 'Create'
)[0].args._property
以上です。
ブロックチェーン大いに盛り上げていきましょう!