tags: Final Report

Fountain Protocol Incremental Audit (FPIA-1)

Copyright © 2022 by Verilog Solutions. All rights reserved.
April 12, 2022
by Verilog Solutions

Fountain-COVER

This report presents Verilog Solutions’s incremental smart contract auditing engagement with Fountain Protocol, especially for its LPOracleAnchoredView.sol smart contract. Fountain Protocol is one of the first Lending protocols on the Emerald Paratime of Oasis Network.


Table of Content


Summary of the Incremental Audit

We audited Fountain Protocol and the previous auditing report is here with hash cc16318c2db70fdc8fbfb52c26c1f7b9d15875f8.

This is the incremental audit for file LPOracleAnchoredView.sol. The main functionality added is the calculation for LP token price.

Privileged Roles

  1. The caller of the constructor (i.e., the deployer of the smart contract) has the privileged role to select the token pairs and passing the tokens’ symbols and addresses to the function.
    constructor(address _ref,OracleTokenConfig[] memory configs) public { ref = IStdReference(_ref); for(uint i = 0; i < configs.length; i++){ OracleTokenConfig memory config = configs[i]; require(config.baseUnit > 0, "baseUnit must be greater than zero"); CTokenConfigs[config.symbol] = config; cTokenSymbol[config.cToken] = config.symbol; } }

Findings & Improvement Suggestions

InformationalMinorMediumMajorCritical

Total Acknowledged Resolved
Critical 0 0 0
Major 1 1 1
Medium 0 0 0
Minor 0 0 0
Informational 3 3 3

Critical

none ;)

Major

  1. The token decimal alignment is not needed in function reserveProductAndTotalSupply(). However, the product of prices of the tokens with different decimals should be considered in function priceProduct(). major
    Description: In the AMM model, to determine the product of the amounts of two tokens in a pair, we do not need to cast the decimal to 18. Besides, doing this kind of casting may result in the loss of accuracy if one of the tokens in a pair has the decimal greater than 18.

    function reserveProductAndTotalSupply(string memory symbol) internal view returns(uint totalSUpply,uint product) { OracleTokenConfig memory config = CTokenConfigs[symbol]; IDexPair dexPair = IDexPair(config.underlying); totalSUpply = dexPair.totalSupply(); (uint112 reserve0, uint112 reserve1,) = dexPair.getReserves(); uint decimal0 = OracleERC20(dexPair.token0()).decimals(); uint decimal1 = OracleERC20(dexPair.token1()).decimals(); uint amount0 = uint(reserve0).mul(1e18).div(10 ** decimal0); uint amount1 = uint(reserve1).mul(1e18).div(10 ** decimal1); product = amount0.mul(amount1); }

    Recommendation: We suggest removing the decimal alignment or do not use this contract for tokens with decimals greater than 18 otherwise there can be a loss of accuracy

    Result: Fixed in commit dd9475ebc63c5fbbb396c4c01fbfdb59d8821896.

Medium

none ;)

Minor

none ;)

Informational

  1. Typo in function return valuesInformational
    Description: There is a typo in the return values of function reserveProductAndTotalSupply(). The totalSUpply should be spelled like totalSupply. The variables should be spelled in camel format.
    Recommendation: totalSUpply -> totalSupply.
    Result: Fixed in commit 11f434ccfa13f9ea49d05259c4b0f5e411322aa6.

  2. Magic Numbers
    Description: There are some magic numbers in the code deck. For example, 1e28 in Line 67, 1e10 in Line 82 and Line 87. Informational
    Recommendation: Make these magic numbers constant values with comments.
    Result: Improved in commit bce7296eedc2922fa6ea0ab42a0d718b3ee1ef31 and d441a1b0561caf2fbf3065c8266df80381904ac6.

  3. Unnecessary ordering between tokenA and tokenB in function priceProduct(). Informational
    Description: In this function, it purposely changes the order of tokenA and tokenB and saves the symbol of the token that has a smaller token address in variable symbol0 and the other in symbol1. The orders of the tokens do not change the result of the product (product = price0.mul(price1)).

    function priceProduct(string memory symbol) internal view returns(uint product){ OracleTokenConfig memory config = CTokenConfigs[symbol]; string memory symbol0; string memory symbol1; if(config.tokenA < config.tokenB){ symbol0 = config.symbolA; symbol1 = config.symbolB; }else{ symbol0 = config.symbolB; symbol1 = config.symbolA; } uint price0 = oraclePrice(symbol0).rate; uint price1 = oraclePrice(symbol1).rate; product = price0.mul(price1); }

Recommendation: Token symbols can be assigned directly without checking the token orders.
Result: Revised in commit ffc99f59d054e78701de8a0a8899faa0f4c33326.


Disclaimer

Verilog 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 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 in no way claims any guarantee of security or functionality of the technology we agree to analyze.

In addition, Verilog 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 has the right to distribute the Report through other means, including via Verilog publications and other distributions. Verilog 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.