Unable To Get Rewards If Admin Withdraws $VE3D tokens From VeTokenMinter
Contract
#202
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
sponsor confirmed
Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Lines of code
https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/VeTokenMinter.sol#L48
Vulnerability details
Vulernability Details
It was observed that users will not be able to get their rewards from the reward contract at certain point of time if admin withdraws $VE3D token from the
VeTokenMinter
contract.Proof-of-Concept
Based on the deployment script, it was understood that at the start of the project deployment, 30 million $VE3D tokens will be pre-minted for the
VeTokenMinter
contract. Thus, theveToken.balanceOf(VeTokenMinter.address)
will be 30 million $VE3D tokens after the deployment.https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/migrations/2_deploy_basic_contracts.js#L18
In the
VeTokenMinter
contract, there is a function calledVeTokenMinter.withdraw
that allows the admin to withdraw $VE3D tokens from the contract. Noted that this withdraw function only perform the transfer, but did not update any of the state variables (e.g. totalSupply, maxSupply) in the contract.https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/VeTokenMinter.sol#L77
Assuming that an admin withdrawed 29 million $VE3D tokens from the
VoteProxy
with the appropriate approval from the DAO or community for some valid purposes. TheveToken.balanceOf(VeTokenMinter.address)
will be 1 million $VE3D tokens after the withdrawal.At this point, notice that
veToken.balanceOf(VeTokenMinter.address)
is 1 million, while theVeTokenMinter.maxSupply
constant is 30 million. Therefore, there exists a discrepency between the actual amount of $VE3D tokens (1 million) stored in the contact versus the max supply (30 million).This discrepency will cause an issue in the
VeTokenMinter.mint
function because the calculation of the amount of $VE3D tokens to be transferred is based on the fact that 30 million $VE3D tokens is always sitting in theVeTokenMinter
contract, and thus there is always sufficient $VE3D tokens available in theVeTokenMinter
contract to send to its users.The
uint256 amtTillMax = maxSupply.sub(supply);
code shows that the calculation is based onmaxSupply
constant, which is 30 million.Assume that
mint(0x001, 10 million)
is called, and the value of the state variables when stepping through this function are as follows:maxSupply
constant = 30 millionveToken.balanceOf(VeTokenMinter.address)
= 1 millionsupply
&totalSupply
= 20 milliontotalCliffs
= 1000reductionPerCliff
= 30,000 (maxSupply / totalCliffs)cliff
= 666 (supply/reductionPerCliff)reduction
= 1000 - 666 = 334_amount
= 10 million * (334/1000) = 3.340 millionamtTillMax
= 10 million (maxSupply - supply) (Over here the contract assume that it still has 10 million VE3D tokens more to reach the max supply)(_amount > amtTillMax)
=False
(since "3.340 million > 10 million" = false )veToken.safeTransfer(0x001, 3.340 million)
(This will revert. Insufficent balance)The
veToken.safeTransfer(0x001, 3.340 million
will fail and revert becauseVeTokenMinter
contract does not hold sufficent amount of $VE3D tokens to transfer out.veToken.balanceOf(VeTokenMinter.address)
= 1 million, while the contract was attempting to send out 3.340 million.https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/VeTokenMinter.sol#L48
The failure/revert of
VeTokenMinter.mint
function will cascade up toBooster.rewardClaimed
, and futher cascade up toBaseRewardPool.getReward
. Thus,BaseRewardPool.getReward
will stop working. As a result, the users will not be able to get any rewards from the reward contracts.This issue will affect all projects (Curve, Pickle, Ribbon, Idle, Angle, Balancer) because
VeTokenMinter
contract is deployed once, and referenced by all the projects. Thus, the impact could be quite widespread if this occurs, and many users would be affected.https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/Booster.sol#L598
https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/BaseRewardPool.sol#L267
Recommended Mitigation Steps
Remove the
VeTokenMinter.withdraw
function if possible. Otherwise, update the internal accounting ofVeTokenMinter
contract during withdrawal so that the actual balance of the $VE3D tokens is taken into consideration within theVeTokenMinter.mint
, and the contract will not attempt to transfer more tokens than what it has.On a side note, Convex's Minter contract, will mint the
CRX
gov tokens to the users on the fly. See https://github.com/convex-eth/platform/blob/1f11027d429e454dacc4c959502687eaeffdb74a/contracts/contracts/Cvx.sol#L76. Thus, there will not be a case where there is not sufficientCRV
tokens in the contract to send to it users.However, in VeToken Protocol, it attempts to transfer the portion of pre-minted $VE3D tokens (30 millions) to the users. See https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/VeTokenMinter.sol#L72. Thus, it is possible that there is not enough $VE3D tokens to send to its users if the admin withdraw the pre-minted $VE3D tokens.
The text was updated successfully, but these errors were encountered: