秒速で1億借りて返す ~Aave Flash Loan in Ropsten~
※Aave V2が既にローンチされています。FlashLoanに変更あり。
dydxのFlashloansを活用して 1,271 ETH、約 $355,880 をぶち抜いたフラッシュローン事件以来、dydx/aave/UniswapなどフラッシュローンをサポートするDeFiプロトコルがさらに注目されているように感じます。2020年はDeFi元年と言われていますがEthereum上のDeFiエコシステムが成熟し、さらに新しいDeFiプロトコルや上のレイヤーのアプリケーションが拡充してくる時期でしょう。この記事では2/15日のdydxフラッシュローン事件の振り返りと、aaveのFlash Loan機能をRopstenテストネットワークでお試ししてみた内容となります。Solidityユーザの方はgithubにリポジトリを作ったので、そちら直にみて頂いたほうが早いと思います。またこの記事では一般的な意味でのフラッシュローンを日本語でフラッシュローンと記載し、個別のプロトコル実装の同機能を英語で表現しています。aaveであればFlash Loan、dydxであればFlashloans、UniswapであればFlashswapと表記します。
目次です。
- フラッシュローンとは
- dydx フラッシュローン事件
- aave/dydx/Uniswapでの相違点
- RopstenでFlash Loanするテスト
フラッシュローンとは
通常の金融世界のローンとはお金などの貸し付けのことであり、ユーザは担保を元にお金を借り受けることができます。その返済には利子が伴うことがあります。流動性選好の観点とお金の貯蔵性からマネーを借りる場合には、利子が発生することは避けられません(減価する貨幣などお金の機能を変化させているものは除く)。フラッシュローンとは担保なしで対象資産(暗号資産やトークンなど)を借り受け、その債務の処理と返済を同じ取引(Ethereum上の1つのトランザクション)内で解消することができるDeFiプロトコルの機能のことです。このとき利子は発生せず、手数料が発生する場合があります。この記事ではaaveのFlash Loanを取り扱います。実装は各DeFiプロトコルによって異なるため各プロトコルのドキュメントを確認する必要があります。[1]
Flash Loans are special uncollateralised loans that allow the borrowing of an asset, as long as the borrowed amount (and a fee) is returned before the end of the transaction. There is no real world analogy to Flash Loans.
フラッシュローンは担保なしでマネーを融通することができる点、またトランザクション内で借りたマネーの操作や返済を一挙に行える点が特徴で、以下のような処理・アプリケーションへの応用が考えられています。アビトラはまさにdydxのフラッシュローン事件で応用されており、当該事件では故意に価格差を発生させることでアビトラを成功させシングルトランザクションで裁定解消まで完結しています。
- Arbitrage(アービトラージ)
- Refinance loan(ローンの借り換え)
- Swap collaterals(担保のスワップ)
フラッシュローンはシングルトランザクション内でアビトラの処理や返済(aaveは手数料も支払う必要あり)ができなければ処理はリバートされるため、もし貸し借りが成立したらどうなるだろうという as-if 的世界に見えます。このような取引は現実の取引所や金融資産では行えなかったので『There is no real world analogy to Flash Loans』という説明は非常に的を得ています。フラッシュローンのユースケースの1つであるアービトラージを例に取ってみましょう。ある商品の価格差(同一商品の先物と現物の価格や複数取引所間の価格など複数パターンあり)を認識して裁定取引のポジションを建てた場合、1) 裁定取引の解消に伴うリスク、2) 手数料などで利益が消滅するリスクが存在します。アービトラージを成功させるには複数の取引が必要でその複数の取引に関連する様々な不確実性が存在しています。端的にいうとそれら複数の取引を1つの取引としてDeFiのエコシステムで実行できることがフラッシュローンのメリットだと考えます。さらにフラッシュローンでは担保をデポジットする必要がありません。アトミックなトランザクション内で返済ができれば1億でも10億でも借りられるため、大きな金額をレバレッジすることが可能になります。特に流動性が価格へ大きな影響を持つ暗号資産の場合には、フラッシュローンで大きな金額を借りることが取引に有利に働くことも多いかと思います。
この記事で取り上げているのはフラッシュローンの機能を使用したアプリケーションの中でもArbitrage(アービトラージ)に注目した活用法ですが、フラッシュローン自体は上記のローンの借り換えや担保のスワップなど様々なアプリケーションに応用ができると考えられています。
dydx フラッシュローン事件
2/15日に発生したbZx、dydx、Fulcrum、Compound、Uniswapのプロトコルを使用したフラッシュローン事件ですが、基本的にはアービトラージ取引とbZxのコントラクトバグを突いた複合的なハックとなっています。アービトラージ取引部分は綿密に計算された正当なフラッシュローンのコントラクトではありますが、そこにbZxのスマートコントラクトのバグを噛ましているところが味噌になっています。対象のbZxプロトコルのバグは事件後すぐにフィックスされました。bZxのバグはマージン取引で借りたWBTCの価格が高騰していたにも関わらずETHの担保不足でポジション清算がされないようになっていたことです。
詳細な解説は以下のサイトに掲載されているのですが、流れだけ箇条書きで記載しておきます。フラッシュローンのやり取りをシングルトランザクションで実行できているために各取引における不確実性のリスクが解消されています。各取引が想定通りでフラッシュローンを返済できる場合にだけ、最後まで裁定解消を確定できることになります。[2]
- dydx から 10,000 ETHを借りる(Flashloan で借りています)
- 5,500 ETH を Compound にデポジットして 112 WBTC を借りる
- 1,300 ETH をデポジットして bZx のマージン取引を執行
- 1,300 ETH を担保に借りた 5637.623762 ETH を KyberSwap で 51.345576 WBTC と交換
- KyberSwap によって交換レートが 1 WBTC / 109.8 WETH へと上昇し Uniswap での WBTC が高騰
- Compound で借りていた 112 WBTC を Uniswap 上で売却し 6871.4 ETH を得る(このときの Uniswap でのレートは 1 WBTC / 61.4 WETH)
- 売却で得た 6871.4 ETH と残っていた 3,200 ETH から dydx の Flashloan で借りていた 10,000 ETH を返済して 71 ETH の裁定利益を得る
- Compound の債務解消を実行し 1,200 ETH を得る(このときの交換レートは 1 WBTC / 38.5 WETH で 5,500 ETH から 4,300 ETH を減算)
- アビトラで得た 71 ETH と 1,200 ETH を合わせて 1,271 ETHの利益
etherscanの履歴を確認するとこのトランザクションには$7.19ほどしかかかっていないことが分かります。dydxはフラッシュローン自体に手数料がかからないため純粋にトランザクション手数料のみで実行されています。
aave/dydx/Uniswapの相違点
フラッシュローンは現在3つのプロトコルaave/dydx/Uniswapで利用することが可能です。比較が以下のサイトに掲載されているので簡単に記載しておきます。[4]
- aave Flash Loan
Pros: 扱える資産の種類が豊富、WETHでなく生のETHを直接扱える、Truffle boxにフラッシュローンのテンプレートが用意されている
Cons: 0.09%の手数料がかかる - dydx Flashloans
Pros: メタトランザクションでフラッシュローンを実現するため手数料がかからない、またdydx上のアービトラージにその債務を使用できる
Cons: 扱える資産の種類が限られる、ETHを直に扱えずWETHしか使用できない、メタトランザクションの構築が初心者には複雑 - Uniswap Flashswap
Pros: swapで支払いより先にトークンを受け取ることができる、WETHでなく生のETHを直接扱える、Uniswap上の取引にそのまま使用できる
Cons: 0.3%の手数料がかかる、但しこれは通常のUniswapの手数料と全く同じ
dydxは上級者向けのような気がしました。今回aaveのテストもしてみて分かりましたが、aaveはドキュメントも平易でトランザクションやフラッシュローンに手数料がかかることを考慮しても全体的なコストは安くつくと思います。学習コストはほとんどかかりません。
RoptenでFlash Loanするテスト
具体的にaaveのプラットフォーム上でFlash Loanをやってみたいと思います(今回はRopstenのテストネットワークで実施)。いくらでも担保なしでローンできることがメリットなので思い切って1億分のETHをRopstenで借りてみます。1 ETH = 約 25,000 円として計算すると1億は4000 ETHです。このテストではRopstenのテストネットで4000 ETHをフラッシュローンで借りて、アビトラなどは行わずそのまま4000 ETHを返済することとします。手数料は0.09%なので3.6ETH(90000円ですね)を上乗せしてレンディングプールへ返す必要があります。1億円の流動性を得て、90000円の手数料が安いのかよく分かりませんが、アビトラなどで0.09%より利が出ていれば清算時にはプラスになる計算です。[5]
※申し訳ございません、実際にはRopstenのレンディングプールに4000 ETHの流動性がなく、少額でテストしています。メインネットはapp.aave.comで流動性が分かりますがテストネットはどうやって確認するか分かりませんでした、app.aave.comはメインネットのウォレットでないと接続できないため
公式のドキュメントではプロジェクトでaaveを使用する方法、Remixでコントラクトを書く方法とTruffleのテンプレートで実行する方法が紹介されています。 truffle unbox aave/flashloan-box
でエラーが発生しうまく行かなかったためローカルにaaveのファイルをベタ貼りしてテストしました。githubのレポはこちらです。truffle init
でプロジェクトを作成してcontracts配下にaaveというディレクトリを作成しました。このディレクトリ内にaave関連のスマートコントラクトを配置します。aaveとopen-zeppelinのgithubからファイルを拝借してください。Solidityのバージョンによって文法を多少変える必要があります。今回はTruffle v5.0.36、Solidity v0.5.8を使用しています。
$ truffle version
Truffle v5.0.36 (core: 5.0.36)
Solidity v0.5.8 (solc-js)
Node v13.7.0
Web3.js v1.2.1$ truffle init
$ ls -al
drwxrwxr-x 2 user user 4096 Jun 24 12:17 contracts
drwxrwxr-x 2 user user 4096 Jun 24 12:17 migrations
drwxrwxr-x 2 user user 4096 Jun 24 12:17 test
-rw-rw-r-- 1 user user 4235 Jun 24 12:17 truffle-config.js$ cd contracts
$ mkdir aave
# 必要なファイルを contracts/aave に配置してください
# 以下のようなかんじ$ ls -al
-rw-rw-r-- 1 user user 6072 Jun 24 21:09 Address.sol
-rw-rw-r-- 1 user user 407 Jun 24 13:33 EthAddressLib.sol
-rw-rw-r-- 1 user user 1608 Jun 24 21:10 FlashLoanReceiverBase.sol
-rw-rw-r-- 1 user user 2696 Jun 24 21:03 IERC20.sol
-rw-rw-r-- 1 user user 499 Jun 24 13:34 IFlashLoanReceiver.sol
-rw-rw-r-- 1 user user 1842 Jun 24 20:46 ILendingPoolAddressesProvider.sol
-rw-rw-r-- 1 user user 2521 Jun 24 12:26 ILendingPool.sol
-rw-rw-r-- 1 user user 3694 Jun 24 13:40 SafeERC20.sol
-rw-rw-r-- 1 user user 5201 Jun 24 13:36 SafeMath.sol
これで事前準備は完了です。
公式の説明を参考にaave Flash Loanがどのように動くのか見ていきましょう。[5]
まず作成するフラッシュローンのコントラクトがaaveのレンディングプールコントラクトを呼び出し、レンディングプールから指定した金額の暗号資産(トークンなど)を作成したフラッシュローンのコントラクトへ移してくれます。実際はどのコントラクトへ移動するかアドレスを address _receiver
として指定できますが、今回は address(this)
としてこのコントラクト自身のアドレスへ資産を移動しています。この処理は function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes calldata _params) external;
で呼び出せます。
借り受けた資産に対するアビトラなどの処理を行いレンディングプールへ返済する処理を executeOperation
という関数で行います。 IFlashLoanReceiver.sol
でインターフェースが定義されます。具体的には function executeOperation(address _reserve, uint256 amount, uint256 _fee, bytes calldata _params) external;
です。各々処理を行った後でレンディングプールへ資産を返却しますが、レンディングプールのコントラクト側で貸した金額と返した金額が等しく、また手数料分が追加されていることが確認された場合にシングルトランザクションとして処理されブロックへ取り込まれます。この executeOperation
は前述の flashloan
を呼び出した後に呼び出され実行されます。したがって借り受けから返済まですべてをシングルトランザクションに内包させることができるようになっています。
今回はETHでのフラッシュローンを行うため問題になりませんが、手数料は借り受けたトークンでないと払えないようです。つまりDAIをフラッシュローンで借りた場合そのDAIを返却するには借りたDAIと同額以上のDAIが対象のフラッシュローンコントラクト上に存在しないといけないことになります。ETH以外で大きな額を借りる場合には注意が必要ですね。
基本的にはこの2ステップのみです。次にInfuraを使ってコントラクトをRopstenネットワークにデプロイしてみます。
デプロイ時にレンディングプールのアドレスをセットします。レンディングプールのアドレスですが、公式ページに紹介されているアドレスをコピペして持ってきました。アドレスはプロトコル側で変わる可能性があるため静的に埋め込まず関数などで変更できるようにしておいた方が無難でしょう。今回はテストなのでベタ書きしました。またメインネットと各テストネットでアドレスが異なりますので間違えないようにします。RopstenのLendingPoolAddressesProviderは0x1c8から始まるものです。
truffle compile
でコントラクトをコンパイルしてRopstenネットワークへデプロイします。無事デプロイできますた。注意点ですがフラッシュローンのコントラクトをサンプルのままデプロイすると、誰でもコントラクトを呼び出し可能な状態になります。もしメインネットでアビトラ等に活用する際は onlyOwner
などを実装して自身のウォレットからしか呼び出せないようにする等した方が良いです。その他フラッシュローンを活用したアプリを開発する場合にもセキュリティの設計は安全に行い、必要に応じてコード監査を行う必要があるでしょう。
$ truffle compile
$ truffle migrate --network ropsten
スマートコントラクトのオブジェクトを取得して実装されている関数を確認してみます。 addressesProvider()
flashloan(address,uint256)
executeOperation(address,uint256,uint256,bytes)
が確認できました。
truffle(ropsten)> let flashloan; Flashloan.deployed().then((ret) => { flashloan = ret; })
truffle(ropsten)> flashloan
ここからがパーリィータイムです。実際にETHを借りてみます。予定していたのは4000ETHでメインネット価格で約1億円です。 flashloan
を呼び出してトランザクションを発行します。ETHの場合はリザーブにメインネットでもテストネットでもモックアドレスである0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeEを指定できます。非常に簡単ですね。
truffle(ropsten)> flashloan.address
'0x6512d73a1e365C9c49bb958a8cd720Ec7d3D9A40'truffle(ropsten)> web3.utils.toWei("4000", "ether")
'4000000000000000000000'truffle(ropsten)> let result = await flashloan.flashloan("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", web3.utils.toWei("4000", "ether"))
以下の通りで4000 ETHを試しましたが、レンディングプールに流動性が不足しているため借り受けできませんでした。メインネットはapp.aave.comへ接続すると流動性が分かりますがテストネットはどうやって確認するか分かりませんでした。app.aave.comはメインネットのウォレットでないと接続できないようです。この点は調査が必要ですね。
というわけで10 ETHでやってみたところうまくいきました。トランザクション成功後のスマートコントラクトのバランスを確認すると、0.091 ETHありました。0.09%の手数料を支払うために事前にコントラクトのアドレスへ0.1 ETH移動しておきました。 0.1–0.009=0.091
で手数料を徴収されたあとの正しい残高があることが確認できます。 flashloan
を呼び出し、シングルトランザクションで借り受けから返済まで自動的に行ってくれています。
truffle(ropsten)> let result = await flashloan.flashloan("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", web3.utils.toWei("10", "ether"))truffle(ropsten)> web3.eth.getBalance(flashloan.address, (error, balance) => { console.log(web3.utils.fromWei(balance, 'ether')); })
0.091
テストは以上です。今回はアビトラなどの処理を何も行わず返済しましたが、aaveを使用してアビトラしている実例がたくさんあるので次回は何かしらの処理を行う例をメインネットで試してみたいと思います。またRopstenでは流動性が足りませんでしたが、メインネットであればそれなりに流動性はありそうです。DeFiのマーケットやドミナンスは暗号資産世界内で大きくなっているので、ユーザが増えるにしたがって様々なユースケースが出現すると考えられます。
githubのリポジトリは以下です。[6]
記事を作成するにあたり参考にしたYouTubeビデオです。
Borrow tokens with Aave FLASHLOANS — Solidity tutorial
このビデオだけでaaveの基本的な理解とサンプルのフラッシュローンのコントラクトの作成が可能だと思います。フラッシュローンが分かりやすく説明されています。
Intro to Aave | Decentralized Lending & Borrowing Protocol on Ethereum
aaveプロトコルとaaveの使い方の説明です。Loan-to-ValueやLiquidationの説明もあってフラッシュローンだけでなくaave全体を俯瞰するには最適なビデオです。app.aave.comを使ってみたい人はこのビデオを見るのが最速ですね。
ETHLondonUK: Code your first flash loan with Aave fam, with Emilio Frangella & Ernesto Boado
英語が訛りすぎていて『ちょっと何言ってるか分からない』のですが興奮していることは伝わってきました。ETHLondonUKというワークショップ?でのフラッシュローンの紹介ビデオだと思います。ワークショップのコードがこちらにありました。ビデオは参考になりませんが、コードは参考にできると思います。