Summary
The PipelineConvertFacet
allows the user to execute arbitrary calls in order to convert his assets in the silo.
Now take into consideration the following context:
1 - There is a penalty fee applied for the grown stalk whenever the user's conversion leads the price to move away from the peg.
2 - As arbitrary calls are allowed, the user might need to perform different conversions until he reaches the one intended.
This can lead the user to receive an amount of tokens that is much less than he is expecting and also to have his grown stalk cut due to the penalty fee.
With the current implementation, the user is also susceptible to front-running as the user can't set a minimum amount to receive.
Vulnerability Details
LibPipelineConvert:
function executePipelineConvert(
address inputToken,
address outputToken,
uint256 fromAmount,
uint256 fromBdv,
uint256 initialGrownStalk,
AdvancedFarmCall[] calldata advancedFarmCalls
) external returns (uint256 toAmount, uint256 newGrownStalk, uint256 newBdv) {
PipelineConvertData memory pipeData = LibPipelineConvert.populatePipelineConvertData(
inputToken,
outputToken
);
pipeData.overallConvertCapacity = LibConvert.abs(LibDeltaB.overallCappedDeltaB());
IERC20(inputToken).transfer(C.PIPELINE, fromAmount);
executeAdvancedFarmCalls(advancedFarmCalls);
@> toAmount = transferTokensFromPipeline(outputToken);
@> pipeData.stalkPenaltyBdv = prepareStalkPenaltyCalculation(
inputToken,
outputToken,
pipeData.deltaB,
pipeData.overallConvertCapacity,
fromBdv,
pipeData.initialLpSupply
);
newGrownStalk = (initialGrownStalk * (fromBdv - pipeData.stalkPenaltyBdv)) / fromBdv;
newBdv = LibTokenSilo.beanDenominatedValue(outputToken, toAmount);
}
Impact
Tools Used
Manual Review
Recommendations
Implement a slippage control i.e: minToAmount
and minToGrownStalk
on pipeline convert where the transaction reverts if the user receive less than the amount expected.
function pipelineConvert(
address inputToken,
int96[] calldata stems,
uint256[] calldata amounts,
address outputToken,
AdvancedFarmCall[] calldata advancedFarmCalls
+ uint256 minToAmount,
+ uint256 minGrownStalk
)
external
payable
fundsSafu
nonReentrant
returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv)
{
// require that input and output tokens be wells (Unripe not supported)
require(
LibWell.isWell(inputToken) || inputToken == C.BEAN,
"Convert: Input token must be Bean or a well"
);
require(
LibWell.isWell(outputToken) || outputToken == C.BEAN,
"Convert: Output token must be Bean or a well"
);
// mow input and output tokens:
LibSilo._mow(LibTractor._user(), inputToken);
LibSilo._mow(LibTractor._user(), outputToken);
// Calculate the maximum amount of tokens to withdraw
for (uint256 i = 0; i < stems.length; i++) {
fromAmount = fromAmount.add(amounts[i]);
}
// withdraw tokens from deposits and calculate the total grown stalk and bdv.
uint256 grownStalk;
(grownStalk, fromBdv) = LibConvert._withdrawTokens(inputToken, stems, amounts, fromAmount);
(toAmount, grownStalk, toBdv) = LibPipelineConvert.executePipelineConvert(
inputToken,
outputToken,
fromAmount,
fromBdv,
grownStalk,
advancedFarmCalls
);
+ require(minToAmount >= toAmount && minGrownStalk >= grownStalk, "Not enough amount");
toStem = LibConvert._depositTokensForConvert(outputToken, toAmount, toBdv, grownStalk);
emit Convert(LibTractor._user(), inputToken, outputToken, fromAmount, toAmount);
}