Platform: Code4rena
Start Date: 08/05/2023
Pot Size: $90,500 USDC
Total HM: 17
Participants: 102
Period: 7 days
Judge: 0xean
Total Solo HM: 4
Id: 236
League: ETH
Rank: 48/102
Findings: 2
Award: $140.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x73696d616f
Also found by: 0xadrii, 0xbepresent, 0xkazim, J4de, peanuts, pontifex, rvierdiiev, volodya
96.0525 USDC - $96.05
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Comptroller.sol#L187-L199 https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Comptroller.sol#L292-L299
Users can avoid correct estimation of assets and redeem more tokens than would redeem in case of estimation with updated oracle prices.
exitMarket
function doesn't call oracle.updatePrice
before _checkRedeemAllowed
check at all.
preRedeemHook
and preTransferHook
call oracle.updatePrice
before _checkRedeemAllowed
check, but only for redeemed VToken
.
The _checkRedeemAllowed
check estimates the user assets liquidity through the _getHypotheticalLiquiditySnapshot
function.
_getHypotheticalLiquiditySnapshot
calculates snapshot.shortfall
with oracle.getUnderlyingPrice(address(asset))
from _safeGetUnderlyingPrice
function.
The oracle.updatePrice
should be called every time before calling oracle.getUnderlyingPrice
for every token but it doesn't in case of Comptroller.sol.
Manual review
I suggest calling oracle.updatePrice
for all assets from users accountAssets
in _getHypotheticalLiquiditySnapshot
function.
Oracle
#0 - c4-judge
2023-05-17T15:55:26Z
0xean marked the issue as duplicate of #88
#1 - c4-judge
2023-06-05T13:57:28Z
0xean marked the issue as satisfactory
#2 - c4-judge
2023-06-05T14:09:17Z
0xean changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-06-08T14:39:52Z
0xean marked the issue as duplicate of #486
#4 - c4-judge
2023-06-08T14:41:14Z
0xean marked the issue as partial-50
🌟 Selected for report: JCN
Also found by: 0xAce, 0xSmartContract, Aymen0909, K42, Rageur, Raihan, ReyAdmirado, SAAJ, SM3_SS, Sathish9098, Team_Rocket, Udsen, c3phas, codeslide, descharre, fatherOfBlocks, hunter_w3b, j4ld1na, lllu_23, matrix_0wl, naman1778, petrichor, pontifex, rapha, souilos, wahedtalash77
44.9387 USDC - $44.94
if
/validation statements as up as possible in the function bodyThere are 10 instances.
333: if (!markets[vToken].isListed) { 336: if (!markets[vToken].accountMembership[borrower]) { 439: if (!markets[vTokenBorrowed].isListed) { 442: if (!markets[vTokenCollateral].isListed) { 750: if (newLiquidationThresholdMantissa < newCollateralFactorMantissa) { 941: _ensureMaxLoops(rewardsDistributorsLen + 1);
1045: if (borrower == liquidator) { 1050: if (repayAmount == 0) { 1055: if (repayAmount == type(uint256).max) { 1307: if (src == dst) {
The emit can be rearranged in this way:
707: emit NewCloseFactor(closeFactorMantissa, newCloseFactorMantissa); 708: closeFactorMantissa = newCloseFactorMantissa;
Instead of:
707: uint256 oldCloseFactorMantissa = closeFactorMantissa; 708: closeFactorMantissa = newCloseFactorMantissa; 709: emit NewCloseFactor(oldCloseFactorMantissa, closeFactorMantissa);
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Comptroller.sol#LL707C1-L709C74 With this arrangement, both the deploy time (~1.4 k * 4 = 5.6k gas per function) and every time the functions are triggered (~16 gas) gas savings will be achieved. There are 20 other instances:
791: emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); 917: emit NewMinLiquidatableCollateral(oldMinLiquidatableCollateral, newMinLiquidatableCollateral); 966: emit NewPriceOracle(oldOracle, newOracle);
319: emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_); 1168: emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa); 1262: emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel);
298: emit NextBidderBlockLimitUpdated(oldNextBidderBlockLimit, _nextBidderBlockLimit); 312: emit IncentiveBpsUpdated(oldIncentiveBps, _incentiveBps); 325: emit MinimumPoolBadDebtUpdated(oldMinimumPoolBadDebt, _minimumPoolBadDebt); 338: emit WaitForFirstBidderUpdated(oldWaitForFirstBidder, _waitForFirstBidder); 352: emit PoolRegistryUpdated(oldPoolRegistry, _poolRegistry);
337: emit PoolNameSet(comptroller, oldName, name); 347: emit PoolMetadataUpdated(comptroller, oldMetadata, _metadata); 427: emit NewShortfallContract(oldShortfall, address(shortfall_)); 436: emit NewProtocolShareReserve(oldProtocolShareReserve, protocolShareReserve_);
103: emit PoolRegistryUpdated(oldPoolRegistry, _poolRegistry); 119: emit ShortfallContractUpdated(oldShortfallContractAddress, shortfallContractAddress_); 130: emit PancakeSwapRouterUpdated(oldPancakeSwapRouter, pancakeSwapRouter_); 142: emit MinAmountToConvertUpdated(oldMinAmountToConvert, minAmountToConvert_);
57: emit PoolRegistryUpdated(oldPoolRegistry, _poolRegistry);
Use vTokenAddress
instead.
201: Market storage marketToExit = markets[address(vToken)];
Use a state variable instead of a caching variable.
There are 4 instances:
Exlude accountBorrowsPrev
:
896: uint256 accountBorrowsPrev = _borrowBalanceStored(borrower); 897: uint256 accountBorrowsNew = accountBorrowsPrev + borrowAmount;
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/VToken.sol#L896-L897
Exlude poolData
:
147: PoolData memory poolData = getPoolDataFromVenusPool(poolRegistryAddress, venusPool); 148: poolDataItems[i] = poolData;
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Lens/PoolLens.sol#LL147C1-L148C41
Exlude srcTokensNew
and dstTokensNew
. Use -=
and +=
to change state variables:
1321: uint256 srcTokensNew = accountTokens[src] - tokens; 1322: uint256 dstTokensNew = accountTokens[dst] + tokens; 1327: accountTokens[src] = srcTokensNew; 1328: accountTokens[dst] = dstTokensNew;
There are 6 instances:
Use existing rewardsDistributorsLength
instead of rewardsDistributorsLen
940: uint256 rewardsDistributorsLen = rewardsDistributors.length; 941: _ensureMaxLoops(rewardsDistributorsLen + 1);
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Comptroller.sol#LL940C1-L941C53
Use existing contributorAccrued
instead of rewardTokenAccrued[contributor]
:
268: emit ContributorRewardsUpdated(contributor, rewardTokenAccrued[contributor]);
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Rewards/RewardsDistributor.sol#LL265C1-L268C90
Declare marketsCount
in the top and use instead of markets.length
158: require(markets.length == amountsOutMin.length, "Risk fund: markets and amountsOutMin are unequal lengths"); 159: require(markets.length == paths.length, "Risk fund: markets and paths are unequal lengths"); 162: uint256 marketsCount = markets.length;
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/RiskFund/RiskFund.sol#LL158C1-L162C47
Use existing kink_
instead of kink
:
160: kink = kink_; 162: emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/BaseJumpRateModelV2.sol#L162
Declare badDebtOld
in the top and use instead of badDebt
490: require(recoveredAmount_ <= badDebt, "more than bad debt recovered from auction"); 492: uint256 badDebtOld = badDebt;
marketsList[marketIdx]
can be cached before the loop:
898: for (uint256 actionIdx; actionIdx < actionsCount; ++actionIdx) { 899: _setActionPaused(address(marketsList[marketIdx]), actionsList[actionIdx], paused);
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/Comptroller.sol#LL898C1-L899C99
totalReserves
and protocolShareReserve
should be cached:
1215: if (reduceAmount > totalReserves) { 1223: totalReservesNew = totalReserves - reduceAmount; 1230: _doTransferOut(protocolShareReserve, reduceAmount); 1233: IProtocolShareReserve(protocolShareReserve).updateAssetsState(address(comptroller), underlying); 1235: emit ReservesReduced(protocolShareReserve, reduceAmount, totalReservesNew);
#0 - c4-judge
2023-06-05T14:18:49Z
0xean marked the issue as grade-b