-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: refactor the fee mechanism for both pools #36
Conversation
@@ -24,11 +27,22 @@ abstract contract Fees is IFees, PausableRole { | |||
controllerGasLimit = _controllerGasLimit; | |||
} | |||
|
|||
function _setProtocolFee(PoolId id, uint24 newProtocolFee) internal virtual; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_getPool
does not make sense because we may have different pool types
if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.TEN_PERCENT_FEE)) revert FeeTooLarge(); | ||
/// @notice init value for dynamic lp fee is 0, but hook can still set it in afterInitialize | ||
uint24 lpFee = key.fee.getInitialLPFee(); | ||
lpFee.validate(LPFeeLibrary.TEN_PERCENT_FEE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirm: are we still keeping 10% for bin pool lp fee ?
It's in fact at max (1 - 0.1%) * 10% = 9.99%
charged on input token compare to previous
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aren't we setting max fee to 100% just like uni?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"aren't we setting max fee to 100% just like uni?"
Before We were just following the original traderjoe behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirm: are we still keeping 10% for bin pool lp fee ?
let's create an issue to track this -- so we can answer there and pass the issue link to future devs when they ask about why joe is 10% limit. On a side note, so long this PR does not change the current max 10% fee behavior, then its alright.
It's in fact at max (1 - 0.1%) * 10% = 9.99% charged on input token compare to previous
can you explain a bit more? you mean before this PR the fee is 10% and somehow now is 9.99%? or previously and now is 9.99% ?
Also won't this question be applicable for CLPool as well? in this PR, the max lp fee for CL pool is 99.9%?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"can you explain a bit more? you mean before this PR the fee is 10% and somehow now is 9.99%? or previously and now is 9.99% ?"
sorry not 9.99%, it should be 10.09%
I mean before the PR the fee is 10% because what we set was actually swap fee which includes protocol fee already. But after this PR we are setting lp fee here which does not include protocol fee, although it can still be 10%, but since it's charged after protocol fee on the rest part. The swap fee a bit larger. It's at max (saying 0.1% protocol fee ) 0.1% + (1 - 0.1%) * 10% = 10.09%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, i think thats fine 💪 - we should create an issue to ensure our developer docs state this information down
uint24 swapFee; | ||
(delta, feeForProtocol, activeId, swapFee) = | ||
BinPool.SwapState memory state; | ||
(delta, state) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stack too deep
emit Swap(id, msg.sender, delta.amount0(), delta.amount1(), activeId, swapFee, feeForProtocol); | ||
emit Swap( | ||
id, msg.sender, delta.amount0(), delta.amount1(), state.activeId, state.swapFee, state.protocolFee | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last parameter has been updated from "accumulated protocol fee value" to fee percentage (align with clpool)
|
||
(amountIn, amountOutLeft, fee) = pools[id].getSwapIn( | ||
BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), fee: totalSwapFee}), | ||
amountOut | ||
BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), lpFee: lpFee}), amountOut |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For bin-pool, the fee change involves 4 functions:
- getSwapIn
- getSwapOut
- Swap
- Mint
|
||
(amountInLeft, amountOut, fee) = pools[id].getSwapOut( | ||
BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), fee: totalSwapFee}), | ||
amountIn | ||
BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), lpFee: lpFee}), amountIn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we pass in lp fee here and calculate swap fee based on lp fee and protocol fee
src/pool-bin/libraries/BinHelper.sol
Outdated
fees = feeY.encodeSecond(); | ||
feeForProtocol = (amountY - receivedAmountY).getCompositionFee(protocolFee).encodeSecond(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @param amountsIn The amounts of tokens to add | ||
/// @param totalSupply The total supply of the liquidity book | ||
/// @param shares The share of the liquidity book that the user will receive | ||
/// @return fees The encoded fees that will be charged | ||
function getCompositionFees( | ||
bytes32 binReserves, | ||
uint24 fee, // fee: 100 = 0.01% | ||
uint24 protocolFee, // fee: 100 = 0.01% | ||
uint24 lpFee, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reason why i pass-in both fee and calculate swap fee inside:
Because protocolFee includes two direction i.e. 0to1 and 1to0. we'll need more gas to figure out which way it's going if we do it outside
src/pool-bin/libraries/BinPool.sol
Outdated
|
||
if (amountsInWithFees > 0) { | ||
amountsLeft = amountsLeft.sub(amountsInWithFees); | ||
amountsOut = amountsOut.add(amountsOutOfBin); | ||
|
||
bytes32 pFee = totalFees.getExternalFeeAmt(self.slot0.protocolFee); | ||
/// @dev calc protocol fee for current bin, charged on amoutIn and charged before lpFee | ||
bytes32 pFee = amountsInWithFees.getExternalFeeAmt(slot0Cache.protocolFee); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of
totalFees * protocolFeePercent
: this implies protocol fee is part of swap fee
now we have
amountsInWithFees * protocolFeePercent
: this implies protocol fee is charged directly on input amount
uint128 feeForY = fee1 == 0 ? 0 : amountY / fee1; | ||
uint128 feeForX; | ||
uint128 feeForY; | ||
unchecked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double check:
is it safe to use unchecked
here ? I didn't see any overflow / underflow case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to me too.
maybe can add a comment here for us to double check later since we added this unchecked optimization here eg.
// todo: double check on this unchecked condition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM as well
|
||
if (amountsInWithFees > 0) { | ||
amountsLeft = amountsLeft.sub(amountsInWithFees); | ||
amountsOut = amountsOut.add(amountsOutOfBin); | ||
|
||
bytes32 pFee = totalFees.getExternalFeeAmt(self.slot0.protocolFee); | ||
/// @dev calc protocol fee for current bin, totalFee * protocolFee / (protocolFee + lpFee) | ||
bytes32 pFee = totalFee.getExternalFeeAmt(slot0Cache.protocolFee, swapState.swapFee); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formula here: totalFee * protocolFee / (protocolFee + lpFee)
|
||
/// @dev If amountSpecified is the output, also given amountSpecified cant be 0, | ||
/// then the tx will always revert if the swap fee is 100% | ||
if (!exactInput && (state.swapFee == LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't happen to bin pool because max lp fee for bin-pool is 10%
protocolFee: 0, | ||
liquidity: cache.liquidityStart | ||
}); | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For cl-pool, the fee change only involves function swap()
why 3.? I mean taking swap fee first and then taking a part of it as protocol fee makes more sense to me |
src/pool-bin/libraries/BinHelper.sol
Outdated
@@ -148,31 +150,41 @@ library BinHelper { | |||
/// @dev Returns the composition fees when adding liquidity to the active bin with a different | |||
/// composition factor than the bin's one, as it does an implicit swap | |||
/// @param binReserves The reserves of the bin | |||
/// @param fee 100 = 0.01%, 1000 = 0.1% | |||
/// @param protocolFee 100 = 0.01%, 1000 = 0.1% | |||
/// @param lpFee 100 = 0.01%, 1000 = 0.1% | |||
/// @param amountsIn The amounts of tokens to add | |||
/// @param totalSupply The total supply of the liquidity book | |||
/// @param shares The share of the liquidity book that the user will receive | |||
/// @return fees The encoded fees that will be charged |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can add doc on
//// @return feeForProtocol ...
src/pool-bin/libraries/BinHelper.sol
Outdated
if (receivedAmountX > amountX) { | ||
uint128 feeY = (amountY - receivedAmountY).getCompositionFee(fee); | ||
protocolFee = protocolFee.getOneForZeroFee(); | ||
|
||
uint24 swapFee = uint24(protocolFee).calculateSwapFee(lpFee); | ||
uint128 feeY = (amountY - receivedAmountY).getCompositionFee(swapFee); | ||
fees = feeY.encodeSecond(); | ||
feeForProtocol = (amountY - receivedAmountY).getCompositionFee(protocolFee).encodeSecond(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional) not sure if this save gas.. though this might be slightly more readable
// if received more X than given X, then swap some Y for X
if (receivedAmountX > amountX) {
protocolFee = protocolFee.getOneForZeroFee();
uint24 swapFee = uint24(protocolFee).calculateSwapFee(lpFee);
uint128 amtSwapped = amountY - receivedAmountY;
fees = amtSwapped.getCompositionFee(swapFee).encodeSecond();
feeForProtocol = amtSwapped.getCompositionFee(protocolFee).encodeSecond();
} else if (receivedAmountY > amountY) {
protocolFee = protocolFee.getZeroForOneFee();
uint24 swapFee = uint24(protocolFee).calculateSwapFee(lpFee);
uint128 amtSwapped = amountX - receivedAmountX;
fees = amtSwapped.getCompositionFee(swapFee).encodeFirst();
feeForProtocol = amtSwapped.getCompositionFee(protocolFee).encodeFirst();
}
{ | ||
if (amountIn == 0) revert BinPool__InsufficientAmountIn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can consider moving this check up if (amountIn == 0) revert BinPool__InsufficientAmountIn();
so in revert case it will consume less gas
bool swapForY = params.swapForY; | ||
swapState.protocolFee = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(gas savings)
instead of storing protocolFee in swapState, simply
uint24 protocolFee =
swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee();
``
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/pool-cl/libraries/CLPool.sol
Outdated
liquidity: cache.liquidityStart | ||
}); | ||
{ | ||
uint16 protocolFee = params.zeroForOne |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional --- maybe can create as issue tracker if keen)
been seeing this if
condition quite a bit. thinking if we might want to make our code cleaner.
eg.
uint16 protocolFee = slot0Start.protocolFee.getProtocolFee(param.zeroForOne);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 prefer if we get either Snoopy or Omelette approval as well before merge
/// @param fee protocolFee | ||
function getExternalFeeAmt(bytes32 amount, uint16 fee) internal pure returns (bytes32 z) { | ||
if (fee == 0) return 0; | ||
/// @param protocolFee protocolFee |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing /// @param swapFee
maybe can just copy other places on what protocolFee / swapFee is over here
hhhh, agree.. The change is bit risky @ChefSnoopy @chef-omelette |
uint24 public constant ONE_HUNDRED_PERCENT_FEE = 1_000_000; | ||
uint24 public constant TEN_PERCENT_FEE = 100_000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional)
I think it's better to rename these to MAX_LP_FEE_CL
and MAX_LP_FEE_BIN
it's more self-explanatory
function validate(uint24 self) internal pure returns (bool) { | ||
if (self != 0) { | ||
uint16 fee0 = getZeroForOneFee(self); | ||
uint16 fee1 = getOneForZeroFee(self); | ||
// The fee is represented in pips and it cannot be greater than the MAX_PROTOCOL_FEE. | ||
if ((fee0 > MAX_PROTOCOL_FEE) || (fee1 > MAX_PROTOCOL_FEE)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional)
just revert instead of returning boolean for simplicity and also to align with lp fee validate
function calculateSwapFee(uint24 self, uint24 lpFee) internal pure returns (uint24) { | ||
unchecked { | ||
uint256 numerator = uint256(self) * uint256(lpFee); | ||
return uint24(uint256(self) + lpFee - UnsafeMath.divRoundingUp(numerator, PIPS_DENOMINATOR)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional)
can be simplified by using mulDiv(self, lpFee, PIPS_DENOMINATOR)
@@ -83,7 +83,7 @@ library SwapMath { | |||
// we didn't reach the target, so take the remainder of the maximum input as fee | |||
feeAmount = uint256(amountRemaining) - amountIn; | |||
} else { | |||
feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); | |||
feeAmount = feePips == 1e6 ? amountIn : FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to confirm: can feePips be 1_000_000? seems meaningless to allow it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, possible
say protocolFee = whatever, and lpFee = 100%
swapFee = protocolFee + (1 - protocolFee) * 100% = 1
uint128 feeForY = fee1 == 0 ? 0 : amountY / fee1; | ||
uint128 feeForX; | ||
uint128 feeForY; | ||
unchecked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM as well
merge for now to unblock follow-up PRs, feel free to add more comments @chef-omelette |
This PR tries to update the underlying fee mechanism including lp and protocol fee.
Major changes: