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: 22/102
Findings: 2
Award: $788.63
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: dacian
Also found by: Co0nan, SaeedAlipoor01988, nadin
731.996 USDC - $732.00
https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/VToken.sol#L256 https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/VToken.sol#L696 [_setInterestRateModelFresh](https://github.com/code-423n4/2023-05-venus/blob/8be784ed9752b80e6f1b8b781e2e6251748d0d7e/contracts/VToken.sol#L1243-L1262
The ability of a borrower to repay a loan is disabled if the interest rate is
set too high by the InterestRateModel
However, there is neither a check when setting the interest rate nor an
indication in the InterestRateModel's
specs of this behavior
If an account wants to repay a loan, the function:
1- VToken.sol#repayBorrow()
is used
2- This function calls accrueInterest()
(line 256) which fetches the current borrow rate of the interest rate model
3- In VToken.sol#accrueInterest()
at line 696 requires an not "absurdly high" rate, or fails otherwise
696: require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high");
4- However, there is no check or indicator in VToken.sol#setInterestRateModel()
to prevent the owner to set such a high rate that effectively disables repay
of borrowed funds. setInterestRateModel -> _setInterestRateModelFresh
function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal { // Used to store old model for use in the event that is emitted on success InterestRateModel oldInterestRateModel; // We fail gracefully unless market's block number equals current block number if (accrualBlockNumber != _getBlockNumber()) { revert SetInterestRateModelFreshCheck(); } // Track the market's current interest rate model oldInterestRateModel = interestRateModel; // Ensure invoke newInterestRateModel.isInterestRateModel() returns true require(newInterestRateModel.isInterestRateModel(), "marker method returned false"); // Set the interest rate model to newInterestRateModel interestRateModel = newInterestRateModel; // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel) emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel);
The similar issue has been accepted before
Manual Review
Disallow setting the interest rate too high in _setInterestRateModelFresh
Other
#0 - 0xean
2023-05-18T10:29:11Z
Will leave open for sponsor comment, but ultimately this falls into the "centralization risks" bucket and was called out of scope by the sponsor.
#1 - c4-sponsor
2023-05-23T20:46:54Z
chechu marked the issue as sponsor confirmed
#2 - chechu
2023-05-23T20:47:05Z
Similar to issues #110 and #10
#3 - c4-sponsor
2023-05-23T20:47:34Z
chechu marked the issue as sponsor acknowledged
#4 - c4-judge
2023-05-31T00:53:55Z
0xean marked the issue as duplicate of #110
#5 - c4-judge
2023-06-05T14:26:18Z
0xean marked the issue as satisfactory
🌟 Selected for report: brgltd
Also found by: 0x73696d616f, 0xAce, 0xSmartContract, 0xWaitress, 0xkazim, 0xnev, Aymen0909, BGSecurity, Bauchibred, Cayo, ChrisTina, Franfran, IceBear, Infect3d, Kose, Lilyjjo, PNS, RaymondFam, Sathish9098, Team_Rocket, Udsen, YakuzaKiawe, YoungWolves, berlin-101, bin2chen, btk, codeslide, fatherOfBlocks, frazerch, kodyvim, koxuan, lfzkoala, lukris02, matrix_0wl, nadin, naman1778, sashik_eth, tnevler, volodya, wonjun, yjrwkk
56.6347 USDC - $56.63
Total 03 Low and 10 Non-Critical
RiskFund.sol#_swapAsset()
uses the wrong function for swapping fee-on-transfer tokensIPancakeswapV2Router.sol#swapExactTokensForTokens
does not follow the standard_incentiveBps
argument in Shortfall.sol#updateIncentiveBps()
RiskFund.sol#_swapAsset()
uses the wrong function for swapping fee-on-transfer tokensNumber of swapped tokens may be lower than actual when the underlying is a fee-on-transfer token with the fee turned on. The function uses swapExactTokensForTokens()
when it should use swapExactTokensForTokensSupportingFeeOnTransferTokens()
instead.
Use swapExactTokensForTokensSupportingFeeOnTransferTokens()
IPancakeswapV2Router.sol#swapExactTokensForTokens
does not follow the standardFile: IPancakeswapV2Router.sol interface IPancakeswapV2Router { null( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory amounts);
It's called in RiskFund#_swappAsset
File: RiskFund.sol uint256[] memory amounts = IPancakeswapV2Router(pancakeSwapRouter).swapExactTokensForTokens(
According to docs the parameters amountIn, amountOutMin, deadline
are uint
not uint256
Comply with the standards of pancakeswap
_incentiveBps
argument in Shortfall.sol#updateIncentiveBps()
Add condition _incentiveBps
< MAX_BPS (10_000)
307: null(uint256 _incentiveBps) external { 308: _checkAccessAllowed("updateIncentiveBps(uint256)"); - 309: require(_incentiveBps != 0, "incentiveBps must not be 0"); + 309: require(_incentiveBps != 0 && Â_incentiveBps < MAX_BPS, " Invalid incentiveBps "); 310: uint256 oldIncentiveBps = incentiveBps; 311: incentiveBps = _incentiveBps; 312: emit IncentiveBpsUpdated(oldIncentiveBps, _incentiveBps); 313: }
File: Shortfall.sol 44: mapping(VToken => uint256) marketDebt; 72: mapping(address => Auction) public auctions;
File: RewardsDistributor.sol 23: mapping(address => RewardToken) public rewardTokenSupplyState; 26: mapping(address => mapping(address => uint256)) public rewardTokenSupplierIndex; 32: mapping(address => uint256) public rewardTokenAccrued; 35: mapping(address => uint256) public rewardTokenBorrowSpeeds; 38: mapping(address => uint256) public rewardTokenSupplySpeeds; 41: mapping(address => RewardToken) public rewardTokenBorrowState; 44: mapping(address => uint256) public rewardTokenContributorSpeeds; 47: mapping(address => uint256) public lastContributorBlock; 50: mapping(address => mapping(address => uint256)) public rewardTokenBorrowerIndex;
File: PoolRegistry.sol 83: mapping(address => VenusPoolMetaData) public metadata; 88: mapping(uint256 => address) private _poolsByID; 98: mapping(address => VenusPool) private _poolByComptroller; 103: mapping(address => mapping(address => address)) private _vTokens; 108: mapping(address => address[]) private _supportedPools;
File: RiskFund.sol 35: mapping(address => uint256) public poolReserves;
File: VTokenInterfaces.sol 107: mapping(address => uint256) internal accountTokens; 110: mapping(address => mapping(address => uint256)) internal transferAllowances; 113: mapping(address => BorrowSnapshot) internal accountBorrows;
File: ComptrollerStorage.sol 41: mapping(address => bool) accountMembership; 74: mapping(address => VToken[]) public accountAssets; 80: mapping(address => Market) public markets; 86: mapping(address => uint256) public borrowCaps; 92: mapping(address => uint256) public supplyCaps; 95: mapping(address => mapping(Action => bool)) internal _actionPaused; 101: mapping(address => bool) internal rewardsDistributorExists;
File: ReserveHelpers.sol 13: mapping(address => uint256) internal assetsReserves; 17: mapping(address => mapping(address => uint256)) internal poolsAssetsReserves;
The linked event is meant to emit the previous and new value of a storage variable being adjusted, however, to do so it redundantly uses a local variable.
File: Comptroller.sol 709: emit NewCloseFactor(oldCloseFactorMantissa, closeFactorMantissa); 791: emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); 917: emit NewMinLiquidatableCollateral(oldMinLiquidatableCollateral, newMinLiquidatableCollateral); 966: emit NewPriceOracle(oldOracle, newOracle);
File: VToken.sol 319: emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_);
File: PoolRegistry.sol 337: emit PoolNameSet(comptroller, oldName, name);
File: RiskFund/ProtocolShareReserve.sol 57: emit PoolRegistryUpdated(oldPoolRegistry, _poolRegistry);
File: RiskFund/RiskFund.sol 103: emit PoolRegistryUpdated(oldPoolRegistry, _poolRegistry); 119: emit ShortfallContractUpdated(oldShortfallContractAddress, shortfallContractAddress_); 130: emit PancakeSwapRouterUpdated(oldPancakeSwapRouter, pancakeSwapRouter_); 142: emit MinAmountToConvertUpdated(oldMinAmountToConvert, minAmountToConvert_);
Advise the emission to occur prior to the assignment of the storage variable by setting the first argument of the event as the existing storage value and the second argument as the input argument of the function.
File: Shortfall.sol - 60: uint256 private constant MAX_BPS = 10000; + 60: uint256 private constant MAX_BPS = 10_000;
File: Comptroller.sol 27: address public immutable poolRegistry;
File: WhitePaperInterestRateModel.sol 22: uint256 public immutable multiplierPerBlock; 27: uint256 public immutable baseRatePerBlock;
WhitePaperInterestRateModel.sol#L22-L27
The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification
https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19
An important feature of Custom Error is that values such as address, tokenID, msg.value can be written inside the () sign, this kind of approach provides a serious advantage in debugging and examining the revert details of dapps such as tenderly
Comptroller.sol ErrorReporter.sol
There are many addresses and constants used in the system. It is recommended to put the most used ones in one file (for example constants.sol, use inheritance to access these values).
This will help with readability and easier maintenance for future changes. This also helps with any issues, as some of these hard-coded values are admin addresses.
Use and import this file in contracts that require access to these values. This is just a suggestion, in some use cases this may result in higher gas usage in the distribution.
File: ComptrollerStorage.sol 103: uint256 internal constant NO_ERROR = 0; File: ErrorReporter.sol 5: uint256 public constant NO_ERROR = 0; // support legacy return codes
ComptrollerStorage.sol#L103 vs ErrorReporter.sol#L5
File: ComptrollerStorage.sol 109: uint256 internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 112: uint256 internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9
ComptrollerStorage.sol#L109-L112
File: BaseJumpRateModelV2.sol 13: uint256 private constant BASE = 1e18; File: ExponentialNoError.sol 20: uint256 internal constant expScale = 1e18; File: VTokenInterfaces.sol 56: uint256 internal constant reserveFactorMaxMantissa = 1e18; File: WhitePaperInterestRateModel.sol 12: uint256 private constant BASE = 1e18;
BaseJumpRateModelV2.sol#L13 vs ExponentialNoError.sol#L20 vs VTokenInterfaces.sol#L56 vs WhitePaperInterestRateModel.sol#L12
Consider using named parameters in mappings (e.g. mapping(address account => uint256 balance)) to improve readability. This feature is present since Solidity 0.8.18
Cases already listed in N-01
#0 - c4-judge
2023-05-18T18:32:03Z
0xean marked the issue as grade-b