BendDAO Audit Report

Copyright © 2022 by Verilog Solutions. All rights reserved.

May 24, 2022

by Verilog Solutions

This report presents our engineering engagement with the BendDAO team on their decentralized non-custodial NFT liquidity and lending protocol – the BendDAO Protocol.

Project NameBend Protocol
Repository Linkhttps://github.com/BendDAO/bend-lending-protocol
Commit Hash087c284fda18e9d7fd3a07de384a2245d5eab6ea
Language Solidity
ChainEthereum

About Verilog Solutions

Founded by a group of cryptography researchers and smart contract engineers in North America, Verilog Solutions elevates the security standards for Web3 ecosystems by being a full-stack Web3 security firm covering smart contract security, consensus security, and operational security for Web3 projects.

Verilog Solutions team works closely with major ecosystems and Web3 projects and applies a quality above quantity approach with a continuous security model. Verilog Solutions onboards the best and most innovative projects and provides the best-in-class advisory services on security needs, including on-chain and off-chain components.

Table of Contents

Service Scope

Service Stages

Our auditing service includes the following two stages:

  • Pre-Audit Consulting Service
  • Smart Contract Auditing Service
  1. Pre-Audit Consulting Service
    • [Price Oracle Design Discussion Meeting]

      The Verilog Solutions team discussed with the BendDAO team regarding its collateral price oracle design. Throughout the discussion, the Verilog Solutions team conducted a review of the current oracle design and provided feedback and suggestions to the BendDAO team. It is acknowledged by both teams that the price oracle should be as simple as possible to ensure the reliability of data and reduce the attack surface.

    • [Protocol Security & Design Discussion Meeting]

      The Verilog Solutions team discussed with the BendDAO team about off-chain component executions and the security best practice for securing the off-chain components. Verilog Solutions team also provided feedback and suggestions on how to ensure reliability for mission-critical off-chain components including the price oracle service and monitoring.

  1. Smart Contract Auditing Service

    The Verilog Solutions team analyzed the entire project using a detailed-oriented approach to capture the fundamental logic and suggested improvements to the existing code. Details can be found under Findings & Improvement Suggestions.

Methodology

  • Code Assessment
    • We evaluate the overall quality of the code and comments as well as the architecture of the repository.
    • We help the project dev team improve the overall quality of the repository by providing suggestions on refactorization to follow the best practice of Web3 software engineering.
  • Code Logic Analysis
    • We dive into the data structures and algorithms in the repository and provide suggestions to improve the data structures and algorithms for the lower time and space complexities.
    • We analyze the hierarchy among multiple modules and the relations among the source code files in the repository and provide suggestions to improve the code architecture with better readability, reusability, and extensibility.
  • Business Logic Analysis
    • We study the technical whitepaper and other documents of the project and compare its specification with the functionality implemented in the code for any potential mismatch between them.
    • We analyze the risks and potential vulnerabilities in the business logic and make suggestions to improve the robustness of the project.
  • Access Control Analysis
    • We perform a comprehensive assessment of the special roles of the project, including their authorities and privileges.
    • We provide suggestions regarding the best practice of privilege role management according to the standard operating procedures (SOP).
  • Off-Chain Components Analysis
    • We analyze the off-chain modules that are interacting with the on-chain functionalities and provide suggestions according to the SOP.
    • We conduct a comprehensive investigation for potential risks and hacks that may happen on the off-chain components and provide suggestions for patches.

Audit Scope

Our auditing for BendDAO covered the repository:

Project Summary

BendDAO is a decentralized peer-to-pool-based NFT liquidity protocol. Depositors provide ETH liquidity to the lending pool to earn interest, while borrowers can borrow ETH from the lending pool using NFTs as collateral.

The BendDAO protocol enables NFT assets to be deposited and converted into ERC721 boundNFTs, which can be used to create NFT loans.

BendDAO Architecture

BendDAO protocol contains three major parts:

Lending Protocol

contains the smart contracts source code and markets’ configuration for Bend Protocol.

LendPool.sol: It is the main entry point into the BendDAO Protocol. Most interactions with the BendDAO Protocol happen via the LendPool.sol. Users can either deposit supported assets to earn interest or deposit supported NFTs to borrow reserve assets.

LendPoolLiquidator.sol: It contains the main logic of the auction and liquidation functions. LendPool.sol delegates calls to LendPoolLiquidator.sol for auction(), redeem() and liquidate().

