31388 - [SC - Critical] Vulnerability in the poke function of Voting co...
Submitted on May 17th 2024 at 22:57:04 UTC by @b0g0 for Boost | Alchemix
Report ID: #31388
Report type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol
Impacts:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
User can indefinitely accrue and mint FluxTokens
Description
Brief/Intro
The poke()
function of Voter.sol
introduces a vulnerability, allowing a token holder to increase his Flux balance as much as he likes. Unrestricted supply of Flux gives unfair advantage to voter and allows him to influence the votes and escape penalty when withdrawing
Vulnerability Details
Voter.sol
has 3 main function to manage votes:
vote()
- used to vote for pools (calledONCE
per epoch)poke()
- updates the voting status of an existing vote (can be calledMANY
time per epoch)reset()
- resets the vote ( calledONCE
per epoch)
Both vote()
and poke()
call the internal _vote()
function, which resets the previous vote, creates a new vote based on the current voting power and accrues new Flux based on it:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L412
The important part to focus on here is that Flux is accrued
every
time _vote()
is invoked. The amount accrued is based on the token voting power. FluxToken::accrueFlux()
looks like this:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/FluxToken.sol#L188
The other important thing to note about FLUX is that it should accrue once EACH EPOCH
The docs explicitly state that :
The FLUX token is a special reward token that accrues to veALCX positions each epoch...
Link -> https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/CONTRACTS.md#fluxtokensol
And this is where the bug lies - Voter::poke()
can be called an unlimited amount of times each epoch. As a result _vote()
will also be called, meaning Flux balances will get accrued on every call, practically enabling the token owner to source as much Flux as he wants.
Impact Details
Having an unlimited supply of Flux gives the NFT owner the power to:
boost all his votes, all the time, influencing the outcome of the votes for the pools - the bigger the value locked in an NFT, the more Flux it can steal and the greater influence it can have on the votes
unlock his position at any time - effectively evading penalty and withdrawing much sooner than intended. This defeats the whole purpose of escrowing and causes harm to the protocol
References
The poke()
function:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L195
Proof of Concept
I've added a POC inside the currently existing Voting.t.sol
test file: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/test/Voting.t.sol
You can run the POC like this: forge test --mt testPokeExploit --fork-url https://eth.llamarpc.com --fork-block-number 17133822 -vvvv
Last updated