自己紹介
こんにちは、ばくちーと申します。
Ethereumを中心にバウンティによく参加してます。 バウンティに取り組んでいる過程でAaveを使ったときに気づいたことでも書いてみようかなと思いました。
この記事ではAaveV1とV2のフラッシュローンの実装の違いを掘り下げ、V2が優れている点を説明します。「フラッシュローンに違いがあるの?」と疑問に思ってくれる方には興味を持っていただけるはずです! 内容はたぶんニッチだと思います!
フラッシュローンって?
まず、フラッシュローンを簡単におさらいしておこうと思います。フラッシュローンは面白いので、好きな方も少なくないでしょう。知っている方はここから読み進められます。
フラッシュローンは、トランザクション終了時に返済さえすれば無担保でトークンを好きなだけ借りることができるローンです。
つまり、流動性があれば一億円相当のトークンも無担保で借りることが可能です、ただしトランザクションの最後に返済する必要があり、さもなれけばトランザクションは失敗します。
簡単な利用例を挙げるなら、アービトラージでしょうか。大量のトークンを借りてDEX間の鞘を抜き取ります。もしくは、借りた大量のトークンを使って、市場に歪みを作りそこから利益を上げるなどができます。その他の利用例は次の章で説明します。
Aaveはフラッシュローンを機能の一つとして提供してます。(他のDeFiでもフラッシュローンを提供しているところはあります)
AAVEって?
おそらく読者の方はほとんどの方がご存知かと思いますが一言で言うと、Aaveはトークンのレンディングプロトコルです。つまり、
トークンを預けて金利収入を得たり、預けたトークンを担保に借り入れを行うことがノンカストディアルでできます。 例えば、Aaveではフラッシュローンをうまく使うことで、担保トークンを別の種類のトークンに変えることを、負債を返済せずに1回のトランザクションのみでできます。
本題
いよいよ本題です。V1とV2ではフラッシュローンの実装が違います。
結論から言うと、返済時のチェックの条件が異なります。 V2の実装では面白いことができるようになります。
V1のフラッシュローン
Aaveの設計ではフラッシュローンのエントリーポイントはトークンに関わらずLendingPool
コントラクトです。これはV2でも変わりません。
以下はV1のLendlingPool.solのflashLoan関数を簡略化したコードです。
_receiver
は_amount
量の借りるトークン_reserve
の受け取りアドレスで、コールバックexecuteOperation()
を受け取るためにIFlashLoanReceiver
インターフェースを実装している必要があります。
次に、require(...)
のところで借りたトークン+feeがきちんと返済されたかをチェックしています。 これがV1の返済時のチェックの条件です。
つまり、V1ではフラッシュローンの前後で借りたトークン(例えば、USDC)の量がLendingPool
内で不変でなければならないです。
function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params)
public
nonReentrant
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
...
//get the FlashLoanReceiver instance
IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver);
//transfer funds to the receiver
core.transferToUser(_reserve, userPayable, _amount);
//execute action of the receiver
receiver.executeOperation(_reserve, _amount, amountFee, _params);
//check that the actual balance of the core contract includes the returned amount
uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress()
? address(core).balance
: IERC20(_reserve).balanceOf(address(core));
// ここで、借りたトークン+feeが返済されたかをチェック
require(
availableLiquidityAfter == availableLiquidityBefore.add(amountFee),
"The actual balance of the protocol is inconsistent"
);
...
}
V2のフラッシュローン
以下はV2のLendlingPool.solのflashLoan関数を簡略化したコードです。
実際、違いはかなり多いのですが今回では一点に絞ってお伝えします。
基本的にはV1と同じフラッシュローンのプロセスを踏みますが、V1ではrequire(...)
で借りたトークン+feeがきちんと返済されたかをチェックするのに対して、V2ではそれに相当するrequire(...)
はありません。代わりに、safeTransferFrom()
でトークンを借りたアドレスから、返済すべき量(借りた量+fee)のトークンを回収します。
こうすることで、V2ではV1と違ってフラッシュローンの前後で借りたトークン(例えば、USDC)の量がLendingPool
内で不変である必要はなくなります。
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external override whenNotPaused {
FlashLoanLocalVars memory vars;
address[] memory aTokenAddresses = new address[](assets.length);
uint256[] memory premiums = new uint256[](assets.length);
vars.receiver = IFlashLoanReceiver(receiverAddress);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
premiums[vars.i] = amounts[vars.i].mul(_flashLoanPremiumTotal).div(10000);
IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
}
require(
vars.receiver.executeOperation(assets, amounts, premiums, msg.sender, params),
Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN
);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i];
vars.currentPremium = premiums[vars.i];
vars.currentATokenAddress = aTokenAddresses[vars.i];
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium);
IERC20(vars.currentAsset).safeTransferFrom(
receiverAddress,
vars.currentATokenAddress,
vars.currentAmountPlusPremium
);
} else {
...
}
...
}
}
結論:何が嬉しいのか?
V1ではフラッシュローンの前後で借りたトークン(例えば、USDC)の量が不変でなければならないが、V2ではそうではなくて借りた量+feeさえ返せばいいことを知りました
では、V2では何が嬉しいのか
*V1の実装では、フラッシュローンのコールバック関数内でaToken
を償還することができません。しかし、V2では借りた量さえ返せばいいので、ここの章で述べたようなAave内でCollateral SwapやCono financeのような仕組みのローン・プロテクションが可能になります!
*Cono Financeのローンプロテクションは、借り入れしているポジションが指定のヘルスファクターを下回った際に、Gelatoにより自動で担保を崩すことで一部ローンを返済し、清算を回避するDeFiです。Gelatoはインセンティブが与えられており、条件を満たした際、代わりにスマートコントラクトで定めたタスクを実行してくれるボットみたいなものです。
Cono Financeのローン・プロテクション
## おわりに
今回は、AaveのフラッシュローンについてV1とV2の実装の違いを理解し、V2ではフラッシュローンのコールバック関数内で、aToken
を償還できることを理解しました。それによって、ローン・プロテクションのようなことができるようになり、フラッシュローンの利用の幅が広がったと言えます。
おそらくニッチな内容でしたが、この発見に興味を持っていただけると嬉しいです。