💲

Compoundの内部実装について調べてみた Part3

に公開

とあるossのDeFiレンディングプロトコルにコントリビュートさせていただける機会があったので、レンディングプロトコルの先駆けであるCompoundの内部実装を調べてみることにしました。

ほぼメモかつ、途中のところが多々あるので、読み物としては非常に読みにくいかもですが、備忘録として容赦してもらえると、、🙏

誤り等あればぜひご指摘もお願いします!

Part1

https://zenn.dev/shodaimomiyama/articles/cfbcde469a02d9

Part2

https://zenn.dev/shodaimomiyama/articles/4d7b551555232a

Part3はInterfaceに関してです。


interface

コアプロトコル

  • CometMainInterface.sol (abstract contract)

    • 実際の実装

      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.15;
      
      import "./CometCore.sol";
      
      /**
       * @title Compound's Comet Main Interface (without Ext)
       * @notice An efficient monolithic money market protocol
       * @author Compound
       */
      abstract contract CometMainInterface is CometCore {
          error Absurd();
          error AlreadyInitialized();
          error BadAsset();
          error BadDecimals();
          error BadDiscount();
          error BadMinimum();
          error BadPrice();
          error BorrowTooSmall();
          error BorrowCFTooLarge();
          error InsufficientReserves();
          error LiquidateCFTooLarge();
          error NoSelfTransfer();
          error NotCollateralized();
          error NotForSale();
          error NotLiquidatable();
          error Paused();
          error ReentrantCallBlocked();
          error SupplyCapExceeded();
          error TimestampTooLarge();
          error TooManyAssets();
          error TooMuchSlippage();
          error TransferInFailed();
          error TransferOutFailed();
          error Unauthorized();
      
          event Supply(address indexed from, address indexed dst, uint amount);
          event Transfer(address indexed from, address indexed to, uint amount);
          event Withdraw(address indexed src, address indexed to, uint amount);
      
          event SupplyCollateral(address indexed from, address indexed dst, address indexed asset, uint amount);
          event TransferCollateral(address indexed from, address indexed to, address indexed asset, uint amount);
          event WithdrawCollateral(address indexed src, address indexed to, address indexed asset, uint amount);
      
          /// @notice Event emitted when a borrow position is absorbed by the protocol
          event AbsorbDebt(address indexed absorber, address indexed borrower, uint basePaidOut, uint usdValue);
      
          /// @notice Event emitted when a user's collateral is absorbed by the protocol
          event AbsorbCollateral(address indexed absorber, address indexed borrower, address indexed asset, uint collateralAbsorbed, uint usdValue);
      
          /// @notice Event emitted when a collateral asset is purchased from the protocol
          event BuyCollateral(address indexed buyer, address indexed asset, uint baseAmount, uint collateralAmount);
      
          /// @notice Event emitted when an action is paused/unpaused
          event PauseAction(bool supplyPaused, bool transferPaused, bool withdrawPaused, bool absorbPaused, bool buyPaused);
      
          /// @notice Event emitted when reserves are withdrawn by the governor
          event WithdrawReserves(address indexed to, uint amount);
      
          function supply(address asset, uint amount) virtual external;
          function supplyTo(address dst, address asset, uint amount) virtual external;
          function supplyFrom(address from, address dst, address asset, uint amount) virtual external;
      
          function transfer(address dst, uint amount) virtual external returns (bool);
          function transferFrom(address src, address dst, uint amount) virtual external returns (bool);
      
          function transferAsset(address dst, address asset, uint amount) virtual external;
          function transferAssetFrom(address src, address dst, address asset, uint amount) virtual external;
      
          function withdraw(address asset, uint amount) virtual external;
          function withdrawTo(address to, address asset, uint amount) virtual external;
          function withdrawFrom(address src, address to, address asset, uint amount) virtual external;
      
          function approveThis(address manager, address asset, uint amount) virtual external;
          function withdrawReserves(address to, uint amount) virtual external;
      
          function absorb(address absorber, address[] calldata accounts) virtual external;
          function buyCollateral(address asset, uint minAmount, uint baseAmount, address recipient) virtual external;
          function quoteCollateral(address asset, uint baseAmount) virtual public view returns (uint);
      
          function getAssetInfo(uint8 i) virtual public view returns (AssetInfo memory);
          function getAssetInfoByAddress(address asset) virtual public view returns (AssetInfo memory);
          function getCollateralReserves(address asset) virtual public view returns (uint);
          function getReserves() virtual public view returns (int);
          function getPrice(address priceFeed) virtual public view returns (uint);
      
          function isBorrowCollateralized(address account) virtual public view returns (bool);
          function isLiquidatable(address account) virtual public view returns (bool);
      
          function totalSupply() virtual external view returns (uint256);
          function totalBorrow() virtual external view returns (uint256);
          function balanceOf(address owner) virtual public view returns (uint256);
          function borrowBalanceOf(address account) virtual public view returns (uint256);
      
          function pause(bool supplyPaused, bool transferPaused, bool withdrawPaused, bool absorbPaused, bool buyPaused) virtual external;
          function isSupplyPaused() virtual public view returns (bool);
          function isTransferPaused() virtual public view returns (bool);
          function isWithdrawPaused() virtual public view returns (bool);
          function isAbsorbPaused() virtual public view returns (bool);
          function isBuyPaused() virtual public view returns (bool);
      
          function accrueAccount(address account) virtual external;
          function getSupplyRate(uint utilization) virtual public view returns (uint64);
          function getBorrowRate(uint utilization) virtual public view returns (uint64);
          function getUtilization() virtual public view returns (uint);
      
          function governor() virtual external view returns (address);
          function pauseGuardian() virtual external view returns (address);
          function baseToken() virtual external view returns (address);
          function baseTokenPriceFeed() virtual external view returns (address);
          function extensionDelegate() virtual external view returns (address);
      
          /// @dev uint64
          function supplyKink() virtual external view returns (uint);
          /// @dev uint64
          function supplyPerSecondInterestRateSlopeLow() virtual external view returns (uint);
          /// @dev uint64
          function supplyPerSecondInterestRateSlopeHigh() virtual external view returns (uint);
          /// @dev uint64
          function supplyPerSecondInterestRateBase() virtual external view returns (uint);
          /// @dev uint64
          function borrowKink() virtual external view returns (uint);
          /// @dev uint64
          function borrowPerSecondInterestRateSlopeLow() virtual external view returns (uint);
          /// @dev uint64
          function borrowPerSecondInterestRateSlopeHigh() virtual external view returns (uint);
          /// @dev uint64
          function borrowPerSecondInterestRateBase() virtual external view returns (uint);
          /// @dev uint64
          function storeFrontPriceFactor() virtual external view returns (uint);
      
          /// @dev uint64
          function baseScale() virtual external view returns (uint);
          /// @dev uint64
          function trackingIndexScale() virtual external view returns (uint);
      
          /// @dev uint64
          function baseTrackingSupplySpeed() virtual external view returns (uint);
          /// @dev uint64
          function baseTrackingBorrowSpeed() virtual external view returns (uint);
          /// @dev uint104
          function baseMinForRewards() virtual external view returns (uint);
          /// @dev uint104
          function baseBorrowMin() virtual external view returns (uint);
          /// @dev uint104
          function targetReserves() virtual external view returns (uint);
      
          function numAssets() virtual external view returns (uint8);
          function decimals() virtual external view returns (uint8);
      
          function initializeStorage() virtual external;
      }
      
    • 役割

      Cometプロトコルの主要な機能を定義

    • 実装内容

      1. 基本的な操作
        • supply(): 資産の供給
        • withdraw(): 資産の引き出し
        • transfer(): 資産の転送
      2. 担保管理
        • supplyCollateral(): 担保の供給
        • withdrawCollateral(): 担保の引き出し
        • transferCollateral(): 担保の転送
      3. 清算機能
        • absorb(): 清算ポジションの吸収
        • buyCollateral(): 担保資産の購入
        • isLiquidatable(): 清算可能かどうかの判定
      4. 金利管理
        • getSupplyRate(): 供給金利の取得
        • getBorrowRate(): 借入金利の取得
        • getUtilization(): 利用率の取得
      5. プロトコル管理
        • pause(): 各種機能の一時停止
        • withdrawReserves(): 準備金の引き出し
        • 各種パラメータの設定と取得
      6. エラー処理
        • 多数のカスタムエラーを定義(BadAsset, NotCollateralized, Pausedなど)
      7. イベント
        • 各種操作に関するイベントを定義(Supply, Transfer, Withdrawなど)
  • CometExtInterface.sol (abstract contract)

    • 実際の実装

      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.15;
      
      import "./CometCore.sol";
      
      /**
       * @title Compound's Comet Ext Interface
       * @notice An efficient monolithic money market protocol
       * @author Compound
       */
      abstract contract CometExtInterface is CometCore {
          error BadAmount();
          error BadNonce();
          error BadSignatory();
          error InvalidValueS();
          error InvalidValueV();
          error SignatureExpired();
      
          function allow(address manager, bool isAllowed) virtual external;
          function allowBySig(address owner, address manager, bool isAllowed, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) virtual external;
      
          function collateralBalanceOf(address account, address asset) virtual external view returns (uint128);
          function baseTrackingAccrued(address account) virtual external view returns (uint64);
      
          function baseAccrualScale() virtual external view returns (uint64);
          function baseIndexScale() virtual external view returns (uint64);
          function factorScale() virtual external view returns (uint64);
          function priceScale() virtual external view returns (uint64);
      
          function maxAssets() virtual external view returns (uint8);
      
          function totalsBasic() virtual external view returns (TotalsBasic memory);
      
          function version() virtual external view returns (string memory);
      
          /**
            * ===== ERC20 interfaces =====
            * Does not include the following functions/events, which are defined in `CometMainInterface` instead:
            * - function decimals() virtual external view returns (uint8)
            * - function totalSupply() virtual external view returns (uint256)
            * - function transfer(address dst, uint amount) virtual external returns (bool)
            * - function transferFrom(address src, address dst, uint amount) virtual external returns (bool)
            * - function balanceOf(address owner) virtual external view returns (uint256)
            * - event Transfer(address indexed from, address indexed to, uint256 amount)
            */
          function name() virtual external view returns (string memory);
          function symbol() virtual external view returns (string memory);
      
          /**
            * @notice Approve `spender` to transfer up to `amount` from `src`
            * @dev This will overwrite the approval amount for `spender`
            *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
            * @param spender The address of the account which may transfer tokens
            * @param amount The number of tokens that are approved (-1 means infinite)
            * @return Whether or not the approval succeeded
            */
          function approve(address spender, uint256 amount) virtual external returns (bool);
      
          /**
            * @notice Get the current allowance from `owner` for `spender`
            * @param owner The address of the account which owns the tokens to be spent
            * @param spender The address of the account which may transfer tokens
            * @return The number of tokens allowed to be spent (-1 means infinite)
            */
          function allowance(address owner, address spender) virtual external view returns (uint256);
      
          event Approval(address indexed owner, address indexed spender, uint256 amount);
      }
      
    • 役割

      Cometプロトコルの拡張機能を定義

    • 実装内容

      1. 権限管理機能
        • allow(): マネージャーへの権限付与/取り消し
        • allowBySig(): 署名を使用した権限付与/取り消し
      2. 情報取得機能
        • collateralBalanceOf(): アカウントの担保残高を取得
        • baseTrackingAccrued(): アカウントの基本追跡利息を取得
        • 各種スケール値の取得(baseAccrualScale(), baseIndexScale()など)
      3. ERC20互換機能
        • name(), symbol(): トークンの基本情報
        • approve(), allowance(): トークンの承認機能

