目的
ここ2~3年最もEthereumで盛り上がっている領域の1つとして、Decentralized Finance(Defi)が挙げられます。
様々なトークンの交換やレンディングを行えるプロトコルなどが提案・稼働されてきました。
そして、Defiの強みはこういった複数のプロトコルを組み合わせて新しい金融システムを再構築できる点は既存の金融とは明らかに異なる強みとなっています。
この再構築できることはMoney Legoと呼ばれており、この記事では複数プロトコルを組み合わせた簡単なスマートコントラクトの実装を通してDefiを体感することを目指しています。
また、この内容は社内の勉強会で使った内容でもあり、エンジニア以外のBizDevの人も参加していたので、最近Defiに興味あるような方全員におすすめです。
作るものの概要
- ETH → DAI → cDAIに変換して利子を得られる+ETHの価格変動リスクを抑えることができるようなDAppsを作る
- ETH → DAIにはUniswapで変換する
- DAI → cDAIへは当たり前だけどCompound
- ETH引き出し(逆方向でcDAI → DAI → ETH)もできるようにする
- デポジット時
- ETH → uniswap → Dai → Compound → cDai
- redeem
- cDai → Compound → Dai → Uniswap → ETH
各プロトコルの説明
Uniswap
- 注意:uniswap v1(v2ではない)
- 誰でもデプロイできるDEXコントラクト
- 1コントラクトでERC20 / ETHとのペアのDEXになる
- ロジックはざっくりいうと、
Token A * Token B = constant
を保ちながら、片方のトークンをデポジットすると、それで浮いた分のもう片方のトークンを受け取れるようなアルゴリズム的に価格が決定する仕組み - 詳しくは:Uniswap - A Unique Exchange
Compound
- Lendingプロトコル(トークンの貸し借り)
- Compoundが承認しているトークンに対して、借りる or 貸すことができる
- Daiを貸すケースを例としたロジックとしては、
- Daiをデポジットして、cDaiを発行する
- cDaiはCompound上でDaiを借りるイベントが発生するたびに、1cDaiの価格が上昇する
- 結果的に利子を得ることができる
- 詳しくは
手順
コード
今回のコードはwinor30/eth-to-dai-to-cdaiにあります。
環境準備
コードのダウンロード
$ git clone https://github.com/winor30/eth-to-dai-to-cdai.git
.envファイルを作る
# https://infura.io/でアカウントを作ってそのapi key
INFURA_KEY="***"
# ethのネットワーク。今回はテストネット
NETWORK="rinkeby"
# HD walletのMNEMONIC。https://iancoleman.io/bip39/ で生成可能
MNEMONIC="***"
# deployしたコントラクトのアドレス。デプロイ後に設定
ETH_TO_DAI="0x******"
デプロイやデポジットに必要なrinkebyのethを取得する
- https://faucet.rinkeby.io/ で取得可能
依存関係を追加する
$ npm install
コントラクトの説明
デポジット
- eth -> cDaiへの変換
// Eth -> Uniswap -> Token -> CTokenに変換して、デポジット
function swapEtherToTokenToCTokenByUniswap (address payable uniswapAddress, address tokenAddress, address cTokenAddress, uint256 deadline) public payable {
// UniswapでETH -> ERC20へ変換
uint256 tokenAmount = swapEthToToken(uniswapAddress, deadline);
emit MyLog("token for swap result", tokenAmount);
// ERC20 -> CTokenの発行
uint256 cTokenResult = supplyErc20ToCompound(tokenAddress, cTokenAddress, tokenAmount);
emit MyLog("ctoken for supply result to compound", cTokenResult);
}
// UniswapでETH -> ERC20へ変換
function swapEthToToken(address payable uniswapAddress, uint256 deadline) public payable returns (uint256) {
// uniswapAddressからuniswapの機能を利用するために、UniswapExchangeInterfaceでインスタンス化
// UniswapExchangeInterface: https://github.com/winor30/eth-to-dai-to-cdai/blob/master/contracts/Eth2Dai.sol#L7-L49
UniswapExchangeInterface _uniswapExchange = UniswapExchangeInterface(uniswapAddress);
// uniswapを使ってethをtoken(Dai)へ変換する
// msg.valueはtxに付加されているETHの量
uint256 tokenAmount = _uniswapExchange.ethToTokenSwapInput.value(msg.value)(1, deadline);
// 取得したDaiの量を返却する
return tokenAmount;
}
// ERC20 -> CTokenの発行
function supplyErc20ToCompound(
address _erc20Contract,
address _cErc20Contract,
uint256 _numTokensToSupply
) public returns (uint256) {
// トークン(Dai)のアドレスを使ってERC20コントラクトのメソッドを使うためにインスタンス化
ERC20 underlying = ERC20(_erc20Contract);
// cトークン(cDai)のアドレスを使ってCERC20コントラクトのメソッドを使うためにインスタンス化
CERC20 cToken = CERC20(_cErc20Contract);
// cDaiでDaiをtransferFromするための権限を与える
underlying.approve(_cErc20Contract, _numTokensToSupply);
// Daiを渡して、cDaiを生成する
uint256 mintResult = cToken.mint(_numTokensToSupply);
return mintResult;
}
Ethの引き出し
- cDai -> ETH
// CTokenを全てETHで引き出す
function redeemAll (address uniswapAddress, address tokenAddress, address cTokenAddress, uint256 deadline) public onlyOwner {
// CtokenをERC20へ戻す
redeemErc20FromCompound(cTokenAddress);
// ERC20からETHへ戻す
uint256 ethAmount = swapTokenToEth(uniswapAddress, tokenAddress, deadline);
emit MyLog("ethAmount for redeem result from uniswap", ethAmount);
address(msg.sender).send(address(this).balance);
}
// CtokenをERC20へ戻す
function redeemErc20FromCompound(address _cErc20Contract) public onlyOwner returns (uint256) {
// cトークン(cDai)のアドレスを使ってCERC20コントラクトのメソッドを使うためにインスタンス化
CERC20 cToken = CERC20(_cErc20Contract);
// 現在のデポジットされているcDaiの残高を取得
uint256 targetRedeemTargetTokenAmount = cToken.balanceOf(address(this));
// cDaiを返却して、Daiを取得する
uint256 redeemResult = cToken.redeem(targetRedeemTargetTokenAmount);
return redeemResult;
}
// ERC20からETHへ戻す
function swapTokenToEth(address uniswapAddress, address _erc20Contract, uint256 deadline) public onlyOwner returns (uint256) {
// トークン(Dai)のアドレスを使ってERC20コントラクトのメソッドを使うためにインスタンス化
ERC20 token = ERC20(_erc20Contract);
// Daiの残高を取得(cTokenから受け取った量)
uint256 tokenAmount = token.balanceOf(address(this));
// DaiをUniswapを介してユーザーへ渡すための権限を与える
bool success = token.approve(uniswapAddress, tokenAmount);
require(success, "failed approve from this to uniswapaddress");
// uniswapAddressからuniswapの機能を利用するために、UniswapExchangeInterfaceでインスタンス化
UniswapExchangeInterface _uniswapExchange = UniswapExchangeInterface(uniswapAddress);
// uniswapを使って、Dai -> Ethへ変換
uint256 ethAmount = _uniswapExchange.tokenToEthSwapInput(tokenAmount, 1, deadline);
return ethAmount;
}
コントラクトのデプロイ
- rinkebyにデプロイする
- 取得できたコントラクトアドレスを.envファイルに追加する
$ npm run deploy:rinkeby
# log...
> eth-to-dai-to-cdai@1.0.0 deploy:rinkeby /Users/tkatsuma/workspace/eth-to-dai
> truffle deploy --network rinkeby
Compiling your contracts...
===========================
...
> contract address: 0xb74d23f3b37FB5F4C396Ca14Fd3C635621e7419c
...
ETHのデポジット
-
npm run deposit
でethをデポジットして、cDaiを取得できる- ↓の例は0.1ETHをデポジットする
$ npm run deposit -- 0.1
> eth-to-dai-to-cdai@1.0.0 deposit /Users/tkatsuma/workspace/eth-to-dai
> node scripts/depositEthWithCompound.js "0.1"
{ NETWORK: 'rinkeby' }
before swapEtherToTokenToCTokenByUniswap
eth balance 0.507880132
ethToDai.methods.swapEtherToTokenToCTokenByUniswap result true
after swapEtherToTokenToCTokenByUniswap
eth balance 0.404020552
ETHを取得する
-
npm run redeem
でETHを再取得する
$ npm run redeem
> eth-to-dai-to-cdai@1.0.0 redeem /Users/tkatsuma/workspace/eth-to-dai
> node scripts/redeemEthFromCompound.js
{ NETWORK: 'rinkeby' }
before send
cDai balanceOf 43915411483
eth balance 0.404020552
ethToDai.methods.redeemAll result true
after send
cDai balanceOf 0
eth balance 0.499911204789187889
参考
Supplying Assets to the Compound Protocol