31388 - [SC - Critical] Vulnerability in the poke function of Voting co...
Last updated
Was this helpful?
Last updated
Was this helpful?
Submitted on May 17th 2024 at 22:57:04 UTC by @b0g0 for
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
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
Voter.sol
has 3 main function to manage votes:
vote()
- used to vote for pools (called ONCE
per epoch)
poke()
- updates the voting status of an existing vote (can be called MANY
time per epoch)
reset()
- resets the vote ( called ONCE
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.
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
The poke()
function:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L195
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