abstract contractのわけ

  • interfaceとabstract contractの違い
    • interface:
      • 関数の宣言のみを含む
      • 状態変数(state variables)を持たん
      • 関数の実装を含むことができない
      • 他のインターフェースを継承できるが、コントラクトは継承できない
    • abstract contract:
      • 関数の宣言と実装の両方を含む
      • 状態変数を持つことができる
      • 一部の関数を実装し、一部を抽象関数として残すことができる
      • 他のコントラクトやインターフェースを継承できる
  • 選択理由
    1. 状態変数の必要性:
      • Cometプロトコルは複雑な状態管理を必要とします
      • abstract contractは状態変数を定義できるため、より柔軟な設計が可能です
    2. 共通の実装の共有:
      • 一部の関数は共通の実装を持つことができます
      • abstract contractを使用することで、これらの共通実装を共有できます
    3. 継承の柔軟性:
      • abstract contractは他のコントラクトやインターフェースを継承できます
      • この場合、CometCoreを継承しているため、その機能を利用できます
    4. エラー定義の共有:
      • カスタムエラーはinterfaceでは定義できませんが、abstract contractでは定義できます
      • エラー定義を共有することで、一貫性のあるエラーハンドリングが可能です
    5. 実装の柔軟性:
      • 一部の関数は抽象関数として残し、具体的な実装は継承先で行うことができます
      • これにより、異なる実装を持つ複数のコントラクトを作成できます

Discussion