Kyber Networkで ERC20 to ETH, ETH to ERC20 を、Solidityで実装、実行してみる【Kyber入門その２】

#この記事の目的
送信したトークンが受け取り側に届く際に自動でスワップされるKyberNetworkの仕組みを、実際にサンプルコードを読みながら実装し、
*Any Token, Anywhere*
の世界を体験する。

2018/09/01に、[@ookimaki_JP]
(https://twitter.com/ookimaki_JP)
さんのやっているdApps開発者ギルド主催の[Kyber NetworkでdAppsを作ってみよう勉強会]
(https://dapps-developers-guild.connpass.com/event/99560/)
に参加してきました。この記事は後半部分です。

まず、その勉強会の前半の内容である、[Kyberが動いているのを10分で体験してみる【Kyber入門その１】](https://qiita.com/hamakihito/items/ed7cafd6d352a723291e)を一通りやってみるのがおすすめです。コードで実装してみたいという方は次にお進み下さい。

##環境
###使うツール
・git
・rpm
・MetaMask
が入っている環境であれば問題ありません。


###参考サイト
KyberDeveloper サイトの [DApps Integration Guide]
(https://developer.kyber.network/docs/DappsGuide)
を参考に、Solidityでコードを書いていきます。適宜参照ください。

###IDE
今回は、[Remixd](https://remix.ethereum.org)を使って書きました。
コードを書くだけでなく、色々コントラクトの挙動やパラメータを操作できたりするので非常に便利です。
TruffleとGanacheを使ってやってもいいんですが、今回はKyberのソース挙動をコントロールするためにSolidityのバージョン指定をする必要があるので、Remixを使いましょう。

##準備(※Mac環境を使っています。Winは適宜読み替え)

###ローカルのフォルダ準備
適当に開発用ディレクトリを作って、gitでソースをダウンロードします。

```
$ mkdir kyber
$ cd kyber
$ git clone https://github.com/KyberNetwork/smart-contracts.git
$ ls
smart-contracts
```
ソースダウンロードが終わったら、開発用IDE remixdをローカルにインストール。

```
$ npm install -g remixd
```
インストールが終わったら、今回は、上記git cloneでつくられた
`smart-contracts/contracts`フォルダをShared folderとして指定します。

```
$ remixd -s <absolute-path>
//例
$ remixd -s /Users/hamakihito/kyber/smart-contracts/contracts
```

ついでに、あとで使うsolidityファイルをここ`smart-contracts/contracts`に作っておきます。

```
$ touch KyberExample.sol
```

これで準備は終わりです。


##IDEを使ってソース編集していく

ブラウザで、[Remixd](https://remix.ethereum.org)にアクセスしましょう。
![Screen Shot 2018-09-02 at 11.14.50.png](https://qiita-image-store.s3.amazonaws.com/0/91514/d6a47a68-bc59-4761-1dd9-2b7e07711bdc.png)
左上にある、🔗マークをクリックすると、ローカルに接続するか聞かれますので
![Screen Shot 2018-09-02 at 11.15.07.png](https://qiita-image-store.s3.amazonaws.com/0/91514/a095bfdc-fa30-69df-1158-16dddd3e2628.png)
Connectでつなぐと、先程Shared Folderに指定したフォルダの内容が表示されます。
![Screen Shot 2018-09-02 at 11.43.08.png](https://qiita-image-store.s3.amazonaws.com/0/91514/4999a932-c86c-0a48-72bc-f56cc6fa2c64.png)
先程作ったKyberExample.solもありますね。開きます。
白紙のはずですので、そこに公式の[DApps Integration Guide](https://developer.kyber.network/docs/DappsGuide)から取ってきた**Contract Example**のソースをコピペします。(2018/09/02時点のソース)

```solidity:KyberExample.sol
pragma solidity 0.4.18;

import "./ERC20Interface.sol";
import "./KyberNetworkProxy.sol";


contract KyberExample {
    //It is possible to take minRate from kyber contract, but best to get it as an input from the user.
    
    ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
    
    event SwapTokenChange(uint balanceBefore, uint balanceAfter, uint change);
    event SwapEtherChange(uint startBalance, uint currentBalance, uint change);
    
    //must have default payable since this contract expected to receive change  
    function() public payable {}
    
    
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param srcToken source token contract address
    //@param srcQty in token wei
    //@param destToken destination token contract address
    //@param destAddress address to send swapped tokens to
    function swapTokenToToken (KyberNetworkProxyInterface _kyberNetworkProxy, ERC20 srcToken, uint srcQty, ERC20 destToken, address destAddress) public {
        uint minRate;
        
        //getExpectedRate returns expected rate and slippage rate
        //we use the slippage rate as the minRate
        (, minRate) = _kyberNetworkProxy.getExpectedRate(srcToken, destToken, srcQty);
    
        //check that user has transferred tokens to this contract
        require(srcToken.transferFrom(msg.sender, this, srcQty));
    
        // Mitigate ERC20 Approve front-running attack, by initially setting
        // allowance to 0
        require(srcToken.approve(_kyberNetworkProxy, 0));
                
        //approve tokens so network can take them during the swap
        srcToken.approve(address(_kyberNetworkProxy), srcQty);
        uint destAmount = _kyberNetworkProxy.swapTokenToToken(srcToken, srcQty, destToken, minRate);

        //send received tokens to destination address
        require(destToken.transfer(destAddress, destAmount));
    }
    
    
    //@dev assumed to be receiving ether wei 
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param token destination token contract address
    //@param destAddress address to send swapped tokens to
    function swapEtherToToken (KyberNetworkProxyInterface _kyberNetworkProxy, ERC20 token, address destAddress) public payable {

        uint minRate;
        (, minRate) = _kyberNetworkProxy.getExpectedRate(ETH_TOKEN_ADDRESS, token, msg.value);
    
        //will send back tokens to this contract's address
        uint destAmount = _kyberNetworkProxy.swapEtherToToken.value(msg.value)(token, minRate);

        //send received tokens to destination address
        require(token.transfer(destAddress, destAmount));
    }
    
    
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param token source token contract address
    //@param tokenQty token wei amount
    //@param destAddress address to send swapped ETH to
    function swapTokenToEther (KyberNetworkProxyInterface _kyberNetworkProxy, ERC20 token, uint tokenQty, address destAddress) public {

        uint minRate;
        (, minRate) = _kyberNetworkProxy.getExpectedRate(token, ETH_TOKEN_ADDRESS, tokenQty);
    
        //check that user has transferred tokens to this contract
        require(token.transferFrom(msg.sender, this, tokenQty));
    
        // Mitigate ERC20 Approve front-running attack, by initially setting
        // allowance to 0
        require(srcToken.approve(_kyberNetworkProxy, 0));
    
        //approve tokens so network can take them during the swap
        token.approve(address(_kyberNetworkProxy), tokenQty);
        uint destAmount = _kyberNetworkProxy.swapTokenToEther(token, tokenQty, minRate);

        //send received ethers to destination address
        require(destAddress.transfer(destAmount));
    }
    
    
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param srcToken source token contract address
    //@param srcQty in token wei
    //@param destToken destination token contract address
    //@param destAddress address to send swapped tokens to
    //@param maxDestQty max number of tokens in swap outcome. will be sent to destAddress
    //@param minRate minimum conversion rate for the swap
    function swapTokenToTokenWithChange (
        KyberNetworkProxyInterface _kyberNetworkProxy,
        ERC20 srcToken,
        uint srcQty,
        ERC20 destToken,
        address destAddress,
        uint maxDestQty,
        uint minRate
    )
        public
    {
        uint startSrcBalance = srcToken.balanceOf(this);
    
        //check that tokens has been transferred to this contract
        require(srcToken.transferFrom(msg.sender, this, srcQty));
    
        // Mitigate ERC20 Approve front-running attack, by initially setting
        // allowance to 0
        require(srcToken.approve(_kyberNetworkProxy, 0));
    
        //approve tokens so network can take them during the swap
        srcToken.approve(address(_kyberNetworkProxy), srcQty);

        _kyberNetworkProxy.trade(srcToken, srcQty, destToken, destAddress, maxDestQty, minRate, 0);
        uint change = srcToken.balanceOf(this) - startSrcBalance;

        SwapTokenChange(startSrcBalance, srcToken.balanceOf(this), change);
        //return any remaining source tokens to user
        srcToken.transfer(msg.sender, change);
    }
    
    
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param token destination token contract address
    //@param destAddress address to send swapped tokens to
    //@param maxDestQty max number of tokens in swap outcome. will be sent to destAddress
    //@param minRate minimum conversion rate for the swap
    function swapEtherToTokenWithChange (
        KyberNetworkProxyInterface _kyberNetworkProxy,
        ERC20 token,
        address destAddress,
        uint maxDestQty,
        uint minRate
    )
        public
        payable
    {
        //note that this.balance has increased by msg.value before the execution of this function
        uint startEthBalance = this.balance;
    
        //send swapped tokens to dest address. change will be sent to this contract.
        _kyberNetworkProxy.trade.value(msg.value)(ETH_TOKEN_ADDRESS, msg.value, token, destAddress, maxDestQty, minRate, 0);
    
        //calculate contract starting ETH balance before receiving msg.value (startEthBalance - msg.value)
        //change = current balance after trade - starting ETH contract balance (this.balance - (startEthBalance - msg.value))
        uint change = this.balance - (startEthBalance - msg.value);
    
        SwapEtherChange(startEthBalance, this.balance, change);
    
        //return change to msg.sender
        msg.sender.transfer(change);
    }
    
    
    //@param _kyberNetworkProxy kyberNetworkProxy contract address
    //@param token source token contract address
    //@param tokenQty token wei amount
    //@param destAddress address to send swapped tokens to
    //@param maxDestQty max number of tokens in swap outcome. will be sent to destAddress
    //@param minRate minimum conversion rate for the swap
    function swapTokenToEtherWithChange (
        KyberNetworkProxyInterface _kyberNetworkProxy,
        ERC20 token,
        uint tokenQty,
        address destAddress,
        uint maxDestQty,
        uint minRate
    )
        public
    {
        uint startTokenBalance = token.balanceOf(this);
    
        //check that tokens has been transferred to this contract
        require(token.transferFrom(msg.sender, this, tokenQty));
    
        // Mitigate ERC20 Approve front-running attack, by initially setting
        // allowance to 0
        require(token.approve(_kyberNetworkProxy, 0));
    
        //approve tokens so network can take them during the swap
        token.approve(address(_kyberNetworkProxy), tokenQty);
    
        _kyberNetworkProxy.trade(token, tokenQty, ETH_TOKEN_ADDRESS, destAddress, maxDestQty, minRate, 0);
        uint change = token.balanceOf(this) - startTokenBalance;
    
        SwapTokenChange(startTokenBalance, token.balanceOf(this), change);
        //return any remaining source tokens to user
        token.transfer(msg.sender, change);
    }
}

```

この時点で、一旦コンパイルしてみます。
![Screen Shot 2018-09-02 at 11.57.05.png](https://qiita-image-store.s3.amazonaws.com/0/91514/a0105c9c-038d-e45d-ba7b-3aec97d3d021.png)
右側のコンソールに、たくさんエラーが出ましたね？笑

まず、RemixのSolidityのバージョンを変更して`0.4.18+commit.9cf6e910`にします。
右上の、Settingタブ->Solidity version
![Screen Shot 2018-09-02 at 11.59.34.png](https://qiita-image-store.s3.amazonaws.com/0/91514/cff47c62-9f37-b0ef-19cb-b572ff7c36fe.png)

コンパイルタブに戻ると、エラーが１つに減っているかと思います。
ちょっとコードにバグが有るようなので順番に修正していきます。そのうち公式も修正してくれるでしょう。
![Screen Shot 2018-09-02 at 12.02.11.png](https://qiita-image-store.s3.amazonaws.com/0/91514/15358bbe-0a1a-a9f2-c0f0-5cf716fe2290.png)

79行目にエラーが出ています。

```solidity:KyberExample.sol
        // Mitigate ERC20 Approve front-running attack, by initially setting
        // allowance to 0
x79       require(srcToken.approve(_kyberNetworkProxy, 0));
```

これの原因は、ちょっと完全には理解してないんですが、、、また追記すると思います。（どなたかコメントくださると嬉しいです。）
ファンクションで宣言されてないsrcTokenが原因だと思います。

```solidity:KyberExample.sol
//上記79行目を以下のように修正します。（srcToken -> token）
  require(token.approve(_kyberNetworkProxy, 0));
```
修正して再コンパイルすると、

```solidity:KyberExample.sol
        //send received ethers to destination address
x86        require(destAddress.transfer(destAmount));
```
```
localhost/KyberExample.sol:86:17: TypeError: Invalid type for argument in function call. Invalid implicit conversion from tuple() to bool requested.
        require(destAddress.transfer(destAmount));
                ^------------------------------^
```
86行目でエラーです。

require()は、boolを返してあげないと、TypeErrorが出ます。

destAddress.transfer(destAmount)のファンクションが何を返すか？
Solidityにおいてのaddress型の定義によると
 `function transfer(uint) returns(void)`
address 型は、transfer() でvoidを返します。なので、requireで型エラーを起こすんですね。ということで、requireを削ります。

```solidity:KyberExample.sol
        //send received ethers to destination address
        destAddress.transfer(destAmount);
```

このあたりは削っても問題ないのか？というところに直感的な理解も必要ですが、別の話になるので改めて。 
//ToDo address.transfer()の特殊性について
これが参考になるかな。
[Understanding ERC-20 token contracts](https://medium.com/@jgm.orinoco/understanding-erc-20-token-contracts-a809a7310aa5)

あとは、下記のソースを
`KyberNetworkProxyInterface.sol`に追記してください。
（ProxyInterfaceにswapメソッドが不足しているため）

```solidity:KyberNetworkProxyInterface.sol
    function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint) ;
    function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint);

    function swapTokenToToken(ERC20 src,
        uint srcAmount,
        ERC20 dest,
        uint minConversionRate
        ) public returns(uint);

    function trade(
        ERC20 src,
        uint srcAmount,
        ERC20 dest,
        address destAddress,
        uint maxDestAmount,
        uint minConversionRate,
        address walletId
        )public payable returns(uint);    
```

これでCompileErrorはすべて解決したはず。
![Screen Shot 2018-09-02 at 15.43.44.png](https://qiita-image-store.s3.amazonaws.com/0/91514/531904ca-2337-8e6f-e153-3f00a119a281.png)

ソースの操作はここまで。
あとはRemix上で試してみましょう。

##KyberをRemixとMetamaskで色々実験してみる

Remix画面右側のRun タブで
ERC20→KyberExampleに設定。
![Screen Shot 2018-09-02 at 15.20.51.png](https://qiita-image-store.s3.amazonaws.com/0/91514/e2b009ea-13e1-9cda-45c1-fbe2a8387cca.png)
メタマスクのアドレスを指定して
![Screen Shot 2018-09-02 at 15.53.06.png](https://qiita-image-store.s3.amazonaws.com/0/91514/dd9729e4-bc9b-2f6f-91cb-c7cb77246fac.png)
Deploy！
すると、以下のような画面が出てくるはずです。
GASが少ないとトランザクション遅くなるので、ちょっと足しておきましょう。20Gweiくらい？
![Screen Shot 2018-09-02 at 15.58.18.png](https://qiita-image-store.s3.amazonaws.com/0/91514/40ae8dad-5b0f-2504-c28c-8bd463d6c1c4.png)
Confirmすると、しばらくしたらRemixの下の方に結果が出てくるはずです。
![Screen Shot 2018-09-02 at 16.24.06.png](https://qiita-image-store.s3.amazonaws.com/0/91514/b8e3a0d8-b2bb-fe5d-d474-2c87293bc56f.png)
そしたら右の方にあるDeployedContractsのところに現れたトランザクションについて、▲をクリックします。
![Screen Shot 2018-09-02 at 16.25.34.png](https://qiita-image-store.s3.amazonaws.com/0/91514/bcb77e4e-9038-82be-cfe2-32fcf6cb80a4.png)

###swapEhterToTokenをしてみる
指定項目は以下
**value**
1ETH送ってみます。
![Screen Shot 2018-09-02 at 16.53.12.png](https://qiita-image-store.s3.amazonaws.com/0/91514/140e58f3-c445-3e11-b12f-ec447fe1a933.png)

**_kyberNetworkProxy**

`0x818E6FECD516Ecc3849DAf6845e3EC868087B755`
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
![Screen Shot 2018-09-02 at 16.35.51.png](https://qiita-image-store.s3.amazonaws.com/0/91514/ee5b2c41-3f02-0b4f-9864-5126ba8f7be1.png)

**token**
`0x499990DB50b34687CDaFb2C8DaBaE4E99d6F38A7`
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
![Screen Shot 2018-09-02 at 16.34.48.png](https://qiita-image-store.s3.amazonaws.com/0/91514/0c1314de-f106-bf42-a3fe-318ef8d31b45.png)

**Address**
メタマスクの自分のアドレス

![Screen Shot 2018-09-02 at 16.50.50.png](https://qiita-image-store.s3.amazonaws.com/0/91514/815d38f2-34fd-f12b-e48e-48e33ba5fe4a.png)
transactを実行すると、先ほどと同じようにGAS指定できるので、20Gweiくらいにして実行しましょう。
結果のトランザクションこちら。
https://ropsten.etherscan.io/tx/0x6b9c2a361c691917e33d9a6ab910885a6025c671c52d448998ad0b505099ddaf
![Screen Shot 2018-09-02 at 16.54.59.png](https://qiita-image-store.s3.amazonaws.com/0/91514/85f4f94c-8ca2-9543-77af-d293b033ffd2.png)
結果、1ETHが減って、ADXが増えています！成功。
![Screen Shot 2018-09-02 at 17.00.55.png](https://qiita-image-store.s3.amazonaws.com/0/91514/525bc1e8-49f1-7681-322f-f7716112f0a0.png)

###swapTokenToEtherのためにTokenをapproveする
Approveが必要なので、ERC20を指定して、Load contract from addressに
`0x499990DB50b34687CDaFb2C8DaBaE4E99d6F38A7`
を入力し、At Addressをクリック。

Deployed ContractsのERC20を開いたら更にapproveを開き、以下を入力
**_spender**
`すでにあるDeveloped ContractsのKyberExampleのvalue`
※下の画面の黄色くなってるところからコピーできます。
**_value**
`10000000`（1000ADXはDecimalが4桁なので、こういうふうに表現する。）
![Screen Shot 2018-09-02 at 17.57.45.png](https://qiita-image-store.s3.amazonaws.com/0/91514/732335be-06a8-c35a-5108-d111035e0dd6.png)
transact!
出てきた画面で同様にGASを指定して実行。
![Screen Shot 2018-09-02 at 18.02.36.png](https://qiita-image-store.s3.amazonaws.com/0/91514/8986de1b-33ae-6b9d-0a6c-53870ff3878f.png)

Transaction例
https://ropsten.etherscan.io/tx/0xef3987bdc18761e25858472c3dd980094a2ce41bb3a0f788b3db9db3a77b3fbe

###KyberでswapTokenToEtherをやってみる
Deployed Contractsから先程のapprove時に使ったKyberExampleを選択

swapTokenToEtherを選択

指定項目は以下（tokenQty以外はswapEtherToTokenと同じ。）
**_kyberNetworkProxy**
`0x818E6FECD516Ecc3849DAf6845e3EC868087B755`

**token**
`0x499990DB50b34687CDaFb2C8DaBaE4E99d6F38A7`

**tokenQty**
`1000000`（100ADXはDecimalが4桁なので、こういうふうに表現する。）
※上記手順でapproveした以内のAmountを入力。

**destAddress**
メタマスクの自分のアドレス
![Screen Shot 2018-09-02 at 18.04.46.png](https://qiita-image-store.s3.amazonaws.com/0/91514/65614da4-bce1-d35c-7bb8-2146f802c0a5.png)
transact!
同様にGAS指定を（以下略）

送トークン前
![Screen Shot 2018-09-02 at 18.05.59.png](https://qiita-image-store.s3.amazonaws.com/0/91514/4e700918-4eb0-92af-9000-dbf9e7643c97.png)
送トークン後
![Screen Shot 2018-09-02 at 18.08.01.png](https://qiita-image-store.s3.amazonaws.com/0/91514/c2431781-132a-e7be-4ca9-2e615e239e54.png)

送金できた！
ADXが100減って、ETHが増えてるのが確認できますね！

transaction例
https://ropsten.etherscan.io/tx/0x6c9c529e4d7638cc8fb92e0df47e1a7b35e8c50fe88929ee8ef56a3140a02687

お疲れ様でした！
今後は他のメソッドも使って色々応用してみようと思います！

##参考
同じワークショップに参加した方の記事です。画像はあまりないですが、手順がまとまってます。うろおぼえだったところ、助かりました！
[Kyber NetworkとRemixでKyber Networkを試してみる]
(https://qiita.com/hidehiro98/items/6b303633413fb555c7a0)