5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ERC・EIPAdvent Calendar 2023

Day 1

[ERC3156] Ethereumチェーン上でフラッシュローンを実装する仕組みを理解しよう!

Posted at

はじめに

初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

今回は、Ethereumブロックチェーン上でフラッシュローンを実装する仕組みを提案している規格であるERC3156についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なERCについてまとめています。

概要

このERCは、イーサリアムのブロックチェーン上で、特定の種類の取引、つまりフラッシュローンを行う時に必要なルールや手順を定めたものです。
フラッシュローンは、あるコントラクトから別のコントラクトへ一時的に資産を貸し出すシステムで、その資産はトランザクションが終わるまでに返却される必要があります。
これには少しの手数料が加わることもあります。

この仕組みにおいて、貸し手となるコントラクトは「レンダーコントラクト」と呼ばれ、借り手のコントラクトはトランザクションの間だけ資産を使うことができます。
ERCは、このような貸し借りがスムーズかつ安全に行われるための「インターフェース」(つまり、コントラクト同士がやり取りをするための共通の言語や手順)を定めています。

あなたが銀行から短時間だけお金を借りて、そのお金で何かをして、取引の終わりまでに元の金額+αを返すようなシステムです。
このERCは、その「借りる」「使う」「返す」というプロセス全体をイーサリアム上で安全に行うためのルールブックのようなものです。

動機

フラッシュローンは、担保を必要とせずにトランザクション内で完結する、特定の量のトークンを一時的に貸し出すスマートコントラクトのことです。
借りたトークンは同じ取引の中で返さなくてはならないというルールがあります。

このフラッシュローンの仕組みを最初に導入した人々は、多様なインターフェースや利用方法を考案しました。
これにより、様々なフラッシュローンの形式と統合するために必要な技術が複雑化し続けています。

これらのプロトコル間の違いには、以下のようなものがあります。

返済方法

トランザクションの最終段階での返済方法が異なります。
あるコントラクトは自動的に本元と手数料を引き出しますが、他のものでは借り手が自分で本元と手数料を返す必要があります。

返済通貨

元々借りたものと異なるトークンでローンを返済できる貸し手もいます。
これにより、フラッシュトランザクションの複雑さやガス料金を減らすことができます。

入り口の一貫性

一部の貸し手は購入、売却、預金、フラッシュローンなど、目的に関わらず一つの入り口を提供します。
他のプロトコルはそれぞれ異なる入り口を持っています。

フラッシュミント

一部の貸し手は、自社のネイティブトークンを任意の量、手数料なしでmintすることを許可し、資産所有ではなく計算上の制約によって限られたフラッシュローンを可能にします。

これらの特徴はフラッシュローンが進化し、様々な方法で利用されていることを示しており、各プロトコルが独自の特徴を持っていることを意味しています。

フラッシュローンはスマートコントラクトを使って、特定の条件下で一時的に資産を借りる方法です。
このシステムは多様で、適用するプロトコルによってその条件や特性が異なります。

仕様

フラッシュレンディング機能は、コールバックパターンを使って2つのスマートコントラクトを統合します。
これらをこのEIPではLENDERRECEIVERと呼びます。

LENDER

pragma solidity ^0.7.0 || ^0.8.0;
import "./IERC3156FlashBorrower.sol";


interface IERC3156FlashLender {

    /**
     * @dev The amount of currency available to be lent.
     * @param token The loan currency.
     * @return The amount of `token` that can be borrowed.
     */
    function maxFlashLoan(
        address token
    ) external view returns (uint256);

