Skip to content

Commit

Permalink
reward converter - pool price query unit test (#1016)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs authored Dec 8, 2023
1 parent 6704631 commit 9fabe9b
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 7 deletions.
5 changes: 5 additions & 0 deletions x/interchainquery/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
36 changes: 36 additions & 0 deletions x/stakeibc/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
"github.com/stretchr/testify/suite"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

"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"
)
Expand Down Expand Up @@ -81,6 +83,40 @@ 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.), 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")
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")

return query
}

func (s *KeeperTestSuite) TestIsRedemptionRateWithinSafetyBounds() {
params := s.App.StakeibcKeeper.GetParams(s.Ctx)
params.DefaultMinRedemptionRateThreshold = 75
Expand Down
12 changes: 5 additions & 7 deletions x/stakeibc/keeper/reward_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,14 +461,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)
Expand Down
72 changes: 72 additions & 0 deletions x/stakeibc/keeper/reward_converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -419,3 +422,72 @@ 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 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
query := s.ValidateQuerySubmission(
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)
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")
}

0 comments on commit 9fabe9b

Please sign in to comment.