LendPoolLoan.sol: It is the NFT loan manager of the protocol, which generates a unique loan when NFTs are used as collateral in borrowing, and maintains the relationship between the NFT and the loan.

BToken.sol: The BToken is an interest-bearing transferrable token minted and burned upon a deposit and withdrawal. The value of BToken is pegged to the value of the corresponding deposited asset at a 1:1 ratio.

DebtToken.sol: The DebtToken is an interest-accruing non-transferrable token minted and burned on borrow and repay. It represents debts owed by a token holder.

NFT Oracle

contains the smart contract source code to provide lending protocol with a market valuation of the underlying asset.

The BendDAO team designed a filter algorithm to fetch the floor price data from OpenSea and LooksRare.

  1. The NFT Oracle is currently maintained and operated by the BendDAO team. The data fetching and filtering algorithm scripts are running off-chain.
  1. In the future, the Bend governance mechanisms will manage the selection of data sources.

Bound NFT

BoundNFTs are promissory-note tokens that are minted and burned upon borrowing and repaying, representing the NFT used as collateral owed by the token holder, with the same token ID and metadata. It has a flash claim feature for users to claim airdrops for NFT holders.

There are two admin roles in BNFT contracts:

  • _owner: handles ownership and admin;
  • _claimAdmin: claims and executes ERC20/ERC721/ERC1155 airdrops.

Periphery Contracts

BendDAO Protocol also has periphery contracts such as WETHGateway.sol and PunkGateway.sol:

  • WETHGateway.sol: LendPool only supports ERC20 tokens as underlying reserve assets. WETHGateway.sol provides an interface that allows users to deposit native tokens such as ETH directly without the need to swap between the native token and the wrapped native token themselves.
  • PunkGateway.sol: Lending pool only supports interactions with the standard ERC721 token. NFT CryptoPunks is not ERC721 compatible. PunkGateway helps users to wrap and unwrap CryptoPunks and interact with lend pool.

Findings & Improvement Suggestions

SeverityTotalAcknowledgedResolved
High000
Medium111
Low441
Informational772

High

none ; )

Medium

  1. LendPool.sol : Time elapsed in the PAUSE state needs to be deducted in Redeem and Auction functions.
    SeverityMedium
    Sourcecontracts/protocol/LendPool.sol: #L408 contracts/protocol/LendPool.sol: #L431
    StatusResolved in commit 64b7861e48cf0156d5f203e3a39166f22ae36dd0.
    • Description

      Time-sensitive functions such as redeem and auction do not deduct time elapsed in the paused state.

      If an auction starts and then the protocol enters into a pause state, the time elapsed during the pause period is still counted in time-sensitive functions such as auction and redeem.

      As a result, any time elapsed in the pause state will erode into the 48-hour grace period of the auction state. Bidders will have a shorter time window than the 48-hour grace period to bid, and the borrower will have a shorter time window than the expected to redeem.

    • Exploit Scenario

      There are 2 conditions that need to be fulfilled:

      1. There is an undergoing auction;
      1. The project team triggered the pause function and put the protocol into a pause state.

      As a result, any time elapsed in the pause state will erode into the 48-hour grace period of the auction state. This may result in:

      1. Bidders will have a shorter window to bid,
      1. Borrowers will have a shorter window to redeem.
    • Recommendations

      After unpause, the protocol needs to calculate the duration of the paused period and update auction timestamp to reflect the time elapsed in the paused state.

