Submitted on May 17th 2024 at 23:44:47 UTC by @gladiator111 for Boost | Alchemix
Report ID: #31390
Report type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/FluxToken.sol
Impacts:
Permanent freezing of unclaimed royalties
Description
Brief/Intro
Precision Loss is there in FluxToken.sol::getClaimableFlux
resulting in getting less Flux Token
Vulnerability Details
Note - Please adjust the Severity Level or impact as you seem proper. I have selected the most close impact from the impact list.
In the function FluxToken.sol::getClaimableFlux
// @audit Precision loss
function getClaimableFlux(uint256 _amount, address _nft) public view returns (uint256 claimableFlux) {
uint256 bpt = calculateBPT(_amount); // .4 times amount
uint256 veMul = IVotingEscrow(veALCX).MULTIPLIER(); // 2
uint256 veMax = IVotingEscrow(veALCX).MAXTIME(); // 365 days
uint256 fluxPerVe = IVotingEscrow(veALCX).fluxPerVeALCX(); // 5000 or 50%
uint256 fluxMul = IVotingEscrow(veALCX).fluxMultiplier(); //4 or 4x
// Amount of flux earned in 1 yr from _amount assuming it was deposited for maxtime
claimableFlux = (((bpt * veMul) / veMax) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul;
//((( 40*amount * 2) / 31536000) * 31536000 * ( 5000 + 10000 ))/ 10000 / 4
// Claimable flux for alchemechNFT is different than patronNFT
if (_nft == alchemechNFT) {
claimableFlux = (claimableFlux * alchemechMultiplier) / BPS; // flux * 5 /10000 or 0.05% flux
}
}
The calculation of claimable flux leads to precision loss because of division before multiply
claimableFlux = (((bpt * veMul) / veMax) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul;
Notice the veMax is divided before multiplying which leads to precision loss.
Impact Details
User will get less Flux because of precision Loss.
Suggestion / Recommendation
- claimableFlux = (((bpt * veMul) / veMax) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul;
+ claimableFlux = (((bpt * veMul)) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul / veMax;
References
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/FluxToken.sol#L224
Proof of Concept
Add the following to the FluxToken.t.sol and run using
forge test --match-test testGetClaimableFluxPrecisionLoss -vvvv --fork-url $FORK_URL
function testGetClaimableFluxPrecisionLoss() public {
uint256 claimableFlux = flux.getClaimableFlux(350000, patronNFT);
uint256 correctClaimableFlux = CorrectedClaimableFlux(350000, patronNFT);
uint256 claimableFluxWithDifferentAmount = flux.getClaimableFlux(1e18, patronNFT);
uint256 correctClaimableFluxWithDifferentAmount = CorrectedClaimableFlux(1e18, patronNFT);
console.log(claimableFlux);
console.log(correctClaimableFlux);
console.log(claimableFluxWithDifferentAmount);
console.log(correctClaimableFluxWithDifferentAmount);
assert(claimableFlux != correctClaimableFlux);
assert(claimableFluxWithDifferentAmount != correctClaimableFluxWithDifferentAmount);
}
function CorrectedClaimableFlux(uint256 _amount, address _nft) public view returns (uint256 claimableFlux) {
// Values are Hardcoded for simplification
uint256 bpt = 40*_amount;/*calculateBPT(_amount);*/ // .4 times amount
uint256 veMul = 2; /*IVotingEscrow(veALCX).MULTIPLIER();*/ // This equals 2
uint256 veMax = 365 days; /*IVotingEscrow(veALCX).MAXTIME(); */ // This equals 365 days
uint256 fluxPerVe = 5000;/*IVotingEscrow(veALCX).fluxPerVeALCX();*/ // This equals 5000 or 50%
uint256 fluxMul = 4;/*IVotingEscrow(veALCX).fluxMultiplier();*/ // This equals 4 or 4x
// Amount of flux earned in 1 yr from _amount assuming it was deposited for maxtime
// Precision Loss fixed here
claimableFlux = (((bpt * veMul)) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul / veMax;
//((( 40*amount * 2) / 31536000) * 31536000 * ( 5000 + 10000 ))/ 10000 / 4
// Claimable flux for alchemechNFT is different than patronNFT
if (_nft == alchemechNFT) {
claimableFlux = (claimableFlux * 5) / BPS; // flux * 5 /10000 or 0.05% flux
}
}
Last updated