Platform: Code4rena
Start Date: 26/05/2022
Pot Size: $75,000 USDT
Total HM: 31
Participants: 71
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 18
Id: 126
League: ETH
Rank: 9/71
Findings: 4
Award: $2,825.25
π Selected for report: 1
π Solo Findings: 1
π Selected for report: kirk-baird
Also found by: Dravee, IllIllI, Koustre, Ruhum, VAD37, csanuragjain, gzeon, unforgiven, xiaoming90
VE3DRewardPool.sol
is forked from cvxRewardPool.sol
. Unlike the original every token address is immutable. The current implementation bundle all token on platform together into a single pool contract. This introduces new addReward()
function to add new token to the pool instead of deploy new contract for every new token.
New improvement introduce new centralize issue with admin power limitation. Compromised admin can replace tokenDeposit address anytime to take control of user rewards or deposits flow into unknown address.
The lack of finality in addReward()
should be addressed.
veAssetDeposits
and ve3Token
address for all tokens inside the pool to attacker wallet contract.getReward()
. VE3DRewardPool.sol
will approve all reward and transfer to attacker contract that implement deposit
and stakeFor
functionThe addReward() function should only allow add token once.
function addReward( address _rewardToken, address _veAssetDeposits, address _ve3TokenRewards, address _ve3Token ) external onlyOwner { require(rewardTokens.add(_rewardToken),"token already added"); // EnumerableSet return false if address already exist rewardTokenInfo[_rewardToken].veAssetDeposits = _veAssetDeposits; //veAssetDepositor idle token rewardTokenInfo[_rewardToken].ve3TokenRewards = _ve3TokenRewards; // baseRewardPool rewardTokenInfo[_rewardToken].ve3Token = _ve3Token; //ve3Idle }
In case of pool migration or add wrong pool address. There should be separate function for DAO specifically to remove pool. Or better create its own pool for each token like Convex.
#0 - solvetony
2022-06-15T16:45:05Z
Duplicate of #171 #179 #136
#1 - GalloDaSballo
2022-07-27T01:22:34Z
Dup of #136
π Selected for report: xiaoming90
562.3122 USDT - $562.31
VeTokenMinter.sol
is similar to Convex token
Admin suppose to mint 30M ve3Token as total supply and add them to VeTokenMinter
contract and allow Booster to transfer some of these token away to rewards user for depositing LP into booster.
User with larger amount of ve3Token can receive bigger portion of token from reward pool.
It doesn't make sense to have extra withdraw function belong to admin to take away booster staking token.
The veFinance class diagram from README.md
does not include withdraw function. And consider the original fork does not have centralized money flow issue that admin can blatantly take away from user. The withdraw function seem quite odds place to be.
The centralized issue of what admin intended to do with ve3Token need to be addressed.
VeTokenMinter
contract, it is possible to have booster stop working due to lack of ve3Token to rewards user.Provide document explain the limitation of admin and ownership on ve3Token.
#0 - solvetony
2022-06-15T16:46:43Z
We would update readme, however let use lower severity for that
#1 - GalloDaSballo
2022-07-26T00:59:44Z
Dup of #202
π Selected for report: xiaoming90
Also found by: 0xNazgul, FSchmoede, Funen, Kumpa, VAD37, berndartmueller, cccz, kirk-baird
earmarkFees()
claim fee rewards and split it into lockFees
rewards and stakerLockRewards
reward pool.
The original fork version does not split the fee, but current version did
Under condition, admin set wrong fee incentive amount, lockFeesIncentive + stakerLockFeesIncentive != FEE_DENOMINATOR
. It is possible to have feeToken stuck inside Booster.sol
or contract revert due to fee bigger than balance.
earmarkFees()
to send asset rewards to reward pools._balance
send to the pool rewards is not 100% total asset balance. Some leftover token still stuck inside the pool.setFeeInfo()
should add require check to prevent splited fee not combined to 100%.
add require(_lockFeesIncentive + _stakerLockFeesIncentive == FEE_DENOMINATOR), "!fee"
here would suffice.
#0 - solvetony
2022-06-15T16:44:23Z
Duplicate of #215
#1 - GalloDaSballo
2022-07-26T00:48:24Z
Dup of #215
π Selected for report: VAD37
2082.6377 USDT - $2,082.64
https://github.com/code-423n4/2022-05-vetoken/blob/2d7cd1f6780a9bcc8387dea8fecfbd758462c152/contracts/VE3DRewardPool.sol#L296-L299 https://github.com/code-423n4/2022-05-vetoken/blob/1be2f03670e407908f175c08cf8cc0ce96c55baf/contracts/VeAssetDepositor.sol#L134-L152
Project veToken is supposed to be a generalized version of Convex for non-Curve token. There is only one contract for all rewards token in the platform.
All ve3Token rewards are bundled together inside ve3DLocker
and ve3DRewardPool
in a loop. Instead of having its own unique contract like VeAssetDepositer
or VoterProxy
for each token.
If one token has pausable transfer, user cannot claim rewards or withdraw if they have multiple rewards include that pause token.
Right now the project intends to support only 6 tokens, including Ribbon token which has pausable transfer controlled by Ribbon DAO.
Normally, this would not be an issue in Convex where only a few pools would be affected by single coin. Since, veAsset are bundled together into single reward pool, it becomes a major problem.
VE3DRewardPool
try call getReward()
, VeAssetDepositor
try deposit token from earned rewards does not work anymore because IERC20.transfer
is blocked. This effectively reverts current function if user have this token reward > 0.It would be a better practice if we had a second getReward()
function that accepts an array of token that we would like to interact with.
It saves gas and only requires some extra work on frontend website.
Instead of current implementation, withdraw all token bundles together.
#0 - solvetony
2022-06-15T16:48:02Z
It might happen rare, but we might fix that
#1 - GalloDaSballo
2022-07-26T01:02:34Z
The warden has shown how, in certain cases, because a reward token could be pausable, this can cause the entire claim process to break as getReward
doesn't allow the caller to specify which tokens to receive.
I have to agree that the odds are low, however the finding is valid and because it's reliant on external conditions I believe Medium Severity to be appropriate