Low

  1. Unnecessary Require and Redundant Parameter
    SeverityLow
    Sourcecontracts/protocol/LendPoolLiquidator.sol
    StatusAcknowledged.
    • Description

      In LendPoolLiquidator.sol, there is an unnecessary require in redeem() function and redundant parameter bidFine.

      require(vars.bidFine <= bidFine, Errors.LPL_BID_INVALID_BID_FINE); 

      Variable bidFine is an input provided by the user, while vars.bidFine is a variable calculated from the contract’s internal states. vars.bidFine is actually referenced in other places, while bidFine is unused anywhere except this requirement check. Therefore, this requirement check is unnecessary, as the input variable being checked bidFine is not used anywhere in other places. One of the consequences of having this unnecessary requirement check is that if the user inputs a value of the variable bidFine that is smaller than vars.bidFine, the function call will revert. Meanwhile, the incorrect bidFine would not have an effect anywhere, as bidFine is not used anywhere else except this require a check.

      require(vars.repayAmount <= vars.maxRepayAmount, Errors.LP_AMOUNT_GREATER_THAN_MAX_REPAY);

      The current design will cause a revert in the cases where the user input amount is bigger than the maxRepayAmount. Thus, the frontend must calculate the correct amount to be used in the function call. If the users are constructing the transaction themselves (i.e., in the case that the frontend is down), the users must calculate the correct amount. Otherwise, the transaction will fail. As an alternative design that can improve user experience, the function can just assign the repayAmount with the maxRepayAmount if the amount that the user inputs are bigger than the maxRepayAmount.

    • Exploit Scenario

      N/A

    • Recommendation

      Remove these two require and assign the repayAmountwith the maxRepayAmountif the amount the user inputs is bigger than the maxRepayAmount.

    • Results

      Acknowledged. The BendDAO team decided to keep this design.

  1. Unused State in LoanState Enumerator
    SeverityLow
    Sourcecontracts/libraries/types/DataTypes.sol
    StatusAcknowledged.
    • Description

      LoanState enumerator in DataTypes.sol has a state variable Created which has never been used across the entire repository.

      DataTypes.sol stored all the pre-defined configuration-related data struct and enumerator. LoanState enumerator defines the current state of a loan. There are in total 6 pre-defined states:

      1. None - Default value, this means zero value in loan;
      1. Created - Loan data has been stored, but not initiated yet;
      1. Active - The loan has been initialized, funds have been delivered to the borrower and the collateral is held;
      1. Auction - The loan is in the auction, and the highest price liquidator will get a chance to claim it;
      1. Repaid - The loan has been repaid and the collateral has been returned to the borrower, which is a terminal state;
      1. Defaulted - The loan was delinquent and collateral was claimed by the liquidator, which is a terminal state.

      However, LoanState.Created has never been used. LoanState is changed to Active after createLoan(). Having unused states might become confusing for subsequent development work.

    • Exploit Scenario

      N/A

    • Recommendation

      Remove the unused state.

    • Results

      Acknowledged. It’s from the previous code design. The BendDAO team decided to keep this design.

  1. Redundant Parameter
    SeverityLow
    Sourcecontracts/protocol/LendPoolLiquidator.sol: L286
    StatusAcknowledged.
    • Description

      In LendPoolLiquidator.sol, the function liquidate() has an input parameter called amount, which is redundant and can be easily bypassed.

      Function liquidate() in LendPoolLiquidator.sol can be called to liquidate a non-healthy position. The function caller (liquidator) buys the collateral asset from the user that is liquidated and receives the collateral asset. There are 3 parameters that the caller needs to pass in, but only the first two are necessary:

      1. nftAsset - The address of the underlying NFT used as collateral;
      1. nftTokenId - The token ID of the underlying NFT used as collateral;
      1. amount - The amount of ETH to be paid by the liquidator.

      However, the function input parameter amount is only referenced in the require statement. The actual amount that the liquidator needs to pay is calculated internally in the liquidate() function.

      Therefore, this requirement check is unnecessary, as the input variable being checked amount is not used anywhere in other places. One of the consequences of having this unnecessary requirement check is that if the user inputs an amount that is greater than vars.extraDebtAmount, the function call will revert. Meanwhile, the incorrect amount would not have an effect anywhere, as amount is not used anywhere else except this require statement.

      The frontend is responsible for calculating the correct amount used in the function call. If the users are constructing the transaction themselves (i.e., in the case that the frontend is down), the users must calculate the correct amount. Otherwise, the transaction will fail.

    • Exploit Scenario

      N/A

    • Recommendation

      Parameter amount in function liquidate() can be removed.

    • Results

      Acknowledged. This parameter is mainly for the WETHGateWay.sol contract, checking the msg.value passing from the gateway contract to the main lend pool contract. The BendDAO team decided to keep this design.

  1. Function executeOperation()uses a method from non-standard EIP721
    SeverityLow
    Sourceboundnft-protocol/contracts.misc/AirdropFlashLoanReceiver.sol: L106
    StatusResolved.
    • Description

      Function executeOperation() uses IERC721Enumerable(token).tokenOfOwnerByIndex(), which is not supported by standard EIP721.

      AirdropFlashLoanReceiver.sol is designed for letting NFT depositors receive their deposited NFT’s airdrops. It can handle multiple situations, such as claim airdrop ERC20 token, ERC721 NFT, or ERC1155.

      Function executeOperation()main logic is the following:

      1. Allow the operator to transfer borrowed NFTs back to BNFT contracts;
      1. Call project airdrop contract;
      1. Transfer airdrop tokens to the borrower.

      However, in STEP 3:

      else if (vars.airdropTokenTypes[typeIndex] == 2) {
        // ERC721
        vars.airdropBalance = IERC721(vars.airdropTokenAddresses[typeIndex]).balanceOf(address(this));
        for (uint256 i = 0; i < vars.airdropBalance; i++) {
          vars.airdropTokenId = IERC721Enumerable(vars.airdropTokenAddresses[typeIndex]).tokenOfOwnerByIndex(
            address(this),
            0
          );
          IERC721Enumerable(vars.airdropTokenAddresses[typeIndex]).safeTransferFrom(
            address(this),
            initiator,
            vars.airdropTokenId
          );
        }
      }

      The enumeration extension is OPTIONAL for ERC-721 smart contracts (see “caveats”, below). This allows your contract to publish its full list of NFTs and make them discoverable. [Reference EIP-721]

      /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
      /// @dev See https://eips.ethereum.org/EIPS/eip-721
      ///  Note: the ERC-165 identifier for this interface is 0x780e9d63.
      interface ERC721Enumerable /* is ERC721 */ {
          /// @notice Count NFTs tracked by this contract
          /// @return A count of valid NFTs tracked by this contract, where each one of
          ///  them has an assigned and queryable owner not equal to the zero address
          function totalSupply() external view returns (uint256);
      
          /// @notice Enumerate valid NFTs
          /// @dev Throws if `_index` >= `totalSupply()`.
          /// @param _index A counter less than `totalSupply()`
          /// @return The token identifier for the `_index`th NFT,
          ///  (sort order not specified)
          function tokenByIndex(uint256 _index) external view returns (uint256);
      
          /// @notice Enumerate NFTs assigned to an owner
          /// @dev Throws if `_index` >= `balanceOf(_owner)` or if
          ///  `_owner` is the zero address, representing invalid NFTs.
          /// @param _owner An address where we are interested in NFTs owned by them
          /// @param _index A counter less than `balanceOf(_owner)`
          /// @return The token identifier for the `_index`th NFT assigned to `_owner`,
          ///   (sort order not specified)
          function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
      }

      This function call will fail when interacting with airdrop tokens that do not support this method.

    • Exploit Scenario

      As stated in the above [Description]

      The exploit will happen when the airdrop is not ERC721 Enumerable, then this function call will fail when interacting with airdrop tokens that do not support the tokenOfOwnerByIndex method.

    • Recommendation
      • In the short term, the team needs to be careful when supporting new airdrop ERC721 tokens. The token must support ERC721Enumerable.
      • In the long term, we suggest modifying the function and only using methods from standard ERC721, which will greatly improve the compatibility of the contract.
    • Results

      Resolved. Fixed at commit 6f2a316faf05bc17ac4c19438a22091424766deb and has already been tested in the latest Doodles airdrop. The BendDAO team will pay extra attention when adding support for new NFT assets.

