From 440180dee4964dcab42ee7c62cefc88994b85307 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Wed, 10 Apr 2024 13:24:35 +0300 Subject: [PATCH 01/20] whitelist auction MsgUpdateParams --- app/proposals_allowlisting.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/proposals_allowlisting.go b/app/proposals_allowlisting.go index 356ae4f77..bf3e8b5de 100644 --- a/app/proposals_allowlisting.go +++ b/app/proposals_allowlisting.go @@ -17,6 +17,7 @@ import ( ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" ccvconsumertypes "github.com/cosmos/interchain-security/v4/x/ccv/consumer/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" + auctiontypes "github.com/skip-mev/block-sdk/x/auction/types" contractmanagertypes "github.com/neutron-org/neutron/v3/x/contractmanager/types" crontypes "github.com/neutron-org/neutron/v3/x/cron/types" @@ -74,6 +75,7 @@ func isSdkMessageWhitelisted(msg sdk.Msg) bool { *crisistypes.MsgUpdateParams, *minttypes.MsgUpdateParams, *pfmtypes.MsgUpdateParams, + *auctiontypes.MsgUpdateParams, *authtypes.MsgUpdateParams: return true } From 2c96ad0f56e0eba36b891a2f1349010e38f1a7d3 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Thu, 11 Apr 2024 19:20:40 +0300 Subject: [PATCH 02/20] proper tokenfactory init --- app/app.go | 2 +- x/tokenfactory/keeper/admins_test.go | 15 ++++ x/tokenfactory/keeper/before_send_test.go | 72 ++++++++++++++++++ .../keeper/testdata/balance_tracker.wasm | Bin 0 -> 173300 bytes 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 x/tokenfactory/keeper/before_send_test.go create mode 100644 x/tokenfactory/keeper/testdata/balance_tracker.wasm diff --git a/app/app.go b/app/app.go index 9e4f84fe8..e0228c9e9 100644 --- a/app/app.go +++ b/app/app.go @@ -606,7 +606,7 @@ func New( appCodec, app.keys[tokenfactorytypes.StoreKey], app.AccountKeeper, - app.BankKeeper.WithMintCoinsRestriction(tokenfactorytypes.NewTokenFactoryDenomMintCoinsRestriction()), + &app.BankKeeper, &app.WasmKeeper, authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go index 2d5854cc2..b19abb6f0 100644 --- a/x/tokenfactory/keeper/admins_test.go +++ b/x/tokenfactory/keeper/admins_test.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/v3/app/params" "github.com/neutron-org/neutron/v3/x/tokenfactory/types" ) @@ -109,6 +110,13 @@ func (suite *KeeperTestSuite) TestMintDenom() { admin: suite.TestAccs[0].String(), valid: true, }, + { + desc: "error: try minting non-tokenfactory denom", + amount: 10, + mintDenom: params.DefaultDenom, + admin: suite.TestAccs[0].String(), + valid: false, + }, } { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account @@ -171,6 +179,13 @@ func (suite *KeeperTestSuite) TestBurnDenom() { admin: suite.TestAccs[0].String(), valid: true, }, + { + desc: "fail case - burn non-tokenfactory denom", + amount: 10, + burnDenom: params.DefaultDenom, + admin: suite.TestAccs[0].String(), + valid: false, + }, } { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account diff --git a/x/tokenfactory/keeper/before_send_test.go b/x/tokenfactory/keeper/before_send_test.go new file mode 100644 index 000000000..16fdee9dc --- /dev/null +++ b/x/tokenfactory/keeper/before_send_test.go @@ -0,0 +1,72 @@ +package keeper_test + +import ( + "encoding/json" + "fmt" + "os" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/neutron-org/neutron/v3/x/tokenfactory/types" +) + +func (suite *KeeperTestSuite) TestTrackBeforeSendWasm() { + for _, tc := range []struct { + name string + wasmFile string + }{ + { + name: "Test bank hook tracking contract ", + // https://github.com/neutron-org/neutron-dev-contracts/tree/feat/balance-tracker-contract/contracts/balance-tracker + wasmFile: "./testdata/balance_tracker.wasm", + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + // setup test + suite.Setup() + + senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() + suite.TopUpWallet(suite.ChainA.GetContext(), senderAddress, suite.TestAccs[0]) + + // load wasm file + wasmCode, err := os.ReadFile(tc.wasmFile) + suite.Require().NoError(err) + + // create new denom + res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "testdenom")) + suite.Require().NoError(err, "test: %v", tc.name) + factoryDenom := res.GetNewTokenDenom() + + // instantiate wasm code + tokenFactoryModuleAddr := suite.GetNeutronZoneApp(suite.ChainA).AccountKeeper.GetModuleAddress("tokenfactory") + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(suite.GetNeutronZoneApp(suite.ChainA).WasmKeeper) + codeID, _, err := contractKeeper.Create(suite.ChainA.GetContext(), suite.TestAccs[0], wasmCode, nil) + suite.Require().NoError(err, "test: %v", tc.name) + initMsg, _ := json.Marshal( + map[string]interface{}{ + "tracked_denom": factoryDenom, + "tokenfactory_module_address": tokenFactoryModuleAddr, + }, + ) + cosmwasmAddress, _, err := contractKeeper.Instantiate( + suite.ChainA.GetContext(), codeID, suite.TestAccs[0], suite.TestAccs[0], initMsg, "", sdk.NewCoins(), + ) + suite.Require().NoError(err, "test: %v", tc.name) + + // set beforesend hook to the new denom + _, err = suite.msgServer.SetBeforeSendHook(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgSetBeforeSendHook(suite.TestAccs[0].String(), factoryDenom, cosmwasmAddress.String())) + suite.Require().NoError(err, "test: %v", tc.name) + + tokenToSend := sdk.NewCoin(factoryDenom, sdk.NewInt(100)) + + // mint tokens + _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(suite.TestAccs[0].String(), tokenToSend)) + suite.Require().NoError(err) + + queryResp, err := suite.GetNeutronZoneApp(suite.ChainA).WasmKeeper.QuerySmart(suite.ChainA.GetContext(), cosmwasmAddress, []byte(`{"total_supply_at":{}}`)) + suite.Require().NoError(err) + suite.Require().Equal("\"100\"", string(queryResp)) + }) + } +} diff --git a/x/tokenfactory/keeper/testdata/balance_tracker.wasm b/x/tokenfactory/keeper/testdata/balance_tracker.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ffc86bc405daee60d12ca5e0d51ad1c7b523d653 GIT binary patch literal 173300 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 OZaIZ(Jy)@{_WuDYvY)~L literal 0 HcmV?d00001 From 571db9e96080a217f0c3b93f17b1906e0f8e048a Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Fri, 12 Apr 2024 16:59:41 +0300 Subject: [PATCH 03/20] empty v3.0.3 upgrade handler --- app/app.go | 3 ++- app/upgrades/v3.0.3/constants.go | 15 +++++++++++++++ app/upgrades/v3.0.3/upgrades.go | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 app/upgrades/v3.0.3/constants.go create mode 100644 app/upgrades/v3.0.3/upgrades.go diff --git a/app/app.go b/app/app.go index e0228c9e9..e60d60b4c 100644 --- a/app/app.go +++ b/app/app.go @@ -22,6 +22,7 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + v303 "github.com/neutron-org/neutron/v3/app/upgrades/v3.0.3" "github.com/neutron-org/neutron/v3/docs" "github.com/neutron-org/neutron/v3/app/upgrades" @@ -183,7 +184,7 @@ const ( ) var ( - Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade} + Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade, v303.Upgrade} // DefaultNodeHome default home directories for the application daemon DefaultNodeHome string diff --git a/app/upgrades/v3.0.3/constants.go b/app/upgrades/v3.0.3/constants.go new file mode 100644 index 000000000..ab772cefd --- /dev/null +++ b/app/upgrades/v3.0.3/constants.go @@ -0,0 +1,15 @@ +package v303 + +import ( + "github.com/neutron-org/neutron/v3/app/upgrades" +) + +const ( + // UpgradeName defines the on-chain upgrade name. + UpgradeName = "v3.0.3" +) + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, +} diff --git a/app/upgrades/v3.0.3/upgrades.go b/app/upgrades/v3.0.3/upgrades.go new file mode 100644 index 000000000..5fb92d8ac --- /dev/null +++ b/app/upgrades/v3.0.3/upgrades.go @@ -0,0 +1,25 @@ +package v303 + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + "github.com/neutron-org/neutron/v3/app/upgrades" +) + +func CreateUpgradeHandler( + _ *module.Manager, + _ module.Configurator, + _ *upgrades.UpgradeKeepers, + _ upgrades.StoreKeys, + _ codec.Codec, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx.Logger().Info(fmt.Sprintf("Empty migration {%s} applied", UpgradeName)) + return vm, nil + } +} From 44f87d32d8e0cc86c2c8d7603b0a9093326c6b4a Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 16 Apr 2024 16:58:14 +0400 Subject: [PATCH 04/20] improve tests --- app/app.go | 1 + x/tokenfactory/keeper/bankactions.go | 26 ++++++++++++++++++++++++++ x/tokenfactory/keeper/keeper.go | 9 +++++++++ x/tokenfactory/keeper/keeper_test.go | 24 +++++++++++++++++++++++- 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index e60d60b4c..4c16ea85d 100644 --- a/app/app.go +++ b/app/app.go @@ -606,6 +606,7 @@ func New( tokenFactoryKeeper := tokenfactorykeeper.NewKeeper( appCodec, app.keys[tokenfactorytypes.StoreKey], + maccPerms, app.AccountKeeper, &app.BankKeeper, &app.WasmKeeper, diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index d79f97883..30146f227 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -1,7 +1,11 @@ package keeper import ( + "sort" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/neutron-org/neutron/v3/x/tokenfactory/types" ) @@ -58,6 +62,28 @@ func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr, toAddr return err } + fromAcc, err := sdk.AccAddressFromBech32(fromAddr) + if err != nil { + return err + } + + sortedPermAddrs := make([]string, 0, len(k.permAddrs)) + for moduleName := range k.permAddrs { + sortedPermAddrs = append(sortedPermAddrs, moduleName) + } + sort.Strings(sortedPermAddrs) + + for _, moduleName := range sortedPermAddrs { + account := k.accountKeeper.GetModuleAccount(ctx, moduleName) + if account == nil { + return status.Errorf(codes.NotFound, "account %s not found", moduleName) + } + + if account.GetAddress().Equals(fromAcc) { + return status.Errorf(codes.Internal, "send from module acc not available") + } + } + fromSdkAddr, err := sdk.AccAddressFromBech32(fromAddr) if err != nil { return err diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go index 8a2d0958d..56f9424d9 100644 --- a/x/tokenfactory/keeper/keeper.go +++ b/x/tokenfactory/keeper/keeper.go @@ -5,6 +5,7 @@ import ( "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -16,6 +17,7 @@ import ( type ( Keeper struct { storeKey storetypes.StoreKey + permAddrs map[string]authtypes.PermissionsForAddress cdc codec.Codec accountKeeper types.AccountKeeper bankKeeper types.BankKeeper @@ -28,14 +30,21 @@ type ( func NewKeeper( cdc codec.Codec, storeKey storetypes.StoreKey, + maccPerms map[string][]string, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, contractKeeper types.ContractKeeper, authority string, ) Keeper { + permAddrs := make(map[string]authtypes.PermissionsForAddress) + for name, perms := range maccPerms { + permAddrs[name] = authtypes.NewPermissionsForAddress(name, perms) + } + return Keeper{ cdc: cdc, storeKey: storeKey, + permAddrs: permAddrs, accountKeeper: accountKeeper, bankKeeper: bankKeeper, contractKeeper: contractKeeper, diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go index f80dc06ec..15c4bd778 100644 --- a/x/tokenfactory/keeper/keeper_test.go +++ b/x/tokenfactory/keeper/keeper_test.go @@ -1,12 +1,13 @@ package keeper_test import ( + "fmt" "testing" "cosmossdk.io/math" - "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/cosmos-sdk/baseapp" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/stretchr/testify/suite" @@ -106,3 +107,24 @@ func CreateRandomAccounts(numAccts int) []sdktypes.AccAddress { return testAddrs } + +func (s *KeeperTestSuite) TestForceTransferMsg() { + s.Setup() + + // Create a denom + s.CreateDefaultDenom(s.ChainA.GetContext()) + + s.Run(fmt.Sprintf("test force transfer"), func() { + mintAmt := sdktypes.NewInt64Coin(s.defaultDenom, 10) + + _, err := s.msgServer.Mint(sdktypes.WrapSDKContext(s.ChainA.GetContext()), types.NewMsgMint(s.TestAccs[0].String(), mintAmt)) + + govModAcc := s.GetNeutronZoneApp(s.ChainA).AccountKeeper.GetModuleAccount(s.ChainA.GetContext(), authtypes.FeeCollectorName) + + err = s.GetNeutronZoneApp(s.ChainA).BankKeeper.SendCoins(s.ChainA.GetContext(), s.TestAccs[0], govModAcc.GetAddress(), sdktypes.NewCoins(mintAmt)) + s.Require().NoError(err) + + _, err = s.msgServer.ForceTransfer(s.ChainA.GetContext(), types.NewMsgForceTransfer(s.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String(), s.TestAccs[1].String())) + s.Require().ErrorContains(err, "send from module acc not available") + }) +} From b37ac6637ce459b8ad4a8da2aa547a825ae99b8c Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 16 Apr 2024 19:13:04 +0400 Subject: [PATCH 05/20] v3.0.4 upgrade handler --- app/app.go | 4 ++-- app/upgrades/{v3.0.3 => v3.0.4}/constants.go | 4 ++-- app/upgrades/{v3.0.3 => v3.0.4}/upgrades.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/upgrades/{v3.0.3 => v3.0.4}/constants.go (86%) rename app/upgrades/{v3.0.3 => v3.0.4}/upgrades.go (97%) diff --git a/app/app.go b/app/app.go index 4c16ea85d..2063ff4cf 100644 --- a/app/app.go +++ b/app/app.go @@ -22,7 +22,7 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - v303 "github.com/neutron-org/neutron/v3/app/upgrades/v3.0.3" + v304 "github.com/neutron-org/neutron/v3/app/upgrades/v3.0.4" "github.com/neutron-org/neutron/v3/docs" "github.com/neutron-org/neutron/v3/app/upgrades" @@ -184,7 +184,7 @@ const ( ) var ( - Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade, v303.Upgrade} + Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade, v304.Upgrade} // DefaultNodeHome default home directories for the application daemon DefaultNodeHome string diff --git a/app/upgrades/v3.0.3/constants.go b/app/upgrades/v3.0.4/constants.go similarity index 86% rename from app/upgrades/v3.0.3/constants.go rename to app/upgrades/v3.0.4/constants.go index ab772cefd..7665fc132 100644 --- a/app/upgrades/v3.0.3/constants.go +++ b/app/upgrades/v3.0.4/constants.go @@ -1,4 +1,4 @@ -package v303 +package v304 import ( "github.com/neutron-org/neutron/v3/app/upgrades" @@ -6,7 +6,7 @@ import ( const ( // UpgradeName defines the on-chain upgrade name. - UpgradeName = "v3.0.3" + UpgradeName = "v3.0.4" ) var Upgrade = upgrades.Upgrade{ diff --git a/app/upgrades/v3.0.3/upgrades.go b/app/upgrades/v3.0.4/upgrades.go similarity index 97% rename from app/upgrades/v3.0.3/upgrades.go rename to app/upgrades/v3.0.4/upgrades.go index 5fb92d8ac..14ebda302 100644 --- a/app/upgrades/v3.0.3/upgrades.go +++ b/app/upgrades/v3.0.4/upgrades.go @@ -1,4 +1,4 @@ -package v303 +package v304 import ( "fmt" From d77113aa403e5879233b87b3ae189fdf3cb1cf38 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Mon, 22 Apr 2024 17:17:31 +0300 Subject: [PATCH 06/20] enable block-sdk services --- app/app.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/app.go b/app/app.go index 9e4f84fe8..1faaf3743 100644 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,7 @@ import ( ibctestingtypes "github.com/cosmos/ibc-go/v7/testing/types" "github.com/cosmos/interchain-security/v4/testutil/integration" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" + "github.com/skip-mev/block-sdk/block/service" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -1311,6 +1312,9 @@ func (app *App) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig // Register new tendermint queries routes from grpc-gateway. tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + // Register new block-sdk queries routes from grpc-gateway. + service.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register app's swagger ui @@ -1322,6 +1326,13 @@ func (app *App) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig // RegisterTxService implements the Application.RegisterTxService method. func (app *App) RegisterTxService(clientCtx client.Context) { authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry) + + // Register the Block SDK mempool transaction service. + mempool, ok := app.BaseApp.Mempool().(blocksdk.Mempool) + if !ok { + panic("mempool is not a block.Mempool") + } + service.RegisterMempoolService(app.GRPCQueryRouter(), mempool) } // RegisterTendermintService implements the Application.RegisterTendermintService method. From 240a96de222a181be6a4b9e907799932fe33d5bc Mon Sep 17 00:00:00 2001 From: Nikhil Vasan Date: Mon, 22 Apr 2024 15:54:51 -0700 Subject: [PATCH 07/20] update block-sdk version --- go.mod | 23 +++++++++++++---------- go.sum | 55 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index e596184b9..562b7a410 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/CosmWasm/wasmvm v1.5.2 github.com/armon/go-metrics v0.4.1 github.com/cometbft/cometbft v0.37.4 - github.com/cometbft/cometbft-db v0.9.1 + github.com/cometbft/cometbft-db v0.11.0 github.com/cosmos/admin-module v0.0.0-20220204080909-475a98e03f31 github.com/cosmos/cosmos-proto v1.0.0-beta.4 github.com/cosmos/cosmos-sdk v0.47.10 @@ -33,13 +33,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 github.com/rs/zerolog v1.32.0 - github.com/skip-mev/block-sdk v1.4.0 + github.com/skip-mev/block-sdk v1.4.2 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 google.golang.org/grpc v1.62.0 google.golang.org/protobuf v1.32.0 @@ -59,6 +59,7 @@ require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect + github.com/DataDog/zstd v1.5.0 // indirect github.com/aws/aws-sdk-go v1.44.224 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -70,9 +71,11 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/cockroachdb/errors v1.10.0 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.0 // indirect github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect @@ -138,7 +141,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/linxGnu/grocksdb v1.8.6 // indirect + github.com/linxGnu/grocksdb v1.8.12 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -160,7 +163,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.8.3 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -183,12 +186,12 @@ require ( go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/mock v0.2.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.155.0 // indirect diff --git a/go.sum b/go.sum index c0793c6bc..0f307159f 100644 --- a/go.sum +++ b/go.sum @@ -221,6 +221,7 @@ github.com/CosmWasm/wasmvm v1.5.2 h1:+pKB1Mz9GZVt1vadxB+EDdD1FOz3dMNjIKq/58/lrag github.com/CosmWasm/wasmvm v1.5.2/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= @@ -359,20 +360,26 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU= -github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= +github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= github.com/cometbft/cometbft v0.37.4 h1:xyvvEqlyfK8MgNIIKVJaMsuIp03wxOcFmVkT26+Ikpg= github.com/cometbft/cometbft v0.37.4/go.mod h1:Cmg5Hp4sNpapm7j+x0xRyt2g0juQfmB752ous+pA0G8= -github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= -github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/cometbft/cometbft-db v0.11.0 h1:M3Lscmpogx5NTbb1EGyGDaFRdsoLWrUWimFEyf7jej8= +github.com/cometbft/cometbft-db v0.11.0/go.mod h1:GDPJAC/iFHNjmZZPN8V8C1yr/eyityhi2W1hz2MGKSc= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= @@ -878,8 +885,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linxGnu/grocksdb v1.8.6 h1:O7I6SIGPrypf3f/gmrrLUBQDKfO8uOoYdWf4gLS06tc= -github.com/linxGnu/grocksdb v1.8.6/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= +github.com/linxGnu/grocksdb v1.8.12 h1:1/pCztQUOa3BX/1gR3jSZDoaKFpeHFvQ1XrqZpSvZVo= +github.com/linxGnu/grocksdb v1.8.12/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -994,8 +1001,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1088,8 +1095,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1120,8 +1127,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skip-mev/block-sdk v1.4.0 h1:j2wEooUDA74ed+FjCDI3I/aPArdYxKLG5asb6C+so2M= -github.com/skip-mev/block-sdk v1.4.0/go.mod h1:Yv+gQqRh41bCbWC0Bpau8DBE7UwxxPfGmNVbtVrgWAo= +github.com/skip-mev/block-sdk v1.4.2 h1:oNXPnNjeSEthCcdMd7L0aB+6/v4W4ydfpIm2miekJ/U= +github.com/skip-mev/block-sdk v1.4.2/go.mod h1:rVPgr1X5tMyOwcPxNalJEDErgC4ynVbVQdmI89Txhv4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1290,8 +1297,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1318,8 +1325,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1384,8 +1391,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1532,14 +1539,14 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1622,8 +1629,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From cba5ad9cdd497c22c768c5054c8817da7cf1b711 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 23 Apr 2024 14:54:03 +0300 Subject: [PATCH 08/20] linter --- x/tokenfactory/keeper/keeper_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go index 15c4bd778..01744e262 100644 --- a/x/tokenfactory/keeper/keeper_test.go +++ b/x/tokenfactory/keeper/keeper_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "testing" "cosmossdk.io/math" @@ -108,23 +107,24 @@ func CreateRandomAccounts(numAccts int) []sdktypes.AccAddress { return testAddrs } -func (s *KeeperTestSuite) TestForceTransferMsg() { - s.Setup() +func (suite *KeeperTestSuite) TestForceTransferMsg() { + suite.Setup() // Create a denom - s.CreateDefaultDenom(s.ChainA.GetContext()) + suite.CreateDefaultDenom(suite.ChainA.GetContext()) - s.Run(fmt.Sprintf("test force transfer"), func() { - mintAmt := sdktypes.NewInt64Coin(s.defaultDenom, 10) + suite.Run("test force transfer", func() { + mintAmt := sdktypes.NewInt64Coin(suite.defaultDenom, 10) - _, err := s.msgServer.Mint(sdktypes.WrapSDKContext(s.ChainA.GetContext()), types.NewMsgMint(s.TestAccs[0].String(), mintAmt)) + _, err := suite.msgServer.Mint(sdktypes.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(suite.TestAccs[0].String(), mintAmt)) + suite.Require().NoError(err) - govModAcc := s.GetNeutronZoneApp(s.ChainA).AccountKeeper.GetModuleAccount(s.ChainA.GetContext(), authtypes.FeeCollectorName) + govModAcc := suite.GetNeutronZoneApp(suite.ChainA).AccountKeeper.GetModuleAccount(suite.ChainA.GetContext(), authtypes.FeeCollectorName) - err = s.GetNeutronZoneApp(s.ChainA).BankKeeper.SendCoins(s.ChainA.GetContext(), s.TestAccs[0], govModAcc.GetAddress(), sdktypes.NewCoins(mintAmt)) - s.Require().NoError(err) + err = suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.SendCoins(suite.ChainA.GetContext(), suite.TestAccs[0], govModAcc.GetAddress(), sdktypes.NewCoins(mintAmt)) + suite.Require().NoError(err) - _, err = s.msgServer.ForceTransfer(s.ChainA.GetContext(), types.NewMsgForceTransfer(s.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String(), s.TestAccs[1].String())) - s.Require().ErrorContains(err, "send from module acc not available") + _, err = suite.msgServer.ForceTransfer(suite.ChainA.GetContext(), types.NewMsgForceTransfer(suite.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String(), suite.TestAccs[1].String())) + suite.Require().ErrorContains(err, "send from module acc not available") }) } From a9cb2b0879db4762dd52bf308c5e310ff56d9016 Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 23 Apr 2024 15:21:21 +0300 Subject: [PATCH 09/20] added uphrade hander --- app/app.go | 4 +++- app/upgrades/v3.0.5/constants.go | 15 +++++++++++++++ app/upgrades/v3.0.5/upgrades.go | 25 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 app/upgrades/v3.0.5/constants.go create mode 100644 app/upgrades/v3.0.5/upgrades.go diff --git a/app/app.go b/app/app.go index c9f4ddce6..712341909 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,8 @@ import ( "path/filepath" "reflect" + v305 "github.com/neutron-org/neutron/v3/app/upgrades/v3.0.5" + "github.com/cosmos/cosmos-sdk/testutil/sims" globalfeetypes "github.com/cosmos/gaia/v11/x/globalfee/types" "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/packetforward" @@ -185,7 +187,7 @@ const ( ) var ( - Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade, v304.Upgrade} + Upgrades = []upgrades.Upgrade{v030.Upgrade, v044.Upgrade, v200.Upgrade, v202.Upgrade, v301.Upgrade, v304.Upgrade, v305.Upgrade} // DefaultNodeHome default home directories for the application daemon DefaultNodeHome string diff --git a/app/upgrades/v3.0.5/constants.go b/app/upgrades/v3.0.5/constants.go new file mode 100644 index 000000000..8248917d7 --- /dev/null +++ b/app/upgrades/v3.0.5/constants.go @@ -0,0 +1,15 @@ +package v305 + +import ( + "github.com/neutron-org/neutron/v3/app/upgrades" +) + +const ( + // UpgradeName defines the on-chain upgrade name. + UpgradeName = "v3.0.4" +) + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, +} diff --git a/app/upgrades/v3.0.5/upgrades.go b/app/upgrades/v3.0.5/upgrades.go new file mode 100644 index 000000000..135aa5815 --- /dev/null +++ b/app/upgrades/v3.0.5/upgrades.go @@ -0,0 +1,25 @@ +package v305 + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + "github.com/neutron-org/neutron/v3/app/upgrades" +) + +func CreateUpgradeHandler( + _ *module.Manager, + _ module.Configurator, + _ *upgrades.UpgradeKeepers, + _ upgrades.StoreKeys, + _ codec.Codec, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx.Logger().Info(fmt.Sprintf("Empty migration {%s} applied", UpgradeName)) + return vm, nil + } +} From 90f4fc4f5bc29a66eaa93cecf290afbb4c6cb46d Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 23 Apr 2024 15:44:44 +0300 Subject: [PATCH 10/20] fixed upgrade name --- app/upgrades/v3.0.5/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/upgrades/v3.0.5/constants.go b/app/upgrades/v3.0.5/constants.go index 8248917d7..23cbb1cc3 100644 --- a/app/upgrades/v3.0.5/constants.go +++ b/app/upgrades/v3.0.5/constants.go @@ -6,7 +6,7 @@ import ( const ( // UpgradeName defines the on-chain upgrade name. - UpgradeName = "v3.0.4" + UpgradeName = "v3.0.5" ) var Upgrade = upgrades.Upgrade{ From 57bcb5aa2f88db799675d8118e301110afc691b9 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Wed, 15 May 2024 20:46:55 -0300 Subject: [PATCH 11/20] add dex telemetry --- x/dex/keeper/core.go | 4 +++ x/dex/keeper/keeper.go | 42 ++++++++++++++++++++++++++ x/dex/keeper/limit_order_expiration.go | 1 + x/dex/keeper/msg_server.go | 2 ++ 4 files changed, 49 insertions(+) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index 6e0f056fa..5ab5e6b29 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -210,6 +210,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin1}, ) + incWithdrawnAmount(ctx, sdk.Coins{coin1}) if err != nil { return err } @@ -299,6 +300,8 @@ func (k Keeper) MultiHopSwapCore( bestRoute.route, )) + gasConsumed(ctx) + return bestRoute.coinOut, nil } @@ -567,6 +570,7 @@ func (k Keeper) WithdrawFilledLimitOrderCore( coinTakerDenomOut := sdk.NewCoin(tradePairID.TakerDenom, amountOutTokenOut.TruncateInt()) coinMakerDenomRefund := sdk.NewCoin(tradePairID.MakerDenom, remainingTokenIn) coins := sdk.NewCoins(coinTakerDenomOut, coinMakerDenomRefund) + incWithdrawnAmount(ctx, coins) if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, callerAddr, coins); err != nil { return err } diff --git a/x/dex/keeper/keeper.go b/x/dex/keeper/keeper.go index f8826265f..4b1d25e3c 100644 --- a/x/dex/keeper/keeper.go +++ b/x/dex/keeper/keeper.go @@ -2,8 +2,11 @@ package keeper import ( "fmt" + "math/big" + "github.com/armon/go-metrics" "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/telemetry" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -12,6 +15,13 @@ import ( "github.com/neutron-org/neutron/v3/x/dex/types" ) +var ( + MetricLabelWithdrawn = "total_withdrawn" + MetricLabelGasConsumed = "gas_consumed" + MetricLabelTotalOrdersExpired = "total_orders_expired" + MetricLabelTotalLimitOrders = "total_orders_limit" +) + type ( Keeper struct { cdc codec.BinaryCodec @@ -45,3 +55,35 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { func (k Keeper) GetAuthority() string { return k.authority } + +func incWithdrawnAmount(ctx sdk.Context, coins sdk.Coins) { + for _, coin := range coins { + divisor := big.NewInt(6) + f, _ := new(big.Int).Div(coin.Amount.BigInt(), divisor).Float64() // todo check err + telemetry.IncrCounterWithLabels([]string{MetricLabelWithdrawn}, float32(f), []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + telemetry.NewLabel("denom", coin.Denom), + }) + } +} + +func gasConsumed(ctx sdk.Context) { + gas := ctx.GasMeter().GasConsumed() + gasFloat := float32(gas) + telemetry.SetGaugeWithLabels([]string{MetricLabelWithdrawn}, gasFloat, []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + telemetry.NewLabel(MetricLabelGasConsumed, "todo"), + }) +} + +func incExpiredOdrers() { + telemetry.IncrCounterWithLabels([]string{MetricLabelTotalOrdersExpired}, float32(1), []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + }) +} + +func totalLimitOrders() { + telemetry.IncrCounterWithLabels([]string{MetricLabelTotalOrdersExpired}, float32(1), []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + }) +} diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index e9403ada3..d4d20c6d8 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -152,5 +152,6 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { } k.RemoveLimitOrderExpirationByKey(ctx, iterator.Key()) + incExpiredOdrers() } } diff --git a/x/dex/keeper/msg_server.go b/x/dex/keeper/msg_server.go index d0f26f0fc..02194ddca 100644 --- a/x/dex/keeper/msg_server.go +++ b/x/dex/keeper/msg_server.go @@ -119,6 +119,8 @@ func (k MsgServer) PlaceLimitOrder( return &types.MsgPlaceLimitOrderResponse{}, err } + totalLimitOrders() + return &types.MsgPlaceLimitOrderResponse{ TrancheKey: trancheKey, CoinIn: coinIn, From 1b0b6abab45df6092a68939e9dcd18aab0a763a7 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Wed, 15 May 2024 21:23:25 -0300 Subject: [PATCH 12/20] lint --- x/dex/keeper/core.go | 4 ++-- x/dex/keeper/keeper.go | 28 +++++++++++++++++++------- x/dex/keeper/limit_order_expiration.go | 2 +- x/dex/keeper/limit_order_tranche.go | 4 ++++ x/dex/keeper/pool.go | 2 ++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index 5ab5e6b29..d8037bea3 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -210,7 +210,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin1}, ) - incWithdrawnAmount(ctx, sdk.Coins{coin1}) + incWithdrawnAmount(sdk.Coins{coin1}) if err != nil { return err } @@ -570,7 +570,7 @@ func (k Keeper) WithdrawFilledLimitOrderCore( coinTakerDenomOut := sdk.NewCoin(tradePairID.TakerDenom, amountOutTokenOut.TruncateInt()) coinMakerDenomRefund := sdk.NewCoin(tradePairID.MakerDenom, remainingTokenIn) coins := sdk.NewCoins(coinTakerDenomOut, coinMakerDenomRefund) - incWithdrawnAmount(ctx, coins) + incWithdrawnAmount(coins) if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, callerAddr, coins); err != nil { return err } diff --git a/x/dex/keeper/keeper.go b/x/dex/keeper/keeper.go index 4b1d25e3c..1fa243492 100644 --- a/x/dex/keeper/keeper.go +++ b/x/dex/keeper/keeper.go @@ -16,10 +16,12 @@ import ( ) var ( - MetricLabelWithdrawn = "total_withdrawn" - MetricLabelGasConsumed = "gas_consumed" - MetricLabelTotalOrdersExpired = "total_orders_expired" - MetricLabelTotalLimitOrders = "total_orders_limit" + MetricLabelWithdrawn = "total_withdrawn" + MetricLabelGasConsumed = "gas_consumed" + MetricLabelTotalOrdersExpired = "total_orders_expired" + MetricLabelTotalLimitOrders = "total_orders_limit" + MetricLabelTotalTickLiquiditiesInc = "total_tick_liqidities_inc" + MetricLabelTotalTickLiquiditiesDec = "total_tick_liqidities_dec" ) type ( @@ -56,7 +58,7 @@ func (k Keeper) GetAuthority() string { return k.authority } -func incWithdrawnAmount(ctx sdk.Context, coins sdk.Coins) { +func incWithdrawnAmount(coins sdk.Coins) { for _, coin := range coins { divisor := big.NewInt(6) f, _ := new(big.Int).Div(coin.Amount.BigInt(), divisor).Float64() // todo check err @@ -76,14 +78,26 @@ func gasConsumed(ctx sdk.Context) { }) } -func incExpiredOdrers() { +func incExpiredOrders() { telemetry.IncrCounterWithLabels([]string{MetricLabelTotalOrdersExpired}, float32(1), []metrics.Label{ telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), }) } func totalLimitOrders() { - telemetry.IncrCounterWithLabels([]string{MetricLabelTotalOrdersExpired}, float32(1), []metrics.Label{ + telemetry.IncrCounterWithLabels([]string{MetricLabelTotalLimitOrders}, float32(1), []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + }) +} + +func incTotalTickLiquidites() { + telemetry.IncrCounterWithLabels([]string{MetricLabelTotalTickLiquiditiesInc}, float32(1), []metrics.Label{ + telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), + }) +} + +func decTotalTickLiquidites() { + telemetry.IncrCounterWithLabels([]string{MetricLabelTotalTickLiquiditiesDec}, float32(1), []metrics.Label{ telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), }) } diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index d4d20c6d8..78441d486 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -152,6 +152,6 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { } k.RemoveLimitOrderExpirationByKey(ctx, iterator.Key()) - incExpiredOdrers() + incExpiredOrders() } } diff --git a/x/dex/keeper/limit_order_tranche.go b/x/dex/keeper/limit_order_tranche.go index e960f0bd2..e7608b6fa 100644 --- a/x/dex/keeper/limit_order_tranche.go +++ b/x/dex/keeper/limit_order_tranche.go @@ -55,6 +55,7 @@ func (k Keeper) SaveTranche(ctx sdk.Context, tranche *types.LimitOrderTranche) { } else { k.SetInactiveLimitOrderTranche(ctx, tranche) k.RemoveLimitOrderTranche(ctx, tranche.Key) + decTotalTickLiquidites() } ctx.EventManager().EmitEvent(types.CreateTickUpdateLimitOrderTranche(tranche)) @@ -218,6 +219,7 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, &JITGoodTilTime) + incTotalTickLiquidites() case types.LimitOrderType_GOOD_TIL_TIME: limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, @@ -225,9 +227,11 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, goodTil) + incTotalTickLiquidites() default: placeTranche = k.GetPlaceTranche(ctx, tradePairID, tickIndexTakerToMaker) if placeTranche == nil { + incTotalTickLiquidites() limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, diff --git a/x/dex/keeper/pool.go b/x/dex/keeper/pool.go index fa0658801..3aa96ce69 100644 --- a/x/dex/keeper/pool.go +++ b/x/dex/keeper/pool.go @@ -21,6 +21,7 @@ func (k Keeper) GetOrInitPool( return pool, nil } + incTotalTickLiquidites() return k.InitPool(ctx, pairID, centerTickIndexNormalized, fee) } @@ -149,6 +150,7 @@ func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves if reserves.HasToken() { k.SetPoolReserves(ctx, reserves) } else { + decTotalTickLiquidites() k.RemovePoolReserves(ctx, reserves.Key) } } From 8880904e3e29f0e3e5ff2da1b2fa207c49fef58a Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Wed, 5 Jun 2024 17:13:02 -0300 Subject: [PATCH 13/20] emit events instead of telemetry --- x/dex/keeper/core.go | 7 +- x/dex/keeper/keeper.go | 99 ++++++++++++++++---------- x/dex/keeper/limit_order_expiration.go | 2 +- x/dex/keeper/limit_order_tranche.go | 9 +-- x/dex/keeper/msg_server.go | 6 +- x/dex/keeper/pool.go | 2 +- x/dex/types/types.go | 14 ++++ 7 files changed, 92 insertions(+), 47 deletions(-) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index d8037bea3..c9dd70af4 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -196,6 +196,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin0}, ) + ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(sdk.Coins{coin0})) if err != nil { return err } @@ -210,7 +211,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin1}, ) - incWithdrawnAmount(sdk.Coins{coin1}) + ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(sdk.Coins{coin1})) if err != nil { return err } @@ -300,8 +301,6 @@ func (k Keeper) MultiHopSwapCore( bestRoute.route, )) - gasConsumed(ctx) - return bestRoute.coinOut, nil } @@ -570,7 +569,7 @@ func (k Keeper) WithdrawFilledLimitOrderCore( coinTakerDenomOut := sdk.NewCoin(tradePairID.TakerDenom, amountOutTokenOut.TruncateInt()) coinMakerDenomRefund := sdk.NewCoin(tradePairID.MakerDenom, remainingTokenIn) coins := sdk.NewCoins(coinTakerDenomOut, coinMakerDenomRefund) - incWithdrawnAmount(coins) + ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(coins)) if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, callerAddr, coins); err != nil { return err } diff --git a/x/dex/keeper/keeper.go b/x/dex/keeper/keeper.go index 1fa243492..d85f5fd25 100644 --- a/x/dex/keeper/keeper.go +++ b/x/dex/keeper/keeper.go @@ -2,12 +2,9 @@ package keeper import ( "fmt" - "math/big" + "strconv" - "github.com/armon/go-metrics" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/telemetry" - "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,8 +13,8 @@ import ( ) var ( - MetricLabelWithdrawn = "total_withdrawn" - MetricLabelGasConsumed = "gas_consumed" + AttributeWithdrawn = "total_withdrawn" + AttributeGasConsumed = "gas_consumed" MetricLabelTotalOrdersExpired = "total_orders_expired" MetricLabelTotalLimitOrders = "total_orders_limit" MetricLabelTotalTickLiquiditiesInc = "total_tick_liqidities_inc" @@ -58,46 +55,76 @@ func (k Keeper) GetAuthority() string { return k.authority } -func incWithdrawnAmount(coins sdk.Coins) { +func getEventsWithdrawnAmount(coins sdk.Coins) sdk.Events { + events := sdk.Events{} for _, coin := range coins { - divisor := big.NewInt(6) - f, _ := new(big.Int).Div(coin.Amount.BigInt(), divisor).Float64() // todo check err - telemetry.IncrCounterWithLabels([]string{MetricLabelWithdrawn}, float32(f), []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - telemetry.NewLabel("denom", coin.Denom), - }) + event := sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeDenom, coin.Denom), + sdk.NewAttribute(types.AttributeWithdrawn, coin.Amount.String()), + ) + events = append(events, event) } + return events } -func gasConsumed(ctx sdk.Context) { - gas := ctx.GasMeter().GasConsumed() - gasFloat := float32(gas) - telemetry.SetGaugeWithLabels([]string{MetricLabelWithdrawn}, gasFloat, []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - telemetry.NewLabel(MetricLabelGasConsumed, "todo"), - }) +func getEventsGasConsumed(gasBefore, gasAfter sdk.Gas) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeGasConsumed, strconv.FormatUint(gasAfter-gasBefore, 10)), + ), + } } -func incExpiredOrders() { - telemetry.IncrCounterWithLabels([]string{MetricLabelTotalOrdersExpired}, float32(1), []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - }) +func getEventsIncExpiredOrders() sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeIncExpiredOrders), + ), + } } -func totalLimitOrders() { - telemetry.IncrCounterWithLabels([]string{MetricLabelTotalLimitOrders}, float32(1), []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - }) +func getEventsDecExpiredOrders() sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeDecExpiredOrders), + ), + } } -func incTotalTickLiquidites() { - telemetry.IncrCounterWithLabels([]string{MetricLabelTotalTickLiquiditiesInc}, float32(1), []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - }) +func getEventsIncTotalOrders() sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalLimitOrders), + ), + } } -func decTotalTickLiquidites() { - telemetry.IncrCounterWithLabels([]string{MetricLabelTotalTickLiquiditiesDec}, float32(1), []metrics.Label{ - telemetry.NewLabel(telemetry.MetricLabelNameModule, types.ModuleName), - }) +func getEventsIncTotalTickLiquidities() sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalTickLiquiditiesInc), + ), + } +} + +func getEventsDecTotalTickLiquidities() sdk.Events { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalTickLiquiditiesDec), + ), + } } diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index 78441d486..43380dcb7 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -152,6 +152,6 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { } k.RemoveLimitOrderExpirationByKey(ctx, iterator.Key()) - incExpiredOrders() + ctx.EventManager().EmitEvents(getEventsDecExpiredOrders()) } } diff --git a/x/dex/keeper/limit_order_tranche.go b/x/dex/keeper/limit_order_tranche.go index e7608b6fa..77b5dbaf6 100644 --- a/x/dex/keeper/limit_order_tranche.go +++ b/x/dex/keeper/limit_order_tranche.go @@ -55,7 +55,7 @@ func (k Keeper) SaveTranche(ctx sdk.Context, tranche *types.LimitOrderTranche) { } else { k.SetInactiveLimitOrderTranche(ctx, tranche) k.RemoveLimitOrderTranche(ctx, tranche.Key) - decTotalTickLiquidites() + ctx.EventManager().EmitEvents(getEventsDecTotalTickLiquidities()) } ctx.EventManager().EmitEvent(types.CreateTickUpdateLimitOrderTranche(tranche)) @@ -219,19 +219,20 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, &JITGoodTilTime) - incTotalTickLiquidites() + ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) case types.LimitOrderType_GOOD_TIL_TIME: + ctx.EventManager().EmitEvents(getEventsIncExpiredOrders()) limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, goodTil) - incTotalTickLiquidites() + ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) default: placeTranche = k.GetPlaceTranche(ctx, tradePairID, tickIndexTakerToMaker) if placeTranche == nil { - incTotalTickLiquidites() + ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, diff --git a/x/dex/keeper/msg_server.go b/x/dex/keeper/msg_server.go index 02194ddca..85375dc9e 100644 --- a/x/dex/keeper/msg_server.go +++ b/x/dex/keeper/msg_server.go @@ -119,7 +119,7 @@ func (k MsgServer) PlaceLimitOrder( return &types.MsgPlaceLimitOrderResponse{}, err } - totalLimitOrders() + ctx.EventManager().EmitEvents(getEventsIncTotalOrders()) return &types.MsgPlaceLimitOrderResponse{ TrancheKey: trancheKey, @@ -168,6 +168,8 @@ func (k MsgServer) MultiHopSwap( goCtx context.Context, msg *types.MsgMultiHopSwap, ) (*types.MsgMultiHopSwapResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + gasBefore := ctx.GasMeter().GasConsumed() callerAddr := sdk.MustAccAddressFromBech32(msg.Creator) receiverAddr := sdk.MustAccAddressFromBech32(msg.Receiver) @@ -184,6 +186,8 @@ func (k MsgServer) MultiHopSwap( return &types.MsgMultiHopSwapResponse{}, err } + gasAfter := ctx.GasMeter().GasConsumed() + ctx.EventManager().EmitEvents(getEventsGasConsumed(gasBefore, gasAfter)) return &types.MsgMultiHopSwapResponse{CoinOut: coinOut}, nil } diff --git a/x/dex/keeper/pool.go b/x/dex/keeper/pool.go index 3aa96ce69..a97b32f25 100644 --- a/x/dex/keeper/pool.go +++ b/x/dex/keeper/pool.go @@ -21,7 +21,7 @@ func (k Keeper) GetOrInitPool( return pool, nil } - incTotalTickLiquidites() + ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) return k.InitPool(ctx, pairID, centerTickIndexNormalized, fee) } diff --git a/x/dex/types/types.go b/x/dex/types/types.go index ab1254f4c..21cd98902 100644 --- a/x/dex/types/types.go +++ b/x/dex/types/types.go @@ -1 +1,15 @@ package types + +const ( + // EventTypeNeutronMessage defines the event type used by the Interchain Queries module events. + EventTypeNeutronMessage = "neutron" + + AttributeDenom = "denom" + AttributeWithdrawn = "total_withdrawn" + AttributeGasConsumed = "gas_consumed" + AttributeIncExpiredOrders = "total_orders_expired_inc" + AttributeDecExpiredOrders = "total_orders_expired_dec" + AttributeTotalLimitOrders = "total_orders_limit" + AttributeTotalTickLiquiditiesInc = "total_tick_liqidities_inc" + AttributeTotalTickLiquiditiesDec = "total_tick_liqidities_dec" +) From 77a6e8d1cba7e99b13999ec72a3047a784d581e7 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 6 Jun 2024 08:50:18 -0300 Subject: [PATCH 14/20] rm upgrades --- app/upgrades/v3.0.4/constants.go | 15 --------------- app/upgrades/v3.0.4/upgrades.go | 25 ------------------------- app/upgrades/v3.0.5/constants.go | 15 --------------- app/upgrades/v3.0.5/upgrades.go | 25 ------------------------- 4 files changed, 80 deletions(-) delete mode 100644 app/upgrades/v3.0.4/constants.go delete mode 100644 app/upgrades/v3.0.4/upgrades.go delete mode 100644 app/upgrades/v3.0.5/constants.go delete mode 100644 app/upgrades/v3.0.5/upgrades.go diff --git a/app/upgrades/v3.0.4/constants.go b/app/upgrades/v3.0.4/constants.go deleted file mode 100644 index 7665fc132..000000000 --- a/app/upgrades/v3.0.4/constants.go +++ /dev/null @@ -1,15 +0,0 @@ -package v304 - -import ( - "github.com/neutron-org/neutron/v3/app/upgrades" -) - -const ( - // UpgradeName defines the on-chain upgrade name. - UpgradeName = "v3.0.4" -) - -var Upgrade = upgrades.Upgrade{ - UpgradeName: UpgradeName, - CreateUpgradeHandler: CreateUpgradeHandler, -} diff --git a/app/upgrades/v3.0.4/upgrades.go b/app/upgrades/v3.0.4/upgrades.go deleted file mode 100644 index 14ebda302..000000000 --- a/app/upgrades/v3.0.4/upgrades.go +++ /dev/null @@ -1,25 +0,0 @@ -package v304 - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - - "github.com/neutron-org/neutron/v3/app/upgrades" -) - -func CreateUpgradeHandler( - _ *module.Manager, - _ module.Configurator, - _ *upgrades.UpgradeKeepers, - _ upgrades.StoreKeys, - _ codec.Codec, -) upgradetypes.UpgradeHandler { - return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { - ctx.Logger().Info(fmt.Sprintf("Empty migration {%s} applied", UpgradeName)) - return vm, nil - } -} diff --git a/app/upgrades/v3.0.5/constants.go b/app/upgrades/v3.0.5/constants.go deleted file mode 100644 index 23cbb1cc3..000000000 --- a/app/upgrades/v3.0.5/constants.go +++ /dev/null @@ -1,15 +0,0 @@ -package v305 - -import ( - "github.com/neutron-org/neutron/v3/app/upgrades" -) - -const ( - // UpgradeName defines the on-chain upgrade name. - UpgradeName = "v3.0.5" -) - -var Upgrade = upgrades.Upgrade{ - UpgradeName: UpgradeName, - CreateUpgradeHandler: CreateUpgradeHandler, -} diff --git a/app/upgrades/v3.0.5/upgrades.go b/app/upgrades/v3.0.5/upgrades.go deleted file mode 100644 index 135aa5815..000000000 --- a/app/upgrades/v3.0.5/upgrades.go +++ /dev/null @@ -1,25 +0,0 @@ -package v305 - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - - "github.com/neutron-org/neutron/v3/app/upgrades" -) - -func CreateUpgradeHandler( - _ *module.Manager, - _ module.Configurator, - _ *upgrades.UpgradeKeepers, - _ upgrades.StoreKeys, - _ codec.Codec, -) upgradetypes.UpgradeHandler { - return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { - ctx.Logger().Info(fmt.Sprintf("Empty migration {%s} applied", UpgradeName)) - return vm, nil - } -} From e4e8752ede8eb87169f5a899425c9994e44a72e7 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 6 Jun 2024 19:29:40 -0300 Subject: [PATCH 15/20] reconceptualize dex events --- x/dex/keeper/core.go | 6 +- x/dex/keeper/keeper.go | 75 ------------------ x/dex/keeper/limit_order_expiration.go | 6 +- x/dex/keeper/limit_order_tranche.go | 13 ++-- x/dex/keeper/liquidity.go | 4 + x/dex/keeper/msg_server.go | 11 +-- x/dex/keeper/pool.go | 11 ++- x/dex/types/events.go | 102 +++++++++++++++++++++++++ x/dex/types/types.go | 18 +++-- 9 files changed, 141 insertions(+), 105 deletions(-) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index 0e5d1e59f..afac38aa7 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -211,7 +211,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin0}, ) - ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(sdk.Coins{coin0})) + ctx.EventManager().EmitEvents(types.GetEventsWithdrawnAmount(sdk.Coins{coin0})) if err != nil { return err } @@ -226,7 +226,7 @@ func (k Keeper) WithdrawCore( receiverAddr, sdk.Coins{coin1}, ) - ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(sdk.Coins{coin1})) + ctx.EventManager().EmitEvents(types.GetEventsWithdrawnAmount(sdk.Coins{coin1})) if err != nil { return err } @@ -607,7 +607,7 @@ func (k Keeper) WithdrawFilledLimitOrderCore( coinTakerDenomOut := sdk.NewCoin(tradePairID.TakerDenom, amountOutTokenOut) coinMakerDenomRefund := sdk.NewCoin(tradePairID.MakerDenom, remainingTokenIn) coins := sdk.NewCoins(coinTakerDenomOut, coinMakerDenomRefund) - ctx.EventManager().EmitEvents(getEventsWithdrawnAmount(coins)) + ctx.EventManager().EmitEvents(types.GetEventsWithdrawnAmount(coins)) if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, callerAddr, coins); err != nil { return err } diff --git a/x/dex/keeper/keeper.go b/x/dex/keeper/keeper.go index 5dfa22167..797b74d48 100644 --- a/x/dex/keeper/keeper.go +++ b/x/dex/keeper/keeper.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - "strconv" "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" @@ -48,77 +47,3 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { func (k Keeper) GetAuthority() string { return k.authority } - -func getEventsWithdrawnAmount(coins sdk.Coins) sdk.Events { - events := sdk.Events{} - for _, coin := range coins { - event := sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeDenom, coin.Denom), - sdk.NewAttribute(types.AttributeWithdrawn, coin.Amount.String()), - ) - events = append(events, event) - } - return events -} - -func getEventsGasConsumed(gasBefore, gasAfter storetypes.Gas) sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeGasConsumed, strconv.FormatUint(gasAfter-gasBefore, 10)), - ), - } -} - -func getEventsIncExpiredOrders() sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeIncExpiredOrders), - ), - } -} - -func getEventsDecExpiredOrders() sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeDecExpiredOrders), - ), - } -} - -func getEventsIncTotalOrders() sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalLimitOrders), - ), - } -} - -func getEventsIncTotalTickLiquidities() sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalTickLiquiditiesInc), - ), - } -} - -func getEventsDecTotalTickLiquidities() sdk.Events { - return sdk.Events{ - sdk.NewEvent( - types.EventTypeNeutronMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeTotalTickLiquiditiesDec), - ), - } -} diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index f7fd6d644..a64ff8c2e 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -154,6 +154,10 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { } k.RemoveLimitOrderExpirationByKey(ctx, iterator.Key()) - ctx.EventManager().EmitEvents(getEventsDecExpiredOrders()) + // TODO: either delete tradePairID from this event or provide pair ID somehow (seems it's unreachable here) + ctx.EventManager().EmitEvents(types.GetEventsDecExpiringOrders(&types.TradePairID{ + MakerDenom: "", + TakerDenom: "", + })) } } diff --git a/x/dex/keeper/limit_order_tranche.go b/x/dex/keeper/limit_order_tranche.go index 0da7813ba..d20bb0777 100644 --- a/x/dex/keeper/limit_order_tranche.go +++ b/x/dex/keeper/limit_order_tranche.go @@ -57,7 +57,11 @@ func (k Keeper) SaveTranche(ctx sdk.Context, tranche *types.LimitOrderTranche) { } else { k.SetInactiveLimitOrderTranche(ctx, tranche) k.RemoveLimitOrderTranche(ctx, tranche.Key) - ctx.EventManager().EmitEvents(getEventsDecTotalTickLiquidities()) + // TODO: either delete tradePairID from this event or provide pair ID somehow (seems it's unreachable here) + ctx.EventManager().EmitEvents(types.GetEventsDecTotalOrders(&types.TradePairID{ + MakerDenom: "", + TakerDenom: "", + })) } ctx.EventManager().EmitEvent(types.CreateTickUpdateLimitOrderTranche(tranche)) @@ -222,26 +226,25 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, &JITGoodTilTime) - ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) + ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(tradePairID)) case types.LimitOrderType_GOOD_TIL_TIME: - ctx.EventManager().EmitEvents(getEventsIncExpiredOrders()) limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, goodTil) - ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) + ctx.EventManager().EmitEvents(types.GetEventsIncExpiringOrders(tradePairID)) default: placeTranche = k.GetGTCPlaceTranche(ctx, tradePairID, tickIndexTakerToMaker) if placeTranche == nil { - ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, TrancheKey: NewTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, nil) + ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(tradePairID)) if err != nil { return nil, err } diff --git a/x/dex/keeper/liquidity.go b/x/dex/keeper/liquidity.go index af9ec980c..145657505 100644 --- a/x/dex/keeper/liquidity.go +++ b/x/dex/keeper/liquidity.go @@ -15,6 +15,7 @@ func (k Keeper) Swap( maxAmountMakerDenom *math.Int, limitPrice *math_utils.PrecDec, ) (totalTakerCoin, totalMakerCoin sdk.Coin, orderFilled bool, err error) { + gasBefore := ctx.GasMeter().GasConsumed() useMaxOut := maxAmountMakerDenom != nil var remainingMakerDenom *math.Int if useMaxOut { @@ -73,6 +74,9 @@ func (k Keeper) Swap( } totalTakerDenom := maxAmountTakerDenom.Sub(remainingTakerDenom) + gasAfter := ctx.GasMeter().GasConsumed() + ctx.EventManager().EmitEvents(types.GetEventsGasConsumed(gasBefore, gasAfter)) + return sdk.NewCoin( tradePairID.TakerDenom, totalTakerDenom, diff --git a/x/dex/keeper/msg_server.go b/x/dex/keeper/msg_server.go index ea3522b9d..b1e7a2357 100644 --- a/x/dex/keeper/msg_server.go +++ b/x/dex/keeper/msg_server.go @@ -152,7 +152,10 @@ func (k MsgServer) PlaceLimitOrder( return &types.MsgPlaceLimitOrderResponse{}, err } - ctx.EventManager().EmitEvents(getEventsIncTotalOrders()) + ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(&types.TradePairID{ + MakerDenom: coinIn.Denom, + TakerDenom: coinOutSwap.Denom, + })) return &types.MsgPlaceLimitOrderResponse{ TrancheKey: trancheKey, @@ -217,9 +220,6 @@ func (k MsgServer) MultiHopSwap( goCtx context.Context, msg *types.MsgMultiHopSwap, ) (*types.MsgMultiHopSwapResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - gasBefore := ctx.GasMeter().GasConsumed() - if err := msg.Validate(); err != nil { return nil, errors.Wrap(err, "failed to validate MsgMultiHopSwap") } @@ -243,9 +243,6 @@ func (k MsgServer) MultiHopSwap( if err != nil { return &types.MsgMultiHopSwapResponse{}, err } - - gasAfter := ctx.GasMeter().GasConsumed() - ctx.EventManager().EmitEvents(getEventsGasConsumed(gasBefore, gasAfter)) return &types.MsgMultiHopSwapResponse{CoinOut: coinOut}, nil } diff --git a/x/dex/keeper/pool.go b/x/dex/keeper/pool.go index a9b13791f..85782a05a 100644 --- a/x/dex/keeper/pool.go +++ b/x/dex/keeper/pool.go @@ -20,8 +20,7 @@ func (k Keeper) GetOrInitPool( if found { return pool, nil } - - ctx.EventManager().EmitEvents(getEventsIncTotalTickLiquidities()) + ctx.EventManager().EmitEvents(types.GetEventsIncTotalPoolReserves(pool.GetId())) return k.InitPool(ctx, pairID, centerTickIndexNormalized, fee) } @@ -137,8 +136,8 @@ func (k Keeper) GetPoolIDByParams( } func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool) { - k.updatePoolReserves(ctx, pool.LowerTick0) - k.updatePoolReserves(ctx, pool.UpperTick1) + k.updatePoolReserves(ctx, pool.LowerTick0, pool.GetId()) + k.updatePoolReserves(ctx, pool.UpperTick1, pool.GetId()) // TODO: this will create a bit of extra noise since not every Save is updating both ticks // This should be solved upstream by better tracking of dirty ticks @@ -146,11 +145,11 @@ func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool) { ctx.EventManager().EmitEvent(types.CreateTickUpdatePoolReserves(*pool.UpperTick1)) } -func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves) { +func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves, poolID uint64) { if reserves.HasToken() { k.SetPoolReserves(ctx, reserves) } else { - ctx.EventManager().EmitEvents(getEventsDecTotalTickLiquidities()) + ctx.EventManager().EmitEvents(types.GetEventsDecTotalPoolReserves(poolID)) k.RemovePoolReserves(ctx, reserves.Key) } } diff --git a/x/dex/types/events.go b/x/dex/types/events.go index 4307eb304..7ea9425f9 100644 --- a/x/dex/types/events.go +++ b/x/dex/types/events.go @@ -244,3 +244,105 @@ func GoodTilPurgeHitLimitEvent(gas types.Gas) sdk.Event { return sdk.NewEvent(EventTypeGoodTilPurgeHitGasLimit, attrs...) } + +func GetEventsWithdrawnAmount(coins sdk.Coins) sdk.Events { + events := sdk.Events{} + for _, coin := range coins { + event := sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(AttributeDenom, coin.Denom), + sdk.NewAttribute(AttributeWithdrawn, coin.Amount.String()), + ) + events = append(events, event) + } + return events +} + +func GetEventsGasConsumed(gasBefore, gasAfter types.Gas) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(AttributeGasConsumed, strconv.FormatUint(gasAfter-gasBefore, 10)), + ), + } +} + +func GetEventsIncExpiringOrders(pairID *TradePairID) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeInc), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLimitOrder), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(true)), + sdk.NewAttribute(AttributePairID, pairID.String()), + ), + } +} + +func GetEventsDecExpiringOrders(pairID *TradePairID) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeDec), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLimitOrder), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(true)), + sdk.NewAttribute(AttributePairID, pairID.String()), + ), + } +} + +func GetEventsIncTotalOrders(pairID *TradePairID) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeInc), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLimitOrder), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), + sdk.NewAttribute(AttributePairID, pairID.String()), + ), + } +} + +func GetEventsDecTotalOrders(pairID *TradePairID) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeDec), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLimitOrder), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), + sdk.NewAttribute(AttributePairID, pairID.String()), + ), + } +} + +func GetEventsIncTotalPoolReserves(pairID uint64) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeInc), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLp), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), + sdk.NewAttribute(AttributePairID, strconv.FormatUint(pairID, 10)), + ), + } +} + +func GetEventsDecTotalPoolReserves(pairID uint64) sdk.Events { + return sdk.Events{ + sdk.NewEvent( + EventTypeNeutronMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, AttributeDec), + sdk.NewAttribute(AttributeLiquidityTickType, AttributeLp), + sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), + sdk.NewAttribute(AttributePairID, strconv.FormatUint(pairID, 10)), + ), + } +} diff --git a/x/dex/types/types.go b/x/dex/types/types.go index 21cd98902..18196ebae 100644 --- a/x/dex/types/types.go +++ b/x/dex/types/types.go @@ -4,12 +4,14 @@ const ( // EventTypeNeutronMessage defines the event type used by the Interchain Queries module events. EventTypeNeutronMessage = "neutron" - AttributeDenom = "denom" - AttributeWithdrawn = "total_withdrawn" - AttributeGasConsumed = "gas_consumed" - AttributeIncExpiredOrders = "total_orders_expired_inc" - AttributeDecExpiredOrders = "total_orders_expired_dec" - AttributeTotalLimitOrders = "total_orders_limit" - AttributeTotalTickLiquiditiesInc = "total_tick_liqidities_inc" - AttributeTotalTickLiquiditiesDec = "total_tick_liqidities_dec" + AttributeDenom = "denom" + AttributeWithdrawn = "total_withdrawn" + AttributeGasConsumed = "gas_consumed" + AttributeLiquidityTickType = "liquidity_tick_type" + AttributeLp = "lp" + AttributeLimitOrder = "limit_order" + AttributeIsExpiringLimitOrder = "is_expiring_limit_order" + AttributeInc = "inc" + AttributeDec = "dec" + AttributePairID = "pair_id" ) From 43cc2a91b53df166895121a8a6121f1e639fd02f Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 10 Jun 2024 08:14:43 -0300 Subject: [PATCH 16/20] fix comments --- x/dex/keeper/core.go | 4 ++-- x/dex/keeper/limit_order_expiration.go | 9 +++------ x/dex/keeper/liquidity.go | 9 ++++++--- x/dex/keeper/pool.go | 10 +++++----- x/dex/types/events.go | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index afac38aa7..cd44d3e17 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -83,7 +83,7 @@ func (k Keeper) DepositCore( inAmount0, inAmount1, outShares := pool.Deposit(amount0, amount1, existingShares, autoswap) - k.SetPool(ctx, pool) + k.SetPool(ctx, pool, pairID) if inAmount0.IsZero() && inAmount1.IsZero() { return nil, nil, nil, failedDeposits, types.ErrZeroTrueDeposit @@ -178,7 +178,7 @@ func (k Keeper) WithdrawCore( } outAmount0, outAmount1 := pool.Withdraw(sharesToRemove, totalShares) - k.SetPool(ctx, pool) + k.SetPool(ctx, pool, pairID) if sharesToRemove.IsPositive() { if err := k.BurnShares(ctx, callerAddr, sharesToRemove, poolDenom); err != nil { diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index a64ff8c2e..f9127a38f 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -140,7 +140,7 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { return } - + var pairID types.TradePairID if _, ok := archivedTranches[string(val.TrancheRef)]; !ok { tranche, found := k.GetLimitOrderTrancheByKey(ctx, val.TrancheRef) if found { @@ -149,15 +149,12 @@ func (k Keeper) PurgeExpiredLimitOrders(ctx sdk.Context, curTime time.Time) { k.RemoveLimitOrderTranche(ctx, tranche.Key) archivedTranches[string(val.TrancheRef)] = true + pairID = *tranche.Key.TradePairId ctx.EventManager().EmitEvent(types.CreateTickUpdateLimitOrderTranchePurge(tranche)) } } k.RemoveLimitOrderExpirationByKey(ctx, iterator.Key()) - // TODO: either delete tradePairID from this event or provide pair ID somehow (seems it's unreachable here) - ctx.EventManager().EmitEvents(types.GetEventsDecExpiringOrders(&types.TradePairID{ - MakerDenom: "", - TakerDenom: "", - })) + ctx.EventManager().EmitEvents(types.GetEventsDecExpiringOrders(&pairID)) } } diff --git a/x/dex/keeper/liquidity.go b/x/dex/keeper/liquidity.go index 145657505..8134fd82b 100644 --- a/x/dex/keeper/liquidity.go +++ b/x/dex/keeper/liquidity.go @@ -43,7 +43,10 @@ func (k Keeper) Swap( inAmount, outAmount := liq.Swap(remainingTakerDenom, remainingMakerDenom) - k.SaveLiquidity(ctx, liq) + k.SaveLiquidity(ctx, liq, &types.PairID{ + Token0: tradePairID.MakerDenom, + Token1: tradePairID.TakerDenom, + }) remainingTakerDenom = remainingTakerDenom.Sub(inAmount) totalMakerDenom = totalMakerDenom.Add(outAmount) @@ -107,13 +110,13 @@ func (k Keeper) SwapWithCache( return totalIn, totalOut, orderFilled, err } -func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity) { +func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity, pairID *types.PairID) { switch liquidity := liquidityI.(type) { case *types.LimitOrderTranche: k.SaveTranche(sdkCtx, liquidity) case *types.PoolLiquidity: - k.SetPool(sdkCtx, liquidity.Pool) + k.SetPool(sdkCtx, liquidity.Pool, pairID) default: panic("Invalid liquidity type") } diff --git a/x/dex/keeper/pool.go b/x/dex/keeper/pool.go index 85782a05a..4f036eb6a 100644 --- a/x/dex/keeper/pool.go +++ b/x/dex/keeper/pool.go @@ -20,7 +20,7 @@ func (k Keeper) GetOrInitPool( if found { return pool, nil } - ctx.EventManager().EmitEvents(types.GetEventsIncTotalPoolReserves(pool.GetId())) + ctx.EventManager().EmitEvents(types.GetEventsIncTotalPoolReserves(*pairID)) return k.InitPool(ctx, pairID, centerTickIndexNormalized, fee) } @@ -135,9 +135,9 @@ func (k Keeper) GetPoolIDByParams( return poolID, true } -func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool) { - k.updatePoolReserves(ctx, pool.LowerTick0, pool.GetId()) - k.updatePoolReserves(ctx, pool.UpperTick1, pool.GetId()) +func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool, pairID *types.PairID) { + k.updatePoolReserves(ctx, pool.LowerTick0, *pairID) + k.updatePoolReserves(ctx, pool.UpperTick1, *pairID) // TODO: this will create a bit of extra noise since not every Save is updating both ticks // This should be solved upstream by better tracking of dirty ticks @@ -145,7 +145,7 @@ func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool) { ctx.EventManager().EmitEvent(types.CreateTickUpdatePoolReserves(*pool.UpperTick1)) } -func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves, poolID uint64) { +func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves, poolID types.PairID) { if reserves.HasToken() { k.SetPoolReserves(ctx, reserves) } else { diff --git a/x/dex/types/events.go b/x/dex/types/events.go index 7ea9425f9..bf3670855 100644 --- a/x/dex/types/events.go +++ b/x/dex/types/events.go @@ -321,7 +321,7 @@ func GetEventsDecTotalOrders(pairID *TradePairID) sdk.Events { } } -func GetEventsIncTotalPoolReserves(pairID uint64) sdk.Events { +func GetEventsIncTotalPoolReserves(pairID PairID) sdk.Events { return sdk.Events{ sdk.NewEvent( EventTypeNeutronMessage, @@ -329,12 +329,12 @@ func GetEventsIncTotalPoolReserves(pairID uint64) sdk.Events { sdk.NewAttribute(sdk.AttributeKeyAction, AttributeInc), sdk.NewAttribute(AttributeLiquidityTickType, AttributeLp), sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), - sdk.NewAttribute(AttributePairID, strconv.FormatUint(pairID, 10)), + sdk.NewAttribute(AttributePairID, pairID.String()), ), } } -func GetEventsDecTotalPoolReserves(pairID uint64) sdk.Events { +func GetEventsDecTotalPoolReserves(pairID PairID) sdk.Events { return sdk.Events{ sdk.NewEvent( EventTypeNeutronMessage, @@ -342,7 +342,7 @@ func GetEventsDecTotalPoolReserves(pairID uint64) sdk.Events { sdk.NewAttribute(sdk.AttributeKeyAction, AttributeDec), sdk.NewAttribute(AttributeLiquidityTickType, AttributeLp), sdk.NewAttribute(AttributeIsExpiringLimitOrder, strconv.FormatBool(false)), - sdk.NewAttribute(AttributePairID, strconv.FormatUint(pairID, 10)), + sdk.NewAttribute(AttributePairID, pairID.String()), ), } } From d40c332158026ced206fb1dc674de342e4a02eea Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 10 Jun 2024 09:40:42 -0300 Subject: [PATCH 17/20] fix tests --- x/dex/keeper/core_helper_test.go | 2 +- x/dex/keeper/liquidity_test.go | 2 +- x/dex/keeper/pool_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x/dex/keeper/core_helper_test.go b/x/dex/keeper/core_helper_test.go index c09d45d99..2ffdd79e0 100644 --- a/x/dex/keeper/core_helper_test.go +++ b/x/dex/keeper/core_helper_test.go @@ -72,7 +72,7 @@ func (s *CoreHelpersTestSuite) setLPAtFee1Pool(tickIndex int64, amountA, amountB lowerTick.ReservesMakerDenom = amountAInt upperTick.ReservesMakerDenom = amountBInt - s.app.DexKeeper.SetPool(s.ctx, pool) + s.app.DexKeeper.SetPool(s.ctx, pool, &types.PairID{Token0: "", Token1: ""}) } // FindNextTick //////////////////////////////////////////////////// diff --git a/x/dex/keeper/liquidity_test.go b/x/dex/keeper/liquidity_test.go index df4fda66a..3014b2522 100644 --- a/x/dex/keeper/liquidity_test.go +++ b/x/dex/keeper/liquidity_test.go @@ -556,7 +556,7 @@ func (s *DexTestSuite) addDeposit(deposit *Deposit) { s.Assert().NoError(err) pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(deposit.AmountA) pool.UpperTick1.ReservesMakerDenom = pool.UpperTick1.ReservesMakerDenom.Add(deposit.AmountB) - s.App.DexKeeper.SetPool(s.Ctx, pool) + s.App.DexKeeper.SetPool(s.Ctx, pool, &types.PairID{Token0: "", Token1: ""}) } func (s *DexTestSuite) addDeposits(deposits ...*Deposit) { diff --git a/x/dex/keeper/pool_test.go b/x/dex/keeper/pool_test.go index f2b2d55ea..3f6f6dd08 100644 --- a/x/dex/keeper/pool_test.go +++ b/x/dex/keeper/pool_test.go @@ -20,7 +20,7 @@ func createNPools(k *keeper.Keeper, ctx sdk.Context, n int) []*types.Pool { panic("failed to create pool") } pool.Deposit(math.NewInt(10), math.NewInt(0), math.ZeroInt(), true) - k.SetPool(ctx, pool) + k.SetPool(ctx, pool, &types.PairID{Token0: "", Token1: ""}) items[i] = pool } @@ -33,7 +33,7 @@ func TestPoolInit(t *testing.T) { pool, err := keeper.InitPool(ctx, defaultPairID, 0, 1) require.NoError(t, err) pool.Deposit(math.NewInt(1000), math.NewInt(1000), math.NewInt(0), true) - keeper.SetPool(ctx, pool) + keeper.SetPool(ctx, pool, &types.PairID{Token0: "", Token1: ""}) dbPool, found := keeper.GetPool(ctx, defaultPairID, 0, 1) From cb64677a3542b9921c12bcda7dcc0b8d4d7528c7 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 10 Jun 2024 14:24:50 -0300 Subject: [PATCH 18/20] final fixes --- x/dex/keeper/core.go | 6 +++--- x/dex/keeper/core_helper_test.go | 2 +- x/dex/keeper/liquidity.go | 2 +- x/dex/keeper/liquidity_test.go | 2 +- x/dex/keeper/msg_server.go | 5 ----- x/dex/keeper/pool.go | 10 +++++----- x/dex/keeper/pool_test.go | 4 ++-- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/x/dex/keeper/core.go b/x/dex/keeper/core.go index cd44d3e17..04e0d29fa 100644 --- a/x/dex/keeper/core.go +++ b/x/dex/keeper/core.go @@ -83,7 +83,7 @@ func (k Keeper) DepositCore( inAmount0, inAmount1, outShares := pool.Deposit(amount0, amount1, existingShares, autoswap) - k.SetPool(ctx, pool, pairID) + k.SetPool(ctx, pool) if inAmount0.IsZero() && inAmount1.IsZero() { return nil, nil, nil, failedDeposits, types.ErrZeroTrueDeposit @@ -178,7 +178,7 @@ func (k Keeper) WithdrawCore( } outAmount0, outAmount1 := pool.Withdraw(sharesToRemove, totalShares) - k.SetPool(ctx, pool, pairID) + k.SetPool(ctx, pool) if sharesToRemove.IsPositive() { if err := k.BurnShares(ctx, callerAddr, sharesToRemove, poolDenom); err != nil { @@ -607,7 +607,7 @@ func (k Keeper) WithdrawFilledLimitOrderCore( coinTakerDenomOut := sdk.NewCoin(tradePairID.TakerDenom, amountOutTokenOut) coinMakerDenomRefund := sdk.NewCoin(tradePairID.MakerDenom, remainingTokenIn) coins := sdk.NewCoins(coinTakerDenomOut, coinMakerDenomRefund) - ctx.EventManager().EmitEvents(types.GetEventsWithdrawnAmount(coins)) + ctx.EventManager().EmitEvents(types.GetEventsWithdrawnAmount(sdk.NewCoins(coinTakerDenomOut))) if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, callerAddr, coins); err != nil { return err } diff --git a/x/dex/keeper/core_helper_test.go b/x/dex/keeper/core_helper_test.go index 2ffdd79e0..c09d45d99 100644 --- a/x/dex/keeper/core_helper_test.go +++ b/x/dex/keeper/core_helper_test.go @@ -72,7 +72,7 @@ func (s *CoreHelpersTestSuite) setLPAtFee1Pool(tickIndex int64, amountA, amountB lowerTick.ReservesMakerDenom = amountAInt upperTick.ReservesMakerDenom = amountBInt - s.app.DexKeeper.SetPool(s.ctx, pool, &types.PairID{Token0: "", Token1: ""}) + s.app.DexKeeper.SetPool(s.ctx, pool) } // FindNextTick //////////////////////////////////////////////////// diff --git a/x/dex/keeper/liquidity.go b/x/dex/keeper/liquidity.go index 8134fd82b..94d2436aa 100644 --- a/x/dex/keeper/liquidity.go +++ b/x/dex/keeper/liquidity.go @@ -116,7 +116,7 @@ func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity, pa k.SaveTranche(sdkCtx, liquidity) case *types.PoolLiquidity: - k.SetPool(sdkCtx, liquidity.Pool, pairID) + k.SetPool(sdkCtx, liquidity.Pool) default: panic("Invalid liquidity type") } diff --git a/x/dex/keeper/liquidity_test.go b/x/dex/keeper/liquidity_test.go index 3014b2522..df4fda66a 100644 --- a/x/dex/keeper/liquidity_test.go +++ b/x/dex/keeper/liquidity_test.go @@ -556,7 +556,7 @@ func (s *DexTestSuite) addDeposit(deposit *Deposit) { s.Assert().NoError(err) pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(deposit.AmountA) pool.UpperTick1.ReservesMakerDenom = pool.UpperTick1.ReservesMakerDenom.Add(deposit.AmountB) - s.App.DexKeeper.SetPool(s.Ctx, pool, &types.PairID{Token0: "", Token1: ""}) + s.App.DexKeeper.SetPool(s.Ctx, pool) } func (s *DexTestSuite) addDeposits(deposits ...*Deposit) { diff --git a/x/dex/keeper/msg_server.go b/x/dex/keeper/msg_server.go index b1e7a2357..1211cc57b 100644 --- a/x/dex/keeper/msg_server.go +++ b/x/dex/keeper/msg_server.go @@ -152,11 +152,6 @@ func (k MsgServer) PlaceLimitOrder( return &types.MsgPlaceLimitOrderResponse{}, err } - ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(&types.TradePairID{ - MakerDenom: coinIn.Denom, - TakerDenom: coinOutSwap.Denom, - })) - return &types.MsgPlaceLimitOrderResponse{ TrancheKey: trancheKey, CoinIn: coinIn, diff --git a/x/dex/keeper/pool.go b/x/dex/keeper/pool.go index 4f036eb6a..e58e4dd9c 100644 --- a/x/dex/keeper/pool.go +++ b/x/dex/keeper/pool.go @@ -135,9 +135,9 @@ func (k Keeper) GetPoolIDByParams( return poolID, true } -func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool, pairID *types.PairID) { - k.updatePoolReserves(ctx, pool.LowerTick0, *pairID) - k.updatePoolReserves(ctx, pool.UpperTick1, *pairID) +func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool) { + k.updatePoolReserves(ctx, pool.LowerTick0) + k.updatePoolReserves(ctx, pool.UpperTick1) // TODO: this will create a bit of extra noise since not every Save is updating both ticks // This should be solved upstream by better tracking of dirty ticks @@ -145,11 +145,11 @@ func (k Keeper) SetPool(ctx sdk.Context, pool *types.Pool, pairID *types.PairID) ctx.EventManager().EmitEvent(types.CreateTickUpdatePoolReserves(*pool.UpperTick1)) } -func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves, poolID types.PairID) { +func (k Keeper) updatePoolReserves(ctx sdk.Context, reserves *types.PoolReserves) { if reserves.HasToken() { k.SetPoolReserves(ctx, reserves) } else { - ctx.EventManager().EmitEvents(types.GetEventsDecTotalPoolReserves(poolID)) + ctx.EventManager().EmitEvents(types.GetEventsDecTotalPoolReserves(*reserves.Key.TradePairId.MustPairID())) k.RemovePoolReserves(ctx, reserves.Key) } } diff --git a/x/dex/keeper/pool_test.go b/x/dex/keeper/pool_test.go index 3f6f6dd08..f2b2d55ea 100644 --- a/x/dex/keeper/pool_test.go +++ b/x/dex/keeper/pool_test.go @@ -20,7 +20,7 @@ func createNPools(k *keeper.Keeper, ctx sdk.Context, n int) []*types.Pool { panic("failed to create pool") } pool.Deposit(math.NewInt(10), math.NewInt(0), math.ZeroInt(), true) - k.SetPool(ctx, pool, &types.PairID{Token0: "", Token1: ""}) + k.SetPool(ctx, pool) items[i] = pool } @@ -33,7 +33,7 @@ func TestPoolInit(t *testing.T) { pool, err := keeper.InitPool(ctx, defaultPairID, 0, 1) require.NoError(t, err) pool.Deposit(math.NewInt(1000), math.NewInt(1000), math.NewInt(0), true) - keeper.SetPool(ctx, pool, &types.PairID{Token0: "", Token1: ""}) + keeper.SetPool(ctx, pool) dbPool, found := keeper.GetPool(ctx, defaultPairID, 0, 1) From c08c541c75dd93bfbbdf9bf989df241f9e1fcb03 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 10 Jun 2024 14:29:49 -0300 Subject: [PATCH 19/20] rm unused param --- x/dex/keeper/liquidity.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/x/dex/keeper/liquidity.go b/x/dex/keeper/liquidity.go index 94d2436aa..145657505 100644 --- a/x/dex/keeper/liquidity.go +++ b/x/dex/keeper/liquidity.go @@ -43,10 +43,7 @@ func (k Keeper) Swap( inAmount, outAmount := liq.Swap(remainingTakerDenom, remainingMakerDenom) - k.SaveLiquidity(ctx, liq, &types.PairID{ - Token0: tradePairID.MakerDenom, - Token1: tradePairID.TakerDenom, - }) + k.SaveLiquidity(ctx, liq) remainingTakerDenom = remainingTakerDenom.Sub(inAmount) totalMakerDenom = totalMakerDenom.Add(outAmount) @@ -110,7 +107,7 @@ func (k Keeper) SwapWithCache( return totalIn, totalOut, orderFilled, err } -func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity, pairID *types.PairID) { +func (k Keeper) SaveLiquidity(sdkCtx sdk.Context, liquidityI types.Liquidity) { switch liquidity := liquidityI.(type) { case *types.LimitOrderTranche: k.SaveTranche(sdkCtx, liquidity) From ba466a5465bbfc38a922a6a4696ddc540b206e08 Mon Sep 17 00:00:00 2001 From: Mike Mozhaev Date: Mon, 10 Jun 2024 20:41:33 +0300 Subject: [PATCH 20/20] Update x/dex/keeper/limit_order_tranche.go Co-authored-by: Julian Compagni Portis --- x/dex/keeper/limit_order_tranche.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x/dex/keeper/limit_order_tranche.go b/x/dex/keeper/limit_order_tranche.go index d20bb0777..3247ac897 100644 --- a/x/dex/keeper/limit_order_tranche.go +++ b/x/dex/keeper/limit_order_tranche.go @@ -57,11 +57,7 @@ func (k Keeper) SaveTranche(ctx sdk.Context, tranche *types.LimitOrderTranche) { } else { k.SetInactiveLimitOrderTranche(ctx, tranche) k.RemoveLimitOrderTranche(ctx, tranche.Key) - // TODO: either delete tradePairID from this event or provide pair ID somehow (seems it's unreachable here) - ctx.EventManager().EmitEvents(types.GetEventsDecTotalOrders(&types.TradePairID{ - MakerDenom: "", - TakerDenom: "", - })) +ctx.EventManager().EmitEvents(types.GetEventsDecTotalOrders(tranche.Key.TradePairId)) } ctx.EventManager().EmitEvent(types.CreateTickUpdateLimitOrderTranche(tranche))