AaveV2 on Ethereum Report

Smart Contract Security Assessment

Report Generated: 14 Oct 2022

1 Overview

This report provides a security analysis summary for the AaveV2 protocol running on the Ethereum chain and covers 481 contracts in our system.

Watchdog, our cutting edge static analysis technology, flagged 540 potential instances of vulnerabilities. These can be further classified as 13 high confidence flags, 420 medium confidence flags and 58 low confidence flags.

Moreover, Watchdog, is able to detect 8 bad smells. These are anti-patterns which the research team at Dedaub have identified and formalized. Bad smells might not directly lead to exploitation, but they are an indicator of bad design, and are often worth further inspection.

Lastly our analyzer pointed out a total of 0 component inferences. These correspond to patterns that are safe on their own, but when combined with unsafe patterns in other contracts may lead to cross-contract exploits.

1.1 Potential Vulnerabilities Flagged by Confidence.
1.2 Warnings by Type.

2 Watchdog: Supported Patterns

In this section we present the patterns that Watchdog is on the lookout for. This list may not be exhaustive. The blockchain’s nature is ever evolving, and Dedaub's research team is actively working on supporting novel vulnerabilities.

Vulnerability Description
Accessible selfdestruct SELFDESTRUCT target address can be overwritten by untrusted external caller.
Accessible selfDestruct SELFDESTRUCT target address can be overwritten by untrusted external caller. (symbolic value-flow variant)
Arithmetic error Arithmetic overflow or underflow can occur due to unchecked operations.
Bad Randomness Bad source of randomness can be exploited by untrusted caller.
Call and Delegate Together Contract may call same target address with both a regular call and a DELEGATECALL.
Call DoS External call can cause a denial-of-service by failing/throwing. (symbolic value-flow variant)
Call to Tainted Function Target function of external call can be changed by untrusted caller.
DoS (Unbounded Iteration) Array iterator may be susceptible to DoS caused by untrusted caller increasing storage requirements. (symbolic value-flow variant)
DoS (Unbounded Operation) Array iterator may be susceptible to DoS caused by untrusted caller increasing storage requirements.
DoS (Wallet Griefing) ETH Transfer operation may be susceptible to DoS from griefing wallet stored in storage, able to be overwritten by untrusted callers.
ECDSA without chainid SHA3 hash used in ECDSA signing does not include chainId, allowing for possible cross-chain replay attacks.
ERC20 Underflow Underflow of the balances array found in ERC20.
FlashLoan unchecked callback The flashloan callback method does not contain an assertion checking the initiator of the flash loan.
Inconsistent assertions Sensitive operation can be reached by different entry points without the same assertions being made consistently.
Inconsistent call arg scaling Arguments of external calls to the same function are scaled using different decimal constant factors (e.g., 10^8 vs 10^18).
Inconsistent storage scaling Different writes to the same storage field are scaled using different decimal constant factors (e.g., 10^8 vs 10^18).
Looped delegateCall and msg.value DELEGATECALL operations used in a loop to unknown function, without checking the caller, and with "msg.value".
Permit omits sensitive variable Permit call does not sign sensitive variable.
Reentrancy External call operation possibly susceptible to reentrancy attack.
Repeated Calls Repeated external calls to contract controlled by untrusted caller can allow them to return inconsistent results.
Sensitive call can be reached by anyone Sensitive call to DeFi API can be reached by untrusted external caller.
SSTORE to tainted address SSTORE operation writes to a tainted, user-controlled storage address.
Suspicious money burn operation Suspicious call to sensitive money burn operation.
Suspicious money transfer operation Suspicious call to sensitive money transfer operation.
Swap publicly reachable Swap operation can be reachable by untrusted external caller.
Swap reachable, contract has funds Swap operation can be reachable by untrusted external caller, static patterns suggest the contract holds funds.
Tainted delegatecall Target address of DELEGATECALL operation can be controlled by untrusted caller.
Tainted DELEGATECALL Target address of DELEGATECALL operation can be controlled by untrusted caller. (symbolic value-flow variant)
Tainted ERC20 Token Transfer Various parameters (target token, fund receipt) of an ERC20 token transfer call can be controlled by an untrusted external caller.
Tainted Ether Value ETH send operation has target and value that can be controlled by untrusted caller.
Tainted money-sensitive var in external call Untrusted callers can control the value of a sensitive argument of an external call.
Tainted Ownership Guard Storage field used in guarding logic can be overwritten by untrusted caller, allowing them to bypass sensitive guards. (symbolic value-flow variant)
Tainted Owner Variable Storage field used in guarding logic can be overwritten by untrusted caller, allowing them to bypass sensitive guards.
Tainted selfdestruct SELFDESTRUCT target address can be overwritten by untrusted external caller.
Tainted selfDestruct SELFDESTRUCT target address can be overwritten by untrusted external caller. (symbolic value-flow variant)
transferFrom proxy A public function may call transferFrom for arbitrary contracts.
Transfer of entire balance A token transfer sends the entire balance of the contract.
Twin calls Repeated external calls to contract controlled by untrusted caller can allow them to return inconsistent results. (symbolic value-flow variant)
Unchecked Low-Level Call Return status of external call is not checked.
Uniswap tainted token The contract calls Uniswap operation passing tokens that can be controlled by untrusted external caller.
Unrestricted approve proxy The contract calls an arbitrary token's approve.
Unrestricted transferFrom Proxy Various parameters (target token, fund receipt) of an ERC20 token transferFrom call can be controlled by an untrusted external caller.
Unrestricted transfer proxy Various parameters (target token, fund receipt) of an ERC20 token transfer call can be controlled by an untrusted external caller. (symbolic value-flow variant)
Wallet Griefing ETH Transfer operation may be susceptible to DoS from griefing wallet stored in storage, able to be overwritten by untrusted callers. (symbolic value-flow variant)
Bad smell Description
Inconsistent ECDSA signing ECDSA signing includes several arguments of a function but skips one or more.
No-op external function call External view/pure call has an unused result.
No-op internal function call Internal call with no side effects has an unused result.
this.call() Contract makes external call to itself, whose only purpose would be to change "msg.sender".

