31199 - [SC - Critical] Users might receive less rewars token after Vot...

Submitted on May 14th 2024 at 20:56:20 UTC by @jasonxiale for Boost | Alchemix

Report ID: #31199

Report type: Smart Contract

Report severity: Critical

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

Impacts:

  • Permanent freezing of unclaimed yield

Description

Brief/Intro

A token owner can call Voter.poke to update the voting power, during the Voter.poke call, the Bribe.totalVoting isn't updated correctly, which results that the Bribe.earned will not calculate the rewards correctly.

Vulnerability Details

While a token owner calls Voter.poke, Voter._reset is called at the beginning of the Voter._vote. In Voter._reset, Bribe.withdraw is called in Voter.sol#L396 And Bribe.withdraw is defined as

319     function withdraw(uint256 amount, uint256 tokenId) external {
320         require(msg.sender == voter);
321 
322         totalSupply -= amount;
323         balanceOf[tokenId] -= amount;
324 
325         _writeCheckpoint(tokenId, balanceOf[tokenId]);
326         _writeSupplyCheckpoint();
327 
328         emit Withdraw(msg.sender, tokenId, amount);
329     }

On other side, Bribe.deposit is defined as

As show above, totalVoting isn't updated in Bribe.withdraw, and the function doesn't call _writeVotingCheckpoint to update the checkpoint.

Then, while calculating the reward in Bribe.earned, the function uses votingCheckpoints.votes to calculate the rewards in Bribe.sol#L255-L261 and Bribe.sol#L268-L277

So to sum up, during Voter.poke call:

  1. Bribe.withdraw will be called, but within the function Bribe.totalVoting isn't deducting amount

  2. Bribe.deposit will be called in Voter._vote, but this time, Bribe.totalVoting is added amount So Voter.poke function will cause Bribe.totalVoting to increase. Then when calculating the rewards amount in Bribe.earned, Bribe.totalVoting is used, which will result wrong amount of rewards.

Impact Details

User might receive less reward token after Voter.poke is called, and the unclaimed reward token will stuck in the contract.

References

Add any relevant links to documentation or code

Proof of Concept

Add the following code to src/test/Voting.t.sol, and run

From the output we can see that

  1. in testAliceBribesNoPoke Alice doesn't call Voter.poke, token1 and token2 will get 50000*1e18 aura

  2. in testAliceBribesPoke, Alice calls Voter.poke 3 times, token1 and token2 will get 20000*1e18 aura

Last updated

Was this helpful?