# Security Audit of `Narfex/Oracle` contracts. ## Conclusion This audit was made by Auditor: Vladimir Smelov <vladimirfol@gmail.com>. Date: 2023-02-06 TODO ## Scope TODO ## Methodology 1. Blind audit. Understand the structure of the code without reading any docs. 2. Ask questions to developers. 3. Run static analyzers. 4. Find problems with: - backdoors; - bugs; - math; - potential leaking of funds; - potential locking of the contract; - validate arguments and events; - others. ## Result #### CRITICAL-1 - FIXED. At - NarfexOracle.sol:4 ```solidity=4 import "./PancakeLibrary.sol"; ``` cannot compile the code since there is no such file in the repo ______ #### CRITICAL-2 - DISCUSSING. At - NarfexOracle.sol:63 ```solidity=63 function getPairRatio(address _token0, address _token1) internal view returns (uint) { ``` weak for price manipulations, consider the usage of uniswap v2 cumulative price oracle - https://docs.uniswap.org/contracts/v2/concepts/core-concepts/oracles ##### Client's comment > Не знаю, как решить эту проблему ##### Reaudit Вместо получения цены в конкретный момент в пулле, мы можем брать среднии цены последние 15 минут или за последний час. Вот тут есть примерчик от самого юнисвопа - https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol ______ #### CRITICAL-3 - DISCUSSING At - NarfexOracle.sol:86 ```solidity=86 : getDEXPrice(_address); ``` if backend goes offline, the oracle will give old prices. Consider returning the timestamp (or blocknumber) of the last price update, so the consumer will be able to understand if the prices are out-dated. ##### Client's comment > Ну вот это по-любому надо как-то решить. Нужен надёжный источник, где можно взять цену ##### Reaudit нужно возвращать вместе с ценой еще и метку времени последнего обновления цены ``` price, ts = getPrice() require(now - ts <= 10 minutes) ``` ______ #### MAJOR-1 - DISCUSSING At - NarfexOracle.sol:67 ```solidity=67 return pair.token0() == _token0 ``` pancakeLibrary gives the price with fee, consider just using division of reserves ##### Clients comment > В целом цена из pancakeLibrary для сторонних токенов в текущем Oracle используется только для массового получения цен на фронтенде. Роутер считает цену той же pancakeLibrary уже на месте, и цена с комиссией контракта LP-токена важна. ##### Reply нужно подумать ... а почему цена с комиссией важна? как вообще эти цены с оракула потом использоваться будут? нужно еще принять во внимание что если например токен ВоваКойин внутри своего трансфера берет burningFee и 10% токенов просто сжигает, никакой ДЕКС в цене это не отобразит. ______ #### MAJOR-2. At - NarfexOracle.sol:76 ```solidity=76 : getPairRatio(_address, USDT); ``` not every token has a pair for Token-USDT, a lot of tokens have only Token-ETH pair. What should we do in this case? Should we take the price using the route Token-BNB BNB-USDT? ##### Client's comment > При обмене в роутер посылается путь из токенов. Да, не обязательно есть пара к USDT. Но фронтенд тоже рассчитывает цену по пути через популярные токены, а цена к USDT отображается только в списке токенов, где отображаются балансы. Так что это не критично. ##### Reply имеется ввиду что не нужно надеятся иметь возможность получать все цены с дексов от пары ShitCoin - USDT такие пары могут не существовать. Можно проанализировать насколько таких токенов много. Можно сделать в контракте возможность указывать address[] path для каждого токена по какому path смотреть его цену ______ #### MAJOR-3. At - NarfexOracle.sol:91,248,263 ```solidity=91 /// @return USD prices array with 18 digits of precision ``` ```solidity=248 /// @param _price Fiat price - unsigned number with 18 digits of precision ``` ```solidity=263 /// @param _prices Fiats prices array - unsigned numbers with 18 digits of precision ``` why you say 18 decimals? USDT.decimals()==6, USDC.decimals()==6, for them the price will be PancakeLibrary.getAmountOut(10**IERC20(_token0).decimals(), reserve0, reserve1)==10**6 ##### Client's comment > Комментарии забыл исправить, да. Важное назначение Оракула: передать именно соотношение нашего фиата к USDC в точностью 18 знаков. Т.е. это не отображает, сколько именно USDC , будет получено. Важен именно коэфициент фиат/USDC. А у фиата в каждой сети столько же знаков после запятой, сколько у USDC. Т.е. в сетях 59 и 97 это 18 знаков, а во всех остальных — 6. ###### Reply Нужно очень аккуратным с этим быть)) можно тестов написать на разные decimals ______ #### MAJOR-4. At - NarfexOracle.sol:250 ```solidity=250 function updatePrice(address _address, uint _price) public canUpdate { ``` centralization power risk. Hacker will get the full control over the oracle if he will be in control of one backend server with a private key. Consider the usage of "voting" by several oracle nodes. Committing prices with signatures. ##### Status. NEW ______ #### MAJOR-5. At - NarfexOracle.sol:282 ```solidity=282 delete fiats[i]; ``` you have gaps in array, replace with the last one and use ".pop()" ##### Status. NEW ______ #### WARNING-1. At - NarfexOracle.sol:9 ```solidity=9 function decimals() external view returns (uint8); ``` not all erc20 tokens have it ##### Status. NEW ______ #### WARNING-2. At - NarfexOracle.sol:48 ```solidity=48 address public USDT; // Tether address in current network ``` it's here "Tether USDT", however on the call you told that we are gonna use "USDC", should we rename? ##### Status. NEW ______ #### WARNING-3. At - NarfexOracle.sol:218 ```solidity=218 tokenData.price = !tokenData.isFiat && _skipCoinPrice ``` why do you have it for "!tokenData.isFiat"? ##### Status. NEW ______ #### WARNING-4. At - NarfexOracle.sol:285 ```solidity=285 } ``` should it update the price? ##### Status. NEW ______ #### LOW-1. At - NarfexOracle.sol:8 ```solidity=8 interface IERC20 { ``` use import IERC20Metadata from OpenZeppelin ##### Status. NEW ______ #### LOW-2. At - NarfexOracle.sol:23 ```solidity=23 bool isCustomReward; // Use defalt referral percent on false ``` typo in the word "default" ##### Status. NEW ______ #### LOW-3. At - NarfexOracle.sol:25 ```solidity=25 uint reward; // Referral percent only for fiats ``` where is it used? ##### Status. NEW ______ #### LOW-4. At - NarfexOracle.sol:25-27 ```solidity=25 uint reward; // Referral percent only for fiats int commission; // Commission percent with. Can be lower than zero uint transferFee; // Token transfer fee with 1000 decimals precision (20 for NRFX is 2%) ``` add postfix Numerator for clarity ##### Status. NEW ______ #### LOW-5. At - NarfexOracle.sol:25-27 ```solidity=25 uint reward; // Referral percent only for fiats int commission; // Commission percent with. Can be lower than zero uint transferFee; // Token transfer fee with 1000 decimals precision (20 for NRFX is 2%) ``` should it be part of the oracle? Maybe split it to another contract keeping the oracle as simply as possible and logically isolated. ##### Status. NEW ______ #### LOW-6. At - NarfexOracle.sol:39-40 ```solidity=39 address[] public fiats; // List of tracked fiat stablecoins address[] public coins; // List of crypto tokens with different commission ``` OpenZeppelin enumerableSet ##### Status. NEW ______ #### LOW-7. At - NarfexOracle.sol:40 ```solidity=40 address[] public coins; // List of crypto tokens with different commission ``` rename to tokensWithCustomCommission ##### Status. NEW ______ #### LOW-8. At - NarfexOracle.sol:44 ```solidity=44 int defaultCryptoCommission = 0; // Use as a commission if isCustomCommission = false for coins ``` wrong terminology https://www.bitstamp.net/learn/crypto-101/crypto-coins-vs-tokens/ ##### Status. NEW ______ #### LOW-9. At - NarfexOracle.sol:47 ```solidity=47 address public updater; // Updater account. Has rights for update prices ``` rephrase "to update" ##### Status. NEW ______ #### LOW-10. At - NarfexOracle.sol:48 ```solidity=48 address public USDT; // Tether address in current network ``` "the current" ##### Status. NEW ______ #### LOW-11. At - NarfexOracle.sol:48 ```solidity=48 address public USDT; // Tether address in current network ``` declare immutable ##### Status. NEW ______ #### LOW-12. At - NarfexOracle.sol:50 ```solidity=50 event SetUpdater(address updaterAddress); ``` add events emitted on important settings set ##### Status. NEW ______ #### LOW-13. At - NarfexOracle.sol:62,72 ```solidity=62 // Returns ratio ``` ```solidity=72 // Returns token USD price ``` use NatSpec ##### Status. NEW ______ #### LOW-14. At - NarfexOracle.sol:95,108,174-179 ```solidity=95 for (uint i = 0; i < length; i++) { ``` ```solidity=108 for (uint i = 0; i < length; i++) { ``` ```solidity=174 for (uint i = 0; i < fiatsLength; i++) { responseTokens[i] = fiats[i]; } for (uint i = 0; i < coinsLength; i++) { responseTokens[fiatsLength + i] = coins[i]; } ``` use unchecked declaration to disable embedded safemath for i++ ##### Status. NEW ______ #### LOW-15. At - NarfexOracle.sol:157-160,164-166 ```solidity=157 /// @return Array of fiats addresses function getFiats() public view returns (address[] memory) { return fiats; } ``` ```solidity=164 function getCoins() public view returns (address[] memory) { return coins; } ``` fiats are declared as a public variable with default getter function. This is duplication. Consider removal. ##### Status. NEW ______ #### LOW-16. At - NarfexOracle.sol:188 ```solidity=188 function getSettings() public view returns ( ``` why do you need this function at all? ##### Status. NEW ______ #### LOW-17. At - NarfexOracle.sol:197 ```solidity=197 for (uint i; i < length; i++) { ``` prevent the warning from slither, use "uint i = 0;: ##### Status. NEW ______ #### LOW-18. At - NarfexOracle.sol:233 ```solidity=233 for (uint i; i < _tokens.length; i++) { ``` prevent the warning from slither, use "uint i = 0;: ##### Status. NEW ______ #### LOW-19. At - NarfexOracle.sol:253 ```solidity=253 token.price = _price; ``` emit event? ##### Status. NEW ______ #### LOW-20. At - NarfexOracle.sol:256 ```solidity=256 token.isFiat = true; ``` instead, it's safer to require(token.isFiat); ##### Status. NEW ______ #### LOW-21. At - NarfexOracle.sol:276 ```solidity=276 function removeTokenFromFiats(address _address) public onlyOwner { ``` use openzeppelin EnumerableSet ##### Status. NEW ______ #### LOW-22. At - NarfexOracle.sol:313-399 ```solidity=313 function updateDefaultSettings( int _fiatCommission, int _cryptoCommission, uint _reward ) public onlyOwner { defaultFiatCommission = _fiatCommission; defaultCryptoCommission = _cryptoCommission; defaultReward = _reward; } /// @notice Update tokens commissions /// @param tokensToCustom Array of tokens addresses which should stop using the default value /// @param tokensToDefault Array of tokens addresses which should start using the default value /// @param tokensChanged Array of tokens addresses that will receive changes /// @param newValues An array of commissions corresponding to an array of tokens /// @dev Only owner can use it function updateCommissions( address[] calldata tokensToCustom, address[] calldata tokensToDefault, address[] calldata tokensChanged, int[] calldata newValues ) public onlyOwner { require (tokensChanged.length == newValues.length, "Changed tokens length do not match values length"); for (uint i = 0; i < tokensToCustom.length; i++) { Token storage token = tokens[tokensToCustom[i]]; token.isCustomCommission = true; if (!token.isFiat) { coins.push(tokensToCustom[i]); } } for (uint i = 0; i < tokensToDefault.length; i++) { Token storage token = tokens[tokensToCustom[i]]; token.isCustomCommission = false; if (!token.isFiat) { removeTokenFromCoins(tokensToDefault[i]); } } for (uint i = 0; i < tokensChanged.length; i++) { tokens[tokensToCustom[i]].commission = newValues[i]; } } /// @notice Update default values and tokens commissions by one request /// @param _defaultFiatCommission Default fiat commission /// @param _defaultCryptoCommission Default coin commission /// @param _defaultReward Default referral reward percent /// @param tokensToCustom Array of tokens addresses which should stop using the default value /// @param tokensToDefault Array of tokens addresses which should start using the default value /// @param tokensChanged Array of tokens addresses that will receive changes /// @param newValues An array of commissions corresponding to an array of tokens /// @dev Only owner can use it function updateAllCommissions( int _defaultFiatCommission, int _defaultCryptoCommission, uint _defaultReward, address[] calldata tokensToCustom, address[] calldata tokensToDefault, address[] calldata tokensChanged, int[] calldata newValues ) public onlyOwner { updateCommissions(tokensToCustom, tokensToDefault, tokensChanged, newValues); updateDefaultSettings(_defaultFiatCommission, _defaultCryptoCommission, _defaultReward); } /// @notice Update referral rewards percents for many fiats /// @param tokensToCustom Array of tokens addresses which should stop using the default value /// @param tokensToDefault Array of tokens addresses which should start using the default value /// @param tokensChanged Array of tokens addresses that will receive changes /// @param newValues An array of percents corresponding to an array of tokens /// @dev Only owner can use it function updateReferralPercents( address[] calldata tokensToCustom, address[] calldata tokensToDefault, address[] calldata tokensChanged, uint[] calldata newValues ) public onlyOwner { require (tokensChanged.length == newValues.length, "Changed tokens length do not match values length"); for (uint i = 0; i < tokensToCustom.length; i++) { tokens[tokensToCustom[i]].isCustomReward = true; } for (uint i = 0; i < tokensToDefault.length; i++) { tokens[tokensToCustom[i]].isCustomReward = false; } for (uint i = 0; i < tokensChanged.length; i++) { tokens[tokensToCustom[i]].reward = newValues[i]; } } ``` it looks like it should not be part of the oracle ##### Status. NEW ______ #### LOW-23. At - NarfexOracle.sol:317 ```solidity=317 ) public onlyOwner { ``` emit event ##### Status. NEW