MakerDAOのコードを基に、ERC20トークンを担保にERC20トークンを発行するコントラクト書いてみた。

MakerDAOのコントラクトを参考に、ERC20トークンをデポジットして、それを担保に新しいERC20トークンを発行する簡易版のミニチュアMakerコントラクトのコードを書いてみました。Remixで試したので動くことは検証済みです。学習の記録として共有&解説します。

参考
・[MakerDocs](https://docs.makerdao.com/)
・[Makerdao/dss(github)](https://github.com/makerdao/dss)

##MakerDAOとは

MakerDAOはステーブルコインであるDAIを発行するDeFi（分散型金融）スマートコントラクトの一つです。例えばコントラクトに担保としてETHをデポジットすると、ローンとしてステーブルコインであるDAIを受け取ることができます。

最近のMakerDAOはETHだけでなくBATやKNC、USDC、WBTCなど、様々なErc20トークンを担保にDAIを発行できるMulti-Collateral-Daiモデルに移行しています。ただ特にUSDCやWBTCなど、カウンターパーティーリスクのあるトークンが担保資産として使われていることに、個人的に疑問を持っています。

あと、最近Ethereum上にERC20トークン型のBTCを流入させる動きが活発化しているので、どうせならトラストレスモデルなERC20 BTCだけを担保にした、DAIとは方向性が少し異なるステーブルコイン作れないかなーと思っています。

##どんなコードを書くのか

今回は超基本の基本として、

1-ERC20トークンのデポジット
2-ERC20トークンの引き出し
3-ステーブルコインとなる新しいERC20トークンの発行
4-ステーブルコインとなる新しいERC20トークンの返済

以上、4つの基本的なfunctionを実行できるコントラクトを書きました。

参考にしたソースコードは、Makerのgithubのdssディレクトリ内にある

・[vat.sol](https://github.com/makerdao/dss/blob/master/src/vat.sol)
・[join.sol](https://github.com/makerdao/dss/blob/master/src/join.sol)
・[dai.sol](https://github.com/makerdao/dss/blob/master/src/dai.sol)


の3つです。

vat.solは、MakerDAOでいう所のVault(旧CDP）に辺り、ユーザーの担保資産を管理するコントラクトです。dai.solはステーブルコインDAIのトークンコントラクトです。最後のjoin.solはユーザーインターフェースで、ユーザーの呼び出しに対して、vat.solとdai.solを呼び出す役割を持ちます。

そして僕が作ったコントラクトが以下4つ。

・openvault.sol（join.solに該当）
・vault.sol（vat.solに該当）
・stbtc.sol（dai.solに該当）
・renbtc.sol

stbtcはStableBitcoinの略です。w 最後のrenbtc.solは担保となるERC20トークンのトークンコントラクトです。RenBTCというERC20型のビットコインで、BTCと価格がペッグしています。

RenBTCはもう既にEthereumチェーン上に1万以上流通してるトークンです。今回用意したコードはRenBTCと名付けただけの架空のトークンですが、このなんちゃってRenBTCを担保資産にStable Bitcoin(stBTC)を発行していきたいと思います。

コントラクトの関係を表すイメージ図はこんな感じです。ユーザーはOpenVaultとしかやり取りしません。OpenVaultが司令塔となって、左二つのトークンの送金を指示したり、Vaultコントラクト内の担保資産と債務（ステーブルコイン）の帳簿を更新したりする形です。

![Screenshot 2020-08-19 at 6.21.48 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274043/18a50b1a-a4c7-d522-fc2b-21aef760ebea.png)


##コード

以下はMakerのコードをだいぶ参考（コピペ）にしているので、MakerDAOがコードベースでどのように作られているかを理解するのに役立ちます。ただし簡易化のためにいくつかの関数やrequire構文など取り除いていたりしています。

さて、まずはopenvault.solからです。

###openvault.sol 
```solidity
pragma solidity >=0.5.12;

//intefaceでvault.sol, stbtc.sol, renbtc.sol内のfunctionを使えるようにする。
interface Vault {
    function deposit(address, uint) external;
    function withdraw(address, uint) external;
    function mint(address, uint) external;
    function burn(address, uint) external;
}

interface StableBitcoin {
    function stBTCmint(address, uint) external;
    function stBTCburn(address, uint) external;
    function stBTCtransferFrom(address, address, uint) external returns (bool);
    function getstBTCBalanceOf(address) view external returns(uint);
}

interface RenBTC {
    function rBTCtransferFrom(address, address, uint) external returns (bool);
    function getrBTCBalanceOf(address) view external returns(uint);
}

contract OpenValut {

   //Vault, StableBitcoin, RenBTCの3つのコントラクトをインスタンス化
    Vault public vault;
    StableBitcoin public stbtc;
    RenBTC public rbtc; 

   //OpenVault内で関数を呼び出せるようにする
    constructor(address _vault, address _stbtc, address _rbtc) public {
        vault = Vault(_vault);
        stbtc = StableBitcoin(_stbtc);
        rbtc = RenBTC(_rbtc);
    }
    //renBTCのbalanceチェック
    function rBTCBalanceOf() view external returns(uint) {
       return rbtc.getrBTCBalanceOf(msg.sender);
    }
    //stBTCのbalanceチェック
    function stBTCBalanceOf() view external returns(uint) {
       return stbtc.getstBTCBalanceOf(msg.sender);
    }
    
    //担保をデポジットする
    function depositCol(uint amount) external {
        //vault.depositでVaultコントラクトのdeposit関数を実行
        vault.deposit(msg.sender, amount);
        //RenBTCコントラクトのtransferFrom関数を実行
        rbtc.rBTCtransferFrom(msg.sender, address(vault), amount);
    }
　　 //担保を引き出す
    function withdrawCol(uint amount) external {
        vault.withdraw(msg.sender, amount);
        rbtc.rBTCtransferFrom(address(vault), msg.sender, amount);
    }
    
    //stBTCを借りる
    function borrowstBTC(uint amount) external{
        //vault.mintでVaultコントラクト内のmint関数を実行
        vault.mint(msg.sender, amount);
　　　   //Stable Bitcoinコントラクト内のmint関数を実行
        stbtc.stBTCmint(msg.sender, amount);
    }
    
    //stBTCを返済する
    function paybackstBTC(uint amount) external {
        vault.burn(msg.sender, amount);
        stbtc.stBTCburn(msg.sender, amount);
    }
}
```

openvault.solはあくまでインターフェイスなので、このコントラクト内で何かしらのデータが動くことはありません。以下で紹介するvault.solやstBTC, renBTCなどのコントラクトに値を渡して、関数の実行を呼び出すことしかしません。

次にvault.solです。

###vault.sol
```solidity 
pragma solidity ^0.5.12;

contract Vault {
    
    //--- Math ---
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x);
    }
    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x);
    } 

    //Vault。colは担保資産。loanは発行されたステーブルコイン。
    struct Val {
        uint col;
        uint loan;
    }

　　//Vaultをアドレスと紐付けてvalsマッピングに格納。
    mapping(address => Val) public vals;
    
    //担保資産をデポジット。ユーザーのVault内のcolにamountを追加する。
    function deposit(address user, uint amount) external {
        Val memory val = vals[user];
        val.col = add(val.col, amount);
        vals[user] = val;
    }
　　　
　　//担保資産を引き出す。ユーザーのVault内のcolからamountを引く。
    function withdraw(address user, uint amount) external {
        Val memory val = vals[user];
        val.col = sub(val.col, amount);
        vals[user] = val;
　　　
　　　　　//担保比率200%を下回る担保資産は引き出せないようrequire設定。
        require((vals[user].loan * stBTCprice) <= ((vals[user].col * rBTCprice) / 2), 
        "You can't withdraw collateral unless the collateral ratio is more than 2 times");
    }
    
    //ステーブルコイン借入(発行)。Vault内のloanにamountを追加。
    function mint(address user, uint amount) external {
        Val memory val = vals[user];
        val.loan = add(val.loan, amount);
        vals[user] = val;
　　　　　//担保資産の1/2以上のloanは発行できないようrequire設定。
        require((vals[user].loan * stBTCprice) <= ((vals[user].col * rBTCprice) / 2),
        "You can't mint the loan which is more than 1/2 of collateral");
        vals[user].ratio = ((vals[user].loan * stBTCprice) / (vals[user].col * rBTCprice) * 100);
    }
    
    //ステーブルコイン返済(焼却)。Vault内のloanからamountを引く。
    function burn(address user, uint amount) external {
        Val memory val = vals[user];
        val.loan = sub(val.loan, amount);
        vals[user] = val;
        vals[user].ratio = ((vals[user].loan * stBTCprice) / (vals[user].col * rBTCprice) * 100);
    }
    }

```

vault.sol(vat.sol)はMakerDAOの最も根幹的なコントラクトです。公式ドキュメントみてわかる通り、全ての中心にvat.sol（真ん中の円錐）がいることが分かります。

![MCD System 2.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274043/4262a491-8e55-755e-07c8-5643d85827b8.png)


以上のコードは元のvat.solを超超シンプルにした貧弱verだといえます。ステーブルコインの価値を保証するためのパラメータである担保比率は2倍に設定しているので、withdrawとmintの場面ではrequireで過剰なステーブルコイン発行と、過剰な担保資産の引き出しを制御しています。つまり、担保がないとお金（ステーブルコイン）が借りれない状況を作っています。

以下はstbtc.solとrenbtc.solです。これらのコードは、openzeppelinなどのよくあるERC20トークンコントラクトのコードと大差ありません。加えて、名前や関数名などが違うだけで、2つのコードには大した違いはないです。

###stbtc.sol
```solidity

pragma solidity 0.5.12;

contract StableBitcoin {
    
    string  public constant name     = "Stable Bitcoin";
    string  public constant symbol   = "stBTC";
    string  public constant version  = "1";
    uint8   public constant decimals = 18;
    uint256 public totalSupply;
    
    mapping (address => uint)                      public balanceOf;
    mapping (address => mapping (address => uint)) public allowance;
    mapping (address => uint)                      public nonces;
    
     //--- Math ---
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x);
    }
    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x);
    } 
   
   function getstBTCBalanceOf(address owner) view external returns(uint) {
       return balanceOf[owner];
   }
    function stBTCtransferFrom(address src, address dst, uint wad)
        external returns (bool) {
        balanceOf[src] = sub(balanceOf[src], wad);
        balanceOf[dst] = add(balanceOf[dst], wad);
        return true;
    }
    
    function stBTCmint(address usr, uint wad) external {
        balanceOf[usr] = add(balanceOf[usr], wad);
        totalSupply    = add(totalSupply, wad);
    }
    function stBTCburn(address usr, uint wad) external {
        balanceOf[usr] = sub(balanceOf[usr], wad);
        totalSupply    = sub(totalSupply, wad);
    }
    function stBTCapprove(address usr, uint wad) external returns (bool) {
        allowance[msg.sender][usr] = wad;
        return true;
    }

}

```

###renbtc.sol
```solidity 
pragma solidity 0.5.12;

contract RenBTC {
    
    string  public constant name     = "RenBTC";
    string  public constant symbol   = "rBTC";
    string  public constant version  = "1";
    uint8   public constant decimals = 18;
    uint256 public totalSupply;
    
    mapping (address => uint)                      public balanceOf;
    mapping (address => mapping (address => uint)) public allowance;
    mapping (address => uint)                      public nonces;
    
     //--- Math ---
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x);
    }
    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x);
    } 
   
   function getrBTCBalanceOf(address owner) view external returns(uint) {
       return balanceOf[owner];
   }
    function rBTCtransferFrom(address src, address dst, uint wad)
        external returns (bool) {
        balanceOf[src] = sub(balanceOf[src], wad);
        balanceOf[dst] = add(balanceOf[dst], wad);
        return true;
    }
    
    function rBTCmint(address usr, uint wad) external {
        balanceOf[usr] = add(balanceOf[usr], wad);
        totalSupply    = add(totalSupply, wad);
    }
    function rBTCburn(address usr, uint wad) external {
        require(balanceOf[usr] >= wad, "RenBTC/insufficient-balance");
        if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
            require(allowance[usr][msg.sender] >= wad, "RenBTC/insufficient-allowance");
            allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
        }
        balanceOf[usr] = sub(balanceOf[usr], wad);
        totalSupply    = sub(totalSupply, wad);
    }
    function rBTCapprove(address usr, uint wad) external returns (bool) {
        allowance[msg.sender][usr] = wad;
        return true;
    }
}

```

以上です。Makerの実際のディレクトリを見ると、これに加えて精算プロセスやVaultのクローズ、価格安定化メカニズム、オラクルなど他にも様々なコントラクトがあるのですが、その辺りはまた勉強して、次回以降書いていきます。

###追記

清算プロセスの実行には担保比率が必要。vault.solのValストラクト内にratioという変数を追加し、以下の一文をwithdraw, mint, burn関数内に差し込むと、Vaultから資産を出し入れする中で自動で担保比率が変わる。

```solidity

val.ratio = (val.col * rBTCprice) * 100 / (val.loan * stBTCprice);
```

オラクルが未実装なので、rBTCpriceもstBTCpriceもどちらも1。担保比率(ratio)が300%なら債務に対して3倍の担保があることを意味します。