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) {
uint256 supply = IBean(unripeToken).totalSupply();
amount = LibTransfer.burnToken(IBean(unripeToken), amount, LibTractor._user(), fromMode);
(address underlyingToken, uint256 underlyingAmount) = LibChop.chop(
unripeToken,
amount,
supply
);
require(underlyingAmount > 0, "Chop: no underlying");
IERC20(underlyingToken).sendToken(underlyingAmount, LibTractor._user(), toMode);
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 {
else {
s.sys.fert.bpf = uint128(firstBpf);
s.sys.fert.fertilizedIndex += deltaFertilized;
}
}
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 ");