DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Lack Slippage & deadline checks inside UnripeFacet#chop

Summary

The Chop method of UnripeFacet.sol has the purpose of Chops an unripe asset into its ripe counterpart according to the recapitalization %
This operation depends on the calculations involving several variables that can be changed over time and transaction by transaction .

Due to which , if the user's transaction gets delayed or its executed in some scenario when the output tokens amount is less than desired ,
user will suffer a loss due to lack of ability for user to specify minAmount like parameters in function call of chop.

Vulnerability Details

The Chop method is defined as

function chop(
address unripeToken,
uint256 amount,
LibTransfer.From fromMode,
LibTransfer.To toMode
) external payable fundsSafu noSupplyChange nonReentrant returns (uint256) {
// burn the token from the user address
uint256 supply = IBean(unripeToken).totalSupply();
amount = LibTransfer.burnToken(IBean(unripeToken), amount, LibTractor._user(), fromMode);
// get ripe address and ripe amount
(address underlyingToken, uint256 underlyingAmount) = LibChop.chop(
unripeToken,
amount,
supply
);
// send the corresponding amount of ripe token to the user address
require(underlyingAmount > 0, "Chop: no underlying");
IERC20(underlyingToken).sendToken(underlyingAmount, LibTractor._user(), toMode);
// emit the event
emit Chop(LibTractor._user(), unripeToken, amount, underlyingAmount);
return underlyingAmount;
}

where LibChop.chop is the key function

function chop(
address unripeToken,
uint256 amount,
uint256 supply
) internal returns (address underlyingToken, uint256 underlyingAmount) {
AppStorage storage s = LibAppStorage.diamondStorage();
underlyingAmount = LibUnripe._getPenalizedUnderlying(unripeToken, amount, supply);
LibUnripe.decrementUnderlying(unripeToken, underlyingAmount);
underlyingToken = s.sys.silo.unripeSettings[unripeToken].underlyingToken;
}

the output amount is calculated mainly with

underlyingAmount = LibUnripe._getPenalizedUnderlying(unripeToken, amount, supply);

which is defined as

function getRecapPaidPercentAmount(
uint256 amount
) internal view returns (uint256 penalizedAmount) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.sys.fert.fertilizedIndex.mul(amount).div(s.sys.fert.unfertilizedIndex);
}

the calculation is heavily dependent on s.sys.fert.fertilizedIndex

which is changed in real-time in

function barnReceive(uint256 shipmentAmount, bytes memory) private {
//snip
else {
s.sys.fert.bpf = uint128(firstBpf); // SafeCast unnecessary here.
s.sys.fert.fertilizedIndex += deltaFertilized;
//snip
}
}
//snip
s.sys.fert.fertilizedIndex += deltaFertilized;

Suppose Alice intiates their chop transaction at timestamp = 100 ,
her transaction gets delayed by 3 blocks . and now the values involved in calculation of Ripe Token amount are such that the amount of ripe tokens
being returned to user is less than what they would have desired according to the on-chain state at the time of transaction initiation .

Impact

Loss of tokens for user due to lack of facility to provide minAmount during chop function call

Tools Used

Manual review

Recommendations

Add minAmountOut parameter in function definition of chop and add following line

require(underlyingAmount > 0, "Chop: no underlying");
+ require(underlyingAmount >=minAmountOut, "Chop: not enough underlying ");
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Lack Slippage & deadline checks inside UnripeFacet#chop

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.