    /**
     * @dev The fee to be charged for a given loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function flashFee(
        address token,
        uint256 amount
    ) external view returns (uint256);

    /**
     * @dev Initiate a flash loan.
     * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool);
}

上記のコントラクトはイーサリアムのフラッシュローン機能を提供するためのスマートコントラクトのルールセットです。
このルールセットはIERC3156FlashLenderインターフェースと呼ばれ、フラッシュローンを実施するために貸し手が守るべき一連の機能と手順を定義しています。

以下は、このインターフェースに含まれる主要な関数と、それらがどのように機能するかの説明です。

maxFlashLoan

貸し出し可能なトークンの最大量を知るための関数です。
サポートされていないトークンには0を返します。
これは、「私たちはこのトークンをいくらまで貸し出せますか?」と聞いているようなものです。

flashFee

ローンにかかる手数料を計算します。
もしサポートされていないトークンであればエラーを出します。
これは、「このローンにはどれだけの手数料が必要ですか?」と問い合わせているようなものです。

flashLoan

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
) external returns (bool) {
  ...
  require(
      receiver.onFlashLoan(msg.sender, token, amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"),
      "IERC3156: Callback failed"
  );
  ...
}

実際にフラッシュローンを実行する関数です。
貸し手はこの関数を使って、特定の量のトークンを借り手に一時的に貸し出します。
この過程で、以下を保証する必要があります。

  • ローンを受けるreceiverにトークンを送る前に、onFlashLoanという確認用の機能(コールバック)を呼び出します。
  • このローンを開始した人(msg.sender)をonFlashLoanへの呼び出しに含めます。
  • トークン、量、追加データを変更せずにそのままonFlashLoanに渡します。
  • onFlashLoanにローンの手数料情報も渡し、借り手が正しい金額を返すことを確認します。
  • onFlashLoanから特定のハッシュ値が返されることを確認し、これによってローンが正しく完了したことを検証します。
  • 最後に、借り手から元のローンの金額+手数料を受け取ります。
    • これがうまくいかない場合は、エラーを出してローンを無効にします。

もし全てが計画通りに進めば、flashLoan関数はtrueを返し、成功したことを伝えます。
これらのステップは、フラッシュローンが確実かつ安全に行われるようにするために非常に重要です。

RECEIVER

pragma solidity ^0.7.0 || ^0.8.0;


interface IERC3156FlashBorrower {

    /**
     * @dev Receive a flash loan.
     * @param initiator The initiator of the loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param fee The additional amount of tokens to repay.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
     */
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32);
}

フラッシュローンを受ける人はIERC3156FlashBorrowerというインターフェースを使ったコントラクトを用意する必要があります。
このインターフェースは、フラッシュローンをうまく受け取り、処理するためのルールを決めるものです。

onFlashLoan

この関数はフラッシュローンを受けた時に自動で動きます。
この時、ローンを始めた人(initiator)、ローンの種類(token)、借りた量(amount)、返すべき追加料金(fee)、その他の情報(data)が渡されます。

この関数は、ローンをちゃんと返せたことを証明するために、特定のハッシュ値(ERC3156FlashBorrower.onFlashLoankeccak256ハッシュ)を返さなければなりません。

トランザクションが上手くいくためには、onFlashLoanが終わる前に、レシーバーはローンの元金(amount)と手数料(fee)を合わせた金額をmsg.sender(貸し手)が取り戻せるようにしておく必要があります。
これにより、貸し手は返済が保証されます。

もしonFlashLoanが正しく実行されて、適切なハッシュ値を返せれば、フラッシュローンのトランザクションは成功とされます。
これにより、フラッシュローンが迅速かつ確実に完了するようになります。

補足

