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: 108/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
This contains all my low findings.
The function sweepToken(IERC20Upgradeable token_, address to_, uint256 amount_)
can be used by the owner of PrimeLiquidityProvider to transfer out any amount of any ERC20 token.
It does not take into account the already accrued reward amounts for the token, which are stored in tokenAmountAccrued[token_]
.
Only the current balance is checked against the provided amount, so the owner take any amount up to the contract's balance, including accrued reward amounts:
uint256 balance = token_.balanceOf(address(this)); if (amount_ > balance) { revert InsufficientBalance(amount_, balance); }
Therefore the owner is able to take tokens that should be distributed to Prime token holders.
The function should check the request amount against the leftover balance (e.g. balance - accrued amount):
function sweepToken(IERC20Upgradeable token_, address to_, uint256 amount_) external onlyOwner { uint256 balanceAvailable = token_.balanceOf(address(this)) - tokenAmountAccrued[token_]; if (amount_ > balanceAvailable) { revert InsufficientBalance(amount_, balanceAvailable); } emit SweepToken(address(token_), to_, amount_); token_.safeTransfer(to_, amount_); }
https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/PrimeLiquidityProvider.sol#L192-L205 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/PrimeLiquidityProvider.sol#L261
In PrimeLiquidityProvider there are some locations where total accrued rewards for a token is subtracted from the current balance. If the balance is lower than the rewards amount, an uncaught underflow revert will happen, blocking the functionality. This can happen with for example sweepToken
or rebasing tokens.
For example, in releaseFunds
:
uint256 accruedAmount = tokenAmountAccrued[token_]; tokenAmountAccrued[token_] = 0; IERC20Upgradeable(token_).safeTransfer(prime, accruedAmount);
There is a transfer using the full amount, but there might be enough balance, causing releaseFunds
to always revert until enough new tokens come in.
Also in accrueRewards
on 261.
It would be safer to have releaseFunds
try to send as much as possible and keep the leftover in tokenAmountAccrued[token_]
, so that the Prime contract won't also revert when distributing interest. For example:
uint256 balance = token_.balanceOf(address(this)); uint256 accruedAmount = tokenAmountAccrued[token_]; uint256 leftoverAmount; if (accruedAmount > balance) { leftoverAmount = accruedAmount - balance; accruedAmount = balance; } tokenAmountAccrued[token_] = leftoverAmount; IERC20Upgradeable(token_).safeTransfer(prime, accruedAmount);
https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L411-L414 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L725-L756
The Prime tokens come in irrevocable and revocable versions. The update functions work correctly by not burning irrevocable tokens if the user is no longer eligible, but the burn
function accepts tokens that are irrevocable and it can be called by anyone with the burn
access control role.
If this is not intended, then the _burn
function should check for the irrevocable flag and not allow burning of these tokens.
The issue
function allows for the protocol to directly issue Prime tokens to users. If the user is already a revocable Prime holder, they will be upgraded to irrevocable. But if the user already holds an irrevocable Prime token, the function will incorrectly try to mint
another token to the user on lines 337-342:
if (userToken.exists && !userToken.isIrrevocable) { _upgrade(users[i]); } else { _mint(true, users[i]); _initializeMarkets(users[i]); }
The conditional checks both existence and revocability, but it does not consider the case where it exists and is irrevocable, so it flows into the else
branch, trying to mint
another token.
The minting will revert, because users can only have a single token, causing the whole issuance for all users to be reverted.
The same applies to the other branch, for issuance of revocable tokens on lines 349-357.
The conditional branch should be changed such that the described case will just be ignored instead and not cause a revert. For example:
function issue(bool isIrrevocable, address[] calldata users) external { _checkAccessAllowed("issue(bool,address[])"); if (isIrrevocable) { for (uint256 i = 0; i < users.length; ) { Token storage userToken = tokens[users[i]]; if (userToken.exists) { if (!userToken.isIrrevocable) { _upgrade(users[i]); } } else { _mint(true, users[i]); _initializeMarkets(users[i]); } unchecked { i++; } } } else { for (uint256 i = 0; i < users.length; ) { Token storage userToken = tokens[users[i]]; if (!userToken.exists) { _mint(false, users[i]); _initializeMarkets(users[i]); delete stakedAt[users[i]]; } unchecked { i++; } } } }
#0 - 0xRobocop
2023-10-07T03:04:50Z
L-01 Dup of #42
#1 - c4-pre-sort
2023-10-07T03:05:26Z
0xRobocop marked the issue as low quality report
#2 - c4-judge
2023-11-03T01:36:59Z
fatherGoose1 marked the issue as grade-b