0 注意
このコードは趣味で書いたものであり、コードレビューを受けていません。ETHのメインネットで使用しないでください。
1 初めに
solidityというプログラミング言語で譲渡担保契約プログラムを書いてみました。この記事ではこのプログラムの使い方について説明します。プログラムの内容についても細かく説明します。
このプログラムは今後アップデートしていきます。この記事に無い内容は、追加の記事やTwitter(@gurunbox)を見てください(現在、2021年5月29日土曜日)。
技術的な部分を読み飛ばしたい方は☆マークの部分を読み飛ばしてください。契約の内容や契約締結前後の大まかな流れが知りたいだけであれば、3、4、5、6、7を見るだけで十分でしょう。
具体的なコードはgithubにあげています。
https://github.com/gurunbox-contracts/contracts/tree/main/corateral%20contract
また、実際の動きについての簡単な紹介動画はyoutubeにあげています。
https://www.youtube.com/watch?v=q_DuWHxUXtY
2 準備(☆)
まず、以下のものを用意します。
・債権者用のアドレス(metamaskなどを利用)
・債務者用のアドレス(同上)
・JPYCなどのステーブルコイン(☆ERC20規格によるものを利用。法定通貨と同じ経済的機能を持つものであればなんでも良いです。)
・担保にしたいトークン(☆ERC20規格によるものを利用)
erc20トークンの作り方は下記などを参照。
https://youtu.be/GDq7r1n9zIU
次にETHを入手して、債権者用のアドレス(metamask)と債務者用のアドレス(metamask)それぞれにETHを送ります。それぞれ0.2ETHずつあれば十分でしょう。ETHのメインネットでプログラムを動かすのであれば、実際にETHを購入してmetamaskに送る必要があります。しかし、ひとまずropstenテストネットでプログラムを動かすのであれば、以下のURLから無料でETHを入手できます。
https://faucet.ropsten.be/
次にRemix(ブラウザ版)を起動させます。
https://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.1+commit.df193b15.js
そして、metamaskをRemixに接続します。
Remix上では、まず、新しいファイルを作成します。
次に、本件の譲渡担保契約プログラムのコードを新しいファイルにコピペします。
そして、versionを0.4.26にしてコントラクトコードをcompileします。
metamaskの接続が有効になっているか(injected web3が有効か)確認してから、compileされたコントラクトコードをdeployします(この作業は後述の基本合意後でも構いません)。
(このコードは、コードレビューを受けていません。ETHのメインネットで使用しないでください。)
deployするときに、債権者アドレス、債務者アドレス、債務の履行のためのステーブルコインのコントラクトアドレス、担保トークンのコントラクトアドレス、弁済期、債務額を設定します。このとき、abiを確認します。
deployされたコントラクトをEtherscanで確認し、コントラクトコードを確認します。
Etherscanでコントラクトをverifyさせ、abiを確認します。
3 契約締結の前後
債権者が債務者に500万円を貸し付け、債務者が100TSLA(テスラ社の株式トークン)を担保に提供し、弁済期を1年後とする契約について考えていきます。
債権者や債務者が譲渡担保プログラムのコントラクトとやりとりしたいとき、metamaskをremixやEtherscanに接続してコントラクトとやりとりします。例えば、債権者が譲渡担保プログラムのコントラクトから担保を引き出したいとき、債権者自身のmetamaskをremixやEtherscanに接続してコントラクトから担保を引き出します。以下、当事者が譲渡担保プログラムのコントラクトとやりとりするとき、それぞれが所持しているmetamaskをremixやEtherscanに接続してプログラムを動かしているものとします。
☆ metamask以外のアドレスでもコントラクトとやりとりは可能です(もっとも、metamaskは使いやすいです)。また、remixやetherscan以外でもコントラクトアドレスとabiがあればコントラクトとやりとりできます。
① 基本合意の締結
まず、以下について債権者債務者間で基本合意をします。
・債権者が債務者に対して日本円500万円を貸し付ける。
・弁済期は1年後とする。
・債務の弁済方法はJPYCなどのステーブルコイン(日本円と1対1で交換できる仮想通貨のことです。以下「JPY」といいます。)を譲渡担保契約のコントラクトアドレスに送る方法による。(このアドレスとは、イーサリアムのブロックチェーンに書き込んだ譲渡担保契約のアドレスです。例えば、0xE17a386b194eD19559C22B68407BdE7156B0BebF
などのアドレスがあります。これはropstenのetherscanで確認できます。)
・債務者が100TSLAを譲渡担保に供する。
・譲渡担保に供する方法は、債務者がコントラクトアドレスに100TSLAを送り込む方法による。
・弁済期前に債務が履行されない場合、債権者が一方的に100TSLAを引き出すことができ、債権者が債務者に100TSLAを返却する必要がないものとする。
・担保の実行後100TSLAの評価額あるいは処分価額が500万円を超える場合、債権者は債務者に対して清算金を支払う。
・基本的にプログラムの内容に従う。
次に、債務者が、ブロックチェーンに書き込まれる譲渡担保契約上で、debtBalanceを5,000,000に設定します。
次に、債権者が、ブロックチェーンに書き込まれた譲渡担保契約上で、dueDateの設定として31536000(1年間を秒数に換算した数字)を入力します。
(②と③は、基本合意前、deploy時にやるのでも構いません。)
・500万円の消費貸借契約
・100TSLAを担保とする譲渡担保契約
・その他基本合意に従う旨合意することについての契約
⑤ 債権者は、債務者に対して、日本円500万円を引き渡します(消費貸借契約の金銭授受に相当する行為です。)。
⑥ 債務者は、譲渡担保契約のコントラクトアドレスに対して、100TSLA送ります(担保の提供に相当する行為です。)。
4 担保提供から契約終了後までの流れ(適切に債務が履行される場合)
① 債務者は、日本円500万円を500万JPY(JPYとは、日本円と1対1で交換できる仮想通貨であると考えてください。具体例としてJPYCなどがあります。)に交換します。
② 債務者は、500万JPYを譲渡担保契約のコントラクトアドレスに送ります。(債務の履行にあたる行為です。技術的な問題点については後述。)
③ 債務者は、譲渡担保契約のコントラクトアドレスから、担保として提供した100TSLAを引き出します。(債務者が担保を引き出す条件は、債務者アドレスを利用すること及び500万JPYを譲渡担保契約のコントラクトアドレスに支払うことです。)
④ 債権者は、譲渡担保契約のコントラクトアドレスから、JPYを引き出します。(債権者は、JPYが譲渡担保契約のコントラクトアドレスに送られ次第、直ちにJPYを引き出すことができます。債権者がJPYを引き出す条件は、債権者アドレスを利用することです。)
弁済期前に500万JPYが譲渡担保契約のコントラクトアドレスに送られた場合、債権者は担保の100TSLAを引き出すことができません。
5 担保提供から契約終了後までの流れ(債務が履行されない場合)
弁済期が経過しても債務の履行として、債務者が譲渡担保契約のコントラクトアドレスにJPYを送られなかったとします。
① 債権者は、担保の実行として、譲渡担保契約のコントラクトアドレスから、100TSLAを引き出します。(債権者が100TSLAを引き出す条件は、債権者アドレスを利用すること、弁済期が経過していること、500万JPYが譲渡担保契約のコントラクトアドレスに送られていないことです。)
② 100TSLAの評価額が500万円を超えるのであれば、債権者は、債務者に対して、500万円を超える額の金銭を返還します(担保余剰の精算にあたる行為です)。
6 その他の機能の説明
・債権者が弁済期を伸ばす形で弁済期を変更できる機能があります。弁済期のリスケジュールが可能です。
・債務者が弁済期を早める形で弁済期を変更できる機能があります。期限の利益の放棄が可能です。債務者が破産した場合、債務者が任意で応じれば、弁済期を早めることによって破産手続を処理することが可能です。プログラムの問題によって精算処理が進まないという問題点を、この機能によって回避しようとしています。
・債権者が残債務額を減額できる機能があります。弁済期の放棄が可能です。また、この機能によって債権者債務者間の合意解除により債務者が担保を引き上げることが可能になっています。
・債務者が残債務額を増額できる機能があります。追加融資に対応しています。
・第三者弁済は可能です。債権者以外の者がJPYを送ればいいだけです。
7 担保の法的性質
債務者は債権者にTSLAを送るわけではなく、譲渡担保契約のコントラクトアドレスにTSLAを送ります。そういう意味では、この契約は「譲渡」担保を前提にしているわけではないと言えます。もっとも、そもそも譲渡担保の法的性質として、担保目的物が債権者に帰属しているのか債務者に帰属しているのか見解の一致があるわけではありません。そういう意味では法的主体ではないプログラムコードの中に担保のTSLAを格納させることは、TSLAを債権者のものでも債務者のものでもない状態にして、譲渡担保に近づけるものであると言えるかもしれません。
また、そもそも暗号資産は単なる情報に過ぎず、所有権を観念することができません。そのため、暗号資産であるTSLAについて譲渡担保の法的性質を論じることが自体がナンセンスかもしれません。もっとも、暗号資産の担保は今後ますます発展していくと考えられます(現段階においても、Defiの領域で暗号資産の担保取引は活発です。)。そのため、暗号資産の担保の性質について議論することについては意義はあると考えられます。
8 各種機能の説明(☆)
(1) 変数
creditor:債権者のアドレス
debtor:債務者のアドレス
debtBalance:残債務額
dueDate:弁済期
(2) 関数など
constructor
本件では、creditor、debtor、debtBalance、弁済期としての秒数、担保とするトークン(TSLA)のコントラクトアドレス、ステーブルコイン(JPY)のコントラクトアドレスをdeploy時に入力します。
function getDebtBalance
debtBalanceを確認するための関数です。
function getDueDate
dueDateを確認するための関数です。
function ChangeDebtBalanceByD
debtBalanceを設定するための関数です。入力されたuint型の引数がdebtBalanceになります。
この関数はdebtorのみが呼び出せます。
呼び出す直前のdebtBalanceより大きい値であれば、いつでもdebtBalanceを再設定できます。
追加融資に対応するために実装してあります。
function changeDebtBalanceByC
debtBalanceを変更するための関数です。入力されたuint型の引数がdebtBalanceになります。
この関数はcreditorのみが呼び出せます。
呼び出す直前のdebtBalanceより小さい値を入力するのであれば、いつでもdebtBalanceを設定できます。
債権放棄に対応するために実装しています。また、この機能によって、債権者債務者間の合意によって契約を解除して担保を引き上げる場合にも対応できます。
function returnLoanTokenByC
債権者が譲渡担保のコントラクトアドレス内のJPYを引き出すための関数です。
入力されたaddress型の引数がJPYの送り先となり、入力されたuint型の引数(_amount)がJPYの引出量になります。
この関数はcreditorのみ呼び出せます。
この関数によってJPYを引き出した分だけdebtBalanceが減少します。
debtBalanceと_amountの引き算はsafemathをlibraryから使用しています。オーバーフロー対策です。これにより、債権者は、debtBalanceを上回る量のJPYを引き出せなくなっています。
入力されたuint型の引数に10^17をかけているのは、ERC20トークンのdecimalsがだいたい18に設定されており、10^17をいちいちかけないとスムーズに引数の入力ができないからです。10^17をかけるよりも良い方法があるかもしれません。
function returnCollateralByD
債務者が譲渡担保のコントラクトアドレス内のTSLAを引き出すための関数です。
入力されたaddress型の引数がTSLAの送り先となり、入力されたuint型の引数が TSLAの引出量になります。
この関数はdebtorのみ呼び出せます。
debtBalanceの値が譲渡担保のコントラクトアドレス内のJPYの数量以下である場合のみ、債務者は担保を引き出せます。
function setDueDateByC
債権者が弁済期を変更するための関数です。
現在設定されている弁済期を先に伸ばす形でしか弁済期を変更できません。
入力されたuint型の引数とnow(1970年1月1日0時0分0秒から、関数を呼び出した時点までにかかった秒数)を足した値が、弁済期を表す値になります。例えば、弁済期を1年後に設定したいのであれば、31536000(=606024*365)を引数として入力することになります。
この関数はcreditorのみ呼び出せます。
弁済期のリスケジュールを実装するための機能です。
☆ nowはデフォルトでuint256なので32ビットunixによる2038年問題は生じません。たしかに、契約の性質上32ビットにしても良いように思えますが、struct構造にしない限りsolidityのuint型は基本的に256ビットになってしまうため、32ビットにしてもガス代は節約されず、あえて32ビットに変換することはお勧めできません。
function setDueDateByD
債務者が弁済期を変更するための関数です。
現在設定されている弁済期を早める形でしか弁済期を変更できません。
入力されたuint型の引数とnow(1970年1月1日0時0分0秒から、関数を呼び出した時点までにかかった秒数)を足した値が、弁済期を表す値になります。
この関数はcreditorのみ呼び出せます。
債務者が破産した場合、債務者が任意に応じるのであれば、弁済期を早めて破産手続などの生産手続を円滑に処理することが可能です。
function executeCollateralByC
債権者が譲渡担保のコントラクトアドレス内のTSLAを担保の実行として引き出すための関数です。
入力されたaddress型の引数がTSLAの送り先となり、入力されたuint型の引数が TSLAの引出量になります。
この関数はcreditorのみ呼び出せます。
debtBalanceの値が譲渡担保のコントラクトアドレス内のJPYの数量より多い場合のみ、債権者はTSLAを引き出せます。また、弁済期が経過している場合のみ債権者はTSLAを引き出せます。
9 コードの問題点(☆)
現状、債務の履行があったか否かの判断は、譲渡担保のコントラクトアドレス内のJPY残高に依存しています。このままだと、誰でも譲渡担保のコントラクトアドレス内にJPYを送ることができ、担保の実行を妨害することが考えられます。
これは、債務の履行があったかの判断を債務者がJPYを送ったか否かで判断すれば解決できます。もっとも、これを実現するためにはJPYのapprove関数を債務者側で実行し、譲渡担保のコントラクトアドレス内でJPYのtransferFrom関数を実行させて債務の履行額を譲渡担保のコントラクトに記録するような関数を同コントラクトに書き込む必要があります。また、担保の引き出しに関する関数において、担保の移動に関する条件を、この書き込まれた履行の記録と紐づける必要があります。
このコードを書くこと自体は簡単ですが、初学者向けの今回のコントラクトを複雑化させることを防止するため、問題提起を指摘しておくに留めておきます。