Skip to content

Commit

Permalink
reward converter - Create/Update/Delete TradeRoute unit tests (#1015)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs committed Dec 8, 2023
1 parent 913b4d7 commit 6704631
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 3 deletions.
28 changes: 27 additions & 1 deletion app/apptesting/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,18 +449,44 @@ func (s *AppTestHelper) MockClientLatestHeight(height uint64) {

// Helper function to mock out a client and connection to test
// mapping from connection ID back to chain ID
// This also mocks out the consensus state to enable testing registering interchain accounts
func (s *AppTestHelper) MockClientAndConnection(chainId, clientId, connectionId string) {
clientHeight := clienttypes.Height{
RevisionHeight: uint64(s.Ctx.BlockHeight()),
}
clientState := tendermint.ClientState{
ChainId: chainId,
ChainId: chainId,
LatestHeight: clientHeight,
TrustingPeriod: time.Minute * 10,
}
s.App.IBCKeeper.ClientKeeper.SetClientState(s.Ctx, clientId, &clientState)

consensusState := tendermint.ConsensusState{
Timestamp: s.Ctx.BlockTime(),
}
s.App.IBCKeeper.ClientKeeper.SetClientConsensusState(s.Ctx, clientId, clientHeight, &consensusState)

connection := connectiontypes.ConnectionEnd{
ClientId: clientId,
Versions: []*connectiontypes.Version{connectiontypes.DefaultIBCVersion},
}
s.App.IBCKeeper.ConnectionKeeper.SetConnection(s.Ctx, connectionId, connection)
}

// Helper function to mock out an ICA address
func (s *AppTestHelper) MockICAChannel(connectionId, channelId, owner, address string) {
// Create an open channel with the ICA port
portId, _ := icatypes.NewControllerPortID(owner)
channel := channeltypes.Channel{
State: channeltypes.OPEN,
}
s.App.IBCKeeper.ChannelKeeper.SetChannel(s.Ctx, portId, channelId, channel)

// Then set the address and make the channel active
s.App.ICAControllerKeeper.SetInterchainAccountAddress(s.Ctx, connectionId, portId, address)
s.App.ICAControllerKeeper.SetActiveChannelID(s.Ctx, connectionId, portId, channelId)
}

func (s *AppTestHelper) ConfirmUpgradeSucceededs(upgradeName string, upgradeHeight int64) {
s.Ctx = s.Ctx.WithBlockHeight(upgradeHeight - 1)
plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight}
Expand Down
7 changes: 6 additions & 1 deletion x/stakeibc/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"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"
"github.com/Stride-Labs/stride/v16/x/stakeibc/keeper"
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)

const (
var (
Atom = "uatom"
StAtom = "stuatom"
IbcAtom = "ibc/uatom"
Expand All @@ -36,6 +39,8 @@ const (
DepositAddress = "deposit"
CommunityPoolStakeHoldingAddress = "staking-holding"
CommunityPoolRedeemHoldingAddress = "redeem-holding"

Authority = authtypes.NewModuleAddress(govtypes.ModuleName).String()
)

type KeeperTestSuite struct {
Expand Down
2 changes: 1 addition & 1 deletion x/stakeibc/keeper/msg_server_create_trade_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (ms msgServer) CreateTradeRoute(goCtx context.Context, msg *types.MsgCreate
_, found := ms.Keeper.GetTradeRoute(ctx, msg.RewardDenomOnReward, msg.HostDenomOnHost)
if found {
return nil, errorsmod.Wrapf(types.ErrTradeRouteAlreadyExists,
"startDenom: %s, endDenom: %s", msg.RewardDenomOnReward, msg.HostDenomOnHost)
"trade route already exists for rewardDenom %s, hostDenom %s", msg.RewardDenomOnReward, msg.HostDenomOnHost)
}

// Confirm the host chain exists and the withdrawal address has been initialized
Expand Down
231 changes: 231 additions & 0 deletions x/stakeibc/keeper/msg_server_create_trade_route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package keeper_test

import (
sdkmath "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"

"github.com/Stride-Labs/stride/v16/x/stakeibc/keeper"
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)

func (s *KeeperTestSuite) SetupTestCreateTradeRoute() (msg types.MsgCreateTradeRoute, expectedTradeRoute types.TradeRoute) {
rewardChainId := "reward-0"
tradeChainId := "trade-0"

hostConnectionId := "connection-0"
rewardConnectionId := "connection-1"
tradeConnectionId := "connection-2"

hostToRewardChannelId := "channel-100"
rewardToTradeChannelId := "channel-200"
tradeToHostChannelId := "channel-300"

rewardDenomOnHost := "ibc/reward-on-host"
rewardDenomOnReward := RewardDenom
rewardDenomOnTrade := "ibc/reward-on-trade"
hostDenomOnTrade := "ibc/host-on-trade"
hostDenomOnHost := HostDenom

withdrawalAddress := "withdrawal-address"
unwindAddress := "unwind-address"

poolId := uint64(100)
maxAllowedSwapLossRate := "0.05"
minSwapAmount := sdkmath.NewInt(100)
maxSwapAmount := sdkmath.NewInt(1_000)

// Mock out connections for the reward an trade chain so that an ICA registration can be submitted
s.MockClientAndConnection(rewardChainId, "07-tendermint-0", rewardConnectionId)
s.MockClientAndConnection(tradeChainId, "07-tendermint-1", tradeConnectionId)

// Register an exisiting ICA account for the unwind ICA to test that
// existing accounts are re-used
// TODO [DYDX]: Replace with trade route owner
owner := types.FormatICAAccountOwner(rewardChainId, types.ICAAccountType_CONVERTER_UNWIND)

Check failure on line 46 in x/stakeibc/keeper/msg_server_create_trade_route_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: types.FormatICAAccountOwner
s.MockICAChannel(rewardConnectionId, "channel-0", owner, unwindAddress)

// Create a host zone with an exisiting withdrawal address
hostZone := types.HostZone{
ChainId: HostChainId,
ConnectionId: hostConnectionId,
WithdrawalIcaAddress: withdrawalAddress,
}
s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone)

// Define a valid message given the parameters above
msg = types.MsgCreateTradeRoute{
Authority: Authority,
HostChainId: HostChainId,

StrideToRewardConnectionId: rewardConnectionId,
StrideToTradeConnectionId: tradeConnectionId,

HostToRewardTransferChannelId: hostToRewardChannelId,
RewardToTradeTransferChannelId: rewardToTradeChannelId,
TradeToHostTransferChannelId: tradeToHostChannelId,

RewardDenomOnHost: rewardDenomOnHost,
RewardDenomOnReward: rewardDenomOnReward,
RewardDenomOnTrade: rewardDenomOnTrade,
HostDenomOnTrade: hostDenomOnTrade,
HostDenomOnHost: hostDenomOnHost,

PoolId: poolId,
MaxAllowedSwapLossRate: maxAllowedSwapLossRate,
MinSwapAmount: minSwapAmount,
MaxSwapAmount: maxSwapAmount,
}

// Build out the expected trade route given the above
expectedTradeRoute = types.TradeRoute{
RewardDenomOnHostZone: rewardDenomOnHost,
RewardDenomOnRewardZone: rewardDenomOnReward,
RewardDenomOnTradeZone: rewardDenomOnTrade,
HostDenomOnTradeZone: hostDenomOnTrade,
HostDenomOnHostZone: hostDenomOnHost,

HostAccount: types.ICAAccount{
ChainId: HostChainId,
Type: types.ICAAccountType_WITHDRAWAL,
ConnectionId: hostConnectionId,
Address: withdrawalAddress,
},
RewardAccount: types.ICAAccount{
ChainId: rewardChainId,
Type: types.ICAAccountType_CONVERTER_UNWIND,
ConnectionId: rewardConnectionId,
Address: unwindAddress,
},
TradeAccount: types.ICAAccount{
ChainId: tradeChainId,
Type: types.ICAAccountType_CONVERTER_TRADE,
ConnectionId: tradeConnectionId,
},

HostToRewardChannelId: hostToRewardChannelId,
RewardToTradeChannelId: rewardToTradeChannelId,
TradeToHostChannelId: tradeToHostChannelId,

TradeConfig: types.TradeConfig{
PoolId: poolId,
SwapPrice: sdk.ZeroDec(),
PriceUpdateTimestamp: 0,

MaxAllowedSwapLossRate: sdk.MustNewDecFromStr(maxAllowedSwapLossRate),
MinSwapAmount: minSwapAmount,
MaxSwapAmount: maxSwapAmount,
},
}

return msg, expectedTradeRoute
}

// Helper function to create a trade route and check the created route matched expectations
func (s *KeeperTestSuite) submitCreateTradeRouteAndValidate(msg types.MsgCreateTradeRoute, expectedRoute types.TradeRoute) {
_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().NoError(err, "no error expected when creating trade route")

actualRoute, found := s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, msg.RewardDenomOnReward, msg.HostDenomOnHost)
s.Require().True(found, "trade route should have been created")
s.Require().Equal(expectedRoute, actualRoute, "trade route")
}

// Tests a successful trade route creation
func (s *KeeperTestSuite) TestCreateTradeRoute_Success() {
msg, expectedRoute := s.SetupTestCreateTradeRoute()
s.submitCreateTradeRouteAndValidate(msg, expectedRoute)
}

// Tests creating a trade route that uses the default pool config values
func (s *KeeperTestSuite) TestCreateTradeRoute_Success_DefaultPoolConfig() {
msg, expectedRoute := s.SetupTestCreateTradeRoute()

// Update the message and remove some trade config parameters
// so that the defaults are used
msg.MaxSwapAmount = sdk.ZeroInt()
msg.MaxAllowedSwapLossRate = ""

expectedRoute.TradeConfig.MaxAllowedSwapLossRate = sdk.MustNewDecFromStr(keeper.DefaultMaxAllowedSwapLossRate)
expectedRoute.TradeConfig.MaxSwapAmount = keeper.DefaultMaxSwapAmount

s.submitCreateTradeRouteAndValidate(msg, expectedRoute)
}

// Tests trying to create a route from an invalid authority
func (s *KeeperTestSuite) TestCreateTradeRoute_Failure_Authority() {
msg, _ := s.SetupTestCreateTradeRoute()

msg.Authority = "not-gov-address"

_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "invalid authority")
}

// Tests creating a duplicate trade route
func (s *KeeperTestSuite) TestCreateTradeRoute_Failure_DuplicateTradeRoute() {
msg, _ := s.SetupTestCreateTradeRoute()

// Store down a trade route so the tx hits a duplicate trade route error
s.App.StakeibcKeeper.SetTradeRoute(s.Ctx, types.TradeRoute{
RewardDenomOnRewardZone: RewardDenom,
HostDenomOnHostZone: HostDenom,
})

_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "Trade route already exists")
}

