31249 - [SC - Critical] malicious user can back-run Voterdistribute to ...

Submitted on May 15th 2024 at 19:58:42 UTC by @jasonxiale for Boost | Alchemix

Report ID: #31249

Report type: Smart Contract

Report severity: Critical

Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol

Impacts:

  • Theft of unclaimed yield

Description

Brief/Intro

In current implementation, Voter.distribute is used to distribute ALCX among gauges, during the call there is an issue that a malicious user can back-run Voter.distribute to steal reards.

Vulnerability Details

During the Voter.distribute function, Voter._distribute is called, and at the end of Voter._distribute, IBribe.resetVoting is called at [https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L377] IBribe.resetVoting is defined as:

345     /// @inheritdoc IBribe
346     function resetVoting() external {
347         require(msg.sender == voter);
348         totalVoting = 0;
349     }

So it means that after calling Voter.distribute, Bribe.totalVoting will be set to 0.

Then in Bribe.earned, Bribe.totalVoting is used in Bribe.sol#L257-L261 and Bribe.sol#L268-L277. One thing to note is that:

So it means that if _priorSupply will be set to 1 if it's 0. And reward depends on _priorSupply as:

To sum up:

  1. During Voter.distribute, Bribe.totalVoting will be set to 0

  2. Bribe.earned depends of Bribe.totalVoting to calculate the amount of rewards. And if we can force _priorSupply to 1 while calculating the rewards, we will make more profilt. We can use Voter.poke to update the checkpoint after Voter.distribute.

Impact Details

In current implementation, Voter.distribute is used to distribute ALCX among gauges, during the call there is an issue that a malicious user can back-run Voter.distribute to steal reards.

References

Add any relevant links to documentation or code

Proof of Concept

put the follow code in src/test/Voting.t.sol and run

As we can from above, if Alice doesn't call Voter.poke after Voter.distribute, Alice will receive 33333333333333333333333 bal rewards.

And if Alice calls Voter.poke after Voter.distribute, Alice will receive 100000000000000000000000 bal rewards.

Last updated

Was this helpful?