Skip to content

Commit 4dc2e77

Browse files
mconcatgjermundgarabaAdityaSripalDeshErBojhaa
authored
feat: rate limit module (#8268)
Co-authored-by: Gjermund Garaba <[email protected]> Co-authored-by: mconcat <[email protected]> Co-authored-by: Aditya <[email protected]> Co-authored-by: tamjidahmed <[email protected]>
1 parent 2d98792 commit 4dc2e77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+13582
-108
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
//go:build !test_e2e
2+
3+
package ratelimiting
4+
5+
import (
6+
"context"
7+
"testing"
8+
9+
interchaintest "github.com/cosmos/interchaintest/v10"
10+
ibc "github.com/cosmos/interchaintest/v10/ibc"
11+
testutil "github.com/cosmos/interchaintest/v10/testutil"
12+
testifysuite "github.com/stretchr/testify/suite"
13+
14+
sdkmath "cosmossdk.io/math"
15+
16+
sdk "github.com/cosmos/cosmos-sdk/types"
17+
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
18+
19+
"github.com/cosmos/ibc-go/e2e/testsuite"
20+
"github.com/cosmos/ibc-go/e2e/testsuite/query"
21+
"github.com/cosmos/ibc-go/e2e/testvalues"
22+
ratelimitingtypes "github.com/cosmos/ibc-go/v10/modules/apps/rate-limiting/types"
23+
transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
24+
ibctesting "github.com/cosmos/ibc-go/v10/testing"
25+
)
26+
27+
type RateLimTestSuite struct {
28+
testsuite.E2ETestSuite
29+
}
30+
31+
func TestRateLimitSuite(t *testing.T) {
32+
testifysuite.Run(t, new(RateLimTestSuite))
33+
}
34+
35+
func (s *RateLimTestSuite) SetupSuite() {
36+
s.SetupChains(context.TODO(), 2, nil, func(options *testsuite.ChainOptions) {
37+
options.RelayerCount = 1
38+
})
39+
}
40+
41+
func (s *RateLimTestSuite) TestRateLimit() {
42+
t := s.T()
43+
ctx := context.TODO()
44+
testName := t.Name()
45+
46+
chainA, chainB := s.GetChains()
47+
48+
userA := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
49+
userB := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
50+
51+
authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA)
52+
s.Require().NoError(err)
53+
s.Require().NotNil(authority)
54+
55+
s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName)
56+
relayer := s.GetRelayerForTest(testName)
57+
s.StartRelayer(relayer, testName)
58+
59+
chanAB := s.GetChannelBetweenChains(testName, chainA, chainB)
60+
61+
escrowAddrA := transfertypes.GetEscrowAddress(chanAB.PortID, chanAB.ChannelID)
62+
denomA := chainA.Config().Denom
63+
64+
ibcTokenB := testsuite.GetIBCToken(denomA, chanAB.PortID, chanAB.ChannelID)
65+
66+
t.Run("No rate limit set: transfer succeeds", func(_ *testing.T) {
67+
userABalBefore, err := s.GetChainANativeBalance(ctx, userA)
68+
s.Require().NoError(err)
69+
userBBalBefore, err := query.Balance(ctx, chainB, userB.FormattedAddress(), ibcTokenB.IBCDenom())
70+
s.Require().NoError(err)
71+
s.Require().Zero(userBBalBefore.Int64())
72+
73+
txResp := s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, testvalues.DefaultTransferAmount(denomA), userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
74+
s.AssertTxSuccess(txResp)
75+
76+
packet, err := ibctesting.ParseV1PacketFromEvents(txResp.Events)
77+
s.Require().NoError(err)
78+
s.Require().NotNil(packet)
79+
80+
s.Require().NoError(testutil.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks")
81+
s.AssertPacketRelayed(ctx, chainA, chanAB.PortID, chanAB.ChannelID, packet.Sequence)
82+
83+
userABalAfter, err := s.GetChainANativeBalance(ctx, userA)
84+
s.Require().NoError(err)
85+
86+
// Balanced moved form userA to userB
87+
s.Require().Equal(userABalBefore-testvalues.IBCTransferAmount, userABalAfter)
88+
escrowBalA, err := query.Balance(ctx, chainA, escrowAddrA.String(), denomA)
89+
s.Require().NoError(err)
90+
s.Require().Equal(testvalues.IBCTransferAmount, escrowBalA.Int64())
91+
92+
userBBalAfter, err := query.Balance(ctx, chainB, userB.FormattedAddress(), ibcTokenB.IBCDenom())
93+
s.Require().NoError(err)
94+
s.Require().Equal(testvalues.IBCTransferAmount, userBBalAfter.Int64())
95+
})
96+
97+
t.Run("Add outgoing rate limit on ChainA", func(_ *testing.T) {
98+
resp, err := query.GRPCQuery[ratelimitingtypes.QueryAllRateLimitsResponse](ctx, chainA, &ratelimitingtypes.QueryAllRateLimitsRequest{})
99+
s.Require().NoError(err)
100+
s.Require().Nil(resp.RateLimits)
101+
102+
sendPercentage := int64(10)
103+
recvPercentage := int64(0)
104+
s.addRateLimit(ctx, chainA, userA, denomA, chanAB.ChannelID, authority.String(), sendPercentage, recvPercentage, 1)
105+
106+
resp, err = query.GRPCQuery[ratelimitingtypes.QueryAllRateLimitsResponse](ctx, chainA, &ratelimitingtypes.QueryAllRateLimitsRequest{})
107+
s.Require().NoError(err)
108+
s.Require().Len(resp.RateLimits, 1)
109+
110+
rateLimit := resp.RateLimits[0]
111+
s.Require().Equal(rateLimit.Flow.Outflow.Int64(), int64(0))
112+
s.Require().Equal(rateLimit.Flow.Inflow.Int64(), int64(0))
113+
s.Require().Equal(rateLimit.Quota.MaxPercentSend.Int64(), sendPercentage)
114+
s.Require().Equal(rateLimit.Quota.MaxPercentRecv.Int64(), recvPercentage)
115+
s.Require().Equal(rateLimit.Quota.DurationHours, uint64(1))
116+
})
117+
118+
t.Run("Transfer updates the rate limit flow", func(_ *testing.T) {
119+
userABalBefore, err := s.GetChainANativeBalance(ctx, userA)
120+
s.Require().NoError(err)
121+
122+
txResp := s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, testvalues.DefaultTransferAmount(denomA), userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
123+
s.AssertTxSuccess(txResp)
124+
125+
packet, err := ibctesting.ParseV1PacketFromEvents(txResp.Events)
126+
s.Require().NoError(err)
127+
s.Require().NotNil(packet)
128+
129+
s.Require().NoError(testutil.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks")
130+
s.AssertPacketRelayed(ctx, chainA, chanAB.PortID, chanAB.ChannelID, packet.Sequence)
131+
132+
userABalAfter, err := s.GetChainANativeBalance(ctx, userA)
133+
s.Require().NoError(err)
134+
135+
// Balanced moved form userA to userB
136+
s.Require().Equal(userABalBefore-testvalues.IBCTransferAmount, userABalAfter)
137+
userBBalAfter, err := query.Balance(ctx, chainB, userB.FormattedAddress(), ibcTokenB.IBCDenom())
138+
s.Require().NoError(err)
139+
s.Require().Equal(2*testvalues.IBCTransferAmount, userBBalAfter.Int64())
140+
141+
// Check the flow has been updated.
142+
rateLimit := s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
143+
s.Require().NotNil(rateLimit)
144+
s.Require().Equal(rateLimit.Flow.Outflow.Int64(), testvalues.IBCTransferAmount)
145+
})
146+
147+
t.Run("Fill and exceed quota", func(_ *testing.T) {
148+
rateLim := s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
149+
sendPercentage := rateLim.Quota.MaxPercentSend.Int64()
150+
151+
// Create an account that can almost exhause the outflow limit.
152+
richKidAmt := rateLim.Flow.ChannelValue.MulRaw(sendPercentage).QuoRaw(100).Sub(rateLim.Flow.Outflow)
153+
richKid := interchaintest.GetAndFundTestUsers(t, ctx, "richkid", richKidAmt, chainA)[0]
154+
s.Require().NoError(testutil.WaitForBlocks(ctx, 4, chainA))
155+
156+
sendCoin := sdk.NewCoin(denomA, richKidAmt)
157+
158+
// Fill the quota
159+
txResp := s.Transfer(ctx, chainA, richKid, chanAB.PortID, chanAB.ChannelID, sendCoin, richKid.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
160+
s.AssertTxSuccess(txResp)
161+
162+
// Sending even 10denomA fails due to exceeding the quota
163+
sendCoin = sdk.NewInt64Coin(denomA, 10)
164+
txResp = s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, sendCoin, userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
165+
s.AssertTxFailure(txResp, ratelimitingtypes.ErrQuotaExceeded)
166+
})
167+
168+
t.Run("Reset rate limit: transfer succeeds", func(_ *testing.T) {
169+
rateLimit := s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
170+
sendPercentage := rateLimit.Quota.MaxPercentSend.Int64()
171+
recvPercentage := rateLimit.Quota.MaxPercentRecv.Int64()
172+
173+
s.resetRateLimit(ctx, chainA, userA, denomA, chanAB.ChannelID, authority.String())
174+
175+
rateLimit = s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
176+
// Resetting only clears the flow. It does not change the quota
177+
s.Require().Zero(rateLimit.Flow.Outflow.Int64())
178+
s.Require().Equal(rateLimit.Quota.MaxPercentSend.Int64(), sendPercentage)
179+
s.Require().Equal(rateLimit.Quota.MaxPercentRecv.Int64(), recvPercentage)
180+
181+
txResp := s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, testvalues.DefaultTransferAmount(denomA), userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
182+
s.AssertTxSuccess(txResp)
183+
})
184+
185+
t.Run("Set outflow quota to 0: transfer fails", func(_ *testing.T) {
186+
sendPercentage := int64(0)
187+
recvPercentage := int64(1)
188+
s.updateRateLimit(ctx, chainA, userA, denomA, chanAB.ChannelID, authority.String(), sendPercentage, recvPercentage)
189+
190+
rateLimit := s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
191+
s.Require().Equal(rateLimit.Quota.MaxPercentSend.Int64(), sendPercentage)
192+
s.Require().Equal(rateLimit.Quota.MaxPercentRecv.Int64(), recvPercentage)
193+
194+
txResp := s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, testvalues.DefaultTransferAmount(denomA), userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
195+
s.AssertTxFailure(txResp, ratelimitingtypes.ErrQuotaExceeded)
196+
})
197+
198+
t.Run("Remove rate limit -> transfer succeeds again", func(_ *testing.T) {
199+
s.removeRateLimit(ctx, chainA, userA, denomA, chanAB.ChannelID, authority.String())
200+
201+
rateLimit := s.rateLimit(ctx, chainA, denomA, chanAB.ChannelID)
202+
s.Require().Nil(rateLimit)
203+
204+
// Transfer works again
205+
txResp := s.Transfer(ctx, chainA, userA, chanAB.PortID, chanAB.ChannelID, testvalues.DefaultTransferAmount(denomA), userA.FormattedAddress(), userB.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "")
206+
s.AssertTxSuccess(txResp)
207+
})
208+
}
209+
210+
func (s *RateLimTestSuite) rateLimit(ctx context.Context, chain ibc.Chain, denom, chanID string) *ratelimitingtypes.RateLimit {
211+
respRateLim, err := query.GRPCQuery[ratelimitingtypes.QueryRateLimitResponse](ctx, chain, &ratelimitingtypes.QueryRateLimitRequest{
212+
Denom: denom,
213+
ChannelOrClientId: chanID,
214+
})
215+
s.Require().NoError(err)
216+
return respRateLim.RateLimit
217+
}
218+
219+
func (s *RateLimTestSuite) addRateLimit(ctx context.Context, chain ibc.Chain, user ibc.Wallet, denom, chanID, authority string, sendPercent, recvPercent, duration int64) {
220+
msg := &ratelimitingtypes.MsgAddRateLimit{
221+
Signer: authority,
222+
Denom: denom,
223+
ChannelOrClientId: chanID,
224+
MaxPercentSend: sdkmath.NewInt(sendPercent),
225+
MaxPercentRecv: sdkmath.NewInt(recvPercent),
226+
DurationHours: uint64(duration),
227+
}
228+
s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, user)
229+
}
230+
231+
func (s *RateLimTestSuite) resetRateLimit(ctx context.Context, chain ibc.Chain, user ibc.Wallet, denom, chanID, authority string) {
232+
msg := &ratelimitingtypes.MsgResetRateLimit{
233+
Signer: authority,
234+
Denom: denom,
235+
ChannelOrClientId: chanID,
236+
}
237+
s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, user)
238+
}
239+
240+
func (s *RateLimTestSuite) updateRateLimit(ctx context.Context, chain ibc.Chain, user ibc.Wallet, denom, chanID, authority string, sendPercent, recvPercent int64) {
241+
msg := &ratelimitingtypes.MsgUpdateRateLimit{
242+
Signer: authority,
243+
Denom: denom,
244+
ChannelOrClientId: chanID,
245+
MaxPercentSend: sdkmath.NewInt(sendPercent),
246+
MaxPercentRecv: sdkmath.NewInt(recvPercent),
247+
DurationHours: 1,
248+
}
249+
s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, user)
250+
}
251+
252+
func (s *RateLimTestSuite) removeRateLimit(ctx context.Context, chain ibc.Chain, user ibc.Wallet, denom, chanID, authority string) {
253+
msg := &ratelimitingtypes.MsgRemoveRateLimit{
254+
Signer: authority,
255+
Denom: denom,
256+
ChannelOrClientId: chanID,
257+
}
258+
s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, user)
259+
}

