Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
If the deposits.length=0 in RevenueHandler.sol::claim then over Allowance is given to the alchemist.
Vulnerability Details
In the function RevenueHandler.sol::claim
// @audit unnecessary approval to Alchemist if deposits length is 0
/// @inheritdoc IRevenueHandler
function claim(
uint256 tokenId,
address token,
address alchemist,
uint256 amount,
address recipient
) external override {
require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, tokenId), "Not approved or owner");
uint256 amountBurned = 0;
uint256 amountClaimable = _claimable(tokenId, token);
require(amount <= amountClaimable, "Not enough claimable");
require(amount > 0, "Amount must be greater than 0");
require(amount <= IERC20(token).balanceOf(address(this)), "Not enough revenue to claim");
userCheckpoints[tokenId][token].lastClaimEpoch = currentEpoch;
userCheckpoints[tokenId][token].unclaimed = amountClaimable - amount;
// If the alchemist is defined we know it has an alchemic-token
if (alchemists[alchemist] != address(0)) {
require(token == IAlchemistV2(alchemist).debtToken(), "Invalid alchemist/alchemic-token pair");
(, address[] memory deposits) = IAlchemistV2(alchemist).accounts(recipient);
IERC20(token).approve(alchemist, amount);
// Only burn if there are deposits
amountBurned = deposits.length > 0 ? IAlchemistV2(alchemist).burn(amount, recipient) : 0;
}
/*
burn() will only burn up to total cdp debt
send the leftover directly to the user
*/
if (amountBurned < amount) {
IERC20(token).safeTransfer(recipient, amount - amountBurned);
}
emit ClaimRevenue(tokenId, token, amount, recipient);
}
If the deposits.length==0, then unnecessary/undesirable allowance is given to the alchemist.
if (alchemists[alchemist] != address(0)) {
require(token == IAlchemistV2(alchemist).debtToken(), "Invalid alchemist/alchemic-token pair");
(, address[] memory deposits) = IAlchemistV2(alchemist).accounts(recipient);
@> IERC20(token).approve(alchemist, amount);
// Only burn if there are deposits
@> // If deposits.length=0 then the allowance will not be used up
amountBurned = deposits.length > 0 ? IAlchemistV2(alchemist).burn(amount, recipient) : 0;
}
Impact Details
Unnecessary/Over allowance will be given to the Alchemist.
Suggestion/Recommendation
Update the function as follows
function claim(
uint256 tokenId,
address token,
address alchemist,
uint256 amount,
address recipient
) external override {
require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, tokenId), "Not approved or owner");
uint256 amountBurned = 0;
uint256 amountClaimable = _claimable(tokenId, token);
require(amount <= amountClaimable, "Not enough claimable");
require(amount > 0, "Amount must be greater than 0");
require(amount <= IERC20(token).balanceOf(address(this)), "Not enough revenue to claim");
userCheckpoints[tokenId][token].lastClaimEpoch = currentEpoch;
userCheckpoints[tokenId][token].unclaimed = amountClaimable - amount;
// If the alchemist is defined we know it has an alchemic-token
if (alchemists[alchemist] != address(0)) {
require(token == IAlchemistV2(alchemist).debtToken(), "Invalid alchemist/alchemic-token pair");
(, address[] memory deposits) = IAlchemistV2(alchemist).accounts(recipient);
- IERC20(token).approve(alchemist, amount);
// Only burn if there are deposits
- amountBurned = deposits.length > 0 ? IAlchemistV2(alchemist).burn(amount, recipient) : 0;
+ if(deposits.length>0){
+ IERC20(token).approve(alchemist, amount);
+ amountBurned = IAlchemistV2(alchemist).burn(amount, recipient);
+ } else {
+ amountBurned = 0;
+ }
}
/*
burn() will only burn up to total cdp debt
send the leftover directly to the user
*/
if (amountBurned < amount) {
IERC20(token).safeTransfer(recipient, amount - amountBurned);
}
emit ClaimRevenue(tokenId, token, amount, recipient);
}