このERCは、さまざまなフラッシュレンディングのシナリオをサポートし、同時に安全性とガス効率の良い実装を可能にするために設計されたインターフェースのセットを提供します。

  • flashFeeはサポートされていないトークンに対してはエラーを返します。
    • 数値を返すと誤解を招く可能性があるためです。
  • flashLoanという関数名は、その機能を直感的に理解しやすく、他のコントラクト関数と衝突しにくい名前です。
    • この関数は、貸し手がトークンを持っているか、または新たに発行するかに関わらず、フラッシュローンの要件を満たします。
  • receiverパラメータは、ローンの開始者と受け手が別々であることを可能にし、実装に柔軟性をもたらします。
  • 既存のフラッシュレンダーは通常、1つのコントラクトから複数のトークンタイプに対するフラッシュローンを提供します。
    • そのため、flashLoanonFlashLoanの両方にトークンパラメータを含めることで、実際に見られる機能と密接に一致します。
  • bytes calldata dataパラメータを含めることで、呼び出し側が受け手に任意の情報を渡せるようになりますが、これによってflashLoan標準の有用性に影響はありません。
  • onFlashLoanという関数名もまた十分に説明的であり、受け手コントラクト内の他の関数との衝突が起こりにくい名前です。
    • これはEIP667で使用される命名規則に従っています。
  • initiator(ローンの開始者)は通常、onFlashLoan関数内で必要とされますが、貸し手はこれをmsg.senderとして知っています。
    • dataパラメータにinitiatorを埋め込む方法も考えられますが、受け手がその正確さを確認する追加のメカニズムが必要になるため、お勧めできません。
  • amount(金額)もonFlashLoan関数で必要ですが、貸し手はこれをパラメータとして取ります。
    • dataパラメータにamountを埋め込む方法もありますが、これも受け手が正確さを確認するための追加のメカニズムが必要になります。
  • 手数料は通常、flashLoan関数内で計算され、受け手が返済のためにこれを知る必要があります。
    • 手数料をdataに追加する代わりにパラメータとして渡す方が単純で効果的です。
  • 元金と手数料はreceiverから引き出されます。
    • これにより、貸し手はフラッシュローンの期間中に他の機能(transferFromを使用)をロックすることなく実装できます。
    • 返済が貸し手に直接転送される別の実装も可能ですが、それには貸し手の他の機能もすべてtransferの代わりにtransferFromを使用するように設計する必要があります。
    • よりシンプルで普及している「引き出し」アーキテクチャを考慮して、この方法が選ばれました。

後方互換性

後方互換性の問題は確認されていません。

実装

Flash Borrowerの参考実装

pragma solidity ^0.8.0;

import "./interfaces/IERC20.sol";
import "./interfaces/IERC3156FlashBorrower.sol";
import "./interfaces/IERC3156FlashLender.sol";


