diff --git a/app/app.go b/app/app.go index 7cf6a9175..98ee5b3d4 100644 --- a/app/app.go +++ b/app/app.go @@ -10,13 +10,15 @@ import ( "path/filepath" "time" - "github.com/neutron-org/neutron/v5/x/dynamicfees" dynamicfeestypes "github.com/neutron-org/neutron/v5/x/dynamicfees/types" "github.com/skip-mev/feemarket/x/feemarket" feemarketkeeper "github.com/skip-mev/feemarket/x/feemarket/keeper" feemarkettypes "github.com/skip-mev/feemarket/x/feemarket/types" + "github.com/neutron-org/neutron/v5/x/dynamicfees" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit" + "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" @@ -131,6 +133,8 @@ import ( ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck ibcconnectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + ibcratelimitkeeper "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/keeper" + ibcratelimittypes "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" //nolint:staticcheck ibcporttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" ibchost "github.com/cosmos/ibc-go/v8/modules/core/exported" @@ -268,6 +272,7 @@ var ( ), ibchooks.AppModuleBasic{}, packetforward.AppModuleBasic{}, + ibcratelimit.AppModuleBasic{}, auction.AppModuleBasic{}, globalfee.AppModule{}, feemarket.AppModuleBasic{}, @@ -368,8 +373,10 @@ type App struct { PFMModule packetforward.AppModule - HooksTransferIBCModule *ibchooks.IBCMiddleware - HooksICS4Wrapper ibchooks.ICS4Middleware + TransferStack *ibchooks.IBCMiddleware + Ics20WasmHooks *ibchooks.WasmHooks + RateLimitingICS4Wrapper *ibcratelimit.ICS4Wrapper + HooksICS4Wrapper ibchooks.ICS4Middleware // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -384,7 +391,8 @@ type App struct { ConsensusParamsKeeper consensusparamkeeper.Keeper - WasmKeeper wasmkeeper.Keeper + WasmKeeper wasmkeeper.Keeper + ContractKeeper *wasmkeeper.PermissionedKeeper // slinky MarketMapKeeper *marketmapkeeper.Keeper @@ -405,7 +413,7 @@ type App struct { checkTxHandler checktx.CheckTx } -// AutoCLIOpts returns options based upon the modules in the neutron v4 app. +// AutoCLIOpts returns options based upon the modules in the neutron v5 app. func (app *App) AutoCLIOpts(initClientCtx client.Context) autocli.AppOptions { modules := make(map[string]appmodule.AppModule) for _, m := range app.mm.Modules { @@ -475,7 +483,7 @@ func New( icahosttypes.StoreKey, capabilitytypes.StoreKey, interchainqueriesmoduletypes.StoreKey, contractmanagermoduletypes.StoreKey, interchaintxstypes.StoreKey, wasmtypes.StoreKey, feetypes.StoreKey, feeburnertypes.StoreKey, adminmoduletypes.StoreKey, ccvconsumertypes.StoreKey, tokenfactorytypes.StoreKey, pfmtypes.StoreKey, - crontypes.StoreKey, ibchookstypes.StoreKey, consensusparamtypes.StoreKey, crisistypes.StoreKey, dextypes.StoreKey, auctiontypes.StoreKey, + crontypes.StoreKey, ibcratelimittypes.ModuleName, ibchookstypes.StoreKey, consensusparamtypes.StoreKey, crisistypes.StoreKey, dextypes.StoreKey, auctiontypes.StoreKey, oracletypes.StoreKey, marketmaptypes.StoreKey, feemarkettypes.StoreKey, dynamicfeestypes.StoreKey, globalfeetypes.StoreKey, ) tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey, dextypes.TStoreKey) @@ -507,6 +515,7 @@ func New( scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + app.ScopedTransferKeeper = scopedTransferKeeper scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasmtypes.ModuleName) scopedInterTxKeeper := app.CapabilityKeeper.ScopeToModule(interchaintxstypes.ModuleName) scopedCCVConsumerKeeper := app.CapabilityKeeper.ScopeToModule(ccvconsumertypes.ModuleName) @@ -588,31 +597,7 @@ func New( appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), &app.ConsumerKeeper, app.UpgradeKeeper, scopedIBCKeeper, authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) - app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( - appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), - app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 feerefunder - app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, - scopedICAControllerKeeper, app.MsgServiceRouter(), - authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), - ) - - app.ICAHostKeeper = icahostkeeper.NewKeeper( - appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), - app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 feerefunder - app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, - app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), - authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), - ) - app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) - - app.ContractManagerKeeper = *contractmanagermodulekeeper.NewKeeper( - appCodec, - keys[contractmanagermoduletypes.StoreKey], - keys[contractmanagermoduletypes.MemStoreKey], - &app.WasmKeeper, - authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), - ) - + // Feekeeper needs to be initialized before middlewares injection app.FeeKeeper = feekeeper.NewKeeper( appCodec, keys[feetypes.StoreKey], @@ -623,55 +608,45 @@ func New( ) feeModule := feerefunder.NewAppModule(appCodec, *app.FeeKeeper, app.AccountKeeper, app.BankKeeper) - app.FeeBurnerKeeper = feeburnerkeeper.NewKeeper( + app.ContractManagerKeeper = *contractmanagermodulekeeper.NewKeeper( appCodec, - keys[feeburnertypes.StoreKey], - keys[feeburnertypes.MemStoreKey], - app.AccountKeeper, - &app.BankKeeper, + keys[contractmanagermoduletypes.StoreKey], + keys[contractmanagermoduletypes.MemStoreKey], + &app.WasmKeeper, authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) - feeBurnerModule := feeburner.NewAppModule(appCodec, *app.FeeBurnerKeeper) - app.GlobalFeeKeeper = globalfeekeeper.NewKeeper(appCodec, keys[globalfeetypes.StoreKey], authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String()) + app.WireICS20PreWasmKeeper(appCodec) + app.PFMModule = packetforward.NewAppModule(app.PFMKeeper, app.GetSubspace(pfmtypes.ModuleName)) - // PFMKeeper must be created before TransferKeeper - app.PFMKeeper = pfmkeeper.NewKeeper( - appCodec, - app.keys[pfmtypes.StoreKey], - app.TransferKeeper.Keeper, - app.IBCKeeper.ChannelKeeper, - app.FeeBurnerKeeper, - &app.BankKeeper, + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) - wasmHooks := ibchooks.NewWasmHooks(nil, sdk.GetConfig().GetBech32AccountAddrPrefix()) // The contract keeper needs to be set later - app.HooksICS4Wrapper = ibchooks.NewICS4Middleware( + + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), app.IBCKeeper.ChannelKeeper, - app.PFMKeeper, - &wasmHooks, + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), + authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) + app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) - // Create Transfer Keepers - app.TransferKeeper = wrapkeeper.NewKeeper( + app.FeeBurnerKeeper = feeburnerkeeper.NewKeeper( appCodec, - keys[ibctransfertypes.StoreKey], - app.GetSubspace(ibctransfertypes.ModuleName), - app.HooksICS4Wrapper, // essentially still app.IBCKeeper.ChannelKeeper under the hood because no hook overrides - app.IBCKeeper.ChannelKeeper, - app.IBCKeeper.PortKeeper, + keys[feeburnertypes.StoreKey], + keys[feeburnertypes.MemStoreKey], app.AccountKeeper, &app.BankKeeper, - scopedTransferKeeper, - app.FeeKeeper, - contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), ) + feeBurnerModule := feeburner.NewAppModule(appCodec, *app.FeeBurnerKeeper) - app.PFMKeeper.SetTransferKeeper(app.TransferKeeper.Keeper) - - transferModule := transferSudo.NewAppModule(app.TransferKeeper) + app.GlobalFeeKeeper = globalfeekeeper.NewKeeper(appCodec, keys[globalfeetypes.StoreKey], authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String()) // Create evidence Keeper for to register the IBC light client misbehaviour evidence route evidenceKeeper := evidencekeeper.NewKeeper( @@ -846,7 +821,7 @@ func New( &app.BankKeeper, nil, nil, - app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 feerefunder + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, scopedWasmKeeper, @@ -859,19 +834,10 @@ func New( authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), wasmOpts..., ) - wasmHooks.ContractKeeper = &app.WasmKeeper app.CronKeeper.WasmMsgServer = wasmkeeper.NewMsgServerImpl(&app.WasmKeeper) cronModule := cron.NewAppModule(appCodec, app.CronKeeper) - transferIBCModule := transferSudo.NewIBCModule( - app.TransferKeeper, - contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), - ) - // receive call order: wasmHooks#OnRecvPacketOverride(transferIbcModule#OnRecvPacket()) - ibcHooksMiddleware := ibchooks.NewIBCMiddleware(&transferIBCModule, &app.HooksICS4Wrapper) - app.HooksTransferIBCModule = &ibcHooksMiddleware - // Create static IBC router, add transfer route, then set and seal it ibcRouter := ibcporttypes.NewRouter() @@ -893,23 +859,18 @@ func New( ) interchainTxsModule := interchaintxs.NewAppModule(appCodec, app.InterchainTxsKeeper, app.AccountKeeper, app.BankKeeper) contractManagerModule := contractmanager.NewAppModule(appCodec, app.ContractManagerKeeper) + ibcRateLimitmodule := ibcratelimit.NewAppModule(appCodec, app.RateLimitingICS4Wrapper.IbcratelimitKeeper, app.RateLimitingICS4Wrapper) ibcHooksModule := ibchooks.NewAppModule(app.AccountKeeper) - app.PFMModule = packetforward.NewAppModule(app.PFMKeeper, app.GetSubspace(pfmtypes.ModuleName)) - - var ibcStack ibcporttypes.IBCModule = packetforward.NewIBCMiddleware( - app.HooksTransferIBCModule, - app.PFMKeeper, - 0, - pfmkeeper.DefaultForwardTransferPacketTimeoutTimestamp, - pfmkeeper.DefaultRefundTransferPacketTimeoutTimestamp, - ) + transferModule := transferSudo.NewAppModule(app.TransferKeeper) + app.ContractKeeper = wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper) - ibcStack = gmpmiddleware.NewIBCMiddleware(ibcStack) + app.RateLimitingICS4Wrapper.ContractKeeper = app.ContractKeeper + app.Ics20WasmHooks.ContractKeeper = &app.WasmKeeper ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). - AddRoute(ibctransfertypes.ModuleName, ibcStack). + AddRoute(ibctransfertypes.ModuleName, app.TransferStack). AddRoute(interchaintxstypes.ModuleName, icaControllerStack). AddRoute(wasmtypes.ModuleName, wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper)). AddRoute(ccvconsumertypes.ModuleName, consumerModule) @@ -949,6 +910,7 @@ func New( feeBurnerModule, contractManagerModule, adminModule, + ibcRateLimitmodule, ibcHooksModule, tokenfactory.NewAppModule(appCodec, *app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), cronModule, @@ -997,6 +959,7 @@ func New( feetypes.ModuleName, feeburnertypes.ModuleName, adminmoduletypes.ModuleName, + ibcratelimittypes.ModuleName, ibchookstypes.ModuleName, pfmtypes.ModuleName, crontypes.ModuleName, @@ -1033,6 +996,7 @@ func New( feetypes.ModuleName, feeburnertypes.ModuleName, adminmoduletypes.ModuleName, + ibcratelimittypes.ModuleName, ibchookstypes.ModuleName, pfmtypes.ModuleName, crontypes.ModuleName, @@ -1074,6 +1038,7 @@ func New( feetypes.ModuleName, feeburnertypes.ModuleName, adminmoduletypes.ModuleName, + ibcratelimittypes.ModuleName, ibchookstypes.ModuleName, // after auth keeper pfmtypes.ModuleName, crontypes.ModuleName, @@ -1108,6 +1073,7 @@ func New( ibc.NewAppModule(app.IBCKeeper), params.NewAppModule(app.ParamsKeeper), transferModule, + ibcRateLimitmodule, consumerModule, icaModule, app.PFMModule, @@ -1397,6 +1363,7 @@ func (app *App) setupUpgradeHandlers() { FeeMarketKeeper: app.FeeMarkerKeeper, DynamicfeesKeeper: app.DynamicFeesKeeper, DexKeeper: &app.DexKeeper, + IbcRateLimitKeeper: app.RateLimitingICS4Wrapper.IbcratelimitKeeper, GlobalFeeSubspace: app.GetSubspace(globalfee.ModuleName), CcvConsumerSubspace: app.GetSubspace(ccvconsumertypes.ModuleName), }, @@ -1674,3 +1641,90 @@ func overrideWasmVariables() { wasmtypes.MaxWasmSize = 1_677_722 // ~1.6 mb (1024 * 1024 * 1.6) wasmtypes.MaxProposalWasmSize = wasmtypes.MaxWasmSize } + +// WireICS20PreWasmKeeper Create the IBC Transfer Stack from bottom to top: +// * SendPacket. Originates from the transferKeeper and goes up the stack: +// transferKeeper.SendPacket -> ibc_rate_limit.SendPacket -> ibc_hooks.SendPacket -> channel.SendPacket +// * RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> ibc_hooks.OnRecvPacket -> ibc_rate_limit.OnRecvPacket -> gmp.OnRecvPacket -> pfm.OnRecvPacket -> transfer.OnRecvPacket +// +// Note that the forward middleware is only integrated on the "receive" direction. It can be safely skipped when sending. +// Note also that the forward middleware is called "router", but we are using the name "pfm" (packet forward middleware) for clarity +// This may later be renamed upstream: https://github.com/ibc-apps/middleware/packet-forward-middleware/issues/10 +// +// After this, the wasm keeper is required to be set on both +// app.Ics20WasmHooks AND app.RateLimitingICS4Wrapper +func (app *App) WireICS20PreWasmKeeper( + appCodec codec.Codec, +) { + // PFMKeeper must be created before TransferKeeper + app.PFMKeeper = pfmkeeper.NewKeeper( + appCodec, + app.keys[pfmtypes.StoreKey], + app.TransferKeeper.Keeper, // set later + app.IBCKeeper.ChannelKeeper, + app.FeeBurnerKeeper, + &app.BankKeeper, + app.IBCKeeper.ChannelKeeper, + authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), + ) + + wasmHooks := ibchooks.NewWasmHooks(nil, sdk.GetConfig().GetBech32AccountAddrPrefix()) // The contract keeper needs to be set later + app.Ics20WasmHooks = &wasmHooks + app.HooksICS4Wrapper = ibchooks.NewICS4Middleware( + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &wasmHooks, + ) + + ibcratelimitKeeper := ibcratelimitkeeper.NewKeeper(appCodec, app.keys[ibcratelimittypes.ModuleName], authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String()) + // ChannelKeeper wrapper for rate limiting SendPacket(). The wasmKeeper needs to be added after it's created + rateLimitingICS4Wrapper := ibcratelimit.NewICS4Middleware( + app.HooksICS4Wrapper, + &app.AccountKeeper, + // wasm keeper we set later, right after wasmkeeper init. line 868 + nil, + &app.BankKeeper, + &ibcratelimitKeeper, + ) + app.RateLimitingICS4Wrapper = &rateLimitingICS4Wrapper + + // Create Transfer Keepers + app.TransferKeeper = wrapkeeper.NewKeeper( + appCodec, + app.keys[ibctransfertypes.StoreKey], + app.GetSubspace(ibctransfertypes.ModuleName), + app.RateLimitingICS4Wrapper, + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + app.AccountKeeper, + &app.BankKeeper, + app.ScopedTransferKeeper, + app.FeeKeeper, + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), + authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), + ) + + app.PFMKeeper.SetTransferKeeper(app.TransferKeeper.Keeper) + + // Packet Forward Middleware + // Initialize packet forward middleware router + var ibcStack ibcporttypes.IBCModule = packetforward.NewIBCMiddleware( + transferSudo.NewIBCModule( + app.TransferKeeper, + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), + ), + app.PFMKeeper, + 0, + pfmkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + pfmkeeper.DefaultRefundTransferPacketTimeoutTimestamp, + ) + + ibcStack = gmpmiddleware.NewIBCMiddleware(ibcStack) + // RateLimiting IBC Middleware + rateLimitingTransferModule := ibcratelimit.NewIBCModule(ibcStack, app.RateLimitingICS4Wrapper) + + // Hooks Middleware + hooksTransferModule := ibchooks.NewIBCMiddleware(&rateLimitingTransferModule, &app.HooksICS4Wrapper) + app.TransferStack = &hooksTransferModule +} diff --git a/app/proposals_allowlisting.go b/app/proposals_allowlisting.go index 19402c0a8..e68cd1b46 100644 --- a/app/proposals_allowlisting.go +++ b/app/proposals_allowlisting.go @@ -16,6 +16,7 @@ import ( ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck ccvconsumertypes "github.com/cosmos/interchain-security/v5/x/ccv/consumer/types" + ibcratelimittypes "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" feemarkettypes "github.com/skip-mev/feemarket/x/feemarket/types" marketmaptypes "github.com/skip-mev/slinky/x/marketmap/types" @@ -93,7 +94,8 @@ func isSdkMessageWhitelisted(msg sdk.Msg) bool { *feemarkettypes.MsgParams, *dynamicfeestypes.MsgUpdateParams, *ibctransfertypes.MsgUpdateParams, - *globalfeetypes.MsgUpdateParams: + *globalfeetypes.MsgUpdateParams, + *ibcratelimittypes.MsgUpdateParams: return true } return false diff --git a/app/upgrades/types.go b/app/upgrades/types.go index 7e1450fb2..6a313828f 100644 --- a/app/upgrades/types.go +++ b/app/upgrades/types.go @@ -13,12 +13,12 @@ import ( slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" ccvconsumerkeeper "github.com/cosmos/interchain-security/v5/x/ccv/consumer/keeper" + dexkeeper "github.com/neutron-org/neutron/v5/x/dex/keeper" + ibcratelimitkeeper "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/keeper" auctionkeeper "github.com/skip-mev/block-sdk/v2/x/auction/keeper" feemarketkeeper "github.com/skip-mev/feemarket/x/feemarket/keeper" marketmapkeeper "github.com/skip-mev/slinky/x/marketmap/keeper" - dexkeeper "github.com/neutron-org/neutron/v5/x/dex/keeper" - dynamicfeeskeeper "github.com/neutron-org/neutron/v5/x/dynamicfees/keeper" contractmanagerkeeper "github.com/neutron-org/neutron/v5/x/contractmanager/keeper" @@ -67,6 +67,7 @@ type UpgradeKeepers struct { FeeMarketKeeper *feemarketkeeper.Keeper DynamicfeesKeeper *dynamicfeeskeeper.Keeper DexKeeper *dexkeeper.Keeper + IbcRateLimitKeeper *ibcratelimitkeeper.Keeper // subspaces GlobalFeeSubspace paramtypes.Subspace CcvConsumerSubspace paramtypes.Subspace diff --git a/app/upgrades/v5.0.0/constants.go b/app/upgrades/v5.0.0/constants.go index 043690996..bed22a0a6 100644 --- a/app/upgrades/v5.0.0/constants.go +++ b/app/upgrades/v5.0.0/constants.go @@ -4,6 +4,7 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/neutron-org/neutron/v5/app/upgrades" + ibcratelimittypes "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" ) const ( @@ -11,12 +12,15 @@ const ( UpgradeName = "v5.0.0" MarketMapAuthorityMultisig = "neutron1anjpluecd0tdc0n8xzc3l5hua4h93wyq0x7v56" + // RateLimitContract defines the RL contract addr which we set as a contract address in ibc-rate-limit middleware + // https://neutron.celat.one/neutron-1/contracts/neutron15aqgplxcavqhurr0g5wwtdw6025dknkqwkfh0n46gp2qjl6236cs2yd3nl + RateLimitContract = "neutron15aqgplxcavqhurr0g5wwtdw6025dknkqwkfh0n46gp2qjl6236cs2yd3nl" ) var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, StoreUpgrades: storetypes.StoreUpgrades{ - Added: []string{}, + Added: []string{ibcratelimittypes.ModuleName}, }, } diff --git a/app/upgrades/v5.0.0/upgrades.go b/app/upgrades/v5.0.0/upgrades.go index fb63577ff..5bdc9c1b3 100644 --- a/app/upgrades/v5.0.0/upgrades.go +++ b/app/upgrades/v5.0.0/upgrades.go @@ -15,6 +15,8 @@ import ( "github.com/neutron-org/neutron/v5/app/upgrades" dexkeeper "github.com/neutron-org/neutron/v5/x/dex/keeper" + ibcratelimitkeeper "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/keeper" + ibcratelimittypes "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" ) func CreateUpgradeHandler( @@ -47,6 +49,15 @@ func CreateUpgradeHandler( return nil, err } + ctx.Logger().Info("Running ibc-rate-limit upgrades...") + // Only set rate limit contract for mainnet + if ctx.ChainID() == "neutron-1" { + err = upgradeIbcRateLimitSetContract(ctx, *keepers.IbcRateLimitKeeper) + if err != nil { + return nil, err + } + } + ctx.Logger().Info(fmt.Sprintf("Migration {%s} applied", UpgradeName)) return vm, nil } @@ -68,6 +79,19 @@ func upgradeDexPause(ctx sdk.Context, k dexkeeper.Keeper) error { return nil } +func upgradeIbcRateLimitSetContract(ctx sdk.Context, k ibcratelimitkeeper.Keeper) error { + // Set the dex to paused + ctx.Logger().Info("Setting ibc rate limiting contract...") + + if err := k.SetParams(ctx, ibcratelimittypes.Params{ContractAddress: RateLimitContract}); err != nil { + return err + } + + ctx.Logger().Info("Rate limit contract is set") + + return nil +} + func setMarketMapParams(ctx sdk.Context, marketmapKeeper *marketmapkeeper.Keeper) error { marketmapParams := marketmaptypes.Params{ MarketAuthorities: []string{authtypes.NewModuleAddress(adminmoduletypes.ModuleName).String(), MarketMapAuthorityMultisig}, diff --git a/app/upgrades/v5.0.0/upgrades_test.go b/app/upgrades/v5.0.0/upgrades_test.go index 77fd076a8..67196ffee 100644 --- a/app/upgrades/v5.0.0/upgrades_test.go +++ b/app/upgrades/v5.0.0/upgrades_test.go @@ -83,3 +83,25 @@ func (suite *UpgradeTestSuite) TestUpgradeDexPause() { suite.ErrorIs(err, dextypes.ErrDexPaused) } + +func (suite *UpgradeTestSuite) TestUpgradeSetRateLimitContract() { + var ( + app = suite.GetNeutronZoneApp(suite.ChainA) + ctx = suite.ChainA.GetContext().WithChainID("neutron-1") + ) + + params := app.RateLimitingICS4Wrapper.IbcratelimitKeeper.GetParams(ctx) + + suite.Equal(params.ContractAddress, "") + + upgrade := upgradetypes.Plan{ + Name: v500.UpgradeName, + Info: "some text here", + Height: 100, + } + suite.NoError(app.UpgradeKeeper.ApplyUpgrade(ctx, upgrade)) + + params = app.RateLimitingICS4Wrapper.IbcratelimitKeeper.GetParams(ctx) + + suite.Equal(params.ContractAddress, v500.RateLimitContract) +} diff --git a/proto/neutron/ibcratelimit/v1beta1/genesis.proto b/proto/neutron/ibcratelimit/v1beta1/genesis.proto new file mode 100644 index 000000000..f66c0fcd4 --- /dev/null +++ b/proto/neutron/ibcratelimit/v1beta1/genesis.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package neutron.ibcratelimit.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "neutron/ibcratelimit/v1beta1/params.proto"; + +option go_package = "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types"; + +// GenesisState defines the ibc-rate-limit module's genesis state. +message GenesisState { + // params are all the parameters of the module + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/neutron/ibcratelimit/v1beta1/params.proto b/proto/neutron/ibcratelimit/v1beta1/params.proto new file mode 100644 index 000000000..8dee35a10 --- /dev/null +++ b/proto/neutron/ibcratelimit/v1beta1/params.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package neutron.ibcratelimit.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types"; + +// Params defines the parameters for the ibc-rate-limit module. +message Params { + string contract_address = 1 [ + (gogoproto.moretags) = "yaml:\"contract_address\"", + (gogoproto.nullable) = true + ]; +} diff --git a/proto/neutron/ibcratelimit/v1beta1/query.proto b/proto/neutron/ibcratelimit/v1beta1/query.proto new file mode 100644 index 000000000..79b560ff3 --- /dev/null +++ b/proto/neutron/ibcratelimit/v1beta1/query.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package neutron.ibcratelimit.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "neutron/ibcratelimit/v1beta1/params.proto"; + +option go_package = "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types"; + +// Query defines the gRPC querier service. +service Query { + // Params defines a gRPC query method that returns the ibc-rate-limit module's + // parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/neutron/ibc-rate-limit/v1beta1/params"; + } +} + +// ParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// aramsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/neutron/ibcratelimit/v1beta1/tx.proto b/proto/neutron/ibcratelimit/v1beta1/tx.proto new file mode 100644 index 000000000..71ff52e9c --- /dev/null +++ b/proto/neutron/ibcratelimit/v1beta1/tx.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +package neutron.ibcratelimit.v1beta1; + +import "amino/amino.proto"; +import "cosmos/bank/v1beta1/bank.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/msg/v1/msg.proto"; +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "neutron/ibcratelimit/v1beta1/params.proto"; + +option go_package = "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types"; + +// Msg defines the tokefactory module's gRPC message service. +service Msg { + option (cosmos.msg.v1.service) = true; + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgUpdateParams is the MsgUpdateParams request type. +// +// Since: 0.47 +message MsgUpdateParams { + option (amino.name) = "neutron/ibc-rate-limit/MsgUpdateParams"; + option (cosmos.msg.v1.signer) = "authority"; + + // Authority is the address of the governance account. + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the x/tokenfactory parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [ + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +// +// Since: 0.47 +message MsgUpdateParamsResponse {} diff --git a/testutil/test_helpers.go b/testutil/test_helpers.go index 051e87e95..de7cb00ba 100644 --- a/testutil/test_helpers.go +++ b/testutil/test_helpers.go @@ -4,14 +4,20 @@ import ( "bytes" "encoding/json" "fmt" + "math/rand" "os" "path" "testing" "time" tmrand "github.com/cometbft/cometbft/libs/rand" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" icacontrollerkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/keeper" icacontrollertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types" + "github.com/stretchr/testify/require" "github.com/neutron-org/neutron/v5/utils" @@ -69,6 +75,13 @@ var ( })) ) +const ( + One = 1 + Two = 2 + Three = 3 + Four = 4 +) + func init() { // ibctesting.DefaultTestingAppInit = SetupTestingApp() config.GetDefaultConfig() @@ -85,15 +98,19 @@ type IBCConnectionTestSuite struct { ChainProvider *ibctesting.TestChain ChainA *ibctesting.TestChain ChainB *ibctesting.TestChain + ChainC *ibctesting.TestChain ProviderApp e2e.ProviderApp ChainAApp e2e.ConsumerApp ChainBApp e2e.ConsumerApp - - CCVPathA *ibctesting.Path - CCVPathB *ibctesting.Path - Path *ibctesting.Path - TransferPath *ibctesting.Path + ChainCApp e2e.ConsumerApp + + CCVPathA *ibctesting.Path + CCVPathB *ibctesting.Path + CCVPathC *ibctesting.Path + Path *ibctesting.Path + TransferPath *ibctesting.Path + TransferPathAC *ibctesting.Path } func GetTestConsumerAdditionProp(chain *ibctesting.TestChain) *providertypes.ConsumerAdditionProposal { //nolint:staticcheck @@ -130,18 +147,22 @@ func (suite *IBCConnectionTestSuite) SetupTest() { suite.ChainProvider = suite.Coordinator.GetChain(ibctesting.GetChainID(1)) suite.ChainA = suite.Coordinator.GetChain(ibctesting.GetChainID(2)) suite.ChainB = suite.Coordinator.GetChain(ibctesting.GetChainID(3)) + suite.ChainC = suite.Coordinator.GetChain(ibctesting.GetChainID(4)) suite.ProviderApp = suite.ChainProvider.App.(*appProvider.App) suite.ChainAApp = suite.ChainA.App.(*app.App) suite.ChainBApp = suite.ChainB.App.(*app.App) + suite.ChainCApp = suite.ChainC.App.(*app.App) providerKeeper := suite.ProviderApp.GetProviderKeeper() consumerKeeperA := suite.ChainAApp.GetConsumerKeeper() consumerKeeperB := suite.ChainBApp.GetConsumerKeeper() + consumerKeeperC := suite.ChainCApp.GetConsumerKeeper() // valsets must match providerValUpdates := cmttypes.TM2PB.ValidatorUpdates(suite.ChainProvider.Vals) consumerAValUpdates := cmttypes.TM2PB.ValidatorUpdates(suite.ChainA.Vals) consumerBValUpdates := cmttypes.TM2PB.ValidatorUpdates(suite.ChainB.Vals) + consumerCValUpdates := cmttypes.TM2PB.ValidatorUpdates(suite.ChainB.Vals) suite.Require().True(len(providerValUpdates) == len(consumerAValUpdates), "initial valset not matching") suite.Require().True(len(providerValUpdates) == len(consumerBValUpdates), "initial valset not matching") @@ -149,8 +170,10 @@ func (suite *IBCConnectionTestSuite) SetupTest() { addr1, _ := ccv.TMCryptoPublicKeyToConsAddr(providerValUpdates[i].PubKey) addr2, _ := ccv.TMCryptoPublicKeyToConsAddr(consumerAValUpdates[i].PubKey) addr3, _ := ccv.TMCryptoPublicKeyToConsAddr(consumerBValUpdates[i].PubKey) + addr4, _ := ccv.TMCryptoPublicKeyToConsAddr(consumerCValUpdates[i].PubKey) suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") suite.Require().True(bytes.Equal(addr1, addr3), "validator mismatch") + suite.Require().True(bytes.Equal(addr1, addr4), "validator mismatch") } ct := suite.ChainProvider.GetContext() @@ -158,6 +181,7 @@ func (suite *IBCConnectionTestSuite) SetupTest() { suite.ChainProvider.NextBlock() suite.ChainA.NextBlock() suite.ChainB.NextBlock() + suite.ChainC.NextBlock() // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. prop1 := GetTestConsumerAdditionProp(suite.ChainA) @@ -174,6 +198,13 @@ func (suite *IBCConnectionTestSuite) SetupTest() { ) suite.Require().NoError(err) + prop3 := GetTestConsumerAdditionProp(suite.ChainC) + err = providerKeeper.CreateConsumerClient( + ct, + prop3, + ) + suite.Require().NoError(err) + // move provider to next block to commit the state suite.ChainProvider.NextBlock() @@ -205,11 +236,27 @@ func (suite *IBCConnectionTestSuite) SetupTest() { } consumerKeeperB.InitGenesis(suite.ChainB.GetContext(), &genesisStateB) + // initialize the consumer chain with the genesis state stored on the provider + consumerGenesisC, found := providerKeeper.GetConsumerGenesis( + suite.ChainProvider.GetContext(), + suite.ChainC.ChainID, + ) + suite.Require().True(found, "consumer genesis not found") + + genesisStateC := consumertypes.GenesisState{ + Params: consumerGenesisC.Params, + Provider: consumerGenesisC.Provider, + NewChain: consumerGenesisC.NewChain, + } + consumerKeeperC.InitGenesis(suite.ChainC.GetContext(), &genesisStateC) + // create paths for the CCV channel suite.CCVPathA = ibctesting.NewPath(suite.ChainA, suite.ChainProvider) suite.CCVPathB = ibctesting.NewPath(suite.ChainB, suite.ChainProvider) + suite.CCVPathC = ibctesting.NewPath(suite.ChainC, suite.ChainProvider) SetupCCVPath(suite.CCVPathA, suite) SetupCCVPath(suite.CCVPathB, suite) + SetupCCVPath(suite.CCVPathC, suite) suite.SetupCCVChannels() @@ -225,6 +272,13 @@ func (suite *IBCConnectionTestSuite) ConfigureTransferChannel() { suite.Require().NoError(err) } +func (suite *IBCConnectionTestSuite) ConfigureTransferChannelAC() { + suite.TransferPathAC = NewTransferPath(suite.ChainA, suite.ChainC, suite.ChainProvider) + suite.Coordinator.SetupConnections(suite.TransferPathAC) + err := SetupTransferPath(suite.TransferPathAC) + suite.Require().NoError(err) +} + func (suite *IBCConnectionTestSuite) FundAcc(acc sdk.AccAddress, amounts sdk.Coins) { bankKeeper := suite.GetNeutronZoneApp(suite.ChainA).BankKeeper err := bankKeeper.MintCoins(suite.ChainA.GetContext(), tokenfactorytypes.ModuleName, amounts) @@ -300,7 +354,7 @@ func testHomeDir(chainID string) string { // NewCoordinator initializes Coordinator with interchain security dummy provider and 2 neutron consumer chains func NewProviderConsumerCoordinator(t *testing.T) *ibctesting.Coordinator { coordinator := ibctesting.NewCoordinator(t, 0) - chainID := ibctesting.GetChainID(1) + chainID := ibctesting.GetChainID(One) ibctesting.DefaultTestingAppInit = icssimapp.ProviderAppIniter coordinator.Chains[chainID] = ibctesting.NewTestChain(t, coordinator, chainID) @@ -308,12 +362,16 @@ func NewProviderConsumerCoordinator(t *testing.T) *ibctesting.Coordinator { _ = config.GetDefaultConfig() sdk.SetAddrCacheEnabled(false) - chainID = ibctesting.GetChainID(2) + chainID = ibctesting.GetChainID(Two) ibctesting.DefaultTestingAppInit = SetupTestingApp(cmttypes.TM2PB.ValidatorUpdates(providerChain.Vals)) coordinator.Chains[chainID] = ibctesting.NewTestChainWithValSet(t, coordinator, chainID, providerChain.Vals, providerChain.Signers) - chainID = ibctesting.GetChainID(3) + chainID = ibctesting.GetChainID(Three) + coordinator.Chains[chainID] = ibctesting.NewTestChainWithValSet(t, coordinator, + chainID, providerChain.Vals, providerChain.Signers) + + chainID = ibctesting.GetChainID(Four) coordinator.Chains[chainID] = ibctesting.NewTestChainWithValSet(t, coordinator, chainID, providerChain.Vals, providerChain.Signers) @@ -520,3 +578,114 @@ func SetupTransferPath(path *ibctesting.Path) error { return path.EndpointB.ChanOpenConfirm() } + +// SendMsgsNoCheck is an alternative to ibctesting.TestChain.SendMsgs so that it doesn't check for errors. That should be handled by the caller +func (suite *IBCConnectionTestSuite) SendMsgsNoCheck(chain *ibctesting.TestChain, msgs ...sdk.Msg) (*cometbfttypes.ExecTxResult, error) { + // ensure the suite has the latest time + suite.Coordinator.UpdateTimeForChain(chain) + + // increment acc sequence regardless of success or failure tx execution + defer func() { + err := chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + if err != nil { + panic(err) + } + }() + + resp, err := SignAndDeliver(chain.TB, chain.TxConfig, chain.App.GetBaseApp(), msgs, chain.ChainID, []uint64{chain.SenderAccount.GetAccountNumber()}, []uint64{chain.SenderAccount.GetSequence()}, chain.CurrentHeader.GetTime(), chain.NextVals.Hash(), chain.SenderPrivKey) + if err != nil { + return nil, err + } + + suite.commitBlock(resp, chain) + + suite.Coordinator.IncrementTime() + + require.Len(chain.TB, resp.TxResults, 1) + txResult := resp.TxResults[0] + + if txResult.Code != 0 { + return txResult, fmt.Errorf("%s/%d: %q", txResult.Codespace, txResult.Code, txResult.Log) + } + + suite.Coordinator.IncrementTime() + + return txResult, nil +} + +// SignAndDeliver signs and delivers a transaction without asserting the results. This overrides the function +// from ibctesting +func SignAndDeliver( + tb testing.TB, + txCfg client.TxConfig, + app *baseapp.BaseApp, + msgs []sdk.Msg, + chainID string, + accNums, accSeqs []uint64, + blockTime time.Time, + nextValHash []byte, + priv ...cryptotypes.PrivKey, +) (res *cometbfttypes.ResponseFinalizeBlock, err error) { + tb.Helper() + tx, err := sims.GenSignedMockTx( + // #nosec G404 - math/rand is acceptable for non-cryptographic purposes + rand.New(rand.NewSource(time.Now().UnixNano())), + txCfg, + msgs, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + sims.DefaultGenTxGas, + chainID, + accNums, + accSeqs, + priv..., + ) + if err != nil { + return nil, err + } + + txBytes, err := txCfg.TxEncoder()(tx) + if err != nil { + return nil, err + } + + return app.FinalizeBlock(&cometbfttypes.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Time: blockTime, + NextValidatorsHash: nextValHash, + Txs: [][]byte{txBytes}, + }) +} + +func (suite *IBCConnectionTestSuite) ExecuteContract(contract, sender sdk.AccAddress, msg []byte, funds sdk.Coins) ([]byte, error) { + app := suite.GetNeutronZoneApp(suite.ChainA) + contractKeeper := keeper.NewDefaultPermissionKeeper(app.WasmKeeper) + return contractKeeper.Execute(suite.ChainA.GetContext(), contract, sender, msg, funds) +} + +func (suite *IBCConnectionTestSuite) commitBlock(res *cometbfttypes.ResponseFinalizeBlock, chain *ibctesting.TestChain) { + _, err := chain.App.Commit() + require.NoError(chain.TB, err) + + // set the last header to the current header + // use nil trusted fields + chain.LastHeader = chain.CurrentTMClientHeader() + + // val set changes returned from previous block get applied to the next validators + // of this block. See tendermint spec for details. + chain.Vals = chain.NextVals + + chain.NextVals = ibctesting.ApplyValSetChanges(chain, chain.Vals, res.ValidatorUpdates) + + // increment the current header + chain.CurrentHeader = cmtproto.Header{ + ChainID: chain.ChainID, + Height: chain.App.LastBlockHeight() + 1, + AppHash: chain.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.NextVals.Hash(), + ProposerAddress: chain.CurrentHeader.ProposerAddress, + } +} diff --git a/x/ibc-hooks/bytecode/rate_limiter.wasm b/x/ibc-hooks/bytecode/rate_limiter.wasm new file mode 100644 index 000000000..7627ff34c Binary files /dev/null and b/x/ibc-hooks/bytecode/rate_limiter.wasm differ diff --git a/x/ibc-hooks/ibc_middleware_test.go b/x/ibc-hooks/ibc_middleware_test.go index ea7b4a25b..7deb43ce7 100644 --- a/x/ibc-hooks/ibc_middleware_test.go +++ b/x/ibc-hooks/ibc_middleware_test.go @@ -47,11 +47,11 @@ func (suite *HooksTestSuite) TestOnRecvPacketHooks() { expPass bool }{ {"override", func(status *testutils.Status) { - suite.GetNeutronZoneApp(suite.ChainB).HooksTransferIBCModule. + suite.GetNeutronZoneApp(suite.ChainB).TransferStack. ICS4Middleware.Hooks = testutils.TestRecvOverrideHooks{Status: status} }, true}, {"before and after", func(status *testutils.Status) { - suite.GetNeutronZoneApp(suite.ChainB).HooksTransferIBCModule. + suite.GetNeutronZoneApp(suite.ChainB).TransferStack. ICS4Middleware.Hooks = testutils.TestRecvBeforeAfterHooks{Status: status} }, true}, } @@ -87,7 +87,7 @@ func (suite *HooksTestSuite) TestOnRecvPacketHooks() { data := transfertypes.NewFungibleTokenPacketData(trace.GetFullDenomPath(), amount.String(), suite.ChainA.SenderAccount.GetAddress().String(), receiver, "") packet := channeltypes.NewPacket(data.GetBytes(), seq, suite.TransferPath.EndpointA.ChannelConfig.PortID, suite.TransferPath.EndpointA.ChannelID, suite.TransferPath.EndpointB.ChannelConfig.PortID, suite.TransferPath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) - ack := suite.GetNeutronZoneApp(suite.ChainB).HooksTransferIBCModule. + ack := suite.GetNeutronZoneApp(suite.ChainB).TransferStack. OnRecvPacket(suite.ChainB.GetContext(), packet, suite.ChainA.SenderAccount.GetAddress()) if tc.expPass { @@ -96,14 +96,14 @@ func (suite *HooksTestSuite) TestOnRecvPacketHooks() { suite.Require().False(ack.Success()) } - if _, ok := suite.GetNeutronZoneApp(suite.ChainB).HooksTransferIBCModule. + if _, ok := suite.GetNeutronZoneApp(suite.ChainB).TransferStack. ICS4Middleware.Hooks.(testutils.TestRecvOverrideHooks); ok { suite.Require().True(status.OverrideRan) suite.Require().False(status.BeforeRan) suite.Require().False(status.AfterRan) } - if _, ok := suite.GetNeutronZoneApp(suite.ChainB).HooksTransferIBCModule. + if _, ok := suite.GetNeutronZoneApp(suite.ChainB).TransferStack. ICS4Middleware.Hooks.(testutils.TestRecvBeforeAfterHooks); ok { suite.Require().False(status.OverrideRan) suite.Require().True(status.BeforeRan) diff --git a/x/ibc-hooks/utils/utils.go b/x/ibc-hooks/utils/utils.go index 4e0619da8..44e4ed427 100644 --- a/x/ibc-hooks/utils/utils.go +++ b/x/ibc-hooks/utils/utils.go @@ -71,3 +71,13 @@ func DeriveIntermediateSender(channel, originalSender, bech32Prefix string) (str sender := sdk.AccAddress(senderHash32) return sdk.Bech32ifyAddressBytes(bech32Prefix, sender) } + +// IsAckError checks an IBC acknowledgement to see if it's an error. +// This is a replacement for ack.Success() which is currently not working on some circumstances +func IsAckError(acknowledgement []byte) bool { + var ackErr channeltypes.Acknowledgement_Error + if err := json.Unmarshal(acknowledgement, &ackErr); err == nil && len(ackErr.Error) > 0 { + return true + } + return false +} diff --git a/x/ibc-rate-limit/README.md b/x/ibc-rate-limit/README.md new file mode 100644 index 000000000..34165427e --- /dev/null +++ b/x/ibc-rate-limit/README.md @@ -0,0 +1,305 @@ +# IBC Rate Limit + +The IBC Rate Limit module is responsible for adding a governance-configurable rate limit to IBC transfers. +This is a safety control, intended to protect assets on Neutron in event of: + +* a bug/hack on Neutron +* a bug/hack on the counter-party chain +* a bug/hack in IBC itself + +This is done in exchange for a potential (one-way) bridge liveness tradeoff, in periods of high deposits or withdrawals. + +The architecture of this package is a minimal go package which implements an [IBC Middleware](https://github.com/cosmos/ibc-go/blob/f57170b1d4dd202a3c6c1c61dcf302b6a9546405/docs/ibc/middleware/develop.md) that wraps the [ICS20 transfer](https://ibc.cosmos.network/main/apps/transfer/overview.html) app, and calls into a cosmwasm contract. +The cosmwasm contract then has all of the actual IBC rate limiting logic. +The Cosmwasm code can be found in the [`contracts`](./contracts/) package, with bytecode findable in the [`bytecode`](./bytecode/) folder. The cosmwasm VM usage allows Neutron chain governance (Security SubDAI) to choose to change this safety control with no hard forks, via a parameter change proposal, a great mitigation for faster threat adaptavity. + +The status of the module is being in a state suitable for some initial governance settable rate limits for high value bridged assets. +Its not in its long term / end state for all channels by any means, but does act as a strong protection we +can instantiate today for high value IBC connections. + +## Motivation + +The motivation of IBC-rate-limit comes from the empirical observations of blockchain bridge hacks that a rate limit would have massively reduced the stolen amount of assets in: + +- [Polynetwork Bridge Hack ($611 million)](https://rekt.news/polynetwork-rekt/) +- [BNB Bridge Hack ($586 million)](https://rekt.news/bnb-bridge-rekt/) +- [Wormhole Bridge Hack ($326 million)](https://rekt.news/wormhole-rekt/) +- [Nomad Bridge Hack ($190 million)](https://rekt.news/nomad-rekt/) +- [Harmony Bridge Hack ($100 million)](https://rekt.news/harmony-rekt/) - (Would require rate limit + monitoring) +- [Dragonberry IBC bug](https://forum.cosmos.network/t/ibc-security-advisory-dragonberry/7702) (can't yet disclose amount at risk, but was saved due to being found first by altruistic Neutron core developers) + +In the presence of a software bug on Neutron, IBC itself, or on a counterparty chain, we would like to prevent the bridge from being fully depegged. +This stems from the idea that a 30% asset depeg is ~infinitely better than a 100% depeg. +Its _crazy_ that today these complex bridged assets can instantly go to 0 in event of bug. +The goal of a rate limit is to raise an alert that something has potentially gone wrong, allowing validators and developers to have time to analyze, react, and protect larger portions of user funds. + +The thesis of this is that, it is worthwhile to sacrifice liveness in the case of legitimate demand to send extreme amounts of funds, to prevent the terrible long-tail full fund risks. +Rate limits aren't the end-all of safety controls, they're merely the simplest automated one. More should be explored and added onto IBC! + +## Rate limit types + +We express rate limits in time-based periods. +This means, we set rate limits for (say) 6-hour, daily, and weekly intervals. +The rate limit for a given time period stores the relevant amount of assets at the start of the rate limit. +Rate limits are then defined on percentage terms of the asset. +The time windows for rate limits are currently _not_ rolling, they have discrete start/end times. + +We allow setting separate rate limits for the inflow and outflow of assets. +We do all of our rate limits based on the _net flow_ of assets on a channel pair. This prevents DOS issues, of someone repeatedly sending assets back and forth, to trigger rate limits and break liveness. + +We currently envision creating two kinds of rate limits: + +* Per denomination rate limits + - allows safety statements like "Only 30% of Atom on Neutron can flow out in one day" or "The amount of Atom on Neutron can at most double per day". +* Per channel rate limits + - Limit the total inflow and outflow on a given IBC channel, based on "USDC" equivalent, using Neutron as the price oracle. + +We currently only implement per denomination rate limits for non-native assets. We do not yet implement channel based rate limits. + +Currently these rate limits automatically "expire" at the end of the quota duration. TODO: Think of better designs here. E.g. can we have a constant number of subsequent quotas start filled? Or perhaps harmonically decreasing amounts of next few quotas pre-filled? Halted until DAO override seems not-great. + +## Instantiating rate limits + +Today all rate limit quotas must be set manually by governance. +In the future, we should design towards some conservative rate limit to add as a safety-backstop automatically for channels. +Ideas for how this could look: + +* One month after a channel has been created, automatically add in some USDC-based rate limit +* One month after governance incentivizes an asset, add on a per-denomination rate limit. + +Definitely needs far more ideation and iteration! + +## Parameterizing the rate limit + +One element is we don't want any rate limit timespan that's too short, e.g. not enough time for humans to react to. So we wouldn't want a 1 hour rate limit, unless we think that if its hit, it could be assessed within an hour. + +### Handling rate limit boundaries + +We want to be safe against the case where say we have a daily rate limit ending at a given time, and an adversary attempts to attack near the boundary window. +We would not like them to be able to "double extract funds" by timing their extraction near a window boundary. + +Admittedly, not a lot of thought has been put into how to deal with this well. +Right now we envision simply handling this by saying if you want a quota of duration D, instead include two quotas of duration D, but offset by `D/2` from each other. + +Ideally we can change windows to be more 'rolling' in the future, to avoid this overhead and more cleanly handle the problem. (Perhaps rolling ~1 hour at a time) + +### Inflow parameterization + +The "Inflow" side of a rate limit is essentially protection against unforeseen bug on a counterparty chain. +This can be quite conservative (e.g. bridged amount doubling in one week). This covers a few cases: + +* Counter-party chain B having a token theft attack + - TODO: description of how this looks +* Counter-party chain B runaway mint + - TODO: description of how this looks +* IBC theft + - TODO: description of how this looks + +It does get more complex when the counterparty chain is itself a DEX, but this is still much more protection than nothing. + +### Outflow parameterization + +The "Outflow" side of a rate limit is protection against a bug on Neutron OR IBC. +This has potential for much more user-frustrating issues, if set too low. +E.g. if there's some event that causes many people to suddenly withdraw many STARS or many USDC. + +So this parameterization has to contend with being a tradeoff of withdrawal liveness in high volatility periods vs being a crucial safety rail, in event of on-Neutron bug. + +TODO: Better fill out + +### Example suggested parameterization + +## Code structure + +As mentioned at the beginning of the README, the go code is a relatively minimal ICS 20 wrapper, that dispatches relevant calls to a cosmwasm contract that implements the rate limiting functionality. + +### Go Middleware + +To achieve this, the middleware needs to implement the `porttypes.Middleware` interface and the +`porttypes.ICS4Wrapper` interface. This allows the middleware to send and receive IBC messages by wrapping +any IBC module, and be used as an ICS4 wrapper by a transfer module (for sending packets or writing acknowledgements). + +Of those interfaces, just the following methods have custom logic: + +* `ICS4Wrapper.SendPacket` forwards to contract, with intent of tracking of value sent via an ibc channel +* `Middleware.OnRecvPacket` forwards to contract, with intent of tracking of value received via an ibc channel +* `Middleware.OnAcknowledgementPacket` forwards to contract, with intent of undoing the tracking of a sent packet if the acknowledgment is not a success +* `OnTimeoutPacket` forwards to contract, with intent of undoing the tracking of a sent packet if the packet times out (is not relayed) + +All other methods from those interfaces are passthroughs to the underlying implementations. + +#### Parameters + +The middleware uses the following parameters: + +| Key | Type | +|-----------------|--------| +| ContractAddress | string | + +1. **ContractAddress** - + The contract address is the address of an instantiated version of the contract provided under `./contracts/` + +### Cosmwasm Contract Concepts + +Something to keep in mind with all of the code, is that we have to reason separately about every item in the following matrix: + +| Native Token | Non-Native Token | +|----------------------|--------------------------| +| Send Native Token | Send Non-Native Token | +| Receive Native Token | Receive Non-Native Token | +| Timeout Native Send | Timeout Non-native Send | + +(Error ACK can reuse the same code as timeout) + +TODO: Spend more time on sudo messages in the following description. We need to better describe how we map the quota concepts onto the code. +Need to describe how we get the quota beginning balance, and that its different for sends and receives. +Explain intracacies of tracking that a timeout and/or ErrorAck must appear from the same quota, else we ignore its update to the quotas. + + +The tracking contract uses the following concepts + +1. **RateLimit** - tracks the value flow transferred and the quota for a path. +2. **Path** - is a (denom, channel) pair. +3. **Flow** - tracks the value that has moved through a path during the current time window. +4. **Quota** - is the percentage of the denom's total value that can be transferred through the path in a given period of time (duration) + +#### Messages + +The contract specifies the following messages: + +##### Query + +* GetQuotas - Returns the quotas for a path + +##### Exec + +* AddPath - Adds a list of quotas for a path +* RemovePath - Removes a path +* ResetPathQuota - If a rate limit has been reached, the contract's governance address can reset the quota so that transfers are allowed again + +##### Sudo + +Sudo messages can only be executed by the chain. + +* SendPacket - Increments the amount used out of the send quota and checks that the send is allowed. If it isn't, it will return a RateLimitExceeded error +* RecvPacket - Increments the amount used out of the receive quota and checks that the receive is allowed. If it isn't, it will return a RateLimitExceeded error +* UndoSend - If a send has failed, the undo message is used to remove its cost from the send quota + +All of these messages receive the packet from the chain and extract the necessary information to process the packet and determine if it should be the rate limited. + +### Necessary information + +To determine if a packet should be rate limited, we need: + +* Channel: The channel on the Neutron side: `packet.SourceChannel` for sends, and `packet.DestinationChannel` for receives. +* Denom: The denom of the token being transferred as known on the Neutron side (more on that below) +* Channel Value: The total value of the channel denominated in `Denom` (i.e.: channel-17 is worth 10k osmo). +* Funds: the amount being transferred + +#### Notes on Channel +The contract also supports quotas on a custom channel called "any" that is checked on every transfer. If either the +transfer channel or the "any" channel have a quota that has been filled, the transaction will be rate limited. + +#### Notes on Denom +We always use the the denom as represented on Neutron. For native assets that is the local denom, and for non-native +assets it's the "ibc" prefix and the sha256 hash of the denom trace (`ibc/...`). + +##### Sends + +For native denoms, we can just use the denom in the packet. If the denom is invalid, it will fail somewhere else along the chain. Example result: `uosmo` + +For non-native denoms, the contract needs to hash the denom trace and append it to the `ibc/` prefix. The +contract always receives the parsed denom (i.e.: `transfer/channel-32/uatom` instead of +`ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2`). This is because of the order in which +the middleware is called. When sending a non-native denom, the packet contains `transfer/source-channel/denom` as it +is built on the `relay.SendTransfer()` in the transfer module and then passed to the middleware. Example result: `ibc/` + +##### Receives + +This behaves slightly different if the asset is an Neutron asset that was sent to the counterparty and is being +returned to the chain, or if the asset is being received by the chain and originates on the counterparty. In ibc this +is called being a "source" or a "sink" respectively. + +If the chain is a sink for the denom, we build the local denom by prefixing the port and the channel +(`transfer/local-channel`) and hashing that denom. Example result: `ibc/` + +If the chain is the source for the denom, there are two possibilities: + +* The token is a native token, in which case we just remove the prefix added by the counterparty. Example result: `uosmo` +* The token is a non-native token, in which case we remove the extra prefix and hash it. Example result `ibc/` + +#### Notes on Channel Value +We have iterated on different strategies for calculating the channel value. Our preferred strategy is the following: +* For non-native tokens (`ibc/...`), the channel value should be the supply of those tokens in Neutron +* For native tokens, the channel value should be the total amount of tokens in escrow across all ibc channels + +The later ensures the limits are lower and represent the amount of native tokens that exist outside Neutron. This is +beneficial as we assume the majority of native tokens exist on the native chain and the amount "normal" ibc transfers is +proportional to the tokens that have left the chain. + +This strategy cannot be implemented at the moment because IBC does not track the amount of tokens in escrow across +all channels ([github issue](https://github.com/cosmos/ibc-go/issues/2664)). Instead, we use the current supply on +Neutron for all denoms (i.e.: treat native and non-native tokens the same way). Once that ticket is fixed, we will +update this strategy. + +##### Caching + +The channel value varies constantly. To have better predictability, and avoid issues of the value growing if there is +a potential infinite mint bug, we cache the channel value at the beginning of the period for every quota. + +This means that if we have a daily quota of 1% of the osmo supply, and the channel value is 1M osmo at the beginning of +the quota, no more than 100k osmo can transferred during that day. If 10M osmo were to be minted or IBC'd in during that +period, the quota will not increase until the period expired. Then it will be 1% of the new channel value (~11M) + +### Integration + +The rate limit middleware wraps the `transferIBCModule` and is added as the entry route for IBC transfers. + +The module is also provided to the underlying `transferIBCModule` as its `ICS4Wrapper`; previously, this would have +pointed to a channel, which also implements the `ICS4Wrapper` interface. + +## Testing strategy + + +A general testing strategy is as follows: + +* Setup two chains. +* Send some tokens from A->B and some from B->A (so that there are IBC tokens to play with in both sides) +* Add the rate limiter on A with low limits (i.e. 1% of supply) +* Test Function for chains A' and B' and denom d + * Send some d tokens from A' to B' and get close to the limit. + * Do the same transfer making sure the amount is above the quota and verify it fails with the rate limit error + * Wait until the reset time has passed, and send again. The transfer should now succeed +* Repeat the above test for the following combination of chains and tokens: `(A,B,a)`, `(B,A,a)`, `(A,B,b)`, `(B,A,b)`, + where `a` and `b` are native tokens to chains A and B respectively. + +For more comprehensive tests we can also: +* Add a third chain C and make sure everything works properly for C tokens that have been transferred to A and to B +* Test that the contracts gov address can reset rate limits if the quota has been hit +* Test the queries for getting information about the state of the quotas +* Test that rate limit symmetries hold (i.e.: sending the a token through a rate-limited channel and then sending back + reduces the rate limits by the same amount that it was increased during the first send) +* Ensure that the channels between the test chains have different names (A->B="channel-0", B->A="channel-1", for example) + +## Known Future work + +Items that have been highlighted above: + +* Making automated rate limits get added for channels, instead of manual configuration only +* Improving parameterization strategies / data analysis +* Adding the USDC based rate limits +* We need better strategies for how rate limits "expire". + +Not yet highlighted + +* Making monitoring tooling to know when approaching rate limiting and when they're hit +* Making tooling to easily give us summaries we can use, to reason about "bug or not bug" in event of rate limit being hit +* Enabling ways to pre-declare large transfers so as to not hit rate limits. + * Perhaps you can on-chain declare intent to send these assets with a large delay, that raises monitoring but bypasses rate limits? + * Maybe contract-based tooling to split up the transfer suffices? +* Strategies to account for high volatility periods without hitting rate limits + * Can imagine "Hop network" style markets emerging + * Could imagine tieng it into looking at AMM volatility, or off-chain oracles + * but these are both things we should be wary of security bugs in. + * Maybe [constraint based programming with tracking of provenance](https://youtu.be/HB5TrK7A4pI?t=2852) as a solution +* Analyze changing denom-based rate limits, to just overall withdrawal amount for Neutron diff --git a/x/ibc-rate-limit/bytecode/rate_limiter.wasm b/x/ibc-rate-limit/bytecode/rate_limiter.wasm new file mode 100644 index 000000000..7627ff34c Binary files /dev/null and b/x/ibc-rate-limit/bytecode/rate_limiter.wasm differ diff --git a/x/ibc-rate-limit/client/cli/query.go b/x/ibc-rate-limit/client/cli/query.go new file mode 100644 index 000000000..50126ff5d --- /dev/null +++ b/x/ibc-rate-limit/client/cli/query.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + // Group ibc-rate-limit queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + GetParams(), + ) + + return nil +} + +// GetParams returns the params for the module +func GetParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params [flags]", + Short: "Get the params for the x/ibc-rate-limit module", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/ibc-rate-limit/genesis.go b/x/ibc-rate-limit/genesis.go new file mode 100644 index 000000000..938943b59 --- /dev/null +++ b/x/ibc-rate-limit/genesis.go @@ -0,0 +1,23 @@ +package ibcratelimit + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +// InitGenesis initializes the x/ibc-rate-limit module's state from a provided genesis +// state, which includes the parameter for the contract address. +func (i *ICS4Wrapper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { + err := i.IbcratelimitKeeper.SetParams(ctx, genState.Params) + if err != nil { + panic(err) + } +} + +// ExportGenesis returns the x/ibc-rate-limit module's exported genesis. +func (i *ICS4Wrapper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return &types.GenesisState{ + Params: i.GetParams(ctx), + } +} diff --git a/x/ibc-rate-limit/genesis_test.go b/x/ibc-rate-limit/genesis_test.go new file mode 100644 index 000000000..c2bd546f8 --- /dev/null +++ b/x/ibc-rate-limit/genesis_test.go @@ -0,0 +1,43 @@ +package ibcratelimit_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" + "github.com/stretchr/testify/suite" + + "github.com/neutron-org/neutron/v5/testutil/apptesting" +) + +type GenesisTestSuite struct { + apptesting.KeeperTestHelper +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(GenesisTestSuite)) +} + +func (suite *GenesisTestSuite) SetupTest() { + suite.Setup() +} + +func (suite *GenesisTestSuite) TestInitExportGenesis() { + testAddress := sdk.AccAddress([]byte("addr1_______________")).String() + suite.SetupTest() + k := suite.App.RateLimitingICS4Wrapper + + initialGenesis := types.GenesisState{ + Params: types.Params{ + ContractAddress: testAddress, + }, + } + + k.InitGenesis(suite.Ctx, initialGenesis) + + suite.Require().Equal(testAddress, k.GetParams(suite.Ctx).ContractAddress) + + exportedGenesis := k.ExportGenesis(suite.Ctx) + + suite.Require().Equal(initialGenesis, *exportedGenesis) +} diff --git a/x/ibc-rate-limit/ibc_middleware_test.go b/x/ibc-rate-limit/ibc_middleware_test.go new file mode 100644 index 000000000..e70f2815f --- /dev/null +++ b/x/ibc-rate-limit/ibc_middleware_test.go @@ -0,0 +1,760 @@ +package ibcratelimit_test + +import ( + "fmt" + "strconv" + "strings" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + "github.com/neutron-org/neutron/v5/testutil" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v8/testing" + "github.com/stretchr/testify/suite" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +type MiddlewareTestSuite struct { + testutil.IBCConnectionTestSuite +} + +const channelTest = "channel-2" + +// Setup +func TestMiddlewareTestSuite(t *testing.T) { + suite.Run(t, new(MiddlewareTestSuite)) +} + +// Helpers +func (suite *MiddlewareTestSuite) MessageFromAToB(denom string, amount sdkmath.Int) sdk.Msg { + coin := sdk.NewCoin(denom, amount) + port := suite.TransferPath.EndpointA.ChannelConfig.PortID + channel := suite.TransferPath.EndpointA.ChannelID + accountFrom := suite.ChainA.SenderAccount.GetAddress().String() + accountTo := suite.ChainB.SenderAccount.GetAddress().String() + timeoutHeight := clienttypes.NewHeight(10, 100) + return transfertypes.NewMsgTransfer( + port, + channel, + coin, + accountFrom, + accountTo, + timeoutHeight, + uint64(time.Now().UnixNano()), + "", + ) +} + +func (suite *MiddlewareTestSuite) MessageFromBToA(denom string, amount sdkmath.Int) sdk.Msg { + coin := sdk.NewCoin(denom, amount) + port := suite.TransferPath.EndpointB.ChannelConfig.PortID + channel := suite.TransferPath.EndpointB.ChannelID + accountFrom := suite.ChainB.SenderAccount.GetAddress().String() + accountTo := suite.ChainA.SenderAccount.GetAddress().String() + timeoutHeight := clienttypes.NewHeight(10, 100) + return transfertypes.NewMsgTransfer( + port, + channel, + coin, + accountFrom, + accountTo, + timeoutHeight, + uint64(time.Now().UnixNano()), + "", + ) +} + +func (suite *MiddlewareTestSuite) MessageFromAToC(denom string, amount sdkmath.Int) sdk.Msg { + coin := sdk.NewCoin(denom, amount) + port := suite.TransferPathAC.EndpointA.ChannelConfig.PortID + channel := suite.TransferPathAC.EndpointA.ChannelID + accountFrom := suite.ChainA.SenderAccount.GetAddress().String() + accountTo := suite.ChainC.SenderAccount.GetAddress().String() + timeoutHeight := clienttypes.NewHeight(10, 100) + return transfertypes.NewMsgTransfer( + port, + channel, + coin, + accountFrom, + accountTo, + timeoutHeight, + uint64(time.Now().UnixNano()), + "", + ) +} + +func CalculateChannelValue(ctx sdk.Context, denom string, bankKeeper bankkeeper.Keeper) sdkmath.Int { + return bankKeeper.GetSupply(ctx, denom).Amount +} + +func (suite *MiddlewareTestSuite) FullSendBToA(msg sdk.Msg) (*abci.ExecTxResult, string, error) { + sendResult, err := suite.SendMsgsNoCheck(suite.ChainB, msg) + suite.Require().NoError(err) + + packet, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents()) + suite.Require().NoError(err) + + err = suite.TransferPath.EndpointA.UpdateClient() + suite.Require().NoError(err) + + res, err := suite.TransferPath.EndpointA.RecvPacketWithResult(packet) + suite.Require().NoError(err) + + ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) + suite.Require().NoError(err) + + err = suite.TransferPath.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = suite.TransferPath.EndpointB.UpdateClient() + suite.Require().NoError(err) + + return sendResult, string(ack), err +} + +func (suite *MiddlewareTestSuite) FullSendAToB(msg sdk.Msg) (*abci.ExecTxResult, string, error) { + sendResult, err := suite.SendMsgsNoCheck(suite.ChainA, msg) + if err != nil { + return nil, "", err + } + + packet, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents()) + if err != nil { + return nil, "", err + } + + err = suite.TransferPath.EndpointB.UpdateClient() + if err != nil { + return nil, "", err + } + + res, err := suite.TransferPath.EndpointB.RecvPacketWithResult(packet) + if err != nil { + return nil, "", err + } + ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) + if err != nil { + return nil, "", err + } + err = suite.TransferPath.EndpointA.UpdateClient() + if err != nil { + return nil, "", err + } + err = suite.TransferPath.EndpointB.UpdateClient() + if err != nil { + return nil, "", err + } + + return sendResult, string(ack), nil +} + +func (suite *MiddlewareTestSuite) FullSendAToC(msg sdk.Msg) (*abci.ExecTxResult, string, error) { + sendResult, err := suite.SendMsgsNoCheck(suite.ChainA, msg) + if err != nil { + return nil, "", err + } + + packet, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents()) + if err != nil { + return nil, "", err + } + + err = suite.TransferPathAC.EndpointB.UpdateClient() + if err != nil { + return nil, "", err + } + + res, err := suite.TransferPathAC.EndpointB.RecvPacketWithResult(packet) + if err != nil { + return nil, "", err + } + + ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) + if err != nil { + return nil, "", err + } + + err = suite.TransferPathAC.EndpointA.UpdateClient() + if err != nil { + return nil, "", err + } + err = suite.TransferPathAC.EndpointB.UpdateClient() + if err != nil { + return nil, "", err + } + + return sendResult, string(ack), nil +} + +func (suite *MiddlewareTestSuite) AssertReceive(success bool, msg sdk.Msg) (string, error) { + _, ack, err := suite.FullSendBToA(msg) + if success { + suite.Require().NoError(err) + suite.Require().NotContains(ack, "error", + "acknowledgment is an error") + } else { + suite.Require().Contains(ack, "error", + "acknowledgment is not an error") + suite.Require().Contains(ack, fmt.Sprintf("ABCI code: %d", types.ErrRateLimitExceeded.ABCICode()), + "acknowledgment error is not of the right type") + } + return ack, err +} + +func (suite *MiddlewareTestSuite) AssertSend(success bool, msg sdk.Msg) (*abci.ExecTxResult, error) { + r, _, err := suite.FullSendAToB(msg) + if success { + suite.Require().NoError(err, "IBC send failed. Expected success. %s", err) + } else { + suite.Require().Error(err, "IBC send succeeded. Expected failure") + suite.ErrorContains(err, types.ErrRateLimitExceeded.Error(), "Bad error type") + } + return r, err +} + +func (suite *MiddlewareTestSuite) BuildChannelQuota(name, channel, denom string, duration, sendPercentage, recvPercentage uint32) string { + return fmt.Sprintf(` + {"channel_id": "%s", "denom": "%s", "quotas": [{"name":"%s", "duration": %d, "send_recv":[%d, %d]}] } + `, channel, denom, name, duration, sendPercentage, recvPercentage) +} + +func (suite *MiddlewareTestSuite) BuildChannelQuotaWith2Quotas(name, channel, denom string, duration1 uint32, name2 string, sendPercentage1, recvPercentage1, duration2, sendPercentage2, recvPercentage2 uint32) string { + return fmt.Sprintf(` + {"channel_id": "%s", "denom": "%s", "quotas": [{"name":"%s", "duration": %d, "send_recv":[%d, %d]},{"name":"%s", "duration": %d, "send_recv":[%d, %d]}] } + `, channel, denom, name, duration1, sendPercentage1, recvPercentage1, name2, duration2, sendPercentage2, recvPercentage2) +} + +// Tests + +// Test that Sending IBC messages works when the middleware isn't configured +func (suite *MiddlewareTestSuite) TestSendTransferNoContract() { + suite.ConfigureTransferChannel() + one := sdkmath.NewInt(2) + _, err := suite.AssertSend(true, suite.MessageFromAToB(sdk.DefaultBondDenom, one)) + suite.Require().NoError(err) +} + +// Test that Receiving IBC messages works when the middleware isn't configured +func (suite *MiddlewareTestSuite) TestReceiveTransferNoContract() { + suite.ConfigureTransferChannel() + one := sdkmath.NewInt(2) + _, err := suite.AssertReceive(true, suite.MessageFromBToA(sdk.DefaultBondDenom, one)) + suite.Require().NoError(err) +} + +func (suite *MiddlewareTestSuite) initializeEscrow() { + app := suite.GetNeutronZoneApp(suite.ChainA) + supply := app.BankKeeper.GetSupply(suite.ChainA.GetContext(), sdk.DefaultBondDenom) + + // Move some funds from chainA to chainB so that there is something in escrow + // Each user has 10% of the supply, so we send most of the funds from one user to chainA + transferAmount := supply.Amount.QuoRaw(20) + + // When sending, the amount we're sending goes into escrow before we enter the middleware and thus + // it's used as part of the channel value in the rate limiting contract + // To account for that, we subtract the amount we'll send first (2.5% of transferAmount) here + sendAmount := transferAmount.QuoRaw(40) + + // Send from A to B + _, _, err := suite.FullSendAToB(suite.MessageFromAToB(sdk.DefaultBondDenom, transferAmount.Sub(sendAmount))) + suite.Require().NoError(err) + // Send from B to A + _, _, err = suite.FullSendBToA(suite.MessageFromBToA(sdk.DefaultBondDenom, transferAmount.Sub(sendAmount))) + suite.Require().NoError(err) +} + +func (suite *MiddlewareTestSuite) fullSendTest(native bool) map[string]string { + quotaPercentage := 5 + suite.initializeEscrow() + // Get the denom and amount to send + denom := sdk.DefaultBondDenom + if !native { + denomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", channelTest, denom)) + denom = denomTrace.IBCDenom() + } + + app := suite.GetNeutronZoneApp(suite.ChainA) + + // This is the first one. Inside the tests. It works as expected. + channelValue := CalculateChannelValue(suite.ChainA.GetContext(), denom, app.BankKeeper) + + // The amount to be sent is send 2.5% (quota is 5%) + quota := channelValue.QuoRaw(int64(100 / quotaPercentage)) + sendAmount := quota.QuoRaw(2) + + // Setup contract + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuota("weekly", channelTest, denom, 604800, 5, 5) + addr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(addr) + + // send 2.5% (quota is 5%) + _, err := suite.AssertSend(true, suite.MessageFromAToB(denom, sendAmount)) + suite.Require().NoError(err) + + // send 2.5% (quota is 5%) + r, _ := suite.AssertSend(true, suite.MessageFromAToB(denom, sendAmount)) + + // Calculate remaining allowance in the quota + attrs := suite.ExtractAttributes(suite.FindEvent(r.GetEvents(), "wasm")) + + used, ok := sdkmath.NewIntFromString(attrs["weekly_used_out"]) + suite.Require().True(ok) + + suite.Require().Equal(used, sendAmount.MulRaw(2)) + + // Sending above the quota should fail. We use 2 instead of 1 here to avoid rounding issues + _, err = suite.AssertSend(false, suite.MessageFromAToB(denom, sdkmath.NewInt(2))) + suite.Require().Error(err) + return attrs +} + +// Test rate limiting on sends +func (suite *MiddlewareTestSuite) TestSendTransferWithRateLimitingNative() { + suite.ConfigureTransferChannel() + // Sends denom=stake from A->B. Rate limit receives "stake" in the packet. Nothing to do in the contract + suite.fullSendTest(true) +} + +// Test rate limiting on sends +func (suite *MiddlewareTestSuite) TestSendTransferWithRateLimitingNonNative() { + suite.ConfigureTransferChannel() + // Sends denom=ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878 from A->B. + // Rate limit receives "transfer/channel-0/stake" in the packet (because transfer.relay.SendTransfer is called before the middleware) + // and should hash it before calculating the value + suite.fullSendTest(false) +} + +// Test rate limits are reset when the specified time period has passed +func (suite *MiddlewareTestSuite) TestSendTransferReset() { + suite.ConfigureTransferChannel() + // Same test as above, but the quotas get reset after time passes + attrs := suite.fullSendTest(true) + parts := strings.Split(attrs["weekly_period_end"], ".") // Splitting timestamp into secs and nanos + secs, err := strconv.ParseInt(parts[0], 10, 64) + suite.Require().NoError(err) + nanos, err := strconv.ParseInt(parts[1], 10, 64) + suite.Require().NoError(err) + resetTime := time.Unix(secs, nanos) + + // Move chainA forward one block + suite.ChainA.NextBlock() + + // Reset time + one second + oneSecAfterReset := resetTime.Add(time.Second) + suite.Coordinator.IncrementTimeBy(oneSecAfterReset.Sub(suite.Coordinator.CurrentTime)) + + // Sending should succeed again + _, err = suite.AssertSend(true, suite.MessageFromAToB(sdk.DefaultBondDenom, sdkmath.NewInt(2))) + suite.Require().NoError(err) +} + +// Test rate while having 2 limits (daily & weekly). +// Daily is hit, wait until 'day' ends. Do this twice. Second iteration will hit the 'weekly' quota. +// Then we check that weekly rate limit still hits even after daily quota is refreshed. +func (suite *MiddlewareTestSuite) TestSendTransferDailyReset() { + suite.ConfigureTransferChannel() + quotaPercentage := 4 + suite.initializeEscrow() + // Get the denom and amount to send + denom := sdk.DefaultBondDenom + + app := suite.GetNeutronZoneApp(suite.ChainA) + + // This is the first one. Inside the tests. It works as expected. + channelValue := CalculateChannelValue(suite.ChainA.GetContext(), denom, app.BankKeeper) + + // The amount to be sent is 2% (weekly quota is 4%, daily is 2%) + quota := channelValue.QuoRaw(int64(100 / quotaPercentage)) + sendAmount := quota.QuoRaw(2) + + // Setup contract + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuotaWith2Quotas("weekly", channelTest, denom, 604800, "daily", 4, 4, 86400, 2, 2) + addr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(addr) + + // send 2% (daily quota is 2%) + r, err := suite.AssertSend(true, suite.MessageFromAToB(denom, sendAmount)) + suite.Require().NoError(err) + + // Calculate remaining allowance in the quota + attrs := suite.ExtractAttributes(suite.FindEvent(r.GetEvents(), "wasm")) + + used, ok := sdkmath.NewIntFromString(attrs["daily_used_out"]) + suite.Require().True(ok) + + suite.Require().Equal(used, sendAmount) + + weeklyUsed, ok := sdkmath.NewIntFromString(attrs["weekly_used_out"]) + suite.Require().True(ok) + suite.Require().Equal(weeklyUsed, sendAmount) + + // Sending above the daily quota should fail. + _, err = suite.AssertSend(false, suite.MessageFromAToB(denom, sendAmount)) + suite.Require().Error(err) + // Now we 'wait' until 'day' ends + parts := strings.Split(attrs["daily_period_end"], ".") // Splitting timestamp into secs and nanos + secs, err := strconv.ParseInt(parts[0], 10, 64) + suite.Require().NoError(err) + nanos, err := strconv.ParseInt(parts[1], 10, 64) + suite.Require().NoError(err) + resetTime := time.Unix(secs, nanos) + + // Move chainA forward one block + suite.ChainA.NextBlock() + + // Reset time + one second + oneSecAfterReset := resetTime.Add(time.Second) + suite.Coordinator.IncrementTimeBy(oneSecAfterReset.Sub(suite.Coordinator.CurrentTime)) + + // Sending should succeed again. It hits daily quota for the second time & weekly quota at the same time + r, err = suite.AssertSend(true, suite.MessageFromAToB(sdk.DefaultBondDenom, sendAmount)) + suite.Require().NoError(err) + + attrs = suite.ExtractAttributes(suite.FindEvent(r.GetEvents(), "wasm")) + + used, ok = sdkmath.NewIntFromString(attrs["daily_used_out"]) + suite.Require().True(ok) + + suite.Require().Equal(used, sendAmount) + + weeklyUsed, ok = sdkmath.NewIntFromString(attrs["weekly_used_out"]) + suite.Require().True(ok) + suite.Require().Equal(weeklyUsed, sendAmount.MulRaw(2)) + + parts = strings.Split(attrs["daily_period_end"], ".") // Splitting timestamp into secs and nanos + secs, err = strconv.ParseInt(parts[0], 10, 64) + suite.Require().NoError(err) + nanos, err = strconv.ParseInt(parts[1], 10, 64) + suite.Require().NoError(err) + resetTime = time.Unix(secs, nanos) + + // Move chainA forward one block + suite.ChainA.NextBlock() + + // Reset time + one second + oneSecAfterResetDayTwo := resetTime.Add(time.Second) + // Now we're waiting for the second 'day' to expire + suite.Coordinator.IncrementTimeBy(oneSecAfterResetDayTwo.Sub(suite.Coordinator.CurrentTime)) + + // Sending should fail. Daily quota is refreshed but weekly is over + _, err = suite.AssertSend(false, suite.MessageFromAToB(sdk.DefaultBondDenom, sdkmath.NewInt(2))) + suite.Require().Error(err) +} + +// Test rate limiting on receives +func (suite *MiddlewareTestSuite) fullRecvTest(native bool) { + quotaPercentage := 4 + suite.initializeEscrow() + // Get the denom and amount to send + sendDenom := sdk.DefaultBondDenom + localDenom := sdk.DefaultBondDenom + if native { + denomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", channelTest, localDenom)) + localDenom = denomTrace.IBCDenom() + } else { + denomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", channelTest, sendDenom)) + sendDenom = denomTrace.IBCDenom() + } + + app := suite.GetNeutronZoneApp(suite.ChainA) + + channelValue := CalculateChannelValue(suite.ChainA.GetContext(), localDenom, app.BankKeeper) + + // The amount to be sent is 2% (quota is 4%) + quota := channelValue.QuoRaw(int64(100 / quotaPercentage)) + sendAmount := quota.QuoRaw(2) + + // Setup contract + suite.GetNeutronZoneApp(suite.ChainA) + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuota("weekly", channelTest, localDenom, 604800, 4, 4) + addr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(addr) + + // receive 2.5% (quota is 5%) + _, err := suite.AssertReceive(true, suite.MessageFromBToA(sendDenom, sendAmount)) + suite.Require().NoError(err) + + // receive 2.5% (quota is 5%) + _, err = suite.AssertReceive(true, suite.MessageFromBToA(sendDenom, sendAmount)) + suite.Require().NoError(err) + + // Sending above the quota should fail. We send 2 instead of 1 to account for rounding errors + _, err = suite.AssertReceive(false, suite.MessageFromBToA(sendDenom, sdkmath.NewInt(20000))) + suite.Require().NoError(err) +} + +func (suite *MiddlewareTestSuite) TestRecvTransferWithRateLimitingNative() { + suite.ConfigureTransferChannel() + // Sends denom=stake from B->A. + // Rate limit receives "stake" in the packet and should wrap it before calculating the value + // types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) should return false => Wrap the token + suite.fullRecvTest(true) +} + +func (suite *MiddlewareTestSuite) TestRecvTransferWithRateLimitingNonNative() { + suite.ConfigureTransferChannel() + // Sends denom=ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878 from B->A. + // Rate limit receives "transfer/channel-0/stake" in the packet and should turn it into "stake" + // types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) should return true => unprefix. If unprefixed is not local, hash. + suite.fullRecvTest(false) +} + +// Test no rate limiting occurs when the contract is set, but no quotas are configured for the path +func (suite *MiddlewareTestSuite) TestSendTransferNoQuota() { + suite.ConfigureTransferChannel() + // Setup contract + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + addr := suite.InstantiateRLContract(``) + suite.RegisterRateLimitingContract(addr) + + // send 1 token. + // If the contract doesn't have a quota for the current channel, all transfers are allowed + _, err := suite.AssertSend(true, suite.MessageFromAToB(sdk.DefaultBondDenom, sdkmath.NewInt(1))) + suite.Require().NoError(err) +} + +// Test rate limits are reverted if a "send" fails +func (suite *MiddlewareTestSuite) TestFailedSendTransfer() { + suite.ConfigureTransferChannel() + suite.initializeEscrow() + // Setup contract + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuota("weekly", channelTest, sdk.DefaultBondDenom, 604800, 1, 1) + addr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(addr) + + // Get the escrowed amount + app := suite.GetNeutronZoneApp(suite.ChainA) + // ToDo: This is what we eventually want here, but using the full supply temporarily for performance reasons. See CalculateChannelValue + // escrowAddress := transfertypes.GetEscrowAddress("transfer", "channel-0") + // escrowed := app.BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) + escrowed := app.BankKeeper.GetSupply(suite.ChainA.GetContext(), sdk.DefaultBondDenom) + quota := escrowed.Amount.QuoRaw(100) // 1% of the escrowed amount + + // Use the whole quota + coins := sdk.NewCoin(sdk.DefaultBondDenom, quota) + port := suite.TransferPath.EndpointA.ChannelConfig.PortID + channel := suite.TransferPath.EndpointA.ChannelID + accountFrom := suite.ChainA.SenderAccount.GetAddress().String() + timeoutHeight := clienttypes.NewHeight(10, 100) + msg := transfertypes.NewMsgTransfer(port, channel, coins, accountFrom, "INVALID", timeoutHeight, 0, "") + + // Sending the message manually because AssertSend updates both clients. We need to update the clients manually + // for this test so that the failure to receive on chain B happens after the second packet is sent from chain A. + // That way we validate that chain A is blocking as expected, but the flow is reverted after the receive failure is + // acknowledged on chain A + res, err := suite.SendMsgsNoCheck(suite.ChainA, msg) + suite.Require().NoError(err) + + // Sending again fails as the quota is filled + _, err = suite.AssertSend(false, suite.MessageFromAToB(sdk.DefaultBondDenom, quota)) + suite.Require().Error(err) + + // Move forward one block + suite.ChainA.NextBlock() + suite.ChainA.Coordinator.IncrementTime() + + // Update both clients + err = suite.TransferPath.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = suite.TransferPath.EndpointB.UpdateClient() + suite.Require().NoError(err) + + // Execute the acknowledgement from chain B in chain A + + // extract the sent packet + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + + // recv in chain b + newRes, err := suite.TransferPath.EndpointB.RecvPacketWithResult(packet) + suite.Require().NoError(err) + + // get the ack from the chain b's response + ack, err := ibctesting.ParseAckFromEvents(newRes.GetEvents()) + suite.Require().NoError(err) + + // manually relay it to chain a + err = suite.TransferPath.EndpointA.AcknowledgePacket(packet, ack) + suite.Require().NoError(err) + + // We should be able to send again because the packet that exceeded the quota failed and has been reverted + _, err = suite.AssertSend(true, suite.MessageFromAToB(sdk.DefaultBondDenom, sdkmath.NewInt(2))) + suite.Require().NoError(err) +} + +func (suite *MiddlewareTestSuite) TestUnsetRateLimitingContract() { + // Setup contract + suite.ConfigureTransferChannel() + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + addr := suite.InstantiateRLContract("") + suite.RegisterRateLimitingContract(addr) + + app := suite.GetNeutronZoneApp(suite.ChainA) + + // Unset the contract param + err := app.RateLimitingICS4Wrapper.IbcratelimitKeeper.SetParams(suite.ChainA.GetContext(), types.Params{ContractAddress: ""}) + suite.Require().NoError(err) + // N.B.: this panics if validation fails. +} + +// Test rate limits are reverted if a "send" fails +func (suite *MiddlewareTestSuite) TestNonICS20() { + suite.ConfigureTransferChannel() + suite.initializeEscrow() + // Setup contract + app := suite.GetNeutronZoneApp(suite.ChainA) + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuota("weekly", "channel-0", sdk.DefaultBondDenom, 604800, 1, 1) + addr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(addr) + + data := []byte("{}") + _, err := app.RateLimitingICS4Wrapper.SendPacket(suite.ChainA.GetContext(), capabilitytypes.NewCapability(1), "wasm.neutron1873ls0d60tg7hk00976teq9ywhzv45u3hk2urw8t3eau9eusa4eqtun9xn", "channel-0", clienttypes.NewHeight(0, 0), 1, data) + + suite.Require().Error(err) + // This will error out, but not because of rate limiting + suite.Require().NotContains(err.Error(), "rate limit") + suite.Require().Contains(err.Error(), "channel not found") +} + +func (suite *MiddlewareTestSuite) TestDenomRestrictionFlow() { + suite.ConfigureTransferChannel() + suite.ConfigureTransferChannelAC() + // Setup contract + testOwner := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) + suite.StoreTestCode(suite.ChainA.GetContext(), testOwner, "./bytecode/rate_limiter.wasm") + quotas := suite.BuildChannelQuota("weekly", "channel-0", sdk.DefaultBondDenom, 604800, 1, 1) + contractAddr := suite.InstantiateRLContract(quotas) + suite.RegisterRateLimitingContract(contractAddr) + + denom := sdk.DefaultBondDenom + sendAmount := sdkmath.NewInt(2) + acceptedChannel := suite.TransferPath.EndpointA.ChannelID + + // Sending on a diff channel should work + _, _, err := suite.FullSendAToC(suite.MessageFromAToC(denom, sendAmount)) + suite.Require().NoError(err, "Send on alternative channel should work") + + // Successfully send a denom before any restrictions are added. + _, err = suite.AssertSend(true, suite.MessageFromAToB(denom, sendAmount)) + suite.Require().NoError(err, "Send should succeed without restrictions") + + // Add a restriction that only allows sending on the accepted channel + restrictionMsg := fmt.Sprintf(`{"set_denom_restrictions": {"denom":"%s","allowed_channels":["%s"]}}`, denom, acceptedChannel) + _, err = suite.ExecuteContract(contractAddr, testOwner, []byte(restrictionMsg), sdk.Coins{}) + suite.Require().NoError(err) + + // Sending on the accepted channel should succeed + _, err = suite.AssertSend(true, suite.MessageFromAToB(denom, sendAmount)) + suite.Require().NoError(err, "Send on accepted channel should succeed") + + // Sending on any other channel should fail + _, err = suite.AssertSend(false, suite.MessageFromAToC(denom, sendAmount)) + suite.Require().Error(err, "Send on blocked channel should fail") + + // Unset the restriction and verify that sending on other channels works again + unsetMsg := fmt.Sprintf(`{"unset_denom_restrictions": {"denom":"%s"}}`, denom) + _, err = suite.ExecuteContract(contractAddr, testOwner, []byte(unsetMsg), sdk.Coins{}) + suite.Require().NoError(err, "Unsetting denom restriction should succeed") + + // Sending again on the previously blocked channel should now succeed + _, _, err = suite.FullSendAToC(suite.MessageFromAToC(denom, sendAmount)) + suite.Require().NoError(err, "Send on previously blocked channel should succeed after unsetting restriction") +} + +func (suite *MiddlewareTestSuite) InstantiateRLContract(quotas string) sdk.AccAddress { + app := suite.GetNeutronZoneApp(suite.ChainA) + transferModule := app.AccountKeeper.GetModuleAddress(transfertypes.ModuleName) + initMsgBz := []byte(fmt.Sprintf(`{ + "gov_module": "%s", + "ibc_module":"%s", + "paths": [%s] + }`, + testutil.TestOwnerAddress, transferModule, quotas)) + + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper) + codeID := uint64(1) + creator := suite.ChainA.SenderAccount.GetAddress() + addr, _, err := contractKeeper.Instantiate(suite.ChainA.GetContext(), codeID, creator, creator, initMsgBz, "rate limiting contract", nil) + suite.Require().NoError(err) + return addr +} + +func (suite *MiddlewareTestSuite) InstantiateRLContract2Quotas(quotas1 string) sdk.AccAddress { + app := suite.GetNeutronZoneApp(suite.ChainA) + transferModule := app.AccountKeeper.GetModuleAddress(transfertypes.ModuleName) + initMsgBz := []byte(fmt.Sprintf(`{ + "gov_module": "%s", + "ibc_module":"%s", + "paths": [%s] + }`, + testutil.TestOwnerAddress, transferModule, quotas1)) + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper) + codeID := uint64(1) + creator := suite.ChainA.SenderAccount.GetAddress() + addr, _, err := contractKeeper.Instantiate(suite.ChainA.GetContext(), codeID, creator, creator, initMsgBz, "rate limiting contract", nil) + suite.Require().NoError(err) + return addr +} + +func (suite *MiddlewareTestSuite) RegisterRateLimitingContract(addr []byte) { + addrStr, _ := sdk.Bech32ifyAddressBytes("neutron", addr) + app := suite.GetNeutronZoneApp(suite.ChainA) + _ = app.RateLimitingICS4Wrapper.SetParams(suite.ChainA.GetContext(), types.Params{ContractAddress: addrStr}) + require.True(suite.ChainA.TB, true) +} + +// AssertEventEmitted asserts that ctx's event manager has emitted the given number of events +// of the given type. +func (suite *MiddlewareTestSuite) AssertEventEmitted(ctx sdk.Context, eventTypeExpected string, numEventsExpected int) { + allEvents := ctx.EventManager().Events() + // filter out other events + actualEvents := make([]sdk.Event, 0) + for _, event := range allEvents { + if event.Type == eventTypeExpected { + actualEvents = append(actualEvents, event) + } + } + suite.Require().Equal(numEventsExpected, len(actualEvents)) +} + +func (suite *MiddlewareTestSuite) FindEvent(events []abci.Event, name string) abci.Event { + index := slices.IndexFunc(events, func(e abci.Event) bool { return e.Type == name }) + if index == -1 { + return abci.Event{} + } + return events[index] +} + +func (suite *MiddlewareTestSuite) ExtractAttributes(event abci.Event) map[string]string { + attrs := make(map[string]string) + if event.Attributes == nil { + return attrs + } + for _, a := range event.Attributes { + attrs[a.Key] = a.Value + } + return attrs +} diff --git a/x/ibc-rate-limit/ibc_module.go b/x/ibc-rate-limit/ibc_module.go new file mode 100644 index 000000000..38b4349ca --- /dev/null +++ b/x/ibc-rate-limit/ibc_module.go @@ -0,0 +1,252 @@ +package ibcratelimit + +import ( + "encoding/json" + "strings" + + "github.com/neutron-org/neutron/v5/x/ibc-hooks/utils" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +type IBCModule struct { + app porttypes.IBCModule + ics4Middleware *ICS4Wrapper +} + +func NewIBCModule(app porttypes.IBCModule, ics4 *ICS4Wrapper) IBCModule { + return IBCModule{ + app: app, + ics4Middleware: ics4, + } +} + +// OnChanOpenInit implements the IBCModule interface +func (im *IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) +} + +// OnChanOpenTry implements the IBCModule interface +func (im *IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) +} + +// OnChanOpenAck implements the IBCModule interface +func (im *IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // Here we can add initial limits when a new channel is open. For now, they can be added manually on the contract + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im *IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // Here we can add initial limits when a new channel is open. For now, they can be added manually on the contract + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCModule interface +func (im *IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Here we can remove the limits when a new channel is closed. For now, they can remove them manually on the contract + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im *IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // Here we can remove the limits when a new channel is closed. For now, they can remove them manually on the contract + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +type receiverParser struct { + Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` +} + +func ValidateReceiverAddress(packet exported.PacketI) error { + var receiverObj receiverParser + + if err := json.Unmarshal(packet.GetData(), &receiverObj); err != nil { + return err + } + if len(receiverObj.Receiver) >= types.MaxSupportedIBCReceiverAddressLength { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "IBC Receiver address too long. Max supported length is %d", types.MaxSupportedIBCReceiverAddressLength) + } + return nil +} + +// OnRecvPacket implements the IBCModule interface +func (im *IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) exported.Acknowledgement { + if err := ValidateReceiverAddress(packet); err != nil { + return utils.NewEmitErrorAcknowledgement(ctx, types.ErrBadMessage, err.Error()) + } + + contract := im.ics4Middleware.GetContractAddress(ctx) + if contract == "" { + // The contract has not been configured. Continue as usual + return im.app.OnRecvPacket(ctx, packet, relayer) + } + + err := CheckAndUpdateRateLimits(ctx, im.ics4Middleware.ContractKeeper, msgRecv, contract, packet) + if err != nil { + if strings.Contains(err.Error(), types.RateLimitExceededSubStr) { + return utils.NewEmitErrorAcknowledgement(ctx, types.ErrRateLimitExceeded) + } + fullError := errorsmod.Wrap(types.ErrContractError, err.Error()) + return utils.NewEmitErrorAcknowledgement(ctx, fullError) + } + + // if this returns an Acknowledgement that isn't successful, all state changes are discarded + return im.app.OnRecvPacket(ctx, packet, relayer) +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im *IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // Osmosis have some osmo-specific code here, but it disrupts proper work of neutron chain. + // See: https://github.com/osmosis-labs/osmosis/pull/8308 & https://github.com/osmosis-labs/osmosis/pull/8420 + var ack channeltypes.Acknowledgement + if err := json.Unmarshal(acknowledgement, &ack); err != nil { + return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) + } + + if utils.IsAckError(acknowledgement) { + err := im.RevertSentPacket(ctx, packet) // If there is an error here we should still handle the ack + if err != nil { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventBadRevert, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyFailureType, "acknowledgment"), + sdk.NewAttribute(types.AttributeKeyPacket, string(packet.GetData())), + sdk.NewAttribute(types.AttributeKeyAck, string(acknowledgement)), + ), + ) + } + } + + return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +// OnTimeoutPacket implements the IBCModule interface +func (im *IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + err := im.RevertSentPacket(ctx, packet) // If there is an error here we should still handle the timeout + if err != nil { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventBadRevert, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyFailureType, "timeout"), + sdk.NewAttribute(types.AttributeKeyPacket, string(packet.GetData())), + ), + ) + } + return im.app.OnTimeoutPacket(ctx, packet, relayer) +} + +// RevertSentPacket Notifies the contract that a sent packet wasn't properly received +func (im *IBCModule) RevertSentPacket( + ctx sdk.Context, + packet exported.PacketI, +) error { + contract := im.ics4Middleware.GetContractAddress(ctx) + if contract == "" { + // The contract has not been configured. Continue as usual + return nil + } + + return UndoSendRateLimit( + ctx, + im.ics4Middleware.ContractKeeper, + contract, + packet, + ) +} + +// SendPacket implements the ICS4 Wrapper interface +func (im *IBCModule) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort, sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + return im.ics4Middleware.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (im *IBCModule) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + return im.ics4Middleware.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +func (im *IBCModule) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return im.ics4Middleware.GetAppVersion(ctx, portID, channelID) +} diff --git a/x/ibc-rate-limit/ics4_wrapper.go b/x/ibc-rate-limit/ics4_wrapper.go new file mode 100644 index 000000000..41301ab86 --- /dev/null +++ b/x/ibc-rate-limit/ics4_wrapper.go @@ -0,0 +1,108 @@ +package ibcratelimit + +import ( + "encoding/json" + + errorsmod "cosmossdk.io/errors" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/keeper" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +var ( + _ porttypes.Middleware = &IBCModule{} + _ porttypes.ICS4Wrapper = &ICS4Wrapper{} +) + +type ICS4Wrapper struct { + channel porttypes.ICS4Wrapper + accountKeeper *authkeeper.AccountKeeper + bankKeeper *bankkeeper.BaseKeeper + ContractKeeper *wasmkeeper.PermissionedKeeper + IbcratelimitKeeper *keeper.Keeper +} + +func (i *ICS4Wrapper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return i.channel.GetAppVersion(ctx, portID, channelID) +} + +func NewICS4Middleware( + channel porttypes.ICS4Wrapper, + accountKeeper *authkeeper.AccountKeeper, contractKeeper *wasmkeeper.PermissionedKeeper, + bankKeeper *bankkeeper.BaseKeeper, ibcratelimitkeeper *keeper.Keeper, +) ICS4Wrapper { + return ICS4Wrapper{ + channel: channel, + accountKeeper: accountKeeper, + ContractKeeper: contractKeeper, + bankKeeper: bankKeeper, + IbcratelimitKeeper: ibcratelimitkeeper, + } +} + +// SendPacket implements the ICS4 interface and is called when sending packets. +// This method retrieves the contract from the middleware's parameters and checks if the limits have been exceeded for +// the current transfer, in which case it returns an error preventing the IBC send from taking place. +// If the contract param is not configured, or the contract doesn't have a configuration for the (channel+denom) being +// used, transfers are not prevented and handled by the wrapped IBC app +func (i *ICS4Wrapper) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { + var packetdata transfertypes.FungibleTokenPacketData + if err := json.Unmarshal(data, &packetdata); err != nil { + return i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) + } + if packetdata.Denom == "" || packetdata.Amount == "" { + return i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) + } + contract := i.GetContractAddress(ctx) + if contract == "" { + // The contract has not been configured. Continue as usual + return i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) + } + + // setting 0 as a default so it can be properly parsed by cosmwasm + fullPacket := channeltypes.Packet{ + Sequence: 0, + SourcePort: sourcePort, + SourceChannel: sourceChannel, + DestinationPort: "omitted", + DestinationChannel: "omitted", + Data: data, + TimeoutTimestamp: timeoutTimestamp, + TimeoutHeight: timeoutHeight, + } + + err := CheckAndUpdateRateLimits(ctx, i.ContractKeeper, msgSend, contract, fullPacket) + if err != nil { + return 0, errorsmod.Wrap(err, "rate limit SendPacket failed to authorize transfer") + } + + return i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) +} + +func (i *ICS4Wrapper) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, ack exported.Acknowledgement) error { + return i.channel.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +func (i *ICS4Wrapper) GetContractAddress(ctx sdk.Context) (contract string) { + return i.GetParams(ctx).ContractAddress +} + +func (i *ICS4Wrapper) GetParams(ctx sdk.Context) (params types.Params) { + params = i.IbcratelimitKeeper.GetParams(ctx) + return params +} + +func (i *ICS4Wrapper) SetParams(ctx sdk.Context, params types.Params) error { + return i.IbcratelimitKeeper.SetParams(ctx, params) +} diff --git a/x/ibc-rate-limit/keeper/grpc_query_params.go b/x/ibc-rate-limit/keeper/grpc_query_params.go new file mode 100644 index 000000000..99a9571cb --- /dev/null +++ b/x/ibc-rate-limit/keeper/grpc_query_params.go @@ -0,0 +1,20 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(c) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/x/ibc-rate-limit/keeper/keeper.go b/x/ibc-rate-limit/keeper/keeper.go new file mode 100644 index 000000000..a726c3ef1 --- /dev/null +++ b/x/ibc-rate-limit/keeper/keeper.go @@ -0,0 +1,60 @@ +package keeper + +import ( + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +// Keeper of the globalfee store +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/adminmodule module account. + authority string +} + +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + authority string, +) Keeper { + return Keeper{ + cdc: cdc, + storeKey: key, + authority: authority, + } +} + +// GetAuthority returns the x/ibcratelimit module's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// GetParams returns the total set params. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.ParamsKey) + if bz == nil { + return params + } + + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the total set of params. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { + store := ctx.KVStore(k.storeKey) + bz, err := k.cdc.Marshal(¶ms) + if err != nil { + return err + } + + store.Set(types.ParamsKey, bz) + return nil +} diff --git a/x/ibc-rate-limit/keeper/msg_server.go b/x/ibc-rate-limit/keeper/msg_server.go new file mode 100644 index 000000000..1f7e864bc --- /dev/null +++ b/x/ibc-rate-limit/keeper/msg_server.go @@ -0,0 +1,41 @@ +package keeper + +import ( + "context" + + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} + +// UpdateParams updates the module parameters +func (k Keeper) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if err := req.Validate(); err != nil { + return nil, errors.Wrap(err, "failed to validate MsgUpdateParams") + } + authority := k.GetAuthority() + if authority != req.Authority { + return nil, errors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid authority; expected %s, got %s", authority, req.Authority) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.SetParams(ctx, req.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/x/ibc-rate-limit/module.go b/x/ibc-rate-limit/module.go new file mode 100644 index 000000000..aa794bf49 --- /dev/null +++ b/x/ibc-rate-limit/module.go @@ -0,0 +1,145 @@ +package ibcratelimit + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/keeper" + "github.com/spf13/cobra" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + "cosmossdk.io/core/appmodule" + + ibcratelimitcli "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/client/cli" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ module.HasGenesisBasics = AppModuleBasic{} + + _ appmodule.AppModule = AppModule{} + _ module.HasConsensusVersion = AppModule{} + _ module.HasGenesis = AppModule{} + _ module.HasServices = AppModule{} +) + +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +func (AppModuleBasic) Name() string { return types.ModuleName } + +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis performs genesis state validation for the ibcratelimit module. +func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() +} + +// --------------------------------------- +// Interfaces. +func (b AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck +} + +func (b AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +func (b AppModuleBasic) GetQueryCmd() *cobra.Command { + return ibcratelimitcli.GetQueryCmd() +} + +// RegisterInterfaces registers interfaces and implementations of the ibc-rate-limit module. +func (AppModuleBasic) RegisterInterfaces(reg codectypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface for the capability module. +type AppModule struct { + AppModuleBasic + + ics4wrapper *ICS4Wrapper + keeper *keeper.Keeper +} + +func NewAppModule( + cdc codec.Codec, + keeper *keeper.Keeper, + ics4wrapper *ICS4Wrapper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + ics4wrapper: ics4wrapper, + } +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// IsOnePerModuleType is a marker function just indicates that this is a one-per-module type. +func (am AppModule) IsOnePerModuleType() {} + +// Name returns the txfees module's name. +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// QuerierRoute returns the ibc-rate-limit module's query routing key. +func (AppModule) QuerierRoute() string { return types.RouterKey } + +// RegisterServices registers a GRPC query service to respond to the +// module-specific GRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(*am.keeper)) +} + +// RegisterInvariants registers the txfees module's invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the txfees module's genesis initialization It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(gs, &genState) + am.ics4wrapper.InitGenesis(ctx, genState) +} + +// ExportGenesis returns the txfees module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := am.ics4wrapper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } diff --git a/x/ibc-rate-limit/module_simulation.go b/x/ibc-rate-limit/module_simulation.go new file mode 100644 index 000000000..0bc601524 --- /dev/null +++ b/x/ibc-rate-limit/module_simulation.go @@ -0,0 +1,48 @@ +package ibcratelimit + +import ( + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/neutron-org/neutron/v5/testutil/common/sample" + ibcratelimitsimulation "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/simulation" + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +// avoid unused import issue +var ( + _ = sample.AccAddress + _ = ibcratelimitsimulation.FindAccount + _ = sims.StakePerAccount + _ = simulation.MsgEntryKind + _ = baseapp.Paramspace +) + +// GenerateGenesisState creates a randomized GenState of the module +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + accs := make([]string, len(simState.Accounts)) + for i, acc := range simState.Accounts { + accs[i] = acc.Address.String() + } + interchainqueriesGenesis := types.GenesisState{ + Params: types.DefaultParams(), + } + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&interchainqueriesGenesis) +} + +// ProposalContents doesn't return any content functions for governance proposals +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { + return nil +} + +// RegisterStoreDecoder registers a decoder +func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + operations := make([]simtypes.WeightedOperation, 0) + + return operations +} diff --git a/x/ibc-rate-limit/rate_limit.go b/x/ibc-rate-limit/rate_limit.go new file mode 100644 index 000000000..eb8d01f49 --- /dev/null +++ b/x/ibc-rate-limit/rate_limit.go @@ -0,0 +1,153 @@ +package ibcratelimit + +import ( + "encoding/json" + + errorsmod "cosmossdk.io/errors" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + + "github.com/neutron-org/neutron/v5/x/ibc-rate-limit/types" +) + +var ( + msgSend = "send_packet" + msgRecv = "recv_packet" +) + +func CheckAndUpdateRateLimits(ctx sdk.Context, contractKeeper *wasmkeeper.PermissionedKeeper, + msgType, contract string, packet exported.PacketI, +) error { + contractAddr, err := sdk.AccAddressFromBech32(contract) + if err != nil { + return err + } + + sendPacketMsg, err := BuildWasmExecMsg( + msgType, + packet, + ) + if err != nil { + return errorsmod.Wrap(err, "failed to CheckAndUpdateRateLimits") + } + + _, err = contractKeeper.Sudo(ctx, contractAddr, sendPacketMsg) + if err != nil { + return errorsmod.Wrap(types.ErrRateLimitExceeded, err.Error()) + } + + return nil +} + +type UndoSendMsg struct { + UndoSend UndoPacketMsg `json:"undo_send"` +} + +type UndoPacketMsg struct { + Packet UnwrappedPacket `json:"packet"` +} + +func UndoSendRateLimit(ctx sdk.Context, contractKeeper *wasmkeeper.PermissionedKeeper, + contract string, + packet exported.PacketI, +) error { + contractAddr, err := sdk.AccAddressFromBech32(contract) + if err != nil { + return errorsmod.Wrap(err, "failed to UndoSendRateLimit") + } + + unwrapped, err := unwrapPacket(packet) + if err != nil { + return errorsmod.Wrap(err, "failed to UndoSendRateLimit") + } + + msg := UndoSendMsg{UndoSend: UndoPacketMsg{Packet: unwrapped}} + asJSON, err := json.Marshal(msg) + if err != nil { + return errorsmod.Wrap(err, "failed to UndoSendRateLimit") + } + + _, err = contractKeeper.Sudo(ctx, contractAddr, asJSON) + if err != nil { + return errorsmod.Wrap(types.ErrContractError, err.Error()) + } + + return nil +} + +type SendPacketMsg struct { + SendPacket PacketMsg `json:"send_packet"` +} + +type RecvPacketMsg struct { + RecvPacket PacketMsg `json:"recv_packet"` +} + +type PacketMsg struct { + Packet UnwrappedPacket `json:"packet"` +} + +type UnwrappedPacket struct { + Sequence uint64 `json:"sequence"` + SourcePort string `json:"source_port"` + SourceChannel string `json:"source_channel"` + DestinationPort string `json:"destination_port"` + DestinationChannel string `json:"destination_channel"` + Data transfertypes.FungibleTokenPacketData `json:"data"` + TimeoutHeight clienttypes.Height `json:"timeout_height"` + TimeoutTimestamp uint64 `json:"timeout_timestamp,omitempty"` +} + +func unwrapPacket(packet exported.PacketI) (UnwrappedPacket, error) { + var packetData transfertypes.FungibleTokenPacketData + err := json.Unmarshal(packet.GetData(), &packetData) + if err != nil { + return UnwrappedPacket{}, err + } + height, ok := packet.GetTimeoutHeight().(clienttypes.Height) + if !ok { + return UnwrappedPacket{}, types.ErrBadMessage + } + return UnwrappedPacket{ + Sequence: packet.GetSequence(), + SourcePort: packet.GetSourcePort(), + SourceChannel: packet.GetSourceChannel(), + DestinationPort: packet.GetDestPort(), + DestinationChannel: packet.GetDestChannel(), + Data: packetData, + TimeoutHeight: height, + TimeoutTimestamp: packet.GetTimeoutTimestamp(), + }, nil +} + +func BuildWasmExecMsg(msgType string, packet exported.PacketI) ([]byte, error) { + unwrapped, err := unwrapPacket(packet) + if err != nil { + return []byte{}, err + } + + var asJSON []byte + switch { + case msgType == msgSend: + msg := SendPacketMsg{SendPacket: PacketMsg{ + Packet: unwrapped, + }} + asJSON, err = json.Marshal(msg) + case msgType == msgRecv: + msg := RecvPacketMsg{RecvPacket: PacketMsg{ + Packet: unwrapped, + }} + asJSON, err = json.Marshal(msg) + default: + return []byte{}, types.ErrBadMessage + } + + if err != nil { + return []byte{}, errorsmod.Wrap(err, "failed to BuildWasmExecMsg") + } + + return asJSON, nil +} diff --git a/x/ibc-rate-limit/simulation/simap.go b/x/ibc-rate-limit/simulation/simap.go new file mode 100644 index 000000000..92c437c0d --- /dev/null +++ b/x/ibc-rate-limit/simulation/simap.go @@ -0,0 +1,15 @@ +package simulation + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// FindAccount find a specific address from an account list +func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { + creator, err := sdk.AccAddressFromBech32(address) + if err != nil { + panic(err) + } + return simtypes.FindAccount(accs, creator) +} diff --git a/x/ibc-rate-limit/types/codec.go b/x/ibc-rate-limit/types/codec.go new file mode 100644 index 000000000..e1725d89d --- /dev/null +++ b/x/ibc-rate-limit/types/codec.go @@ -0,0 +1,25 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgUpdateParams{}, "neutron/ibc-rate-limit/update-params", nil) +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgUpdateParams{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc *codec.ProtoCodec +) diff --git a/x/ibc-rate-limit/types/constants.go b/x/ibc-rate-limit/types/constants.go new file mode 100644 index 000000000..4d4fce0bd --- /dev/null +++ b/x/ibc-rate-limit/types/constants.go @@ -0,0 +1,6 @@ +package types + +const ( + MaxSupportedIBCReceiverAddressLength = 4096 + RateLimitExceededSubStr = "rate limit exceeded" +) diff --git a/x/ibc-rate-limit/types/errors.go b/x/ibc-rate-limit/types/errors.go new file mode 100644 index 000000000..42116a7e1 --- /dev/null +++ b/x/ibc-rate-limit/types/errors.go @@ -0,0 +1,11 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +var ( + ErrRateLimitExceeded = errorsmod.Register(ModuleName, 2, "rate limit exceeded") + ErrBadMessage = errorsmod.Register(ModuleName, 3, "bad message") + ErrContractError = errorsmod.Register(ModuleName, 4, "contract error") +) diff --git a/x/ibc-rate-limit/types/events.go b/x/ibc-rate-limit/types/events.go new file mode 100644 index 000000000..36d31181e --- /dev/null +++ b/x/ibc-rate-limit/types/events.go @@ -0,0 +1,8 @@ +package types + +const ( + EventBadRevert = "bad_revert" + AttributeKeyPacket = "packet" + AttributeKeyAck = "acknowledgement" + AttributeKeyFailureType = "failure_type" +) diff --git a/x/ibc-rate-limit/types/genesis.go b/x/ibc-rate-limit/types/genesis.go new file mode 100644 index 000000000..9167936c6 --- /dev/null +++ b/x/ibc-rate-limit/types/genesis.go @@ -0,0 +1,17 @@ +package types + +// DefaultGenesis creates a default GenesisState object. +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + return nil +} diff --git a/x/ibc-rate-limit/types/genesis.pb.go b/x/ibc-rate-limit/types/genesis.pb.go new file mode 100644 index 000000000..735122fb7 --- /dev/null +++ b/x/ibc-rate-limit/types/genesis.pb.go @@ -0,0 +1,329 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: neutron/ibcratelimit/v1beta1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc-rate-limit module's genesis state. +type GenesisState struct { + // params are all the parameters of the module + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_4a6a285b43c9c3fe, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "neutron.ibcratelimit.v1beta1.GenesisState") +} + +func init() { + proto.RegisterFile("neutron/ibcratelimit/v1beta1/genesis.proto", fileDescriptor_4a6a285b43c9c3fe) +} + +var fileDescriptor_4a6a285b43c9c3fe = []byte{ + // 250 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xca, 0x4b, 0x2d, 0x2d, + 0x29, 0xca, 0xcf, 0xd3, 0xcf, 0x4c, 0x4a, 0x2e, 0x4a, 0x2c, 0x49, 0xcd, 0xc9, 0xcc, 0xcd, 0x2c, + 0xd1, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, + 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x81, 0xaa, 0xd5, 0x43, 0x56, 0xab, 0x07, 0x55, + 0x2b, 0x25, 0x99, 0x9c, 0x5f, 0x9c, 0x9b, 0x5f, 0x1c, 0x0f, 0x56, 0xab, 0x0f, 0xe1, 0x40, 0x34, + 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x43, 0xc4, 0x41, 0x2c, 0xa8, 0xa8, 0x64, 0x7a, 0x7e, 0x7e, + 0x7a, 0x4e, 0xaa, 0x3e, 0x98, 0x97, 0x54, 0x9a, 0xa6, 0x9f, 0x98, 0x57, 0x09, 0x95, 0xd2, 0xc4, + 0xeb, 0xaa, 0x82, 0xc4, 0xa2, 0xc4, 0x5c, 0xa8, 0xd9, 0x4a, 0x41, 0x5c, 0x3c, 0xee, 0x10, 0x57, + 0x06, 0x97, 0x24, 0x96, 0xa4, 0x0a, 0x39, 0x71, 0xb1, 0x41, 0xe4, 0x25, 0x18, 0x15, 0x18, 0x35, + 0xb8, 0x8d, 0x54, 0xf4, 0xf0, 0xb9, 0x5a, 0x2f, 0x00, 0xac, 0xd6, 0x89, 0xe5, 0xc4, 0x3d, 0x79, + 0x86, 0x20, 0xa8, 0x4e, 0xa7, 0xa0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, + 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, + 0xb2, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, 0x9a, 0xab, 0x9b, + 0x5f, 0x94, 0x0e, 0x63, 0xeb, 0x97, 0x99, 0xea, 0x57, 0x80, 0x1c, 0xad, 0x0b, 0xb2, 0x49, 0x17, + 0xe2, 0xec, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0x73, 0x8d, 0x01, 0x01, 0x00, 0x00, + 0xff, 0xff, 0xc8, 0xd2, 0x7b, 0x68, 0x71, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-rate-limit/types/keys.go b/x/ibc-rate-limit/types/keys.go new file mode 100644 index 000000000..1ce3db024 --- /dev/null +++ b/x/ibc-rate-limit/types/keys.go @@ -0,0 +1,18 @@ +package types + +import "strings" + +const ( + prefixParamsKey = iota + 1 +) + +const ( + ModuleName = "rate-limited-ibc" // IBC at the end to avoid conflicts with the ibc prefix + +) + +var ParamsKey = []byte{prefixParamsKey} + +// RouterKey is the message route. Can only contain +// alphanumeric characters. +var RouterKey = strings.ReplaceAll(ModuleName, "-", "") diff --git a/x/ibc-rate-limit/types/params.go b/x/ibc-rate-limit/types/params.go new file mode 100644 index 000000000..35b172863 --- /dev/null +++ b/x/ibc-rate-limit/types/params.go @@ -0,0 +1,73 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// Parameter store keys. +var ( + KeyContractAddress = []byte("contract") + + _ paramtypes.ParamSet = &Params{} +) + +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +func NewParams(contractAddress string) (Params, error) { + return Params{ + ContractAddress: contractAddress, + }, nil +} + +// default gamm module parameters. +func DefaultParams() Params { + return Params{ + ContractAddress: "", + } +} + +// validate params. +func (p Params) Validate() error { + if err := validateContractAddress(p.ContractAddress); err != nil { + return err + } + + return nil +} + +// Implements params.ParamSet. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyContractAddress, &p.ContractAddress, validateContractAddress), + } +} + +func validateContractAddress(i interface{}) error { + v, ok := i.(string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + // Empty strings are valid for unsetting the param + if v == "" { + return nil + } + + // Checks that the contract address is valid + bech32, err := sdk.AccAddressFromBech32(v) + if err != nil { + return err + } + + err = sdk.VerifyAddressFormat(bech32) + if err != nil { + return err + } + + return nil +} diff --git a/x/ibc-rate-limit/types/params.pb.go b/x/ibc-rate-limit/types/params.pb.go new file mode 100644 index 000000000..81ce2f24f --- /dev/null +++ b/x/ibc-rate-limit/types/params.pb.go @@ -0,0 +1,322 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: neutron/ibcratelimit/v1beta1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the ibc-rate-limit module. +type Params struct { + ContractAddress string `protobuf:"bytes,1,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty" yaml:"contract_address"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_96b2a3ecd8a27c06, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetContractAddress() string { + if m != nil { + return m.ContractAddress + } + return "" +} + +func init() { + proto.RegisterType((*Params)(nil), "neutron.ibcratelimit.v1beta1.Params") +} + +func init() { + proto.RegisterFile("neutron/ibcratelimit/v1beta1/params.proto", fileDescriptor_96b2a3ecd8a27c06) +} + +var fileDescriptor_96b2a3ecd8a27c06 = []byte{ + // 222 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xcc, 0x4b, 0x2d, 0x2d, + 0x29, 0xca, 0xcf, 0xd3, 0xcf, 0x4c, 0x4a, 0x2e, 0x4a, 0x2c, 0x49, 0xcd, 0xc9, 0xcc, 0xcd, 0x2c, + 0xd1, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x81, 0x2a, 0xd5, 0x43, 0x56, 0xaa, 0x07, 0x55, 0x2a, + 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa8, 0x0f, 0x62, 0x41, 0xf4, 0x28, 0x85, 0x70, 0xb1, + 0x05, 0x80, 0xcd, 0x10, 0xf2, 0xe2, 0x12, 0x48, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x89, + 0x4f, 0x4c, 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x74, 0x92, 0x3f, + 0x71, 0x4f, 0x9e, 0xf1, 0xd3, 0x3d, 0x79, 0xf1, 0xca, 0xc4, 0xdc, 0x1c, 0x2b, 0x25, 0x74, 0x55, + 0x4a, 0x41, 0xfc, 0x30, 0x21, 0x47, 0x88, 0x88, 0x53, 0xd0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, + 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, + 0x1e, 0xcb, 0x31, 0x44, 0x59, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, + 0x43, 0x9d, 0xab, 0x9b, 0x5f, 0x94, 0x0e, 0x63, 0xeb, 0x97, 0x99, 0xea, 0x57, 0x80, 0xbc, 0xaa, + 0x0b, 0xf2, 0x80, 0x2e, 0xc4, 0xb3, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x07, 0x1b, + 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x65, 0xd8, 0x69, 0x11, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ContractAddress) > 0 { + i -= len(m.ContractAddress) + copy(dAtA[i:], m.ContractAddress) + i = encodeVarintParams(dAtA, i, uint64(len(m.ContractAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ContractAddress) + if l > 0 { + n += 1 + l + sovParams(uint64(l)) + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContractAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-rate-limit/types/params_test.go b/x/ibc-rate-limit/types/params_test.go new file mode 100644 index 000000000..f37e4d886 --- /dev/null +++ b/x/ibc-rate-limit/types/params_test.go @@ -0,0 +1,79 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateContractAddress(t *testing.T) { + testCases := map[string]struct { + addr interface{} + expected bool + }{ + // ToDo: Why do tests expect the bech32 prefix to be cosmos? + "valid_addr": { + addr: "cosmos1qm0hhug8kszhcp9f3ryuecz5yw8s3e5v0n2ckd", + expected: true, + }, + "invalid_addr": { + addr: "cosmos1234", + expected: false, + }, + "invalid parameter type": { + addr: 123456, + expected: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := validateContractAddress(tc.addr) + + // Assertions. + if !tc.expected { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestValidateParams(t *testing.T) { + testCases := map[string]struct { + addr interface{} + expected bool + }{ + // ToDo: Why do tests expect the bech32 prefix to be cosmos? + "valid_addr": { + addr: "cosmos1qm0hhug8kszhcp9f3ryuecz5yw8s3e5v0n2ckd", + expected: true, + }, + "invalid_addr": { + addr: "cosmos1234", + expected: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + addr, ok := tc.addr.(string) + require.True(t, ok, "unexpected type of address") + + params := Params{ + ContractAddress: addr, + } + + err := params.Validate() + + // Assertions. + if !tc.expected { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/ibc-rate-limit/types/query.pb.go b/x/ibc-rate-limit/types/query.pb.go new file mode 100644 index 000000000..f794ecbd4 --- /dev/null +++ b/x/ibc-rate-limit/types/query.pb.go @@ -0,0 +1,542 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: neutron/ibcratelimit/v1beta1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a6095f726b1d3aec, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// aramsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a6095f726b1d3aec, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "neutron.ibcratelimit.v1beta1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "neutron.ibcratelimit.v1beta1.QueryParamsResponse") +} + +func init() { + proto.RegisterFile("neutron/ibcratelimit/v1beta1/query.proto", fileDescriptor_a6095f726b1d3aec) +} + +var fileDescriptor_a6095f726b1d3aec = []byte{ + // 318 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0xcd, 0x4a, 0x33, 0x31, + 0x14, 0x86, 0x27, 0x1f, 0x9f, 0x5d, 0x8c, 0xbb, 0xd8, 0x85, 0x94, 0x12, 0xa5, 0x88, 0x54, 0xa1, + 0x89, 0xad, 0x08, 0xae, 0x7b, 0x05, 0xda, 0x9d, 0xee, 0x32, 0x25, 0xc4, 0x40, 0x27, 0x27, 0x4d, + 0x32, 0xc5, 0x6e, 0xbd, 0x02, 0xc1, 0xb5, 0x6b, 0x6f, 0xa5, 0xcb, 0x82, 0x1b, 0x57, 0x22, 0xad, + 0x17, 0x22, 0x9d, 0xc4, 0x52, 0x7f, 0x18, 0x70, 0x77, 0x38, 0x79, 0x9f, 0x87, 0x37, 0x27, 0x6d, + 0x6b, 0x51, 0x78, 0x0b, 0x9a, 0xa9, 0x6c, 0x68, 0xb9, 0x17, 0x23, 0x95, 0x2b, 0xcf, 0x26, 0xdd, + 0x4c, 0x78, 0xde, 0x65, 0xe3, 0x42, 0xd8, 0x29, 0x35, 0x16, 0x3c, 0xe0, 0x66, 0x4c, 0xd2, 0xcd, + 0x24, 0x8d, 0xc9, 0xc6, 0xf1, 0x10, 0x5c, 0x0e, 0x8e, 0x65, 0xdc, 0x89, 0x80, 0xad, 0x25, 0x86, + 0x4b, 0xa5, 0xb9, 0x57, 0xa0, 0x83, 0xa9, 0x51, 0x97, 0x20, 0xa1, 0x1c, 0xd9, 0x6a, 0x8a, 0xdb, + 0xa6, 0x04, 0x90, 0x23, 0xc1, 0xb8, 0x51, 0x8c, 0x6b, 0x0d, 0xbe, 0x44, 0x5c, 0x7c, 0x3d, 0xaa, + 0xec, 0x69, 0xb8, 0xe5, 0x79, 0x8c, 0xb6, 0xea, 0x29, 0xbe, 0x5c, 0x15, 0xb8, 0x28, 0x97, 0x03, + 0x31, 0x2e, 0x84, 0xf3, 0xad, 0xab, 0x74, 0xe7, 0xcb, 0xd6, 0x19, 0xd0, 0x4e, 0xe0, 0x7e, 0x5a, + 0x0b, 0xf0, 0x2e, 0xda, 0x47, 0xed, 0xed, 0xde, 0x01, 0xad, 0xfa, 0x26, 0x0d, 0x74, 0xff, 0xff, + 0xec, 0x75, 0x2f, 0x19, 0x44, 0xb2, 0xf7, 0x84, 0xd2, 0xad, 0xd2, 0x8d, 0x1f, 0x51, 0x5a, 0x0b, + 0x11, 0x7c, 0x52, 0x2d, 0xfa, 0xd9, 0xb0, 0xd1, 0xfd, 0x03, 0x11, 0xda, 0xb7, 0xe8, 0xdd, 0xf3, + 0xfb, 0xc3, 0xbf, 0x36, 0x3e, 0x64, 0x1b, 0xe7, 0xe9, 0xac, 0xd8, 0xce, 0x6f, 0x07, 0xea, 0x0f, + 0x66, 0x0b, 0x82, 0xe6, 0x0b, 0x82, 0xde, 0x16, 0x04, 0xdd, 0x2f, 0x49, 0x32, 0x5f, 0x92, 0xe4, + 0x65, 0x49, 0x92, 0xeb, 0x73, 0xa9, 0xfc, 0x4d, 0x91, 0xd1, 0x21, 0xe4, 0x9f, 0xae, 0x0e, 0x58, + 0xb9, 0xf6, 0x4e, 0xce, 0xd8, 0xed, 0x77, 0xb9, 0x9f, 0x1a, 0xe1, 0xb2, 0x5a, 0x79, 0xf5, 0xd3, + 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xee, 0xd8, 0x97, 0x25, 0x4a, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params defines a gRPC query method that returns the ibc-rate-limit module's + // parameters. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/neutron.ibcratelimit.v1beta1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params defines a gRPC query method that returns the ibc-rate-limit module's + // parameters. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutron.ibcratelimit.v1beta1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "neutron.ibcratelimit.v1beta1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "neutron/ibcratelimit/v1beta1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-rate-limit/types/query.pb.gw.go b/x/ibc-rate-limit/types/query.pb.gw.go new file mode 100644 index 000000000..1e18d9032 --- /dev/null +++ b/x/ibc-rate-limit/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: neutron/ibcratelimit/v1beta1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"neutron", "ibc-rate-limit", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/ibc-rate-limit/types/tx.go b/x/ibc-rate-limit/types/tx.go new file mode 100644 index 000000000..ec4026dc7 --- /dev/null +++ b/x/ibc-rate-limit/types/tx.go @@ -0,0 +1,44 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ sdk.Msg = &MsgUpdateParams{} + +func (msg *MsgUpdateParams) Route() string { + return RouterKey +} + +func (msg *MsgUpdateParams) Type() string { + return "update-params" +} + +func (msg *MsgUpdateParams) GetSigners() []sdk.AccAddress { + authority, err := sdk.AccAddressFromBech32(msg.Authority) + if err != nil { // should never happen as valid basic rejects invalid addresses + panic(err.Error()) + } + return []sdk.AccAddress{authority} +} + +func (msg *MsgUpdateParams) GetSignBytes() []byte { + return ModuleCdc.MustMarshalJSON(msg) +} + +func (msg *MsgUpdateParams) Validate() error { + if _, err := sdk.AccAddressFromBech32(msg.Authority); err != nil { + return errorsmod.Wrap(err, "authority is invalid") + } + + // we allow unsetting the contract + if msg.Params.ContractAddress == "" { + return nil + } + if _, err := sdk.AccAddressFromBech32(msg.Params.ContractAddress); err != nil { + return errorsmod.Wrap(err, "contract_address is invalid") + } + + return nil +} diff --git a/x/ibc-rate-limit/types/tx.pb.go b/x/ibc-rate-limit/types/tx.pb.go new file mode 100644 index 000000000..ddf4576ad --- /dev/null +++ b/x/ibc-rate-limit/types/tx.pb.go @@ -0,0 +1,605 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: neutron/ibcratelimit/v1beta1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/cosmos-sdk/x/bank/types" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgUpdateParams is the MsgUpdateParams request type. +// +// Since: 0.47 +type MsgUpdateParams struct { + // Authority is the address of the governance account. + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params defines the x/tokenfactory parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_88b553b0b85135fe, []int{0} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +// +// Since: 0.47 +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_88b553b0b85135fe, []int{1} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgUpdateParams)(nil), "neutron.ibcratelimit.v1beta1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "neutron.ibcratelimit.v1beta1.MsgUpdateParamsResponse") +} + +func init() { + proto.RegisterFile("neutron/ibcratelimit/v1beta1/tx.proto", fileDescriptor_88b553b0b85135fe) +} + +var fileDescriptor_88b553b0b85135fe = []byte{ + // 390 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcd, 0x4b, 0x2d, 0x2d, + 0x29, 0xca, 0xcf, 0xd3, 0xcf, 0x4c, 0x4a, 0x2e, 0x4a, 0x2c, 0x49, 0xcd, 0xc9, 0xcc, 0xcd, 0x2c, + 0xd1, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x92, 0x81, 0x2a, 0xd3, 0x43, 0x56, 0xa6, 0x07, 0x55, 0x26, 0x25, 0x98, 0x98, 0x9b, + 0x99, 0x97, 0xaf, 0x0f, 0x26, 0x21, 0x1a, 0xa4, 0xe4, 0x92, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xf5, + 0x93, 0x12, 0xf3, 0xb2, 0xe1, 0xc6, 0x81, 0x38, 0x18, 0xf2, 0xc5, 0xa9, 0x70, 0xf9, 0xe4, 0xfc, + 0xcc, 0x3c, 0xa8, 0xbc, 0x38, 0x54, 0x3e, 0xb7, 0x38, 0x5d, 0xbf, 0xcc, 0x10, 0x44, 0x41, 0x25, + 0x24, 0x21, 0x12, 0xf1, 0x60, 0x9e, 0x3e, 0x84, 0x03, 0x95, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x87, + 0x88, 0x83, 0x58, 0x50, 0x51, 0x4d, 0xbc, 0x3e, 0x2c, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa0, + 0x74, 0x96, 0x91, 0x8b, 0xdf, 0xb7, 0x38, 0x3d, 0xb4, 0x20, 0x25, 0xb1, 0x24, 0x35, 0x00, 0x2c, + 0x23, 0x64, 0xc6, 0xc5, 0x99, 0x58, 0x5a, 0x92, 0x91, 0x5f, 0x94, 0x59, 0x52, 0x29, 0xc1, 0xa8, + 0xc0, 0xa8, 0xc1, 0xe9, 0x24, 0x71, 0x69, 0x8b, 0xae, 0x08, 0xd4, 0x66, 0xc7, 0x94, 0x94, 0xa2, + 0xd4, 0xe2, 0xe2, 0xe0, 0x92, 0xa2, 0xcc, 0xbc, 0xf4, 0x20, 0x84, 0x52, 0x21, 0x77, 0x2e, 0x36, + 0x88, 0xd9, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x2a, 0x7a, 0xf8, 0x82, 0x50, 0x0f, 0x62, + 0x9b, 0x13, 0xe7, 0x89, 0x7b, 0xf2, 0x0c, 0x2b, 0x9e, 0x6f, 0xd0, 0x62, 0x0c, 0x82, 0x6a, 0xb7, + 0xb2, 0x6c, 0x7a, 0xbe, 0x41, 0x0b, 0x61, 0x70, 0xd7, 0xf3, 0x0d, 0x5a, 0x6a, 0x48, 0x5e, 0xd2, + 0x05, 0x99, 0xa5, 0x0b, 0xf1, 0x14, 0x9a, 0xdb, 0x95, 0x24, 0xb9, 0xc4, 0xd1, 0x84, 0x82, 0x52, + 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x8d, 0x9a, 0x18, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x85, 0x4a, + 0xb8, 0x78, 0x50, 0xbc, 0xab, 0x8b, 0xdf, 0x99, 0x68, 0xc6, 0x49, 0x99, 0x92, 0xa4, 0x1c, 0x66, + 0xbb, 0x14, 0x6b, 0x03, 0xc8, 0x8b, 0x4e, 0x41, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, + 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, + 0xc7, 0x10, 0x65, 0x91, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0xb5, + 0x41, 0x37, 0xbf, 0x28, 0x1d, 0xc6, 0xd6, 0x2f, 0x33, 0xd5, 0xaf, 0x40, 0xf7, 0x7d, 0x49, 0x65, + 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x38, 0x2a, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd2, 0xe4, + 0xe9, 0x9d, 0xd9, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/neutron.ibcratelimit.v1beta1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutron.ibcratelimit.v1beta1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "neutron.ibcratelimit.v1beta1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "neutron/ibcratelimit/v1beta1/tx.proto", +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index d86ecfe2f..3c911599c 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -32,7 +32,6 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P } im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - msg, err := keeper.PrepareSudoCallbackMessage(packet, &ack) if err != nil { return errors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal Packet/Acknowledgment: %v", err)