Skip to content

Commit

Permalink
reward converter - OnChanOpenAck unit tests (#1004)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs authored Dec 4, 2023
1 parent 74c2d15 commit 4fac08f
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 11 deletions.
14 changes: 14 additions & 0 deletions app/apptesting/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,20 @@ func (s *AppTestHelper) MockClientLatestHeight(height uint64) {
s.App.IBCKeeper.ClientKeeper.SetClientState(s.Ctx, FirstClientId, &clientState)
}

// Helper function to mock out a client and connection to test
// mapping from connection ID back to chain ID
func (s *AppTestHelper) MockClientAndConnection(chainId, clientId, connectionId string) {
clientState := tendermint.ClientState{
ChainId: chainId,
}
s.App.IBCKeeper.ClientKeeper.SetClientState(s.Ctx, clientId, &clientState)

connection := connectiontypes.ConnectionEnd{
ClientId: clientId,
}
s.App.IBCKeeper.ConnectionKeeper.SetConnection(s.Ctx, connectionId, connection)
}

func (s *AppTestHelper) ConfirmUpgradeSucceededs(upgradeName string, upgradeHeight int64) {
s.Ctx = s.Ctx.WithBlockHeight(upgradeHeight - 1)
plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight}
Expand Down
16 changes: 8 additions & 8 deletions x/stakeibc/keeper/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func (k Keeper) StoreHostZoneIcaAddress(ctx sdk.Context, chainId, portId, addres
}

// expected port IDs for each ICA account type
delegationOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_DELEGATION)
delegationPortID, err := icatypes.NewControllerPortID(delegationOwner)
if err != nil {
return err
}
withdrawalOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_WITHDRAWAL)
withdrawalPortID, err := icatypes.NewControllerPortID(withdrawalOwner)
if err != nil {
Expand All @@ -67,13 +72,8 @@ func (k Keeper) StoreHostZoneIcaAddress(ctx sdk.Context, chainId, portId, addres
if err != nil {
return err
}
delegationOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_DELEGATION)
delegationPortID, err := icatypes.NewControllerPortID(delegationOwner)
if err != nil {
return err
}
rewardOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_REDEMPTION)
redemptionPortID, err := icatypes.NewControllerPortID(rewardOwner)
redemptionOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_REDEMPTION)
redemptionPortID, err := icatypes.NewControllerPortID(redemptionOwner)
if err != nil {
return err
}
Expand Down Expand Up @@ -156,7 +156,7 @@ func (k Keeper) StoreHostZoneIcaAddress(ctx sdk.Context, chainId, portId, addres
// Checks if the port matches an ICA account on the trade route, and if so, stores the
// relevant ICA address on the trade route
func (k Keeper) StoreTradeRouteIcaAddress(ctx sdk.Context, chainId, portId, address string) error {
// Get the exepected port Id for each ICA account type (using the chainId)
// Get the expected port Id for each ICA account type (using the chainId)
tradeOwner := types.FormatICAAccountOwner(chainId, types.ICAAccountType_CONVERTER_TRADE)
tradePortID, err := icatypes.NewControllerPortID(tradeOwner)
if err != nil {
Expand Down
291 changes: 291 additions & 0 deletions x/stakeibc/keeper/ibc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package keeper_test

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

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

// ------------------------------------------
// OnChanOpenAck
// ------------------------------------------

func (s *KeeperTestSuite) TestOnChanOpenAck() {
// Define the mocked out ids for both the delegation and trade accounts
delegationChainId := "delegation-1"
delegationAddress := "delegation-address"
delegationConnectionId := "connection-0"
delegationChannelId := "channel-0"
delegationClientId := "07-tendermint-0"

tradeChainId := "trade-1"
tradeAddress := "trade-address"
tradeConnectionId := "connection-1"
tradeChannelId := "channel-1"
tradeClientId := "07-tendermint-1"

// Create a host zone with out any ICA addresses
s.App.StakeibcKeeper.SetHostZone(s.Ctx, types.HostZone{
ChainId: delegationChainId,
})

// Create a trade route without any ICA addresses
s.App.StakeibcKeeper.SetTradeRoute(s.Ctx, types.TradeRoute{
RewardDenomOnRewardZone: RewardDenom,
HostDenomOnHostZone: HostDenom,
TradeAccount: types.ICAAccount{
ChainId: tradeChainId,
},
})

// Create the ICA channels for both the delegation and trade accounts
delegationOwner := types.FormatICAAccountOwner(delegationChainId, types.ICAAccountType_DELEGATION)
delegationPortId, _ := icatypes.NewControllerPortID(delegationOwner)

tradeOwner := types.FormatICAAccountOwner(tradeChainId, types.ICAAccountType_CONVERTER_TRADE)
tradePortId, _ := icatypes.NewControllerPortID(tradeOwner)

// Mock out an ICA address for each
s.App.ICAControllerKeeper.SetInterchainAccountAddress(s.Ctx, delegationConnectionId, delegationPortId, delegationAddress)
s.App.ICAControllerKeeper.SetInterchainAccountAddress(s.Ctx, tradeConnectionId, tradePortId, tradeAddress)

// Mock out a client and connection for each channel so the callback can map back from portId to chainId
s.MockClientAndConnection(delegationChainId, delegationClientId, delegationConnectionId)
s.MockClientAndConnection(tradeChainId, tradeClientId, tradeConnectionId)

// Call the callback with the delegation ICA port and confirm the delegation address is set
err := s.App.StakeibcKeeper.OnChanOpenAck(s.Ctx, delegationPortId, delegationChannelId)
s.Require().NoError(err, "no error expected when running callback with delegation port")

hostZone := s.MustGetHostZone(delegationChainId)
s.Require().Equal(delegationAddress, hostZone.DelegationIcaAddress, "delegation address")

// Call the callback with the trade ICA port and confirm the trade address is set
err = s.App.StakeibcKeeper.OnChanOpenAck(s.Ctx, tradePortId, tradeChannelId)
s.Require().NoError(err, "no error expected when running callback with trade port")

tradeRoute, found := s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, RewardDenom, HostDenom)
s.Require().True(found, "trade route should have been round")
s.Require().Equal(tradeAddress, tradeRoute.TradeAccount.Address, "trade address")

// Call the callback with a non-ICA port and confirm the host zone and trade route remained unchanged
err = s.App.StakeibcKeeper.OnChanOpenAck(s.Ctx, tradePortId, tradeChannelId)
s.Require().NoError(err, "no error expected when running callback with non-ICA port")

finalHostZone := s.MustGetHostZone(delegationChainId)
s.Require().Equal(hostZone, finalHostZone, "host zone should not have been modified")

finalTradeRoute, found := s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, RewardDenom, HostDenom)
s.Require().True(found, "trade route should have been round")
s.Require().Equal(tradeRoute, finalTradeRoute, "trade route should not have been modified")
}

// ------------------------------------------
// StoreHostZoneIcaAddress
// ------------------------------------------

// Helper function to check that a single ICA address was stored on the host zone
// The address stored will match the string of the ICA account type
func (s *KeeperTestSuite) checkHostZoneAddressStored(accountType types.ICAAccountType) {
// Determine the expected ICA addresses based on whether the account in question
// is registered in this test case
delegationAddress := ""
if accountType == types.ICAAccountType_DELEGATION {
delegationAddress = accountType.String()
}
withdrawalAddress := ""
if accountType == types.ICAAccountType_WITHDRAWAL {
withdrawalAddress = accountType.String()
}
redemptionAddress := ""
if accountType == types.ICAAccountType_REDEMPTION {
redemptionAddress = accountType.String()
}
feeAddress := ""
if accountType == types.ICAAccountType_FEE {
feeAddress = accountType.String()
}
communityPoolDepositAddress := ""
if accountType == types.ICAAccountType_COMMUNITY_POOL_DEPOSIT {
communityPoolDepositAddress = accountType.String()
}
communityPoolReturnAddress := ""
if accountType == types.ICAAccountType_COMMUNITY_POOL_RETURN {
communityPoolReturnAddress = accountType.String()
}

// Confirm the expected addresses with the host zone
hostZone := s.MustGetHostZone(HostChainId)

s.Require().Equal(delegationAddress, hostZone.DelegationIcaAddress, "delegation address")
s.Require().Equal(withdrawalAddress, hostZone.WithdrawalIcaAddress, "withdrawal address")
s.Require().Equal(redemptionAddress, hostZone.RedemptionIcaAddress, "redemption address")
s.Require().Equal(feeAddress, hostZone.FeeIcaAddress, "fee address")
s.Require().Equal(communityPoolDepositAddress, hostZone.CommunityPoolDepositIcaAddress, "community pool deposit address")
s.Require().Equal(communityPoolReturnAddress, hostZone.CommunityPoolReturnIcaAddress, "commuity pool return address")
}

// Helper function to check that relevant ICA addresses are whitelisted after the callback
func (s *KeeperTestSuite) checkAddressesWhitelisted(accountType types.ICAAccountType) {
if accountType == types.ICAAccountType_DELEGATION {
isWhitelisted := s.App.RatelimitKeeper.IsAddressPairWhitelisted(s.Ctx, DepositAddress, accountType.String())
s.Require().True(isWhitelisted, "deposit -> delegation whitelist")
}

if accountType == types.ICAAccountType_FEE {
sender := accountType.String()
receiver := s.App.AccountKeeper.GetModuleAccount(s.Ctx, types.RewardCollectorName).GetAddress().String()

isWhitelisted := s.App.RatelimitKeeper.IsAddressPairWhitelisted(s.Ctx, sender, receiver)
s.Require().True(isWhitelisted, "fee -> reward collector whitelist")
}

if accountType == types.ICAAccountType_COMMUNITY_POOL_DEPOSIT {
sender := accountType.String()

receiver := CommunityPoolStakeHoldingAddress
isWhitelisted := s.App.RatelimitKeeper.IsAddressPairWhitelisted(s.Ctx, sender, receiver)
s.Require().True(isWhitelisted, "community pool deposit -> community pool stake holding")

receiver = CommunityPoolRedeemHoldingAddress
isWhitelisted = s.App.RatelimitKeeper.IsAddressPairWhitelisted(s.Ctx, sender, receiver)
s.Require().True(isWhitelisted, "community pool deposit -> community pool redeem holding")
}

if accountType == types.ICAAccountType_COMMUNITY_POOL_RETURN {
sender := CommunityPoolStakeHoldingAddress
receiver := accountType.String()

isWhitelisted := s.App.RatelimitKeeper.IsAddressPairWhitelisted(s.Ctx, sender, receiver)
s.Require().True(isWhitelisted, "community pool stake holding -> community pool return")
}
}

func (s *KeeperTestSuite) TestStoreHostZoneIcaAddress() {
// We'll run a test case for each ICA account, with two of them not being relevant for the host zone
icaAccountTypes := []types.ICAAccountType{
types.ICAAccountType_DELEGATION,
types.ICAAccountType_WITHDRAWAL,
types.ICAAccountType_REDEMPTION,
types.ICAAccountType_FEE,
types.ICAAccountType_COMMUNITY_POOL_DEPOSIT,
types.ICAAccountType_COMMUNITY_POOL_RETURN,

types.ICAAccountType_CONVERTER_TRADE, // not on the host zone
-1, // indicates test case for non-ICA port
}

for _, accountType := range icaAccountTypes {
// Reset the host zone for each test and wipe all addresses
s.App.StakeibcKeeper.SetHostZone(s.Ctx, types.HostZone{
ChainId: HostChainId,
DepositAddress: DepositAddress,
CommunityPoolStakeHoldingAddress: CommunityPoolStakeHoldingAddress,
CommunityPoolRedeemHoldingAddress: CommunityPoolRedeemHoldingAddress,
})

// Determine the port Id from the account type
// If the portId is -1, pass a non-ica port
portId := "not-ica-port"
if accountType != -1 {
owner := types.FormatICAAccountOwner(HostChainId, accountType)
portId, _ = icatypes.NewControllerPortID(owner)
}

// Call StoreHostZoneIcaAddress with the portId
// use the account name as the address to make the matching easier
address := accountType.String()
err := s.App.StakeibcKeeper.StoreHostZoneIcaAddress(s.Ctx, HostChainId, portId, address)
s.Require().NoError(err, "no error expected when calling store host zone ICA for %s", accountType.String())

// Check if the updated addresses matches expectations
s.checkHostZoneAddressStored(accountType)

// Check that the relevant accounts are white listed from the rate limiter
s.checkAddressesWhitelisted(accountType)
}
}

// ------------------------------------------
// StoreTradeRouteIcaAddress
// ------------------------------------------

// Helper function to check that a single ICA address was stored on the trade route
// The address stored will match the string of the ICA account type
func (s *KeeperTestSuite) checkTradeRouteAddressStored(accountType types.ICAAccountType) {
// Determine the expected ICA addresses based on whether the account in question
// is registered in this test case
unwindAddress := ""
if accountType == types.ICAAccountType_CONVERTER_UNWIND {
unwindAddress = types.ICAAccountType_CONVERTER_UNWIND.String()
}
tradeAddress := ""
if accountType == types.ICAAccountType_CONVERTER_TRADE {
tradeAddress = types.ICAAccountType_CONVERTER_TRADE.String()
}

// Confirm the expected addresses with the host zone
tradeRoute, found := s.App.StakeibcKeeper.GetTradeRoute(s.Ctx, RewardDenom, HostDenom)
s.Require().True(found, "trade route should have been found")

s.Require().Equal(unwindAddress, tradeRoute.RewardAccount.Address, "unwind address")
s.Require().Equal(tradeAddress, tradeRoute.TradeAccount.Address, "trade address")
}

func (s *KeeperTestSuite) TestStoreTradeRouteIcaAddress() {
// We'll run a test case for each the two ICA accounts, and 2 test cases for ports not on the trade route
icaAccountTypes := []types.ICAAccountType{
types.ICAAccountType_CONVERTER_UNWIND,
types.ICAAccountType_CONVERTER_TRADE,

types.ICAAccountType_DELEGATION, // not on the trade route
-1, // indicates test case for non-ICA port
}

emptyTradeRoute := types.TradeRoute{
RewardDenomOnRewardZone: RewardDenom,
HostDenomOnHostZone: HostDenom,
RewardAccount: types.ICAAccount{
ChainId: HostChainId,
},
TradeAccount: types.ICAAccount{
ChainId: HostChainId,
},
}

for _, accountType := range icaAccountTypes {
// Reset the trade route for each test and wipe all addresses
s.App.StakeibcKeeper.SetTradeRoute(s.Ctx, emptyTradeRoute)

// Determine the port Id from the account type
// If the portId is -1, pass a non-ica port
portId := "not-ica-port"
if accountType != -1 {
owner := types.FormatICAAccountOwner(HostChainId, accountType)
portId, _ = icatypes.NewControllerPortID(owner)
}

// Call StoreTradeRouteIcaAddress with the portId
// use the account name as the address to make the matching easier
address := accountType.String()
err := s.App.StakeibcKeeper.StoreTradeRouteIcaAddress(s.Ctx, HostChainId, portId, address)
s.Require().NoError(err, "no error expected when calling store trade route ICA for %s", accountType.String())

// Check if the updated addresses matches expectations
s.checkTradeRouteAddressStored(accountType)
}

// Check with a matching port, but no matching chainId
accountType := types.ICAAccountType_CONVERTER_TRADE
owner := types.FormatICAAccountOwner(HostChainId, accountType)
portId, _ := icatypes.NewControllerPortID(owner)
address := accountType.String()

emptyTradeRoute.TradeAccount.ChainId = "different-chain-id"
s.App.StakeibcKeeper.SetTradeRoute(s.Ctx, emptyTradeRoute)

err := s.App.StakeibcKeeper.StoreTradeRouteIcaAddress(s.Ctx, HostChainId, portId, address)
s.Require().NoError(err, "no error expected when calling store trade route ICA for trade ICA with no chainId")

s.checkTradeRouteAddressStored(-1) // checks no matches
}
20 changes: 17 additions & 3 deletions x/stakeibc/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ const (
OsmoPrefix = "osmo"
OsmoChainId = "OSMO"

ValAddress = "cosmosvaloper1uk4ze0x4nvh4fk0xm4jdud58eqn4yxhrdt795p"
HostICAAddress = "cosmos1gcx4yeplccq9nk6awzmm0gq8jf7yet80qj70tkwy0mz7pg87nepswn2dj8"
LSMTokenBaseDenom = ValAddress + "/32"
HostDenom = "udenom"
RewardDenom = "ureward"

ValAddress = "cosmosvaloper1uk4ze0x4nvh4fk0xm4jdud58eqn4yxhrdt795p"
HostICAAddress = "cosmos1gcx4yeplccq9nk6awzmm0gq8jf7yet80qj70tkwy0mz7pg87nepswn2dj8"
LSMTokenBaseDenom = ValAddress + "/32"

DepositAddress = "deposit"
CommunityPoolStakeHoldingAddress = "staking-holding"
CommunityPoolRedeemHoldingAddress = "redeem-holding"
)

type KeeperTestSuite struct {
Expand All @@ -51,6 +58,13 @@ func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

// Helper function to get a host zone and confirm it was found
func (s *KeeperTestSuite) MustGetHostZone(chainId string) types.HostZone {
hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, chainId)
s.Require().True(found, "host zone should have been found")
return hostZone
}

func (s *KeeperTestSuite) TestIsRedemptionRateWithinSafetyBounds() {
params := s.App.StakeibcKeeper.GetParams(s.Ctx)
params.DefaultMinRedemptionRateThreshold = 75
Expand Down

0 comments on commit 4fac08f

Please sign in to comment.