31430 - [SC - Insight] QA

Submitted on May 19th 2024 at 01:07:31 UTC by @imsrybr0 for Boost | Alchemix

Report ID: #31430

Report type: Smart Contract

Report severity: Insight

Target: https://immunefi.com

Impacts:

  • Contract fails to deliver promised returns, but doesn't lose value

Description

VotingEscrow cannot be deployed

Issue Description:

The contract size (27031) currently exceeds the maximum allowed code size.

RevenueHandler@claim might leave unused approvals

Issue Description:

This can happen if :

  • There are no deposits.

  • Debt is lower than the approved amount.

RevenueHandler.sol

Bribe@getRewardForOwner writes the same checkpoint in a loop

Issue Description

When multiple tokens are claimed, the same check point (same balance, same timestamp) will be written multiple times (up to the number of claimed tokens).

Bribe.sol

Voter@claimBribes does not check if the bribe is valid

Issue Description

_bribes[i] is a user input and can be any contract that implements getRewardForOwner.

Voter.sol

staleThreshold is 60 days in RewardDistributor@amountToCompound

Issue Description

It seems that the stale threshold was updated for testing purposes.

RewardsDistributor.sol

Rewards will be lost for a token if it's merged before claiming them

Issue Description

VotingEscrow@merge doesn't claim rewards from RewardsDistributor for the merged token before burning it. This makes it impossible to claim the rewards if it wasn't done before merging since the approval check will fail.

VotingEscrow.sol

RewardsDistributor.sol

Voter@pokeTokens will fail when the poked token lock is expired

Issue Description

It will fail at the reset step if the token already voted in the current epoch, either normally or by the owner front running the pokeTokens transaction with a reset.

It will fail at the poke (poke -> vote) step because the voting power of the token will be 0.

Voter.sol

Boost is not carried over in Voter when poking

Issue Description

The following comment in the Voter@poke function suggests that boosts is carried over when poking :

// Previous boost will be taken into account with weights being pulled from the votes mapping

However this is not the case.

For example, a user votes on a single pool with a weight of X using a token which has a voting power of Y and uses a boost of Z :

  • _totalVoteWeight will be equal to X

  • totalPower will be equal to Y + Z

  • _poolWeight will be equal to X * (Y + Z) / X = Y + Z

Now the user pokes using that same token :

  • _totalVoteWeight will be equal to Y + Z

  • totalPower will be equal to Y + 0 = Y

  • _poolWeight will be equal to (Y + Z) * (Y) / (Y + Z) = Y

The weights stay proportional to the initial vote weights but the boost is lost.

Voter.sol

Issue Description

There is no link between the Minter and Voter epochs even if they share the same duration.

This means that voters can vote as soon as possible even before a distribution.

In this case, total votes will be reset to 0 in bribes discarding previous voters and leading to the rewards being split based on future voters but still be distributed to all of them. Not all of them will able to claim though (fewer voters are accounted for mean more rewards per voter).

Voter.sol

Bribe.sol

Proposals can be created with 0 voting power

Issue Description

  • veALCX supply starts at 0

  • veALCX supply is dynamic

  • The proposer threshold (amount needed to be able to create a proposal) is checked at the proposal creation time based on the veALCX balance of the creator and the veALCX total supply at that time :

  • The proposal quorum threshold is checked against the total supply at the start of the voting period :

  • A voter's power is calculated based on the start of the voting period :

This would allow to create a proposal with 0 voting power as the proposal threshold is initially 0 when veALCX total voting power is still 0 (or decays to 0). This may also lead to easily pass a proposal depending on the veALCX voting power created up to the start of the voting period.

Reentrancy guard modifier is named differently across different contracts

Issue Description

Contract
Name
Custom Implementation

VotingEscrow

nonreentrant

Yes

Voter

lock

Yes

RewardsDistributor

nonReentrant

No (OpenZeppelin ReentrancyGuard)

Bribe

lock

Yes

BaseGauge

lock

Yes

Proof of Concept

QA report

Last updated

Was this helpful?