Informational

  1. Move batchBorrow() and batchRepay() functions to periphery/helper contracts. (LendPool.sol, PunkGateway.sol, WETHGateway.sol)
    SeverityInformational
    Sourcecontracts/protocol/LendPool.sol contracts/protocol/PunkGateway.sol contracts/protocol/WETHGateway.sol
    StatusAcknowledged.
    • Description

      Current contract design integrated batchBorrow() and batchRepay() which enables multiple borrow/repays in a single function call.

      These two functions are essentially multicall to the original borrowand repay function. Calls to external functions were made inside the loops, which might increase the attack surface of the contract. Most projects do not integrate these two function calls into their core smart contract due to higher contract risks.

      We suggest to move batchBorrow() and batchRepay() to a periphery/helper contracts.

    • Exploit Scenario

      N/A

    • Recommendations

      We suggest keeping the main contracts simple and elegant, only with the necessary functionalities. Less code, fewer attack surfaces. Helper functions like batch functions can be moved to a periphery contract/helper contract and done by a multicall to the function of the main contract. Those periphery contracts can easily be replaced without updating/changing the main contract if any vulnerabilities/issues are found in those helper functions.

    • Results

      Acknowledged. BendDAO decides to keep this batch feature in the future version of deployments.

  1. Caution when adding new reserve assets
    SeverityInformational
    SourceN/A
    StatusAcknowledged.
    • Description

      The current BendDAO Protocol only uses ETH as the reserve asset. Although, we know the BendDAO team has no plan of expanding the reserve assets list. It is still worth mentioning and informing BendDAO governance voters that adding new reserve assets may bring risks.

      Tokens with before and after transfer hooks and tokens with double/multiple entry points bring more security risk. Be cautious when adding new tokens as reserve assets.

    • Exploit Scenario

      N/A

    • Recommendations

      Be cautious when adding new tokens as reserve assets.

    • Results

      Acknowledged.