e2e/testsuite/codec.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types"
2828
icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types"
2929
packetforwardtypes "github.com/cosmos/ibc-go/v10/modules/apps/packet-forward-middleware/types"
30+
ratelimitingtypes "github.com/cosmos/ibc-go/v10/modules/apps/rate-limiting/types"
3031
transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
3132
v7migrations "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7"
3233
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
@@ -73,6 +74,7 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, testutil.TestEncodingConfig) {
7374
wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry)
7475
channeltypesv2.RegisterInterfaces(cfg.InterfaceRegistry)
7576
packetforwardtypes.RegisterInterfaces(cfg.InterfaceRegistry)
77+
ratelimitingtypes.RegisterInterfaces(cfg.InterfaceRegistry)
7678

7779
// all other types
7880
upgradetypes.RegisterInterfaces(cfg.InterfaceRegistry)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ require (
4242
cloud.google.com/go/iam v1.2.2 // indirect
4343
cloud.google.com/go/monitoring v1.21.2 // indirect
4444
cloud.google.com/go/storage v1.49.0 // indirect
45-
cosmossdk.io/collections v1.3.0 // indirect
45+
cosmossdk.io/collections v1.3.1 // indirect
4646
cosmossdk.io/depinject v1.2.1 // indirect
4747
cosmossdk.io/schema v1.1.0 // indirect
4848
filippo.io/edwards25519 v1.1.0 // indirect
@@ -95,7 +95,7 @@ require (
9595
github.com/fatih/color v1.17.0 // indirect
9696
github.com/felixge/httpsnoop v1.0.4 // indirect
9797
github.com/fsnotify/fsnotify v1.9.0 // indirect
98-
github.com/getsentry/sentry-go v0.32.0 // indirect
98+
github.com/getsentry/sentry-go v0.33.0 // indirect
9999
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
100100
github.com/go-kit/kit v0.13.0 // indirect
101101
github.com/go-kit/log v0.2.1 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,8 @@ cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT
616616
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
617617
cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg=
618618
cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU=
619-
cosmossdk.io/collections v1.3.0 h1:RUY23xXBy/bu5oSHZ5y+mkJRyA4ZboKDO4Yvx4+g2uc=
620-
cosmossdk.io/collections v1.3.0/go.mod h1:cqVpBMDGEYhuNmNSXIOmqpnQ7Eav43hpJIetzLuEkns=
619+
cosmossdk.io/collections v1.3.1 h1:09e+DUId2brWsNOQ4nrk+bprVmMUaDH9xvtZkeqIjVw=
620+
cosmossdk.io/collections v1.3.1/go.mod h1:ynvkP0r5ruAjbmedE+vQ07MT6OtJ0ZIDKrtJHK7Q/4c=
621621
cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo=
622622
cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y=
623623
cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw=
@@ -909,8 +909,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
909909
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
910910
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
911911
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
912-
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
913-
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
912+
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
913+
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
914914
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
915915
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
916916
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=

modules/apps/packet-forward-middleware/keeper/keeper.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ func (k *Keeper) SetTransferKeeper(transferKeeper types.TransferKeeper) {
7474
k.transferKeeper = transferKeeper
7575
}
7676

77+
// SetICS4Wrapper sets the ICS4 wrapper.
78+
func (k *Keeper) SetICS4Wrapper(ics4Wrapper porttypes.ICS4Wrapper) {
79+
k.ics4Wrapper = ics4Wrapper
80+
}
81+
82+
// ICS4Wrapper gets the ICS4 Wrapper for PFM.
83+
func (k *Keeper) ICS4Wrapper() porttypes.ICS4Wrapper {
84+
return k.ics4Wrapper
85+
}
86+
7787
// Logger returns a module-specific logger.
7888
func (*Keeper) Logger(ctx sdk.Context) log.Logger {
7989
return ctx.Logger().With("module", "x/"+ibcexported.ModuleName+"-"+types.ModuleName)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cli
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/cosmos/cosmos-sdk/client"
7+
)
8+
9+
// GetQueryCmd returns the cli query commands for this module.
10+
func GetQueryCmd() *cobra.Command {
11+
cmd := &cobra.Command{
12+
Use: "ratelimiting",
13+
Short: "IBC ratelimiting querying subcommands",
14+
DisableFlagParsing: true,
15+
SuggestionsMinimumDistance: 2,
16+
RunE: client.ValidateCmd,
17+
}
18+
19+
cmd.AddCommand(
20+
GetCmdQueryRateLimit(),
21+
GetCmdQueryAllRateLimits(),
22+
GetCmdQueryRateLimitsByChainID(),
23+
GetCmdQueryAllBlacklistedDenoms(),
24+
GetCmdQueryAllWhitelistedAddresses(),
25+
)
26+
return cmd
27+
}

0 commit comments

Comments
 (0)