30999 - [SC - Critical] An edge-case mints times more FLUX than it should
Submitted on May 10th 2024 at 12:45:39 UTC by @infosec_us_team for Boost | Alchemix
Report ID: #30999
Report type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol
Impacts:
Protocol insolvency
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Background for Immunefi's triage team
In the 3rd interaction with ChainSecurity (referred to as Version 3
) the poke(...)
and pokeTokens(...)
functions were added to Alchemix's codebase.
This report is about the pokeTokens(...)
function.
Before diving right into it, we want to add context for Immunefi's triage team; below is ChainSecurity's overview of the new pokeTokens(..)
function:
A link to the full PDF is on the Boost page.
Finally, here's Alchemix's overview of this function:
Github Link: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/interfaces/IVoter.sol#L117-L122
Vulnerability Details
The pokeTokens(...)
function is called by the admin to update the voting status of multiple veALCXs and reset expired tokens.
Github Link: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol?#L215-L225
The reset(...)
function besides resetting the token, accrues FLUX rewards.
On top of maintaining the same voting status, the poke(...)
function also accrues FLUX rewards.
As part of the system design, the amount of FLUX rewards that can be accrued in every epoch for a position that enabled "max lock" never decays.
Max lock is 1 year, if a user does not interact with the protocol for over a year his "max lock" position expires
There's an edge case when using pokeTokens(...)
to update the voting status of an expired position with max lock enabled. FLUX is accrued twice for the position, first inside reset(...)
and then inside poke(...)
.
Impact Details
The impacts of minting more FLUX tokens than what the system was designed to are well known to the protocol.
FLUX is used to boost a veToken holder's voting power and exit a ve-position early, therefore edge cases that can break the protocol invariants and mint a bunch of additional FLUX will destabilize Alchemix's ecosystem.
Proof of Concept
We modified the foundry test "testVotingPowerDecay" inside alchemix-v2-dao/src/test/Voting.t.sol
to include this edge case.
Our test (named "testVotingPowerDecayDoubleAccrue") asserts that the user receives the correct amount of FLUX tokens.
When running this test in the current version of the codebase the user will receive 2x more FLUX tokens, therefore the test will "fail" until Alchemix fixes this edge case.
Last updated