-
Notifications
You must be signed in to change notification settings - Fork 624
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/test(CL Swaps): Swap fees for amount out given in #4097
Conversation
|
||
validate_confirmed_results(token_out_total, fee_growth_per_share_total, expected_token_out_total, expected_fee_growth_per_share_total) | ||
|
||
def main(): |
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.
Note: For scripts, I suggest reviewing this file in-detail and comparing it against Go test cases. However, I don't think there is value in reviewing other internal script calculations so feel free to skip
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.
Left some minor comments, please take a look. Upon resolving / answered this LGTM.
(Note that I did not review the python code in detail as they were used for mathematic calculations)
x/concentrated-liquidity/swaps.go
Outdated
func (ss *SwapState) updateFeeGrowthGlobal(feeChargeTotal sdk.Dec) { | ||
if !ss.liquidity.IsZero() { | ||
feeChargePerUnitOfLiquidity := feeChargeTotal.Quo(ss.liquidity) | ||
// TODO: remove print. | ||
fmt.Println("feeChargePerUnitOfLiquidity", feeChargePerUnitOfLiquidity) | ||
ss.feeGrowthGlobal = ss.feeGrowthGlobal.Add(feeChargePerUnitOfLiquidity) | ||
} |
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.
wdyt of including tests if the method is small enough in this PR?
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 might change as we implement the swap in the other direction. I don't think it makes sense to test these right now given the high likelihood of these methods being changed / removed in the next PR.
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.
Also, these are tracked here:
Co-authored-by: Matt, Park <[email protected]>
x/concentrated-liquidity/swaps.go
Outdated
// Charge fee | ||
feeOnAmountRemainingIn := swapState.amountSpecifiedRemaining.Mul(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.
This is probably right, but im not quite understanding the flow on first pass.
We charge a fee, then subtract that fee from the amount remaining on the swap step (this makes sense to me)
What I dont understand is why we run a seperate function computeFeeChargePerSwapStep
to calculate the feeChargeTotal
for the global accumulator. Why would it not just be taken from feeOnAmountRemainingIn
?
(Once again, I am sure this is right, but on first pass its not clear why)
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.
Let's define what swapState.amountSpecifiedRemaining
is.
It is the amount of token in remaining over all swap steps.
After performing the current swap step, the following cases are possible:
- we consumed all amount remaining
- this is what we charge here:
osmosis/x/concentrated-liquidity/fees.go
Lines 292 to 295 in 3df2018
// Otherwise, the current tick had enough liquidity to fulfill the swap // In that case, the fee is the difference between // the amount needed to fulfill and the actual amount we ended up charging. feeChargeTotal = amountSpecifiedRemaining.Sub(amountIn) - this is equivalent to the difference between the original amount remaining and the one actually consumed. The difference between them is the fee
- we did not consume all amount remaining
- then we charge this:
osmosis/x/concentrated-liquidity/fees.go
Line 290 in 3df2018
feeChargeTotal = amountIn.Mul(swapFee) - this is the fee charged on the amount actually consumed during a swap step
- then we charge this:
- Price impact protection makes us exit before consuming all amount remaining
- then we charge this (similar to option 2):
osmosis/x/concentrated-liquidity/fees.go
Line 290 in 3df2018
feeChargeTotal = amountIn.Mul(swapFee) - the swap fee on the amount in actually consumed before price impact protection got trigerred
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.
Awesome! This is something I did not previously know clearly before! Would we be able to document this context somewhere?
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 make sense to me, thanks! If this exact writeup could get documented somewhere that would be awesome
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.
Docs added to architecture.md
. The fee docs need to be refactored - quite outdated. I will come back to it after the swap in the other direction is done
Co-authored-by: Adam Tucker <[email protected]>
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.
Overall LGTM except one comment about chargeFee
making the calc function mutative – happy to approve after that is addressed. Also left a few other minor comments
x/concentrated-liquidity/swaps.go
Outdated
@@ -301,19 +318,25 @@ func (k Keeper) calcOutAmtGivenIn(ctx sdk.Context, | |||
return writeCtx, sdk.Coin{}, sdk.Coin{}, sdk.Int{}, sdk.Dec{}, sdk.Dec{}, fmt.Errorf("could not convert next tick (%v) to nextSqrtPrice", nextTick) | |||
} | |||
|
|||
// Charge fee | |||
feeOnAmountRemainingIn := swapState.amountSpecifiedRemaining.Mul(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.
We should specify in the comment that this is just a preliminary calculation for ComputeSwapStep
and that the actual fee charging is done later by computeFeeChargePerSwapStep
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.
Also nit: we should be rounding this up to ensure swapfee is never undercharged since Mul
does bankers rounding
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.
Done.
Rounding up at 10^-18 is done by doing MulTruncate
and adding SmallestDec
@@ -342,9 +365,13 @@ func (k Keeper) calcOutAmtGivenIn(ctx sdk.Context, | |||
} | |||
} | |||
|
|||
if err := k.chargeFee(ctx, poolId, sdk.NewDecCoinFromDec(tokenInMin.Denom, swapState.feeGrowthGlobal)); err != nil { |
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.
Doesn't this mutate state? I thought this calc method was supposed to be non-mutative
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 doesn't since we are using cache ctx
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.
That's correct, more context on how we actually make this be written to state:
osmosis/x/concentrated-liquidity/swaps.go
Lines 146 to 149 in 4d9b212
// N.B. making the call below ensures that any mutations done inside calcOutAmtGivenIn | |
// are written to store. If this call were skipped, calcOutAmtGivenIn would be non-mutative. | |
// An example of a store write done in calcOutAmtGivenIn is updating ticks as we cross them. | |
writeCtx() |
// coin amounts require int values | ||
// round amountIn up to avoid under charging | ||
amt0 := tokenAmountInAfterFee.Sub(swapState.amountSpecifiedRemaining).RoundInt() | ||
amt0 := tokenAmountInSpecified.Sub(swapState.amountSpecifiedRemaining).RoundInt() |
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 clarify, these two are equivalent since we charge fees stepwise in amountSpecifiedRemaining
instead of upfront in tokenAmountInAfterFee
right?
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, this should be just a name change
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.
Thanks for replying to all the comments! LGTM 🌮
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.
Approving since my only blocking question was answered, nicely done!
Closes: #XXX
What is the purpose of the change
This PR introduces the implementation for swap fees when swapping for amount out given token in.
The test vectors are estimated using the Python
sympy
scripts. The documentation for running the scripts is added.There are some internal methods that are untested:
It is best to test them after swap fees in the other direction are complete (ref: #4088). The reason is that we might refactor and remove these methods so it doesn't make sense to spend time adding detailed tests right now. The test coverage via script estimates is sufficient at this stage.
Lastly, there are some prints left in the codebase for development convenience. Removing them is tracked here: #4096
For context, the original implementation was prototyped here: #3893
Brief Changelog
Testing and Verifying
This change added tests
Documentation and Release Note
Unreleased
section inCHANGELOG.md
? no