#この記事の目的
送信したトークンが受け取り側に届く際に自動でスワップされる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にアクセスしましょう。
左上にある、🔗マークをクリックすると、ローカルに接続するか聞かれますので
Connectでつなぐと、先程Shared Folderに指定したフォルダの内容が表示されます。
先程作ったKyberExample.solもありますね。開きます。
白紙のはずですので、そこに公式のDApps Integration Guideから取ってきたContract Exampleのソースをコピペします。(2018/09/02時点のソース)
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);
}
}
この時点で、一旦コンパイルしてみます。
右側のコンソールに、たくさんエラーが出ましたね?笑
まず、RemixのSolidityのバージョンを変更して0.4.18+commit.9cf6e910
にします。
右上の、Settingタブ->Solidity version
コンパイルタブに戻ると、エラーが1つに減っているかと思います。
ちょっとコードにバグが有るようなので順番に修正していきます。そのうち公式も修正してくれるでしょう。
79行目にエラーが出ています。
// Mitigate ERC20 Approve front-running attack, by initially setting
// allowance to 0
x79 require(srcToken.approve(_kyberNetworkProxy, 0));
これの原因は、ちょっと完全には理解してないんですが、、、また追記すると思います。(どなたかコメントくださると嬉しいです。)
ファンクションで宣言されてないsrcTokenが原因だと思います。
//上記79行目を以下のように修正します。(srcToken -> token)
require(token.approve(_kyberNetworkProxy, 0));
修正して再コンパイルすると、
//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を削ります。
//send received ethers to destination address
destAddress.transfer(destAmount);
このあたりは削っても問題ないのか?というところに直感的な理解も必要ですが、別の話になるので改めて。
//ToDo address.transfer()の特殊性について
これが参考になるかな。
Understanding ERC-20 token contracts
あとは、下記のソースを
KyberNetworkProxyInterface.sol
に追記してください。
(ProxyInterfaceにswapメソッドが不足しているため)
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);
ソースの操作はここまで。
あとはRemix上で試してみましょう。
##KyberをRemixとMetamaskで色々実験してみる
Remix画面右側のRun タブで
ERC20→KyberExampleに設定。
メタマスクのアドレスを指定して
Deploy!
すると、以下のような画面が出てくるはずです。
GASが少ないとトランザクション遅くなるので、ちょっと足しておきましょう。20Gweiくらい?
Confirmすると、しばらくしたらRemixの下の方に結果が出てくるはずです。
そしたら右の方にあるDeployedContractsのところに現れたトランザクションについて、▲をクリックします。
###swapEhterToTokenをしてみる
指定項目は以下
value
1ETH送ってみます。
_kyberNetworkProxy
0x818E6FECD516Ecc3849DAf6845e3EC868087B755
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
token
0x499990DB50b34687CDaFb2C8DaBaE4E99d6F38A7
https://developer.kyber.network/docs/RopstenEnvGuide
から取ってくる。
Address
メタマスクの自分のアドレス
transactを実行すると、先ほどと同じようにGAS指定できるので、20Gweiくらいにして実行しましょう。
結果のトランザクションこちら。
https://ropsten.etherscan.io/tx/0x6b9c2a361c691917e33d9a6ab910885a6025c671c52d448998ad0b505099ddaf
結果、1ETHが減って、ADXが増えています!成功。
###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桁なので、こういうふうに表現する。)
transact!
出てきた画面で同様にGASを指定して実行。
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
メタマスクの自分のアドレス
transact!
同様にGAS指定を(以下略)
送金できた!
ADXが100減って、ETHが増えてるのが確認できますね!
transaction例
https://ropsten.etherscan.io/tx/0x6c9c529e4d7638cc8fb92e0df47e1a7b35e8c50fe88929ee8ef56a3140a02687
お疲れ様でした!
今後は他のメソッドも使って色々応用してみようと思います!
##参考
同じワークショップに参加した方の記事です。画像はあまりないですが、手順がまとまってます。うろおぼえだったところ、助かりました!
[Kyber NetworkとRemixでKyber Networkを試してみる]
(https://qiita.com/hidehiro98/items/6b303633413fb555c7a0)