Orbiting Sangria Porpoise
Medium
NumaLeverageVaultSwap::swap() calculates buy/sell fees based on the estimated input amount rather than the actually used amount during token swaps. This leads to users paying higher fees than necessary when the actual amount needed for the swap is less than the initial estimate.
When a user initiates a leverage strategy through CNumaToken::leverageStrategy(), the protocol first estimates the amount of tokens needed for the swap (borrowAmount
) to ensure getting the desired output amount (_borrowAmount
) after accounting for slippage. This estimate calculated by NumaLeverageVaultSwap::getAmountIn()
is intentionally higher than likely needed as a safety margin. This and other factors give rise to the following situation:
- The protocol estimates required input (e.g., 31 rETH) to get desired output (e.g., 30 NUMA worth):
uint borrowAmount = strat.getAmountIn(_borrowAmount, false);
- This full estimated amount is temporarily borrowed to repay the vault:
borrowInternalNoTransfer(borrowAmount, msg.sender);
- The swap is executed:
(uint collateralReceived, uint unUsedInput) = strat.swap(
borrowAmount,
_borrowAmount,
false
);
-
strat.swap
internally calledvault.buy()
which calledbuyNoMax()
. Fee was calculated based on the outputnumaAmount
. This valuenumaAmount
after deduction of fees was returned by the function, which got stored in the variablecollateralReceived
above. -
CNumaToken::leverageStrategy()
then continues and checks ifcollateralReceived > _borrowAmount
and returns excess amount to the borrower if true:
//refund if more collateral is received than needed
if (collateralReceived > _borrowAmount) {
// send back the surplus
SafeERC20.safeTransfer(
IERC20(underlyingCollateral),
msg.sender,
collateralReceived - _borrowAmount
);
}
As can be seen, while the excess collateralReceived
itself is properly returned, the excess fees charged is not. User had to pay the fees even on the collateralReceived - _borrowAmount
amount.
Users pay higher fees than expected.
The same issue also crops up when closing a leveraged strategy:
- Leverage Closing:
- closeLeverageStrategy() --> closeLeverageAmount() -->
NumaLeverageVaultSwap::getAmountIn()
thenstrat.swap()
- closeLeverageStrategy() --> closeLeverageAmount() -->
Implement a fee refund mechanism that returns the excess fee after the if (collateralReceived > _borrowAmount)
check.