3 Investigation Workflow at Dedaub

The process of manual inspection is performed on the analysis warnings, prioritising them based on the severity and confidence level of the analysis type.

It should be noted that on our current workflow analysis results are inspected on a per deployed-address basis. This means that the manual inspection results are dependent not only on the correctness of the static analysis results, but on the use of the contract in a protocol (achieved by inspecting code of other relevant contracts, as well as past transactions when needed).

A case where the use of a contract can invalidate an otherwise valid warning of the analysis would be; a contract that is only meant to be used as a proxy implementation and should never hold any state.

The output of the inspection (per vulnerability type and address) is a three-valued “state” (can be unknown, safe, unsafe), and an optional description text.

Our system has, up to this day, been used to perform 11 vulnerability inspections, 8 of which were marked as safe and 3 as unsafe.

3.1 Total code vulnerabilities inspected.
3.2 Criticality of inspected vulnerabilities.

4 Stateful Assessment

Based on the results of the manual inspection several of the warning types are elevated into reports which periodically check state conditions to automatically assess the impact of possible attacks.

The currently supported report types are the following:

Report Type Report Name Description
Destructible_Implementation Uninitialized Proxy Implementation Uninitialized proxy implementation can be selfdestructed by untrusted callers, causing a DoS to the proxies that use it.
Reachable_Swap Publicly Reachable Swap Swap operation can be executed by untrusted callers, making it especially susceptible to price manipulation attacks (flash loans, MEV).
Unrestricted_Transfer Publicly Reachable Unrestricted Token Transfer ERC20 Token Transfer operation can be executed by untrusted callers, allowing to steal the contract's tokens.
Unrestricted_TransferFrom Publicly Reachable Unrestricted Token TransferFrom ERC20 Token TransferFrom operation can be executed by untrusted callers
Vuln_Money High Value Vulnerability, has Money Contract has been flagged with a high-value vulnerability and either has or handles considerable funds.

5 Results Post Manual Inspection

Out of the 500 possible vulnerabilities, 11 potential vulnerabilities were manually inspected. Of which 3 were marked as unsafe, whilst 8 were marked as safe.

Sample Address Vulnerability Type Inspection Details Criticality
0xd784927ff2f95ba542bfc824c8a8a98f3495f6b5 Tainted delegatecall If proxy is uninitialized, anyone can call initialize(). UNKNOWN

Vulnerable public functions:

00000000

File contract.sol
                    require(_implementation() == address(0));
    assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
    _setImplementation(_logic);
    if (_data.length > 0) {
      (bool success, ) = _logic.delegatecall(_data);
      require(success);
    }
  }
}

