DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Valid

Lack of slippage on Pipeline Facet can lead to loss of funds

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
);
// Store the capped overall deltaB, this limits the overall convert power for the block
pipeData.overallConvertCapacity = LibConvert.abs(LibDeltaB.overallCappedDeltaB());
IERC20(inputToken).transfer(C.PIPELINE, fromAmount);
executeAdvancedFarmCalls(advancedFarmCalls);
// user MUST leave final assets in pipeline, allowing us to verify that the farm has been called successfully.
// this also let's us know how many assets to attempt to pull out of the final type
@> toAmount = transferTokensFromPipeline(outputToken); // @audit no slippage control
// Calculate stalk penalty using start/finish deltaB of pools, and the capped deltaB is
// passed in to setup max convert power.
@> pipeData.stalkPenaltyBdv = prepareStalkPenaltyCalculation( // @audit lack of slippage
inputToken,
outputToken,
pipeData.deltaB,
pipeData.overallConvertCapacity,
fromBdv,
pipeData.initialLpSupply
);
// Update grownStalk amount with penalty applied
newGrownStalk = (initialGrownStalk * (fromBdv - pipeData.stalkPenaltyBdv)) / fromBdv;
newBdv = LibTokenSilo.beanDenominatedValue(outputToken, toAmount);
}

Impact

  • User can receive less tokens than expected.

  • User can pay an unexpected penalty fee when he shouldn't.

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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of slippage on Pipeline Facet

Appeal created

holydevoti0n Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of slippage on Pipeline Facet

Support

FAQs

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