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

fix(ProtoRev): Updating Binary Search Range with CL Pools #5930

Merged
merged 18 commits into from
Aug 4, 2023

Conversation

davidterpay
Copy link
Contributor

@davidterpay davidterpay commented Jul 31, 2023

What is the purpose of the change

This PR introduces a fix to protorev to update the binary search range in the case where it is too gas intensive to search over the hard-coded range. This is particularly useful for Concentrated Liquidity pools where moving across several ticks is very expensive. By determining if a route has an CL pools in it, we can bound the upper input amount by figuring out the maximum input amount respecting a maximum number of ticks. We do so across all CL pools and use this as our maximum input amount on the entire route.

Note, MaxTicksMoved is currently hard coded, but we plan to update that in a follow up PR. This will be configurable by the DeveloperAccount.

Testing and Verifying

This change added tests and can be verified as follows:

  • Added unit tests for TestFindMaxProfitRoute that test liquid and illiquid CL pools
    • One of the test cases reduces the search range respecting the number of ticks that can be moved.

Documentation and Release Note

This PR is related to these open issues (#5858) and (#5884)

@davidterpay davidterpay requested a review from p0mvn as a code owner July 31, 2023 18:20
@github-actions github-actions bot added the C:app-wiring Changes to the app folder label Jul 31, 2023
@davidterpay davidterpay added the protorev All things related to x/protorev label Jul 31, 2023
@davidterpay davidterpay added the V:state/breaking State machine breaking PR label Jul 31, 2023
CHANGELOG.md Outdated
@@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#5831](https://github.com/osmosis-labs/osmosis/pull/5831) Fix superfluid_delegations query
* [#5835](https://github.com/osmosis-labs/osmosis/pull/5835) Fix println's for "amountZeroInRemainingBigDec before fee" making it into production
* [#5841] (https://github.com/osmosis-labs/osmosis/pull/5841) Fix protorev's out of gas erroring of the user's transcation.
* [#5930] (https://github.com/osmosis-labs/osmosis/pull/5930) Updating Binary Search Range with CL Pools
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* [#5930] (https://github.com/osmosis-labs/osmosis/pull/5930) Updating Binary Search Range with CL Pools
* [#5930] (https://github.com/osmosis-labs/osmosis/pull/5930) Updating Protorev Binary Search Range Logic with CL Pools

continue
}
profit = uosmoProfit
profit, err := k.ConvertProfits(ctx, inputCoin, profit)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may also need to add bounding in ConvertProfits. If a CL pool is the highest liquidity pool then we will swap the entire amount without any tick bounds.

If a CL pool is the highest liquidity than I imagine this won't run into any issues, but maybe good to note in as a comment if we don't want to add the tick checking here that we are not doing so.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

adding a comment

Copy link
Member

Choose a reason for hiding this comment

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

Am I correct in saying we went for the former and didn't just add a comment right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wait lol adding comment rn, misread the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per convo offline, we decided to not make the check. More likely than not we will not run into the issue of over-stepping MaxTicksCrossed in ConvertProfits. The upper bound check should be sufficient enough to cover most cases.

profit = uosmoProfit
profit, err := k.ConvertProfits(ctx, inputCoin, profit)
if err != nil {
k.Logger(ctx).Error("Error converting profits: ", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

This error won't propagate properly because it accepts kv pairs, let's follow this format:

ctx.Logger().Error("ProtoRevTrade failed with error: " + err.Error())

Realized I missed all places that use the format k.Logger(ctx).Error, I replaced all the ctx.Logger().Error instances, can we add this fix in this PR too for all k.Logger(ctx).Error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gotchu fam

}

// If the profit for the maximum amount in is still increasing, then we can increase the range of the binary search
if maxInProfit.GTE(sdk.ZeroInt()) {
maxInPlusOne := curRight.Add(sdk.OneInt()).Mul(route.StepSize)
if updatedMax.LT(maxInPlusOne) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think updatedMax is not multiplied by stepsize since the check previously in UpdateSearchRangeIfNeeded is

	if updatedMax.LT(curRight) {
		return curLeft, updatedMax, nil
	}

so this check smells wrong to me


// MaxInputAmount returns the max input amount that can be used in any route. We use uosmo as the base
// unit of account for the max input amount.
func (k Keeper) MaxInputAmount(ctx sdk.Context) (sdk.Int, sdk.Int, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Re: our in-person sync, if we go the reverse iteration method I don't think we need to choose any base unit of account for this so should be able to remove

Copy link
Contributor

@NotJeremyLiu NotJeremyLiu 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 comments and synced in person about using a reverse iteration approach to reduce the bounds

@davidterpay davidterpay marked this pull request as draft July 31, 2023 21:08
@davidterpay davidterpay marked this pull request as ready for review August 1, 2023 18:39
s.CreateFullRangePosition(clPool, fundCoins)

// Create a concentrated liquidity pool for range testing
// Pool 52
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Pool 52
// Pool 53

@davidterpay
Copy link
Contributor Author

Will update the doomsday gas test case in a follow up PR (where you can configure max ticks moved).

@@ -340,12 +340,12 @@ func (s *KeeperTestSuite) TestAnteHandle() {
params: param{
trades: []types.Trade{
{
Pool: 51,
Pool: 54,
Copy link
Contributor

Choose a reason for hiding this comment

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

Synced in person: So after the tick change, this test no longer tests what we want it to since this does not run out of gas. So in the follow up PR that allows maxTicks to be configurable, we'll make the maxTicks really large for this test so that the rebalance logic runs out of gas again

@@ -27,6 +27,10 @@ const MaxPoolPointsPerTx uint64 = 50
// to the maximum execution time (in ms) of protorev per block
const MaxPoolPointsPerBlock uint64 = 200

// Max number of ticks we can move in a concentrated pool swap. This will be parameterized in a
// follow up PR.
const MaxTicksMoved uint64 = 5
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's call this MaxTicksCrossed to stay consistent with the CL method naming of ComputeMaxInAmtGivenMaxTicksCrossed

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.

Nice work on this, was super clear/easy to review despite lacking context.

continue
}
profit = uosmoProfit
profit, err := k.ConvertProfits(ctx, inputCoin, profit)
Copy link
Member

Choose a reason for hiding this comment

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

Am I correct in saying we went for the former and didn't just add a comment right?

}

// Ensure that the normalized upper bound is not greater than the extended max input amount
upperBound := intermidiateCoin.Amount.Quo(route.StepSize)
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible for a route's step size to be zero?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tldr no, we manually set these and enforce that the step size cannot be zero (when we run validate basic on a msg).

Comment on lines 290 to 291
// At most we can swap half of the liquidity in the pool
liquidTokenAmt := liquidity.AmountOf(outputCoin.Denom).Quo(sdk.NewInt(2))
Copy link
Member

Choose a reason for hiding this comment

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

Is this just a self imposed limit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, this would allow us to still capture some arb on illiquid pools while ensuring we never error out when we make the calculation on upper bound. Additionally, on larger/more liquid pools, the trade size will never be larger than ExtendedMaxInputAmount which most certainly will be less than half the liquidity in a liquid pool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will admit that this is a bit of a hacky work around atm.

@stackman27 stackman27 self-requested a review August 2, 2023 08:26
Copy link
Contributor

@NotJeremyLiu NotJeremyLiu left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Contributor

@stackman27 stackman27 left a comment

Choose a reason for hiding this comment

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

this lgtm, can we please update the README.md with this changes

return sdk.NewCoin(tokenInDenom, sdk.ZeroInt()), err
}

// At most we can swap half of the liquidity in the pool
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not merge-blocking, but due to the way we handle rounding (always in the pool's favor and can be meaningfully high in some scenarios), this assumption does not necessarily true.

Rounding error can be nontrivial here and accumulates over time, so while this is likely the simplest way to handle this specific case, it should be kept in mind for future logic leaning on assumptions related to absolute assets in the pool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, totally agree. We've been going back and forth on how to best estimate maximal swap in when CL/stable/balancer pools are involved and we more or less ran into the same issue with all of our approaches. I'll decrease this to 1/4 for now and will revisit this once we have a better idea of how frequently this is an actual issue.

Also related, we plan on doing some structural refactoring - decoupling the various components of protorev - sometime soon which will let us navigate this with more granularity.

Copy link
Contributor

Choose a reason for hiding this comment

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

@AlpinYukseloglu Based on your understanding, does the reduction to 1/4 improve the situation or are you thinking of a different approach here?

@davidterpay
Copy link
Contributor Author

@stackman27 anything specific you want updated in the README.md?

@stackman27
Copy link
Contributor

@stackman27 anything specific you want updated in the README.md?

maybe something around how "UpdateSearchRangeIfNeeded" updates the search range for BS. And what we do when BS gets gas intensive with CL pools

@davidterpay
Copy link
Contributor Author

Updated the readme. @stackman27

@github-actions github-actions bot added the C:docs Improvements or additions to documentation label Aug 4, 2023
@czarcas7ic czarcas7ic merged commit edd1822 into main Aug 4, 2023
@czarcas7ic czarcas7ic deleted the terpay/search-range-update branch August 4, 2023 23:11
@github-actions github-actions bot mentioned this pull request Apr 15, 2024
@github-actions github-actions bot mentioned this pull request May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C:app-wiring Changes to the app folder C:docs Improvements or additions to documentation protorev All things related to x/protorev V:state/breaking State machine breaking PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants