diff --git a/x/concentrated-liquidity/bench_test.go b/x/concentrated-liquidity/bench_test.go new file mode 100644 index 00000000000..3720e78c7b7 --- /dev/null +++ b/x/concentrated-liquidity/bench_test.go @@ -0,0 +1,240 @@ +package concentrated_liquidity_test + +import ( + "fmt" + "math" + "math/rand" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/osmosis-labs/osmosis/v15/app/apptesting" + clmodel "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/model" + "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types" +) + +type BenchTestSuite struct { + apptesting.KeeperTestHelper +} + +func (s BenchTestSuite) createPosition(accountIndex int, poolId uint64, coin0, coin1 sdk.Coin, lowerTick, upperTick int64) { + tokensDesired := sdk.NewCoins(coin0, coin1) + + _, _, _, _, _, _, _, err := s.App.ConcentratedLiquidityKeeper.CreatePosition(s.Ctx, poolId, s.TestAccs[accountIndex], tokensDesired, sdk.ZeroInt(), sdk.ZeroInt(), lowerTick, upperTick) + if err != nil { + // This can happen for ticks that map to the very small prices + // e.g 2 * 10^(-18) ends up mapping to the same sqrt price + fmt.Println("error creating position", err) + } +} + +func BenchmarkSwapExactAmountIn(b *testing.B) { + // Notice we stop the timer to skip setup code. + b.StopTimer() + + // We cannot use s.Require().NoError() becuase the suite context + // is defined on the testing.T and not testing.B + noError := func(err error) { + require.NoError(b, err) + } + + const ( + numberOfPositions = 10000 + + // max amount of each token deposited per position. + maxAmountDeposited = int64(1_000_000_000_000) + + // amount swapped in. + amountIn = "9999999999999999999" + + // flag controlling whether to create additional numberOfPositions full + // range positions for deeper liquidity. + shouldCreateFullRangePositions = true + + // flag controlling whether to create positions concentrated around current tick to mimic + // realistic scenario and deeper liquidity. + // if true, + // creates numberOfPositions positions within 10 ticks of the current tick. + // creates numberOfPositions positions within 100 ticks of the current tick. + shouldConcentrate = true + + // tickSpacing is the spacing between ticks. + tickSpacing = 1 + ) + + var ( + // denoms of the pool. + denom0 = DefaultCoin0.Denom + denom1 = DefaultCoin1.Denom + + // denom of the token to swap in. + denomIn = denom0 + + numberOfPositionsInt = sdk.NewInt(numberOfPositions) + maxAmountOfEachToken = sdk.NewInt(maxAmountDeposited).Mul(numberOfPositionsInt) + + // Seed controlling determinism of the randomized positions. + seed = int64(1) + ) + rand.Seed(seed) + + for i := 0; i < b.N; i++ { + s := BenchTestSuite{} + s.Setup() + + // Fund all accounts with max amounts they would need to consume. + for _, acc := range s.TestAccs { + simapp.FundAccount(s.App.BankKeeper, s.Ctx, acc, sdk.NewCoins(sdk.NewCoin(denom0, maxAmountOfEachToken), sdk.NewCoin(denom1, maxAmountOfEachToken), sdk.NewCoin("uosmo", maxAmountOfEachToken))) + } + + // Create a pool + poolId, err := s.App.PoolManagerKeeper.CreatePool(s.Ctx, clmodel.NewMsgCreateConcentratedPool(s.TestAccs[0], denom0, denom1, tickSpacing, sdk.MustNewDecFromStr("0.001"))) + noError(err) + + clKeeper := s.App.ConcentratedLiquidityKeeper + + // Create first position to set a price of 1 and tick of zero. + tokenDesired0 := sdk.NewCoin(denom0, sdk.NewInt(100)) + tokenDesired1 := sdk.NewCoin(denom1, sdk.NewInt(100)) + tokensDesired := sdk.NewCoins(tokenDesired0, tokenDesired1) + _, _, _, _, _, _, _, err = clKeeper.CreatePosition(s.Ctx, poolId, s.TestAccs[0], tokensDesired, sdk.ZeroInt(), sdk.ZeroInt(), types.MinTick, types.MaxTick) + + pool, err := clKeeper.GetPoolById(s.Ctx, poolId) + noError(err) + + // Zero by default, can configure by setting a specific position. + currentTick := pool.GetCurrentTick() + + // Setup numberOfPositions positions at random ranges + for i := 0; i < numberOfPositions; i++ { + + var ( + lowerTick int64 + upperTick int64 + ) + + if denomIn == denom0 { + // Decreasing price so want to be below current tick + + // minTick <= lowerTick <= currentTick + lowerTick = rand.Int63n(currentTick.Int64()-types.MinTick+1) + types.MinTick + // lowerTick <= upperTick <= currentTick + upperTick = currentTick.Int64() - rand.Int63n(int64(math.Abs(float64(currentTick.Int64()-lowerTick)))) + } else { + // Increasing price so want to be above current tick + + // currentTick <= lowerTick <= maxTick + lowerTick := rand.Int63n(types.MaxTick-currentTick.Int64()+1) + currentTick.Int64() + // lowerTick <= upperTick <= maxTick + upperTick = types.MaxTick - rand.Int63n(int64(math.Abs(float64(types.MaxTick-lowerTick)))) + } + // Normalize lowerTick to be a multiple of tickSpacing + lowerTick = lowerTick + (tickSpacing - lowerTick%tickSpacing) + // Normalize upperTick to be a multiple of tickSpacing + upperTick = upperTick - upperTick%tickSpacing + + tokenDesired0 := sdk.NewCoin(denom0, sdk.NewInt(rand.Int63n(maxAmountDeposited))) + tokenDesired1 := sdk.NewCoin(denom1, sdk.NewInt(rand.Int63n(maxAmountDeposited))) + + accountIndex := rand.Intn(len(s.TestAccs)) + + s.createPosition(accountIndex, poolId, tokenDesired0, tokenDesired1, lowerTick, upperTick) + } + + // Setup numberOfPositions full range positions for deeper liquidity. + if shouldCreateFullRangePositions { + for i := 0; i < numberOfPositions; i++ { + lowerTick := types.MinTick + upperTick := types.MaxTick + + maxAmountDepositedFullRange := sdk.NewInt(maxAmountDeposited).MulRaw(5) + tokenDesired0 := sdk.NewCoin(denom0, maxAmountDepositedFullRange) + tokenDesired1 := sdk.NewCoin(denom1, maxAmountDepositedFullRange) + tokensDesired := sdk.NewCoins(tokenDesired0, tokenDesired1) + + accountIndex := rand.Intn(len(s.TestAccs)) + + account := s.TestAccs[accountIndex] + + simapp.FundAccount(s.App.BankKeeper, s.Ctx, account, tokensDesired) + + s.createPosition(accountIndex, poolId, tokenDesired0, tokenDesired1, lowerTick, upperTick) + } + } + + // Setup numberOfPositions * 2 positions at random ranges around the current tick for deeper + // liquidity. + if shouldConcentrate { + // Within 10 ticks of the current + if tickSpacing <= 10 { + for i := 0; i < numberOfPositions; i++ { + lowerTick := currentTick.Int64() - 10 + upperTick := currentTick.Int64() + 10 + + tokenDesired0 := sdk.NewCoin(denom0, sdk.NewInt(maxAmountDeposited).MulRaw(5)) + tokenDesired1 := sdk.NewCoin(denom1, sdk.NewInt(maxAmountDeposited).MulRaw(5)) + tokensDesired := sdk.NewCoins(tokenDesired0, tokenDesired1) + + accountIndex := rand.Intn(len(s.TestAccs)) + + account := s.TestAccs[accountIndex] + + simapp.FundAccount(s.App.BankKeeper, s.Ctx, account, tokensDesired) + + s.createPosition(accountIndex, poolId, tokenDesired0, tokenDesired1, lowerTick, upperTick) + } + } + + // Within 100 ticks of the current + for i := 0; i < numberOfPositions; i++ { + lowerTick := currentTick.Int64() - 100 + upperTick := currentTick.Int64() + 100 + // Normalize lowerTick to be a multiple of tickSpacing + lowerTick = lowerTick + (tickSpacing - lowerTick%tickSpacing) + // Normalize upperTick to be a multiple of tickSpacing + upperTick = upperTick - upperTick%tickSpacing + + tokenDesired0 := sdk.NewCoin(denom0, sdk.NewInt(maxAmountDeposited).MulRaw(5)) + tokenDesired1 := sdk.NewCoin(denom1, sdk.NewInt(maxAmountDeposited).MulRaw(5)) + tokensDesired := sdk.NewCoins(tokenDesired0, tokenDesired1) + + accountIndex := rand.Intn(len(s.TestAccs)) + + account := s.TestAccs[accountIndex] + + simapp.FundAccount(s.App.BankKeeper, s.Ctx, account, tokensDesired) + + s.createPosition(accountIndex, poolId, tokenDesired0, tokenDesired1, lowerTick, upperTick) + } + } + + swapAmountIn := sdk.MustNewDecFromStr(amountIn).TruncateInt() + largeSwapInCoin := sdk.NewCoin(denomIn, swapAmountIn) + + liquidityNet, err := clKeeper.GetTickLiquidityNetInDirection(s.Ctx, pool.GetId(), largeSwapInCoin.Denom, currentTick, sdk.Int{}) + noError(err) + + fmt.Println("num_ticks_traversed", len(liquidityNet)) + fmt.Println("current_tick", currentTick) + + // Increase block time so that some incentives uptime accumulator update logic + // isn't a no-op. + s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Second)) + + // Fund swap amount. + simapp.FundAccount(s.App.BankKeeper, s.Ctx, s.TestAccs[0], sdk.NewCoins(largeSwapInCoin)) + + // Notice that we start the timer as this is the system under test + b.StartTimer() + + // System under test + _, err = clKeeper.SwapExactAmountIn(s.Ctx, s.TestAccs[0], pool, largeSwapInCoin, denom1, sdk.NewInt(1), pool.GetSwapFee(s.Ctx)) + noError(err) + + // Notice that we stop the timer again in case there are multiple iterations. + b.StopTimer() + } +} diff --git a/x/concentrated-liquidity/math/math.go b/x/concentrated-liquidity/math/math.go index 17d85eee922..bb6fdcd808b 100644 --- a/x/concentrated-liquidity/math/math.go +++ b/x/concentrated-liquidity/math/math.go @@ -1,6 +1,8 @@ package math import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/osmomath" @@ -44,6 +46,10 @@ func Liquidity1(amount sdk.Int, sqrtPriceA, sqrtPriceB sdk.Dec) sdk.Dec { sqrtPriceBBigDec := osmomath.BigDecFromSDKDec(sqrtPriceB) diff := sqrtPriceBBigDec.Sub(sqrtPriceABigDec) + if diff.Equal(osmomath.ZeroDec()) { + panic(fmt.Sprintf("diff is zero: sqrtPriceA %s sqrtPriceB %s", sqrtPriceA, sqrtPriceB)) + } + return amountBigDec.Quo(diff).SDKDec() }