# Security Audit of `SpotV1` contracts. ## Conclusion This audit was made by Auditor: TODO. Date: 2023-03-24 ## Scope https://testnet.bscscan.com/address/0x36AD17070546CAB7BcCFecbe1e8DBC45DE933191 - IRDNRegistry.sol - RDNRegistry.sol - WithdrawAnyERC20Token.sol https://testnet.bscscan.com/address/0xBC526044b63b0aEe97F66195B956aA932C6776e9 - ERC20Spot.sol - SafeUniswapV2Router.sol - SpotV1.sol - IRDNRegistry.sol - RDNOwnable.sol https://testnet.bscscan.com/address/0x81953203B4DDa69eF89d4a2370B5DA7E782a6Fc6 - ISpotV1.sol - SpotFactoryV1.sol - IRDNRegistry.sol md5 hashes: - 557e42e5e9eee64eab9ab17c5ccad488 contracts/ERC20Spot.sol - d9f4aa3a24ec1985b279e625e7f2525e contracts/IRDNRegistry.sol - b291a12383ca9ed97198831ee0b33803 contracts/ISpotV1.sol - 8fb5c78bf695e5f0b83aa771d4a5f9ea contracts/RDNOwnable.sol - baff4823cf421cdb2a4bf02291a72f62 contracts/RDNRegistry.sol - fda3b5335bab4284a0378c54118ac48d contracts/SafeUniswapV2Router.sol - c6f88a1e59d65fe467cd08244101049c contracts/SpotFactoryV1.sol - 098ea1a4a7ecf95f32875716d46289ce contracts/SpotV1.sol - 22bad6e816918f504a649cd45228888c contracts/WithdrawAnyERC20Token.sol ## 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 #### MAJOR-1. At - SafeUniswapV2Router.sol:50 ```solidity=50 return router.addLiquidity(tokenA, tokenB, amountA, amountB, 0, 0, to, deadline); ``` - SafeUniswapV2Router.sol:76 ```solidity=76 (amountA, amountB) = router.removeLiquidity(tokenA, tokenB, balance, 0, 0, to, deadline); ``` minAmountOut=0 sandwich transaction / MEV Flashbots attacks are possible ##### Status. NEW ______ #### MAJOR-2. At - SpotFactoryV1.sol:63 ```solidity=63 IERC20(swap0.path[0]).safeTransferFrom(msg.sender, address(this), amount); ``` some deflationary or fee-on-transfer tokens may deliver you different amount of tokens, consider using balanceOf check to ensure how much tokens did you receive after transferFrom ##### Status. NEW ______ #### MAJOR-3. At - SpotFactoryV1.sol:64 ```solidity=64 IERC20(swap0.path[0]).approve(spot, amount); ``` use safeApprove ##### Status. NEW ______ #### MAJOR-4. At - SpotV1.sol:82-89 ```solidity=82 function deposit( uint amount, Swap memory swap0, Swap memory swap1, Swap memory swapReward0, Swap memory swapReward1, uint deadline ) public payable { ``` - SpotV1.sol:129-136 ```solidity=129 function withdraw( uint amountToBurn, Swap memory swap0, Swap memory swap1, Swap memory swapReward0, Swap memory swapReward1, uint deadline ) public onlyRDNOwner(msg.sender) { ``` - SpotV1.sol:172-174 ```solidity=172 function callAny(address payable _addr, bytes memory _data) public payable onlyRDNOwner(msg.sender) returns(bool success, bytes memory data){ (success, data) = _addr.call{value: msg.value}(_data); } ``` - SpotV1.sol:176-180 ```solidity=176 function restake( Swap memory swapReward0, Swap memory swapReward1, uint deadline ) public { ``` use nonReentrant modifier to protect fron reentrancy attacks ##### Status. NEW ______ #### MAJOR-5. At - SpotV1.sol:108 ```solidity=108 IERC20(swap0.path[0]).safeTransferFrom(msg.sender, address(this), amount); ``` some deflationary or fee-on-transfer tokens may deliver you different amount of tokens, consider using balanceOf check to ensure how much tokens did you receive after transferFrom, otherwise your code will not work with such tokens because receiving amount < transferred ##### Status. NEW ______ #### MAJOR-6. At - SpotV1.sol:287-288 ```solidity=287 0, 0, ``` - SpotV1.sol:310-311 ```solidity=310 0, 0, ``` no minAmountOut sandwich attacks / MVE flashbots possible ##### Status. NEW ______ #### MAJOR-7. At - RDNOwnable.sol:22 ```solidity=22 require(IRDNRegistry(registry).getUserIdByAddress(_userAddress) == ownerId, "RDNOwnable: access denied"); ``` getUserIdByAddress returns 0 for non existent _userAddress, in case if ownerId=0, any non-existent user may pass this check. I'm not sure how serious is it. Probably you will never set ownerId=0. Maybe it makes sense to add explicit require that ownerId!=0. And add explaining comment to the code. Maybe use isRegisteredByAddress check. ##### Status. NEW ______ #### WARNING-1. At - SpotFactoryV1.sol:13-17 ```solidity=13 // todo // ownerid shoud be active / RDNConnected // tariffs / subscription rules // onlyRDN // BNB create and deposit ``` - SpotV1.sol:239 ```solidity=239 // todo: return remained ``` - SpotV1.sol:268 ```solidity=268 // todo: return remained ``` resolve todo ##### Status. NEW ______ #### WARNING-2. At - WithdrawAnyERC20Token.sol:18 ```solidity=18 if (_isDefaultAdminRole) { ``` i believe it should always happen otherwise there will be no admin ##### Status. NEW ______ #### WARNING-3. At - WithdrawAnyERC20Token.sol:25 ```solidity=25 IERC20(_token).transfer(_target, _amount); ``` use safeTransfer ##### Status. NEW ______ #### WARNING-4. At - SafeUniswapV2Router.sol:12-22 ```solidity=12 function safeSwapExactTokensForTokens( IUniswapV2Router router, uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline ) internal returns (uint256[] memory amounts) { if (path[0] != path[path.length - 1]) amounts = router.swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline); } ``` you may want to use swapExactTokensForTokensSupportingFeeOnTransferTokens https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L339 ##### Status. NEW ______ #### WARNING-5. At - SafeUniswapV2Router.sol:24 ```solidity=24 function addAllLiquidity( ``` please note in the function comment that it may not spend all of your tokens balance just to not misuse it ##### Status. NEW ______ #### WARNING-6. At - SpotV1.sol:17-30 ```solidity=17 // todo // return remained !!! // swap fees support !!! // safeERC20 !!! // BNB support !!! // restake rules // transfers logs to userId // informative name and symbol // owner should be active // tariffs / subscription rules // deposit / withdraw without restake ?? // APR / APY // StableSwap support // !!!!!! init functions permissions, close reInit ``` resolve todos ##### Status. NEW ______ #### WARNING-7. At - SpotV1.sol:42 ```solidity=42 address public pool; ``` there is a lot of logic about interacting with IMasterChef2, but in the example contract there is no pool set so I cannot check interactions - https://testnet.bscscan.com/address/0xBC526044b63b0aEe97F66195B956aA932C6776e9#readContract pool==address(0) probably this one is meant to be used https://bscscan.com/address/0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652#code ##### Status. NEW ______ #### WARNING-8. At - SpotV1.sol:104 ```solidity=104 if (msg.value > 0) { ``` require path[0] == wETH? ##### Status. NEW ______ #### WARNING-9. At - SpotV1.sol:105 ```solidity=105 amount = msg.value; ``` require amount == msg.value? ##### Status. NEW ______ #### WARNING-10. At - SpotV1.sol:121 ```solidity=121 amountToMint = ((afterDepositBalance - beforeDepositBalance) * beforeDepositSupply) / beforeDepositBalance; ``` - SpotV1.sol:152 ```solidity=152 uint amountToWithdraw = ((amountToBurn * beforeWithdrawBalance)) / beforeWithdrawSupply; ``` tiny rounding error may possible what may lead to tokens dust aggregation ##### Status. NEW ______ #### WARNING-11. At - SpotV1.sol:282-291 ```solidity=282 IUniswapV2Router(_router).addLiquidity( token0, token1, amountIn0, amountIn1, 0, 0, address(this), deadline ); ``` unused tokens remains on the contract balance, check https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router01.sol#L58 it transfers only amount needed for even addLiquidity ensure _returnRemainder is always called ##### Status. NEW ______ #### WARNING-12. At - SpotV1.sol:336 ```solidity=336 IUniswapV2Router(router).swapExactTokensForTokensSupportingFeeOnTransferTokens( ``` do you want to support Native currency? ##### Status. NEW ______ #### WARNING-13. At - SpotV1.sol:360 ```solidity=360 if (tokens[i] == address(0)) continue; ``` should we process native eth in that case? ##### Status. NEW ______ #### WARNING-14. At - RDNOwnable.sol:14 ```solidity=14 ownerId = _ownerId; ``` ownerId is unchangeable, maybe you will want to add a transferOwnership method (then not forget to change Factory mapping) ##### Status. NEW ______ #### WARNING-15. At - ERC20Spot.sol:10 ```solidity=10 mapping(uint => uint) private _balances; ``` it's not clear why mapping is from userId=>amount not address=>amount as usual. What is the overall business rationale behind this userId uint based mechanism? Can you write explaining comment? ##### Status. NEW ______ #### WARNING-16. At - ERC20Spot.sol:11 ```solidity=11 mapping(address => mapping(address => uint)) private _allowances; ``` allowance is given to address not userId, ensure there is no misusing possible ##### Status. NEW ______ #### WARNING-17. At - ERC20Spot.sol:26 ```solidity=26 uint userId = IRDNRegistry(registry).getUserIdByAddress(_account); ``` be careful with non existent user ##### Status. NEW ______ #### WARNING-18. At - ERC20Spot.sol:50-64 ```solidity=50 function transfer(address to, uint amount) external returns (bool) { return false; } function approve(address spender, uint amount) external returns (bool) { return false; } function transferFrom( address from, address to, uint amount ) external returns (bool) { return false; } ``` not all DeFi projects correctly implement erc20 interface call and check return value after the call, so the may think that transfer did success because it did not fail, consider add a revert inside ##### Status. NEW ______ #### WARNING-19. At - ERC20Spot.sol:76 ```solidity=76 } ``` - ERC20Spot.sol:91 ```solidity=91 } ``` external DeFi tool may wrongly estimate account balance. Transfer is emitted only on mint/burn however in RDNRegistry user may transfer the userId ownership to another wallet, no Transfer event will be emited in this case and some DeFi tools will still think that the previous wallet has this balance, but its not. ##### Status. NEW ______ #### WARNING-20. At - RDNRegistry.sol:120 ```solidity=120 users[_userId].userAddress = _newAddress; ``` emit event, it's important state change you may want to catch it on backend/frontend ##### Status. NEW ______ #### WARNING-21. At - RDNRegistry.sol:138 ```solidity=138 changeAddressAccess[_userId][_grantedAddress] = false; ``` changeAddressAddresses is not cleared, i'm not sure how serious is it, maybe use EnumerableSet for changeAddressAddresses for cheap deletion ##### Status. NEW ______ #### WARNING-22. At - RDNRegistry.sol:146 ```solidity=146 function levelUp(uint _userId, uint _level) public onlyRole(LEVELUP_ROLE) onlyValidUser(_userId) { ``` role system is account based, it may happen that account userId will change but the permission remain the same, is it OK? ##### Status. NEW ______ #### WARNING-23. At - RDNRegistry.sol:180 ```solidity=180 function grantCompleteAdmin(address _admin) public { ``` - RDNRegistry.sol:192 ```solidity=192 function revokeCompleteAdmin(address _admin) public { ``` place onlyRole(DEFAULT_ADMIN_ROLE) modifier and use internal _grantRole to avoid multiple role checks and to state in the declaration that the method is for admin only ##### Status. NEW ______ #### WARNING-24. At - RDNRegistry.sol:218 ```solidity=218 function _addUser(address _userAddress, uint _parentId) private returns(uint) { ``` any parent may be set by the user, I don't know which mechanism will you use but you could add signature backend verification to prevent setting wrong parent ##### Status. NEW ______ #### WARNING-25. At - RDNRegistry.sol:290-312 ```solidity=290 function isActive(uint _userId) public view returns(bool) { return (users[_userId].activeUntill > block.timestamp); } function getParentId(uint _userId) public view returns(uint) { return users[_userId].parentId; } function getLevel(uint _userId) public view returns(uint) { return users[_userId].level; } function getTariff(uint _userId) public view returns(uint) { return users[_userId].tariff; } function getActiveUntill(uint _userId) public view returns(uint) { return users[_userId].activeUntill; } function getUserAddress(uint _userId) public view returns(address) { return users[_userId].userAddress; } ``` - RDNRegistry.sol:318-320 ```solidity=318 function getUser(uint _userId) public view returns(User memory) { return users[_userId]; } ``` maybe use isValidUser or isRegisteredByAddress ##### Status. NEW ______ #### WARNING-26. At - RDNRegistry.sol:322-324 ```solidity=322 function getUserIdByAddress(address _userAddress) public view returns(uint) { return userId[_userAddress]; } ``` it returns 0 if user not exist. It may lead to misuse the info for userId=0. Place isRegistered or is isValidUser modifier ##### Status. NEW ______ #### WARNING-27. At - RDNRegistry.sol:343 ```solidity=343 function isHasChangeAddressAccess(uint _userId, address _grantedAddress) public view returns(bool) { ``` check if _userId is valid, otherwise isHasChangeAddressAccess(666, address(0))==true ##### Status. NEW ______ #### LOW-1. At - ERC20Spot.sol:6 ```solidity=6 ``` - ERC20Spot.sol:21 ```solidity=21 ``` - ERC20Spot.sol:24 ```solidity=24 ``` - RDNRegistry.sol:350-351 ```solidity=350 ``` remove blank line, follow style guide - https://docs.soliditylang.org/en/v0.8.17/style-guide.html ##### Status. NEW ______ #### LOW-2. At - RDNOwnable.sol:11 ```solidity=11 function initRDNOwnable(address _registry, uint _ownerId) internal { ``` - ERC20Spot.sol:16 ```solidity=16 function initERC20Spot(address _registry, uint _ownerId) internal { ``` prefix internal method with _ ##### Status. NEW ______ #### LOW-3. At - RDNOwnable.sol:7 ```solidity=7 contract RDNOwnable { ``` - ERC20Spot.sol:8 ```solidity=8 contract ERC20Spot is IERC20, RDNOwnable { ``` put the "abstract" declaration for the contract which will not be deployed but just used as a parent contract ##### Status. NEW ______ #### LOW-4. At - SpotV1.sol:94 ```solidity=94 if (IMasterChef2(pool).pendingCake(poolIndex, address(this)) > 0) { ``` - SpotV1.sol:141 ```solidity=141 if (IMasterChef2(pool).pendingCake(poolIndex, address(this)) > 0) { ``` use _pool, _poolIndex ##### Status. NEW ______ #### LOW-5. At - WithdrawAnyERC20Token.sol:24-26 ```solidity=24 function withdrawAnyERC20Token(address _token, address _target, uint _amount) public onlyRole(WITHDRAWANY_ROLE) { IERC20(_token).transfer(_target, _amount); } ``` - SafeUniswapV2Router.sol:12 ```solidity=12 function safeSwapExactTokensForTokens( ``` - SafeUniswapV2Router.sol:12 ```solidity=12 function safeSwapExactTokensForTokens( ``` - SafeUniswapV2Router.sol:24 ```solidity=24 function addAllLiquidity( ``` - SafeUniswapV2Router.sol:53 ```solidity=53 function removeAllLiquidity( ``` this function is never used in the scope of this audit ##### Status. NEW ______ #### LOW-6. At - WithdrawAnyERC20Token.sol:3 ```solidity=3 // SPDX-License-Identifier: MIT ``` - SpotFactoryV1.sol:2 ```solidity=2 // SPDX-License-Identifier: MIT ``` - SpotV1.sol:2 ```solidity=2 // SPDX-License-Identifier: MIT ``` - ISpotV1.sol:2 ```solidity=2 // SPDX-License-Identifier: MIT ``` - ERC20Spot.sol:2 ```solidity=2 // SPDX-License-Identifier: MIT ``` place on top https://docs.soliditylang.org/en/v0.8.19/layout-of-source-files.html#spdx-license-identifier ##### Status. NEW ______ #### LOW-7. At - WithdrawAnyERC20Token.sol:8-12 ```solidity=8 // interface IWithdrawAnyERC20Token { // function withdrawAnyERC20Token(address _token, address _target, uint _amount) external; // } ``` remove ##### Status. NEW ______ #### LOW-8. At - WithdrawAnyERC20Token.sol:15 ```solidity=15 bytes32 public constant WITHDRAWANY_ROLE = keccak256("WITHDRAWANY_ROLE"); ``` place underscore ##### Status. NEW ______ #### LOW-9. At - WithdrawAnyERC20Token.sol:19 ```solidity=19 _setupRole(DEFAULT_ADMIN_ROLE, _admin); ``` - WithdrawAnyERC20Token.sol:21 ```solidity=21 _setupRole(WITHDRAWANY_ROLE, _admin); ``` deprecated use _grantRole ##### Status. NEW ______ #### LOW-10. At - WithdrawAnyERC20Token.sol:24-26 ```solidity=24 function withdrawAnyERC20Token(address _token, address _target, uint _amount) public onlyRole(WITHDRAWANY_ROLE) { IERC20(_token).transfer(_target, _amount); } ``` emit event to make transfers made by calling withdrawAnyERC20Token ##### Status. NEW ______ #### LOW-11. At - WithdrawAnyERC20Token.sol:24-26 ```solidity=24 function withdrawAnyERC20Token(address _token, address _target, uint _amount) public onlyRole(WITHDRAWANY_ROLE) { IERC20(_token).transfer(_target, _amount); } ``` you may also add withdrawAnyNative method ##### Status. NEW ______ #### LOW-12. At - SafeUniswapV2Router.sol:1 ```solidity=1 // SPDX-License-Identifier: BSD-3-Clause ``` SPDX license is different from other files, are you sure? ##### Status. NEW ______ #### LOW-13. At - SafeUniswapV2Router.sol:2 ```solidity=2 pragma solidity ^0.8.6; ``` maybe use "0.8.17" for consistency ##### Status. NEW ______ #### LOW-14. At - SafeUniswapV2Router.sol:12-22 ```solidity=12 function safeSwapExactTokensForTokens( IUniswapV2Router router, uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline ) internal returns (uint256[] memory amounts) { if (path[0] != path[path.length - 1]) amounts = router.swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline); } ``` what safe check this function makes? it's not clear ##### Status. NEW ______ #### LOW-15. At - SafeUniswapV2Router.sol:20 ```solidity=20 if (path[0] != path[path.length - 1]) ``` check that path.length > 0 ##### Status. NEW ______ #### LOW-16. At - SafeUniswapV2Router.sol:21 ```solidity=21 amounts = router.swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline); ``` use named parameters to avoid misplacing args, see https://docs.soliditylang.org/en/v0.8.19/control-structures.html#function-calls-with-named-parameters ##### Status. NEW ______ #### LOW-17. At - SafeUniswapV2Router.sol:33-35 ```solidity=33 uint256, uint256, uint256 ``` place NatSpec description of return values ##### Status. NEW ______ #### LOW-18. At - SafeUniswapV2Router.sol:39-42 ```solidity=39 if (IERC20(tokenA).allowance(address(this), address(router)) > 0) { IERC20(tokenA).safeApprove(address(router), 0); } IERC20(tokenA).safeApprove(address(router), amountA); ``` - SafeUniswapV2Router.sol:45-48 ```solidity=45 if (IERC20(tokenB).allowance(address(this), address(router)) > 0) { IERC20(tokenB).safeApprove(address(router), 0); } IERC20(tokenB).safeApprove(address(router), amountB); ``` - SafeUniswapV2Router.sol:71-74 ```solidity=71 if (IERC20(pair).allowance(address(this), address(router)) > 0) { IERC20(pair).safeApprove(address(router), 0); } IERC20(pair).safeApprove(address(router), balance); ``` what is the purpose of this? why do we need to set allowance to 0 ##### Status. NEW ______ #### LOW-19. At - SpotFactoryV1.sol:11 ```solidity=11 contract SpotFactoryV1 { ``` what is the purpose of this contract? write detailed NatSpec docs ##### Status. NEW ______ #### LOW-20. At - SpotFactoryV1.sol:21 ```solidity=21 using SafeERC20 for IERC20; ``` place on the top of the contract code ##### Status. NEW ______ #### LOW-21. At - SpotFactoryV1.sol:30 ```solidity=30 mapping(uint => mapping(uint => address)) public spots; ``` what is the key in this mapping, add comment ##### Status. NEW ______ #### LOW-22. At - SpotFactoryV1.sol:51-54 ```solidity=51 ISpotV1.Swap memory swap0, ISpotV1.Swap memory swap1, ISpotV1.Swap memory swapReward0, ISpotV1.Swap memory swapReward1, ``` unclear semantics, consider documenting in NatSpec what is swap0 and swap1 ##### Status. NEW ______ #### LOW-23. At - SpotFactoryV1.sol:61 ```solidity=61 ISpotV1(spot).deposit{value: msg.value}(amount, swap0, swap1, swapReward0, swapReward1, deadline); ``` - SpotFactoryV1.sol:65 ```solidity=65 ISpotV1(spot).deposit(amount, swap0, swap1, swapReward0, swapReward1, deadline); ``` - SpotFactoryV1.sol:78 ```solidity=78 ISpotV1(spot).init(wrapper, pool, router, stakingToken, rewardToken, poolIndex, ownerId, registry, address(this)); ``` use named parameters for readability and to avoid misplacing args, see https://docs.soliditylang.org/en/v0.8.19/control-structures.html#function-calls-with-named-parameters ##### Status. NEW ______ #### LOW-24. At - SpotFactoryV1.sol:80 ```solidity=80 // pools[ownerId].push(poolIndex); ``` remove this comment ##### Status. NEW ______ #### LOW-25. At - SpotFactoryV1.sol:89 ```solidity=89 uint poolsCount = IMasterChef2(pool).poolLength(); ``` IMasterChef2 is not part of this audit scope, I cannot be sure this is a valid pools length ##### Status. NEW ______ #### LOW-26. At - SpotFactoryV1.sol:98 ```solidity=98 return spots[ownerId][poolIndex]; ``` add a check that this spot exists ##### Status. NEW ______ #### LOW-27. At - SpotV1.sol:13 ```solidity=13 contract SpotV1 is ERC20Spot { ``` inherit ISpotV1 ##### Status. NEW ______ #### LOW-28. At - SpotV1.sol:33-35 ```solidity=33 // interaction changes: // wrapper in init // deposit BNB, withdraw BNB ``` remove these comments ##### Status. NEW ______ #### LOW-29. At - SpotV1.sol:37-40 ```solidity=37 struct Swap { address[] path; uint outMin; } ``` remove it because it's already a part of ISpotV1 ##### Status. NEW ______ #### LOW-30. At - SpotV1.sol:43 ```solidity=43 address public router; ``` maybe rename as IMasterChef2 public masterChef2Pool; for clarity ##### Status. NEW ______ #### LOW-31. At - SpotV1.sol:51 ```solidity=51 address public wrapper; ``` maybe delcare it as "IWrapper public wBNB;" ##### Status. NEW ______ #### LOW-32. At - SpotV1.sol:53 ```solidity=53 constructor() { ``` why is it empty? note that this contract is intended to be used with proxy ##### Status. NEW ______ #### LOW-33. At - SpotV1.sol:82-89 ```solidity=82 function deposit( uint amount, Swap memory swap0, Swap memory swap1, Swap memory swapReward0, Swap memory swapReward1, uint deadline ) public payable { ``` - SpotV1.sol:129-136 ```solidity=129 function withdraw( uint amountToBurn, Swap memory swap0, Swap memory swap1, Swap memory swapReward0, Swap memory swapReward1, uint deadline ) public onlyRDNOwner(msg.sender) { ``` unclear functions semantic consider documenting ##### Status. NEW ______ #### LOW-34. At - SpotV1.sol:161 ```solidity=161 require(sentRecipient, "transfer ETH to recipeint failed"); ``` typo in word recipient (also not ETH but BNB) ##### Status. NEW ______ #### LOW-35. At - SpotV1.sol:166 ```solidity=166 //burn ``` comment not need ##### Status. NEW ______ #### LOW-36. At - SpotV1.sol:214 ```solidity=214 function _buyLiquidity( ``` what does this function supposed to do? why does it have 2 swap arguments? ##### Status. NEW ______ #### LOW-37. At - SpotV1.sol:220 ```solidity=220 require(swap0.path[0] == swap1.path[0], "start tokens should be equal"); ``` explain. write comments ##### Status. NEW ______ #### LOW-38. At - SpotV1.sol:323 ```solidity=323 token.safeApprove(spender, 0); ``` why do we need this? ##### Status. NEW ______ #### LOW-39. At - SpotV1.sol:334 ```solidity=334 if (path[0] == path[path.length - 1]) return; ``` maybe fail? it's something really unexpected ##### Status. NEW ______ #### LOW-40. At - SpotV1.sol:337 ```solidity=337 // IUniswapV2Router(router).swapExactTokensForTokens( ``` remove comment ##### Status. NEW ______ #### LOW-41. At - SpotV1.sol:366 ```solidity=366 require(sentRecipient, "transfer ETH to recipeint failed"); ``` typo in word recipient (also in fact it's not ETH it's BNB) ##### Status. NEW ______ #### LOW-42. At - IRDNRegistry.sol:12 ```solidity=12 uint activeUntill; ``` - IRDNRegistry.sol:40 ```solidity=40 function getActiveUntill(uint _userId) external view returns(uint); ``` - IRDNRegistry.sol:48 ```solidity=48 function setActiveUntill(uint _userId, uint _activeUntill) external; ``` typo in word "until" ##### Status. NEW ______ #### LOW-43. At - RDNOwnable.sol:8-9 ```solidity=8 address public registry; uint public ownerId; ``` consider the usage of immutable declaration or add setter ##### Status. NEW ______ #### LOW-44. At - RDNOwnable.sol:13 ```solidity=13 require(IRDNRegistry(registry).isValidUser(_ownerId)); ``` add revert message for clarity of failed transactions ##### Status. NEW ______ #### LOW-45. At - RDNOwnable.sol:17 ```solidity=17 // modifier RDNOnly(address _sender) { ``` remove unused code ##### Status. NEW ______ #### LOW-46. At - ERC20Spot.sol:13-14 ```solidity=13 string private _name; string private _symbol; ``` use immutable declaration ##### Status. NEW ______ #### LOW-47. At - ERC20Spot.sol:18 ```solidity=18 _name = "SPOTv0 Share Token"; ``` what does v0 mean? Consider renaming to v1 or explain what does v0 mean ##### Status. NEW ______ #### LOW-48. At - ERC20Spot.sol:28 ```solidity=28 } ``` maybe it's useful to have function balanceOf(uint _userId) as well ##### Status. NEW ______ #### LOW-49. At - ERC20Spot.sol:31 ```solidity=31 return _allowances[owner][spender]; ``` this will always return 0, since approve is not implemented ##### Status. NEW ______ #### LOW-50. At - ERC20Spot.sol:68 ```solidity=68 require(account != address(0), "ERC20: mint to the zero address"); ``` everywhere, in general string error messages are suboptimal, consider the usage of custom solidity errors, see - https://blog.soliditylang.org/2021/04/21/custom-errors/ https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2839 ##### Status. NEW ______ #### LOW-51. At - RDNRegistry.sol:9 ```solidity=9 // Parent 0 address restricted ``` make the comment more clear ##### Status. NEW ______ #### LOW-52. At - RDNRegistry.sol:15-29 ```solidity=15 bytes32 public constant SETLEVEL_ROLE = keccak256("SETLEVEL_ROLE"); // admin role for userlevel change (levelUp function) bytes32 public constant LEVELUP_ROLE = keccak256("LEVELUP_ROLE"); // admin role for changing factors contract address bytes32 public constant FACTORSADDRESS_ROLE = keccak256("FACTORSADDRESS_ROLE"); // admin role for RDNPOS contract bytes32 public constant TARIFFUPDATE_ROLE = keccak256("TARIFFUPDATE_ROLE"); // admin role for RDNPOS contract bytes32 public constant ACTIVEUNTILLUPDATE_ROLE = keccak256("ACTIVEUNTILLUPDATE_ROLE"); // admin role fore points rate updating bytes32 public constant POINTSRATEUPDATE_ROLE = keccak256("POINTSRATEUPDATE_ROLE"); // admin role for RDNDistributors configuration bytes32 public constant SETDISTRIBUTOR_ROLE = keccak256("SETDISTRIBUTOR_ROLE"); // admin role for adding custom users bytes32 public constant ADDUSERBYADMIN_ROLE = keccak256("ADDUSERBYADMIN_ROLE"); ``` Rename SETLEVEL_ROLE to SET_LEVEL_ROLE and update references accordingly for better readability. ##### Status. NEW ______ #### LOW-53. At - RDNRegistry.sol:20 ```solidity=20 // admin role for RDNPOS contract ``` - RDNRegistry.sol:22 ```solidity=22 // admin role for RDNPOS contract ``` unclear comment ##### Status. NEW ______ #### LOW-54. At - RDNRegistry.sol:32 ```solidity=32 mapping (address => uint) public userId; ``` - RDNRegistry.sol:34 ```solidity=34 User[] public users; ``` - RDNRegistry.sol:36-37 ```solidity=36 uint public usersCount; mapping(uint => uint[]) public children; ``` - RDNRegistry.sol:32 ```solidity=32 mapping (address => uint) public userId; ``` - RDNRegistry.sol:40-41 ```solidity=40 mapping(uint => mapping(address => bool)) public changeAddressAccess; mapping(uint => address[]) public changeAddressAddresses; ``` Consider declaring variables like userId, users, and children as internal to avoid having duplicate getters. ##### Status. NEW ______ #### LOW-55. At - RDNRegistry.sol:35-36 ```solidity=35 // gas saving counter uint public usersCount; ``` it's saves nothing, array length is places at the storage slot of the array pointer and cheap to SLOAD (the same cheap as sload usersCount), see e.g. https://mixbytes.io/blog/collisions-solidity-storage-layouts ##### Status. NEW ______ #### LOW-56. At - RDNRegistry.sol:37 ```solidity=37 mapping(uint => uint[]) public children; ``` maybe use EnumerableSet for cheap checks for "consist" in future in case if you will need it ##### Status. NEW ______ #### LOW-57. At - RDNRegistry.sol:41 ```solidity=41 mapping(uint => address[]) public changeAddressAddresses; ``` never used for any logic, consider removal ##### Status. NEW ______ #### LOW-58. At - RDNRegistry.sol:44 ```solidity=44 address public factorsAddress; ``` this variable is never used in the scope of this audit ##### Status. NEW ______ #### LOW-59. At - RDNRegistry.sol:46 ```solidity=46 // token => rate (rate is 1/USDprice for token, based in token.decimals) ``` the comment is unclear, what rate is it ##### Status. NEW ______ #### LOW-60. At - RDNRegistry.sol:47 ```solidity=47 mapping (address => uint) public pointsRate; ``` this is never used in the scope of this audit for any logic ##### Status. NEW ______ #### LOW-61. At - RDNRegistry.sol:58 ```solidity=58 // when users activeUntill updated ``` everywhere in project fix typo in word "until" ##### Status. NEW ______ #### LOW-62. At - RDNRegistry.sol:71-72 ```solidity=71 * @param _root: userAdrress for userId = 1 * @param _admin: default admin ``` typo in word address ##### Status. NEW ______ #### LOW-63. At - RDNRegistry.sol:71 ```solidity=71 * @param _root: userAdrress for userId = 1 ``` NatSpec comments do not need ":" ##### Status. NEW ______ #### LOW-64. At - RDNRegistry.sol:80 ```solidity=80 //add root user (userId 1), referencing parantId=0. ``` typo in word parentId ##### Status. NEW ______ #### LOW-65. At - RDNRegistry.sol:81 ```solidity=81 User memory _rootUser = User(12, _root, 0, 7, block.timestamp + 36500 days, block.timestamp); ``` what does the magic value "12" and "7" mean? ##### Status. NEW ______ #### LOW-66. At - RDNRegistry.sol:117 ```solidity=117 emit UserAddressChanged(_userId, _newAddress, msg.sender, users[_userId].userAddress); ``` tiny gas optimisation is possible ##### Status. NEW ______ #### LOW-67. At - RDNRegistry.sol:126-127 ```solidity=126 function grantChangeAddressAccess(address _grantedAddress) public onlyRegisteredByAddress(msg.sender) { uint _userId = userId[msg.sender]; ``` gas optimization possible, userId sload twice (inside modifier and below) ##### Status. NEW ______ #### LOW-68. At - RDNRegistry.sol:158 ```solidity=158 factorsAddress = _factorsAddress; ``` maybe emit event here ##### Status. NEW ______ #### LOW-69. At - RDNRegistry.sol:177 ```solidity=177 distributors[_token] = _distributor; ``` emit event ##### Status. NEW ______ #### LOW-70. At - RDNRegistry.sol:205 ```solidity=205 * @notice sender must have 3 roles ``` list all required roles ##### Status. NEW ______ #### LOW-71. At - RDNRegistry.sol:208 ```solidity=208 (uint _userId) = _addUser(_userAddress, _parentId); ``` brackets are not needed ##### Status. NEW ______ #### LOW-72. At - RDNRegistry.sol:211 ```solidity=211 setLevel(_userId, _level); ``` maybe emit specific event to note that the user was added by admin ##### Status. NEW ______ #### LOW-73. At - RDNRegistry.sol:222 ```solidity=222 User memory _user = User(1, _userAddress, _parentId, 0, 0, block.timestamp); ``` better to use User({level: 1, userAddress: _userAddress, ...}) for readability and decreasing the chance to misuse an argument ##### Status. NEW ______ #### LOW-74. At - RDNRegistry.sol:224-228 ```solidity=224 usersCount += 1; userId[_userAddress] = usersCount - 1; children[_parentId].push(usersCount - 1); emit UserAdded(usersCount - 1, _parentId, _userAddress); return (usersCount - 1); ``` SLOAD(usersCount) called 5 times, it's inefficient, consider to put it to stack ##### Status. NEW ______ #### LOW-75. At - RDNRegistry.sol:238 ```solidity=238 require(isRegisteredByAddress(_userAddress), "user not registered"); ``` gas optimisation is possible, instead of using this check as modifier and then get userId you can write getUserIdByAddressSafe reverting if user does not exist, and use it at the beginning of functions ##### Status. NEW ______ #### LOW-76. At - RDNRegistry.sol:242 ```solidity=242 modifier onlyRegistered(uint _userId) { ``` this modifier is never used in the scope of this audit ##### Status. NEW ______ #### LOW-77. At - RDNRegistry.sol:247 ```solidity=247 modifier onlyValidUser(uint _userId) { ``` it's not clear what does it mean "valid" what is the difference with onlyRegisteredByAddress, add comment to explain ##### Status. NEW ______ #### LOW-78. At - RDNRegistry.sol:330 ```solidity=330 function getChildren(uint _userId) public view returns(uint[] memory) { ``` check if _userId is valid ##### Status. NEW