// Tests creating a trade route when the host zone or withdrawal address does not exist
func (s *KeeperTestSuite) TestCreateTradeRoute_Failure_HostZoneNotRegistered() {
msg, _ := s.SetupTestCreateTradeRoute()

// Remove the host zone withdrawal address and confirm it fails
invalidHostZone := s.MustGetHostZone(HostChainId)
invalidHostZone.WithdrawalIcaAddress = ""
s.App.StakeibcKeeper.SetHostZone(s.Ctx, invalidHostZone)

_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "withdrawal account not initialized on host zone")

// Remove the host zone completely and check that that also fails
s.App.StakeibcKeeper.RemoveHostZone(s.Ctx, HostChainId)

_, err = s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "host zone not found")
}

// Tests creating a trade route where the ICA channels cannot be created
// because the ICA connections do not exist
func (s *KeeperTestSuite) TestCreateTradeRoute_Failure_ConnectionNotFound() {
// Test with non-existent reward connection
msg, _ := s.SetupTestCreateTradeRoute()
msg.StrideToRewardConnectionId = "connection-X"

// Remove the host zone completely and check that that also fails
_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "unable to register the unwind ICA account: connection connection-X not found")

// Setup again, but this time use a non-existent trade connection
msg, _ = s.SetupTestCreateTradeRoute()
msg.StrideToTradeConnectionId = "connection-Y"

