From 636b017ca549b5a0dd489e1d498763e8ce233885 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Wed, 7 Jun 2023 19:48:53 +0000 Subject: [PATCH 01/50] feat/docs: CL external incentives tool --- Makefile | 12 ++ tests/cl-go-client/README.md | 19 +++ tests/cl-go-client/main.go | 172 ++++++++++++++++++++++++---- tests/localosmosis/scripts/setup.sh | 4 +- 4 files changed, 184 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 384a0eaeabf..6658df1ab11 100644 --- a/Makefile +++ b/Makefile @@ -459,6 +459,18 @@ localnet-cl-small-swap: localnet-cl-large-swap: go run tests/cl-go-client/main.go --operation 2 +# creates a gauge and waits for one epoch so that the gauge +# is converted into an incentive record for pool id 1. +localnet-cl-external-incentive: + go run tests/cl-go-client/main.go --operation 3 + +# attempts to create a CL pool at id 1. +# if pool already exists, this is a no-op. +# if pool with different id is desired, tweak expectedPoolId +# in the script. +localnet-cl-create-pool: + go run tests/cl-go-client/main.go --operation 4 + # does both of localnet-cl-create-positions and localnet-cl-small-swap localnet-cl-positions-small-swaps: localnet-cl-create-positions localnet-cl-small-swap diff --git a/tests/cl-go-client/README.md b/tests/cl-go-client/README.md index ddcd6f3522a..b4964c22c0b 100644 --- a/tests/cl-go-client/README.md +++ b/tests/cl-go-client/README.md @@ -111,3 +111,22 @@ make localnet-cl-positions-large-swaps ``` This script runs "Create Positions" and "Make Large Invertible Swaps" scripts in sequence. + +### Create Incentive + +Creates a new gauge and waits for one epoch so that the gauge +is converted into an incentive record for pool id 1. + +```bash +make localnet-cl-external-incentive +``` + +### Create Pool + +Attempts to create a CL pool at id 1. If pool at id 1 already exists, this is a no-op. +If pool with different id is desired, tweak expectedPoolId in the script. + +```bash +make localnet-cl-create-pool +``` + diff --git a/tests/cl-go-client/main.go b/tests/cl-go-client/main.go index 16364af7aca..dafae78d26b 100644 --- a/tests/cl-go-client/main.go +++ b/tests/cl-go-client/main.go @@ -17,10 +17,14 @@ import ( "github.com/ignite/cli/ignite/pkg/cosmosaccount" "github.com/ignite/cli/ignite/pkg/cosmosclient" + clqueryproto "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/client/queryproto" "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/model" cltypes "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types" + incentivestypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" + lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" poolmanagerqueryproto "github.com/osmosis-labs/osmosis/v16/x/poolmanager/client/queryproto" poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" + epochstypes "github.com/osmosis-labs/osmosis/x/epochs/types" ) // operation defines the desired operation to be run by this script. @@ -38,6 +42,12 @@ const ( // and swaps it back while accounting for the spread factor. This is done to // ensure that we cross ticks while minimizing the chance of running out of funds or liquidity. makeManyInvertibleLargeSwaps + + // createExternalCLIncentives creates external CL incentives. + createExternalCLIncentives + + // createPoolOperation creates a pool with expectedPoolId. + createPoolOperation ) const ( @@ -49,19 +59,22 @@ const ( denom1 = "uusdc" tickSpacing int64 = 100 accountNamePrefix = "lo-test" - numPositions = 100 - numSwaps = 100 - minAmountDeposited = int64(1_000_000) - randSeed = 1 - maxAmountDeposited = 1_00_000_000 - maxAmountSingleSwap = 1_000_000 - largeSwapAmount = 90_000_000_000 + // Note, this is localosmosis-specific. + expectedEpochIdentifier = "hour" + numPositions = 100 + numSwaps = 100 + minAmountDeposited = int64(1_000_000) + randSeed = 1 + maxAmountDeposited = 1_00_000_000 + maxAmountSingleSwap = 1_000_000 + largeSwapAmount = 90_000_000_000 ) var ( defaultAccountName = fmt.Sprintf("%s%d", accountNamePrefix, 1) defaultMinAmount = sdk.ZeroInt() defaultSpreadFactor = sdk.MustNewDecFromStr("0.001") + externalGaugeCoins = sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(1000_000_000))) accountMutex sync.Mutex ) @@ -97,9 +110,6 @@ func main() { log.Println("connected to: ", "chain-id", statusResp.NodeInfo.Network, "height", statusResp.SyncInfo.LatestBlockHeight) - // Instantiate a query client - clQueryClient := poolmanagerqueryproto.NewQueryClient(igniteClient.Context()) - // Print warnings with common problems log.Printf("\n\n\nWARNING 1: your localosmosis and client home are assummed to be %s. Run 'osmosisd get-env' and confirm it matches the path you see printed here\n\n\n", clientHome) @@ -107,16 +117,9 @@ func main() { log.Println("\n\n\nWARNING 3: sometimes the script hangs when just started. In that case, kill it and restart") - // Query pool with id 1 and create new if does not exist. - _, err = clQueryClient.Pool(ctx, &poolmanagerqueryproto.PoolRequest{PoolId: expectedPoolId}) - if err != nil { - if !strings.Contains(err.Error(), poolmanagertypes.FailedToFindRouteError{PoolId: expectedPoolId}.Error()) { - log.Fatal(err) - } - createdPoolId := createPool(igniteClient, defaultAccountName) - if createdPoolId != expectedPoolId { - log.Fatalf("created pool id (%d), expected pool id (%d)", createdPoolId, expectedPoolId) - } + // Check if need to create pool before every opperation. + if operation(desiredOperation) != createPoolOperation { + createPoolOp(igniteClient) } rand.Seed(randSeed) @@ -130,6 +133,10 @@ func main() { return case makeManyInvertibleLargeSwaps: swapGivenLargeAmountsBothDirections(igniteClient, expectedPoolId, numSwaps, largeSwapAmount) + case createExternalCLIncentives: + createExternalCLIncentive(igniteClient, expectedPoolId, externalGaugeCoins, expectedEpochIdentifier) + case createPoolOperation: + createPoolOp(igniteClient) default: log.Fatalf("invalid operation: %d", desiredOperation) } @@ -231,6 +238,78 @@ func swapGivenLargeAmountsBothDirections(igniteClient cosmosclient.Client, poolI log.Println("finished swapping, num swaps done", numSwaps) } +func createExternalCLIncentive(igniteClient cosmosclient.Client, poolId uint64, gaugeCoins sdk.Coins, expectedEpochIdentifier string) { + var ( + randAccountNum = rand.Intn(8) + 1 + accountName = fmt.Sprintf("%s%d", accountNamePrefix, randAccountNum) + ) + + epochsQueryClient := epochstypes.NewQueryClient(igniteClient.Context()) + currentEpochResponse, err := epochsQueryClient.CurrentEpoch(context.Background(), &epochstypes.QueryCurrentEpochRequest{ + expectedEpochIdentifier, + }) + if err != nil { + log.Fatal(err) + } + + log.Println("current epoch", currentEpochResponse.CurrentEpoch, "epoch identifier", expectedEpochIdentifier) + + log.Println("querying epoch info. Note that incentives are distributed at the end of epoch. That's why it matters.") + epochInfosRespose, err := epochsQueryClient.EpochInfos(context.Background(), &epochstypes.QueryEpochsInfoRequest{}) + if err != nil { + log.Fatal(err) + } + + if len(epochInfosRespose.Epochs) > 0 { + lastEpochInfo := epochInfosRespose.Epochs[len(epochInfosRespose.Epochs)-1] + log.Println("epoch duration", lastEpochInfo, "next epoch start time", lastEpochInfo.StartTime.Add(lastEpochInfo.Duration)) + } else { + log.Println("could not find information about previous epoch. If duration is too long, this test might be infeasible") + } + + //.Create gauge + runMessageWithRetries(func() error { + return createGauge(igniteClient, expectedPoolId, accountName, gaugeCoins) + }) + + epochAfterGaugeCreation := int64(-1) + for { + // Wait for 1 epoch to pass + currentEpochResponse, err = epochsQueryClient.CurrentEpoch(context.Background(), &epochstypes.QueryCurrentEpochRequest{ + expectedEpochIdentifier, + }) + if err != nil { + log.Fatal(err) + } + if epochAfterGaugeCreation == -1 { + log.Println("current epoch after gauge creation", currentEpochResponse.CurrentEpoch) + log.Println("waiting for next epoch...") + epochAfterGaugeCreation = currentEpochResponse.CurrentEpoch + continue + } + + // One epoch after gauge creation has passed + if epochAfterGaugeCreation+1 == currentEpochResponse.CurrentEpoch { + log.Println("next epoch reached, checking incentive records...") + break + } + + log.Println("current epoch", currentEpochResponse.CurrentEpoch) + time.Sleep(5 * time.Second) + } + + clQueryClient := clqueryproto.NewQueryClient(igniteClient.Context()) + + incentiveRecordsResponse, err := clQueryClient.IncentiveRecords(context.Background(), &clqueryproto.IncentiveRecordsRequest{ + PoolId: expectedPoolId, + }) + if err != nil { + log.Fatal(err) + } + + log.Println("incentive records. If empty, something probably went wrong", incentiveRecordsResponse.IncentiveRecords) +} + func createPool(igniteClient cosmosclient.Client, accountName string) uint64 { msg := &model.MsgCreateConcentratedPool{ Sender: getAccountAddressFromKeyring(igniteClient, accountName), @@ -309,6 +388,57 @@ func makeSwap(client cosmosclient.Client, poolId uint64, senderKeyringAccountNam return resp.TokenOutAmount, nil } +func createGauge(client cosmosclient.Client, poolId uint64, senderKeyringAccountName string, gaugeCoins sdk.Coins) error { + accountMutex.Lock() // Lock access to getAccountAddressFromKeyring + senderAddress := getAccountAddressFromKeyring(client, senderKeyringAccountName) + accountMutex.Unlock() // Unlock access to getAccountAddressFromKeyring + + log.Println("creating CL gauge for pool id", expectedPoolId, "gaugeCoins", gaugeCoins) + + msg := &incentivestypes.MsgCreateGauge{ + IsPerpetual: true, + Owner: senderAddress, + DistributeTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.ByDuration, + Denom: "uosmo", + Duration: time.Second * 120, + }, + StartTime: time.Now(), + Coins: gaugeCoins, + NumEpochsPaidOver: 1, + } + txResp, err := client.BroadcastTx(senderKeyringAccountName, msg) + if err != nil { + return err + } + resp := &incentivestypes.MsgCreateGaugeResponse{} + if err := txResp.Decode(resp); err != nil { + return err + } + + log.Println("gauge created") + return nil +} + +func createPoolOp(igniteClient cosmosclient.Client) { + // Instantiate a query client + poolManagerClient := poolmanagerqueryproto.NewQueryClient(igniteClient.Context()) + + // Query pool with id 1 and create new if does not exist. + _, err := poolManagerClient.Pool(context.Background(), &poolmanagerqueryproto.PoolRequest{PoolId: expectedPoolId}) + if err != nil { + if !strings.Contains(err.Error(), poolmanagertypes.FailedToFindRouteError{PoolId: expectedPoolId}.Error()) { + log.Fatal(err) + } + createdPoolId := createPool(igniteClient, defaultAccountName) + if createdPoolId != expectedPoolId { + log.Fatalf("created pool id (%d), expected pool id (%d)", createdPoolId, expectedPoolId) + } + } else { + log.Println("pool already exists. Tweak expectedPoolId variable if you want another pool, current expectedPoolId", expectedPoolId) + } +} + func getAccountAddressFromKeyring(igniteClient cosmosclient.Client, accountName string) string { account, err := igniteClient.Account(accountName) if err != nil { @@ -338,7 +468,7 @@ func runMessageWithRetries(runMsg func() error) { for j := 0; j < maxRetries; j++ { err := runMsg() if err != nil { - log.Println("retrying, error occurred while creating position: ", err) + log.Println("retrying, error occurred while running message: ", err) time.Sleep(8 * time.Second) } else { time.Sleep(200 * time.Millisecond) diff --git a/tests/localosmosis/scripts/setup.sh b/tests/localosmosis/scripts/setup.sh index 1191bbeef5a..4b1330882b1 100755 --- a/tests/localosmosis/scripts/setup.sh +++ b/tests/localosmosis/scripts/setup.sh @@ -66,11 +66,11 @@ edit_genesis () { dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[1]' "120s" dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[2]' "180s" dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[3]' "240s" - dasel put string -f $GENESIS '.app_state.incentives.params.distr_epoch_identifier' "day" + dasel put string -f $GENESIS '.app_state.incentives.params.distr_epoch_identifier' "hour" # Update mint module dasel put string -f $GENESIS '.app_state.mint.params.mint_denom' "uosmo" - dasel put string -f $GENESIS '.app_state.mint.params.epoch_identifier' "day" + dasel put string -f $GENESIS '.app_state.mint.params.epoch_identifier' "hour" # Update poolmanager module dasel put string -f $GENESIS '.app_state.poolmanager.params.pool_creation_fee.[0].denom' "uosmo" From 3bde12840dcf4be4e26a2d514724651f5ee36869 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:14:14 +0000 Subject: [PATCH 02/50] updates --- .vscode/launch.json | 4 +- Makefile | 2 +- proto/osmosis/incentives/tx.proto | 6 + proto/osmosis/lockup/lock.proto | 1 + tests/cl-go-client/go.mod | 2 +- tests/cl-go-client/go.sum | 2 + tests/cl-go-client/main.go | 9 +- .../msg/transmuter/transmuter_test.go | 2 +- x/incentives/keeper/bench_test.go | 2 +- x/incentives/keeper/distribute.go | 189 ++++---- x/incentives/keeper/distribute_test.go | 422 +++++++++--------- x/incentives/keeper/export_test.go | 6 - x/incentives/keeper/gauge.go | 42 +- x/incentives/keeper/gauge_test.go | 114 ++++- x/incentives/keeper/genesis_test.go | 2 +- x/incentives/keeper/msg_server.go | 2 +- x/incentives/keeper/suite_test.go | 3 +- x/incentives/types/keys.go | 6 + x/incentives/types/msgs.go | 17 +- x/incentives/types/msgs_test.go | 39 +- x/incentives/types/tx.pb.go | 121 +++-- x/lockup/types/lock.pb.go | 86 ++-- x/pool-incentives/keeper/grpc_query_test.go | 6 +- x/pool-incentives/keeper/keeper.go | 16 +- x/pool-incentives/types/expected_keepers.go | 2 +- x/superfluid/keeper/intermediary_account.go | 2 +- x/superfluid/types/expected_keepers.go | 2 +- 27 files changed, 666 insertions(+), 441 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 66fabebba47..3d4bafe7501 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -129,12 +129,12 @@ "type": "go", "request": "launch", "mode": "test", - "program": "${workspaceFolder}/x/incentives", + "program": "${workspaceFolder}/x/incentives/keeper", "args": [ "-test.timeout", "30m", "-test.run", - "TestKeeperTestSuite/TestYourName", + "TestKeeperTestSuite/TestDistribute_ExternalIncentives_ConcentratedPool", "-test.v" ], }, diff --git a/Makefile b/Makefile index 6658df1ab11..8f3df11efe2 100644 --- a/Makefile +++ b/Makefile @@ -265,7 +265,7 @@ test: test-unit test-build test-all: test-race test-cover test-unit: - @VERSION=$(VERSION) SKIP_WASM_WSL_TESTS=$(SKIP_WASM_WSL_TESTS) go test -mod=readonly -tags='ledger test_ledger_mock norace' $(PACKAGES_UNIT) + @VERSION=$(VERSION) SKIP_WASM_WSL_TESTS=$(SKIP_WASM_WSL_TESTS) go test -mod=readonly -tags='ledger test_ledger_mock norace' $(PACKAGES_UNIT) -count=1 test-race: @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' $(PACKAGES_UNIT) diff --git a/proto/osmosis/incentives/tx.proto b/proto/osmosis/incentives/tx.proto index f4652ac7633..e24f1cea73f 100644 --- a/proto/osmosis/incentives/tx.proto +++ b/proto/osmosis/incentives/tx.proto @@ -45,6 +45,12 @@ message MsgCreateGauge { // num_epochs_paid_over is the number of epochs distribution will be completed // over uint64 num_epochs_paid_over = 6; + + // pool_id is the ID of the pool that the gauge is meant to be associated + // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be + // "NoLock" with all other fields unset. + // The denom query condition is set to "no-lock/{pool_id}" prefix in such a case. + uint64 pool_id = 7; } message MsgCreateGaugeResponse {} diff --git a/proto/osmosis/lockup/lock.proto b/proto/osmosis/lockup/lock.proto index 879dcfdd68d..322428288e0 100644 --- a/proto/osmosis/lockup/lock.proto +++ b/proto/osmosis/lockup/lock.proto @@ -56,6 +56,7 @@ enum LockQueryType { ByDuration = 0; ByTime = 1; + NoLock = 2; } // QueryCondition is a struct used for querying locks upon different conditions. diff --git a/tests/cl-go-client/go.mod b/tests/cl-go-client/go.mod index 1cc2a5f7204..e9f92c05de0 100644 --- a/tests/cl-go-client/go.mod +++ b/tests/cl-go-client/go.mod @@ -88,7 +88,7 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230516205127-c213fddde069 // indirect github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230606194542-47ea1e2c85ca // indirect - github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e // indirect + github.com/osmosis-labs/osmosis/v16 v16.0.0-20230608013130-4c65a87ef8f1 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/tests/cl-go-client/go.sum b/tests/cl-go-client/go.sum index f8fa21d7348..cf3ad9d12f1 100644 --- a/tests/cl-go-client/go.sum +++ b/tests/cl-go-client/go.sum @@ -697,6 +697,8 @@ github.com/osmosis-labs/osmosis/v16 v16.0.0-20230602200356-bdf5b96b3674 h1:sCXD8 github.com/osmosis-labs/osmosis/v16 v16.0.0-20230602200356-bdf5b96b3674/go.mod h1:Vg+05vXFc682OEF52HTqhEKF+deQ0GSt9rkisCFJ8Ug= github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e h1:Rbkpe0cLh67eyWpCMN8u/6xDNHlWimrLceMEhtNJ0TI= github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e/go.mod h1:Vg+05vXFc682OEF52HTqhEKF+deQ0GSt9rkisCFJ8Ug= +github.com/osmosis-labs/osmosis/v16 v16.0.0-20230608013130-4c65a87ef8f1 h1:y3UZi17rDKrI26UHUM789NN0PfDa8sMxQ53d+5/QbfU= +github.com/osmosis-labs/osmosis/v16 v16.0.0-20230608013130-4c65a87ef8f1/go.mod h1:3+K8CJxnPSCzf+eytwgHCRlkWnFFf5lF3wqoxK2f7Dk= github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 h1:RIrWLzIiZN5Xd2JOfSOtGZaf6V3qEQYg6EaDTAkMnCo= github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230331072320-5d6f6cfa2627 h1:A0SwZgp4bmJFbivYJc8mmVhMjrr3EdUZluBYFke11+w= github.com/osmosis-labs/wasmd v0.31.0-osmo-v16 h1:X747cZYdnqc/+RV48iPVeGprpVb/fUWSaKGsZUWrdbg= diff --git a/tests/cl-go-client/main.go b/tests/cl-go-client/main.go index dafae78d26b..01d5b469333 100644 --- a/tests/cl-go-client/main.go +++ b/tests/cl-go-client/main.go @@ -396,16 +396,15 @@ func createGauge(client cosmosclient.Client, poolId uint64, senderKeyringAccount log.Println("creating CL gauge for pool id", expectedPoolId, "gaugeCoins", gaugeCoins) msg := &incentivestypes.MsgCreateGauge{ - IsPerpetual: true, + IsPerpetual: false, Owner: senderAddress, DistributeTo: lockuptypes.QueryCondition{ - LockQueryType: lockuptypes.ByDuration, - Denom: "uosmo", - Duration: time.Second * 120, + LockQueryType: lockuptypes.NoLock, }, StartTime: time.Now(), Coins: gaugeCoins, - NumEpochsPaidOver: 1, + NumEpochsPaidOver: 5, + PoolId: expectedPoolId, } txResp, err := client.BroadcastTx(senderKeyringAccountName, msg) if err != nil { diff --git a/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go b/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go index 6bf7b219a9b..b1f17c47016 100644 --- a/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go +++ b/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go @@ -86,7 +86,7 @@ func (s *TransmuterSuite) TestFunctionalTransmuter() { LockQueryType: lockuptypes.ByDuration, Denom: expectedShareDenom, Duration: lockDuration, - }, s.Ctx.BlockTime(), 1) + }, s.Ctx.BlockTime(), 1, 0) s.Require().NoError(err) gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeId) s.Require().NoError(err) diff --git a/x/incentives/keeper/bench_test.go b/x/incentives/keeper/bench_test.go index c1f059fe25d..a234326f391 100644 --- a/x/incentives/keeper/bench_test.go +++ b/x/incentives/keeper/bench_test.go @@ -114,7 +114,7 @@ func benchmarkDistributionLogic(b *testing.B, numAccts, numDenoms, numGauges, nu numEpochsPaidOver = uint64(r.Int63n(durationMillisecs/millisecsPerEpoch)) + 1 } - gaugeId, err := app.IncentivesKeeper.CreateGauge(ctx, isPerpetual, addr, rewards, distributeTo, startTime, numEpochsPaidOver) + gaugeId, err := app.IncentivesKeeper.CreateGauge(ctx, isPerpetual, addr, rewards, distributeTo, startTime, numEpochsPaidOver, 0) if err != nil { fmt.Printf("Create Gauge, %v\n", err) b.FailNow() diff --git a/x/incentives/keeper/distribute.go b/x/incentives/keeper/distribute.go index 95108fe6a17..67b3a779eb3 100644 --- a/x/incentives/keeper/distribute.go +++ b/x/incentives/keeper/distribute.go @@ -1,7 +1,6 @@ package keeper import ( - "errors" "fmt" "time" @@ -11,7 +10,6 @@ import ( "github.com/osmosis-labs/osmosis/v16/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" - poolincentivestypes "github.com/osmosis-labs/osmosis/v16/x/pool-incentives/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" ) @@ -268,29 +266,6 @@ func (k Keeper) distributeSyntheticInternal( return k.distributeInternal(ctx, gauge, sortedAndTrimmedQualifiedLocks, distrInfo) } -// distributeConcentratedLiquidity runs the distribution logic for CL pools only. It creates new incentive record with osmo incentives -// and distributes all the tokens to the dedicated pool -func (k Keeper) distributeConcentratedLiquidity(ctx sdk.Context, poolId uint64, sender sdk.AccAddress, incentiveCoin sdk.Coin, emissionRate sdk.Dec, startTime time.Time, minUptime time.Duration, gauge types.Gauge) error { - _, err := k.clk.CreateIncentive(ctx, - poolId, - sender, - incentiveCoin, - emissionRate, - startTime, - minUptime, - ) - if err != nil { - return err - } - - // updateGaugePostDistribute adds the coins that were just distributed to the gauge's distributed coins field. - err = k.updateGaugePostDistribute(ctx, gauge, sdk.NewCoins(incentiveCoin)) - if err != nil { - return err - } - return nil -} - // distributeInternal runs the distribution logic for a gauge, and adds the sends to // the distrInfo struct. It also updates the gauge for the distribution. // Locks is expected to be the correct set of lock recipients for this gauge. @@ -298,12 +273,6 @@ func (k Keeper) distributeInternal( ctx sdk.Context, gauge types.Gauge, locks []lockuptypes.PeriodLock, distrInfo *distributionInfo, ) (sdk.Coins, error) { totalDistrCoins := sdk.NewCoins() - denom := lockuptypes.NativeDenom(gauge.DistributeTo.Denom) - lockSum := lockuptypes.SumLocksByDenom(locks, denom) - - if lockSum.IsZero() { - return nil, nil - } remainCoins := gauge.Coins.Sub(gauge.DistributedCoins) // if its a perpetual gauge, we set remaining epochs to 1. @@ -313,34 +282,89 @@ func (k Keeper) distributeInternal( remainEpochs = gauge.NumEpochsPaidOver - gauge.FilledEpochs } - for _, lock := range locks { - distrCoins := sdk.Coins{} - for _, coin := range remainCoins { - // distribution amount = gauge_size * denom_lock_amount / (total_denom_lock_amount * remain_epochs) - denomLockAmt := lock.Coins.AmountOfNoDenomValidation(denom) - amt := coin.Amount.Mul(denomLockAmt).Quo(lockSum.Mul(sdk.NewInt(int64(remainEpochs)))) - if amt.IsPositive() { - newlyDistributedCoin := sdk.Coin{Denom: coin.Denom, Amount: amt} - distrCoins = distrCoins.Add(newlyDistributedCoin) - } + // This is a no lock distribution flow that assumes that we have a pool associated with the gauge. + // Currently, this flow is only used for CL pools. Fails if the pool is not found. + // Fails if the pool found is not a CL pool. + if gauge.DistributeTo.LockQueryType == lockuptypes.NoLock { + pool, err := k.GetPoolFromGaugeId(ctx, gauge.Id, gauge.DistributeTo.Duration) + + if err != nil { + return nil, err } - distrCoins = distrCoins.Sort() - if distrCoins.Empty() { - continue + + poolType := pool.GetType() + if poolType != poolmanagertypes.Concentrated { + return nil, fmt.Errorf("pool type %s is not supported for no lock distribution", poolType) } - // update the amount for that address - rewardReceiver := lock.RewardReceiverAddress - // if the reward receiver stored in state is an empty string, it indicates that the owner is the reward receiver. - if rewardReceiver == "" { - rewardReceiver = lock.Owner + // Get distribution epoch duration. This is used to calculate the emission rate. + currentEpoch := k.GetEpochInfo(ctx) + + // only want to run this logic if the gaugeId is associated with CL PoolId + for _, remainCoin := range remainCoins { + remainAmountPerEpoch := remainCoin.Amount.Quo(sdk.NewIntFromUint64(remainEpochs)) + remainCoinPerEpoch := sdk.NewCoin(remainCoin.Denom, remainAmountPerEpoch) + + // emissionRate calculates amount of tokens to emit per second + // for ex: 10000tokens to be distributed over 1day epoch will be 1000 tokens ÷ 86,400 seconds ≈ 0.01157 tokens per second (truncated) + // Note: reason why we do millisecond conversion is because floats are non-deterministic so if someone refactors this and accidentally + // uses the return of currEpoch.Duration.Seconds() in math operations, this will lead to an app hash. + emissionRate := sdk.NewDecFromInt(remainAmountPerEpoch).QuoTruncate(sdk.NewDec(currentEpoch.Duration.Milliseconds()).QuoInt(sdk.NewInt(1000))) + + _, err := k.clk.CreateIncentive(ctx, + pool.GetId(), + k.ak.GetModuleAddress(types.ModuleName), + remainCoinPerEpoch, + emissionRate, + ctx.BlockTime(), + // Note that the minimum uptime does not affect the distribution of incentives from the gauge and + // thus can be any value authorized by the CL module. + types.DefaultConcentratedUptime, + ) + if err != nil { + return nil, err + } + + totalDistrCoins = totalDistrCoins.Add(remainCoinPerEpoch) } - err := distrInfo.addLockRewards(lock.Owner, rewardReceiver, distrCoins) - if err != nil { - return nil, err + } else { + // This is a standard lock distribution flow that assumes that we have locks associated with the gauge. + denom := lockuptypes.NativeDenom(gauge.DistributeTo.Denom) + lockSum := lockuptypes.SumLocksByDenom(locks, denom) + + if lockSum.IsZero() { + return nil, nil } - totalDistrCoins = totalDistrCoins.Add(distrCoins...) + for _, lock := range locks { + distrCoins := sdk.Coins{} + for _, coin := range remainCoins { + // distribution amount = gauge_size * denom_lock_amount / (total_denom_lock_amount * remain_epochs) + denomLockAmt := lock.Coins.AmountOfNoDenomValidation(denom) + amt := coin.Amount.Mul(denomLockAmt).Quo(lockSum.Mul(sdk.NewInt(int64(remainEpochs)))) + if amt.IsPositive() { + newlyDistributedCoin := sdk.Coin{Denom: coin.Denom, Amount: amt} + distrCoins = distrCoins.Add(newlyDistributedCoin) + } + } + distrCoins = distrCoins.Sort() + if distrCoins.Empty() { + continue + } + // update the amount for that address + rewardReceiver := lock.RewardReceiverAddress + + // if the reward receiver stored in state is an empty string, it indicates that the owner is the reward receiver. + if rewardReceiver == "" { + rewardReceiver = lock.Owner + } + err := distrInfo.addLockRewards(lock.Owner, rewardReceiver, distrCoins) + if err != nil { + return nil, err + } + + totalDistrCoins = totalDistrCoins.Add(distrCoins...) + } } err := k.updateGaugePostDistribute(ctx, gauge, totalDistrCoins) @@ -384,59 +408,20 @@ func (k Keeper) Distribute(ctx sdk.Context, gauges []types.Gauge) (sdk.Coins, er locksByDenomCache := make(map[string][]lockuptypes.PeriodLock) totalDistributedCoins := sdk.NewCoins() - // get pool Id corresponding to the gaugeId - incentiveParams := k.GetParams(ctx).DistrEpochIdentifier - currentEpoch := k.ek.GetEpochInfo(ctx, incentiveParams) - for _, gauge := range gauges { var gaugeDistributedCoins sdk.Coins - pool, err := k.GetPoolFromGaugeId(ctx, gauge.Id, currentEpoch.Duration) - // Note: getting NoPoolAssociatedWithGaugeError implies that there is no pool associated with the gauge but we still want to distribute. - // This happens with superfluid gauges which are not connected to any specific pool directly but, instead, - // via an intermediary account. - if err != nil && !errors.Is(err, poolincentivestypes.NoPoolAssociatedWithGaugeError{GaugeId: gauge.Id, Duration: currentEpoch.Duration}) { - // TODO: add test case to cover this - return nil, err - } else if pool != nil && pool.GetType() == poolmanagertypes.Concentrated { - // only want to run this logic if the gaugeId is associated with CL PoolId - for _, coin := range gauge.Coins { - // emissionRate calculates amount of tokens to emit per second - // for ex: 10000tokens to be distributed over 1day epoch will be 1000 tokens ÷ 86,400 seconds ≈ 0.01157 tokens per second (truncated) - // Note: reason why we do millisecond conversion is because floats are non-deterministic so if someone refactors this and accidentally - // uses the return of currEpoch.Duration.Seconds() in math operations, this will lead to an app hash. - emissionRate := sdk.NewDecFromInt(coin.Amount).QuoTruncate(sdk.NewDec(currentEpoch.Duration.Milliseconds()).QuoInt(sdk.NewInt(1000))) - err := k.distributeConcentratedLiquidity(ctx, - pool.GetId(), - k.ak.GetModuleAddress(types.ModuleName), - coin, - emissionRate, - gauge.GetStartTime(), - // Note that the minimum uptime does not affect the distribution of incentives from the gauge and - // thus can be any value authorized by the CL module. - types.DefaultConcentratedUptime, - gauge, - ) - if err != nil { - return nil, err - } - gaugeDistributedCoins = gaugeDistributedCoins.Add(coin) - } + filteredLocks := k.getDistributeToBaseLocks(ctx, gauge, locksByDenomCache) + // send based on synthetic lockup coins if it's distributing to synthetic lockups + var err error + if lockuptypes.IsSyntheticDenom(gauge.DistributeTo.Denom) { + gaugeDistributedCoins, err = k.distributeSyntheticInternal(ctx, gauge, filteredLocks, &distrInfo) } else { - // Assume that there is no pool associated with the gauge and attempt to distribute to base locks - filteredLocks := k.getDistributeToBaseLocks(ctx, gauge, locksByDenomCache) - // send based on synthetic lockup coins if it's distributing to synthetic lockups - var err error - if lockuptypes.IsSyntheticDenom(gauge.DistributeTo.Denom) { - // TODO: add test case to cover this - gaugeDistributedCoins, err = k.distributeSyntheticInternal(ctx, gauge, filteredLocks, &distrInfo) - } else { - gaugeDistributedCoins, err = k.distributeInternal(ctx, gauge, filteredLocks, &distrInfo) - } - if err != nil { - // TODO: add test case to cover this - return nil, err - } + gaugeDistributedCoins, err = k.distributeInternal(ctx, gauge, filteredLocks, &distrInfo) + } + if err != nil { + return nil, err } + totalDistributedCoins = totalDistributedCoins.Add(gaugeDistributedCoins...) } diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index aaade1b54ef..eb871f34394 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/suite" appParams "github.com/osmosis-labs/osmosis/v16/app/params" - cltypes "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types" "github.com/osmosis-labs/osmosis/v16/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" @@ -166,7 +165,7 @@ func (s *KeeperTestSuite) TestDistribute() { } } -func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { +func (s *KeeperTestSuite) TestDistribute_InternalIncentives_ConcentratedPool() { defaultGauge := perpGaugeDesc{ lockDenom: defaultLPDenom, lockDuration: defaultLockDuration, @@ -189,9 +188,8 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { tokensToAddToGauge sdk.Coins gaugeStartTime time.Time gaugeCoins sdk.Coins - poolType poolmanagertypes.PoolType lockExist bool - authorizedUptimes []time.Duration + isBalancerPool bool // expected expectErr bool @@ -200,74 +198,38 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { "valid case: one poolId and gaugeId": { numPools: 1, gaugeStartTime: defaultGaugeStartTime, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), - authorizedUptimes: cltypes.SupportedUptimes, + gaugeCoins: sdk.NewCoins(fiveKRewardCoins), expectedDistributions: sdk.NewCoins(fiveKRewardCoins), expectErr: false, }, "valid case: gauge with multiple coins": { numPools: 1, gaugeStartTime: defaultGaugeStartTime, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000)), sdk.NewCoin(appParams.BaseCoinUnit, sdk.NewInt(5000))), - authorizedUptimes: cltypes.SupportedUptimes, + gaugeCoins: sdk.NewCoins(fiveKRewardCoins, fiveKRewardCoinsUosmo), expectedDistributions: sdk.NewCoins(fiveKRewardCoins, fiveKRewardCoinsUosmo), expectErr: false, }, "valid case: multiple gaugeId and poolId": { numPools: 3, gaugeStartTime: defaultGaugeStartTime, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), - authorizedUptimes: cltypes.SupportedUptimes, + gaugeCoins: sdk.NewCoins(fiveKRewardCoins), expectedDistributions: sdk.NewCoins(fifteenKRewardCoins), expectErr: false, }, - "valid case: attempt to create balancer pool": { + "valid case: one poolId and gaugeId, five 5000 coins": { numPools: 1, - poolType: poolmanagertypes.Balancer, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), gaugeStartTime: defaultGaugeStartTime, - authorizedUptimes: cltypes.SupportedUptimes, - expectedDistributions: sdk.NewCoins(), - expectErr: false, // still a valid case we just donot update the CL incentive parameters - }, - "valid case: distributing to locks since no pool associated with gauge": { - numPools: 0, - poolType: poolmanagertypes.Balancer, - gaugeCoins: sdk.NewCoins(), - gaugeStartTime: defaultGaugeStartTime, - authorizedUptimes: cltypes.SupportedUptimes, - expectedDistributions: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(3000))), - lockExist: true, - expectErr: false, // we do not expect error because we run the gauge distribution to lock logic - }, - "valid case: one poolId and gaugeId, limited authorized uptimes": { - numPools: 1, - gaugeStartTime: defaultGaugeStartTime, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), - authorizedUptimes: []time.Duration{time.Nanosecond, time.Hour * 24}, + gaugeCoins: sdk.NewCoins(fiveKRewardCoins), expectedDistributions: sdk.NewCoins(fiveKRewardCoins), expectErr: false, }, - "valid case: one poolId and gaugeId, default authorized uptimes (1ns)": { + "valid case: attempt to createIncentiveRecord with start time < currentBlockTime - gets set to block time in incentive record": { numPools: 1, - gaugeStartTime: defaultGaugeStartTime, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), + gaugeStartTime: defaultGaugeStartTime.Add(-5 * time.Hour), + gaugeCoins: sdk.NewCoins(fiveKRewardCoins), expectedDistributions: sdk.NewCoins(fiveKRewardCoins), expectErr: false, }, - "invalid case: attempt to createIncentiveRecord with starttime < currentBlockTime": { - numPools: 1, - poolType: poolmanagertypes.Concentrated, - gaugeCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(5000))), - gaugeStartTime: defaultGaugeStartTime.Add(-5 * time.Hour), - authorizedUptimes: cltypes.SupportedUptimes, - expectErr: true, - }, } for name, tc := range tests { @@ -277,13 +239,6 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { // We fix blocktime to ensure tests are deterministic s.Ctx = s.Ctx.WithBlockTime(defaultGaugeStartTime) - // Set up authorized CL uptimes to robustly test distribution - if tc.authorizedUptimes != nil { - clParams := s.App.ConcentratedLiquidityKeeper.GetParams(s.Ctx) - clParams.AuthorizedUptimes = tc.authorizedUptimes - s.App.ConcentratedLiquidityKeeper.SetParams(s.Ctx, clParams) - } - var gauges []types.Gauge // prepare the minting account @@ -295,24 +250,32 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { err := s.App.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, addr, types.ModuleName, coinsToMint) s.Require().NoError(err) - var poolId uint64 - // prepare a CL Pool that creates gauge at the end of createPool - if tc.poolType == poolmanagertypes.Concentrated { - for i := 0; i < tc.numPools; i++ { + for i := 0; i < tc.numPools; i++ { + var ( + poolId uint64 + duration time.Duration + ) + if tc.isBalancerPool { + poolId = s.PrepareBalancerPool() + + duration = s.App.PoolIncentivesKeeper.GetLockableDurations(s.Ctx)[0] + } else { poolId = s.PrepareConcentratedPool().GetId() - // get the gaugeId corresponding to the CL pool - gaugeId, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, poolId, currentEpoch.Duration) - s.Require().NoError(err) + duration = currentEpoch.Duration + } - // get the gauge from the gaudeId - gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeId) - s.Require().NoError(err) + // get the gaugeId corresponding to the CL pool + gaugeId, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, poolId, duration) + s.Require().NoError(err) - gauge.Coins = tc.gaugeCoins - gauge.StartTime = tc.gaugeStartTime - gauges = append(gauges, *gauge) - } + // get the gauge from the gaudeId + gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeId) + s.Require().NoError(err) + + gauge.Coins = tc.gaugeCoins + gauge.StartTime = tc.gaugeStartTime + gauges = append(gauges, *gauge) } var addrs []sdk.AccAddress @@ -346,7 +309,7 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { s.Require().NoError(err) // this check is specifically for CL pool gauges, because we donot create pools other than CL - if tc.poolType == poolmanagertypes.Concentrated { + if tc.isBalancerPool { // check that gauge is not empty s.Require().NotEqual(len(gauges), 0) @@ -394,6 +357,186 @@ func (s *KeeperTestSuite) TestDistributeToConcentratedLiquidityPools() { } } +// TestDistribute_ExternalIncentives_ConcentratedPool tests the distribution of external incentives +// to concentrated liquidity pools. It creates an external gauge with the correct configuration +// and uses it to attempt to distribute tokens to a concentrated liquidity pool. +// It attempts to distribute with all possible gauge configurations and with various tokens. +// However, it does not test distribution of NoLock gauges. +func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_ConcentratedPool() { + const ( + defaultCLPool = uint64(1) + defaultBalancerPool = uint64(2) + + defaultAmount = int64(5000) + ) + + fiveKRewardCoins := sdk.NewInt64Coin(defaultRewardDenom, defaultAmount) + tenKOtherCoin := sdk.NewInt64Coin(otherDenom, defaultAmount+defaultAmount) + + defaultBothCoins := sdk.NewCoins(fiveKRewardCoins, tenKOtherCoin) + + defauBlockTime := time.Unix(123456789, 0) + oneHourAfterDefault := defauBlockTime.Add(time.Hour) + + type test struct { + // setup + isPerpertual bool + tokensToAddToGauge sdk.Coins + gaugeStartTime time.Time + gaugeCoins sdk.Coins + distrTo lockuptypes.QueryCondition + startTime time.Time + numEpochsPaidOver uint64 + poolId uint64 + + isBalancerPool bool + + // expected + expectErr bool + expectedDistributions sdk.Coins + expectedRemainingAmountIncentiveRecord []sdk.Dec + } + + defaultTest := test{ + isPerpertual: false, + gaugeStartTime: defauBlockTime, + gaugeCoins: sdk.NewCoins(fiveKRewardCoins), + distrTo: lockuptypes.QueryCondition{LockQueryType: lockuptypes.NoLock}, + startTime: oneHourAfterDefault, + numEpochsPaidOver: 1, + poolId: defaultCLPool, + expectErr: false, + + expectedDistributions: sdk.NewCoins(fiveKRewardCoins), + expectedRemainingAmountIncentiveRecord: []sdk.Dec{sdk.NewDec(defaultAmount)}, + } + + withIsPerpetual := func(tc test, isPerpetual bool) test { + tc.isPerpertual = isPerpetual + return tc + } + + withGaugeCoins := func(tc test, gaugeCoins sdk.Coins) test { + tc.gaugeCoins = gaugeCoins + tc.expectedDistributions = gaugeCoins + tc.expectedRemainingAmountIncentiveRecord = make([]sdk.Dec, len(gaugeCoins)) + for i := range tc.expectedRemainingAmountIncentiveRecord { + tc.expectedRemainingAmountIncentiveRecord[i] = sdk.NewDec(gaugeCoins[i].Amount.Int64()) + } + return tc + } + + withNumEpochs := func(tc test, numEpochs uint64) test { + tc.numEpochsPaidOver = numEpochs + + // Do deep copies + tempDistributions := make(sdk.Coins, len(tc.expectedDistributions)) + copy(tempDistributions, tc.expectedDistributions) + + tempRemainingAmountIncentiveRecord := make([]sdk.Dec, len(tc.expectedRemainingAmountIncentiveRecord)) + copy(tempRemainingAmountIncentiveRecord, tc.expectedRemainingAmountIncentiveRecord) + + for i := range tc.expectedRemainingAmountIncentiveRecord { + // update expected distributions + tempDistributions[i].Amount = tc.expectedDistributions[i].Amount.Quo(sdk.NewInt(int64(numEpochs))) + + // update expected remaining amount in incentive record + tempRemainingAmountIncentiveRecord[i] = tc.expectedRemainingAmountIncentiveRecord[i].QuoTruncate(sdk.NewDec(int64(numEpochs))).TruncateDec() + } + + tc.expectedDistributions = tempDistributions + tc.expectedRemainingAmountIncentiveRecord = tempRemainingAmountIncentiveRecord + return tc + } + + withPoolId := func(tc test, poolId uint64) test { + tc.poolId = poolId + tc.expectErr = true + return tc + } + + tests := map[string]test{ + "non-perpetual, 1 coin, paid over 1 epoch": defaultTest, + "perpetual, 1 coin, paid over 1 epoch": withIsPerpetual(defaultTest, true), + "non-perpetual, 2 coins, paid over 1 epoch": withGaugeCoins(defaultTest, defaultBothCoins), + "perpetual, 2 coins, paid over 1 epoch": withIsPerpetual(withGaugeCoins(defaultTest, defaultBothCoins), true), + "non-perpetual, 1 coin, paid over 2 epochs": withNumEpochs(defaultTest, 2), + "non-perpetual, 2 coins, paid over 3 epochs": withNumEpochs(withGaugeCoins(defaultTest, defaultBothCoins), 3), + "error: balancer pool id": withPoolId(defaultTest, defaultBalancerPool), + } + + for name, tc := range tests { + s.Run(name, func() { + // setup test + s.SetupTest() + + // We fix blocktime to ensure tests are deterministic + s.Ctx = s.Ctx.WithBlockTime(defauBlockTime) + + // Create CL and Balancer pools + s.PrepareConcentratedPool() + s.PrepareBalancerPool() + + // Set block time one hour after block creation so that incentives logic + // can function properly. + s.Ctx = s.Ctx.WithBlockTime(oneHourAfterDefault) + + s.FundAcc(s.TestAccs[0], tc.gaugeCoins) + + // Create gauge and get it from state + externalGaugeid, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, tc.isPerpertual, s.TestAccs[0], tc.gaugeCoins, tc.distrTo, tc.startTime, tc.numEpochsPaidOver, defaultCLPool) + s.Require().NoError(err) + externalGauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, externalGaugeid) + s.Require().NoError(err) + + // Force gauge's pool id to balancer to trigger error + if tc.poolId == defaultBalancerPool { + s.App.PoolIncentivesKeeper.SetPoolGaugeId(s.Ctx, defaultBalancerPool, tc.distrTo.Duration, externalGaugeid) + } + + // Activate the gauge. + err = s.App.IncentivesKeeper.MoveUpcomingGaugeToActiveGauge(s.Ctx, *externalGauge) + s.Require().NoError(err) + + gauges := []types.Gauge{*externalGauge} + + // System under test. + totalDistributedCoins, err := s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + // check the totalAmount of tokens distributed, for both lock gauges and CL pool gauges + s.Require().Equal(tc.expectedDistributions, totalDistributedCoins) + + // Get module account + moduleAccount := s.App.AccountKeeper.GetModuleAccount(s.Ctx, types.ModuleName) + incentiveModuleAddress := moduleAccount.GetAddress() + + incentivesEpochDuration := s.App.IncentivesKeeper.GetEpochInfo(s.Ctx).Duration + incentivesEpochDurationSeconds := sdk.NewDec(incentivesEpochDuration.Milliseconds()).QuoInt(sdk.NewInt(1000)) + + // Check that incentive records were created + for i, coin := range tc.expectedDistributions { + incentiveRecords, err := s.App.ConcentratedLiquidityKeeper.GetIncentiveRecord(s.Ctx, tc.poolId, coin.Denom, time.Nanosecond, incentiveModuleAddress) + s.Require().NoError(err) + + expectedEmissionRatePerEpoch := coin.Amount.ToDec().QuoTruncate(incentivesEpochDurationSeconds) + + s.Require().Equal(incentiveModuleAddress.String(), incentiveRecords.IncentiveCreatorAddr) + s.Require().Equal(tc.startTime.UTC(), incentiveRecords.IncentiveRecordBody.StartTime.UTC()) + s.Require().Equal(coin.Denom, incentiveRecords.IncentiveDenom) + s.Require().Equal(tc.expectedRemainingAmountIncentiveRecord[i], incentiveRecords.IncentiveRecordBody.RemainingAmount) + s.Require().Equal(expectedEmissionRatePerEpoch, incentiveRecords.IncentiveRecordBody.EmissionRate) + s.Require().Equal(time.Nanosecond, incentiveRecords.MinUptime) + } + } + }) + } +} + // TestSyntheticDistribute tests that when the distribute command is executed on a provided gauge // the correct amount of rewards is sent to the correct synthetic lock owners. func (s *KeeperTestSuite) TestSyntheticDistribute() { @@ -737,142 +880,3 @@ func (s *KeeperTestSuite) TestGetPoolFromGaugeId() { }) } } - -func (s *KeeperTestSuite) TestDistributeConcentratedLiquidity() { - var ( - timeBeforeBlock = time.Unix(0, 0) - defaultBlockTime = timeBeforeBlock.Add(10 * time.Second) - defaultAmountCoin = sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)} - defaultGauge = perpGaugeDesc{ - lockDenom: defaultLPDenom, - lockDuration: defaultLockDuration, - rewardAmount: defaultAmountCoin, - } - withLength = func(gauge perpGaugeDesc, length time.Duration) perpGaugeDesc { - gauge.lockDuration = length - return gauge - } - withAmount = func(gauge perpGaugeDesc, amount sdk.Coins) perpGaugeDesc { - gauge.rewardAmount = amount - return gauge - } - ) - - type distributeConcentratedLiquidityInternalTestCase struct { - name string - poolId uint64 - sender sdk.AccAddress - incentiveDenom string - incentiveAmount sdk.Int - emissionRate sdk.Dec - startTime time.Time - minUptime time.Duration - expectedCoins sdk.Coins - gauge perpGaugeDesc - authorizedUptimes []time.Duration - expectedError bool - } - - testCases := []distributeConcentratedLiquidityInternalTestCase{ - { - name: "valid: valid incentive record with valid gauge", - poolId: 1, - sender: s.TestAccs[0], - incentiveDenom: defaultRewardDenom, - incentiveAmount: sdk.NewInt(100), - emissionRate: sdk.NewDec(1), - startTime: defaultBlockTime, - minUptime: time.Hour * 24, - gauge: defaultGauge, - authorizedUptimes: []time.Duration{time.Hour * 24}, - - expectedCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(100))), - }, - { - name: "valid: valid incentive record with valid gauge (default authorized uptimes)", - poolId: 1, - sender: s.TestAccs[0], - incentiveDenom: defaultRewardDenom, - incentiveAmount: sdk.NewInt(100), - emissionRate: sdk.NewDec(1), - startTime: defaultBlockTime, - minUptime: time.Nanosecond, - gauge: defaultGauge, - - expectedCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(100))), - }, - { - name: "valid: valid incentive with double length record with valid gauge", - poolId: 1, - sender: s.TestAccs[0], - incentiveDenom: defaultRewardDenom, - incentiveAmount: sdk.NewInt(100), - emissionRate: sdk.NewDec(1), - startTime: defaultBlockTime, - minUptime: time.Hour * 24, - gauge: withLength(defaultGauge, defaultGauge.lockDuration*2), - authorizedUptimes: []time.Duration{time.Hour * 24}, - - expectedCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(100))), - }, - { - name: "valid: valid incentive with double amount record and valid gauge", - poolId: 1, - sender: s.TestAccs[0], - incentiveDenom: defaultRewardDenom, - incentiveAmount: sdk.NewInt(100), - emissionRate: sdk.NewDec(1), - startTime: defaultBlockTime, - minUptime: time.Hour * 24, - gauge: withAmount(defaultGauge, defaultAmountCoin.Add(defaultAmountCoin...)), - authorizedUptimes: []time.Duration{time.Hour * 24}, - - expectedCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(100))), - }, - { - name: "Invalid Case: invalid incentive Record with valid Gauge", - poolId: 1, - sender: s.TestAccs[0], - incentiveDenom: defaultRewardDenom, - incentiveAmount: sdk.NewInt(200), - emissionRate: sdk.NewDec(2), - startTime: timeBeforeBlock, - minUptime: time.Hour * 2, - gauge: defaultGauge, - authorizedUptimes: cltypes.SupportedUptimes, - - expectedError: true, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - s.SetupTest() - s.Ctx = s.Ctx.WithBlockTime(defaultBlockTime) - - // Set up authorized CL uptimes to robustly test distribution - if tc.authorizedUptimes != nil { - clParams := s.App.ConcentratedLiquidityKeeper.GetParams(s.Ctx) - clParams.AuthorizedUptimes = tc.authorizedUptimes - s.App.ConcentratedLiquidityKeeper.SetParams(s.Ctx, clParams) - } - - s.PrepareConcentratedPool() - - s.FundAcc(tc.sender, sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)))) - gauges := s.SetupGauges([]perpGaugeDesc{tc.gauge}, defaultRewardDenom) - - err := s.App.IncentivesKeeper.DistributeConcentratedLiquidity(s.Ctx, tc.poolId, tc.sender, sdk.NewCoin(tc.incentiveDenom, tc.incentiveAmount), tc.emissionRate, tc.startTime, tc.minUptime, gauges[0]) - if tc.expectedError { - s.Require().Error(err) - } else { - s.Require().NoError(err) - - gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gauges[0].Id) - s.Require().NoError(err) - - s.Require().Equal(gauge.DistributedCoins, gauges[0].DistributedCoins.Add(tc.expectedCoins...)) - } - }) - } -} diff --git a/x/incentives/keeper/export_test.go b/x/incentives/keeper/export_test.go index 8b6f3a98a6b..6f390fa8ba2 100644 --- a/x/incentives/keeper/export_test.go +++ b/x/incentives/keeper/export_test.go @@ -1,8 +1,6 @@ package keeper import ( - "time" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/v16/x/incentives/types" @@ -42,7 +40,3 @@ func (k Keeper) MoveActiveGaugeToFinishedGauge(ctx sdk.Context, gauge types.Gaug func (k Keeper) ChargeFeeIfSufficientFeeDenomBalance(ctx sdk.Context, address sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) error { return k.chargeFeeIfSufficientFeeDenomBalance(ctx, address, fee, gaugeCoins) } - -func (k Keeper) DistributeConcentratedLiquidity(ctx sdk.Context, poolId uint64, sender sdk.AccAddress, incentiveCoin sdk.Coin, emissionRate sdk.Dec, startTime time.Time, minUptime time.Duration, gauge types.Gauge) error { - return k.distributeConcentratedLiquidity(ctx, poolId, sender, incentiveCoin, emissionRate, startTime, minUptime, gauge) -} diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index ec68d60e6a1..b8d8ba3959e 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -18,6 +18,7 @@ import ( "github.com/osmosis-labs/osmosis/v16/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" + poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" epochtypes "github.com/osmosis-labs/osmosis/x/epochs/types" ) @@ -94,7 +95,7 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { } // CreateGauge creates a gauge and sends coins to the gauge. -func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64) (uint64, error) { +func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) if distrTo.LockQueryType == lockuptypes.ByDuration { @@ -110,13 +111,44 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr } } - // check if denom this gauge pays out to exists on-chain - if !k.bk.HasSupply(ctx, distrTo.Denom) && !strings.Contains(distrTo.Denom, "osmovaloper") { - return 0, fmt.Errorf("denom does not exist: %s", distrTo.Denom) + nextGaugeId := k.GetLastGaugeID(ctx) + 1 + + // For no lock gauges, a pool id must be set. + // A pool with such id must exist and be a concentrated pool. + if distrTo.LockQueryType == lockuptypes.NoLock { + if poolId == 0 { + return 0, fmt.Errorf("no lock gauges must have a pool id") + } + if distrTo.Denom != "" { + return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}") + } + + pool, err := k.pmk.GetPool(ctx, poolId) + if err != nil { + return 0, err + } + + if pool.GetType() != poolmanagertypes.Concentrated { + return 0, fmt.Errorf("no lock gauges must be created for concentrated pools only") + } + + // We assume that external gauges are created with 0 duration, + // while internal gauges are created with an incentive epoch duration. + k.pik.SetPoolGaugeId(ctx, poolId, distrTo.Duration, nextGaugeId) + } else { + // For all other gauges, pool id must be 0. + if poolId != 0 { + return 0, fmt.Errorf("pool id must be 0 for gauges with lock") + } + + // check if denom this gauge pays out to exists on-chain + if !k.bk.HasSupply(ctx, distrTo.Denom) && !strings.Contains(distrTo.Denom, "osmovaloper") { + return 0, fmt.Errorf("denom does not exist: %s", distrTo.Denom) + } } gauge := types.Gauge{ - Id: k.GetLastGaugeID(ctx) + 1, + Id: nextGaugeId, IsPerpetual: isPerpetual, DistributeTo: distrTo, Coins: coins, diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index f8052258867..26833ca823f 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -24,11 +24,11 @@ func (s *KeeperTestSuite) TestInvalidDurationGaugeCreationValidation() { Denom: defaultLPDenom, Duration: defaultLockDuration / 2, // 0.5 second, invalid duration } - _, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1) + _, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1, 0) s.Require().Error(err) distrTo.Duration = defaultLockDuration - _, err = s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1) + _, err = s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1, 0) s.Require().NoError(err) } @@ -43,10 +43,10 @@ func (s *KeeperTestSuite) TestNonExistentDenomGaugeCreation() { Denom: defaultLPDenom, Duration: defaultLockDuration, } - _, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrNoSupply, defaultLiquidTokens, distrTo, time.Time{}, 1) + _, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrNoSupply, defaultLiquidTokens, distrTo, time.Time{}, 1, 0) s.Require().Error(err) - _, err = s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1) + _, err = s.App.IncentivesKeeper.CreateGauge(s.Ctx, false, addrs[0], defaultLiquidTokens, distrTo, time.Time{}, 1, 0) s.Require().NoError(err) } @@ -426,3 +426,109 @@ func (s *KeeperTestSuite) TestAddToGaugeRewards() { }) } } + +// TestCreateGauge_NoLockGauges tests the CreateGauge function +// specifically focusing on the no lock gauge type and test cases around it. +// It tests the following: +// - For no lock gauges, a CL pool id must be given as well and then pool must exist +// - Otherwise, the given pool id must be zero. Errors if not. +func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { + const ( + zeroPoolId = uint64(0) + balancerPool = uint64(1) + concentratedPool = uint64(2) + invalidPool = uint64(3) + // 3 are created for balancer pool and 1 for CL. + // As a result, the next gauge id should be 5. + defaultExpectedGaugeId = uint64(5) + + defaultIsPerpetualParam = false + + defaultNumEpochPaidOver = uint64(10) + ) + + var ( + defaultCoins = sdk.NewCoins( + sdk.NewCoin("uosmo", sdk.NewInt(100000)), + sdk.NewCoin("atom", sdk.NewInt(99999)), + ) + + defaultTime = time.Unix(0, 0) + ) + testCases := []struct { + name string + distrTo lockuptypes.QueryCondition + poolId uint64 + + expectedGaugeId uint64 + expectErr bool + }{ + { + name: "create valid no lock gauge with CL pool", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + }, + poolId: concentratedPool, + + expectedGaugeId: defaultExpectedGaugeId, + expectErr: false, + }, + { + name: "fail to create no lock gauge with balancer pool", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + }, + poolId: balancerPool, + + expectErr: true, + }, + { + name: "fail to create no lock gauge with non-existent pool", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + }, + poolId: invalidPool, + + expectErr: true, + }, + { + name: "fail to create no lock gauge with zero pool id", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + }, + poolId: zeroPoolId, + + expectErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + s.SetupTest() + + s.PrepareBalancerPool() + s.PrepareConcentratedPool() + + s.FundAcc(s.TestAccs[0], defaultCoins) + + // System under test + // Note that the default params are used for some inputs since they are not relevant to the test case. + gaugeId, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, defaultIsPerpetualParam, s.TestAccs[0], defaultCoins, tc.distrTo, defaultTime, defaultNumEpochPaidOver, tc.poolId) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + s.Require().Equal(tc.expectedGaugeId, gaugeId) + + // Assert that pool id and gauge id links are created + gaugeId, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, tc.poolId, tc.distrTo.Duration) + s.Require().NoError(err) + + s.Require().Equal(tc.expectedGaugeId, gaugeId) + } + }) + } +} diff --git a/x/incentives/keeper/genesis_test.go b/x/incentives/keeper/genesis_test.go index 9f49f2bd412..a342f985958 100644 --- a/x/incentives/keeper/genesis_test.go +++ b/x/incentives/keeper/genesis_test.go @@ -44,7 +44,7 @@ func TestIncentivesExportGenesis(t *testing.T) { // create a gauge that distributes coins to earlier created LP token and duration startTime := time.Now() - gaugeID, err := app.IncentivesKeeper.CreateGauge(ctx, true, addr, coins, distrTo, startTime, 1) + gaugeID, err := app.IncentivesKeeper.CreateGauge(ctx, true, addr, coins, distrTo, startTime, 1, 0) require.NoError(t, err) // export genesis using default configurations diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index ddac84ee5e9..76bdf9945c2 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -38,7 +38,7 @@ func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateG return nil, err } - gaugeID, err := server.keeper.CreateGauge(ctx, msg.IsPerpetual, owner, msg.Coins, msg.DistributeTo, msg.StartTime, msg.NumEpochsPaidOver) + gaugeID, err := server.keeper.CreateGauge(ctx, msg.IsPerpetual, owner, msg.Coins, msg.DistributeTo, msg.StartTime, msg.NumEpochsPaidOver, msg.PoolId) if err != nil { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) } diff --git a/x/incentives/keeper/suite_test.go b/x/incentives/keeper/suite_test.go index 44d010ae83d..10e6d4282ff 100644 --- a/x/incentives/keeper/suite_test.go +++ b/x/incentives/keeper/suite_test.go @@ -35,6 +35,7 @@ var ( lockAmounts: []sdk.Coins{defaultLPSyntheticTokens, defaultLPSyntheticTokens}, } defaultRewardDenom string = "rewardDenom" + otherDenom string = "someOtherDenom" ) // TODO: Switch more code to use userLocks and perpGaugeDesc @@ -134,7 +135,7 @@ func (s *KeeperTestSuite) SetupGauges(gaugeDescriptors []perpGaugeDesc, denom st // CreateGauge creates a gauge struct given the required params. func (s *KeeperTestSuite) CreateGauge(isPerpetual bool, addr sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpoch uint64) (uint64, *types.Gauge) { s.FundAcc(addr, coins) - gaugeID, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, isPerpetual, addr, coins, distrTo, startTime, numEpoch) + gaugeID, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, isPerpetual, addr, coins, distrTo, startTime, numEpoch, 0) s.Require().NoError(err) gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeID) s.Require().NoError(err) diff --git a/x/incentives/types/keys.go b/x/incentives/types/keys.go index c486adac413..071189f4379 100644 --- a/x/incentives/types/keys.go +++ b/x/incentives/types/keys.go @@ -1,5 +1,7 @@ package types +import "fmt" + var ( // ModuleName defines the module name. ModuleName = "incentives" @@ -50,3 +52,7 @@ var ( func KeyPrefix(p string) []byte { return []byte(p) } + +func NoLockGaugePrefix(poolId uint64) []byte { + return []byte(fmt.Sprintf("no-lock/%d", poolId)) +} diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 5b94e041a9c..099878d26c0 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -55,8 +55,21 @@ func (m MsgCreateGauge) ValidateBasic() error { return errors.New("distribution period should be 1 epoch for perpetual gauge") } - if lockuptypes.LockQueryType_name[int32(m.DistributeTo.LockQueryType)] != "ByDuration" { - return errors.New("only duration query condition is allowed. Start time distr conditions is an obsolete codepath slated for deletion") + lockType := m.DistributeTo.LockQueryType + if lockType == lockuptypes.ByTime { + return errors.New("start time distr conditions is an obsolete codepath slated for deletion") + } + + if lockType == lockuptypes.ByDuration && m.PoolId != 0 { + return errors.New("pool id should not be set for duration distr condition") + } + + if lockType == lockuptypes.NoLock && m.PoolId == 0 { + return errors.New("pool id should be set for no lock distr condition") + } + + if lockType == lockuptypes.NoLock && m.DistributeTo.Denom != "" { + return errors.New("no lock gauge denom should be unset. It will be automatically set to the no-lock/{pool-id} format") } return nil diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index a69fe97a212..8afee23b9e7 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -17,8 +17,8 @@ import ( lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" ) -// TestMsgCreatePool tests if valid/invalid create pool messages are properly validated/invalidated -func TestMsgCreatePool(t *testing.T) { +// TestMsgCreateGauge tests if valid/invalid create pool messages are properly validated/invalidated +func TestMsgCreateGauge(t *testing.T) { // generate a private/public key pair and get the respective address pk1 := ed25519.GenPrivKey().PubKey() addr1 := sdk.AccAddress(pk1.Address()) @@ -139,6 +139,41 @@ func TestMsgCreatePool(t *testing.T) { }), expectPass: true, }, + { + name: "invalid: by time lock type", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.ByTime + return msg + }), + expectPass: false, + }, + { + name: "invalid: by duration with pool id set", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.ByDuration + msg.PoolId = 1 + return msg + }), + expectPass: false, + }, + { + name: "invalid: no lock with pool id unset", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + msg.PoolId = 0 + return msg + }), + expectPass: false, + }, + { + name: "valid no lick with pool id unset", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + msg.PoolId = 1 + return msg + }), + expectPass: true, + }, } for _, test := range tests { diff --git a/x/incentives/types/tx.pb.go b/x/incentives/types/tx.pb.go index 8349647bfb3..d5cd5818639 100644 --- a/x/incentives/types/tx.pb.go +++ b/x/incentives/types/tx.pb.go @@ -56,6 +56,10 @@ type MsgCreateGauge struct { // num_epochs_paid_over is the number of epochs distribution will be completed // over NumEpochsPaidOver uint64 `protobuf:"varint,6,opt,name=num_epochs_paid_over,json=numEpochsPaidOver,proto3" json:"num_epochs_paid_over,omitempty"` + // pool_id is the ID of the pool that the gauge is meant to be associated + // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be + // "NoLock" with all other fields unset. + PoolId uint64 `protobuf:"varint,7,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` } func (m *MsgCreateGauge) Reset() { *m = MsgCreateGauge{} } @@ -133,6 +137,13 @@ func (m *MsgCreateGauge) GetNumEpochsPaidOver() uint64 { return 0 } +func (m *MsgCreateGauge) GetPoolId() uint64 { + if m != nil { + return m.PoolId + } + return 0 +} + type MsgCreateGaugeResponse struct { } @@ -279,47 +290,48 @@ func init() { func init() { proto.RegisterFile("osmosis/incentives/tx.proto", fileDescriptor_8ea120e22291556e) } var fileDescriptor_8ea120e22291556e = []byte{ - // 628 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0x3f, 0x4f, 0xdc, 0x30, - 0x18, 0xc6, 0xcf, 0xbd, 0xe3, 0x9f, 0x0f, 0x2a, 0x88, 0x68, 0x1b, 0xae, 0x55, 0x72, 0x44, 0x55, - 0x95, 0x22, 0x9d, 0x5d, 0xa8, 0xc4, 0xc0, 0xd6, 0x43, 0x55, 0xc5, 0x80, 0x4a, 0x23, 0xa4, 0x4a, - 0x48, 0x55, 0xe4, 0xc4, 0x6e, 0xb0, 0xb8, 0xc4, 0x51, 0xec, 0x1c, 0xf0, 0x15, 0x3a, 0xf1, 0x39, - 0x3a, 0x75, 0xed, 0xd6, 0x91, 0x91, 0xb1, 0x13, 0x54, 0x30, 0x74, 0xe7, 0x13, 0x54, 0x71, 0x12, - 0xe0, 0x54, 0x28, 0x4b, 0x97, 0x38, 0xf6, 0xf3, 0xbe, 0xaf, 0xfd, 0x3e, 0x3f, 0x27, 0xf0, 0xa9, - 0x90, 0xb1, 0x90, 0x5c, 0x62, 0x9e, 0x84, 0x2c, 0x51, 0x7c, 0xc8, 0x24, 0x56, 0x07, 0x28, 0xcd, - 0x84, 0x12, 0x86, 0x51, 0x89, 0xe8, 0x5a, 0xec, 0xcc, 0x47, 0x22, 0x12, 0x5a, 0xc6, 0xc5, 0x5b, - 0x19, 0xd9, 0x99, 0x23, 0x31, 0x4f, 0x04, 0xd6, 0xcf, 0x6a, 0xc9, 0x8e, 0x84, 0x88, 0x06, 0x0c, - 0xeb, 0x59, 0x90, 0x7f, 0xc6, 0x8a, 0xc7, 0x4c, 0x2a, 0x12, 0xa7, 0x55, 0x80, 0x15, 0xea, 0xf2, - 0x38, 0x20, 0x92, 0xe1, 0xe1, 0x72, 0xc0, 0x14, 0x59, 0xc6, 0xa1, 0xe0, 0x49, 0xad, 0xdf, 0x72, - 0xb4, 0x88, 0xe4, 0x11, 0xab, 0xf4, 0x85, 0x5a, 0x1f, 0x88, 0x70, 0x2f, 0x4f, 0xf5, 0x50, 0x4a, - 0xce, 0xf7, 0x26, 0x7c, 0xb8, 0x29, 0xa3, 0xf5, 0x8c, 0x11, 0xc5, 0xde, 0x15, 0x39, 0xc6, 0x22, - 0x9c, 0xe6, 0xd2, 0x4f, 0x59, 0x96, 0x32, 0x95, 0x93, 0x81, 0x09, 0xba, 0xc0, 0x9d, 0xf4, 0xda, - 0x5c, 0x6e, 0xd5, 0x4b, 0xc6, 0x0b, 0x38, 0x26, 0xf6, 0x13, 0x96, 0x99, 0x0f, 0xba, 0xc0, 0x9d, - 0xea, 0xcf, 0x5e, 0x9e, 0xda, 0xd3, 0x87, 0x24, 0x1e, 0xac, 0x39, 0x7a, 0xd9, 0xf1, 0x4a, 0xd9, - 0xd8, 0x80, 0x33, 0x94, 0x4b, 0x95, 0xf1, 0x20, 0x57, 0xcc, 0x57, 0xc2, 0x6c, 0x76, 0x81, 0xdb, - 0x5e, 0xb1, 0x50, 0x6d, 0x57, 0x79, 0x20, 0xf4, 0x21, 0x67, 0xd9, 0xe1, 0xba, 0x48, 0x28, 0x57, - 0x5c, 0x24, 0xfd, 0xd6, 0xf1, 0xa9, 0xdd, 0xf0, 0xa6, 0xaf, 0x53, 0xb7, 0x85, 0x41, 0xe0, 0x58, - 0xd1, 0xb1, 0x34, 0x5b, 0xdd, 0xa6, 0xdb, 0x5e, 0x59, 0x40, 0xa5, 0x27, 0xa8, 0xf0, 0x04, 0x55, - 0x9e, 0xa0, 0x75, 0xc1, 0x93, 0xfe, 0xab, 0x22, 0xfb, 0xeb, 0x99, 0xed, 0x46, 0x5c, 0xed, 0xe6, - 0x01, 0x0a, 0x45, 0x8c, 0x2b, 0x03, 0xcb, 0xa1, 0x27, 0xe9, 0x1e, 0x56, 0x87, 0x29, 0x93, 0x3a, - 0x41, 0x7a, 0x65, 0x65, 0xe3, 0x23, 0x84, 0x52, 0x91, 0x4c, 0xf9, 0x85, 0xff, 0xe6, 0x98, 0x3e, - 0x6a, 0x07, 0x95, 0x70, 0x50, 0x0d, 0x07, 0x6d, 0xd7, 0x70, 0xfa, 0xcf, 0x8a, 0x8d, 0x2e, 0x4f, - 0xed, 0xd9, 0xb2, 0xf5, 0x2b, 0x6a, 0xce, 0xd1, 0x99, 0x0d, 0xbc, 0x29, 0x5d, 0xab, 0x88, 0x36, - 0x30, 0x9c, 0x4f, 0xf2, 0xd8, 0x67, 0xa9, 0x08, 0x77, 0xa5, 0x9f, 0x12, 0x4e, 0x7d, 0x31, 0x64, - 0x99, 0x39, 0xde, 0x05, 0x6e, 0xcb, 0x9b, 0x4b, 0xf2, 0xf8, 0xad, 0x96, 0xb6, 0x08, 0xa7, 0xef, - 0x87, 0x2c, 0x5b, 0x7b, 0xfe, 0xe5, 0xf7, 0xb7, 0x25, 0xfb, 0x16, 0xaa, 0xa1, 0xe6, 0xd4, 0xd3, - 0x70, 0x1d, 0x13, 0x3e, 0x1e, 0x45, 0xe7, 0x31, 0x99, 0x8a, 0x44, 0x32, 0xe7, 0x0c, 0xc0, 0x99, - 0x4d, 0x19, 0xbd, 0xa1, 0x74, 0x5b, 0x94, 0x50, 0xaf, 0x88, 0x81, 0x7f, 0x13, 0x5b, 0x80, 0x93, - 0xba, 0xb8, 0xcf, 0xa9, 0x86, 0xdb, 0xf2, 0x26, 0xf4, 0x7c, 0x83, 0x1a, 0x0c, 0x4e, 0x64, 0x6c, - 0x9f, 0x64, 0x54, 0x9a, 0xcd, 0xff, 0xcf, 0xa0, 0xae, 0x7d, 0x77, 0xef, 0x84, 0xd2, 0x9e, 0x12, - 0x55, 0xef, 0x4f, 0xe0, 0xa3, 0x91, 0x06, 0xeb, 0xd6, 0x57, 0x7e, 0x00, 0xd8, 0xdc, 0x94, 0x91, - 0xf1, 0x09, 0xb6, 0x6f, 0x5e, 0x6a, 0x07, 0xfd, 0xfd, 0x85, 0xa2, 0x51, 0xf7, 0x3a, 0x4b, 0xf7, - 0xc7, 0xd4, 0xdb, 0x18, 0x3b, 0x10, 0xde, 0x70, 0x77, 0xf1, 0x8e, 0xcc, 0xeb, 0x90, 0xce, 0xcb, - 0x7b, 0x43, 0xea, 0xda, 0xfd, 0xad, 0xe3, 0x73, 0x0b, 0x9c, 0x9c, 0x5b, 0xe0, 0xd7, 0xb9, 0x05, - 0x8e, 0x2e, 0xac, 0xc6, 0xc9, 0x85, 0xd5, 0xf8, 0x79, 0x61, 0x35, 0x76, 0x56, 0x6f, 0xd8, 0x59, - 0x95, 0xeb, 0x0d, 0x48, 0x20, 0xeb, 0x09, 0x1e, 0x2e, 0xaf, 0xe2, 0x83, 0x91, 0x3f, 0x54, 0x61, - 0x71, 0x30, 0xae, 0x6f, 0xef, 0xeb, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xb8, 0xbe, 0xaa, - 0xc4, 0x04, 0x00, 0x00, + // 642 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x41, 0x4f, 0xd4, 0x40, + 0x14, 0xde, 0xb2, 0xc0, 0xc2, 0x2c, 0x18, 0x68, 0x50, 0xca, 0x6a, 0xda, 0xa5, 0x31, 0x66, 0x25, + 0xd9, 0x19, 0xc1, 0x84, 0x03, 0x37, 0x97, 0x18, 0xc3, 0x81, 0x88, 0x0d, 0x89, 0x09, 0x89, 0x69, + 0xa6, 0xed, 0x58, 0x26, 0x6c, 0xfb, 0x9a, 0xce, 0x74, 0x81, 0xbf, 0xe0, 0x89, 0xdf, 0xe1, 0xc9, + 0x9f, 0xe0, 0x91, 0x23, 0xf1, 0xe4, 0x09, 0x0c, 0x1c, 0xbc, 0xf3, 0x0b, 0x4c, 0xa7, 0x2d, 0xec, + 0x46, 0x90, 0x8b, 0x97, 0x4e, 0xdf, 0x7c, 0xef, 0x7d, 0xf3, 0xde, 0xf7, 0x4d, 0x8b, 0x9e, 0x82, + 0x88, 0x40, 0x70, 0x41, 0x78, 0xec, 0xb3, 0x58, 0xf2, 0x01, 0x13, 0x44, 0x1e, 0xe1, 0x24, 0x05, + 0x09, 0xba, 0x5e, 0x82, 0xf8, 0x16, 0x6c, 0x2d, 0x84, 0x10, 0x82, 0x82, 0x49, 0xfe, 0x56, 0x64, + 0xb6, 0xe6, 0x69, 0xc4, 0x63, 0x20, 0xea, 0x59, 0x6e, 0x59, 0x21, 0x40, 0xd8, 0x67, 0x44, 0x45, + 0x5e, 0xf6, 0x99, 0x48, 0x1e, 0x31, 0x21, 0x69, 0x94, 0x94, 0x09, 0xa6, 0xaf, 0xe8, 0x89, 0x47, + 0x05, 0x23, 0x83, 0x55, 0x8f, 0x49, 0xba, 0x4a, 0x7c, 0xe0, 0x71, 0x85, 0xdf, 0xd1, 0x5a, 0x48, + 0xb3, 0x90, 0x95, 0xf8, 0x52, 0x85, 0xf7, 0xc1, 0x3f, 0xc8, 0x12, 0xb5, 0x14, 0x90, 0xfd, 0xa3, + 0x8e, 0x1e, 0x6d, 0x8b, 0x70, 0x33, 0x65, 0x54, 0xb2, 0x77, 0x79, 0x8d, 0xbe, 0x8c, 0x66, 0xb8, + 0x70, 0x13, 0x96, 0x26, 0x4c, 0x66, 0xb4, 0x6f, 0x68, 0x6d, 0xad, 0x33, 0xe5, 0x34, 0xb9, 0xd8, + 0xa9, 0xb6, 0xf4, 0x17, 0x68, 0x02, 0x0e, 0x63, 0x96, 0x1a, 0x63, 0x6d, 0xad, 0x33, 0xdd, 0x9b, + 0xbb, 0x3e, 0xb7, 0x66, 0x8e, 0x69, 0xd4, 0xdf, 0xb0, 0xd5, 0xb6, 0xed, 0x14, 0xb0, 0xbe, 0x85, + 0x66, 0x03, 0x2e, 0x64, 0xca, 0xbd, 0x4c, 0x32, 0x57, 0x82, 0x51, 0x6f, 0x6b, 0x9d, 0xe6, 0x9a, + 0x89, 0x2b, 0xb9, 0x8a, 0x86, 0xf0, 0x87, 0x8c, 0xa5, 0xc7, 0x9b, 0x10, 0x07, 0x5c, 0x72, 0x88, + 0x7b, 0xe3, 0xa7, 0xe7, 0x56, 0xcd, 0x99, 0xb9, 0x2d, 0xdd, 0x05, 0x9d, 0xa2, 0x89, 0x7c, 0x62, + 0x61, 0x8c, 0xb7, 0xeb, 0x9d, 0xe6, 0xda, 0x12, 0x2e, 0x34, 0xc1, 0xb9, 0x26, 0xb8, 0xd4, 0x04, + 0x6f, 0x02, 0x8f, 0x7b, 0xaf, 0xf2, 0xea, 0xaf, 0x17, 0x56, 0x27, 0xe4, 0x72, 0x3f, 0xf3, 0xb0, + 0x0f, 0x11, 0x29, 0x05, 0x2c, 0x96, 0xae, 0x08, 0x0e, 0x88, 0x3c, 0x4e, 0x98, 0x50, 0x05, 0xc2, + 0x29, 0x98, 0xf5, 0x8f, 0x08, 0x09, 0x49, 0x53, 0xe9, 0xe6, 0xfa, 0x1b, 0x13, 0xaa, 0xd5, 0x16, + 0x2e, 0xcc, 0xc1, 0x95, 0x39, 0x78, 0xb7, 0x32, 0xa7, 0xf7, 0x2c, 0x3f, 0xe8, 0xfa, 0xdc, 0x9a, + 0x2b, 0x46, 0xbf, 0x71, 0xcd, 0x3e, 0xb9, 0xb0, 0x34, 0x67, 0x5a, 0x71, 0xe5, 0xd9, 0x3a, 0x41, + 0x0b, 0x71, 0x16, 0xb9, 0x2c, 0x01, 0x7f, 0x5f, 0xb8, 0x09, 0xe5, 0x81, 0x0b, 0x03, 0x96, 0x1a, + 0x93, 0x6d, 0xad, 0x33, 0xee, 0xcc, 0xc7, 0x59, 0xf4, 0x56, 0x41, 0x3b, 0x94, 0x07, 0xef, 0x07, + 0x2c, 0xd5, 0x17, 0x51, 0x23, 0x01, 0xe8, 0xbb, 0x3c, 0x30, 0x1a, 0x2a, 0x67, 0x32, 0x0f, 0xb7, + 0x82, 0x8d, 0xe7, 0x5f, 0x7e, 0x7f, 0x5b, 0xb1, 0xee, 0xb0, 0xdb, 0x57, 0x06, 0x76, 0x95, 0xeb, + 0xb6, 0x81, 0x9e, 0x8c, 0x7a, 0xea, 0x30, 0x91, 0x40, 0x2c, 0x98, 0x7d, 0xa1, 0xa1, 0xd9, 0x6d, + 0x11, 0xbe, 0x09, 0x82, 0x5d, 0x28, 0xdc, 0xbe, 0xb1, 0x52, 0xfb, 0xb7, 0x95, 0x4b, 0x68, 0x4a, + 0x91, 0xe7, 0x3d, 0x8d, 0xa9, 0x9e, 0x1a, 0x2a, 0xde, 0x0a, 0x74, 0x86, 0x1a, 0x29, 0x3b, 0xa4, + 0x69, 0x20, 0x8c, 0xfa, 0xff, 0x37, 0xa7, 0xe2, 0xbe, 0x7f, 0x76, 0x1a, 0x04, 0x5d, 0x09, 0xe5, + 0xec, 0x8b, 0xe8, 0xf1, 0xc8, 0x80, 0xd5, 0xe8, 0x6b, 0xdf, 0x35, 0x54, 0xdf, 0x16, 0xa1, 0xfe, + 0x09, 0x35, 0x87, 0x6f, 0xbb, 0x8d, 0xff, 0xfe, 0x74, 0xf1, 0xa8, 0x7a, 0xad, 0x95, 0x87, 0x73, + 0xaa, 0x63, 0xf4, 0x3d, 0x84, 0x86, 0xd4, 0x5d, 0xbe, 0xa7, 0xf2, 0x36, 0xa5, 0xf5, 0xf2, 0xc1, + 0x94, 0x8a, 0xbb, 0xb7, 0x73, 0x7a, 0x69, 0x6a, 0x67, 0x97, 0xa6, 0xf6, 0xeb, 0xd2, 0xd4, 0x4e, + 0xae, 0xcc, 0xda, 0xd9, 0x95, 0x59, 0xfb, 0x79, 0x65, 0xd6, 0xf6, 0xd6, 0x87, 0xe4, 0x2c, 0xe9, + 0xba, 0x7d, 0xea, 0x89, 0x2a, 0x20, 0x83, 0xd5, 0x75, 0x72, 0x34, 0xf2, 0xeb, 0xca, 0x25, 0xf6, + 0x26, 0xd5, 0xb5, 0x7e, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x41, 0x82, 0x0e, 0xdd, 0x04, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -458,6 +470,11 @@ func (m *MsgCreateGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.PoolId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.PoolId)) + i-- + dAtA[i] = 0x38 + } if m.NumEpochsPaidOver != 0 { i = encodeVarintTx(dAtA, i, uint64(m.NumEpochsPaidOver)) i-- @@ -647,6 +664,9 @@ func (m *MsgCreateGauge) Size() (n int) { if m.NumEpochsPaidOver != 0 { n += 1 + sovTx(uint64(m.NumEpochsPaidOver)) } + if m.PoolId != 0 { + n += 1 + sovTx(uint64(m.PoolId)) + } return n } @@ -896,6 +916,25 @@ func (m *MsgCreateGauge) Unmarshal(dAtA []byte) error { break } } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType) + } + m.PoolId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PoolId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/lockup/types/lock.pb.go b/x/lockup/types/lock.pb.go index dd7965493ef..fb636a4e8ca 100644 --- a/x/lockup/types/lock.pb.go +++ b/x/lockup/types/lock.pb.go @@ -36,16 +36,19 @@ type LockQueryType int32 const ( ByDuration LockQueryType = 0 ByTime LockQueryType = 1 + NoLock LockQueryType = 2 ) var LockQueryType_name = map[int32]string{ 0: "ByDuration", 1: "ByTime", + 2: "NoLock", } var LockQueryType_value = map[string]int32{ "ByDuration": 0, "ByTime": 1, + "NoLock": 2, } func (x LockQueryType) String() string { @@ -328,47 +331,48 @@ func init() { func init() { proto.RegisterFile("osmosis/lockup/lock.proto", fileDescriptor_7e9d7527a237b489) } var fileDescriptor_7e9d7527a237b489 = []byte{ - // 640 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x3f, 0x6f, 0xd4, 0x30, - 0x1c, 0xbd, 0xdc, 0x9f, 0xd2, 0xba, 0xf4, 0x7a, 0xb2, 0x8a, 0x48, 0x0f, 0x48, 0x4e, 0x19, 0xd0, - 0x09, 0xb5, 0x09, 0x57, 0x24, 0x06, 0x36, 0xd2, 0x63, 0xa8, 0xd4, 0x01, 0x42, 0xc5, 0xd0, 0x25, - 0x4a, 0x62, 0x93, 0x5a, 0x4d, 0xe2, 0x10, 0x27, 0x2d, 0xf9, 0x06, 0x8c, 0x1d, 0x41, 0x62, 0x63, - 0xe3, 0x5b, 0xb0, 0x75, 0xec, 0xc8, 0x74, 0x45, 0xed, 0xc6, 0xd8, 0x4f, 0x80, 0x6c, 0x27, 0xd7, - 0x6b, 0x51, 0xa5, 0x0e, 0x30, 0xe5, 0xec, 0xf7, 0xfb, 0x3d, 0xff, 0xfc, 0xde, 0xf3, 0x81, 0x55, - 0xca, 0x62, 0xca, 0x08, 0xb3, 0x22, 0x1a, 0xec, 0x17, 0xa9, 0xf8, 0x98, 0x69, 0x46, 0x73, 0x0a, - 0xbb, 0x15, 0x64, 0x4a, 0xa8, 0xbf, 0x12, 0xd2, 0x90, 0x0a, 0xc8, 0xe2, 0xbf, 0x64, 0x55, 0x5f, - 0x0b, 0x29, 0x0d, 0x23, 0x6c, 0x89, 0x95, 0x5f, 0xbc, 0xb7, 0x50, 0x91, 0x79, 0x39, 0xa1, 0x49, - 0x85, 0xeb, 0xd7, 0xf1, 0x9c, 0xc4, 0x98, 0xe5, 0x5e, 0x9c, 0xd6, 0x04, 0x81, 0x38, 0xc7, 0xf2, - 0x3d, 0x86, 0xad, 0x83, 0x91, 0x8f, 0x73, 0x6f, 0x64, 0x05, 0x94, 0x54, 0x04, 0xc6, 0x8f, 0x16, - 0x00, 0xaf, 0x71, 0x46, 0x28, 0xda, 0xa6, 0xc1, 0x3e, 0xec, 0x82, 0xe6, 0xd6, 0x58, 0x55, 0x06, - 0xca, 0xb0, 0xed, 0x34, 0xb7, 0xc6, 0xf0, 0x31, 0xe8, 0xd0, 0xc3, 0x04, 0x67, 0x6a, 0x73, 0xa0, - 0x0c, 0x17, 0xec, 0xde, 0xc5, 0x44, 0xbf, 0x5b, 0x7a, 0x71, 0xf4, 0xc2, 0x10, 0xdb, 0x86, 0x23, - 0x61, 0xb8, 0x07, 0xe6, 0xeb, 0xc9, 0xd4, 0xd6, 0x40, 0x19, 0x2e, 0x6e, 0xac, 0x9a, 0x72, 0x34, - 0xb3, 0x1e, 0xcd, 0x1c, 0x57, 0x05, 0xf6, 0xe8, 0x78, 0xa2, 0x37, 0x7e, 0x4f, 0x74, 0x58, 0xb7, - 0xac, 0xd1, 0x98, 0xe4, 0x38, 0x4e, 0xf3, 0xf2, 0x62, 0xa2, 0x2f, 0x4b, 0xfe, 0x1a, 0x33, 0x3e, - 0x9f, 0xea, 0x8a, 0x33, 0x65, 0x87, 0x0e, 0x98, 0xc7, 0x09, 0x72, 0xf9, 0x3d, 0xd5, 0xb6, 0x38, - 0xa9, 0xff, 0xd7, 0x49, 0x3b, 0xb5, 0x08, 0xf6, 0x03, 0x7e, 0xd4, 0x25, 0x69, 0xdd, 0x69, 0x1c, - 0x71, 0xd2, 0x3b, 0x38, 0x41, 0xbc, 0x14, 0x7a, 0xa0, 0xc3, 0x25, 0x61, 0x6a, 0x67, 0xd0, 0x12, - 0xa3, 0x4b, 0xd1, 0x4c, 0x2e, 0x9a, 0x59, 0x89, 0x66, 0x6e, 0x52, 0x92, 0xd8, 0x4f, 0x39, 0xdf, - 0xf7, 0x53, 0x7d, 0x18, 0x92, 0x7c, 0xaf, 0xf0, 0xcd, 0x80, 0xc6, 0x56, 0xa5, 0xb0, 0xfc, 0xac, - 0x33, 0xb4, 0x6f, 0xe5, 0x65, 0x8a, 0x99, 0x68, 0x60, 0x8e, 0x64, 0x86, 0xbb, 0xe0, 0x7e, 0x86, - 0x0f, 0xbd, 0x0c, 0xb9, 0x19, 0x0e, 0x30, 0x39, 0xc0, 0x99, 0xeb, 0x21, 0x94, 0x61, 0xc6, 0xd4, - 0x39, 0x21, 0xad, 0x71, 0x31, 0xd1, 0x35, 0x39, 0xe5, 0x0d, 0x85, 0x86, 0x73, 0x4f, 0x22, 0x4e, - 0x05, 0xbc, 0xac, 0xf6, 0xbf, 0x34, 0x41, 0xf7, 0x4d, 0x81, 0xb3, 0x72, 0x93, 0x26, 0x88, 0x08, - 0x95, 0x5e, 0x81, 0x65, 0x9e, 0x2b, 0xf7, 0x03, 0xdf, 0x76, 0xf9, 0x3c, 0xc2, 0xd4, 0xee, 0xc6, - 0x23, 0xf3, 0x6a, 0xee, 0x4c, 0x6e, 0xbb, 0x68, 0xde, 0x29, 0x53, 0xec, 0x2c, 0x45, 0xb3, 0x4b, - 0xb8, 0x02, 0x3a, 0x08, 0x27, 0x34, 0x96, 0xf6, 0x3b, 0x72, 0xc1, 0x2d, 0xb8, 0xbd, 0xd9, 0xd7, - 0x1c, 0xb8, 0xc9, 0xd6, 0x77, 0x60, 0x61, 0x1a, 0xdd, 0x5b, 0xf8, 0xfa, 0xb0, 0x62, 0xed, 0x49, - 0xd6, 0x69, 0xab, 0x34, 0xf6, 0x92, 0xca, 0xf8, 0xda, 0x04, 0x4b, 0x6f, 0xcb, 0x24, 0xdf, 0xc3, - 0x39, 0x09, 0x44, 0xc4, 0xd7, 0x00, 0x2c, 0x12, 0x84, 0xb3, 0xa8, 0x24, 0x49, 0xe8, 0x0a, 0x95, - 0x08, 0xaa, 0x22, 0xdf, 0xbb, 0x44, 0x78, 0xed, 0x16, 0x82, 0x3a, 0x58, 0x64, 0xbc, 0xdd, 0x9d, - 0xd5, 0x01, 0x88, 0xad, 0x71, 0x2d, 0xc6, 0x34, 0x8f, 0xad, 0x7f, 0x94, 0xc7, 0xd9, 0xd7, 0xd4, - 0xfe, 0x9f, 0xaf, 0xe9, 0xc9, 0x08, 0x2c, 0x5d, 0x09, 0x00, 0xec, 0x02, 0x60, 0x97, 0x35, 0x77, - 0xaf, 0x01, 0x01, 0x98, 0xb3, 0x4b, 0x3e, 0x54, 0x4f, 0xe9, 0xb7, 0x3f, 0x7d, 0xd3, 0x1a, 0xf6, - 0xf6, 0xf1, 0x99, 0xa6, 0x9c, 0x9c, 0x69, 0xca, 0xaf, 0x33, 0x4d, 0x39, 0x3a, 0xd7, 0x1a, 0x27, - 0xe7, 0x5a, 0xe3, 0xe7, 0xb9, 0xd6, 0xd8, 0xdd, 0x98, 0x79, 0x14, 0x55, 0xca, 0xd6, 0x23, 0xcf, - 0x67, 0xf5, 0xc2, 0x3a, 0x18, 0x3d, 0xb7, 0x3e, 0xd6, 0xff, 0x85, 0xe2, 0x91, 0xf8, 0x73, 0xe2, - 0x42, 0xcf, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xa9, 0xc7, 0xca, 0xaa, 0x2a, 0x05, 0x00, 0x00, + // 649 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x3f, 0x6f, 0xd3, 0x40, + 0x1c, 0x8d, 0xf3, 0xa7, 0xb4, 0x57, 0x9a, 0x46, 0xa7, 0x22, 0xd2, 0x00, 0x76, 0xe4, 0x01, 0x45, + 0xa8, 0xb5, 0x49, 0x91, 0x18, 0x90, 0x18, 0x70, 0xc3, 0x50, 0xa9, 0x42, 0x60, 0x2a, 0x86, 0x2e, + 0x96, 0xed, 0x3b, 0xdc, 0x53, 0x6d, 0x9f, 0xf1, 0xd9, 0x2d, 0xfe, 0x06, 0x8c, 0x1d, 0x41, 0x62, + 0x63, 0xe3, 0x5b, 0xb0, 0x75, 0xec, 0xc8, 0x94, 0xa2, 0x76, 0x63, 0xec, 0x27, 0x40, 0x77, 0x67, + 0x27, 0x69, 0x51, 0xa5, 0x0e, 0x30, 0xd9, 0x77, 0xef, 0xf7, 0x7b, 0xf7, 0xf3, 0x7b, 0xef, 0x0c, + 0x56, 0x29, 0x8b, 0x28, 0x23, 0xcc, 0x0c, 0xa9, 0xbf, 0x9f, 0x27, 0xe2, 0x61, 0x24, 0x29, 0xcd, + 0x28, 0x6c, 0x97, 0x90, 0x21, 0xa1, 0xde, 0x4a, 0x40, 0x03, 0x2a, 0x20, 0x93, 0xbf, 0xc9, 0xaa, + 0x9e, 0x1a, 0x50, 0x1a, 0x84, 0xd8, 0x14, 0x2b, 0x2f, 0x7f, 0x6f, 0xa2, 0x3c, 0x75, 0x33, 0x42, + 0xe3, 0x12, 0xd7, 0xae, 0xe2, 0x19, 0x89, 0x30, 0xcb, 0xdc, 0x28, 0xa9, 0x08, 0x7c, 0x71, 0x8e, + 0xe9, 0xb9, 0x0c, 0x9b, 0x07, 0x43, 0x0f, 0x67, 0xee, 0xd0, 0xf4, 0x29, 0x29, 0x09, 0xf4, 0x1f, + 0x0d, 0x00, 0x5e, 0xe3, 0x94, 0x50, 0xb4, 0x4d, 0xfd, 0x7d, 0xd8, 0x06, 0xf5, 0xad, 0x51, 0x57, + 0xe9, 0x2b, 0x83, 0xa6, 0x5d, 0xdf, 0x1a, 0xc1, 0x87, 0xa0, 0x45, 0x0f, 0x63, 0x9c, 0x76, 0xeb, + 0x7d, 0x65, 0xb0, 0x60, 0x75, 0x2e, 0xc6, 0xda, 0xed, 0xc2, 0x8d, 0xc2, 0x67, 0xba, 0xd8, 0xd6, + 0x6d, 0x09, 0xc3, 0x3d, 0x30, 0x5f, 0x4d, 0xd6, 0x6d, 0xf4, 0x95, 0xc1, 0xe2, 0xc6, 0xaa, 0x21, + 0x47, 0x33, 0xaa, 0xd1, 0x8c, 0x51, 0x59, 0x60, 0x0d, 0x8f, 0xc7, 0x5a, 0xed, 0xf7, 0x58, 0x83, + 0x55, 0xcb, 0x1a, 0x8d, 0x48, 0x86, 0xa3, 0x24, 0x2b, 0x2e, 0xc6, 0xda, 0xb2, 0xe4, 0xaf, 0x30, + 0xfd, 0xf3, 0xa9, 0xa6, 0xd8, 0x13, 0x76, 0x68, 0x83, 0x79, 0x1c, 0x23, 0x87, 0x7f, 0x67, 0xb7, + 0x29, 0x4e, 0xea, 0xfd, 0x75, 0xd2, 0x4e, 0x25, 0x82, 0x75, 0x8f, 0x1f, 0x35, 0x25, 0xad, 0x3a, + 0xf5, 0x23, 0x4e, 0x7a, 0x0b, 0xc7, 0x88, 0x97, 0x42, 0x17, 0xb4, 0xb8, 0x24, 0xac, 0xdb, 0xea, + 0x37, 0xc4, 0xe8, 0x52, 0x34, 0x83, 0x8b, 0x66, 0x94, 0xa2, 0x19, 0x9b, 0x94, 0xc4, 0xd6, 0x63, + 0xce, 0xf7, 0xfd, 0x54, 0x1b, 0x04, 0x24, 0xdb, 0xcb, 0x3d, 0xc3, 0xa7, 0x91, 0x59, 0x2a, 0x2c, + 0x1f, 0xeb, 0x0c, 0xed, 0x9b, 0x59, 0x91, 0x60, 0x26, 0x1a, 0x98, 0x2d, 0x99, 0xe1, 0x2e, 0xb8, + 0x9b, 0xe2, 0x43, 0x37, 0x45, 0x4e, 0x8a, 0x7d, 0x4c, 0x0e, 0x70, 0xea, 0xb8, 0x08, 0xa5, 0x98, + 0xb1, 0xee, 0x9c, 0x90, 0x56, 0xbf, 0x18, 0x6b, 0xaa, 0x9c, 0xf2, 0x9a, 0x42, 0xdd, 0xbe, 0x23, + 0x11, 0xbb, 0x04, 0x5e, 0x94, 0xfb, 0x5f, 0xea, 0xa0, 0xfd, 0x26, 0xc7, 0x69, 0xb1, 0x49, 0x63, + 0x44, 0x84, 0x4a, 0x2f, 0xc1, 0x32, 0xcf, 0x95, 0xf3, 0x81, 0x6f, 0x3b, 0x7c, 0x1e, 0x61, 0x6a, + 0x7b, 0xe3, 0x81, 0x71, 0x39, 0x77, 0x06, 0xb7, 0x5d, 0x34, 0xef, 0x14, 0x09, 0xb6, 0x97, 0xc2, + 0xd9, 0x25, 0x5c, 0x01, 0x2d, 0x84, 0x63, 0x1a, 0x49, 0xfb, 0x6d, 0xb9, 0xe0, 0x16, 0xdc, 0xdc, + 0xec, 0x2b, 0x0e, 0x5c, 0x67, 0xeb, 0x3b, 0xb0, 0x30, 0x89, 0xee, 0x0d, 0x7c, 0xbd, 0x5f, 0xb2, + 0x76, 0x24, 0xeb, 0xa4, 0x55, 0x1a, 0x3b, 0xa5, 0xd2, 0xbf, 0xd6, 0xc1, 0xd2, 0xdb, 0x22, 0xce, + 0xf6, 0x70, 0x46, 0x7c, 0x11, 0xf1, 0x35, 0x00, 0xf3, 0x18, 0xe1, 0x34, 0x2c, 0x48, 0x1c, 0x38, + 0x42, 0x25, 0x82, 0xca, 0xc8, 0x77, 0xa6, 0x08, 0xaf, 0xdd, 0x42, 0x50, 0x03, 0x8b, 0x8c, 0xb7, + 0x3b, 0xb3, 0x3a, 0x00, 0xb1, 0x35, 0xaa, 0xc4, 0x98, 0xe4, 0xb1, 0xf1, 0x8f, 0xf2, 0x38, 0x7b, + 0x9b, 0x9a, 0xff, 0xf3, 0x36, 0x3d, 0x7a, 0x0e, 0x96, 0x2e, 0x05, 0x00, 0xb6, 0x01, 0xb0, 0x8a, + 0x8a, 0xbb, 0x53, 0x83, 0x00, 0xcc, 0x59, 0x05, 0x1f, 0xaa, 0xa3, 0xf0, 0xf7, 0x57, 0x94, 0x97, + 0x77, 0xea, 0xbd, 0xe6, 0xa7, 0x6f, 0x6a, 0xcd, 0xda, 0x3e, 0x3e, 0x53, 0x95, 0x93, 0x33, 0x55, + 0xf9, 0x75, 0xa6, 0x2a, 0x47, 0xe7, 0x6a, 0xed, 0xe4, 0x5c, 0xad, 0xfd, 0x3c, 0x57, 0x6b, 0xbb, + 0x1b, 0x33, 0x17, 0xa4, 0x4c, 0xdc, 0x7a, 0xe8, 0x7a, 0xac, 0x5a, 0x98, 0x07, 0xc3, 0xa7, 0xe6, + 0xc7, 0xea, 0xbf, 0x28, 0x2e, 0x8c, 0x37, 0x27, 0x3e, 0xee, 0xc9, 0x9f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x27, 0xff, 0x3d, 0x4a, 0x36, 0x05, 0x00, 0x00, } func (m *PeriodLock) Marshal() (dAtA []byte, err error) { diff --git a/x/pool-incentives/keeper/grpc_query_test.go b/x/pool-incentives/keeper/grpc_query_test.go index e963c422d02..20e48898394 100644 --- a/x/pool-incentives/keeper/grpc_query_test.go +++ b/x/pool-incentives/keeper/grpc_query_test.go @@ -242,7 +242,7 @@ func (s *KeeperTestSuite) TestIncentivizedPools() { LockQueryType: lockuptypes.ByDuration, Denom: "stake", Duration: time.Hour, - }, time.Now(), 1) + }, time.Now(), 1, 0) s.Require().NoError(err) distRecords = append(distRecords, types.DistrRecord{GaugeId: gaugePerpetualId, Weight: sdk.NewInt(300)}) } @@ -252,7 +252,7 @@ func (s *KeeperTestSuite) TestIncentivizedPools() { LockQueryType: lockuptypes.ByDuration, Denom: "stake", Duration: time.Hour, - }, time.Now(), 1) + }, time.Now(), 1, 0) s.Require().NoError(err) distRecords = append(distRecords, types.DistrRecord{GaugeId: gaugeNonPerpetualId, Weight: sdk.NewInt(100)}) } @@ -394,7 +394,7 @@ func (s *KeeperTestSuite) TestExternalIncentiveGauges() { LockQueryType: lockuptypes.ByDuration, Denom: "stake", Duration: time.Hour, - }, time.Now(), 1) + }, time.Now(), 1, 0) s.Require().NoError(err) } } diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 0ce1db2067c..987b139c554 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -79,6 +79,7 @@ func (k Keeper) CreateLockablePoolGauges(ctx sdk.Context, poolId uint64) error { }, ctx.BlockTime(), 1, + 0, ) if err != nil { return err @@ -100,29 +101,26 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin return fmt.Errorf("pool %d is not concentrated liquidity pool", poolId) } - gaugeIdCL, err := k.incentivesKeeper.CreateGauge( + incentivesEpoch := k.incentivesKeeper.GetEpochInfo(ctx) + + _, err = k.incentivesKeeper.CreateGauge( ctx, true, k.accountKeeper.GetModuleAddress(types.ModuleName), sdk.Coins{}, - // dummy variable so that the existing logic does not break - // CreateGauge checks if LockQueryType is `ByDuration` or not, we bypass this check by passing - // lockQueryType as byTime. Although we do not need this check, we still cannot pass empty struct. lockuptypes.QueryCondition{ - LockQueryType: lockuptypes.ByTime, + LockQueryType: lockuptypes.NoLock, Denom: appParams.BaseCoinUnit, + Duration: incentivesEpoch.Duration, }, ctx.BlockTime(), 1, + poolId, ) if err != nil { return err } - incParams := k.incentivesKeeper.GetEpochInfo(ctx) - // lockable duration is epoch duration because we create incentive_record on every epoch - k.SetPoolGaugeId(ctx, poolId, incParams.Duration, gaugeIdCL) - return nil } diff --git a/x/pool-incentives/types/expected_keepers.go b/x/pool-incentives/types/expected_keepers.go index 626bd427feb..b6db0fb4878 100644 --- a/x/pool-incentives/types/expected_keepers.go +++ b/x/pool-incentives/types/expected_keepers.go @@ -32,7 +32,7 @@ type PoolManagerKeeper interface { // IncentivesKeeper creates and gets gauges, and also allows additions to gauge rewards. type IncentivesKeeper interface { - CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64) (uint64, error) + CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) GetGaugeByID(ctx sdk.Context, gaugeID uint64) (*incentivestypes.Gauge, error) GetGauges(ctx sdk.Context) []incentivestypes.Gauge GetParams(ctx sdk.Context) incentivestypes.Params diff --git a/x/superfluid/keeper/intermediary_account.go b/x/superfluid/keeper/intermediary_account.go index d38e414124f..0aaaa79eca8 100644 --- a/x/superfluid/keeper/intermediary_account.go +++ b/x/superfluid/keeper/intermediary_account.go @@ -78,7 +78,7 @@ func (k Keeper) GetOrCreateIntermediaryAccount(ctx sdk.Context, denom, valAddr s // move this synthetic denom creation to a dedicated function Denom: stakingSyntheticDenom(denom, valAddr), Duration: k.sk.GetParams(ctx).UnbondingTime, - }, ctx.BlockTime(), 1) + }, ctx.BlockTime(), 1, 0) if err != nil { k.Logger(ctx).Error(err.Error()) return types.SuperfluidIntermediaryAccount{}, err diff --git a/x/superfluid/types/expected_keepers.go b/x/superfluid/types/expected_keepers.go index ecf7bb76f4e..1a0275c7b5a 100644 --- a/x/superfluid/types/expected_keepers.go +++ b/x/superfluid/types/expected_keepers.go @@ -91,7 +91,7 @@ type CommunityPoolKeeper interface { // IncentivesKeeper expected incentives keeper. type IncentivesKeeper interface { - CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64) (uint64, error) + CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) AddToGaugeRewards(ctx sdk.Context, owner sdk.AccAddress, coins sdk.Coins, gaugeID uint64) error GetActiveGauges(ctx sdk.Context) []incentivestypes.Gauge From efa9a2d3f581b860438571e736aa16ce3a349cd2 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:17:22 +0000 Subject: [PATCH 03/50] updates --- x/incentives/keeper/gauge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index b8d8ba3959e..1cdf09814b7 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -120,7 +120,7 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr return 0, fmt.Errorf("no lock gauges must have a pool id") } if distrTo.Denom != "" { - return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}") + return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}, was %s", &distrTo.Denom) } pool, err := k.pmk.GetPool(ctx, poolId) From 5adbfa400bc4c361bb1fba2497a9375ca92836ec Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:18:36 +0000 Subject: [PATCH 04/50] updates --- x/incentives/keeper/gauge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 1cdf09814b7..0798c5c7c39 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -120,7 +120,7 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr return 0, fmt.Errorf("no lock gauges must have a pool id") } if distrTo.Denom != "" { - return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}, was %s", &distrTo.Denom) + return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}, was %s", distrTo.Denom) } pool, err := k.pmk.GetPool(ctx, poolId) From 64255071357f4e546e5ea894160f752f5ad47a27 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:26:07 +0000 Subject: [PATCH 05/50] updates --- x/incentives/keeper/gauge.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 0798c5c7c39..a0a6a8e9330 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -96,6 +96,8 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { // CreateGauge creates a gauge and sends coins to the gauge. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { + ctx.Logger().Error(distrTo.Denom) + // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) if distrTo.LockQueryType == lockuptypes.ByDuration { From 95cf1466636fbad3a82fb90e8f2005bd2b3e612f Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:28:40 +0000 Subject: [PATCH 06/50] updates --- x/incentives/keeper/msg_server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index 76bdf9945c2..6598dab7bb4 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -29,6 +29,7 @@ var _ types.MsgServer = msgServer{} // Emits create gauge event and returns the create gauge response. func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateGauge) (*types.MsgCreateGaugeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + ctx.Logger().Info("CreateGauge", "msg", msg) owner, err := sdk.AccAddressFromBech32(msg.Owner) if err != nil { return nil, err From 5eb5ce2ad851ed91e9b893b3fcd902bab3111dcf Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:30:31 +0000 Subject: [PATCH 07/50] updates --- x/incentives/keeper/msg_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index 6598dab7bb4..c4c10406d8a 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -29,7 +29,7 @@ var _ types.MsgServer = msgServer{} // Emits create gauge event and returns the create gauge response. func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateGauge) (*types.MsgCreateGaugeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - ctx.Logger().Info("CreateGauge", "msg", msg) + ctx.Logger().Error("CreateGauge", msg) owner, err := sdk.AccAddressFromBech32(msg.Owner) if err != nil { return nil, err From ec2d50ae0614f0e68212be32a899509fc42e942b Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:32:00 +0000 Subject: [PATCH 08/50] updates --- x/incentives/types/msgs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 099878d26c0..d500ca5c5ee 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -2,6 +2,7 @@ package types import ( "errors" + "fmt" "time" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" @@ -72,6 +73,8 @@ func (m MsgCreateGauge) ValidateBasic() error { return errors.New("no lock gauge denom should be unset. It will be automatically set to the no-lock/{pool-id} format") } + fmt.Println("m.DistributeTo.Denom", m.DistributeTo.Denom) + return nil } From b758cd4926f771f3d86cb7ddc1152d385faea1a8 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:36:05 +0000 Subject: [PATCH 09/50] updates --- x/incentives/keeper/gauge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index a0a6a8e9330..bcc414f5285 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -96,7 +96,7 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { // CreateGauge creates a gauge and sends coins to the gauge. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { - ctx.Logger().Error(distrTo.Denom) + ctx.Logger().Error("create gauge", distrTo.Denom) // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) From 03c59df01e9a515358633a569d3f1e1828bb2a87 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 18:39:34 +0000 Subject: [PATCH 10/50] updates --- x/incentives/keeper/gauge.go | 2 +- x/incentives/keeper/msg_server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index bcc414f5285..c4ea8d46fc9 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -96,7 +96,7 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { // CreateGauge creates a gauge and sends coins to the gauge. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { - ctx.Logger().Error("create gauge", distrTo.Denom) + ctx.Logger().Error("create gauge", "gauge", distrTo.Denom) // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index c4c10406d8a..e4a45b0ab83 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -29,7 +29,7 @@ var _ types.MsgServer = msgServer{} // Emits create gauge event and returns the create gauge response. func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateGauge) (*types.MsgCreateGaugeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - ctx.Logger().Error("CreateGauge", msg) + ctx.Logger().Error("CreateGauge", "msg", msg) owner, err := sdk.AccAddressFromBech32(msg.Owner) if err != nil { return nil, err From ca3ca48d52702d6b7f7d4270a5107d26b450cf1d Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:01:19 +0000 Subject: [PATCH 11/50] updates --- x/incentives/keeper/gauge.go | 12 +++++- x/incentives/keeper/gauge_test.go | 59 ++++++++++++++++++++++++------ x/incentives/keeper/msg_server.go | 1 - x/incentives/types/keys.go | 10 ++++- x/incentives/types/msgs.go | 3 -- x/incentives/types/msgs_test.go | 33 ++++++++++++++++- x/pool-incentives/keeper/keeper.go | 6 +-- 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index c4ea8d46fc9..f683d19fe05 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -121,8 +121,16 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr if poolId == 0 { return 0, fmt.Errorf("no lock gauges must have a pool id") } - if distrTo.Denom != "" { - return 0, fmt.Errorf("no lock gauges must not have a denom. It will be automatically set to no-lock/{pool-id}, was %s", distrTo.Denom) + + // If not internal gauge denom, then must be set to "" + // and get overwritten with the external prefix + pool id + // for internal query purposes. + if distrTo.Denom != types.NoLockInternalGaugeDenom(poolId) { + // If denom is set, then fails. + if distrTo.Denom != "" { + return 0, fmt.Errorf("no lock external gauges must have an empty denom set") + } + distrTo.Denom = types.NoLockExternalGaugeDenom(poolId) } pool, err := k.pmk.GetPool(ctx, poolId) diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index 26833ca823f..a2afb55a0f1 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -431,13 +431,16 @@ func (s *KeeperTestSuite) TestAddToGaugeRewards() { // specifically focusing on the no lock gauge type and test cases around it. // It tests the following: // - For no lock gauges, a CL pool id must be given as well and then pool must exist +// - For no lock gauges, the denom must be set either to NoLockExternalGaugeDenom() +// or be unset. If not unset, fails with error. Also, assumes that the gauge was created externally +// if unset and overwrites the denom to NoLockExternalGaugeDenom() // - Otherwise, the given pool id must be zero. Errors if not. func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { const ( - zeroPoolId = uint64(0) - balancerPool = uint64(1) - concentratedPool = uint64(2) - invalidPool = uint64(3) + zeroPoolId = uint64(0) + balancerPoolId = uint64(1) + concentratedPoolId = uint64(2) + invalidPool = uint64(3) // 3 are created for balancer pool and 1 for CL. // As a result, the next gauge id should be 5. defaultExpectedGaugeId = uint64(5) @@ -460,25 +463,53 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { distrTo lockuptypes.QueryCondition poolId uint64 - expectedGaugeId uint64 - expectErr bool + expectedGaugeId uint64 + expectedDenomSet string + expectErr bool }{ { - name: "create valid no lock gauge with CL pool", + name: "create valid no lock gauge with CL pool (no denom set)", distrTo: lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, + // Note: this assumes the gauge is external + Denom: "", }, - poolId: concentratedPool, + poolId: concentratedPoolId, - expectedGaugeId: defaultExpectedGaugeId, - expectErr: false, + expectedGaugeId: defaultExpectedGaugeId, + expectedDenomSet: types.NoLockExternalGaugeDenom(concentratedPoolId), + expectErr: false, + }, + { + name: "create valid no lock gauge with CL pool (denom set to uosmo)", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + // Note: this assumes the gauge is internal + Denom: types.NoLockInternalGaugeDenom(concentratedPoolId), + }, + poolId: concentratedPoolId, + + expectedGaugeId: defaultExpectedGaugeId, + expectedDenomSet: types.NoLockInternalGaugeDenom(concentratedPoolId), + expectErr: false, + }, + { + name: "fail to create gauge because invalid denom is set", + distrTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + // Note: this is invalid for NoLock gauges + Denom: "uosmo", + }, + poolId: concentratedPoolId, + + expectErr: true, }, { name: "fail to create no lock gauge with balancer pool", distrTo: lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, }, - poolId: balancerPool, + poolId: balancerPoolId, expectErr: true, }, @@ -528,6 +559,12 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { s.Require().NoError(err) s.Require().Equal(tc.expectedGaugeId, gaugeId) + + // Get gage and check that the denom is set correctly + gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, tc.expectedGaugeId) + s.Require().NoError(err) + + s.Require().Equal(tc.expectedDenomSet, gauge.DistributeTo.Denom) } }) } diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index e4a45b0ab83..76bdf9945c2 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -29,7 +29,6 @@ var _ types.MsgServer = msgServer{} // Emits create gauge event and returns the create gauge response. func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateGauge) (*types.MsgCreateGaugeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - ctx.Logger().Error("CreateGauge", "msg", msg) owner, err := sdk.AccAddressFromBech32(msg.Owner) if err != nil { return nil, err diff --git a/x/incentives/types/keys.go b/x/incentives/types/keys.go index 071189f4379..5b1bacd4420 100644 --- a/x/incentives/types/keys.go +++ b/x/incentives/types/keys.go @@ -53,6 +53,12 @@ func KeyPrefix(p string) []byte { return []byte(p) } -func NoLockGaugePrefix(poolId uint64) []byte { - return []byte(fmt.Sprintf("no-lock/%d", poolId)) +// NoLockExternalGaugeDenom returns the gauge denom for the no-lock external gauge for the given pool ID. +func NoLockExternalGaugeDenom(poolId uint64) string { + return fmt.Sprintf("no-lock/e/%d", poolId) +} + +// NoLockInternalGaugeDenom returns the gauge denom for the no-lock internal gauge for the given pool ID. +func NoLockInternalGaugeDenom(poolId uint64) string { + return fmt.Sprintf("no-lock/i/%d", poolId) } diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index d500ca5c5ee..099878d26c0 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -2,7 +2,6 @@ package types import ( "errors" - "fmt" "time" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" @@ -73,8 +72,6 @@ func (m MsgCreateGauge) ValidateBasic() error { return errors.New("no lock gauge denom should be unset. It will be automatically set to the no-lock/{pool-id} format") } - fmt.Println("m.DistributeTo.Denom", m.DistributeTo.Denom) - return nil } diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index 8afee23b9e7..8856d1e2fd5 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/v15/x/incentives/types" incentivestypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" "github.com/osmosis-labs/osmosis/v16/app/apptesting" @@ -166,7 +167,7 @@ func TestMsgCreateGauge(t *testing.T) { expectPass: false, }, { - name: "valid no lick with pool id unset", + name: "valid no lock with pool id unset", msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { msg.DistributeTo.LockQueryType = lockuptypes.NoLock msg.PoolId = 1 @@ -174,6 +175,36 @@ func TestMsgCreateGauge(t *testing.T) { }), expectPass: true, }, + { + name: "invalid due to denom being set", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + msg.DistributeTo.Denom = "stake" + return msg + }), + expectPass: false, + }, + { + name: "invalid due to external denom being set", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + // This is set by the system. Client should provide empty string. + msg.DistributeTo.Denom = types.NoLockExternalGaugeDenom(1) + return msg + }), + expectPass: false, + }, + { + name: "invalid due to internal denom being set", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + // This is set by the system when creating internal gauges. + // Client should provide empty string. + msg.DistributeTo.Denom = types.NoLockInternalGaugeDenom(1) + return msg + }), + expectPass: false, + }, } for _, test := range tests { diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 987b139c554..cc580e7439f 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -15,7 +15,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - appParams "github.com/osmosis-labs/osmosis/v16/app/params" poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" ) @@ -101,8 +100,6 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin return fmt.Errorf("pool %d is not concentrated liquidity pool", poolId) } - incentivesEpoch := k.incentivesKeeper.GetEpochInfo(ctx) - _, err = k.incentivesKeeper.CreateGauge( ctx, true, @@ -110,8 +107,7 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin sdk.Coins{}, lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, - Denom: appParams.BaseCoinUnit, - Duration: incentivesEpoch.Duration, + Denom: incentivestypes.NoLockInternalGaugeDenom(pool.GetId()), }, ctx.BlockTime(), 1, From 15a575f240900ba4fcda170e362c10ffbf3984c7 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:07:34 +0000 Subject: [PATCH 12/50] updates --- x/incentives/types/msgs.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 099878d26c0..471bb2bfd4c 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -36,10 +36,13 @@ func (m MsgCreateGauge) Type() string { return TypeMsgCreateGauge } // ValidateBasic checks that the create gauge message is valid. func (m MsgCreateGauge) ValidateBasic() error { + lockType := m.DistributeTo.LockQueryType + if m.Owner == "" { return errors.New("owner should be set") } - if sdk.ValidateDenom(m.DistributeTo.Denom) != nil { + // For no lock type, the denom must be empty and we check that down below. + if lockType != lockuptypes.NoLock && sdk.ValidateDenom(m.DistributeTo.Denom) != nil { return errors.New("denom should be valid for the condition") } if lockuptypes.LockQueryType_name[int32(m.DistributeTo.LockQueryType)] == "" { @@ -55,7 +58,6 @@ func (m MsgCreateGauge) ValidateBasic() error { return errors.New("distribution period should be 1 epoch for perpetual gauge") } - lockType := m.DistributeTo.LockQueryType if lockType == lockuptypes.ByTime { return errors.New("start time distr conditions is an obsolete codepath slated for deletion") } From 68e42a61aeaaf70feff0c76b06b40f6be0b83a96 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:08:43 +0000 Subject: [PATCH 13/50] updates --- x/incentives/keeper/gauge.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index f683d19fe05..8be2c03fde5 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -96,8 +96,6 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { // CreateGauge creates a gauge and sends coins to the gauge. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { - ctx.Logger().Error("create gauge", "gauge", distrTo.Denom) - // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) if distrTo.LockQueryType == lockuptypes.ByDuration { From d0174831c1f0fcd4321519435797ff769ccb79e6 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:14:48 +0000 Subject: [PATCH 14/50] updates --- go.mod | 2 +- proto/osmosis/incentives/tx.proto | 6 ++++-- x/incentives/types/msgs_test.go | 2 +- x/incentives/types/tx.pb.go | 5 ++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 62b499354f3..8aee803c008 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 github.com/CosmWasm/wasmd v0.31.0 github.com/cosmos/cosmos-proto v1.0.0-beta.2 - github.com/cosmos/cosmos-sdk v0.47.2 + github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230524151648-c02fa46c2860 github.com/cosmos/ibc-go/v4 v4.3.1 diff --git a/proto/osmosis/incentives/tx.proto b/proto/osmosis/incentives/tx.proto index e24f1cea73f..b7d6c520430 100644 --- a/proto/osmosis/incentives/tx.proto +++ b/proto/osmosis/incentives/tx.proto @@ -48,8 +48,10 @@ message MsgCreateGauge { // pool_id is the ID of the pool that the gauge is meant to be associated // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be - // "NoLock" with all other fields unset. - // The denom query condition is set to "no-lock/{pool_id}" prefix in such a case. + // "NoLock" with all other fields unset, including "QueryCondition.Denom". + // However, note that, internally, the empty string ends up being overwritten + // with incentivestypes.NoLockExternalGaugeDenom() so that the + // gauges associated with a pool can be queried by this prefix if needed. uint64 pool_id = 7; } message MsgCreateGaugeResponse {} diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index 8856d1e2fd5..5d9e5eeaf03 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -9,7 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/osmosis-labs/osmosis/v15/x/incentives/types" + "github.com/osmosis-labs/osmosis/v16/x/incentives/types" incentivestypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" "github.com/osmosis-labs/osmosis/v16/app/apptesting" diff --git a/x/incentives/types/tx.pb.go b/x/incentives/types/tx.pb.go index d5cd5818639..d1036e24aa2 100644 --- a/x/incentives/types/tx.pb.go +++ b/x/incentives/types/tx.pb.go @@ -58,7 +58,10 @@ type MsgCreateGauge struct { NumEpochsPaidOver uint64 `protobuf:"varint,6,opt,name=num_epochs_paid_over,json=numEpochsPaidOver,proto3" json:"num_epochs_paid_over,omitempty"` // pool_id is the ID of the pool that the gauge is meant to be associated // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be - // "NoLock" with all other fields unset. + // "NoLock" with all other fields unset, including "QueryCondition.Denom". + // However, note that, internally, the empty string ends up being overwritten + // with incentivestypes.NoLockExternalGaugeDenom() so that the + // gauges associated with a pool can be queried by this prefix if needed. PoolId uint64 `protobuf:"varint,7,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` } From 7f4a4e2fdf46189676ced3ca11f29e4935daf1b4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 8 Jun 2023 15:16:59 -0400 Subject: [PATCH 15/50] Apply suggestions from code review --- x/incentives/types/msgs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index 5d9e5eeaf03..f5dd7492071 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -18,7 +18,7 @@ import ( lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" ) -// TestMsgCreateGauge tests if valid/invalid create pool messages are properly validated/invalidated +// TestMsgCreateGauge tests if valid/invalid create gauge messages are properly validated/invalidated func TestMsgCreateGauge(t *testing.T) { // generate a private/public key pair and get the respective address pk1 := ed25519.GenPrivKey().PubKey() From 3832b0e82e486d99e443d4f0b9f7e7c271241ee9 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:19:29 +0000 Subject: [PATCH 16/50] updates --- x/incentives/types/msgs.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 471bb2bfd4c..4e8e99bd624 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -71,7 +71,8 @@ func (m MsgCreateGauge) ValidateBasic() error { } if lockType == lockuptypes.NoLock && m.DistributeTo.Denom != "" { - return errors.New("no lock gauge denom should be unset. It will be automatically set to the no-lock/{pool-id} format") + return errors.New(`no lock gauge denom should be unset. It will be automatically set to the NoLockExternalGaugeDenom() + format internally, allowing for querying the gauges by denom with this prefix`) } return nil From 4ced1494dfeddb1bab1efe26c4aaaf5e268ceb78 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:23:00 +0000 Subject: [PATCH 17/50] updates --- x/incentives/keeper/distribute_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index eb871f34394..d60fb5a817e 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -165,7 +165,7 @@ func (s *KeeperTestSuite) TestDistribute() { } } -func (s *KeeperTestSuite) TestDistribute_InternalIncentives_ConcentratedPool() { +func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { defaultGauge := perpGaugeDesc{ lockDenom: defaultLPDenom, lockDuration: defaultLockDuration, From c84b0d1f9409fedda149db7b120fcbcab06bd85d Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:29:08 +0000 Subject: [PATCH 18/50] more assertions in TestCreateGauge_NoLockGauges --- x/incentives/keeper/gauge_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index a2afb55a0f1..f0cc0e43a6b 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -430,10 +430,11 @@ func (s *KeeperTestSuite) TestAddToGaugeRewards() { // TestCreateGauge_NoLockGauges tests the CreateGauge function // specifically focusing on the no lock gauge type and test cases around it. // It tests the following: -// - For no lock gauges, a CL pool id must be given as well and then pool must exist -// - For no lock gauges, the denom must be set either to NoLockExternalGaugeDenom() -// or be unset. If not unset, fails with error. Also, assumes that the gauge was created externally -// if unset and overwrites the denom to NoLockExternalGaugeDenom() +// - For no lock gauges, a CL pool id must be given and then pool must exist +// - For no lock gauges, the denom must be set either to NoLockInternalGaugeDenom() +// or be unset. If set to anything other than the internal prefix, fails with error. +// Assumes that the gauge was created externally (via MsgCreateGauge) if the denom is unset and overwrites it +// with NoLockExternalGaugeDenom() // - Otherwise, the given pool id must be zero. Errors if not. func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { const ( @@ -565,6 +566,11 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { s.Require().NoError(err) s.Require().Equal(tc.expectedDenomSet, gauge.DistributeTo.Denom) + s.Require().Equal(tc.distrTo.LockQueryType, gauge.DistributeTo.LockQueryType) + s.Require().Equal(defaultIsPerpetualParam, gauge.IsPerpetual) + s.Require().Equal(defaultCoins, gauge.Coins) + s.Require().Equal(defaultTime.UTC(), gauge.StartTime.UTC()) + s.Require().Equal(defaultNumEpochPaidOver, gauge.NumEpochsPaidOver) } }) } From 54bd0b5b9711da021af5f4aeacde27632f0d9bff Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:36:27 +0000 Subject: [PATCH 19/50] add comments to CreateGauge --- x/incentives/keeper/gauge.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 8be2c03fde5..5e4edb6a822 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -94,7 +94,19 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { } } -// CreateGauge creates a gauge and sends coins to the gauge. +// CreateGauge creates a gauge with the given parameters and sends coins to the gauge. +// There can be 2 kinds of gauges for a given set of paramters: +// * lockuptypes.ByDuration - a gauge that incentivizes one of the lockable durations. +// For this gauge, the pool id must be 0. Fails if not. +// +// * lockuptypes.NoLock - a gauge that incentivizes pools without locking. Initially, +// this is meant specifically for the concentrated liquidity pools. As a result, +// if NoLock gauge is being created, the given pool id must be non-zero, the pool +// at this id must exist and be of a concentrated liquidity type. Fails if not. +// Additionally, lockuptypes.Denom must be either an empty string, signifying that +// this is an external gauge, or be equal to types.NoLockInternalGaugeDenom(poolId). +// If the denom is empty, it will get overwritten to types.NoLockExternalGaugeDenom(poolId). +// This denom formatting is useful for querying internal vs externa gauges associated with a pool. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) From 17dff348f6b99704e43fdf7d23a05533f1123126 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:51:08 +0000 Subject: [PATCH 20/50] update comments for Distribute --- x/incentives/keeper/distribute.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/x/incentives/keeper/distribute.go b/x/incentives/keeper/distribute.go index 67b3a779eb3..832f23240c8 100644 --- a/x/incentives/keeper/distribute.go +++ b/x/incentives/keeper/distribute.go @@ -268,7 +268,17 @@ func (k Keeper) distributeSyntheticInternal( // distributeInternal runs the distribution logic for a gauge, and adds the sends to // the distrInfo struct. It also updates the gauge for the distribution. -// Locks is expected to be the correct set of lock recipients for this gauge. +// It handles any kind of gauges: +// - distributing to locks +// - Locks is expected to be the correct set of lock recipients for this gauge. +// - perpetual +// - non-perpetual +// +// - distributing to pools +// - perpetual +// - non-perpetual +// +// CONTRACT: gauge must be active func (k Keeper) distributeInternal( ctx sdk.Context, gauge types.Gauge, locks []lockuptypes.PeriodLock, distrInfo *distributionInfo, ) (sdk.Coins, error) { @@ -300,15 +310,17 @@ func (k Keeper) distributeInternal( // Get distribution epoch duration. This is used to calculate the emission rate. currentEpoch := k.GetEpochInfo(ctx) - // only want to run this logic if the gaugeId is associated with CL PoolId + // For every coin in the gauge, calculate the remaining reward per epoch + // and create a concentrated liquidity incentive record for it that + // is supposed to distribute over that epoch. for _, remainCoin := range remainCoins { + // remaining coin amount per epoch. remainAmountPerEpoch := remainCoin.Amount.Quo(sdk.NewIntFromUint64(remainEpochs)) remainCoinPerEpoch := sdk.NewCoin(remainCoin.Denom, remainAmountPerEpoch) // emissionRate calculates amount of tokens to emit per second - // for ex: 10000tokens to be distributed over 1day epoch will be 1000 tokens ÷ 86,400 seconds ≈ 0.01157 tokens per second (truncated) - // Note: reason why we do millisecond conversion is because floats are non-deterministic so if someone refactors this and accidentally - // uses the return of currEpoch.Duration.Seconds() in math operations, this will lead to an app hash. + // for ex: 10000uosmo to be distributed over 1day epoch will be 1000 tokens ÷ 86,400 seconds ≈ 0.01157 tokens per second (truncated) + // Note: reason why we do millisecond conversion is because floats are non-deterministic. emissionRate := sdk.NewDecFromInt(remainAmountPerEpoch).QuoTruncate(sdk.NewDec(currentEpoch.Duration.Milliseconds()).QuoInt(sdk.NewInt(1000))) _, err := k.clk.CreateIncentive(ctx, @@ -316,9 +328,11 @@ func (k Keeper) distributeInternal( k.ak.GetModuleAddress(types.ModuleName), remainCoinPerEpoch, emissionRate, + // Use current block time as start time, NOT the gauge start time. + // Gauge start time should be checked whenever moving between active + // and inactive gauges. By the time we get here, the gauge should be active. ctx.BlockTime(), - // Note that the minimum uptime does not affect the distribution of incentives from the gauge and - // thus can be any value authorized by the CL module. + // Only default uptime is supported at launch. types.DefaultConcentratedUptime, ) if err != nil { @@ -401,7 +415,8 @@ func (k Keeper) getDistributeToBaseLocks(ctx sdk.Context, gauge types.Gauge, cac return FilterLocksByMinDuration(allLocks, gauge.DistributeTo.Duration) } -// Distribute distributes coins from an array of gauges to all eligible locks. +// Distribute distributes coins from an array of gauges to all eligible locks and pools in the case of "NoLock" gauges. +// CONTRACT: gauges must be active. func (k Keeper) Distribute(ctx sdk.Context, gauges []types.Gauge) (sdk.Coins, error) { distrInfo := newDistributionInfo() From 9704f9c34469672e55e09aaf5e9e38cac5418071 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:55:55 +0000 Subject: [PATCH 21/50] updates --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f3df11efe2..6658df1ab11 100644 --- a/Makefile +++ b/Makefile @@ -265,7 +265,7 @@ test: test-unit test-build test-all: test-race test-cover test-unit: - @VERSION=$(VERSION) SKIP_WASM_WSL_TESTS=$(SKIP_WASM_WSL_TESTS) go test -mod=readonly -tags='ledger test_ledger_mock norace' $(PACKAGES_UNIT) -count=1 + @VERSION=$(VERSION) SKIP_WASM_WSL_TESTS=$(SKIP_WASM_WSL_TESTS) go test -mod=readonly -tags='ledger test_ledger_mock norace' $(PACKAGES_UNIT) test-race: @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' $(PACKAGES_UNIT) From cc6ee0f2080c5553bcab69700aff7c42ea88c165 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:56:35 +0000 Subject: [PATCH 22/50] revert --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d4bafe7501..66fabebba47 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -129,12 +129,12 @@ "type": "go", "request": "launch", "mode": "test", - "program": "${workspaceFolder}/x/incentives/keeper", + "program": "${workspaceFolder}/x/incentives", "args": [ "-test.timeout", "30m", "-test.run", - "TestKeeperTestSuite/TestDistribute_ExternalIncentives_ConcentratedPool", + "TestKeeperTestSuite/TestYourName", "-test.v" ], }, From eef881fd15ba90f1141ce6163f80036bcb0a49bf Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 19:59:03 +0000 Subject: [PATCH 23/50] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcb77f7efe..3a770b1c629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ and control over token transfers. * [#4682](https://github.com/osmosis-labs/osmosis/pull/4682) feat(CL): x/poolmanager spot price query for concentrated liquidity * [#5138](https://github.com/osmosis-labs/osmosis/pull/5138) refactor: rename swap fee to spread factor * [#5020](https://github.com/osmosis-labs/osmosis/pull/5020) Add gas config to the client.toml + * [#5459](https://github.com/osmosis-labs/osmosis/pull/5459) Create locktypes.LockQueryType.NoLock gauge. MsgCreateGauge takes pool id for new gauge type. ## State Breaking * [#5380](https://github.com/osmosis-labs/osmosis/pull/5380) feat: add ica authorized messages in upgrade handler From be45979484803a3e493479a0bc709790dabaf13f Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:07:33 +0000 Subject: [PATCH 24/50] add incentives module docs --- x/incentives/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/x/incentives/README.md b/x/incentives/README.md index 42a036480df..1c5ec19ccda 100644 --- a/x/incentives/README.md +++ b/x/incentives/README.md @@ -42,6 +42,26 @@ There are two kinds of gauges: **`perpetual`** and **`non-perpetual`**: - **`Perpetual gauges`** distribute all their tokens at a single time and only distribute their tokens again once the gauge is refilled (this is mainly used to distribute minted OSMO tokens to LP token stakers). Perpetual gauges persist and will re-disburse tokens when refilled (there is no "active" period) +Besides the perpetual and non-perpetual categorization, gauges can also be grouped across another dimension - `ByDuration` or `NoLock`. +This is set on the `DistrTo.LockQueryType` field of the `MsgCreateGauge`. + +- **ByDuration** when the gauge of this kind is created, it is meant to incentivize locks. When it is set, +the `PoolId` field of the `MsgCreateGauge` must be zero. + +- **NoLock** when the gauge of this kind is created, it is meant to incentivize pools directly. When it is set, +the `PoolId` field of the `MsgCreateGauge` must be non-zero. Additionally, it must be associated with a CL +pool at launch. Moreover, the `DistrTo.Denom` field must be set to empty string in such a case. + +Each of the `ByDuration` and `NoLock` gauges can be perpetual or non-perpetual and function according to the +conventional rules of the respective gauge type. + + +Additionally, for `NoLock` gauges, lockuptypes.Denom must be either an empty string, signifying that +this is an external gauge, or be equal to types.NoLockInternalGaugeDenom(poolId) (when created from the `AfterPoolCreatedHook`) +If the denom is empty, it will get overwritten to types.NoLockExternalGaugeDenom(poolId). +This denom formatting is useful for querying internal vs externa gauges associated with a pool since the denom prefix is +appended into the store prefix. + ## State ### Incentives management @@ -135,6 +155,7 @@ type MsgCreateGauge struct { Rewards sdk.Coins StartTime time.Time // start time to start distribution NumEpochsPaidOver uint64 // number of epochs distribution will be done + PoolID uint64 // pool id of the gauge. This should only be non-zero if DistributeTo.LockQueryType is NoLock } ``` From 6a91d9ea1dc0c91d06d8b33f7f26364d7f671179 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:09:42 +0000 Subject: [PATCH 25/50] fix test names --- x/incentives/keeper/distribute_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index d60fb5a817e..0ac9782de2f 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -357,12 +357,12 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { } } -// TestDistribute_ExternalIncentives_ConcentratedPool tests the distribution of external incentives -// to concentrated liquidity pools. It creates an external gauge with the correct configuration +// TestDistribute_ExternalIncentives_NoLock tests the distribution of externally +// created NoLock gauges. It creates an external gauge with the correct configuration // and uses it to attempt to distribute tokens to a concentrated liquidity pool. // It attempts to distribute with all possible gauge configurations and with various tokens. // However, it does not test distribution of NoLock gauges. -func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_ConcentratedPool() { +func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { const ( defaultCLPool = uint64(1) defaultBalancerPool = uint64(2) From be800fd718a22f801422ec310c3c5aafcd64ebec Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:19:01 +0000 Subject: [PATCH 26/50] CL module README --- x/concentrated-liquidity/README.md | 12 ++++++++++++ x/incentives/keeper/gauge.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x/concentrated-liquidity/README.md b/x/concentrated-liquidity/README.md index 88ab2fc4e28..f55322a790a 100644 --- a/x/concentrated-liquidity/README.md +++ b/x/concentrated-liquidity/README.md @@ -1413,6 +1413,18 @@ that has been in the pool for the required amount of time qualifies for claiming While it is technically possible for Osmosis to enable the creation of incentive records directly in the CL module, incentive creation is currently funneled through existing gauge infrastructure in the `x/incentives` module. This simplifies UX drastically for frontends, external incentive creators, and governance, while making CL incentives fully backwards-compatible with incentive creation and querying flows that everyone is already used to. As of the initial version of Osmosis's CL, all incentive creation and querying logic will be handled by respective gauge functions (e.g. the `IncentivizedPools` query in the `x/incentives` module will include CL pools that have internal incentives on them). +To create a gauge dedicated to the concentrated liquidity pool, run a `MsgCreateGauge` message in the `x/incentives` module with the following parameters: +- `PoolId`: The ID of the CL pool to create a gauge for. +- `DistrTo.LockQueryType` must be set to `locktypes.LockQueryType.NoLock` +- `DistrTo.Denom` must be an empty string. + +The rest of the parameters can be set according to the desired configuration of the gauge. Please read the `x/incentives` module documentation for more information on how to configure gauges. + +Note, that the created gauge will start emitting at the first epoch after the given `StartTime`. During the epoch, a `x/concentrated-liquidity` +module `IncentiveRecord` will be created for every denom in the gauge. This incentive record will be condifured to emit all given incentives +over the period of an epoch. If the gauge is non-perpetual (emits over several epochs), the distribution will be split evenly between the epochs. +and a new `IncentiveRecord` will be created for each denom every epoch with the emission rate and token set to finish emitting at the end of the epoch. + ### Reward Splitting Between Classic and CL pools While we want to nudge Classic pool LPs to transition to CL pools, we also want to ensure that we do not have a hard cutoff for incentives where past a certain point it is no longer worth it to provide liquidity to Classic pools. This is because we want to ensure that we have a healthy transition period where liquidity is not split between Classic and CL pools, but rather that liquidity is added to CL pools while Classic pools are slowly drained of liquidity. diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 5e4edb6a822..ce7e1768c86 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -95,7 +95,7 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { } // CreateGauge creates a gauge with the given parameters and sends coins to the gauge. -// There can be 2 kinds of gauges for a given set of paramters: +// There can be 2 kinds of gauges for a given set of parameters: // * lockuptypes.ByDuration - a gauge that incentivizes one of the lockable durations. // For this gauge, the pool id must be 0. Fails if not. // From db9e71200f32ea2e5d0f573a82d1664aad31f167 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:25:51 +0000 Subject: [PATCH 27/50] updates --- x/pool-incentives/keeper/keeper.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index cc580e7439f..570fe4af681 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -100,6 +100,8 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin return fmt.Errorf("pool %d is not concentrated liquidity pool", poolId) } + incentivesEpoch := k.incentivesKeeper.GetEpochInfo(ctx) + _, err = k.incentivesKeeper.CreateGauge( ctx, true, @@ -108,6 +110,7 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, Denom: incentivestypes.NoLockInternalGaugeDenom(pool.GetId()), + Duration: incentivesEpoch.Duration, }, ctx.BlockTime(), 1, From cc8e484debf1dd35effd88406385afca5727f84e Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:28:30 +0000 Subject: [PATCH 28/50] typo --- x/concentrated-liquidity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/concentrated-liquidity/README.md b/x/concentrated-liquidity/README.md index f55322a790a..e9e148ada8b 100644 --- a/x/concentrated-liquidity/README.md +++ b/x/concentrated-liquidity/README.md @@ -1421,7 +1421,7 @@ To create a gauge dedicated to the concentrated liquidity pool, run a `MsgCreate The rest of the parameters can be set according to the desired configuration of the gauge. Please read the `x/incentives` module documentation for more information on how to configure gauges. Note, that the created gauge will start emitting at the first epoch after the given `StartTime`. During the epoch, a `x/concentrated-liquidity` -module `IncentiveRecord` will be created for every denom in the gauge. This incentive record will be condifured to emit all given incentives +module `IncentiveRecord` will be created for every denom in the gauge. This incentive record will be configured to emit all given incentives over the period of an epoch. If the gauge is non-perpetual (emits over several epochs), the distribution will be split evenly between the epochs. and a new `IncentiveRecord` will be created for each denom every epoch with the emission rate and token set to finish emitting at the end of the epoch. From 6f08fbda20fe41862afb59b2e2d331b814384bdb Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:32:09 +0000 Subject: [PATCH 29/50] fix test --- x/pool-incentives/keeper/keeper_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x/pool-incentives/keeper/keeper_test.go b/x/pool-incentives/keeper/keeper_test.go index 015ee9edd06..4771cfab42e 100644 --- a/x/pool-incentives/keeper/keeper_test.go +++ b/x/pool-incentives/keeper/keeper_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/v16/app/apptesting" - appParams "github.com/osmosis-labs/osmosis/v16/app/params" gammtypes "github.com/osmosis-labs/osmosis/v16/x/gamm/types" incentivestypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" "github.com/osmosis-labs/osmosis/v16/x/pool-incentives/types" @@ -220,7 +219,7 @@ func (s *KeeperTestSuite) TestCreateConcentratedLiquidityPoolGauge() { s.Require().True(gaugeInfo.IsPerpetual) s.Require().Empty(gaugeInfo.Coins) s.Require().Equal(s.Ctx.BlockTime(), gaugeInfo.StartTime) - s.Require().Equal(appParams.BaseCoinUnit, gaugeInfo.DistributeTo.Denom) + s.Require().Equal(incentivestypes.NoLockInternalGaugeDenom(tc.poolId), gaugeInfo.DistributeTo.Denom) s.Require().Equal(uint64(1), gaugeInfo.NumEpochsPaidOver) } }) From 55bfe545f38bc8590e7fa63f68850dd2a3206f59 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Thu, 8 Jun 2023 20:35:33 +0000 Subject: [PATCH 30/50] test updates --- x/incentives/types/msgs_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index f5dd7492071..04f7033b1cd 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -170,6 +170,7 @@ func TestMsgCreateGauge(t *testing.T) { name: "valid no lock with pool id unset", msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { msg.DistributeTo.LockQueryType = lockuptypes.NoLock + msg.DistributeTo.Denom = "" msg.PoolId = 1 return msg }), @@ -208,11 +209,14 @@ func TestMsgCreateGauge(t *testing.T) { } for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) - } + test := test + t.Run(test.name, func(t *testing.T) { + if test.expectPass { + require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) + } + }) } } From bf0513c4231f47e0d01f014b1931f7c7971709de Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 8 Jun 2023 16:55:40 -0400 Subject: [PATCH 31/50] Update x/incentives/README.md Co-authored-by: Sishir Giri --- x/incentives/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/README.md b/x/incentives/README.md index 1c5ec19ccda..018ac2aad6f 100644 --- a/x/incentives/README.md +++ b/x/incentives/README.md @@ -59,7 +59,7 @@ conventional rules of the respective gauge type. Additionally, for `NoLock` gauges, lockuptypes.Denom must be either an empty string, signifying that this is an external gauge, or be equal to types.NoLockInternalGaugeDenom(poolId) (when created from the `AfterPoolCreatedHook`) If the denom is empty, it will get overwritten to types.NoLockExternalGaugeDenom(poolId). -This denom formatting is useful for querying internal vs externa gauges associated with a pool since the denom prefix is +This denom formatting is useful for querying internal vs external gauges associated with a pool since the denom prefix is appended into the store prefix. ## State From b809226e068e126545ead91298bf7b6994b0ff56 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Thu, 8 Jun 2023 17:48:19 -0500 Subject: [PATCH 32/50] Update x/incentives/keeper/gauge_test.go --- x/incentives/keeper/gauge_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index f0cc0e43a6b..79c0b00c205 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -561,7 +561,7 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { s.Require().Equal(tc.expectedGaugeId, gaugeId) - // Get gage and check that the denom is set correctly + // Get gauge and check that the denom is set correctly gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, tc.expectedGaugeId) s.Require().NoError(err) From db3d9c247c829de92f60d3af4dced45681ecd8e4 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Thu, 8 Jun 2023 17:48:29 -0500 Subject: [PATCH 33/50] Update x/incentives/keeper/gauge.go --- x/incentives/keeper/gauge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index ce7e1768c86..0e8e06f968c 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -106,7 +106,7 @@ func (k Keeper) SetGaugeWithRefKey(ctx sdk.Context, gauge *types.Gauge) error { // Additionally, lockuptypes.Denom must be either an empty string, signifying that // this is an external gauge, or be equal to types.NoLockInternalGaugeDenom(poolId). // If the denom is empty, it will get overwritten to types.NoLockExternalGaugeDenom(poolId). -// This denom formatting is useful for querying internal vs externa gauges associated with a pool. +// This denom formatting is useful for querying internal vs external gauges associated with a pool. func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddress, coins sdk.Coins, distrTo lockuptypes.QueryCondition, startTime time.Time, numEpochsPaidOver uint64, poolId uint64) (uint64, error) { // Ensure that this gauge's duration is one of the allowed durations on chain durations := k.GetLockableDurations(ctx) From c9760e656676090942fc08dabb8503e65826e142 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 9 Jun 2023 14:51:13 -0400 Subject: [PATCH 34/50] refactor/fix: external vs internal pool id and gauge id links for "NoLock" gauge (#5475) * refactor/fix: external vs internal pool id and gauge id links for "NoLock" gauge * fix test * Update x/incentives/keeper/gauge.go * Update x/pool-incentives/keeper/keeper.go * Update x/pool-incentives/keeper/keeper.go * Update x/pool-incentives/keeper/keeper.go Co-authored-by: Adam Tucker * Update x/pool-incentives/keeper/keeper_test.go Co-authored-by: Adam Tucker * comment updates and renames --------- Co-authored-by: Adam Tucker --- x/incentives/keeper/distribute_test.go | 4 +- x/incentives/keeper/gauge.go | 9 +- x/incentives/keeper/gauge_test.go | 11 +- x/incentives/types/expected_keepers.go | 2 +- x/incentives/types/keys.go | 7 +- x/pool-incentives/keeper/genesis.go | 6 +- x/pool-incentives/keeper/genesis_test.go | 13 ++ x/pool-incentives/keeper/grpc_query.go | 44 ++++++- x/pool-incentives/keeper/grpc_query_test.go | 126 ++++++++++++++++++++ x/pool-incentives/keeper/keeper.go | 64 +++++++++- x/pool-incentives/keeper/keeper_test.go | 9 ++ x/pool-incentives/types/key.go | 18 ++- 12 files changed, 289 insertions(+), 24 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 0ac9782de2f..4075181b274 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -491,7 +491,7 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { // Force gauge's pool id to balancer to trigger error if tc.poolId == defaultBalancerPool { - s.App.PoolIncentivesKeeper.SetPoolGaugeId(s.Ctx, defaultBalancerPool, tc.distrTo.Duration, externalGaugeid) + s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, defaultBalancerPool, tc.distrTo.Duration, externalGaugeid) } // Activate the gauge. @@ -865,7 +865,7 @@ func (s *KeeperTestSuite) TestGetPoolFromGaugeId() { } if tc.shouldSetPoolGaugeId { - s.App.PoolIncentivesKeeper.SetPoolGaugeId(s.Ctx, validPoolId, duration, poolIdOne) + s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, validPoolId, duration, poolIdOne) } pool, err := s.App.IncentivesKeeper.GetPoolFromGaugeId(s.Ctx, tc.gaugeId, duration) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 0e8e06f968c..5debbb728ce 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -152,9 +152,12 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr return 0, fmt.Errorf("no lock gauges must be created for concentrated pools only") } - // We assume that external gauges are created with 0 duration, - // while internal gauges are created with an incentive epoch duration. - k.pik.SetPoolGaugeId(ctx, poolId, distrTo.Duration, nextGaugeId) + // Note that this is a general linking between the gauge and the pool + // for "NoLock" gauges. It occurs for both external and internal gauges. + // That being said, internal gauges have an additional linking + // by duration where duration is the incentives epoch duration. + // The internal incentive linking is set in x/pool-incentives CreateConcentratedLiquidityPoolGauge. + k.pik.SetPoolGaugeIdNoLock(ctx, poolId, nextGaugeId) } else { // For all other gauges, pool id must be 0. if poolId != 0 { diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index 79c0b00c205..a4ddf321f62 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -555,9 +555,16 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { s.Require().Equal(tc.expectedGaugeId, gaugeId) - // Assert that pool id and gauge id links are created - gaugeId, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, tc.poolId, tc.distrTo.Duration) + // Assert that pool id and gauge id link meant for internally incentivized gauges is unset. + _, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, tc.poolId, tc.distrTo.Duration) + s.Require().Error(err) + + // Confirm that the general pool id to gauge id link is set. + gaugeIds, err := s.App.PoolIncentivesKeeper.GetNoLockGaugeIdsFromPool(s.Ctx, tc.poolId) s.Require().NoError(err) + // One must have been created at pool creation time for internal incentives. + s.Require().Len(gaugeIds, 2) + gaugeId := gaugeIds[1] s.Require().Equal(tc.expectedGaugeId, gaugeId) diff --git a/x/incentives/types/expected_keepers.go b/x/incentives/types/expected_keepers.go index 1b3a70d84ed..3b378952113 100644 --- a/x/incentives/types/expected_keepers.go +++ b/x/incentives/types/expected_keepers.go @@ -57,7 +57,7 @@ type AccountKeeper interface { type PoolIncentiveKeeper interface { GetPoolIdFromGaugeId(ctx sdk.Context, gaugeId uint64, lockableDuration time.Duration) (uint64, error) - SetPoolGaugeId(ctx sdk.Context, poolId uint64, lockableDuration time.Duration, gaugeId uint64) + SetPoolGaugeIdNoLock(ctx sdk.Context, poolId uint64, gaugeId uint64) } type GAMMKeeper interface { diff --git a/x/incentives/types/keys.go b/x/incentives/types/keys.go index 5b1bacd4420..c3bbf64d5e9 100644 --- a/x/incentives/types/keys.go +++ b/x/incentives/types/keys.go @@ -47,6 +47,9 @@ var ( // LockableDurationsKey defines key for storing valid durations for giving incentives. LockableDurationsKey = []byte("lockable_durations") + + NoLockInternalPrefix = "no-lock/i/" + NoLockExternalPrefix = "no-lock/e/" ) func KeyPrefix(p string) []byte { @@ -55,10 +58,10 @@ func KeyPrefix(p string) []byte { // NoLockExternalGaugeDenom returns the gauge denom for the no-lock external gauge for the given pool ID. func NoLockExternalGaugeDenom(poolId uint64) string { - return fmt.Sprintf("no-lock/e/%d", poolId) + return fmt.Sprintf("%s%d", NoLockExternalPrefix, poolId) } // NoLockInternalGaugeDenom returns the gauge denom for the no-lock internal gauge for the given pool ID. func NoLockInternalGaugeDenom(poolId uint64) string { - return fmt.Sprintf("no-lock/i/%d", poolId) + return fmt.Sprintf("%s%d", NoLockInternalPrefix, poolId) } diff --git a/x/pool-incentives/keeper/genesis.go b/x/pool-incentives/keeper/genesis.go index 2d48a2f4c61..fddea6abb9b 100644 --- a/x/pool-incentives/keeper/genesis.go +++ b/x/pool-incentives/keeper/genesis.go @@ -20,7 +20,11 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { } if genState.PoolToGauges != nil { for _, record := range genState.PoolToGauges.PoolToGauge { - k.SetPoolGaugeId(ctx, record.PoolId, record.Duration, record.GaugeId) + if record.Duration == 0 { + k.SetPoolGaugeIdNoLock(ctx, record.PoolId, record.GaugeId) + } else { + k.SetPoolGaugeIdInternalIncentive(ctx, record.PoolId, record.Duration, record.GaugeId) + } } } } diff --git a/x/pool-incentives/keeper/genesis_test.go b/x/pool-incentives/keeper/genesis_test.go index c86de1ddd54..dba321b3b74 100644 --- a/x/pool-incentives/keeper/genesis_test.go +++ b/x/pool-incentives/keeper/genesis_test.go @@ -48,6 +48,19 @@ var ( GaugeId: 2, Duration: time.Second, }, + // This duplication with zero duration + // can happen with "NoLock" gauges + // where the link containing the duration + // is used to signify that the gauge is internal + // while the link without the duration is used + // for general purpose. This redundancy is + // made for convinience of plugging in the + // later added "NoLock" gauge into the existing + // logic without having to change majority of the queries. + { + PoolId: 2, + GaugeId: 2, + }, }, }, } diff --git a/x/pool-incentives/keeper/grpc_query.go b/x/pool-incentives/keeper/grpc_query.go index f8b5ea7e36a..217c842e638 100644 --- a/x/pool-incentives/keeper/grpc_query.go +++ b/x/pool-incentives/keeper/grpc_query.go @@ -11,6 +11,7 @@ import ( incentivetypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" "github.com/osmosis-labs/osmosis/v16/x/pool-incentives/types" + poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" ) var _ types.QueryServer = Querier{} @@ -25,21 +26,54 @@ func NewQuerier(k Keeper) Querier { return Querier{Keeper: k} } -// GaugeIds takes provided gauge request and returns the respective gaugeIDs. +// GaugeIds takes provided gauge request and returns the respective internally incentivized gaugeIDs. +// If internally incentivized for a given pool id is not found, returns an error. func (q Querier) GaugeIds(ctx context.Context, req *types.QueryGaugeIdsRequest) (*types.QueryGaugeIdsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } sdkCtx := sdk.UnwrapSDKContext(ctx) - lockableDurations := q.Keeper.GetLockableDurations(sdkCtx) - distrInfo := q.Keeper.GetDistrInfo(sdkCtx) - gaugeIdsWithDuration := make([]*types.QueryGaugeIdsResponse_GaugeIdWithDuration, len(lockableDurations)) + distrInfo := q.Keeper.GetDistrInfo(sdkCtx) totalWeightDec := distrInfo.TotalWeight.ToDec() incentivePercentage := sdk.NewDec(0) percentMultiplier := sdk.NewInt(100) + pool, err := q.Keeper.poolmanagerKeeper.GetPool(sdkCtx, req.PoolId) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + isConcentratedPool := pool.GetType() == poolmanagertypes.Concentrated + if isConcentratedPool { + incentiveEpochDuration := q.Keeper.incentivesKeeper.GetEpochInfo(sdkCtx).Duration + gaugeId, err := q.Keeper.GetPoolGaugeId(sdkCtx, req.PoolId, incentiveEpochDuration) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + for _, record := range distrInfo.Records { + if record.GaugeId == gaugeId { + // Pool incentive % = (gauge_id_weight / sum_of_all_pool_gauge_weight) * 100 + incentivePercentage = record.Weight.ToDec().Quo(totalWeightDec).MulInt(percentMultiplier) + } + } + + return &types.QueryGaugeIdsResponse{ + GaugeIdsWithDuration: []*types.QueryGaugeIdsResponse_GaugeIdWithDuration{ + { + GaugeId: gaugeId, + Duration: incentiveEpochDuration, + GaugeIncentivePercentage: incentivePercentage.String(), + }, + }, + }, nil + } + + lockableDurations := q.Keeper.GetLockableDurations(sdkCtx) + gaugeIdsWithDuration := make([]*types.QueryGaugeIdsResponse_GaugeIdWithDuration, len(lockableDurations)) + for i, duration := range lockableDurations { gaugeId, err := q.Keeper.GetPoolGaugeId(sdkCtx, req.PoolId, duration) if err != nil { @@ -136,7 +170,7 @@ func (q Querier) ExternalIncentiveGauges(ctx context.Context, req *types.QueryEx sdkCtx := sdk.UnwrapSDKContext(ctx) store := sdkCtx.KVStore(q.Keeper.storeKey) - prefixStore := prefix.NewStore(store, []byte("pool-incentives")) + prefixStore := prefix.NewStore(store, []byte("pool-incentives/")) iterator := prefixStore.Iterator(nil, nil) defer iterator.Close() diff --git a/x/pool-incentives/keeper/grpc_query_test.go b/x/pool-incentives/keeper/grpc_query_test.go index 20e48898394..414443124e5 100644 --- a/x/pool-incentives/keeper/grpc_query_test.go +++ b/x/pool-incentives/keeper/grpc_query_test.go @@ -424,6 +424,132 @@ func (s *KeeperTestSuite) TestExternalIncentiveGauges() { } } +// TestExternalIncentiveGauges_NoLock tests the ExternalIncentiveGauges +// around gauges of type NoLock. +// For every test case, this test sets up a balancer pool and a concentrated pool. +// Balancer pool creates 3 internal gauges with ids 1, 2, 3 +// Concentrated pool creates 1 internal gauge with id 4 +// Next, each test case creates external gauges with different distributeTo conditions +// based on the test case parameters. +// Finally, the test calls ExternalIncentiveGauges and ensures that the correct +// gauges are returned. +func (s *KeeperTestSuite) TestExternalIncentiveGauges_NoLock() { + type gaugeConfig struct { + distributeTo lockuptypes.QueryCondition + poolId uint64 + } + + const ( + defaultIsPerpetual = true + defaultNumEpochsPaidOver = 1 + + balancerPoolId = 1 + concentratedPoolId = 2 + + // Balancer pool creates 3 internal gauges with ids 1, 2, 3 + // Concentrated pool creates 1 internal gauge with id 4 + firstExpectedExternalGaugeId = 5 + + defaultDenom = "stake" + ) + + var ( + defaultCoins = sdk.NewCoins(sdk.NewCoin(defaultDenom, sdk.NewInt(10000000000))) + + defaultLockableDuration = s.App.IncentivesKeeper.GetLockableDurations(s.Ctx)[0] + + defaultNoLockGaugeConfig = gaugeConfig{ + distributeTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.NoLock, + }, + poolId: concentratedPoolId, + } + + defaultByDurationGaugeConfig = gaugeConfig{ + distributeTo: lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.ByDuration, + Duration: defaultLockableDuration, + Denom: defaultDenom, + }, + } + ) + + tests := map[string]struct { + gaugesToCreate []gaugeConfig + + expectedGaugeIds []uint64 + + expectError bool + }{ + "1 no lock external gauge": { + gaugesToCreate: []gaugeConfig{ + defaultNoLockGaugeConfig, + }, + expectedGaugeIds: []uint64{firstExpectedExternalGaugeId}, + }, + "1 by duration external gauge": { + gaugesToCreate: []gaugeConfig{ + defaultByDurationGaugeConfig, + }, + expectedGaugeIds: []uint64{firstExpectedExternalGaugeId}, + }, + "5 gauges no lock and by duration mixed": { + gaugesToCreate: []gaugeConfig{ + defaultByDurationGaugeConfig, + defaultNoLockGaugeConfig, + defaultByDurationGaugeConfig, + defaultNoLockGaugeConfig, + defaultByDurationGaugeConfig, + }, + expectedGaugeIds: []uint64{ + firstExpectedExternalGaugeId, + firstExpectedExternalGaugeId + 1, + firstExpectedExternalGaugeId + 2, + firstExpectedExternalGaugeId + 3, + firstExpectedExternalGaugeId + 4, + }, + }, + } + for name, tc := range tests { + s.Run(name, func() { + s.SetupTest() + + defaultStartTime := s.Ctx.BlockTime() + + queryClient := s.queryClient + + // Prepare a balancer and a CL pool + // Creates 3 NoLock internal gauges with ids 1, 2, 3 + s.PrepareBalancerPool() + // Creates 1 NoLock internal gauge with id 4 + s.PrepareConcentratedPool() + + // Pre-create external gauges + for _, gauge := range tc.gaugesToCreate { + + // Fund creator + s.FundAcc(s.TestAccs[0], defaultCoins) + + // Note that some parameters are defaults as they are not relevant to this test + _, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, defaultIsPerpetual, s.TestAccs[0], defaultCoins, gauge.distributeTo, defaultStartTime, defaultNumEpochsPaidOver, gauge.poolId) + s.Require().NoError(err) + } + + res, err := queryClient.ExternalIncentiveGauges(context.Background(), &types.QueryExternalIncentiveGaugesRequest{}) + + if tc.expectError { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().Equal(len(tc.expectedGaugeIds), len(res.Data)) + for i, gaugeId := range tc.expectedGaugeIds { + s.Require().Equal(gaugeId, res.Data[i].Id) + } + } + }) + } +} + func (s *KeeperTestSuite) TestGaugeIncentivePercentage() { s.SetupTest() diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 570fe4af681..31df910297d 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -84,7 +84,7 @@ func (k Keeper) CreateLockablePoolGauges(ctx sdk.Context, poolId uint64) error { return err } - k.SetPoolGaugeId(ctx, poolId, lockableDuration, gaugeId) + k.SetPoolGaugeIdInternalIncentive(ctx, poolId, lockableDuration, gaugeId) } return nil } @@ -102,7 +102,7 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin incentivesEpoch := k.incentivesKeeper.GetEpochInfo(ctx) - _, err = k.incentivesKeeper.CreateGauge( + gaugeId, err := k.incentivesKeeper.CreateGauge( ctx, true, k.accountKeeper.GetModuleAddress(types.ModuleName), @@ -120,20 +120,53 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin return err } + // Although the pool id <> gauge "NoLock" link is created in CreateGauge, + // we create an additional "ByDuration" link here for tracking + // internal incentive "NoLock" gauges + incentivesEpochDuration := k.incentivesKeeper.GetEpochInfo(ctx).Duration + k.SetPoolGaugeIdInternalIncentive(ctx, poolId, incentivesEpochDuration, gaugeId) + return nil } -func (k Keeper) SetPoolGaugeId(ctx sdk.Context, poolId uint64, lockableDuration time.Duration, gaugeId uint64) { - key := types.GetPoolGaugeIdStoreKey(poolId, lockableDuration) +// SetPoolGaugeIdInternalIncentive sets the gauge id for the pool and internally incentivized duration. +// CONTRACT: this link is created only for the internally incentivized gauges. +func (k Keeper) SetPoolGaugeIdInternalIncentive(ctx sdk.Context, poolId uint64, incentivizedDuration time.Duration, gaugeId uint64) { + // Note: this index is used for internal incentive gauges only. + key := types.GetPoolGaugeIdInternalStoreKey(poolId, incentivizedDuration) + store := ctx.KVStore(k.storeKey) + store.Set(key, sdk.Uint64ToBigEndian(gaugeId)) + + // Note: this index is used for general linking. + key = types.GetPoolIdFromGaugeIdStoreKey(gaugeId, incentivizedDuration) + store.Set(key, sdk.Uint64ToBigEndian(poolId)) +} + +// SetPoolGaugeIdNoLock sets the link between pool id and gauge id for "NoLock" gauges. +// CONTRACT: the gauge of the given id must be "NoLock" gauge. +func (k Keeper) SetPoolGaugeIdNoLock(ctx sdk.Context, poolId uint64, gaugeId uint64) { store := ctx.KVStore(k.storeKey) + // maps pool id and gauge id to gauge id. + // Note: this could be pool id and gauge id to empty byte array, + // but is chosen this way for ease of implementation at the cost of space. + // Note 2: this index is used for "NoLock" gauges only. + key := types.GetPoolNoLockGaugeIdStoreKey(poolId, gaugeId) store.Set(key, sdk.Uint64ToBigEndian(gaugeId)) - key = types.GetPoolIdFromGaugeIdStoreKey(gaugeId, lockableDuration) + // Note: this index is used for general linking. + key = types.GetPoolIdFromGaugeIdStoreKey(gaugeId, 0) store.Set(key, sdk.Uint64ToBigEndian(poolId)) } +// GetPoolGaugeId returns the gauge id associated with the pool id and lockable duration. +// This can only be used for the internally incentivized gauges. +// Externally incentivized gauges do not have such link created. func (k Keeper) GetPoolGaugeId(ctx sdk.Context, poolId uint64, lockableDuration time.Duration) (uint64, error) { - key := types.GetPoolGaugeIdStoreKey(poolId, lockableDuration) + if lockableDuration == 0 { + return 0, fmt.Errorf("cannot get gauge id from pool id without a lockable duration. There can be many gauges for pool id %d and duration 0", poolId) + } + + key := types.GetPoolGaugeIdInternalStoreKey(poolId, lockableDuration) store := ctx.KVStore(k.storeKey) bz := store.Get(key) @@ -144,6 +177,25 @@ func (k Keeper) GetPoolGaugeId(ctx sdk.Context, poolId uint64, lockableDuration return sdk.BigEndianToUint64(bz), nil } +// GetNoLockGaugeIdsFromPool returns all the NoLock gauge ids associated with the pool id. +// This can only be used for the "NoLock" gauges. For "NoLock" gauges there are 2 kinds +// of links created: +// - general +// - by duration (for internal incentives) +// +// Every "NoLock" gauge has the first link. Only the internal incentives "NoLock" gauges +// have the second link. +func (k Keeper) GetNoLockGaugeIdsFromPool(ctx sdk.Context, poolId uint64) ([]uint64, error) { + store := ctx.KVStore(k.storeKey) + gaugeIds, err := osmoutils.GatherValuesFromStorePrefix(store, types.GetPoolNoLockGaugeIdIterationStoreKey(poolId), func(b []byte) (uint64, error) { + return sdk.BigEndianToUint64(b), nil + }) + if err != nil { + return nil, err + } + return gaugeIds, nil +} + func (k Keeper) GetPoolIdFromGaugeId(ctx sdk.Context, gaugeId uint64, lockableDuration time.Duration) (uint64, error) { key := types.GetPoolIdFromGaugeIdStoreKey(gaugeId, lockableDuration) store := ctx.KVStore(k.storeKey) diff --git a/x/pool-incentives/keeper/keeper_test.go b/x/pool-incentives/keeper/keeper_test.go index 4771cfab42e..38a771905b9 100644 --- a/x/pool-incentives/keeper/keeper_test.go +++ b/x/pool-incentives/keeper/keeper_test.go @@ -91,6 +91,15 @@ func (s *KeeperTestSuite) TestCreateConcentratePoolGauges() { // Same amount of gauges as lockableDurations must be created for every pool created. gaugeId, err := keeper.GetPoolGaugeId(s.Ctx, clPool.GetId(), currEpoch.Duration) s.NoError(err) + + // Same amount of NoLock gauges as lockableDurations must be created for every pool created. + gaugeIds, err := keeper.GetNoLockGaugeIdsFromPool(s.Ctx, clPool.GetId()) + s.NoError(err) + + s.Equal(1, len(gaugeIds)) + + s.Equal(gaugeId, gaugeIds[0]) + gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeId) s.NoError(err) s.Equal(0, len(gauge.Coins)) diff --git a/x/pool-incentives/types/key.go b/x/pool-incentives/types/key.go index 7ab6a5d83c7..f1e8d5164b7 100644 --- a/x/pool-incentives/types/key.go +++ b/x/pool-incentives/types/key.go @@ -20,8 +20,9 @@ var ( DistrInfoKey = []byte("distr_info") ) -// GetPoolGaugeIdStoreKey returns a StoreKey with pool ID and its duration as inputs -func GetPoolGaugeIdStoreKey(poolId uint64, duration time.Duration) []byte { +// GetPoolGaugeIdInternalStoreKey returns a StoreKey with pool ID and its duration as inputs +// This is used for linking pool id, duration and gauge id for internal incentives. +func GetPoolGaugeIdInternalStoreKey(poolId uint64, duration time.Duration) []byte { return []byte(fmt.Sprintf("pool-incentives/%d/%s", poolId, duration.String())) } @@ -29,3 +30,16 @@ func GetPoolGaugeIdStoreKey(poolId uint64, duration time.Duration) []byte { func GetPoolIdFromGaugeIdStoreKey(gaugeId uint64, duration time.Duration) []byte { return []byte(fmt.Sprintf("pool-incentives-pool-id/%d/%s", gaugeId, duration.String())) } + +// GetPoolNoLockGaugeIdStoreKey returns a StoreKey with pool ID and gauge id as input +// assumming that the pool has no lockable duration. +func GetPoolNoLockGaugeIdStoreKey(poolId uint64, gaugeId uint64) []byte { + return []byte(fmt.Sprintf("no-lock-pool-incentives/%d/%d", poolId, gaugeId)) +} + +// GetPoolNoLockGaugeIdIterationStoreKey returns a StoreKey with pool ID as input +// assumming that the pool has no lockable duration. It is used for collecting +// values by iterating over this prefix. +func GetPoolNoLockGaugeIdIterationStoreKey(poolId uint64) []byte { + return []byte(fmt.Sprintf("no-lock-pool-incentives/%d/", poolId)) +} From d3ce09ab4ef16b13e5d18a4c14abe4260e6f7926 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:00:54 +0000 Subject: [PATCH 35/50] superfluid comment --- x/incentives/keeper/gauge.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 5debbb728ce..965a8db251e 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -165,6 +165,10 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr } // check if denom this gauge pays out to exists on-chain + // N.B.: The reason we check for osmovaloper is to account for gauges that pay out to + // superfluid synthetic locks. These locks have the following format: + // "cl/pool/1/superbonding/osmovaloper1wcfyglfgjs2xtsyqu7pl60d0mpw5g7f4wh7pnm" + // See x/superfluid module README for details. if !k.bk.HasSupply(ctx, distrTo.Denom) && !strings.Contains(distrTo.Denom, "osmovaloper") { return 0, fmt.Errorf("denom does not exist: %s", distrTo.Denom) } From c856cc41b21b8b10c8a039078cdd3879b0d7178b Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:17:18 +0000 Subject: [PATCH 36/50] disallow no lock gauge with duration set --- x/incentives/types/msgs.go | 30 +++++++++++++++++++----------- x/incentives/types/msgs_test.go | 14 ++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 4e8e99bd624..0bc1e6ff7a0 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -2,6 +2,7 @@ package types import ( "errors" + "fmt" "time" lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" @@ -37,6 +38,7 @@ func (m MsgCreateGauge) Type() string { return TypeMsgCreateGauge } // ValidateBasic checks that the create gauge message is valid. func (m MsgCreateGauge) ValidateBasic() error { lockType := m.DistributeTo.LockQueryType + isNoLockGauge := lockType == lockuptypes.NoLock if m.Owner == "" { return errors.New("owner should be set") @@ -62,17 +64,23 @@ func (m MsgCreateGauge) ValidateBasic() error { return errors.New("start time distr conditions is an obsolete codepath slated for deletion") } - if lockType == lockuptypes.ByDuration && m.PoolId != 0 { - return errors.New("pool id should not be set for duration distr condition") - } - - if lockType == lockuptypes.NoLock && m.PoolId == 0 { - return errors.New("pool id should be set for no lock distr condition") - } - - if lockType == lockuptypes.NoLock && m.DistributeTo.Denom != "" { - return errors.New(`no lock gauge denom should be unset. It will be automatically set to the NoLockExternalGaugeDenom() - format internally, allowing for querying the gauges by denom with this prefix`) + if isNoLockGauge { + if m.PoolId == 0 { + return errors.New("pool id should be set for no lock distr condition") + } + + if m.DistributeTo.Denom != "" { + return errors.New(`no lock gauge denom should be unset. It will be automatically set to the NoLockExternalGaugeDenom() + format internally, allowing for querying the gauges by denom with this prefix`) + } + + if m.DistributeTo.Duration != 0 { + return fmt.Errorf("no lock gauge must have duration set to 0, was (%d)", m.DistributeTo.Duration) + } + } else { + if m.PoolId != 0 { + return errors.New("pool id should not be set for duration distr condition") + } } return nil diff --git a/x/incentives/types/msgs_test.go b/x/incentives/types/msgs_test.go index 04f7033b1cd..ef9617a60d6 100644 --- a/x/incentives/types/msgs_test.go +++ b/x/incentives/types/msgs_test.go @@ -171,6 +171,7 @@ func TestMsgCreateGauge(t *testing.T) { msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { msg.DistributeTo.LockQueryType = lockuptypes.NoLock msg.DistributeTo.Denom = "" + msg.DistributeTo.Duration = 0 msg.PoolId = 1 return msg }), @@ -206,6 +207,19 @@ func TestMsgCreateGauge(t *testing.T) { }), expectPass: false, }, + { + name: "invalid due to no lock with non-zero lock duration", + msg: createMsg(func(msg incentivestypes.MsgCreateGauge) incentivestypes.MsgCreateGauge { + msg.DistributeTo.LockQueryType = lockuptypes.NoLock + msg.DistributeTo.Denom = "" + msg.PoolId = 1 + + // breaks + msg.DistributeTo.Duration = time.Hour + return msg + }), + expectPass: false, + }, } for _, test := range tests { From 897e9df0d4baa7073ce32a52c03142e666961994 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:42:48 +0000 Subject: [PATCH 37/50] test gauge distribution state update --- x/incentives/keeper/distribute_test.go | 24 +++++++++++++++++++----- x/incentives/keeper/keeper_test.go | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 4075181b274..96fca1922c8 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -322,8 +322,10 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { for _, gauge := range gauges { for _, coin := range gauge.Coins { + gaugeId := gauge.GetId() + // get poolId from GaugeId - poolId, err := s.App.PoolIncentivesKeeper.GetPoolIdFromGaugeId(s.Ctx, gauge.GetId(), currentEpoch.Duration) + poolId, err := s.App.PoolIncentivesKeeper.GetPoolIdFromGaugeId(s.Ctx, gaugeId, currentEpoch.Duration) s.Require().NoError(err) // GetIncentiveRecord to see if pools received incentives properly @@ -332,6 +334,9 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { expectedEmissionRate := sdk.NewDecFromInt(coin.Amount).QuoTruncate(sdk.NewDec(int64(currentEpoch.Duration.Seconds()))) + // Check that gauge distribution state is updated. + s.ValidateDistributedGauge(gaugeId, 1, totalDistributedCoins) + // check every parameter in incentiveRecord so that it matches what we created s.Require().Equal(poolId, incentiveRecord.PoolId) s.Require().Equal(defaultRewardDenom, incentiveRecord.IncentiveDenom) @@ -532,6 +537,9 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { s.Require().Equal(expectedEmissionRatePerEpoch, incentiveRecords.IncentiveRecordBody.EmissionRate) s.Require().Equal(time.Nanosecond, incentiveRecords.MinUptime) } + + // Check that the gauge's distribution state was updated + s.ValidateDistributedGauge(externalGaugeid, 1, tc.expectedDistributions) } }) } @@ -697,8 +705,8 @@ func (s *KeeperTestSuite) TestGetModuleDistributedCoins() { s.Require().Equal(coins, distrCoins) } -// TestNoLockPerpetualGaugeDistribution tests that the creation of a perp gauge that has no locks associated does not distribute any tokens. -func (s *KeeperTestSuite) TestNoLockPerpetualGaugeDistribution() { +// TestBuDurationPerpetualGaugeDistribution_NoLockNoOp tests that the creation of a perp gauge that has no locks associated does not distribute any tokens. +func (s *KeeperTestSuite) TestBuDurationPerpetualGaugeDistribution_NoLockNoOp() { s.SetupTest() // setup a perpetual gauge with no associated locks @@ -742,10 +750,13 @@ func (s *KeeperTestSuite) TestNoLockPerpetualGaugeDistribution() { gauges = s.App.IncentivesKeeper.GetNotFinishedGauges(s.Ctx) s.Require().Len(gauges, 1) s.Require().Equal(gauges[0].String(), expectedGauge.String()) + + // Check that gauge distribution state is not updated. + s.ValidateNotDistributedGauge(gaugeID) } -// TestNoLockNonPerpetualGaugeDistribution tests that the creation of a non perp gauge that has no locks associated does not distribute any tokens. -func (s *KeeperTestSuite) TestNoLockNonPerpetualGaugeDistribution() { +// TestByDurationNonPerpetualGaugeDistribution_NoLockNoOp tests that the creation of a non perp gauge that has no locks associated does not distribute any tokens. +func (s *KeeperTestSuite) TestByDurationNonPerpetualGaugeDistribution_NoLockNoOp() { s.SetupTest() // setup non-perpetual gauge with no associated locks @@ -789,6 +800,9 @@ func (s *KeeperTestSuite) TestNoLockNonPerpetualGaugeDistribution() { gauges = s.App.IncentivesKeeper.GetNotFinishedGauges(s.Ctx) s.Require().Len(gauges, 1) s.Require().Equal(gauges[0].String(), expectedGauge.String()) + + // Check that gauge distribution state is not updated. + s.ValidateNotDistributedGauge(gaugeID) } func (s *KeeperTestSuite) TestGetPoolFromGaugeId() { diff --git a/x/incentives/keeper/keeper_test.go b/x/incentives/keeper/keeper_test.go index 5fce1ee669b..4674a2287e8 100644 --- a/x/incentives/keeper/keeper_test.go +++ b/x/incentives/keeper/keeper_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/v16/app/apptesting" @@ -28,3 +29,19 @@ func (s *KeeperTestSuite) SetupTest() { func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } + +// ValidateDistributedGauge checks that the gauge is updated as expected after distribution +func (s *KeeperTestSuite) ValidateDistributedGauge(gaugeID uint64, expectedFilledEpoch uint64, expectedDistributions sdk.Coins) { + // Check that filled epcohs is not updated + gauge, err := s.App.IncentivesKeeper.GetGaugeByID(s.Ctx, gaugeID) + s.Require().NoError(err) + s.Require().Equal(expectedFilledEpoch, gauge.FilledEpochs) + + // Check that distributed coins is not updated + s.Require().Equal(expectedDistributions, gauge.DistributedCoins) +} + +// ValidateNotDistributedGauge checks that the gauge is not updated after distribution +func (s *KeeperTestSuite) ValidateNotDistributedGauge(gaugeID uint64) { + s.ValidateDistributedGauge(gaugeID, 0, sdk.Coins(nil)) +} From 975275bbb4ba4841198502cf02ab3c3abc85813e Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:47:25 +0000 Subject: [PATCH 38/50] error message improvement --- x/incentives/keeper/gauge.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 965a8db251e..4bf943e03ab 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -129,16 +129,17 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr // A pool with such id must exist and be a concentrated pool. if distrTo.LockQueryType == lockuptypes.NoLock { if poolId == 0 { - return 0, fmt.Errorf("no lock gauges must have a pool id") + return 0, fmt.Errorf("'no lock' type gauges must have a pool id") } // If not internal gauge denom, then must be set to "" // and get overwritten with the external prefix + pool id // for internal query purposes. - if distrTo.Denom != types.NoLockInternalGaugeDenom(poolId) { + distrToDenom := distrTo.Denom + if distrToDenom != types.NoLockInternalGaugeDenom(poolId) { // If denom is set, then fails. - if distrTo.Denom != "" { - return 0, fmt.Errorf("no lock external gauges must have an empty denom set") + if distrToDenom != "" { + return 0, fmt.Errorf("'no lock' type external gauges must have an empty denom set, was %s", distrToDenom) } distrTo.Denom = types.NoLockExternalGaugeDenom(poolId) } @@ -149,7 +150,7 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr } if pool.GetType() != poolmanagertypes.Concentrated { - return 0, fmt.Errorf("no lock gauges must be created for concentrated pools only") + return 0, fmt.Errorf("'no lock' type gauges must be created for concentrated pools only") } // Note that this is a general linking between the gauge and the pool From 56684e9487b2db8fc827e01627d023d552f0d3f1 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:48:09 +0000 Subject: [PATCH 39/50] remove obsolete field --- x/incentives/keeper/distribute_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 96fca1922c8..c04ac236843 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -394,8 +394,6 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { numEpochsPaidOver uint64 poolId uint64 - isBalancerPool bool - // expected expectErr bool expectedDistributions sdk.Coins From 4cc0e8300553dd9c048e98757367e6a691c13f40 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 19:59:00 +0000 Subject: [PATCH 40/50] clarify test name --- x/incentives/keeper/gauge_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index a4ddf321f62..7372f92da8d 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -482,7 +482,7 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() { expectErr: false, }, { - name: "create valid no lock gauge with CL pool (denom set to uosmo)", + name: "create valid no lock gauge with CL pool (denom set to no lock internal prefix)", distrTo: lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, // Note: this assumes the gauge is internal From 657e293dd9405998a5c697a170290b7aabb4c662 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 20:02:02 +0000 Subject: [PATCH 41/50] updates --- x/incentives/types/msgs.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 0bc1e6ff7a0..4b807e9dba1 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -43,10 +43,6 @@ func (m MsgCreateGauge) ValidateBasic() error { if m.Owner == "" { return errors.New("owner should be set") } - // For no lock type, the denom must be empty and we check that down below. - if lockType != lockuptypes.NoLock && sdk.ValidateDenom(m.DistributeTo.Denom) != nil { - return errors.New("denom should be valid for the condition") - } if lockuptypes.LockQueryType_name[int32(m.DistributeTo.LockQueryType)] == "" { return errors.New("lock query type is invalid") } @@ -81,6 +77,11 @@ func (m MsgCreateGauge) ValidateBasic() error { if m.PoolId != 0 { return errors.New("pool id should not be set for duration distr condition") } + + // For no lock type, the denom must be empty and we check that above. + if err := sdk.ValidateDenom(m.DistributeTo.Denom); err != nil { + return fmt.Errorf("denom should be valid for the condition, %s", err) + } } return nil From 52bd732327858632c4bd490a3c155b61c208eae5 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 20:11:15 +0000 Subject: [PATCH 42/50] doc improvements --- proto/osmosis/incentives/tx.proto | 9 +++++---- x/concentrated-liquidity/README.md | 2 +- x/incentives/types/tx.pb.go | 9 +++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/proto/osmosis/incentives/tx.proto b/proto/osmosis/incentives/tx.proto index b7d6c520430..4f724fde8ef 100644 --- a/proto/osmosis/incentives/tx.proto +++ b/proto/osmosis/incentives/tx.proto @@ -48,10 +48,11 @@ message MsgCreateGauge { // pool_id is the ID of the pool that the gauge is meant to be associated // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be - // "NoLock" with all other fields unset, including "QueryCondition.Denom". - // However, note that, internally, the empty string ends up being overwritten - // with incentivestypes.NoLockExternalGaugeDenom() so that the - // gauges associated with a pool can be queried by this prefix if needed. + // "NoLock" with all other fields of the "QueryCondition.LockQueryType" struct + // unset, including "QueryCondition.Denom". However, note that, internally, + // the empty string in "QueryCondition.Denom" ends up being overwritten with + // incentivestypes.NoLockExternalGaugeDenom() so that the gauges + // associated with a pool can be queried by this prefix if needed. uint64 pool_id = 7; } message MsgCreateGaugeResponse {} diff --git a/x/concentrated-liquidity/README.md b/x/concentrated-liquidity/README.md index e9e148ada8b..24e2e42e217 100644 --- a/x/concentrated-liquidity/README.md +++ b/x/concentrated-liquidity/README.md @@ -1413,7 +1413,7 @@ that has been in the pool for the required amount of time qualifies for claiming While it is technically possible for Osmosis to enable the creation of incentive records directly in the CL module, incentive creation is currently funneled through existing gauge infrastructure in the `x/incentives` module. This simplifies UX drastically for frontends, external incentive creators, and governance, while making CL incentives fully backwards-compatible with incentive creation and querying flows that everyone is already used to. As of the initial version of Osmosis's CL, all incentive creation and querying logic will be handled by respective gauge functions (e.g. the `IncentivizedPools` query in the `x/incentives` module will include CL pools that have internal incentives on them). -To create a gauge dedicated to the concentrated liquidity pool, run a `MsgCreateGauge` message in the `x/incentives` module with the following parameters: +To create a gauge dedicated to the concentrated liquidity pool, run a `MsgCreateGauge` message in the `x/incentives` module with the following parameter constraints: - `PoolId`: The ID of the CL pool to create a gauge for. - `DistrTo.LockQueryType` must be set to `locktypes.LockQueryType.NoLock` - `DistrTo.Denom` must be an empty string. diff --git a/x/incentives/types/tx.pb.go b/x/incentives/types/tx.pb.go index d1036e24aa2..48d3e6a414f 100644 --- a/x/incentives/types/tx.pb.go +++ b/x/incentives/types/tx.pb.go @@ -58,10 +58,11 @@ type MsgCreateGauge struct { NumEpochsPaidOver uint64 `protobuf:"varint,6,opt,name=num_epochs_paid_over,json=numEpochsPaidOver,proto3" json:"num_epochs_paid_over,omitempty"` // pool_id is the ID of the pool that the gauge is meant to be associated // with. if pool_id is set, then the "QueryCondition.LockQueryType" must be - // "NoLock" with all other fields unset, including "QueryCondition.Denom". - // However, note that, internally, the empty string ends up being overwritten - // with incentivestypes.NoLockExternalGaugeDenom() so that the - // gauges associated with a pool can be queried by this prefix if needed. + // "NoLock" with all other fields of the "QueryCondition.LockQueryType" struct + // unset, including "QueryCondition.Denom". However, note that, internally, + // the empty string in "QueryCondition.Denom" ends up being overwritten with + // incentivestypes.NoLockExternalGaugeDenom() so that the gauges + // associated with a pool can be queried by this prefix if needed. PoolId uint64 `protobuf:"varint,7,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` } From f9a3d73d8fe258ceef58b2f0685c071690ca8940 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 20:54:14 +0000 Subject: [PATCH 43/50] clean up TestDistribute_InternalIncentives_NoLock --- x/incentives/keeper/distribute_test.go | 91 +++++++++----------------- 1 file changed, 30 insertions(+), 61 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index c04ac236843..14f94f4cb6e 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -166,12 +166,6 @@ func (s *KeeperTestSuite) TestDistribute() { } func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { - defaultGauge := perpGaugeDesc{ - lockDenom: defaultLPDenom, - lockDuration: defaultLockDuration, - rewardAmount: sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}, - } - fiveKRewardCoins := sdk.NewInt64Coin(defaultRewardDenom, 5000) fiveKRewardCoinsUosmo := sdk.NewInt64Coin(appParams.BaseCoinUnit, 5000) fifteenKRewardCoins := sdk.NewInt64Coin(defaultRewardDenom, 15000) @@ -188,8 +182,6 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { tokensToAddToGauge sdk.Coins gaugeStartTime time.Time gaugeCoins sdk.Coins - lockExist bool - isBalancerPool bool // expected expectErr bool @@ -255,15 +247,9 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { poolId uint64 duration time.Duration ) - if tc.isBalancerPool { - poolId = s.PrepareBalancerPool() + poolId = s.PrepareConcentratedPool().GetId() - duration = s.App.PoolIncentivesKeeper.GetLockableDurations(s.Ctx)[0] - } else { - poolId = s.PrepareConcentratedPool().GetId() - - duration = currentEpoch.Duration - } + duration = currentEpoch.Duration // get the gaugeId corresponding to the CL pool gaugeId, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, poolId, duration) @@ -278,13 +264,6 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { gauges = append(gauges, *gauge) } - var addrs []sdk.AccAddress - // this is the case where retrieving pool fails so we run the else logic where gauge is distributed via locks - if tc.lockExist { - gauges = s.SetupGauges([]perpGaugeDesc{defaultGauge}, defaultLPDenom) - addrs = s.SetupUserLocks([]userLocks{oneLockupUser}) - } - // Distribute tokens from the gauge totalDistributedCoins, err := s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) if tc.expectErr { @@ -308,53 +287,43 @@ func (s *KeeperTestSuite) TestDistribute_InternalIncentives_NoLock() { } else { s.Require().NoError(err) - // this check is specifically for CL pool gauges, because we donot create pools other than CL - if tc.isBalancerPool { - // check that gauge is not empty - s.Require().NotEqual(len(gauges), 0) + // check that gauge is not empty + s.Require().NotEqual(len(gauges), 0) - // check if module amount got deducted correctly - balance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress(types.ModuleName)) - for _, coin := range balance { - actualbalanceAfterDistribution := coinsToMint.AmountOf(coin.Denom).Sub(coin.Amount) - s.Require().Equal(tc.expectedDistributions.AmountOf(coin.Denom).Add(sdk.ZeroInt()), actualbalanceAfterDistribution.Add(sdk.ZeroInt())) - } + // check if module amount got deducted correctly + balance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress(types.ModuleName)) + for _, coin := range balance { + actualbalanceAfterDistribution := coinsToMint.AmountOf(coin.Denom).Sub(coin.Amount) + s.Require().Equal(tc.expectedDistributions.AmountOf(coin.Denom).Add(sdk.ZeroInt()), actualbalanceAfterDistribution.Add(sdk.ZeroInt())) + } - for _, gauge := range gauges { - for _, coin := range gauge.Coins { - gaugeId := gauge.GetId() + for _, gauge := range gauges { + for _, coin := range gauge.Coins { + gaugeId := gauge.GetId() - // get poolId from GaugeId - poolId, err := s.App.PoolIncentivesKeeper.GetPoolIdFromGaugeId(s.Ctx, gaugeId, currentEpoch.Duration) - s.Require().NoError(err) + // get poolId from GaugeId + poolId, err := s.App.PoolIncentivesKeeper.GetPoolIdFromGaugeId(s.Ctx, gaugeId, currentEpoch.Duration) + s.Require().NoError(err) - // GetIncentiveRecord to see if pools received incentives properly - incentiveRecord, err := s.App.ConcentratedLiquidityKeeper.GetIncentiveRecord(s.Ctx, poolId, defaultRewardDenom, types.DefaultConcentratedUptime, s.App.AccountKeeper.GetModuleAddress(types.ModuleName)) - s.Require().NoError(err) + // GetIncentiveRecord to see if pools received incentives properly + incentiveRecord, err := s.App.ConcentratedLiquidityKeeper.GetIncentiveRecord(s.Ctx, poolId, defaultRewardDenom, types.DefaultConcentratedUptime, s.App.AccountKeeper.GetModuleAddress(types.ModuleName)) + s.Require().NoError(err) - expectedEmissionRate := sdk.NewDecFromInt(coin.Amount).QuoTruncate(sdk.NewDec(int64(currentEpoch.Duration.Seconds()))) + expectedEmissionRate := sdk.NewDecFromInt(coin.Amount).QuoTruncate(sdk.NewDec(int64(currentEpoch.Duration.Seconds()))) - // Check that gauge distribution state is updated. - s.ValidateDistributedGauge(gaugeId, 1, totalDistributedCoins) + // Check that gauge distribution state is updated. + s.ValidateDistributedGauge(gaugeId, 1, tc.gaugeCoins) - // check every parameter in incentiveRecord so that it matches what we created - s.Require().Equal(poolId, incentiveRecord.PoolId) - s.Require().Equal(defaultRewardDenom, incentiveRecord.IncentiveDenom) - s.Require().Equal(s.App.AccountKeeper.GetModuleAddress(types.ModuleName).String(), incentiveRecord.IncentiveCreatorAddr) - s.Require().Equal(expectedEmissionRate, incentiveRecord.GetIncentiveRecordBody().EmissionRate) - s.Require().Equal(gauge.StartTime, incentiveRecord.GetIncentiveRecordBody().StartTime) - s.Require().Equal(types.DefaultConcentratedUptime, incentiveRecord.MinUptime) - s.Require().Equal(fiveKRewardCoins.Amount, incentiveRecord.GetIncentiveRecordBody().RemainingAmount.RoundInt()) - } + // check every parameter in incentiveRecord so that it matches what we created + s.Require().Equal(poolId, incentiveRecord.PoolId) + s.Require().Equal(defaultRewardDenom, incentiveRecord.IncentiveDenom) + s.Require().Equal(s.App.AccountKeeper.GetModuleAddress(types.ModuleName).String(), incentiveRecord.IncentiveCreatorAddr) + s.Require().Equal(expectedEmissionRate, incentiveRecord.GetIncentiveRecordBody().EmissionRate) + s.Require().Equal(s.Ctx.BlockTime().UTC().String(), incentiveRecord.GetIncentiveRecordBody().StartTime.UTC().String()) + s.Require().Equal(types.DefaultConcentratedUptime, incentiveRecord.MinUptime) + s.Require().Equal(fiveKRewardCoins.Amount, incentiveRecord.GetIncentiveRecordBody().RemainingAmount.RoundInt()) } } - - // this check is specifically for gauge distribution via locks - for i, addr := range addrs { - bal := s.App.BankKeeper.GetAllBalances(s.Ctx, addr) - s.Require().Equal(tc.expectedDistributions[i].String(), bal.String(), "test %v, person %d", name, i) - } - // check the totalAmount of tokens distributed, for both lock gauges and CL pool gauges s.Require().Equal(tc.expectedDistributions, totalDistributedCoins) } From 24ba68cd902cb10a48f3d7f008558e81b10f998c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 9 Jun 2023 16:57:53 -0400 Subject: [PATCH 44/50] clarifying comment --- x/pool-incentives/keeper/keeper.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 31df910297d..ab9786e28e7 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -110,7 +110,8 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin lockuptypes.QueryCondition{ LockQueryType: lockuptypes.NoLock, Denom: incentivestypes.NoLockInternalGaugeDenom(pool.GetId()), - Duration: incentivesEpoch.Duration, + // We specify this duration so that we can query this duration in the IncentivizedPools() query. + Duration: incentivesEpoch.Duration, }, ctx.BlockTime(), 1, From 02049e6eb06d277c13d8d7290fa0b8625750866e Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 9 Jun 2023 22:43:59 +0000 Subject: [PATCH 45/50] updates --- x/pool-incentives/keeper/keeper.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index ab9786e28e7..95fcb73d69c 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -155,6 +155,9 @@ func (k Keeper) SetPoolGaugeIdNoLock(ctx sdk.Context, poolId uint64, gaugeId uin store.Set(key, sdk.Uint64ToBigEndian(gaugeId)) // Note: this index is used for general linking. + // We supply zero for incentivized duration as "NoLock" gauges are not + // associated with any lockable duration. Instead, they incentivize + // pools directly. key = types.GetPoolIdFromGaugeIdStoreKey(gaugeId, 0) store.Set(key, sdk.Uint64ToBigEndian(poolId)) } From 7499cff5a375a53c472db7f643f10a361cbd0eb4 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 10 Jun 2023 13:12:00 -0400 Subject: [PATCH 46/50] Update x/incentives/keeper/distribute_test.go Co-authored-by: Adam Tucker --- x/incentives/keeper/distribute_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 14f94f4cb6e..2e824c36264 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -672,8 +672,8 @@ func (s *KeeperTestSuite) TestGetModuleDistributedCoins() { s.Require().Equal(coins, distrCoins) } -// TestBuDurationPerpetualGaugeDistribution_NoLockNoOp tests that the creation of a perp gauge that has no locks associated does not distribute any tokens. -func (s *KeeperTestSuite) TestBuDurationPerpetualGaugeDistribution_NoLockNoOp() { +// TestByDurationPerpetualGaugeDistribution_NoLockNoOp tests that the creation of a perp gauge that has no locks associated does not distribute any tokens. +func (s *KeeperTestSuite) TestByDurationPerpetualGaugeDistribution_NoLockNoOp() { s.SetupTest() // setup a perpetual gauge with no associated locks From ffcaa0473cff673deaf2ddf868a27d608dddb90d Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Sat, 10 Jun 2023 17:16:12 +0000 Subject: [PATCH 47/50] error message --- x/incentives/types/msgs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/types/msgs.go b/x/incentives/types/msgs.go index 4b807e9dba1..2f3a105399f 100644 --- a/x/incentives/types/msgs.go +++ b/x/incentives/types/msgs.go @@ -71,7 +71,7 @@ func (m MsgCreateGauge) ValidateBasic() error { } if m.DistributeTo.Duration != 0 { - return fmt.Errorf("no lock gauge must have duration set to 0, was (%d)", m.DistributeTo.Duration) + return fmt.Errorf("'no lock' gauge must have duration set to 0, was (%d)", m.DistributeTo.Duration) } } else { if m.PoolId != 0 { From 692cb862f8a26d80b2ac1d6a085f5ac820fdd266 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Sat, 10 Jun 2023 17:32:18 +0000 Subject: [PATCH 48/50] zero duration check and test --- x/incentives/keeper/distribute_test.go | 11 +++++++++-- x/pool-incentives/keeper/keeper.go | 17 ++++++++++++++--- x/pool-incentives/keeper/keeper_test.go | 25 ++++++++++++++++++++----- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 2e824c36264..1b7d45f5df6 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -422,6 +422,11 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { } withPoolId := func(tc test, poolId uint64) test { + if poolId == defaultBalancerPool { + // If we do not set it, SetPoolGaugeIdInternalIncentive(...) errors with + // "zero duration is invalid" + tc.distrTo.Duration = time.Hour + } tc.poolId = poolId tc.expectErr = true return tc @@ -463,7 +468,8 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { // Force gauge's pool id to balancer to trigger error if tc.poolId == defaultBalancerPool { - s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, defaultBalancerPool, tc.distrTo.Duration, externalGaugeid) + err := s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, defaultBalancerPool, tc.distrTo.Duration, externalGaugeid) + s.Require().NoError(err) } // Activate the gauge. @@ -846,7 +852,8 @@ func (s *KeeperTestSuite) TestGetPoolFromGaugeId() { } if tc.shouldSetPoolGaugeId { - s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, validPoolId, duration, poolIdOne) + err := s.App.PoolIncentivesKeeper.SetPoolGaugeIdInternalIncentive(s.Ctx, validPoolId, duration, poolIdOne) + s.Require().NoError(err) } pool, err := s.App.IncentivesKeeper.GetPoolFromGaugeId(s.Ctx, tc.gaugeId, duration) diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 95fcb73d69c..863415878aa 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -84,7 +84,9 @@ func (k Keeper) CreateLockablePoolGauges(ctx sdk.Context, poolId uint64) error { return err } - k.SetPoolGaugeIdInternalIncentive(ctx, poolId, lockableDuration, gaugeId) + if err := k.SetPoolGaugeIdInternalIncentive(ctx, poolId, lockableDuration, gaugeId); err != nil { + return err + } } return nil } @@ -125,14 +127,21 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin // we create an additional "ByDuration" link here for tracking // internal incentive "NoLock" gauges incentivesEpochDuration := k.incentivesKeeper.GetEpochInfo(ctx).Duration - k.SetPoolGaugeIdInternalIncentive(ctx, poolId, incentivesEpochDuration, gaugeId) + if err := k.SetPoolGaugeIdInternalIncentive(ctx, poolId, incentivesEpochDuration, gaugeId); err != nil { + return err + } return nil } // SetPoolGaugeIdInternalIncentive sets the gauge id for the pool and internally incentivized duration. +// Returns error if the incentivized duration is zero. // CONTRACT: this link is created only for the internally incentivized gauges. -func (k Keeper) SetPoolGaugeIdInternalIncentive(ctx sdk.Context, poolId uint64, incentivizedDuration time.Duration, gaugeId uint64) { +func (k Keeper) SetPoolGaugeIdInternalIncentive(ctx sdk.Context, poolId uint64, incentivizedDuration time.Duration, gaugeId uint64) error { + if incentivizedDuration == 0 { + return fmt.Errorf("incentivized duration cannot be zero, pool id: %d", poolId) + } + // Note: this index is used for internal incentive gauges only. key := types.GetPoolGaugeIdInternalStoreKey(poolId, incentivizedDuration) store := ctx.KVStore(k.storeKey) @@ -141,6 +150,8 @@ func (k Keeper) SetPoolGaugeIdInternalIncentive(ctx sdk.Context, poolId uint64, // Note: this index is used for general linking. key = types.GetPoolIdFromGaugeIdStoreKey(gaugeId, incentivizedDuration) store.Set(key, sdk.Uint64ToBigEndian(poolId)) + + return nil } // SetPoolGaugeIdNoLock sets the link between pool id and gauge id for "NoLock" gauges. diff --git a/x/pool-incentives/keeper/keeper_test.go b/x/pool-incentives/keeper/keeper_test.go index 38a771905b9..23c3b758f17 100644 --- a/x/pool-incentives/keeper/keeper_test.go +++ b/x/pool-incentives/keeper/keeper_test.go @@ -112,11 +112,12 @@ func (s *KeeperTestSuite) TestCreateLockablePoolGauges() { durations := s.App.PoolIncentivesKeeper.GetLockableDurations(s.Ctx) tests := []struct { - name string - poolId uint64 - expectedGaugeDurations []time.Duration - expectedGaugeIds []uint64 - expectedErr bool + name string + poolId uint64 + isInvalidLockableDuration bool + expectedGaugeDurations []time.Duration + expectedGaugeIds []uint64 + expectedErr bool }{ { name: "Create Gauge with valid PoolId", @@ -132,13 +133,27 @@ func (s *KeeperTestSuite) TestCreateLockablePoolGauges() { expectedGaugeIds: []uint64{}, expectedErr: true, }, + { + name: "error: invalid lockable duration", + poolId: uint64(1), + isInvalidLockableDuration: true, + + expectedErr: true, + }, } for _, tc := range tests { s.Run(tc.name, func() { s.SetupTest() + poolId := s.PrepareBalancerPool() + // This should trigger error when creating a pool id <> gauge id internal incentive link. + if tc.isInvalidLockableDuration { + durations = []time.Duration{time.Duration(0)} + s.App.PoolIncentivesKeeper.SetLockableDurations(s.Ctx, durations) + } + err := s.App.PoolIncentivesKeeper.CreateLockablePoolGauges(s.Ctx, tc.poolId) if tc.expectedErr { s.Require().Error(err) From a701132a74faab38aebfd7220615984521e2beac Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Sat, 10 Jun 2023 17:43:30 +0000 Subject: [PATCH 49/50] clean up --- x/pool-incentives/keeper/keeper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index 863415878aa..36a8e1910ce 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -103,6 +103,7 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin } incentivesEpoch := k.incentivesKeeper.GetEpochInfo(ctx) + incentivesEpochDuration := incentivesEpoch.Duration gaugeId, err := k.incentivesKeeper.CreateGauge( ctx, @@ -113,7 +114,7 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin LockQueryType: lockuptypes.NoLock, Denom: incentivestypes.NoLockInternalGaugeDenom(pool.GetId()), // We specify this duration so that we can query this duration in the IncentivizedPools() query. - Duration: incentivesEpoch.Duration, + Duration: incentivesEpochDuration, }, ctx.BlockTime(), 1, @@ -126,7 +127,6 @@ func (k Keeper) CreateConcentratedLiquidityPoolGauge(ctx sdk.Context, poolId uin // Although the pool id <> gauge "NoLock" link is created in CreateGauge, // we create an additional "ByDuration" link here for tracking // internal incentive "NoLock" gauges - incentivesEpochDuration := k.incentivesKeeper.GetEpochInfo(ctx).Duration if err := k.SetPoolGaugeIdInternalIncentive(ctx, poolId, incentivesEpochDuration, gaugeId); err != nil { return err } From 7cdf7aea3fcaca17bbe0e34ccb7d93af941dcec6 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Sat, 10 Jun 2023 19:05:58 +0000 Subject: [PATCH 50/50] lint --- tests/cl-genesis-positions/go.mod | 5 ++--- tests/cl-genesis-positions/go.sum | 4 ---- x/pool-incentives/keeper/genesis.go | 4 +++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/cl-genesis-positions/go.mod b/tests/cl-genesis-positions/go.mod index 07e23dc9dbf..b6e9e4febce 100644 --- a/tests/cl-genesis-positions/go.mod +++ b/tests/cl-genesis-positions/go.mod @@ -7,11 +7,11 @@ require ( github.com/ignite/cli v0.23.0 github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230516205127-c213fddde069 github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230529060317-d6d2dda0fb22 - // this commit points to https://github.com/osmosis-labs/osmosis/commit/b764323ce7702185d2089b9e76a0115c7058f37e - github.com/osmosis-labs/osmosis/v15 v15.0.0-20230601221251-b764323ce770 github.com/tendermint/tendermint v0.34.26 ) +require github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e + require ( cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect @@ -97,7 +97,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e // indirect github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 // indirect github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230331072320-5d6f6cfa2627 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect diff --git a/tests/cl-genesis-positions/go.sum b/tests/cl-genesis-positions/go.sum index fb39317977c..7182cb466db 100644 --- a/tests/cl-genesis-positions/go.sum +++ b/tests/cl-genesis-positions/go.sum @@ -699,10 +699,6 @@ github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230516205127-c213fddde06 github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230516205127-c213fddde069/go.mod h1:a7lhiXRpn8QJ21OhFpaEnUNErTSIafaYpp02q6uI/Dk= github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230529060317-d6d2dda0fb22 h1:I14d+U4gDSL5dHoQ3G+kGLhZP5Zj3mOxMb/97Xflusc= github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230529060317-d6d2dda0fb22/go.mod h1:GIvgXqD8NOtrpu5bJ052tZxWLFj4ekpi1BMwEHIvXVU= -github.com/osmosis-labs/osmosis/v15 v15.0.0-20230601221251-b764323ce770 h1:to+kNe/UeQwFpYHMqOkdY7TGIjt5mp+QxIniNRDzDUc= -github.com/osmosis-labs/osmosis/v15 v15.0.0-20230601221251-b764323ce770/go.mod h1:ww2QtVh+XW91yTpbyfM4mJBmIRk4AU0PigLL94zr0+8= -github.com/osmosis-labs/osmosis/v16 v16.0.0-20230602200356-bdf5b96b3674 h1:sCXD8SajkGC3AmHrwiklgTWHZUXcSHm8YFJKbvw6Lk4= -github.com/osmosis-labs/osmosis/v16 v16.0.0-20230602200356-bdf5b96b3674/go.mod h1:Vg+05vXFc682OEF52HTqhEKF+deQ0GSt9rkisCFJ8Ug= github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e h1:Rbkpe0cLh67eyWpCMN8u/6xDNHlWimrLceMEhtNJ0TI= github.com/osmosis-labs/osmosis/v16 v16.0.0-20230603032959-4d2ba21b8d1e/go.mod h1:Vg+05vXFc682OEF52HTqhEKF+deQ0GSt9rkisCFJ8Ug= github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 h1:RIrWLzIiZN5Xd2JOfSOtGZaf6V3qEQYg6EaDTAkMnCo= diff --git a/x/pool-incentives/keeper/genesis.go b/x/pool-incentives/keeper/genesis.go index fddea6abb9b..2acd9f11451 100644 --- a/x/pool-incentives/keeper/genesis.go +++ b/x/pool-incentives/keeper/genesis.go @@ -23,7 +23,9 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { if record.Duration == 0 { k.SetPoolGaugeIdNoLock(ctx, record.PoolId, record.GaugeId) } else { - k.SetPoolGaugeIdInternalIncentive(ctx, record.PoolId, record.Duration, record.GaugeId) + if err := k.SetPoolGaugeIdInternalIncentive(ctx, record.PoolId, record.Duration, record.GaugeId); err != nil { + panic(err) + } } } }