31588 - [SC - Low] Users could start cooldown period for their wit...
Submitted on May 21st 2024 at 15:18:40 UTC by @savi0ur for Boost | Alchemix
Report ID: #31588
Report type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Bug Description
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L379
function claimableFlux(uint256 _tokenId) public view returns (uint256) {
// If the lock is expired, no flux is claimable at the current epoch
if (block.timestamp > locked[_tokenId].end) { //@audit
return 0;
}
// Amount of flux claimable is <fluxPerVeALCX> percent of the balance
return (_balanceOfTokenAt(_tokenId, block.timestamp) * fluxPerVeALCX) / BPS;
}In claimableFlux function, block.timestamp > locked[_tokenId].end is used to check if lock is expired. According to this condition, lock is not expired till block.timestamp == locked[_tokenId].end.
But in other places of the code, same check is not there. As shown below. https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L1156
While calculating point and bias, its first checking if lock has not expired _locked.end > _time and _locked.amount > 0, then calculate Point. According to above check from claimableFlux function, it should be _locked.end >= _time.
Similarly, https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L727
It should be if (!_locked.maxLockEnabled) require(_locked.end >= block.timestamp, "Lock expired");
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L672
It should be require(_locked.end >= block.timestamp, "Cannot add to expired lock. Withdraw");
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L748
For cooldown period check, it should not allow to withdraw when block.timestamp == _locked.cooldown, as cooldown has not expired yet when its equal. It should be require(block.timestamp > _locked.cooldown, "Cooldown period in progress");
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L630-L631
It should be require(_lockedX.end >= block.timestamp, "Cannot merge when lock expired");.
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Bribe.sol#L196
It should be if (votingCheckpoints[nCheckpoints - 1].timestamp <= timestamp) {. See getPriorBalanceIndex function.
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/Bribe.sol#L229
It should be if (block.timestamp - _bribeStart(_startTimestamp) <= DURATION) {.
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L792
It should be if (block.timestamp <= _locked.end) {.
Due to the above incorrect checks, user's could start cooldown period for their withdrawal without paying any FLUX tokens.
Impact
User's could start cooldown period for their withdrawal without paying any FLUX tokens. User will have both FLUX tokens and their BPT tokens at the end of EPOCH. Its not align with what the project requirement is, i.e, to charge rage quit fee in terms of FLUX tokens for withdrawing before lock ends.
This free FLUX tokens later can be use to boost their votes.
Recommendation
Implement correct checks as described above.
References
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Bribe.sol
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/Voter.sol
Proof Of Concept
Steps to Run using Foundry:
Paste following foundry code in
src/test/VotingEscrow.t.solRun using
FOUNDRY_PROFILE=default forge test --fork-url $FORK_URL --fork-block-number 17133822 --match-contract VotingEscrowTest --match-test testCooldownWithoutPayingFlux -vv
Console Output:
Last updated
Was this helpful?