3. Function removeAsset() code optimization by Using enumerate mapping

SeverityInformational
Sourcecontracts/protocol/NFTOracle.sol
StatusAcknowledged.
  • Description

    Function removeAsset() in NFTOracle.sol are not optimized.

    removeAsset() function is designed for removing unused or delist NFT addresses from the asset list. Inside the function, it uses a for loop to iterate through the entire list of the oracle asset and delete the target one. Similar functionality can be optimized by using iterative mapping technics.

    [checkout Recommendations for a sample improved code]

    Generally speaking, it is a better practice to keep the complexity within O(1) for the contracts deployed on the Ethereum mainnet.

  • Exploit Scenario

    N/A

  • Recommendations

    Here is the improved logic:

    1. When addNFT(), save the NFT address to the array and record its index;
    1. When removeNFT(), copy the address of the last element to the target removed NFT index and then pop the last one.

    Please check the enumerate mapping below as an example:

    address[] public activeNFTs;
    mapping(address => uint256) public indexOfNFT;
    
    function addNFT(address nftAddress) public {
        require(nftAddress != address(0), "AO: nftAddress is zero address");
        require(indexOfNFT[nftAddress] == 0, "AO: already existed");
        activeNFTs.push(nftAddress);
        indexOfNFT[nftAddress] = activeNFTs.length;
    }
    
    function removeNFT(address nftAddress) public {
        require(nftAddress != address(0), "AO: nftAddress is zero address");
        uint256 valueIndex = indexOfNFT[nftAddress];
        require(valueIndex != 0, "AO: does not existed");
        uint256 toDeteleIndex = indexOfNFT[nftAddress] - 1;
        uint256 lastIndex = activeNFTs.length - 1;
    
        if (lastIndex != toDeteleIndex) {
            address lastValue = activeNFTs[lastIndex];
            activeNFTs[toDeteleIndex] = lastValue;
            indexOfNFT[lastValue] = valueIndex;
        }
    
        activeNFTs.pop();
        detele indexOfNFT[nftAddress];
    }
  • Results

    Acknowledged. This operation is rarely executed and only executed by admin, not users, Hence, it’s not urgent for the BendDAO team to update the contract code for this suggestion.

4. Function setAssetData() variable incrementation

SeverityInformational
Sourcecontracts/protocol/NFTOracle.sol
StatusResolved in commit 152921a01c9ecf7815f7f6d13ab6f2a3623eb505.
  • Description

    _roundId in setAssetData() should be self-incremented instead of treated as an input by the admin.

    Function setAssetData() in NFTOracle.sol has been designed to only allow the admin of the protocol to upload the current floor price of the target NFT. There are 4 inputs of this function:

    1. _nftContract - Contract address of the NFT in the asset list.
    1. _price - NFT latest floor price
    1. [optional] _timestamp - The timestamp.
    1. _roundId - An index for labeling.

    The _roundId is currently treated as an input. It would be better if there is an incrementation logic in setAssetData() to ensure the _roundId will not collide with the ones before.

  • Exploit Scenario

    The _roundId collision may not have a material effect, as _roundId is not used in any critical value calculation. However, having a _roundId collision might cause confusion in subsequent development work.

  • Recommendations

    Add an incrementation logic inside the function to make sure the _roundId will never collide with the previous ones.

    Please check the following code as an example:

    function setAssetData(
        address _nftContract,
        uint256 _price,
        uint256 /*_timestamp*/
      ) external override onlyAdmin whenNotPaused(_nftContract) {
        requireKeyExisted(_nftContract, true);
        uint256 _timestamp = _blockTimestamp();
        require(_timestamp > getLatestTimestamp(_nftContract), "NFTOracle: incorrect timestamp");
        require(_price > 0, "NFTOracle: price can not be 0");
        bool dataValidity = checkValidityOfPrice(_nftContract, _price, _timestamp);
        require(dataValidity, "NFTOracle: invalid price data");
        
        // NOTE: read latest _roundID and increment it by 1
        uint256 newRoundId = getLatestRoundId(_nftContract) + 1;
        // NOTE: assign the newRoundID to data
        NFTPriceData memory data = NFTPriceData({price: _price, timestamp: _timestamp, roundId: newRoundId});
        nftPriceFeedMap[_nftContract].nftPriceData.push(data);
    
        emit SetAssetData(_nftContract, _price, _timestamp, _roundId);
      }

