From 074ca80bdab6bb6e21e0d377930760fc453e3a7d Mon Sep 17 00:00:00 2001 From: sampocs Date: Tue, 5 Dec 2023 18:02:27 -0600 Subject: [PATCH 1/4] added unit test for pool price query --- x/interchainquery/types/keys.go | 5 ++ x/stakeibc/keeper/keeper_test.go | 7 +-- x/stakeibc/keeper/reward_converter.go | 12 ++-- x/stakeibc/keeper/reward_converter_test.go | 72 +++++++++++++++++++++- 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/x/interchainquery/types/keys.go b/x/interchainquery/types/keys.go index 03de143c0..7da8303e9 100644 --- a/x/interchainquery/types/keys.go +++ b/x/interchainquery/types/keys.go @@ -54,6 +54,11 @@ func KeyPrefix(p string) []byte { } func FormatOsmosisMostRecentTWAPKey(poolId uint64, denom1, denom2 string) []byte { + // Sort denoms + if denom1 > denom2 { + denom1, denom2 = denom2, denom1 + } + poolIdBz := fmt.Sprintf("%0.20d", poolId) return []byte(fmt.Sprintf("%s%s%s%s%s%s", OsmosisMostRecentTWAPsPrefix, poolIdBz, OsmosisKeySeparator, denom1, OsmosisKeySeparator, denom2)) } diff --git a/x/stakeibc/keeper/keeper_test.go b/x/stakeibc/keeper/keeper_test.go index 94976b8b1..b9adb41f7 100644 --- a/x/stakeibc/keeper/keeper_test.go +++ b/x/stakeibc/keeper/keeper_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/Stride-Labs/stride/v16/app/apptesting" - epochtypes "github.com/Stride-Labs/stride/v16/x/epochs/types" "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) @@ -67,11 +66,11 @@ func (s *KeeperTestSuite) MustGetHostZone(chainId string) types.HostZone { return hostZone } -// Helper function to create an stride epoch tracker that dictates the timeout -func (s *KeeperTestSuite) CreateStrideEpochForICATimeout(timeoutDuration time.Duration) { +// Helper function to create an epoch tracker that dictates the timeout +func (s *KeeperTestSuite) CreateEpochForICATimeout(epochType string, timeoutDuration time.Duration) { epochEndTime := uint64(s.Ctx.BlockTime().Add(timeoutDuration).UnixNano()) epochTracker := types.EpochTracker{ - EpochIdentifier: epochtypes.STRIDE_EPOCH, + EpochIdentifier: epochType, NextEpochStartTime: epochEndTime, } s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, epochTracker) diff --git a/x/stakeibc/keeper/reward_converter.go b/x/stakeibc/keeper/reward_converter.go index 6cc4eb59c..d877f6465 100644 --- a/x/stakeibc/keeper/reward_converter.go +++ b/x/stakeibc/keeper/reward_converter.go @@ -434,14 +434,12 @@ func (k Keeper) PoolPriceQuery(ctx sdk.Context, route types.TradeRoute) error { tradeAccount := route.TradeAccount k.Logger(ctx).Info(utils.LogWithHostZone(tradeAccount.ChainId, "Submitting ICQ for spot price in this pool")) - // Sort denom's - denom1, denom2 := route.RewardDenomOnTradeZone, route.HostDenomOnTradeZone - if denom1 > denom2 { - denom1, denom2 = denom2, denom1 - } - // Build query request data which consists of the TWAP store key built from each denom - queryData := icqtypes.FormatOsmosisMostRecentTWAPKey(route.TradeConfig.PoolId, denom1, denom2) + queryData := icqtypes.FormatOsmosisMostRecentTWAPKey( + route.TradeConfig.PoolId, + route.RewardDenomOnTradeZone, + route.HostDenomOnTradeZone, + ) // Timeout query at end of epoch hourEpochTracker, found := k.GetEpochTracker(ctx, epochstypes.HOUR_EPOCH) diff --git a/x/stakeibc/keeper/reward_converter_test.go b/x/stakeibc/keeper/reward_converter_test.go index f6f510822..549c6b700 100644 --- a/x/stakeibc/keeper/reward_converter_test.go +++ b/x/stakeibc/keeper/reward_converter_test.go @@ -6,10 +6,13 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/gogoproto/proto" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibctesting "github.com/cosmos/ibc-go/v7/testing" epochtypes "github.com/Stride-Labs/stride/v16/x/epochs/types" + icqtypes "github.com/Stride-Labs/stride/v16/x/interchainquery/types" + "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) @@ -313,7 +316,7 @@ func (s *KeeperTestSuite) TestSwapRewardTokens() { } // Create an epoch tracker to dictate the timeout - s.CreateStrideEpochForICATimeout(time.Minute) // arbitrary timeout time + s.CreateEpochForICATimeout(epochtypes.STRIDE_EPOCH, time.Minute) // arbitrary timeout time // Execute the swap and confirm the sequence number increments startSequence := s.MustGetNextSequenceNumber(portId, channelId) @@ -344,3 +347,70 @@ func (s *KeeperTestSuite) TestSwapRewardTokens() { err = s.App.StakeibcKeeper.SwapRewardTokens(s.Ctx, rewardAmount, route) s.Require().ErrorContains(err, "epoch not found") } + +func (s *KeeperTestSuite) TestPoolPriceQuery() { + // Create a transfer channel so the connection exists for the query submission + s.CreateTransferChannel(HostChainId) + + // Create an epoch tracker to dictate the query timeout + timeoutDuration := time.Minute * 10 + s.CreateEpochForICATimeout(epochtypes.HOUR_EPOCH, timeoutDuration) + + // Define the trade route + poolId := uint64(100) + tradeRewardDenom := "ibc/reward-denom-on-trade" + tradeHostDenom := "ibc/reward-denom-on-host" + + route := types.TradeRoute{ + RewardDenomOnRewardZone: RewardDenom, + HostDenomOnHostZone: HostDenom, + RewardDenomOnTradeZone: tradeRewardDenom, + HostDenomOnTradeZone: tradeHostDenom, + + TradeAccount: types.ICAAccount{ + ChainId: HostChainId, + ConnectionId: ibctesting.FirstConnectionID, + }, + TradeConfig: types.TradeConfig{ + PoolId: poolId, + }, + } + + expectedCallbackData := types.TradeRouteCallback{ + RewardDenom: RewardDenom, + HostDenom: HostDenom, + } + + // Submit the pool price ICQ + err := s.App.StakeibcKeeper.PoolPriceQuery(s.Ctx, route) + s.Require().NoError(err, "no error expected when submitting pool price query") + + // Confirm the query object was stored + queries := s.App.InterchainqueryKeeper.AllQueries(s.Ctx) + s.Require().Len(queries, 1, "there should have been 1 query submitted") + query := queries[0] + + s.Require().Equal(HostChainId, query.ChainId, "query chain ID") + s.Require().Equal(ibctesting.FirstConnectionID, query.ConnectionId, "query connection ID") + s.Require().Equal(icqtypes.TWAP_STORE_QUERY_WITH_PROOF, query.QueryType, "query type") + s.Require().Equal(types.ModuleName, query.CallbackModule, "query module") + + // Confirm the query request key is the same regardless of which order the denom's are specified + expectedRequestData := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeRewardDenom, tradeHostDenom) + expectedRequestDataSwapped := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeHostDenom, tradeRewardDenom) + s.Require().Equal(string(expectedRequestData), string(query.RequestData), "query request data") + s.Require().Equal(expectedRequestData, expectedRequestDataSwapped, "osmosis twap denoms should be sorted") + + // Check the callback data + var actualCallbackData types.TradeRouteCallback + err = proto.Unmarshal(query.CallbackData, &actualCallbackData) + s.Require().NoError(err, "no error expected when unmarshalling callback data") + s.Require().Equal(expectedCallbackData, actualCallbackData, "query callback ID") + s.Require().Equal(keeper.ICQCallbackID_PoolPrice, query.CallbackId, "query callback ID") + + // Check the query timeout + expectedTimeoutTimestamp := s.Ctx.BlockTime().Add(timeoutDuration).UnixNano() + s.Require().Equal(timeoutDuration, query.TimeoutDuration, "query timeout duration") + s.Require().Equal(expectedTimeoutTimestamp, int64(query.TimeoutTimestamp), "query timeout timestamp") + s.Require().Equal(icqtypes.TimeoutPolicy_REJECT_QUERY_RESPONSE, query.TimeoutPolicy, "query timeout policy") +} From d21779601658c34921e5cc4d233e84965c98a79e Mon Sep 17 00:00:00 2001 From: sampocs Date: Tue, 5 Dec 2023 18:54:21 -0600 Subject: [PATCH 2/4] added helper for validating query --- x/stakeibc/keeper/keeper_test.go | 29 ++++++++++++++++++++++ x/stakeibc/keeper/reward_converter_test.go | 29 ++++++++++------------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/x/stakeibc/keeper/keeper_test.go b/x/stakeibc/keeper/keeper_test.go index b9adb41f7..44e826b04 100644 --- a/x/stakeibc/keeper/keeper_test.go +++ b/x/stakeibc/keeper/keeper_test.go @@ -5,9 +5,11 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" "github.com/stretchr/testify/suite" "github.com/Stride-Labs/stride/v16/app/apptesting" + icqtypes "github.com/Stride-Labs/stride/v16/x/interchainquery/types" "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) @@ -76,6 +78,33 @@ func (s *KeeperTestSuite) CreateEpochForICATimeout(epochType string, timeoutDura s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, epochTracker) } +// Validates the query object stored after an ICQ submission, using some default testing +// values (e.g. HostChainId, stakeibc module name, etc.) +func (s *KeeperTestSuite) ValidateQueryObject( + query icqtypes.Query, + queryType string, + queryData []byte, + callbackId string, + timeoutDuration time.Duration, + timeoutPolicy icqtypes.TimeoutPolicy, +) { + // Validate the chainId and connectionId + s.Require().Equal(HostChainId, query.ChainId, "query chain ID") + s.Require().Equal(ibctesting.FirstConnectionID, query.ConnectionId, "query connection ID") + s.Require().Equal(types.ModuleName, query.CallbackModule, "query module") + + // Validate the query type and request data + s.Require().Equal(queryType, query.QueryType, "query type") + s.Require().Equal(string(queryData), string(query.RequestData), "query request data") + s.Require().Equal(callbackId, query.CallbackId, "query callback ID") + + // Validate the query timeout + expectedTimeoutTimestamp := s.Ctx.BlockTime().Add(timeoutDuration).UnixNano() + s.Require().Equal(timeoutDuration, query.TimeoutDuration, "query timeout duration") + s.Require().Equal(expectedTimeoutTimestamp, int64(query.TimeoutTimestamp), "query timeout timestamp") + s.Require().Equal(icqtypes.TimeoutPolicy_REJECT_QUERY_RESPONSE, query.TimeoutPolicy, "query timeout policy") +} + func (s *KeeperTestSuite) TestIsRedemptionRateWithinSafetyBounds() { params := s.App.StakeibcKeeper.GetParams(s.Ctx) params.DefaultMinRedemptionRateThreshold = 75 diff --git a/x/stakeibc/keeper/reward_converter_test.go b/x/stakeibc/keeper/reward_converter_test.go index 549c6b700..c77472268 100644 --- a/x/stakeibc/keeper/reward_converter_test.go +++ b/x/stakeibc/keeper/reward_converter_test.go @@ -390,27 +390,24 @@ func (s *KeeperTestSuite) TestPoolPriceQuery() { s.Require().Len(queries, 1, "there should have been 1 query submitted") query := queries[0] - s.Require().Equal(HostChainId, query.ChainId, "query chain ID") - s.Require().Equal(ibctesting.FirstConnectionID, query.ConnectionId, "query connection ID") - s.Require().Equal(icqtypes.TWAP_STORE_QUERY_WITH_PROOF, query.QueryType, "query type") - s.Require().Equal(types.ModuleName, query.CallbackModule, "query module") - // Confirm the query request key is the same regardless of which order the denom's are specified expectedRequestData := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeRewardDenom, tradeHostDenom) expectedRequestDataSwapped := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeHostDenom, tradeRewardDenom) - s.Require().Equal(string(expectedRequestData), string(query.RequestData), "query request data") s.Require().Equal(expectedRequestData, expectedRequestDataSwapped, "osmosis twap denoms should be sorted") - // Check the callback data + // Validate the fields of the query + s.ValidateQueryObject( + query, + icqtypes.TWAP_STORE_QUERY_WITH_PROOF, + expectedRequestData, + keeper.ICQCallbackID_PoolPrice, + timeoutDuration, + icqtypes.TimeoutPolicy_REJECT_QUERY_RESPONSE, + ) + + // Validate the query callback data var actualCallbackData types.TradeRouteCallback err = proto.Unmarshal(query.CallbackData, &actualCallbackData) - s.Require().NoError(err, "no error expected when unmarshalling callback data") - s.Require().Equal(expectedCallbackData, actualCallbackData, "query callback ID") - s.Require().Equal(keeper.ICQCallbackID_PoolPrice, query.CallbackId, "query callback ID") - - // Check the query timeout - expectedTimeoutTimestamp := s.Ctx.BlockTime().Add(timeoutDuration).UnixNano() - s.Require().Equal(timeoutDuration, query.TimeoutDuration, "query timeout duration") - s.Require().Equal(expectedTimeoutTimestamp, int64(query.TimeoutTimestamp), "query timeout timestamp") - s.Require().Equal(icqtypes.TimeoutPolicy_REJECT_QUERY_RESPONSE, query.TimeoutPolicy, "query timeout policy") + s.Require().NoError(err) + s.Require().Equal(expectedCallbackData, actualCallbackData, "query callback data") } From 8432feb28baabdf67bd2210395eb769f64b5bdd0 Mon Sep 17 00:00:00 2001 From: sampocs Date: Tue, 5 Dec 2023 19:20:51 -0600 Subject: [PATCH 3/4] moved query length check --- x/stakeibc/keeper/keeper_test.go | 15 +++++++++++---- x/stakeibc/keeper/reward_converter_test.go | 8 +------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/x/stakeibc/keeper/keeper_test.go b/x/stakeibc/keeper/keeper_test.go index 44e826b04..b7b3d3a9c 100644 --- a/x/stakeibc/keeper/keeper_test.go +++ b/x/stakeibc/keeper/keeper_test.go @@ -79,15 +79,20 @@ func (s *KeeperTestSuite) CreateEpochForICATimeout(epochType string, timeoutDura } // Validates the query object stored after an ICQ submission, using some default testing -// values (e.g. HostChainId, stakeibc module name, etc.) -func (s *KeeperTestSuite) ValidateQueryObject( - query icqtypes.Query, +// values (e.g. HostChainId, stakeibc module name, etc.), and returning the query +// NOTE: This assumes there was only one submission and grabs the first query from the store +func (s *KeeperTestSuite) ValidateQuerySubmission( queryType string, queryData []byte, callbackId string, timeoutDuration time.Duration, timeoutPolicy icqtypes.TimeoutPolicy, -) { +) icqtypes.Query { + // Check that there's only one query + queries := s.App.InterchainqueryKeeper.AllQueries(s.Ctx) + s.Require().Len(queries, 1, "there should have been 1 query submitted") + query := queries[0] + // Validate the chainId and connectionId s.Require().Equal(HostChainId, query.ChainId, "query chain ID") s.Require().Equal(ibctesting.FirstConnectionID, query.ConnectionId, "query connection ID") @@ -103,6 +108,8 @@ func (s *KeeperTestSuite) ValidateQueryObject( s.Require().Equal(timeoutDuration, query.TimeoutDuration, "query timeout duration") s.Require().Equal(expectedTimeoutTimestamp, int64(query.TimeoutTimestamp), "query timeout timestamp") s.Require().Equal(icqtypes.TimeoutPolicy_REJECT_QUERY_RESPONSE, query.TimeoutPolicy, "query timeout policy") + + return query } func (s *KeeperTestSuite) TestIsRedemptionRateWithinSafetyBounds() { diff --git a/x/stakeibc/keeper/reward_converter_test.go b/x/stakeibc/keeper/reward_converter_test.go index c77472268..125ddb073 100644 --- a/x/stakeibc/keeper/reward_converter_test.go +++ b/x/stakeibc/keeper/reward_converter_test.go @@ -385,19 +385,13 @@ func (s *KeeperTestSuite) TestPoolPriceQuery() { err := s.App.StakeibcKeeper.PoolPriceQuery(s.Ctx, route) s.Require().NoError(err, "no error expected when submitting pool price query") - // Confirm the query object was stored - queries := s.App.InterchainqueryKeeper.AllQueries(s.Ctx) - s.Require().Len(queries, 1, "there should have been 1 query submitted") - query := queries[0] - // Confirm the query request key is the same regardless of which order the denom's are specified expectedRequestData := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeRewardDenom, tradeHostDenom) expectedRequestDataSwapped := icqtypes.FormatOsmosisMostRecentTWAPKey(poolId, tradeHostDenom, tradeRewardDenom) s.Require().Equal(expectedRequestData, expectedRequestDataSwapped, "osmosis twap denoms should be sorted") // Validate the fields of the query - s.ValidateQueryObject( - query, + query := s.ValidateQuerySubmission( icqtypes.TWAP_STORE_QUERY_WITH_PROOF, expectedRequestData, keeper.ICQCallbackID_PoolPrice, From 67f45a80fb3b8306a2d5ca7d4c6e003d6aec8bc8 Mon Sep 17 00:00:00 2001 From: sampocs Date: Thu, 7 Dec 2023 16:21:14 -0600 Subject: [PATCH 4/4] added error test cases for pool price query --- x/stakeibc/keeper/reward_converter_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/x/stakeibc/keeper/reward_converter_test.go b/x/stakeibc/keeper/reward_converter_test.go index 5c4633701..9ed5a1633 100644 --- a/x/stakeibc/keeper/reward_converter_test.go +++ b/x/stakeibc/keeper/reward_converter_test.go @@ -479,4 +479,15 @@ func (s *KeeperTestSuite) TestPoolPriceQuery() { err = proto.Unmarshal(query.CallbackData, &actualCallbackData) s.Require().NoError(err) s.Require().Equal(expectedCallbackData, actualCallbackData, "query callback data") + + // Remove the connection ID from the trade account and confirm the query submission fails + invalidRoute := route + invalidRoute.TradeAccount.ConnectionId = "" + err = s.App.StakeibcKeeper.PoolPriceQuery(s.Ctx, invalidRoute) + s.Require().ErrorContains(err, "invalid interchain query request") + + // Remove the epoch tracker so the function fails to get a timeout + s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.HOUR_EPOCH) + err = s.App.StakeibcKeeper.PoolPriceQuery(s.Ctx, route) + s.Require().ErrorContains(err, "hour: epoch not found") }