2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-02

#この記事の目的
送信したトークンが受け取り側に届く際に自動でスワップされる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入門その1】を一通りやってみるのがおすすめです。コードで実装してみたいという方は次にお進み下さい。

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

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

###IDE
今回は、Remixdを使って書きました。
コードを書くだけでなく、色々コントラクトの挙動やパラメータを操作できたりするので非常に便利です。
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にアクセスしましょう。
Screen Shot 2018-09-02 at 11.14.50.png
左上にある、🔗マークをクリックすると、ローカルに接続するか聞かれますので
Screen Shot 2018-09-02 at 11.15.07.png
Connectでつなぐと、先程Shared Folderに指定したフォルダの内容が表示されます。
Screen Shot 2018-09-02 at 11.43.08.png
先程作ったKyberExample.solもありますね。開きます。
白紙のはずですので、そこに公式のDApps Integration Guideから取ってきたContract Exampleのソースをコピペします。(2018/09/02時点のソース)

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
右側のコンソールに、たくさんエラーが出ましたね?笑

まず、RemixのSolidityのバージョンを変更して0.4.18+commit.9cf6e910にします。
右上の、Settingタブ->Solidity version
Screen Shot 2018-09-02 at 11.59.34.png

コンパイルタブに戻ると、エラーが1つに減っているかと思います。
ちょっとコードにバグが有るようなので順番に修正していきます。そのうち公式も修正してくれるでしょう。
Screen Shot 2018-09-02 at 12.02.11.png

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

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

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

KyberExample.sol
//上記79行目を以下のように修正します。(srcToken -> token)
  require(token.approve(_kyberNetworkProxy, 0));

修正して再コンパイルすると、

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を削ります。

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

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

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

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

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

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

Remix画面右側のRun タブで
ERC20→KyberExampleに設定。
Screen Shot 2018-09-02 at 15.20.51.png
メタマスクのアドレスを指定して
Screen Shot 2018-09-02 at 15.53.06.png
Deploy!
すると、以下のような画面が出てくるはずです。
GASが少ないとトランザクション遅くなるので、ちょっと足しておきましょう。20Gweiくらい?
Screen Shot 2018-09-02 at 15.58.18.png
Confirmすると、しばらくしたらRemixの下の方に結果が出てくるはずです。
Screen Shot 2018-09-02 at 16.24.06.png
そしたら右の方にあるDeployedContractsのところに現れたトランザクションについて、▲をクリックします。
Screen Shot 2018-09-02 at 16.25.34.png

###swapEhterToTokenをしてみる
指定項目は以下
value
1ETH送ってみます。
Screen Shot 2018-09-02 at 16.53.12.png

_kyberNetworkProxy

0x818E6FECD516Ecc3849DAf6845e3EC868087B755
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
Screen Shot 2018-09-02 at 16.35.51.png

token
0x499990DB50b34687CDaFb2C8DaBaE4E99d6F38A7
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
Screen Shot 2018-09-02 at 16.34.48.png

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

Screen Shot 2018-09-02 at 16.50.50.png
transactを実行すると、先ほどと同じようにGAS指定できるので、20Gweiくらいにして実行しましょう。
結果のトランザクションこちら。
https://ropsten.etherscan.io/tx/0x6b9c2a361c691917e33d9a6ab910885a6025c671c52d448998ad0b505099ddaf
Screen Shot 2018-09-02 at 16.54.59.png
結果、1ETHが減って、ADXが増えています!成功。
Screen Shot 2018-09-02 at 17.00.55.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
transact!
出てきた画面で同様にGASを指定して実行。
Screen Shot 2018-09-02 at 18.02.36.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
transact!
同様にGAS指定を(以下略)

送トークン前
Screen Shot 2018-09-02 at 18.05.59.png
送トークン後
Screen Shot 2018-09-02 at 18.08.01.png

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

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

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

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

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?