Skip to content
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

Merged
merged 15 commits into from
Jan 27, 2023

Conversation

p0mvn
Copy link
Member

@p0mvn p0mvn commented Jan 25, 2023

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

  • implement swap fee for amount out given in
  • implement tests with swap fee for all existing edge case tests
    • implement Python scripts to estimate the right values for them
  • refactor with better abstractions

Testing and Verifying

This change added tests

Documentation and Release Note

  • Does this pull request introduce a new feature or user-facing behavior changes? no
  • Is a relevant changelog entry added to the Unreleased section in CHANGELOG.md? no
  • How is the feature or change documented? not applicable

@p0mvn p0mvn added the V:state/compatible/no_backport State machine compatible PR, depends on prior breaks label Jan 25, 2023

validate_confirmed_results(token_out_total, fee_growth_per_share_total, expected_token_out_total, expected_fee_growth_per_share_total)

def main():
Copy link
Member Author

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

@p0mvn p0mvn marked this pull request as ready for review January 25, 2023 05:56
Copy link
Member

@mattverse mattverse left a 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)

Comment on lines 34 to 40
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)
}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

@p0mvn p0mvn Jan 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 321 to 323
// Charge fee
feeOnAmountRemainingIn := swapState.amountSpecifiedRemaining.Mul(swapFee)

Copy link
Member

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)

Copy link
Member Author

@p0mvn p0mvn Jan 26, 2023

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:

  1. we consumed all amount remaining
  • this is what we charge here:
    // 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
  1. we did not consume all amount remaining
  2. Price impact protection makes us exit before consuming all amount remaining
  • then we charge this (similar to option 2):
    feeChargeTotal = amountIn.Mul(swapFee)
  • the swap fee on the amount in actually consumed before price impact protection got trigerred

Copy link
Member

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?

Copy link
Member

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

Copy link
Member Author

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]>
@p0mvn p0mvn requested review from czarcas7ic and mattverse January 26, 2023 06:57
Copy link
Contributor

@AlpinYukseloglu AlpinYukseloglu left a 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

@@ -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)
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Member Author

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 {
Copy link
Contributor

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

Copy link
Member

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

Copy link
Member Author

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:

// 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()
Copy link
Contributor

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?

Copy link
Member Author

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

@p0mvn p0mvn requested a review from AlpinYukseloglu January 26, 2023 08:32
Copy link
Member

@mattverse mattverse left a 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 🌮

Copy link
Member

@czarcas7ic czarcas7ic left a 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!

@github-actions github-actions bot added the C:docs Improvements or additions to documentation label Jan 27, 2023
@p0mvn p0mvn merged commit 75da7ff into main Jan 27, 2023
@p0mvn p0mvn deleted the roman/fees-swapout-givenin branch January 27, 2023 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C:docs Improvements or additions to documentation C:x/concentrated-liquidity V:state/compatible/no_backport State machine compatible PR, depends on prior breaks
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants