30925 - [SC - Critical] Manipulation of governance voting result by unl...
Manipulation of governance voting result by unlimited minting the Flux Token by exploiting the logic of reset and merge tokenId
Submitted on May 8th 2024 at 11:09:39 UTC by @perseverance for Boost | Alchemix
Report ID: #30925
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
Description
Description
Brief/Intro
Flux token implements a standard ERC20 token with extra features. Flux tokens are accrued by users of VotingEscrow when voting in the contract Voter. Flux tokens can be used to: i) exit a ve-position early by paying a penalty fee when calling function startCooldown, ii) boost voting power of a NFT holder in contract Voter, or iii) as a normal ERC20 token that can be traded in other systems.
So Flux tokens can be used to boost the voting power of a NFT holder. It is shown in the code of vote() function as below.
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol#L228C5-L233C6
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol#L412-L455
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol#L195-L199
So users can boost the voting power up to unclaimedFlux of the tokenId.
The unclaimedFlux[_tokenId] is updated in the function accrueFlux()
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol#L188C5-L192C6
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol#L377-L385
So according to the design of the Alchemix DAO system, if an user have a locked tokenID then with balanceA then the user can get maximum Fluxtoken for 1 epoch is
The vulnerability
Vulnerability Details
Now an attacker can mint the several times bigger than the amount of Flux Token for 1 epoch intended by Alchemix by the using the same amount of capital.
So if an user have locked 10 * 10 ** 18 BPT token for 2 weeks, then the maximal amount of flux tokens can be claimed is:
216_214_167_934_958_887 = 216 * 10 ** 15 Flux token.
This amount here is just an example.
Attacker can manipulate the system to mint several times to receive about more this amount in 1 epoch. I can demonstrate in the POC, the attacker was able to mint 5 times of the intended amount.
The amount can be minted
By doing so, the attacker can use the Flux token as boost to manipulate the governance voting result by calling the Vote() in Voter contract. Or the attacker can use the unclaimed Flux to mint Flux token.
How to accomplish the exploit of minting?
With an capital (for example 10 * 10 ** 18) The attacker can mint 10 tokenIDs with each lock 10**18 of BPT
The attacker can call the function reset() and merge()
Why this is possible?
Step 1: Attacker call reset(tokenId1)
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol#L183C5-L192C6
Since the attacker is the owner of tokenId so this is normal. This call will call accrueFlux function to update the unclaimedFlux for the tokenId.
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol#L188C5-L192C6
Now this tokenId1 has the balanceTokenId1 => unclaimedFlux[_tokenId] += balanceTokenId1 * K
K is explained above.
Step 2: Attacker call function to merge the tokenId1 with tokenId2
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol#L618C5-L651C6
In function mergeFlux, the unclaimedFlux of tokenId1 is added to unclaimedFlux of tokenId2
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol#L180C5-L185C6
You notice that the balance of tokenId2 is also added with balance of tokenId1 in the _depositFor
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol#L1331
So now if the attacker call reset(tokenId2) to accrue the unclaimFlux
The unclaimFlux will be
So you can see that after first loop, the unclaimedFlux is increased abnormally.
By repeating this loop, the attacker will be able to get several times bigger amount of Flux token. In the POC, I demontrated that the attacker can get 5 times bigger by repeating this loop 9 times.
Impacts
About the severity assessment
The impact is that the attacker will be able to exploit the system to get several times bigger Flux token for the same capital. For example, in the POC, the attacker can get 5 times bigger with the same capital.
Since the Flux tokens can be used to boost the Voting power in Vote function, the the boost can manipulate the governance voting result. The attacker can also mint Flux token to get benefit as the Flux token can be traded for other assets as stated by the protocol document.
The severity: Critial
Category:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Unauthorized or malicious minting of Flux token
Capital for the attack: Gas to execute the transactions. Amount of BPT can be big, the the bigger amount of Flux tokens can be minted, manipulated.
Easy to exploit and easy to be automated.
Proof of concept
Proof of concept
The POC code:
In this POC, I use 10 * 10**18 BPT token. The attacker create 10 tokenIds and repeately call reset(tokenId) and merge
The log shows:
So at the end of the attack: the attacker still have a tokenId with amount: 10000000000000000000 = 10 ** 18 Lock duration: 2 weeks.
So the attacker still can withdraw his capital of BPT token as normal.
The unclaimedFlux of tokenId1: 1189177923641421562
I also created the normal scenario where a user lock 10 ** 10**18 BPT token.
The log of this test case shows:
So the unclaimedFlux1 of the tokenID1 is 216214167934958887
To compare, the attacker get 5 times bigger
So attacker can use the gained Flux token to boost the voting power. In this POC, I demonstrated that the attacker can mint Flux token.
To run the test Copy the test code into the file:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/test/VotingEscrow.t.sol
and run the test command for the function in the alchemix-v2-dao folder:
The full log:
The full log file with debug: https://drive.google.com/file/d/1Zo_Osm4rcdqRBumhmvloZdqdzDuCU24f/view?usp=sharing
Last updated
Was this helpful?