5. Suggest integrating Chainlink Any API

SeverityInformational
Sourcecontracts/protocol/NFTOracle.sol
StatusAcknowledged.
  • Description

    To improve the general security and overall decentralization, we suggest the BendDAO team considered integrating with Chainlink Any API.

    The BendDAO protocol uses OpenSea & LooksRare as the data source. Currently, all data queries & sorting are managed off-chain. Centralized servers are querying data and pushing data to the on-chain smart contract.

    Bringing data queries & sorting on-chain can improve the transparency and reliability of the data source.

  • Exploit Scenario

    N/A

  • Recommendations

    Integrating with Chainlink product Any API. Chainlink enables smart contracts to access any external data sources through its decentralized oracle network.

  • Results

    Acknowledged. The BendDAO team is considering this suggestion in future development.

6. Comment mistake

SeverityInformational
Sourcecontracts/protocol/NFTOracle.sol: L178
StatusResolved.
  • Description

    Comment mistake in the NFTOracle.sol.

    _interval variable no longer existed in the contract, as the latest code is using twapInterval.

  • Exploit Scenario

    N/A

  • Recommendations

    change _interval in the comment to twapInterval

  • Results

    Resolved.

7. NFTOracle.sol: getAssetPrice() returns TWAP price only.

SeverityInformational
Sourcecontracts/protocol/NFTOracle.sol
StatusAcknowledged
  • Description

    Function getAssetPrice() only returns TWAP price of the NFT asset.

    In branch Oracle/roundid’s commit, the updated getAssetPrice() does not return the raw price data.

  • Exploit Scenario

    N/A

  • Recommendations

    Consider returning a (twapPrice, rawPrice) tuple, or adding an extra getAssetRawPrice() function to fetch the raw price data.

  • Results

    Acknowledged. The BendDAO team decided to keep the original design.

Access Control Analysis

Lending Pool

There are two major privileged roles for the lending pool:

  • poolAdmin:
    • Configure the pool including upgrading contracts, setting essential parameters, and adding/removing reserve assets and NFT assets.
  • emergencyAdmin:
    • Pause and unpause the lending pool.

NFT Oracle

There are two types of admin roles in the NFT Oracle:

  • _owner: A multi-sig wallet controlled by the BendDAO dev team.

    1. setPriceFeedAdmin()

    Set a new address for an admin account in the smart contracts.

    2. setAssets()

    Add a list of addresses of NFT to the oracle.

    3. addAsset()

    Add a single address of NFT to the oracle.

    4. removeAsset()

    Remove a single address of NFT from the oracle.

    5. setDataValidityParameters()

    Set a list of validity checking & security-related parameters (e.g., maximum price deviation, the shortest time interval for the price update).

    6. setPause()

    An emergency brake for a specific NFT of the oracle.

  • _admin: A wallet imported to a cloud server to feed price on-chain.

    1. setAssetData()

    Set the price of a specific NFT collection.

Off-Chain OpSec

BendDAO protocol off-chain components are majorly in the following components:

  • Lending Protocol,
  • NFT Oracle,
  • Admin Addresses.

Lending Protocol

There are two major privileged roles for the lending pool:

  • poolAdmin:
    • Configure the pool including upgrading contracts, setting essential parameters, and adding/removing reserve assets and NFT assets.
  • emergencyAdmin:
    • Pause and unpause the lending pool.

