Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
In AlchemixDAO, each $veToken can vote once per epoch, and users can receive $FLUX as a reward after voting. However, the Voter.poke interface allows users to easily repeat previous votes without check whether the $veToken has already voted in the current epoch. As a result, users can call the poke interface infinitely to receive $FLUX repeatedly.
Vulnerability Details
Please see the following code. The Voter.vote interface uses the onlyNewEpoch modifier to check whether $veToken has voted in the current epoch.
However, the Voter.poke interface, which also has the voting function, does not check lastVoted, causing users to call the interface repeatedly.
///// https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Voter.sol#L195-L212
functionpoke(uint256_tokenId) public {// Previous boost will be taken into account with weights being pulled from the votes mappinguint256 _boost =0;if (msg.sender != admin) {require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId),"not approved or owner"); }address[] memory _poolVote = poolVote[_tokenId];uint256 _poolCnt = _poolVote.length;uint256[] memory _weights =newuint256[](_poolCnt);for (uint256 i =0; i < _poolCnt; i++) { _weights[i] = votes[_tokenId][_poolVote[i]]; }_vote(_tokenId, _poolVote, _weights, _boost); }
Suggested fix
Check the lastVoted of the token.
- function poke(uint256 _tokenId) public {+ function poke(uint256 _tokenId) public onlyNewEpoch(_tokenId) { // Previous boost will be taken into account with weights being pulled from the votes mapping uint256 _boost = 0; if (msg.sender != admin) { require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId), "not approved or owner"); } address[] memory _poolVote = poolVote[_tokenId]; uint256 _poolCnt = _poolVote.length; uint256[] memory _weights = new uint256[](_poolCnt); for (uint256 i = 0; i < _poolCnt; i++) { _weights[i] = votes[_tokenId][_poolVote[i]]; } _vote(_tokenId, _poolVote, _weights, _boost); }
Impact Details
Users can infinitely copy $FLUX causing Alchemix token economics to collapse.