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: 36/71
Findings: 2
Award: $203.03
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: xiaoming90
Also found by: 0xNazgul, FSchmoede, Funen, Kumpa, VAD37, berndartmueller, cccz, kirk-baird
The fee manager of the Booster
contract can set any high fee values for both lockFeesIncentive
and stakerLockFeesIncentive
via the Booster.setFeeInfo
function.
Both fee values should sum up to a maximum of 100% (FEE_DENOMINATOR = 10000
). If set to higher values, it can DoS the Booster.earmarkFees
function if there are insufficient fee tokens in the contract.
Booster.setFeeInfo(uint256 _lockFeesIncentive, uint256 _stakerLockFeesIncentive)
function setFeeInfo(uint256 _lockFeesIncentive, uint256 _stakerLockFeesIncentive) external { require(msg.sender == feeManager, "!auth"); lockFeesIncentive = _lockFeesIncentive; // @audit-info missing upper bound validation stakerLockFeesIncentive = _stakerLockFeesIncentive; // @audit-info missing upper bound validation address _feeToken = IFeeDistro(feeDistro).token(); if (feeToken != _feeToken) { //create a new reward contract for the new token lockFees = IRewardFactory(rewardFactory).CreateTokenRewards(_feeToken, lockRewards); if (_feeToken != veAsset) { IRewards(stakerLockRewards).addReward( _feeToken, address(0), address(0), address(0), address(this), false ); } feeToken = _feeToken; } }
Manual review
Consider adding a reasonable upper bounds for both lockFeesIncentive
and stakerLockFeesIncentive
.
#0 - jetbrain10
2022-06-15T16:45:42Z
same as #85
#1 - GalloDaSballo
2022-07-24T22:07:13Z
Dup of #215
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xf15ers, BouSalman, Chom, Deivitto, Dravee, ElKu, FSchmoede, Funen, GimelSec, Hawkeye, MiloTruck, Picodes, SecureZeroX, SmartSek, TerrierLover, WatchPug, _Adam, asutorufos, berndartmueller, c3phas, catchup, cccz, cogitoergosumsw, cryptphi, csanuragjain, delfin454000, dipp, ellahi, gzeon, hansfriese, horsefacts, hyh, kirk-baird, minhquanym, oyc_109, pauliax, reassor, robee, sashik_eth, shenwilly, simon135, sorrynotsorry, sseefried, unforgiven, xiaoming90, z3s
103.4161 USDT - $103.42
Ownable
have renounceOwnership()
functionalityRewardContractsUpdated
always emits even if reward contracts have not changedZero-address checks are a best practice for input validation of critical address parameters. While the codebase applies this to most cases, there are many places where this is missing in constructors and setters.
Impact: Accidental use of zero-addresses may result in exceptions, burn fees/tokens or force redeployment of contracts.
L103: stakingToken = IERC20(stakingToken_);
L104: rewardToken = IERC20(rewardToken_);
L105: operator = operator_;
L106: rewardManager = rewardManager_;
L111: staker = _staker;
L116: minter = _minter;
L117: veAsset = _veAsset;
L118: feeDistro = _feeDistro;
L131: feeManager = _feeM;
L137: poolManager = _poolM;
L164: rewardArbitrator = _arb;
L170: voteDelegate = _voteDelegate;
L97: stakingToken = IERC20(stakingToken_);
L99: rewardManager = rewardManager_;
L45: staker = _staker;
L46: minter = _minter;
L47: veAsset = _veAsset;
L48: escrow = _escrow;
L55: feeManager = _feeManager;
L27: veToken = ERC20(veTokenAddress);
L50: veAsset = _veAsset;
L51: escrow = _escrow;
L52: gaugeProxy = _gaugeProxy;
L54: minter = _minter;
L64: owner = _owner;
Add zero-address checks, e.g.:
require(_asset != address(0), "Zero-address");
The owner
plays a critical role in the Booster
and VoterProxy
contracts.
Given that changing owner
uses a single-step process for ownership transfer, it is very risky to perform those changes in a single step because it is irrecoverable from any mistakes.
function setOwner(address _owner){ require(msg.sender == owner, "!auth"); owner = _owner; emit OwnerUpdated(_owner); }
function setOwner(address _owner){ require(msg.sender == owner, "!auth"); owner = _owner; }
Consider implementing a two-step process where the current owner
nominates an account and the nominated account needs to call an acceptOwner()
function for the transfer of owner
ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
Ownable
have renounceOwnership()
functionalityRenouncing ownership will leave the contract without an owner. This functionality is desirable in certain scenarios and is typically allowed by libraries such as Ownable
.
Following contracts inherit from Ownable
, thus having the functionality to renounce ownership:
VE3DRewardPool.sol
VeTokenMinter.sol
Consider overriding renounceOwnership()
function in contracts to prevent renouncing ownership accidentally.
When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.
L102: function addReward(
L114: function addOperator(address _newOperator)
L118: function removeOperator(address _operator)
L134: function addExtraReward(address _reward){
L141: function clearExtraRewards()
L32: function addOperator(address _newOperator)
L36: function removeOperator(address _operator)
L41: function updateveAssetWeight(address veAssetOperator, uint256 newWeight)
L62: function setOwner(address _owner)
L67: function setOperator(address _operator)
L77: function setDepositor(address _depositor)
L83: function setStashAccess(address _stash, bool _status)
Emit events for state variable changes.
The VoterProxy.voteGaugeWeight
function accepts two arrays _tokenVote
and _weight
and fails to perform input validation to check if their lengths match. A mismatch could lead to an exception in the best case or undefined behavior in the worst case.
Add input validation to check if the length of both arrays is equal.
RewardContractsUpdated
always emits even if reward contracts have not changedThe event RewardContractsUpdated
in the Booster.setRewardContracts
function always emits, even if the reward contract address have not changed.
The event should only be emitted if contract addresses have actually changed.
Only emit the event RewardContractsUpdated
if reward contracts have actually changed.
The VE3DRewardPool.getReward
function allows claiming the rewards of other users.
While the tokens are sent to the correct address _account
, this can lead to issues with smart contracts that might rely on claiming the rewards themselves.
As one example, suppose the _account
address corresponds to a smart contract that has a function of the following form:
function claimAndDoSomething(epochs) { uint256 claimedAmount = token.balanceOf(address(this)); vE3DRewardPool.getReward(address(this), true, false); claimedAmount = token.balanceOf(address(this)) - claimedAmount; token.transfer(externalWallet, claimedAmount); }
If the contract has no other functions to transfer out funds, they may be locked forever in this contract.
Claiming can also incur a taxable event and the timing is better left to the actual owner.
Do not allow users to claim on behalf of other addresses.
#0 - GalloDaSballo
2022-07-06T23:33:02Z
Valid Low
Valid NC
Disagree with finding, renouncing ownership is a desirable functionality
##Â [L-04] Events not emitted for important state changes Valid NC
Valid Refactor (it just reverts)
NC
I think this is not correct as a general take, however I appreciate the warden flagging it up
Short and sweet good report
1L, 1R, 3NC