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:

pokeTokens: introduced in Version 3, allows the admin to call the poke function on all veALCX tokens in the system. Note that we assume that this function is called by the keeper at the end of each epoch (otherwise users would be unable to vote).

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

Was this helpful?