_, err = s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "unable to register the trade ICA account: connection connection-Y not found")
}

// Tests creating a trade route where the ICA registration step fails
func (s *KeeperTestSuite) TestCreateTradeRoute_Failure_UnableToRegisterICA() {
msg, expectedRoute := s.SetupTestCreateTradeRoute()

// Disable ICA middleware for the trade channel so the ICA fails
// TODO [DYDX]: Replace with new formatter
tradeAccount := expectedRoute.TradeAccount
tradeOwner := types.FormatICAAccountOwner(tradeAccount.ChainId, types.ICAAccountType_CONVERTER_TRADE)

Check failure on line 225 in x/stakeibc/keeper/msg_server_create_trade_route_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: types.FormatICAAccountOwner (typecheck)
tradePortId, _ := icatypes.NewControllerPortID(tradeOwner)
s.App.ICAControllerKeeper.SetMiddlewareDisabled(s.Ctx, tradePortId, tradeAccount.ConnectionId)

_, err := s.GetMsgServer().CreateTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "unable to register the trade ICA account")
}
44 changes: 44 additions & 0 deletions x/stakeibc/keeper/msg_server_delete_trade_route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package keeper_test

import (
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func (s *KeeperTestSuite) TestDeleteTradeRoute() {
initialRoute := types.TradeRoute{
RewardDenomOnRewardZone: RewardDenom,
HostDenomOnHostZone: HostDenom,
}
s.App.StakeibcKeeper.SetTradeRoute(s.Ctx, initialRoute)

msg := types.MsgDeleteTradeRoute{
Authority: Authority,
RewardDenom: RewardDenom,
HostDenom: HostDenom,
}

// Confirm the route is present before attepmting to delete was deleted
_, found := s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, RewardDenom, HostDenom)
s.Require().True(found, "trade route should have been found before delete message")

// Delete the trade route
_, err := s.GetMsgServer().DeleteTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().NoError(err, "no error expected when deleting trade route")

// Confirm it was deleted
_, found = s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, RewardDenom, HostDenom)
s.Require().False(found, "trade route should have been deleted")

// Attempt to delete it again, it should fail since it doesn't exist
_, err = s.GetMsgServer().DeleteTradeRoute(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().ErrorContains(err, "trade route not found")

// Attempt to delete with the wrong authority - it should fail
invalidMsg := msg
invalidMsg.Authority = "not-gov-address"

_, err = s.GetMsgServer().DeleteTradeRoute(sdk.WrapSDKContext(s.Ctx), &invalidMsg)
s.Require().ErrorContains(err, "invalid authority")
}
Loading

0 comments on commit 6704631

Please sign in to comment.