/**

              
0xc6845a5c768bf8d7681249f8927877efda425baf Tainted delegatecall Storage field _addressesProvider can be tainted during contract initialization, allowing the tainting of the delegatecall. UNKNOWN

Vulnerable public functions:

00000000

File LendingPool.sol
                        abi.encodeWithSignature(
          'liquidationCall(address,address,address,uint256,bool)',
          collateralAsset,
          debtAsset,
          user,
          debtToCover,
          receiveAToken
        )
      );
    require(success, Errors.LP_LIQUIDATION_CALL_FAILED);

    (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
              
0xc6845a5c768bf8d7681249f8927877efda425baf Tainted Owner Variable Storage field _addressesProvider can be tainted during contract initialization. UNKNOWN

Vulnerable public functions:

00000000

File LendingPool.sol
                   *   on subsequent operations
   * @param provider The address of the LendingPoolAddressesProvider
   **/
  function initialize(ILendingPoolAddressesProvider provider) public initializer {
    _addressesProvider = provider;
  }

  /**
   * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
   * - E.g. User deposits 100 USDC and gets in return 100 aUSDC
   * @param asset The address of the underlying asset to deposit
              
Sample Address Vulnerability Type Inspection Details Criticality
0x9363d9d04404a52143f432e363a307da015b2710 Tainted delegatecall No details given UNKNOWN
0xd66481f93b6be1ddae7f1899d1bd77f733cfd4ea Tainted Owner Variable No details given UNKNOWN
0x25f2226b597e8f9514b3f68f00f494cf4f286491 Inconsistent assertions No details given UNKNOWN

Vulnerable public functions:

upgradeToAndCall(address,bytes), initialize(address,address,bytes), initialize(address,bytes), upgradeTo(address)

File BaseUpgradeabilityProxy.sol
                   * @param newImplementation Address of the new implementation.
   */
  function _setImplementation(address newImplementation) internal {
    require(
      Address.isContract(newImplementation),
      'Cannot set a proxy implementation to a non-contract address'
    );

    bytes32 slot = IMPLEMENTATION_SLOT;

    assembly {
              
0x62c936a16905afc49b589a41d033ee222a2325ad Call DoS No details given HIGH

Vulnerable public functions:

distribute(address[])

File contract.sol
                        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC20: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }

              
0x62c936a16905afc49b589a41d033ee222a2325ad Reentrancy While the attacker can indeed reenter using the address of a fake token there seems to be nothing to be gained from doing that. This contract is made to distribute tokens to a set of receivers set during its initialization, as these cannot be changed it looks safe. UNKNOWN

Vulnerable public functions:

distribute(address[])

File contract.sol
                        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC20: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }

              
0xd784927ff2f95ba542bfc824c8a8a98f3495f6b5 No-op internal function call It has a require statement. UNKNOWN

Vulnerable public functions:

00000000

File contract.sol
                   * @dev fallback implementation.
   * Extracted to enable manual triggering.
   */
  function _fallback() internal {
    _willFallback();
    _delegate(_implementation());
  }
}

/**
 * @title BaseUpgradeabilityProxy

              
0xf5543cdd5f551635e13ebe07e47d01d0fc9cbbd5 Repeated Calls No repeated calls made. UNKNOWN

Vulnerable public functions:

totalSupply()

File ValidationLogic.sol
                    require(isActive, Errors.VL_NO_ACTIVE_RESERVE);

    //if the usage ratio is below 95%, no rebalances are needed
    uint256 totalDebt =
      stableDebtToken.totalSupply().add(variableDebtToken.totalSupply()).wadToRay();
    uint256 availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress).wadToRay();
    uint256 usageRatio = totalDebt == 0 ? 0 : totalDebt.rayDiv(availableLiquidity.add(totalDebt));

    //if the liquidity rate is below REBALANCE_UP_THRESHOLD of the max variable APR at 95% usage,
    //then we allow rebalancing of the stable rate positions.

              
0xf5543cdd5f551635e13ebe07e47d01d0fc9cbbd5 Sensitive call can be reached by anyone Methods called are not sensitive. Are also staticcalls, should probably invalidate them due to this. UNKNOWN

Vulnerable public functions:

548cad09, a8695b1d, 5fa297e5

File ValidationLogic.sol
                
      require(
        !userConfig.isUsingAsCollateral(reserve.id) ||
          reserve.configuration.getLtv() == 0 ||
          stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
        Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
      );
    } else {
      revert(Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED);
    }
  }
              
File ValidationLogic.sol
                    require(isActive, Errors.VL_NO_ACTIVE_RESERVE);

    //if the usage ratio is below 95%, no rebalances are needed
    uint256 totalDebt =
      stableDebtToken.totalSupply().add(variableDebtToken.totalSupply()).wadToRay();
    uint256 availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress).wadToRay();
    uint256 usageRatio = totalDebt == 0 ? 0 : totalDebt.rayDiv(availableLiquidity.add(totalDebt));

    //if the liquidity rate is below REBALANCE_UP_THRESHOLD of the max variable APR at 95% usage,
    //then we allow rebalancing of the stable rate positions.

              
File ValidationLogic.sol
                    mapping(uint256 => address) storage reserves,
    uint256 reservesCount,
    address oracle
  ) external view {
    uint256 underlyingBalance = IERC20(reserve.aTokenAddress).balanceOf(msg.sender);

    require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0);

    require(
      useAsCollateral ||
        GenericLogic.balanceDecreaseAllowed(
              

6 About Watchdog

The audited contracts have been analyzed using automated techniques and extensive human inspection in accordance with state-of-the-art practices as of the date of this report. The audit makes no statements or warranties on the security of the code. On its own, it cannot be considered a sufficient assessment of the correctness of the contract. While we have conducted an analysis to the best of our ability, it is our recommendation for high-value contracts to commission several independent audits, a public bug bounty program, as well as continuous security auditing and monitoring through Dedaub Watchdog.

7 About Dedaub

Dedaub offers significant security expertise combined with cutting-edge program analysis technology to secure the most prominent protocols in the space. The founders, as well as many of Dedaub's auditors, have a strong academic research background together with a real-world hacker mentality to secure code. Prominent blockchain protocols hire us for our foundational analysis tools and deep expertise in program analysis, reverse engineering, DeFi exploits, cryptography and financial mathematics.