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: 34/115
Findings: 2
Award: $163.77
🌟 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
**Issue Description: The smart contract utilizes score update rounds when certain actions occur, such as adding a new market, setting a new alpha value, or updating multipliers. The process of an update round looks like this.
_startScoreUpdateRound()
nextScoreUpdateRoundId
gets increased by one and pendingScoreUpdates
gets set to the amount of tokens we have.updateScores()
gets called multiple times to update each users score.pendingScoreUpdates
each time a user gets updated)However, a potential issue arises when the script is executed twice in a short timeframe. In such cases, the first execution's calls to updateScores()
may not have completed when the second execution begins, potentially causing a transaction revert due to an incomplete update process.
Proof of Concept (POC):
updateScores()
can update a maximum of 2 users before running out of gasnextScoreUpdateRoundId = 0
pendingScoreUpdates = 0
Actions:
_startScoreUpdateRound()
nextScoreUpdateRoundId = 1
pendingScoreUpdates = 3
updateUsers()
for Users A and B
nextScoreUpdateRoundId = 1
pendingScoreUpdates = 1
isScoreUpdated[1][A, B] = true
_startScoreUpdateRound()
nextScoreUpdateRoundId = 2
pendingScoreUpdates = 3
updateUsers()
for User C
nextScoreUpdateRoundId = 2
pendingScoreUpdates = 2
isScoreUpdated[1][C] = true
updateUsers()
for Users A and B
nextScoreUpdateRoundId = 2
pendingScoreUpdates = 0
isScoreUpdated[2][A, B] = true
updateUsers()
for User C
nextScoreUpdateRoundId = 2
pendingScoreUpdates = 0
(pendingScoreUpdates == 0)
Recommended Mitigation Steps:
To address this issue, a simple mitigation can be implemented. The contract should check if pendingScoreUpdates
is not zero before allowing a new call to _startScoreUpdateRound()
. This check can be added at the beginning of the _startScoreUpdateRound()
function:
if(pendingScoreUpdates != 0) revert;
Additionally, a custom error named updatingRoundNotFinished
could be introduced to provide better clarity on the revert reason.
PrimeLiquidityProvider: Line 249
Issue Description:
The smart contract relies on block.number
to perform certain operations, including the accrueTokens()
function. However, on the Arbitrum network, block.number
is updated only once per minute, resulting in the function accruing tokens approximately every 4 blocks instead of every block.
Recommended Mitigation Steps:
If accruing tokens once per minute aligns with the protocol's management requirements, it's advisable to retain the current implementation and update the documentation to reflect this behavior. However, if the protocol should accrue tokens on every block, consider using the arbBlockNumber()
function from the ArbSys to obtain the accurate block number.
Issue Description:
If a user has never staked, (stakedAt[user] == 0)
the function returns the STAKING_PERIOD
which is incorrect as this user has not staked and will not be able to retrieve anything when the staking period has passed.
Recommended Mitigation Steps:
I would recommend returning type(uint256).max
as this will probably make it more clear to a user that he has not staked enough.
Issue Description:
The calculateAPR()
function, used to predict a user's APR on a provided market, does not validate whether the supplied address represents a valid market. Consequently, if an incorrect address is provided, the function may return arbitrary and inaccurate values.
Recommended Mitigation Steps:
To enhance the robustness of the calculateAPR()
function, consider adding a validation check to ensure that the provided address corresponds to a valid prime market. If the address does not exist as a prime market, return 0 to prevent misleading data. A validation check could be implemented as follows:
if (!markets[market].exists) { return (0,0); }
PrimeLiquidityProvider: Line 232
Issue Description:
In case of the accrueTokens()
function already having been called shortly before and leading to IERC20Upgradeable(token_).balanceOf(address(PLP)) == tokenAmountAccrued[token_]
the getEffectiveDistributionSpeed()
function incorrectly returns 0 which is not the distribution speed for the provided market. This not only results in an inaccurate calculation within Prime's _incomeDistributionYearly()
function but also provides an incorrect return value to external callers of the view function.
Recommended Mitigation Steps:
To address this issue, it is advisable to modify the getEffectiveDistributionSpeed()
function to always return tokenDistributionSpeeds[token_]
, as follows:
function getEffectiveDistributionSpeed(address token_) external view returns (uint256) { return tokenDistributionSpeeds[token_]; }
Issue Description:
The IPrime
interface is lacking the inclusion of several external/public functions that are present in the Prime
contract.
Recommended Mitigation Steps:
If the intention is to include all externally callable functions within the IPrime
interface, it should be updated to include the missing functions listed below. However, if the interface is intended to expose functions only to users, the interface can exclude some functions.
Functions missing in IPrime
:
initialize()
getPendingInterests()
updateScores()
updateAlpha()
updateMultipliers()
addMarket()
setLimit()
issue()
claim()
burn()
togglePause()
2x claimInterest()
updateAssetsState()
getAllMarkets()
claimTimeRemaining()
calculateAPR()
estimateAPR()
getInterestAccrued()
If only functions intended for the users should be included the interface is missing:
getPendingInterests()
updateScores()
claim()
2x claimInterest()
getAllMarkets()
claimTimeRemaining()
calculateAPR()
estimateAPR()
getInterestAccrued()
Issue Description:
The internal function isEligible
does not adhere to Solidity style guidelines for internal functions, as its name lacks a leading underscore.
Recommended Mitigation Steps:
To align with best practices, rename the function to _isEligible
.
Issue Description:
The provided audit sponsor description incorrectly states that "all currently unreleased funds issued can be sent from both PrimeLiquidityProvider
and ProtocolShareReserve
to the Prime
contract by anyone." This statement is inaccurate, as the funds of the PLP are released only when a user claims interest and the Prime token balance is insufficient. Furthermore, the releaseFunds()
function of PLP can only be called by the Prime token itself.
Recommended Mitigation Steps: Revise the description/documentation to accurately reflect the process and restrictions associated with the release of funds.
Issue Description:
The documentation inaccurately states that the variable responsible for tracking pending upgrades is named totalScoreUpdatesRequired
, while the actual variable name is pendingScoreUpdates
.
Recommended Mitigation Steps:
Update the documentation to use the correct variable name, replacing totalScoreUpdatesRequired
with pendingScoreUpdates
.
PrimeLiquidityProvider Line 116 PrimeLiquidityProvider Line 175 PrimeLiquidityProvider Line 214
Issue Description:
In the PrimeLiquidityProvider
contract, multiple functions are ambiguously defined with access control annotations, specifically stating either @custom:access Only Governance
or @custom:access Only owner
. Consistency in access control definitions is needed.
Recommended Mitigation Steps:
Choose a standardized convention for function headers, either Only Governance
or Only owner
, and apply it consistently to all affected functions.
PrimeLiquidityProvider Line 332 PrimeLiquidityProvider Line 344
Issue Description:
Several functions in the PrimeLiquidityProvider
contract throw errors, but these errors are not declared in the function headers. This lack of error declaration can lead to confusion.
Recommended Mitigation Steps: For each affected function, include the relevant error declarations in the function headers to improve clarity and documentation.
_ensureTokenInitialized()
The function throws the TokenNotInitialized
error but that is not declared in the header
Add this line to the header:
* @custom:error Throw TokenNotInitialized if the address does not own a token
_ensureZeroAddress()
The function throws the InvalidArguments
error but that is not declared in the header
Add this line to the header:
* @custom:error Throw InvalidArguments if the address is 0
Issue Description:
Several functions in the Prime
contract throw errors, but these errors are not declared in the function headers. This omission may result in a lack of clarity for developers interacting with the contract.
Recommended Mitigation Steps: To enhance clarity and documentation, include the relevant error declarations in the headers of the affected functions.
constructor()
The function throws the InvalidAddress
and InvalidBlocksPerYear
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw InvalidAddress if the wbnb address is 0 * @custom:error Throw InvalidAddress if the vbnb address is 0 * @custom:error Throw InvalidBlocksPerYear if the blocks per year are 0
initialize()
The function throws the InvalidAddress
error but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw InvalidAddress if the xvsVault address is 0 * @custom:error Throw InvalidAddress if the xvsVaultRewardToken address is 0 * @custom:error Throw InvalidAddress if the protocolShareReserve address is 0 * @custom:error Throw InvalidAddress if the comptroller address is 0 * @custom:error Throw InvalidAddress if the oracle address is 0 * @custom:error Throw InvalidAddress if the primeLiquidityProvider address is 0
updateScores()
The function throws the NoScoreUpdatesRequired
and UserHasNoPrimeToken
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw NoScoreUpdatesRequired if the pendingScoreUpdates is 0 * @custom:error Throw NoScoreUpdatesRequired if the nextScoreUpdateRoundId is 0 * @custom:error Throw UserHasNoPrimeToken if the user doesn't have a prime token
updateMultipliers()
The function throws the MarketNotSupported
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw MarketNotSupported if the market does not exist
addMarket()
The function throws the MarketAlreadyExists
and InvalidVToken
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw MarketAlreadyExists if the market is already in the prime program. * @custom:error Throw InvalidVToken if the market does not exist.
setLimit()
The function throws the InvalidLimit
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw InvalidLimit if one of the new total values is smaller than the current value
claim()
The function throws the IneligibleToClaim
and WaitMoreTime
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw IneligibleToClaim if the user has not staked enough XVS * @custom:error Throw WaitMoreTime if the time since the user has staked is less than the staking period.
updateAssetsState()
The function throws the InvalidCaller
, InvalidComptroller
and MarketNotSupported
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw InvalidCaller if the function is called by someone else than the protocolShareReserve * @custom:error Throw InvalidComptroller if the address passed as _comptroller parameter is not the comptroller * @custom:error Throw MarketNotSupported if the vToken address is 0.
accrueInterest()
The function throws the MarketNotSupported
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw MarketNotSupported if the market is not a prime market
_mint()
The function throws the IneligibleToClaim
and WaitMoreTime
errors but that is not declared in the header.
Add these lines to the header:
* @custom:error Throw IneligibleToClaim if the user does not have a prime token * @custom:error Throw InvalidLimit if all tokens of that category have already been minted
_burn()
The function throws the UserHasNoPrimeToken
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw UserHasNoPrimeToken if the user has no prime token
_upgrade()
The function throws the InvalidLimit
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw InvalidLimit if all tokens of that category have already been minted
_checkAlphaArguments()
The function throws the InvalidAlphaArguments
error but that is not declared in the header.
Add this line to the header:
* @custom:error Throw InvalidAlphaArguments if the denominator is 0 or smaller than the numerator
Issue Description: Several functions in the Prime
contract are access-restricted by the AccessManager, but this access control is not mentioned in their function headers. Properly documenting access control is essential for understanding contract behavior. The affected functions are:
updateAlpha()
updateMultipliers()
addMarket()
setLimit()
issue()
burn()
togglePause()
Recommended Mitigation Steps: Add the following access control declaration to the headers of all affected functions:
* @custom:access Controlled by ACM
Prime Line 63 Prime Line 74 Prime Line 82
Issue Description:
In the events MintLimitsUpdated
, AlphaUpdated
, and MultiplierUpdated
, the new values are indexed incorrectly while the old ones are indexed. Proper indexing is essential for making contract events more informative for users monitoring the contract.
Recommended Mitigation Steps: Adjust the indexing to focus on the new values rather than the old ones for the mentioned events to provide clearer event data.
Issue Description:
The InterestClaimed
event lacks indexing for the amount
parameter, which can hinder users seeking specific event data.
Recommended Mitigation Steps:
Add indexing to the amount
parameter in the InterestClaimed
event to improve event data accessibility.
Issue Description:
The mint
event does not index the isIrrevocable
parameter, potentially limiting the usefulness of the event data.
Recommended Mitigation Steps:
Index the isIrrevocable
parameter in the mint
event to enhance the event's informative value.
Prime Line 809 Prime Line 854 Prime Line 904
Issue Description:
The functions _checkAlphaArguments
, _xvsBalanceForScore
, and isEligible
do not read or modify any state variables within the contract. Therefore, they can be declared as pure
for improved clarity.
Recommended Mitigation Steps:
Declare the functions as pure
to signify their lack of state-changing behavior and improve contract readability.
Issue Description:
The function claimTimeRemaining()
has a misleading name, as it does not return the time remaining for users to claim their tokens but rather the time it takes until users can claim their tokens.
Recommended Mitigation Steps:
Rename the function to timeUntilClaimRemaining()
to accurately convey its functionality.
The error MarketAlreadyExists
is badly named as it is used when checking for if the market is already marked as a prime market, not if the market exists.
Issue Description:
The error MarketAlreadyExists
is misleading, as it is used to check if a market is already marked as a prime market, not if the market exists.
Recommended Mitigation Steps:
Rename the error to MarketIsAlreadyPrimeMarket
to align with its actual usage.
Issue Description:
In the contract functions, there is inconsistent naming of the vToken
address parameter, sometimes referred to as vToken
and other times as market
. Consistency in parameter naming enhances code readability.
In the following functions the address is called vToken:
addMarket()
claimInterest()
accrueInterest()
getInterestAccrued()
_claimInterest()
_executeBoost()
_interestAccrued()
_getUnderlying()
_incomePerBlock()
_incomeDistributionYearly()
)
While in these functions the address is called marketupdateMultipliers()
accrueInterestAndUpdateScore()
calculateAPR()
_calculateScore()
_updateScore()
Recommended Mitigation Steps:
Standardize the parameter naming to either vToken
or market
across all functions to improve code consistency and clarity.
PrimeLiquidityProvider: Line 23
Issue Description: The description in the header for the variable lastAccruedBlock
inaccurately states that it represents the rate at which tokens are distributed to the Prime
contract. In reality, it signifies the block at which the token was initialized or the last block at which accrueTokens()
was called.
Recommended Mitigation Steps: Revise the description to accurately convey that lastAccruedBlock
represents the block at which token initialization or accrual occurred.
/// @notice The last block at which the tokens interest was accrued (or the token was initialized) mapping(address => uint256) public lastAccruedBlock;
#0 - 0xRobocop
2023-10-07T15:49:50Z
L-02 Dup of #132
#1 - c4-pre-sort
2023-10-07T15:49:56Z
0xRobocop marked the issue as sufficient quality report
🌟 Selected for report: 3agle
Also found by: 0x3b, 0xDetermination, 0xweb3boy, ArmedGoose, Bauchibred, Breeje, J4X, hunter_w3b, jamshed, kaveyjoe, oakcobalt, radev_sw, versiyonbir
159.397 USDC - $159.40
This analysis focuses on the Venus Prime audit hosted on Code4rena.
| Spec | Value | | ----------- | ----------- | | Name | Venus Prime | | Duration | 6 days | | nSLOC | 1039 |
The primary objectives of this audit are to identify potential vulnerabilities, propose mitigation strategies, and optimize gas usage within the codebase.
The audit process was divided into three distinct phases:
In the Recon phase, I conducted a preliminary review of the codebase to gain a broad understanding of its structure and functionality. This initial pass allowed me to familiarize myself with the code before diving deeper. Additionally, I examined the provided documentation and gathered supplementary information from online sources and the audit's Discord channel.
The Analysis phase constituted the primary focus of the audit, where I conducted an in-depth examination of the codebase. My approach involved meticulously documenting suspicious code sections, identifying minor bugs, and envisioning potential attack vectors. I explored how various attack scenarios could manifest within the code and delved into the relevant functions. Throughout this phase, I maintained communication with the protocol team to verify findings and clarify any ambiguities.
After compiling initial notes during the Analysis phase, I refined and expanded upon these findings. I also developed test cases to complement the identified issues and integrated them into the final report. Furthermore, I generated Quality Assurance (QA) and Gas reports, alongside this comprehensive analysis.
Venus Prime comprises a system where users stake a specified amount of XVS tokens in the XVS-Vault, the native token of the Venus protocol. In return, users receive a unique token, subject to specific conditions. Notably, to obtain a token, users must stake 1000 XVS tokens for a 90-day period.
Two distinct types of tokens exist within the system: "normal" revocable tokens and irrevocable tokens. The former may be revoked (burned) if a user's stake falls below 1000 XVS, whereas the latter remains immune to such revocation. Notably, these tokens do not adhere to the ERC standard and instead employ custom implementations. Furthermore, tokens are non-transferable, and each address may possess only one token at any given time.
The codebase also incorporates the PrimeLiquidityProvider, responsible for managing liquidity within the Prime contract. It receives tokens (interest) generated by the protocol and distributes them to the Prime contract when required.
In addition to the primary contracts mentioned above, several smaller libraries and contracts contribute to the system's functionality:
The system interacts with several other contracts within the Venus protocol, with significant relevance attributed to the protocolShareReserve and xvsVault.
The Prime token contract is the centerpiece of the system, encompassing token minting, burning, interest claiming, and APR calculation. The contract handles interest accrual and equitable distribution to users. Additionally, it maintains a record of markets designated as prime markets, each associated with an upper limit set during deployment.
Recommendations:
The PrimeLiquidityProvider offers a second source of liquidity for the Prime contract when it runs out of tokens and must provide users with their accrued interest.
Recommendations:
The security of the Prime functionality significantly relies on the security of Governance and the AccessControlManager (ACM). If the ACM is compromised, malicious actors could exploit functions with potentially harmful consequences. For example, an attacker could burn other users' prime tokens, issue arbitrary prime tokens, pause the protocol, or conduct Denial-of-Service (DOS) attacks.
Recommendations on Centralization Risk: Mitigate this risk by safeguarding the ACM against any compromise or unauthorized access. Ensuring the ACM's integrity is paramount to prevent the exploitation of functions that could disrupt the protocol.
The codebase exhibits a high level of code quality. It is well-documented and adheres to best practices, encompassing custom errors/events, interfaces, and parameter checks. Variables and functions mostly comply with Solidity Style Guide standards. Moreover, the code maintains simplicity and organization, utilizing separate contracts for distinct functionalities.
The test suite consists of two files, one for the Prime token and another for the PrimeLiquidityProvider. It leverages the Hardhat framework and incorporates libraries such as smock
, hardhat-network-helpers
, chai
, ethers
, and typechain
. Proxy and initialization occur in a single transaction, reducing front-running risk.
Recommendations on Test Suite: Some of the functions are not tested fully. Examples are:
Prime.updateMultipliers()
is not tested at allPrime.setLimit()
is only used in the setup but never testedPrime.getPendingInterests()
return values are never testedPrime.togglePause()
is never tested (pausing functionality is essential and should definitely be tested)Prime.claimTimeRemaining()
is not tested for edge cases (user has not deposited enough/anything)PrimeLiquidityProvider.getEffectiveDistributionSpeed()
is not testedPrimeLiquidityProvider.sweepToken()
is not tested for access by someone else than the owner
It is recommended to add additional tests for these functions to verify their correct functionality.The access control manager always returns true if someone wants to access a function, so there is no testing to verify that a function reverts in the case of the caller not being allowed to access it. It is recommended to add testcases in which the ACM returns false for certain addresses.
Additionally the test suite is lacking tests in which multiple functionalities get combined to check for cross reactions, especially for the prime contract. It is recommended to add more cross functionality tests, as well as bigger testcases simulating multiple users staking/unstaking XVS as well as retrieving funds.
The codebase features helpful comments that aid in understanding its functionality, making it accessible to new developers. NatSpec is employed to provide standardized descriptions, adhering to the Solidity Style Guide. However, adherence to NatSpec standards could be improved in some areas, as inconsistencies occasionally arise.
Recommendations on Comments and Documentation: Offer additional explanations for functions responsible for updating Scores and accruing interest, as these are critical but may not be entirely clear from existing comments. Enhanced clarity in these areas would benefit future developers working on the code.
The audit spanned a full six days and comprised three phases:
Total time spent: 40 hours.
40 hours
#0 - c4-pre-sort
2023-10-07T05:29:32Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-judge
2023-11-03T20:22:38Z
fatherGoose1 marked the issue as grade-a