contract FlashBorrower is IERC3156FlashBorrower {
    enum Action {NORMAL, OTHER}

    IERC3156FlashLender lender;

    constructor (
        IERC3156FlashLender lender_
    ) {
        lender = lender_;
    }

    /// @dev ERC-3156 Flash loan callback
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external override returns(bytes32) {
        require(
            msg.sender == address(lender),
            "FlashBorrower: Untrusted lender"
        );
        require(
            initiator == address(this),
            "FlashBorrower: Untrusted loan initiator"
        );
        (Action action) = abi.decode(data, (Action));
        if (action == Action.NORMAL) {
            // do one thing
        } else if (action == Action.OTHER) {
            // do another
        }
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    /// @dev Initiate a flash loan
    function flashBorrow(
        address token,
        uint256 amount
    ) public {
        bytes memory data = abi.encode(Action.NORMAL);
        uint256 _allowance = IERC20(token).allowance(address(this), address(lender));
        uint256 _fee = lender.flashFee(token, amount);
        uint256 _repayment = amount + _fee;
        IERC20(token).approve(address(lender), _allowance + _repayment);
        lender.flashLoan(this, token, amount, data);
    }
}

Action

enum Action {NORMAL, OTHER}

概要

特定の操作を分類するための列挙型。

詳細

この列挙型は、スマートコントラクト内で異なるアクションや状態を識別するために使用されます。
例えば、異なる関数の動作を切り替えるために使用することができます。
NORMALOTHERの2つの値を持ち、スマートコントラクトの動作を適切に調整するために使用されます。

パラメータ

  • NORMAL
    • 標準的な動作や状態を示します。
  • OTHER
    • 標準外または特別な動作や状態を示します。

IERC3156FlashLender

IERC3156FlashLender lender;

概要

フラッシュローンを提供するERC3156準拠のレンダーコントラクトへの参照。

詳細

この変数は、ERC3156インターフェースを実装したフラッシュレンダーコントラクトへの参照を保持します。
これを通じて、フラッシュローンのリクエストや、関連する機能の呼び出しなどが行われます。
スマートコントラクトがフラッシュローンを利用する時には、この変数を通してレンダーコントラクトとやり取りを行います。

パラメータ

  • lender
    • ERC3156準拠のレンダーコントラクトへの参照。
    • フラッシュローンの機能を提供します。

onFlashLoan

function onFlashLoan(
    address initiator,
    address token,
    uint256 amount,
    uint256 fee,
    bytes calldata data
) external override returns(bytes32)

概要

ERC3156フラッシュローンのコールバック関数。

詳細

この関数はフラッシュローンのリクエストが行われた時にレンダーコントラクトから呼び出されます。
検証を行い、指定されたアクションに応じた操作を実行します。
安全性を確保するために、呼び出し元が信頼できるレンダーであり、かつローンの開始者がこのコントラクト自身であることを確認します。
操作が成功した後、特定のハッシュ値を返して正常な終了を示します。

引数

  • initiator
    • フラッシュローンの開始者のアドレス。
  • token
    • フラッシュローンで使用されるトークンのアドレス。
  • amount
    • ローンの金額。
  • fee
    • ローンにかかる手数料。
  • data
    • 任意のデータ。
    • ここでは操作のタイプを示すAction値がエンコードされています。

戻り値

  • bytes32
    • 正常終了時に特定のハッシュ値を返します。

flashBorrow

function flashBorrow(
    address token,
    uint256 amount
) public

概要

フラッシュローンを開始する関数。

詳細

この関数は、指定されたトークンで指定された量のフラッシュローンをリクエストします。
ローンの手数料を計算し、トークンの承認を行い、最終的にレンダーコントラクトのflashLoan関数を呼び出してローンを開始します。
この関数は通常、ユーザーがフラッシュローンを利用したいときに呼び出されます。

引数

  • token
    • フラッシュローンで使用されるトークンのアドレス。
  • amount
    • ローンの金額。

戻り値

  • この関数は戻り値を持ちませんが、内部的にflashLoan関数を呼び出し、フラッシュローンのプロセスを開始します。

Flash Mintの参考実装

pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC3156FlashBorrower.sol";
import "../interfaces/IERC3156FlashLender.sol";


/**
 * @author Alberto Cuesta Cañada
 * @dev Extension of {ERC20} that allows flash minting.
 */
contract FlashMinter is ERC20, IERC3156FlashLender {

    bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
    uint256 public fee; //  1 == 0.01 %.

    /**
     * @param fee_ The percentage of the loan `amount` that needs to be repaid, in addition to `amount`.
     */
    constructor (
        string memory name,
        string memory symbol,
        uint256 fee_
    ) ERC20(name, symbol) {
        fee = fee_;
    }

    /**
     * @dev The amount of currency available to be lent.
     * @param token The loan currency.
     * @return The amount of `token` that can be borrowed.
     */
    function maxFlashLoan(
        address token
    ) external view override returns (uint256) {
        return type(uint256).max - totalSupply();
    }

    /**
     * @dev The fee to be charged for a given loan.
     * @param token The loan currency. Must match the address of this contract.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function flashFee(
        address token,
        uint256 amount
    ) external view override returns (uint256) {
        require(
            token == address(this),
            "FlashMinter: Unsupported currency"
        );
        return _flashFee(token, amount);
    }

    /**
     * @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the ERC3156 callback.
     * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
     * @param token The loan currency. Must match the address of this contract.
     * @param amount The amount of tokens lent.
     * @param data A data parameter to be passed on to the `receiver` for any custom use.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external override returns (bool){
        require(
            token == address(this),
            "FlashMinter: Unsupported currency"
        );
        uint256 fee = _flashFee(token, amount);
        _mint(address(receiver), amount);
        require(
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,
            "FlashMinter: Callback failed"
        );
        uint256 _allowance = allowance(address(receiver), address(this));
        require(
            _allowance >= (amount + fee),
            "FlashMinter: Repay not approved"
        );
        _approve(address(receiver), address(this), _allowance - (amount + fee));
        _burn(address(receiver), amount + fee);
        return true;
    }

    /**
     * @dev The fee to be charged for a given loan. Internal function with no checks.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function _flashFee(
        address token,
        uint256 amount
    ) internal view returns (uint256) {
        return amount * fee / 10000;
    }
}

CALLBACK_SUCCESS

bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

概要

フラッシュローンのコールバックが成功したことを示す定数。

詳細

この定数はERC3156FlashBorrower.onFlashLoanという文字列のkeccak256ハッシュ値を保持しており、フラッシュローンのコールバックが正常に完了した際に期待される戻り値です。
この値はフラッシュローンのプロセス中に検証され、コールバックが正しく実行されたことを確認するために使用されます。


fee

uint256 public fee; //  1 == 0.01 %.

概要

フラッシュローンに適用される手数料率を表す変数。

詳細

この変数は、フラッシュローンの手数料率を保持しており、通常百分率の形で表されます。
例えば、10.01%の手数料率を意味します。
フラッシュローンが実行される時、この手数料率がローン金額に適用され、借り手が支払うべき手数料の計算に使用されます。


maxFlashLoan

function maxFlashLoan(
    address token
) external view override returns (uint256)

概要

貸し出し可能な通貨の最大量を取得する関数。

詳細

この関数は、指定されたトークンの最大貸出可能量を返します。
貸出可能な最大量は、uint256の最大値からこのコントラクトの合計供給量を引いた値です。
この関数は、フラッシュローンをリクエストする前に利用可能な資金を知るために使用されます。

引数

  • token
    • 貸出通貨のアドレス。

戻り値

  • uint256
    • 指定されたトークンの最大貸出可能量。

flashFee

function flashFee(
    address token,
    uint256 amount
) external view override returns (uint256)

概要

特定のローンに対して請求される手数料を取得する関数。

詳細

この関数は、指定されたトークンと金額に対するローン手数料を計算して返します。
この手数料は、返済時に本元に上乗せされる金額です。
手数料は内部関数_flashFeeを呼び出して計算されます。
この関数は、ローンがこのコントラクトの通貨で行われているかどうかを検証します。

引数

  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。

戻り値

  • uint256
    • ローンに対して請求される手数料。

flashLoan

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
) external override returns (bool)

概要

フラッシュローンを実行する関数。

詳細

この関数は、指定された量のトークンをreceiverに貸し出し、ERC3156のコールバック後に元金と手数料を受け取ります。
ローンがこのコントラクトの通貨で行われているかどうかを検証し、レシーバーが正常にローンを処理したことを確認します。
レシーバーが十分な承認を与えていることを確認した後、ローンの元金と手数料を焼却します。

引数

  • receiver
    • トークンを受け取るコントラクト。IERC3156FlashBorrowerインターフェースを実装する必要があります。
  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。
  • data
    • receiverに渡される任意のデータ。

戻り値

  • bool
    • ローンが成功したかどうかを示すブール値。

_flashFee

function _flashFee(
    address token,
    uint256 amount
) internal view returns (uint256)

概要

指定されたローンに対する手数料を計算する内部関数。

詳細

この関数は、特定のトークンと金額に基づいて、ローンの手数料を計算します。
手数料はトークンの量に基づいた割合で計算され、このコントラクトのfee変数に依存します。
この関数は外部から直接アクセスできず、他の関数から呼び出される内部ユーティリティです。

引数

  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。

戻り値

  • uint256
    • 計算された手数料。

Flash Loanの参考実装

pragma solidity ^0.8.0;

import "../interfaces/IERC20.sol";
import "../interfaces/IERC3156FlashBorrower.sol";
import "../interfaces/IERC3156FlashLender.sol";


/**
 * @author Alberto Cuesta Cañada
 * @dev Extension of {ERC20} that allows flash lending.
 */
contract FlashLender is IERC3156FlashLender {

    bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
    mapping(address => bool) public supportedTokens;
    uint256 public fee; //  1 == 0.01 %.


    /**
     * @param supportedTokens_ Token contracts supported for flash lending.
     * @param fee_ The percentage of the loan `amount` that needs to be repaid, in addition to `amount`.
     */
    constructor(
        address[] memory supportedTokens_,
        uint256 fee_
    ) {
        for (uint256 i = 0; i < supportedTokens_.length; i++) {
            supportedTokens[supportedTokens_[i]] = true;
        }
        fee = fee_;
    }

    /**
     * @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the callback.
     * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param data A data parameter to be passed on to the `receiver` for any custom use.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external override returns(bool) {
        require(
            supportedTokens[token],
            "FlashLender: Unsupported currency"
        );
        uint256 fee = _flashFee(token, amount);
        require(
            IERC20(token).transfer(address(receiver), amount),
            "FlashLender: Transfer failed"
        );
        require(
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,
            "FlashLender: Callback failed"
        );
        require(
            IERC20(token).transferFrom(address(receiver), address(this), amount + fee),
            "FlashLender: Repay failed"
        );
        return true;
    }

    /**
     * @dev The fee to be charged for a given loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function flashFee(
        address token,
        uint256 amount
    ) external view override returns (uint256) {
        require(
            supportedTokens[token],
            "FlashLender: Unsupported currency"
        );
        return _flashFee(token, amount);
    }

    /**
     * @dev The fee to be charged for a given loan. Internal function with no checks.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function _flashFee(
        address token,
        uint256 amount
    ) internal view returns (uint256) {
        return amount * fee / 10000;
    }

    /**
     * @dev The amount of currency available to be lent.
     * @param token The loan currency.
     * @return The amount of `token` that can be borrowed.
     */
    function maxFlashLoan(
        address token
    ) external view override returns (uint256) {
        return supportedTokens[token] ? IERC20(token).balanceOf(address(this)) : 0;
    }
}

CALLBACK_SUCCESS

bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

概要

フラッシュローンのコールバックが成功したことを示す定数。

詳細

この定数は、フラッシュローンのコールバック関数ERC3156FlashBorrower.onFlashLoanが正常に完了した場合に期待される戻り値のkeccak256ハッシュ値を保持します。
この値はフラッシュローンプロセス中にコールバック関数の戻り値として検証され、期待される結果を返したことを確認するために使用されます。


supportedTokens

mapping(address => bool) public supportedTokens;

概要

サポートされているトークンを追跡するマッピング配列。

詳細

このマッピングは、特定のトークンアドレスがこのコントラクトによってサポートされているかどうかを追跡します。
トークンアドレスをキーとし、そのトークンがサポートされているかどうかを示すbool値が値として格納されます。
これにより、コントラクトはサポートされているトークンに対してのみ操作を許可し、他のトークンに対しては拒否することができます。

パラメータ

  • address
    • トークンのアドレス。
  • bool
    • そのトークンがサポートされているかどうかを示す真偽値。

fee

uint256 public fee; //  1 == 0.01 %.

概要

フラッシュローンに適用される手数料率を表す変数。

詳細

この変数はフラッシュローンの手数料率を保持し、通常は百分率で表されます。
ここでは、10.01%の手数料率を意味します。
フラッシュローンが実行されると、この手数料率がローン金額に適用され、借り手が支払うべき手数料の計算に使用されます。

パラメータ

  • fee
    • フラッシュローンの手数料率を表す数値。
    • 10.01%を意味します。

flashLoan

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
) external override returns(bool)

概要

指定された量のトークンを一時的に貸し出し、その後で元金と手数料を取り戻す関数。

詳細

この関数はフラッシュローンのプロセスを開始し、トークンをレシーバーに転送します。
その後、レシーバーはコールバック関数onFlashLoanを実行し、ローンと手数料を返済する必要があります。
この関数は、レシーバーがサポートされたトークンに対してフラッシュローンを行うことができるかどうかを検証し、トランザクションが成功したかどうかを返します。

引数

  • receiver
    • トークンを受け取るコントラクト。
    • IERC3156FlashBorrowerインターフェースを実装する必要があります。
  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。
  • data
    • レシーバーに渡される任意のデータ。

戻り値

  • bool
    • ローンが成功したかどうかを示すブール値。

flashFee

function flashFee(
    address token,
    uint256 amount
) external view override returns (uint256)

概要

特定のローンに対して請求される手数料を取得する関数。

詳細

この関数は、指定されたトークンと金額に対するローン手数料を計算して返します。
手数料は内部関数_flashFeeを呼び出して計算されます。
この関数は、ローンがサポートされている通貨で行われているかどうかを検証します。

引数

  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。

戻り値

  • uint256
    • ローンに対して請求される手数料。

_flashFee

function _flashFee(
    address token,
    uint256 amount
) internal view returns (uint256)

概要

指定されたローンに対する手数料を計算する内部関数。

詳細

この関数は、特定のトークンと金額に基づいて、ローンの手数料を計算します。
手数料はトークンの量に基づいた割合で計算され、このコントラクトのfee変数に依存します。
この関数は外部から直接アクセスできず、他の関数から呼び出される内部ユーティリティです。

引数

  • token
    • ローン通貨のアドレス。
  • amount
    • 貸し出されるトークンの量。

戻り値

  • uint256
    • 計算された手数料。

maxFlashLoan

function maxFlashLoan(
    address token
) external view override returns (uint256)

概要

貸し出し可能な通貨の最大量を取得する関数。

詳細

この関数は、指定されたトークンの最大貸出可能量を返します。
最大貸出可能量は、サポートされているトークンの場合、そのコントラクトが保持するトークンの残高と等しくなります。
トークンがサポートされていない場合は、0を返します。

引数

  • token
    • 貸出通貨のアドレス。

戻り値

  • uint256
    • 指定されたトークンの最大貸出可能量。

セキュリティ

コールバック引数の検証

onFlashLoan関数の引数はフラッシュローンの条件を表していますが、これらの情報は必ずしも真実とは限りません。
引数の真偽を確認するためには、以下のような検証が必要です。

引数の検証

  • initiator(ローン開始者)、token(トークン)、amount(金額)
    • これらは過去のトランザクションを参照していますが、onFlashLoanを呼び出す人が偽情報を提供している可能性があるため、信頼できないかもしれません。
  • fee(手数料)
    • 誤っているか、間違って計算される可能性があります。
  • data(データ)
    • 呼び出し者によって操作される可能性があります。

信頼性の確保

initiatortokenamountfeeの値が正しいと確信するためには、onFlashLoanの呼び出し者が信頼できるフラッシュレンダーのリストに載っているかを確認する必要があります。
多くの場合、flashLoanの呼び出し者はonFlashLoanのコールバックも受け取るので、これは比較的簡単です。
それ以外の場合は、フラッシュレンダーを事前に承認しておく必要があります。
dataの値が正しいと確信するためには、上記のチェックに加えて、initiatorが信頼できるアドレスのグループに属していることを確認することが推奨されます。

これらの検証ステップはフラッシュローンが安全に、かつ正確に行われるために重要です。
検証されていない情報に基づいて行動すると、誤った結果や悪意のある攻撃に繋がる可能性があります。
フラッシュローンを使用する時は、これらの点を十分に理解し適切な検証を行うことが重要です。

フラッシュレンディングのセキュリティ

フラッシュレンディングの安全性を確保するためには、以下のような対策が重要です。

承認の自動化

フラッシュローンを実行する前に、借りる金額と手数料の合計について承認を行うのが最も安全です。
これにより、不正な取引を防ぐことができます。

信頼できる開始者の確認

レンダーコントラクトに対する承認を保持しているレシーバーは、onFlashLoan内でローンの開始者が信頼できるかどうかを確認するメカニズムを持つべきです。
これにより、信頼できない開始者による不正行為を防ぐことができます。

不注意なコントラクトやEOAの保護

もしコントラクトやEOAが、ERC3156を実装したレンダーコントラクトを承認しており、その承認を直ちに使わなかった場合、そしてレンダーコントラクトがonFlashLoanの戻り値を適切に確認しない場合、悪意のある第三者がflashLoanを呼び出して、コントラクトやEOAの資金を許可された額や残高の限界まで引き出す可能性があります。
このリスクを避けるために、レンダーコントラクトはonFlashLoanERC3156FlashBorrower.onFlashLoankeccak256ハッシュを返さない場合には処理を中止するようにするべきです。

これらの対策は、フラッシュローンの安全性を確保し、資金の不正利用や損失のリスクを減らすために非常に重要です。
適切な承認と検証メカニズムの実装により、フラッシュローンを安心して使用することができます。

フラッシュMintの外部セキュリティへの配慮

フラッシュミンティングは、非常に大きな量のトークンを一時的に生成することを可能にする取引で、これによって新しい種類の攻撃が可能になることがあります。

金利攻撃の例

安定した金利を提供する貸出プロトコルがあるとします。
このプロトコルに金利の最低値や最高値の制限がなく、フラッシュミントによる資金の流入が金利にすぐに反映されない場合、以下のような攻撃が可能です。

  1. まず、膨大な量のSTABトークンをフラッシュミントし、それをETHと共に預け入れます。
  2. この大きな預金が金利を極端に低く押し下げることにより、ほぼ無利息でSTABを借りることができます。
  3. 借りたSTABを使って元のフラッシュミントを閉じることができ、結果としてほとんど利息のかからない大きなローンを手に入れることができます。

このような状況を避けるためには、金利の最低値と最高値の設定や、流動性の変化に基づいた金利の調整が必要です。

算術オーバーフローとアンダーフローの例

フラッシュミントプロバイダーがミント可能なトークンの量に制限を設けていない場合、誰でも最大値近くのトークンをミントすることができます。
このような膨大な量のトークンが関与する取引は、オーバーフローやアンダーフローといった算術的な問題を引き起こす可能性があります。

フラッシュミントを受けるプロトコルは、オーバーフロー保護を組み込んだコンパイラを使用するか、明示的なチェックを設定することで、これらの問題に対処する必要があります。

これらの例からわかるように、フラッシュミンティングは便利な技術ですが、それを安全に使用するためには慎重な設計と十分なセキュリティ対策が必要です。
適切な制限と検証メカニズムを確立することで、攻撃のリスクを最小限に抑えることができます。

フラッシュMintの内部セキュリティへの配慮

フラッシュミンティングはビジネスの特定の機能と組み合わせると、予期せぬ問題を引き起こしやすいです。
例えば、自身のトークンをフラッシュ貸し出すスマートコントラクトがあり、そのトークンがburnされる時に第三者から借り入れをするとします。
このようなシステムでは、以下のような問題が起こる可能性があります。

  1. 大量のトークン(FOO)をフラッシュミントし、それを他のトークン(BAR)に交換します。
    • これにより、レンダーコントラクトが第三者(アンダーライター)から借入限度まで借り入れを行います。
  2. 次に、アンダーライターで債務率を増加させることにより、レンダーコントラクトが担保不足になります。
  3. 最終的に、レンダーコントラクトが担保不足になったことを利用して、その資産を清算して利益を得ます。

この例から分かるように、フラッシュミンティングを実装する時には、フラッシュミンティングが引き起こす可能性のある流動性の急激な変化や金利の変動など、様々なリスクを十分に考慮して適切なセキュリティ対策を講じることが非常に重要です。
これには、トークンの交換レートの監視、債務率の管理、そして不正な取引を防ぐための機能の実装などが含まれます。
適切な対策を行うことで、フラッシュミンティングの利便性を享受しつつ、リスクを管理することが可能になります。

引用

Alberto Cuesta Cañada (@alcueca), Fiona Kobayashi (@fifikobayashi), fubuloubu (@fubuloubu), Austin Williams (@onewayfunction), "ERC-3156: Flash Loans," Ethereum Improvement Proposals, no. 3156, November 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3156.

最後に

今回は「Ethereumブロックチェーン上でフラッシュローンを実装する仕組みを提案している規格であるERC3156」についてまとめてきました!
いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

Twitter @cardene777

他の媒体でも情報発信しているのでぜひ他も見ていってください!

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?