From 8941f1a52a1d9563eb086b3d4f7e03e66a9f7565 Mon Sep 17 00:00:00 2001 From: Joe Bowman Date: Wed, 18 Jan 2023 23:08:46 +0000 Subject: [PATCH] Release/v1.2.x (#282) * do not remove delegation records on absence from delegatordelegations callback * add tests for delegations callback changes * fix onboarding race condition with attempting to set withdrawal address on non-existent / unitialized ICA * fix GetTxEvents pagination/sort issue * test fixes * ensure undelegate msg receipt triggers single validator requery, not entire set * add Uncapped OverrideRedemptionRate function; add tests for capped and uncapped redemption rate calculations --- app/app.go | 8 + x/interchainstaking/keeper/callbacks.go | 19 - x/interchainstaking/keeper/callbacks_test.go | 360 ++++++++++++++++++ x/interchainstaking/keeper/hooks.go | 4 +- .../keeper/ibc_packet_handlers.go | 30 +- x/interchainstaking/keeper/keeper.go | 12 +- x/interchainstaking/keeper/keeper_test.go | 152 ++++++++ x/interchainstaking/keeper/zones.go | 7 +- 8 files changed, 557 insertions(+), 35 deletions(-) diff --git a/app/app.go b/app/app.go index f0b31d476..52c93677a 100644 --- a/app/app.go +++ b/app/app.go @@ -872,6 +872,14 @@ func (app *Quicksilver) Name() string { return app.BaseApp.Name() } // BeginBlocker updates every begin block func (app *Quicksilver) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + if ctx.ChainID() == "quicksilver-2" && ctx.BlockHeight() == 235001 { + zone, found := app.InterchainstakingKeeper.GetZone(ctx, "stargaze-1") + if !found { + panic("ERROR: unable to find expected stargaze-1 zone") + } + app.InterchainstakingKeeper.OverrideRedemptionRateNoCap(ctx, zone) + } + return app.mm.BeginBlock(ctx, req) } diff --git a/x/interchainstaking/keeper/callbacks.go b/x/interchainstaking/keeper/callbacks.go index 1b02b692d..59c8d4107 100644 --- a/x/interchainstaking/keeper/callbacks.go +++ b/x/interchainstaking/keeper/callbacks.go @@ -9,7 +9,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/bech32" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - querypb "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -226,23 +225,6 @@ func DepositIntervalCallback(k Keeper, ctx sdk.Context, args []byte, query icqty return err } - // TODO: use pagination.GetTotal() to dispatch the correct number of requests now; rather than iteratively. - if len(txs.Pagination.NextKey) > 0 { - req := tx.GetTxsEventRequest{} - if len(query.Request) == 0 { - return errors.New("attempted to unmarshal zero length byte slice (5)") - } - err := k.cdc.Unmarshal(query.Request, &req) - if err != nil { - return err - } - if req.Pagination == nil { - req.Pagination = new(querypb.PageRequest) - } - req.Pagination.Key = txs.Pagination.NextKey - k.ICQKeeper.MakeRequest(ctx, query.ConnectionId, query.ChainId, "cosmos.tx.v1beta1.Service/GetTxsEvent", k.cdc.MustMarshal(&req), sdk.NewInt(-1), types.ModuleName, "depositinterval", 0) - } - for _, txn := range txs.TxResponses { req := tx.GetTxRequest{Hash: txn.TxHash} hashBytes := k.cdc.MustMarshal(&req) @@ -251,7 +233,6 @@ func DepositIntervalCallback(k Keeper, ctx sdk.Context, args []byte, query icqty k.Logger(ctx).Info("Found previously handled tx. Ignoring.", "txhash", txn.TxHash) continue } - // k.HandleReceiptTransaction(ctx, txn, txs.Txs[idx], zone) k.ICQKeeper.MakeRequest(ctx, query.ConnectionId, query.ChainId, "tendermint.Tx", hashBytes, sdk.NewInt(-1), types.ModuleName, "deposittx", 0) } return nil diff --git a/x/interchainstaking/keeper/callbacks_test.go b/x/interchainstaking/keeper/callbacks_test.go index 7a6106f0f..2222a55e2 100644 --- a/x/interchainstaking/keeper/callbacks_test.go +++ b/x/interchainstaking/keeper/callbacks_test.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/bech32" + "github.com/cosmos/cosmos-sdk/types/query" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -978,3 +979,362 @@ func TestValsetCallbackNilValidatorReqPagination(t *testing.T) { data := []byte("\x12\"\n 00000000000000000000000000000000") _ = keeper.ValsetCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID}) } + +func TestDelegationsCallbackAllPresentNoChange(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.QueryDelegatorDelegationsResponse{DelegationResponses: []stakingtypes.DelegationResponse{ + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + }} + + data := cdc.MustMarshal(&response) + + delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zone.DelegationAddress.Address, Pagination: &query.PageRequest{Limit: uint64(len(zone.Validators))}} + bz := cdc.MustMarshal(&delegationQuery) + + err := keeper.DelegationsCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + + s.Require().NoError(err) + + delegationRequests := 0 + for _, query := range app.InterchainQueryKeeper.AllQueries(ctx) { + if query.CallbackId == "delegation" { + delegationRequests++ + } + } + + s.Require().Equal(0, delegationRequests) + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} + +func TestDelegationsCallbackAllPresentOneChange(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.QueryDelegatorDelegationsResponse{DelegationResponses: []stakingtypes.DelegationResponse{ + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Shares: sdk.NewDec(2000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(2000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + }} + + data := cdc.MustMarshal(&response) + + delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zone.DelegationAddress.Address, Pagination: &query.PageRequest{Limit: uint64(len(zone.Validators))}} + bz := cdc.MustMarshal(&delegationQuery) + + err := keeper.DelegationsCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + + s.Require().NoError(err) + + delegationRequests := 0 + for _, query := range app.InterchainQueryKeeper.AllQueries(ctx) { + if query.CallbackId == "delegation" { + delegationRequests++ + } + } + + s.Require().Equal(1, delegationRequests) + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} + +func TestDelegationsCallbackOneMissing(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.QueryDelegatorDelegationsResponse{DelegationResponses: []stakingtypes.DelegationResponse{ + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + }} + + data := cdc.MustMarshal(&response) + + delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zone.DelegationAddress.Address, Pagination: &query.PageRequest{Limit: uint64(len(zone.Validators))}} + bz := cdc.MustMarshal(&delegationQuery) + + err := keeper.DelegationsCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + + s.Require().NoError(err) + + delegationRequests := 0 + for _, query := range app.InterchainQueryKeeper.AllQueries(ctx) { + if query.CallbackId == "delegation" { + delegationRequests++ + } + } + + s.Require().Equal(1, delegationRequests) // callback for 'missing' delegation. + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) // new delegation doesn't get removed until the callback. +} + +func TestDelegationsCallbackOneAdditional(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.QueryDelegatorDelegationsResponse{DelegationResponses: []stakingtypes.DelegationResponse{ + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + {Delegation: stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[3].OperatorAddress, Shares: sdk.NewDec(1000)}, Balance: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))}, + }} + + data := cdc.MustMarshal(&response) + + delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zone.DelegationAddress.Address, Pagination: &query.PageRequest{Limit: uint64(len(zone.Validators))}} + bz := cdc.MustMarshal(&delegationQuery) + + err := keeper.DelegationsCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + + s.Require().NoError(err) + + delegationRequests := 0 + for _, query := range app.InterchainQueryKeeper.AllQueries(ctx) { + if query.CallbackId == "delegation" { + delegationRequests++ + } + } + + s.Require().Equal(1, delegationRequests) + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) // new delegation doesn't get added until the end +} + +func TestDelegationCallbackNew(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[3].OperatorAddress, Shares: sdk.NewDec(1000)} + + data := cdc.MustMarshal(&response) + + delAddr, err := utils.AccAddressFromBech32(zone.DelegationAddress.Address, "") + s.Require().NoError(err) + valAddr, err := utils.ValAddressFromBech32(vals[3].OperatorAddress, "") + s.Require().NoError(err) + bz := stakingtypes.GetDelegationKey(delAddr, valAddr) + + err = keeper.DelegationCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + s.Require().NoError(err) + + s.Require().Equal(4, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} + +func TestDelegationCallbackUpdate(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Shares: sdk.NewDec(2000)} + + data := cdc.MustMarshal(&response) + + delAddr, err := utils.AccAddressFromBech32(zone.DelegationAddress.Address, "") + s.Require().NoError(err) + valAddr, err := utils.ValAddressFromBech32(vals[3].OperatorAddress, "") + s.Require().NoError(err) + bz := stakingtypes.GetDelegationKey(delAddr, valAddr) + + err = keeper.DelegationCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + s.Require().NoError(err) + + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} + +func TestDelegationCallbackNoOp(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.Delegation{DelegatorAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Shares: sdk.NewDec(1000)} + + data := cdc.MustMarshal(&response) + + delAddr, err := utils.AccAddressFromBech32(zone.DelegationAddress.Address, "") + s.Require().NoError(err) + valAddr, err := utils.ValAddressFromBech32(vals[3].OperatorAddress, "") + s.Require().NoError(err) + bz := stakingtypes.GetDelegationKey(delAddr, valAddr) + + err = keeper.DelegationCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + s.Require().NoError(err) + + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} + +func TestDelegationCallbackRemove(t *testing.T) { + s := new(KeeperTestSuite) + s.SetT(t) + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + app.InterchainstakingKeeper.CallbackHandler().RegisterCallbacks() + ctx := s.chainA.GetContext() + cdc := app.IBCKeeper.Codec() + + zone, found := app.InterchainstakingKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationA) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationB) + app.InterchainstakingKeeper.SetDelegation(ctx, &zone, delegationC) + + response := stakingtypes.Delegation{} + + data := cdc.MustMarshal(&response) + + delAddr, err := utils.AccAddressFromBech32(zone.DelegationAddress.Address, "") + s.Require().NoError(err) + valAddr, err := utils.ValAddressFromBech32(vals[3].OperatorAddress, "") + s.Require().NoError(err) + bz := stakingtypes.GetDelegationKey(delAddr, valAddr) + + err = keeper.DelegationCallback(app.InterchainstakingKeeper, ctx, data, icqtypes.Query{ChainId: s.chainB.ChainID, Request: bz}) + s.Require().NoError(err) + + delegationRequests := 0 + for _, query := range app.InterchainQueryKeeper.AllQueries(ctx) { + if query.CallbackId == "delegation" { + delegationRequests++ + } + } + + s.Require().Equal(3, len(app.InterchainstakingKeeper.GetAllDelegations(ctx, &zone))) +} diff --git a/x/interchainstaking/keeper/hooks.go b/x/interchainstaking/keeper/hooks.go index 50a9a3033..6b633cc05 100644 --- a/x/interchainstaking/keeper/hooks.go +++ b/x/interchainstaking/keeper/hooks.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -59,7 +60,7 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb // OnChanOpenAck calls SetWithdrawalAddress (see ibc_module.go) k.Logger(ctx).Info("Withdrawing rewards") - delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zoneInfo.DelegationAddress.Address} + delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: zoneInfo.DelegationAddress.Address, Pagination: &query.PageRequest{Limit: uint64(len(zoneInfo.Validators))}} bz := k.cdc.MustMarshal(&delegationQuery) k.ICQKeeper.MakeRequest( @@ -73,7 +74,6 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb "delegations", 0, ) - // zoneInfo.DelegationAddress.IncrementBalanceWaitgroup() rewardsQuery := distrtypes.QueryDelegationTotalRewardsRequest{DelegatorAddress: zoneInfo.DelegationAddress.Address} bz = k.cdc.MustMarshal(&rewardsQuery) diff --git a/x/interchainstaking/keeper/ibc_packet_handlers.go b/x/interchainstaking/keeper/ibc_packet_handlers.go index d46d2696b..3e89382fd 100644 --- a/x/interchainstaking/keeper/ibc_packet_handlers.go +++ b/x/interchainstaking/keeper/ibc_packet_handlers.go @@ -653,20 +653,31 @@ func (k *Keeper) HandleUndelegate(ctx sdk.Context, msg sdk.Msg, completion time. k.Logger(ctx).Info("withdrawal record to save", "rcd", record) k.UpdateWithdrawalRecordStatus(ctx, &record, WithdrawStatusUnbond) } - delegationQuery := stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: undelegateMsg.DelegatorAddress} - bz := k.cdc.MustMarshal(&delegationQuery) + delAddr, err := utils.AccAddressFromBech32(undelegateMsg.DelegatorAddress, "") + if err != nil { + return err + } + valAddr, err := utils.ValAddressFromBech32(undelegateMsg.ValidatorAddress, "") + if err != nil { + return err + } + + data := stakingtypes.GetDelegationKey(delAddr, valAddr) + + // send request to update delegation record for undelegated del/val tuple. k.ICQKeeper.MakeRequest( ctx, zone.ConnectionId, zone.ChainId, - "cosmos.staking.v1beta1.Query/DelegatorDelegations", - bz, + "store/staking/key", + data, sdk.NewInt(-1), types.ModuleName, - "delegations", + "delegation", 0, ) + return nil } @@ -809,11 +820,8 @@ func (k *Keeper) UpdateDelegationRecordsForAddress(ctx sdk.Context, zone types.Z } data := stakingtypes.GetDelegationKey(delAddr, valAddr) - if err := k.RemoveDelegation(ctx, &zone, existingDelegation); err != nil { - return err - } - - // send request to prove delegation no longer exists. + // send request to prove delegation no longer exists. If the response is nil (i.e. no delegation), then + // the delegation record is removed by the callback. k.ICQKeeper.MakeRequest( ctx, zone.ConnectionId, @@ -827,8 +835,6 @@ func (k *Keeper) UpdateDelegationRecordsForAddress(ctx sdk.Context, zone types.Z ) } - // k.SetZone(ctx, &zone) - return nil } diff --git a/x/interchainstaking/keeper/keeper.go b/x/interchainstaking/keeper/keeper.go index 96863f72e..855e8516a 100644 --- a/x/interchainstaking/keeper/keeper.go +++ b/x/interchainstaking/keeper/keeper.go @@ -317,7 +317,7 @@ func (k Keeper) depositInterval(ctx sdk.Context) zoneItrFn { if !zoneInfo.DepositAddress.Balance.Empty() { k.Logger(ctx).Info("balance is non zero", "balance", zoneInfo.DepositAddress.Balance) - req := tx.GetTxsEventRequest{Events: []string{"transfer.recipient='" + zoneInfo.DepositAddress.GetAddress() + "'"}, Pagination: &query.PageRequest{Limit: types.TxRetrieveCount, Reverse: true}} + req := tx.GetTxsEventRequest{Events: []string{"transfer.recipient='" + zoneInfo.DepositAddress.GetAddress() + "'"}, OrderBy: tx.OrderBy_ORDER_BY_DESC, Pagination: &query.PageRequest{Limit: types.TxRetrieveCount}} k.ICQKeeper.MakeRequest(ctx, zoneInfo.ConnectionId, zoneInfo.ChainId, "cosmos.tx.v1beta1.Service/GetTxsEvent", k.cdc.MustMarshal(&req), sdk.NewInt(-1), types.ModuleName, "depositinterval", 0) } @@ -440,6 +440,16 @@ func (k *Keeper) UpdateRedemptionRate(ctx sdk.Context, zone types.Zone, epochRew k.SetZone(ctx, &zone) } +func (k *Keeper) OverrideRedemptionRateNoCap(ctx sdk.Context, zone types.Zone) { + ratio, _ := k.GetRatio(ctx, zone, sdk.ZeroInt()) + k.Logger(ctx).Info("Last redemption rate", "rate", zone.LastRedemptionRate) + k.Logger(ctx).Info("Current redemption rate", "rate", zone.RedemptionRate) + k.Logger(ctx).Info("New redemption rate", "rate", ratio, "supply", k.BankKeeper.GetSupply(ctx, zone.LocalDenom).Amount, "lv", k.GetDelegatedAmount(ctx, &zone).Amount) + + zone.RedemptionRate = ratio + k.SetZone(ctx, &zone) +} + func (k *Keeper) GetRatio(ctx sdk.Context, zone types.Zone, epochRewards math.Int) (sdk.Dec, bool) { // native asset amount nativeAssetAmount := k.GetDelegatedAmount(ctx, &zone).Amount diff --git a/x/interchainstaking/keeper/keeper_test.go b/x/interchainstaking/keeper/keeper_test.go index d365b91cf..efe3415ff 100644 --- a/x/interchainstaking/keeper/keeper_test.go +++ b/x/interchainstaking/keeper/keeper_test.go @@ -491,3 +491,155 @@ func (s *KeeperTestSuite) TestGetRatio() { }) } } + +func (s *KeeperTestSuite) TestUpdateRedemptionRate() { + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + ctx := s.chainA.GetContext() + icsKeeper := app.InterchainstakingKeeper + zone, found := icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + + app.MintKeeper.MintCoins(ctx, sdk.NewCoins(sdk.NewCoin(zone.LocalDenom, sdk.NewInt(3000)))) + + // no change! + s.Require().Equal(sdk.OneDec(), zone.RedemptionRate) + icsKeeper.UpdateRedemptionRate(ctx, zone, sdk.ZeroInt()) + + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.OneDec(), zone.RedemptionRate) + + // add 1% + s.Require().Equal(sdk.OneDec(), zone.RedemptionRate) + icsKeeper.UpdateRedemptionRate(ctx, zone, sdk.NewInt(30)) + delegationA.Amount.Amount = delegationA.Amount.Amount.AddRaw(10) + delegationB.Amount.Amount = delegationB.Amount.Amount.AddRaw(10) + delegationC.Amount.Amount = delegationC.Amount.Amount.AddRaw(10) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.NewDecWithPrec(101, 2), zone.RedemptionRate) + + // add >2%; cap at 2% + icsKeeper.UpdateRedemptionRate(ctx, zone, sdk.NewInt(500)) + delegationA.Amount.Amount = delegationA.Amount.Amount.AddRaw(166) + delegationB.Amount.Amount = delegationB.Amount.Amount.AddRaw(167) + delegationC.Amount.Amount = delegationC.Amount.Amount.AddRaw(167) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + // should be capped at 2% increase. (1.01*1.02 == 1.0302) + s.Require().Equal(sdk.NewDecWithPrec(10302, 4), zone.RedemptionRate) + + // add nothing, still cap at 2% + icsKeeper.UpdateRedemptionRate(ctx, zone, sdk.ZeroInt()) + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + // should be capped at 2% increase. (1.01*1.02*1.02 == 1.050804) + s.Require().Equal(sdk.NewDecWithPrec(1050804, 6), zone.RedemptionRate) + + delegationA.Amount.Amount = delegationA.Amount.Amount.SubRaw(500) + delegationB.Amount.Amount = delegationB.Amount.Amount.SubRaw(500) + delegationC.Amount.Amount = delegationC.Amount.Amount.SubRaw(500) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + + // remove > 5%, cap at -5% + icsKeeper.UpdateRedemptionRate(ctx, zone, sdk.ZeroInt()) + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + s.Require().Equal(sdk.NewDecWithPrec(9982638, 7), zone.RedemptionRate) +} + +func (s *KeeperTestSuite) TestOverrideRedemptionRateNoCap() { + s.SetupTest() + s.setupTestZones() + + app := s.GetQuicksilverApp(s.chainA) + ctx := s.chainA.GetContext() + icsKeeper := app.InterchainstakingKeeper + zone, found := icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + vals := s.GetQuicksilverApp(s.chainB).StakingKeeper.GetAllValidators(s.chainB.GetContext()) + delegationA := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[0].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationB := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[1].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + delegationC := icstypes.Delegation{DelegationAddress: zone.DelegationAddress.Address, ValidatorAddress: vals[2].OperatorAddress, Amount: sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))} + + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + + app.MintKeeper.MintCoins(ctx, sdk.NewCoins(sdk.NewCoin(zone.LocalDenom, sdk.NewInt(3000)))) + + // no change! + s.Require().Equal(sdk.OneDec(), zone.RedemptionRate) + icsKeeper.OverrideRedemptionRateNoCap(ctx, zone) + + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.OneDec(), zone.RedemptionRate) + + // add 1% + delegationA.Amount.Amount = delegationA.Amount.Amount.AddRaw(10) + delegationB.Amount.Amount = delegationB.Amount.Amount.AddRaw(10) + delegationC.Amount.Amount = delegationC.Amount.Amount.AddRaw(10) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + icsKeeper.OverrideRedemptionRateNoCap(ctx, zone) + + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.NewDecWithPrec(101, 2), zone.RedemptionRate) + + // add >2%; no cap + delegationA.Amount.Amount = delegationA.Amount.Amount.AddRaw(166) + delegationB.Amount.Amount = delegationB.Amount.Amount.AddRaw(167) + delegationC.Amount.Amount = delegationC.Amount.Amount.AddRaw(167) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + icsKeeper.OverrideRedemptionRateNoCap(ctx, zone) + + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.NewDecWithPrec(1176666666666666667, 18), zone.RedemptionRate) + + // add nothing, no change + icsKeeper.OverrideRedemptionRateNoCap(ctx, zone) + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + s.Require().Equal(sdk.NewDecWithPrec(1176666666666666667, 18), zone.RedemptionRate) + + delegationA.Amount.Amount = delegationA.Amount.Amount.SubRaw(500) + delegationB.Amount.Amount = delegationB.Amount.Amount.SubRaw(500) + delegationC.Amount.Amount = delegationC.Amount.Amount.SubRaw(500) + icsKeeper.SetDelegation(ctx, &zone, delegationA) + icsKeeper.SetDelegation(ctx, &zone, delegationB) + icsKeeper.SetDelegation(ctx, &zone, delegationC) + icsKeeper.OverrideRedemptionRateNoCap(ctx, zone) + zone, found = icsKeeper.GetZone(ctx, s.chainB.ChainID) + s.Require().True(found) + + s.Require().Equal(sdk.NewDecWithPrec(676666666666666667, 18), zone.RedemptionRate) +} diff --git a/x/interchainstaking/keeper/zones.go b/x/interchainstaking/keeper/zones.go index b43668fe4..e00c0f151 100644 --- a/x/interchainstaking/keeper/zones.go +++ b/x/interchainstaking/keeper/zones.go @@ -178,9 +178,14 @@ func (k *Keeper) EnsureWithdrawalAddresses(ctx sdk.Context, zone *types.Zone) er k.Logger(ctx).Info("Delegation address not set") return nil } + + if zone.DepositAddress == nil { + k.Logger(ctx).Info("Deposit address not set") + return nil + } withdrawalAddress := zone.WithdrawalAddress.Address - if zone.DepositAddress.WithdrawalAddress != zone.DepositAddress.Address { + if zone.DepositAddress.WithdrawalAddress != zone.WithdrawalAddress.Address { msg := distrTypes.MsgSetWithdrawAddress{DelegatorAddress: zone.DepositAddress.Address, WithdrawAddress: withdrawalAddress} err := k.SubmitTx(ctx, []sdk.Msg{&msg}, zone.DepositAddress, "") if err != nil {