Ethereum/DApp開発でこれまでに得た知見

# はじめに

Ethereumを使ったDApp開発をこれまで少しづつしてきた中で、知見だな〜と思ったことを上げてきます。

---

# Remixをローカルで使う

基本的にローカル環境のTruffleで作業することが多いんですが、デバッグ中とかテスト中に思った挙動になってなくてイラッとする時ありますよね。
そういうときは細かいデバッグができる [Remix](https://remix.ethereum.org/) を使うと原因を追いやすくなります。
ブレークポイントで処理止めながら追えるので。
このRemix、オンラインエディタとしても使えますが、ローカルの開発環境としても持てて、私はそちらを使います。開きやすいので。

[remix-ide](https://github.com/ethereum/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
```


---


以上です。
ブロックチェーン大いに盛り上げていきましょう！