NFT Oracle

There are two types of admin roles in the NFT Oracle:

  • _owner: A multi-sig wallet controlled by the BendDAO dev team

    1. setPriceFeedAdmin()

    Set a new address for an admin account in the smart contracts.

    2. setAssets()

    Add a list of addresses of NFT to the oracle.

    3. addAsset()

    Add a single address of NFT to the oracle.

    4. removeAsset()

    Remove a single address of NFT from the oracle.

    5. setDataValidityParameters()

    Set a list of validity checking & security-related parameters (e.g., maximum price deviation, the shortest time interval for the price update).

    6. setPause()

    An emergency brake for a specific NFT of the oracle.

  • _admin: A wallet imported to the cloud server to feed price on-chain.

    1. setAssetData()

    Set the price of a specific NFT collection.

The admin addresses and private keys are managed and stored on a cloud server, and multiple measures were taken to enhance the security of the price oracle. There are two sets of oracle nodes with independent private keys. The duplicate node design, combined with an on-chain price filter, is to mitigate the price manipulation attack in case one of the private keys is leaked. In addition, the IP addresses of these oracle nodes are hidden to protect the nodes from DDOS attacks.

End-to-end encryption is enabled

In addition, the BendDAO team has acknowledged the risks and would actively monitor the oracle addresses. The team also manages a multi-sig wallet as the owner to replace the current EOA admin account of the NFT Oracle smart contract, if necessary.

To minimize the room for price manipulation, there are various constraints for the prices fed to the oracle and the frequency of price updates (e.g., price feeding granularity settings and filters for abnormal prices and prices with volatility higher than accepted).

The operation of the BendDAO protocol is highly dependent on the NFT price oracle. To further enhance the stability and security of the protocol, discussions about NFT oracle design improvement were arranged between the BendDAO dev team and the Verilog Solutions audit team. Based on our discussion about the oracle structure and analysis of the historical data, we concluded that a time-weighted moving average floor price (TWAP) (e.g., a 6h TWAP price can filter out abnormal floor price data) can be used as a valid market valuation.

6h TWAP floor price compared with the raw floor price
  • Blue Line represents the frequently fetched market raw floor price.
  • Red Line represents the 6h TWAP floor price.

Securing Admin Addresses

Lending Protocol, NFT Oracle, and BoundNFT have admin roles. The admin roles are secured via multi-sig and TimelockController mechanisms.

The details of the multi-sig and TimelockController addresses can be found at BendDAO: Security and Audits Docs.

Appendix I: Severity Categories

SeverityDescription
HighIssues that are highly exploitable security vulnerabilities. It may cause direct loss of funds / permanent freezing of funds. All high severity issues should be resolved.
MediumIssues that are only exploitable under some conditions or with some privileged access to the system. Users’ yields/rewards/information is at risk. All medium severity issues should be resolved unless there is a clear reason not to.
LowIssues that are low risk. Not fixing those issues will not result in the failure of the system. A fix on low severity issues is recommended but subject to the clients’ decisions.
InformationalIssues that pose no risk to the system and are related to the security best practices. Not fixing those issues will not result in the failure of the system. A fix on informational issues or adoption of those security best practices-related suggestions is recommended but subject to clients’ decision.

Appendix II: Status Categories

StatusDescription
UnresolvedThe issue is not acknowledged and not resolved.
Partially ResolvedThe issue has been partially resolved.
AcknowledgedThe Finding / Suggestion is acknowledged but not fixed / not implemented.
ResolvedThe issue has been sufficiently resolved.

Disclaimer

Verilog Solutions receives compensation from one or more clients for performing the smart contract and auditing analysis contained in these reports. The report created is solely for Clients and published with their consent. As such, the scope of our audit is limited to a review of code, and only the code we note as being within the scope of our audit is detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Our sole goal is to help reduce the attack vectors and the high level of variance associated with utilizing new and consistently changing technologies. Thus, Verilog Solutions in no way claims any guarantee of security or functionality of the technology we agree to analyze.

In addition, Verilog Solutions reports do not provide any indication of the technologies proprietors, business, business model, or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog Solutions has the right to distribute the Report through other means, including via Verilog Solutions publications and other distributions. Verilog Solutions makes the reports available to parties other than the Clients (i.e., “third parties”) – on its website in hopes that it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.