ETHをcDaiに変換するコントラクトでDefiを体感しよう

### 目的
ここ2~3年最もEthereumで盛り上がっている領域の１つとして、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](https://medium.com/scalar-capital/uniswap-a-unique-exchange-f4ef44f807bf)

![https://miro.medium.com/max/535/1*gh35sRDaNmnVY7S5avb42A.png](https://miro.medium.com/max/535/1*gh35sRDaNmnVY7S5avb42A.png)

### Compound

- Lendingプロトコル（トークンの貸し借り）
- Compoundが承認しているトークンに対して、借りる or 貸すことができる
- Daiを貸すケースを例としたロジックとしては、
    - Daiをデポジットして、cDaiを発行する
    - cDaiはCompound上でDaiを借りるイベントが発生するたびに、1cDaiの価格が上昇する
        - 結果的に利子を得ることができる
- 詳しくは
  - [https://compound.finance/ctokens](https://compound.finance/ctokens)
  - [「cDAI/cトークン」とは？｜DeFiレンディングプロトコルCompoundの債権トークン | ZENISM](https://zenism.jp/projects/lending/about-c-token/2019/09/05/)

### 手順

#### コード
今回のコードは[winor30/eth-to-dai-to-cdai](https://github.com/winor30/eth-to-dai-to-cdai)にあります。

#### 環境準備

コードのダウンロード

```sh
$ 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/ で取得可能


依存関係を追加する

```sh

$ npm install
```

#### コントラクトの説明
##### デポジット
- eth -> cDaiへの変換

```solidity

// 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

```solidity
// 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ファイルに追加する

```sh
$ 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を再取得する

```sh
$ 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](https://medium.com/compound-finance/supplying-assets-to-the-compound-protocol-ec2cf5df5aa)

[compound-developers/compound-supply-examples](https://github.com/compound-developers/compound-supply-examples#minting-test-dai)

[「cDAI/cトークン」とは？｜DeFiレンディングプロトコルCompoundの債権トークン | ZENISM](https://zenism.jp/projects/lending/about-c-token/2019/09/05/)