From c1f2548ded56fd138c4afa858c31658ac64df465 Mon Sep 17 00:00:00 2001 From: Kropiunig <48442031+Kropiunig@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:45:36 +0100 Subject: [PATCH] fix: correct fee calculation for exactOut swaps in afterSwap example The afterSwap hook fee example incorrectly calculated the fee amount using outputAmount for both exactIn and exactOut swaps. However, as noted in the documentation tip, the returned int128 is for the unspecified currency: - exactIn: unspecified = output token - exactOut: unspecified = input token The previous code always used outputAmount to compute feeAmount, which is incorrect for exactOut swaps where the fee should be calculated from the input amount. This fix: - Determines the unspecified token based on swap type - Uses the correct amount (output for exactIn, input for exactOut) - Simplifies feeCurrency assignment since it always matches unspecifiedIsToken0 Fixes #1101 --- .../contracts/v4/guides/custom-accounting.mdx | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/docs/contracts/v4/guides/custom-accounting.mdx b/docs/contracts/v4/guides/custom-accounting.mdx index d1ee15a70..d77cfb52c 100644 --- a/docs/contracts/v4/guides/custom-accounting.mdx +++ b/docs/contracts/v4/guides/custom-accounting.mdx @@ -314,26 +314,31 @@ function _afterSwap( BalanceDelta delta, bytes calldata ) internal override returns (bytes4, int128) { - bool outputIsToken0 = params.zeroForOne ? false : true; + bool isExactIn = (params.amountSpecified < 0); + + // Determine the unspecified currency and amount + // exactIn: unspecified is output; exactOut: unspecified is input + bool unspecifiedIsToken0; + if (isExactIn) { + unspecifiedIsToken0 = params.zeroForOne ? false : true; // output token + } else { + unspecifiedIsToken0 = params.zeroForOne ? true : false; // input token + } + + int256 unspecifiedAmount = unspecifiedIsToken0 ? delta.amount0() : delta.amount1(); - int256 outputAmount = outputIsToken0 ? delta.amount0() : delta.amount1(); - if (outputAmount <= 0) { + // For exactIn, unspecifiedAmount (output) is positive + // For exactOut, unspecifiedAmount (input) is negative, so we negate it + int256 amountForFee = isExactIn ? unspecifiedAmount : -unspecifiedAmount; + if (amountForFee <= 0) { return (BaseHook.afterSwap.selector, 0); } - uint256 feeAmount = (uint256(outputAmount) * HOOK_FEE_PERCENTAGE) / FEE_DENOMINATOR; + uint256 feeAmount = (uint256(amountForFee) * HOOK_FEE_PERCENTAGE) / FEE_DENOMINATOR; require(feeAmount <= ((uint256(1) << 127) - 1), "fee too large"); - bool isExactIn = (params.amountSpecified < 0); - - Currency feeCurrency; - if (isExactIn) { - feeCurrency = outputIsToken0 ? key.currency0 : key.currency1; - } else { - bool inputIsToken0 = params.zeroForOne ? true : false; - feeCurrency = inputIsToken0 ? key.currency0 : key.currency1; - } + Currency feeCurrency = unspecifiedIsToken0 ? key.currency0 : key.currency1; poolManager.take(feeCurrency, address(this), feeAmount); @@ -380,32 +385,37 @@ contract HookFeeAfterSwap is BaseHook { } function _afterSwap( - address , + address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata ) internal override returns (bytes4, int128) { - bool outputIsToken0 = params.zeroForOne ? false : true; + bool isExactIn = (params.amountSpecified < 0); + + // Determine the unspecified currency and amount + // exactIn: unspecified is output; exactOut: unspecified is input + bool unspecifiedIsToken0; + if (isExactIn) { + unspecifiedIsToken0 = params.zeroForOne ? false : true; // output token + } else { + unspecifiedIsToken0 = params.zeroForOne ? true : false; // input token + } + + int256 unspecifiedAmount = unspecifiedIsToken0 ? delta.amount0() : delta.amount1(); - int256 outputAmount = outputIsToken0 ? delta.amount0() : delta.amount1(); - if (outputAmount <= 0) { + // For exactIn, unspecifiedAmount (output) is positive + // For exactOut, unspecifiedAmount (input) is negative, so we negate it + int256 amountForFee = isExactIn ? unspecifiedAmount : -unspecifiedAmount; + if (amountForFee <= 0) { return (BaseHook.afterSwap.selector, 0); } - uint256 feeAmount = (uint256(outputAmount) * HOOK_FEE_PERCENTAGE) / FEE_DENOMINATOR; + uint256 feeAmount = (uint256(amountForFee) * HOOK_FEE_PERCENTAGE) / FEE_DENOMINATOR; require(feeAmount <= ((uint256(1) << 127) - 1), "fee too large"); - bool isExactIn = (params.amountSpecified < 0); - - Currency feeCurrency; - if (isExactIn) { - feeCurrency = outputIsToken0 ? key.currency0 : key.currency1; - } else { - bool inputIsToken0 = params.zeroForOne ? true : false; - feeCurrency = inputIsToken0 ? key.currency0 : key.currency1; - } + Currency feeCurrency = unspecifiedIsToken0 ? key.currency0 : key.currency1; poolManager.take(feeCurrency, address(this), feeAmount);