Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reward converter - Create/Update/Delete TradeRoute unit tests #1015

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion app/apptesting/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,18 +446,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) {
ethan-stride marked this conversation as resolved.
Show resolved Hide resolved
// 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,13 +7,16 @@ 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"
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"
)

const (
var (
Atom = "uatom"
StAtom = "stuatom"
IbcAtom = "ibc/uatom"
Expand All @@ -37,6 +40,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)
sampocs marked this conversation as resolved.
Show resolved Hide resolved
}

// 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)
s.MockICAChannel(rewardConnectionId, "channel-0", owner, unwindAddress)

// Create a host zone with an exisiting withdrawal address
sampocs marked this conversation as resolved.
Show resolved Hide resolved
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, RewardDenom, HostDenom)
sampocs marked this conversation as resolved.
Show resolved Hide resolved
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() {
sampocs marked this conversation as resolved.
Show resolved Hide resolved
// 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)
ethan-stride marked this conversation as resolved.
Show resolved Hide resolved
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