Platform: Code4rena
Start Date: 28/09/2023
Pot Size: $36,500 USDC
Total HM: 5
Participants: 115
Period: 6 days
Judge: 0xDjango
Total Solo HM: 1
Id: 290
League: ETH
Rank: 79/115
Findings: 1
Award: $4.37
π Selected for report: 0
π Solo Findings: 0
π Selected for report: Bauchibred
Also found by: 0x3b, 0xDetermination, 0xMosh, 0xScourgedev, 0xTheC0der, 0xTiwa, 0xWaitress, 0xdice91, 0xfusion, 0xpiken, 0xprinc, 0xweb3boy, ArmedGoose, Aymen0909, Breeje, Brenzee, Daniel526, DavidGiladi, DeFiHackLabs, Flora, Fulum, HChang26, Hama, IceBear, J4X, Krace, KrisApostolov, Maroutis, Mirror, MohammedRizwan, Norah, PwnStars, SPYBOY, TangYuanShen, Testerbot, ThreeSigma, Tricko, al88nsk, alexweb3, ast3ros, berlin-101, bin2chen, blutorque, btk, d3e4, deth, e0d1n, ether_sky, ge6a, gkrastenov, glcanvas, hals, imare, inzinko, jkoppel, jnforja, joaovwfreire, josephdara, kutugu, lotux, lsaudit, mahdirostami, merlin, n1punp, nadin, neumo, nisedo, nobody2018, oakcobalt, orion, peanuts, pep7siup, pina, ptsanev, rokinot, rvierdiiev, said, santipu_, sashik_eth, seerether, squeaky_cactus, terrancrypt, tonisives, twicek, vagrant, xAriextz, y4y
4.3669 USDC - $4.37
address(0)
I checked functions in the Prime.sol file and found that these functions do not check address(0) of input. Including (address[] memory users)
parameter.
If you don't check for user and user is an invalid address (0), you may cause a logic error in the function, because there may be no valid data to calculate or query.
No need to check address(0)
for internal functions. Checking should inherently be checked in public/external functions.
If the function is called from a contract or a user interface, checking the user can help avoid unnecessary errors when calling the function. If an invalid address(0)
is passed in, the function may return an error or not make any changes.
function getPendingInterests(address user) external returns (PendingInterest[] memory pendingInterests) { ... }
function updateScores(address[] memory users) external { ... }
function issue(bool isIrrevocable, address[] calldata users) external { ... }
function xvsUpdated(address user) external { ... }
function updateAssetsState(address _comptroller, address asset) external { ... }
function accrueInterest(address vToken) public {}
Use a modifier responsible for checking for address(0)
.
Some internal functions can become pure functions.
view
function promise not to modify the state.
pure
function promise not to modify or read from the state.
function _checkAlphaArguments(uint128 _alphaNumerator, uint128 _alphaDenominator) internal { ... }
function _xvsBalanceForScore(uint256 xvs) internal view returns (uint256) { ... }
function isEligible(uint256 amount) internal view returns (bool) { ... }
Add or edit the function to pure
functions.
When you upgrade the Solidity version of an interface to a newer version, you can still use that interface in contracts with the older Solidity version.
The Solidity source code of an interface specifies how other contracts can interact with it, and the Solidity version of the contract using the interface need not match the Solidity version of the interface. This means that contracts with older Solidity versions can still call functions from interfaces that have been upgraded to newer Solidity versions without any changes to the old contract.
pragma solidity ^0.5.16;
Can be edited to version ^0.8.0
or 0.8.13
You have declared the variable pendingInterests
in the function getPendingInterests
, but this variable name has been used above in the function's return type declaration.
function getPendingInterests(address user) external returns (PendingInterest[] memory) { address[] storage _allMarkets = allMarkets; PendingInterest[] memory pendingInterests = new PendingInterest[](_allMarkets.length); for (uint256 i = 0; i < _allMarkets.length; ) { address market = _allMarkets[i]; uint256 interestAccrued = getInterestAccrued(market, user); uint256 accrued = interests[market][user].accrued; pendingInterests[i] = PendingInterest({ market: IVToken(market).underlying(), amount: interestAccrued + accrued }); unchecked { i++; } } return pendingInterests; }
Here, the return variable name can be deleted, to avoid conflicts with the local variable pendingInterests.
I don't know why the interface file is outside the interfaces folder. Maybe your purpose is to easily find this interface. However I still want to refactor it.
... βββ contracts/ βββ interfaces/ βββIPrime.sol βββ libs/ ... ...
I don't know much about your sorting method. I see that your sorting logic does not divide functions according to their visibility, nor according to the general function of the functions. So below is how I structured the function according to its visibility.
The way I see your project is that the two files Prime.sol
and the file PrimeLiquidityProvider.sol
do not have an inconsistent arrangement of the file structure.
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/PrimeLiquidityProvider.sol
Note: I have deleted all comments and unnecessary things just to keep this report from being too long.
For structuring contracts according to functions visibility.
// SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.13; import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import {AccessControlledV8} from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol"; import {ResilientOracleInterface} from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {MaxLoopsLimitHelper} from "@venusprotocol/isolated-pools/contracts/MaxLoopsLimitHelper.sol"; import {PrimeStorageV1} from "./PrimeStorage.sol"; import {Scores} from "./libs/Scores.sol"; import {IPrimeLiquidityProvider} from "./Interfaces/IPrimeLiquidityProvider.sol"; import {IXVSVault} from "./Interfaces/IXVSVault.sol"; import {IVToken} from "./Interfaces/IVToken.sol"; import {IProtocolShareReserve} from "./Interfaces/IProtocolShareReserve.sol"; import {IIncomeDestination} from "./Interfaces/IIncomeDestination.sol"; import {InterfaceComptroller} from "./Interfaces/InterfaceComptroller.sol"; contract Prime is IIncomeDestination, AccessControlledV8, PausableUpgradeable, MaxLoopsLimitHelper, PrimeStorageV1 { ////////////////////////////////////////////////// //////////////// Errors ///////////////////////// //////////////////////////////////////////////// error MarketNotSupported(); error InvalidLimit(); error IneligibleToClaim(); error WaitMoreTime(); error UserHasNoPrimeToken(); error InvalidCaller(); error InvalidComptroller(); error NoScoreUpdatesRequired(); error MarketAlreadyExists(); error InvalidAddress(); error InvalidBlocksPerYear(); error InvalidAlphaArguments(); error InvalidVToken(); ////////////////////////////////////////////////// //////////////// Types ////////////////////////// //////////////////////////////////////////////// using SafeERC20Upgradeable for IERC20Upgradeable; ////////////////////////////////////////////////// //////////////// State Variables //////////////// //////////////////////////////////////////////// uint256 public immutable BLOCKS_PER_YEAR; address public immutable WBNB; address public immutable VBNB; ////////////////////////////////////////////////// //////////////// Events ///////////////////////// //////////////////////////////////////////////// event Mint(address indexed user, bool isIrrevocable); event Burn(address indexed user); event UpdatedAssetsState( address indexed comptroller, address indexed asset ); event MarketAdded( address indexed market, uint256 indexed supplyMultiplier, uint256 indexed borrowMultiplier ); event MintLimitsUpdated( uint256 indexed oldIrrevocableLimit, uint256 indexed oldRevocableLimit, uint256 indexed newIrrevocableLimit, uint256 newRevocableLimit ); event UserScoreUpdated(address indexed user); event AlphaUpdated( uint128 indexed oldNumerator, uint128 indexed oldDenominator, uint128 indexed newNumerator, uint128 newDenominator ); event MultiplierUpdated( address indexed market, uint256 indexed oldSupplyMultiplier, uint256 indexed oldBorrowMultiplier, uint256 newSupplyMultiplier, uint256 newBorrowMultiplier ); event InterestClaimed( address indexed user, address indexed market, uint256 amount ); event TokenUpgraded(address indexed user); ////////////////////////////////////////////////// //////////////// Constuctor ///////////////////// //////////////////////////////////////////////// constructor(address _wbnb, address _vbnb, uint256 _blocksPerYear) {} ////////////////////////////////////////////////// //////////////// External Funtions ////////////// //////////////////////////////////////////////// function initialize() external virtual initializer {} function updateMultipliers() external {} function addMarket() external {} function setLimit() external {} function issue(bool isIrrevocable, address[] calldata users) external {} function xvsUpdated(address user) external {} function accrueInterestAndUpdateScore() external {} function claim() external {} function burn(address user) external {} function togglePause() external {} function claimInterest() external whenNotPaused returns (uint256) {} function updateAssetsState(address _comptroller, address asset) external {} ////////////////////////////////////////////////// ////////////// Public Functions////////////////// //////////////////////////////////////////////// function accrueInterest(address vToken) public {} function getInterestAccrued() public returns (uint256) {} ////////////////////////////////////////////////// ////////////// Internal Functions//////////////// //////////////////////////////////////////////// function _accrueInterestAndUpdateScore(address user) internal {} function _initializeMarkets(address account) internal {} function _calculateScore() internal returns (uint256) {} function _claimInterest() internal returns (uint256) {} function _mint(bool isIrrevocable, address user) internal {} function _burn(address user) internal {} function _upgrade(address user) internal {} function _executeBoost(address user, address vToken) internal {} function _updateScore(address user, address market) internal {} function _startScoreUpdateRound() internal {} function _updateRoundAfterTokenBurned(address user) internal {} ////////////////////////////////////////////////// ////// Internal View/Pure Funtions ////////////// //////////////////////////////////////////////// function _checkAlphaArguments() internal pure {} function _xvsBalanceOfUser(address user) internal view returns (uint256) {} function _xvsBalanceForScore(uint256 xvs) internal pure returns (uint256) {} function _capitalForScore() internal view returns () {} function _isEligible(uint256 amount) internal pure returns (bool) {} function _interestAccrued() internal view returns (uint256) {} function _getUnderlying(address vToken) internal view returns (address) {} function _incomePerBlock(address vToken) internal view returns (uint256) {} function _distributionPercentage() internal view returns (uint256) {} function _incomeDistributionYearly() internal view returns () {} function _calculateUserAPR() internal view returns () {} ////////////////////////////////////////////////// ////// External View/Pure Funtions ////////////// //////////////////////////////////////////////// function getAllMarkets() external view returns (address[] memory) {} function claimTimeRemaining(address user) external view returns (uint256) {} function calculateAPR() external view {} function estimateAPR() external view {} }
All of the structures I gave you as examples above are suggestions.
I think your project needs to follow a common convention for how to organize the folder structure, as well as a general convention for how to structure the project. So when a new person joins the team, just look at the structure to easily find what they need. As well as serving future audits.
You may want to have an Interface to structure all external functions, errors and events in it more clearly. However, I also understand that you may want other protocols, or other smart contracts, to have the right to call what is in the Prime contract once it is deployed by expressing it in the IPrime.sol file.
One more suggestion on how to organize files:
Layout of Contract:
Layout of Funtions:
For internal functions, I understand that your code uses hyphens in front of the function name, but here it is not followed.
function isEligible(uint256 amount) internal view returns (bool) {}
function _isEligible(uint256 amount) internal view returns (bool) {}
#0 - c4-pre-sort
2023-10-07T02:10:15Z
0xRobocop marked the issue as low quality report