From 0fb54fc162b9a3d31d00f6fd0c62bfaa203b0da6 Mon Sep 17 00:00:00 2001 From: swelf Date: Fri, 18 Aug 2023 17:26:09 +0300 Subject: [PATCH 01/18] ictx + transfer with limited sudo call --- app/upgrades/sdk47/upgrades.go | 10 ++ app/upgrades/types.go | 2 + go.sum | 3 + network/init-neutrond.sh | 2 +- proto/neutron/contractmanager/params.proto | 5 +- proto/neutron/cron/genesis.proto | 6 +- proto/neutron/cron/params.proto | 10 +- proto/neutron/cron/query.proto | 40 +++--- proto/neutron/cron/schedule.proto | 28 ++--- proto/neutron/cron/tx.proto | 3 +- proto/neutron/transfer/v1/tx.proto | 3 +- .../osmosis/tokenfactory/v1beta1/params.proto | 18 +-- .../osmosis/tokenfactory/v1beta1/query.proto | 3 +- .../interchaintxs/types/expected_keepers.go | 14 +++ .../mocks/transfer/types/expected_keepers.go | 23 +++- x/contractmanager/keeper/params.go | 11 +- x/contractmanager/types/params.go | 6 +- x/contractmanager/types/params.pb.go | 52 ++++++-- x/interchaintxs/keeper/ibc_handlers.go | 114 ++++++++--------- x/interchaintxs/keeper/ibc_handlers_test.go | 47 ++++--- x/interchaintxs/types/expected_keepers.go | 1 + x/tokenfactory/types/params.pb.go | 6 +- x/transfer/ibc_handlers.go | 118 ++++++++---------- x/transfer/ibc_handlers_test.go | 71 +++++++---- x/transfer/types/expected_keepers.go | 2 + 25 files changed, 346 insertions(+), 252 deletions(-) diff --git a/app/upgrades/sdk47/upgrades.go b/app/upgrades/sdk47/upgrades.go index b962ec819..9c92d08f0 100644 --- a/app/upgrades/sdk47/upgrades.go +++ b/app/upgrades/sdk47/upgrades.go @@ -12,6 +12,7 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" v6 "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/migrations/v6" "github.com/neutron-org/neutron/app/upgrades" + contractmanagermoduletypes "github.com/neutron-org/neutron/x/contractmanager/types" crontypes "github.com/neutron-org/neutron/x/cron/types" feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" @@ -91,6 +92,15 @@ func CreateUpgradeHandler( return nil, err } + ctx.Logger().Info("Setting sudo callback limit...") + cmParams := contractmanagermoduletypes.Params{ + SudoCallGasLimit: 1_000_000, + } + err = keepers.ContractManager.SetParams(ctx, cmParams) + if err != nil { + return nil, err + } + ctx.Logger().Info("Upgrade complete") return vm, nil } diff --git a/app/upgrades/types.go b/app/upgrades/types.go index 91fe12f10..de1f24b33 100644 --- a/app/upgrades/types.go +++ b/app/upgrades/types.go @@ -8,6 +8,7 @@ import ( paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + contractmanagerkeeper "github.com/neutron-org/neutron/x/contractmanager/keeper" cronkeeper "github.com/neutron-org/neutron/x/cron/keeper" feeburnerkeeper "github.com/neutron-org/neutron/x/feeburner/keeper" icqkeeper "github.com/neutron-org/neutron/x/interchainqueries/keeper" @@ -42,6 +43,7 @@ type UpgradeKeepers struct { ParamsKeeper paramskeeper.Keeper CapabilityKeeper *capabilitykeeper.Keeper BuilderKeeper builderkeeper.Keeper + ContractManager contractmanagerkeeper.Keeper // subspaces GlobalFeeSubspace paramtypes.Subspace } diff --git a/go.sum b/go.sum index 71b2262b1..cb32bfa60 100644 --- a/go.sum +++ b/go.sum @@ -796,6 +796,7 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= @@ -908,6 +909,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= @@ -928,6 +930,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= github.com/neutron-org/admin-module v0.0.0-20230705134325-b23404470a1d h1:oexw79znoA0TEo7CGdWHrolbvZqCDD3aI+031CbOq9Y= github.com/neutron-org/admin-module v0.0.0-20230705134325-b23404470a1d/go.mod h1:QuxQ7FJlEAFMRssyEYOrR9ORnYQvBFMTlO8BXny6ntw= +github.com/neutron-org/wasmd v0.40.0-rc.0.0.20230808084410-6083b888424e h1:uVJCBWf1vcCYY0pzOA2SCPIZT8WsR8fsOxs57mnJbM4= github.com/neutron-org/wasmd v0.40.0-rc.0.0.20230808084410-6083b888424e/go.mod h1:Oagy36cU49438NzxKG/gmGTG903tiAI7LIUdH7x2qNY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/network/init-neutrond.sh b/network/init-neutrond.sh index f656840a5..f16144a62 100755 --- a/network/init-neutrond.sh +++ b/network/init-neutrond.sh @@ -627,9 +627,9 @@ set_genesis_param min_signed_per_window "\"$SLASHING_MIN_SIGNED\"," set_genesis_param slash_fraction_double_sign "\"$SLASHING_FRACTION_DOUBLE_SIGN\"," # slashing set_genesis_param slash_fraction_downtime "\"$SLASHING_FRACTION_DOWNTIME\"" # slashing set_genesis_param minimum_gas_prices "$MIN_GAS_PRICES" # globalfee - set_genesis_param proposer_fee "\"0.25\"" # builder(POB) set_genesis_param escrow_account_address "\"$DAO_CONTRACT_ADDRESS_B64\"," # builder(POB) +set_genesis_param sudo_call_gas_limit "\"1000000\"" # contractmanager if ! jq -e . "$GENESIS_PATH" >/dev/null 2>&1; then echo "genesis appears to become incorrect json" >&2 diff --git a/proto/neutron/contractmanager/params.proto b/proto/neutron/contractmanager/params.proto index 39925ae90..c7f410c0b 100644 --- a/proto/neutron/contractmanager/params.proto +++ b/proto/neutron/contractmanager/params.proto @@ -6,4 +6,7 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/neutron-org/neutron/x/contractmanager/types"; // Params defines the parameters for the module. -message Params { option (gogoproto.goproto_stringer) = false; } +message Params { + option (gogoproto.goproto_stringer) = false; + uint64 sudo_call_gas_limit = 1; +} diff --git a/proto/neutron/cron/genesis.proto b/proto/neutron/cron/genesis.proto index e7e455276..2045b3c66 100644 --- a/proto/neutron/cron/genesis.proto +++ b/proto/neutron/cron/genesis.proto @@ -10,7 +10,7 @@ option go_package = "github.com/neutron-org/neutron/x/cron/types"; // GenesisState defines the cron module's genesis state. message GenesisState { - repeated Schedule scheduleList = 2 [(gogoproto.nullable) = false]; - Params params = 1 [ (gogoproto.nullable) = false ]; - // this line is used by starport scaffolding # genesis/proto/state + repeated Schedule scheduleList = 2 [ (gogoproto.nullable) = false ]; + Params params = 1 [ (gogoproto.nullable) = false ]; + // this line is used by starport scaffolding # genesis/proto/state } diff --git a/proto/neutron/cron/params.proto b/proto/neutron/cron/params.proto index 7dfad3181..c94db26d5 100644 --- a/proto/neutron/cron/params.proto +++ b/proto/neutron/cron/params.proto @@ -7,9 +7,9 @@ option go_package = "github.com/neutron-org/neutron/x/cron/types"; // Params defines the parameters for the module. message Params { - option (gogoproto.goproto_stringer) = false; - // Security address that can remove schedules - string security_address = 1; - // Limit of schedules executed in one block - uint64 limit = 2; + option (gogoproto.goproto_stringer) = false; + // Security address that can remove schedules + string security_address = 1; + // Limit of schedules executed in one block + uint64 limit = 2; } diff --git a/proto/neutron/cron/query.proto b/proto/neutron/cron/query.proto index 0105437fa..16205fea8 100644 --- a/proto/neutron/cron/query.proto +++ b/proto/neutron/cron/query.proto @@ -13,45 +13,43 @@ option go_package = "github.com/neutron-org/neutron/x/cron/types"; // Query defines the gRPC querier service. service Query { // Queries the parameters of the module. - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/neutron/cron/params"; - } + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/neutron/cron/params"; + } - // Queries a Schedule by name. - rpc Schedule(QueryGetScheduleRequest) returns (QueryGetScheduleResponse) { - option (google.api.http).get = "/neutron/cron/schedule/{name}"; - } + // Queries a Schedule by name. + rpc Schedule(QueryGetScheduleRequest) returns (QueryGetScheduleResponse) { + option (google.api.http).get = "/neutron/cron/schedule/{name}"; + } - // Queries a list of Schedule items. - rpc Schedules(QuerySchedulesRequest) returns (QuerySchedulesResponse) { - option (google.api.http).get = "/neutron/cron/schedule"; - } + // Queries a list of Schedule items. + rpc Schedules(QuerySchedulesRequest) returns (QuerySchedulesResponse) { + option (google.api.http).get = "/neutron/cron/schedule"; + } -// this line is used by starport scaffolding # 2 + // this line is used by starport scaffolding # 2 } message QueryParamsRequest {} message QueryParamsResponse { - // params holds all the parameters of this module. - Params params = 1 [(gogoproto.nullable) = false]; + // params holds all the parameters of this module. + Params params = 1 [ (gogoproto.nullable) = false ]; } -message QueryGetScheduleRequest { - string name = 1; -} +message QueryGetScheduleRequest { string name = 1; } message QueryGetScheduleResponse { - Schedule schedule = 1 [(gogoproto.nullable) = false]; + Schedule schedule = 1 [ (gogoproto.nullable) = false ]; } message QuerySchedulesRequest { - cosmos.base.query.v1beta1.PageRequest pagination = 1; + cosmos.base.query.v1beta1.PageRequest pagination = 1; } message QuerySchedulesResponse { - repeated Schedule schedules = 1 [(gogoproto.nullable) = false]; - cosmos.base.query.v1beta1.PageResponse pagination = 2; + repeated Schedule schedules = 1 [ (gogoproto.nullable) = false ]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; } // this line is used by starport scaffolding # 3 diff --git a/proto/neutron/cron/schedule.proto b/proto/neutron/cron/schedule.proto index d4fbd337e..3549a8070 100644 --- a/proto/neutron/cron/schedule.proto +++ b/proto/neutron/cron/schedule.proto @@ -6,24 +6,24 @@ option go_package = "github.com/neutron-org/neutron/x/cron/types"; import "gogoproto/gogo.proto"; message Schedule { - // Name of schedule - string name = 1; - // Period in blocks - uint64 period = 2; - // Msgs that will be executed every period amount of time - repeated MsgExecuteContract msgs = 3 [ (gogoproto.nullable) = false ]; - // Last execution's block height - uint64 last_execute_height = 4; + // Name of schedule + string name = 1; + // Period in blocks + uint64 period = 2; + // Msgs that will be executed every period amount of time + repeated MsgExecuteContract msgs = 3 [ (gogoproto.nullable) = false ]; + // Last execution's block height + uint64 last_execute_height = 4; } message MsgExecuteContract { - // Contract is the address of the smart contract - string contract = 1; - // Msg is json encoded message to be passed to the contract - string msg = 2; + // Contract is the address of the smart contract + string contract = 1; + // Msg is json encoded message to be passed to the contract + string msg = 2; } message ScheduleCount { - // Count is the number of current schedules - int32 count = 1; + // Count is the number of current schedules + int32 count = 1; } diff --git a/proto/neutron/cron/tx.proto b/proto/neutron/cron/tx.proto index 79cf8b235..d83271fca 100644 --- a/proto/neutron/cron/tx.proto +++ b/proto/neutron/cron/tx.proto @@ -7,8 +7,7 @@ option go_package = "github.com/neutron-org/neutron/x/cron/types"; // Msg defines the Msg service. service Msg { -// this line is used by starport scaffolding # proto/tx/rpc + // this line is used by starport scaffolding # proto/tx/rpc } - // this line is used by starport scaffolding # proto/tx/message diff --git a/proto/neutron/transfer/v1/tx.proto b/proto/neutron/transfer/v1/tx.proto index b5f8f471a..08019e389 100644 --- a/proto/neutron/transfer/v1/tx.proto +++ b/proto/neutron/transfer/v1/tx.proto @@ -43,8 +43,7 @@ message MsgTransfer { string memo = 8; - neutron.feerefunder.Fee fee = 9 - [ (gogoproto.nullable) = false ]; + neutron.feerefunder.Fee fee = 9 [ (gogoproto.nullable) = false ]; } // MsgTransferResponse is the modified response type for diff --git a/proto/osmosis/tokenfactory/v1beta1/params.proto b/proto/osmosis/tokenfactory/v1beta1/params.proto index 4bd96f977..40a850b16 100644 --- a/proto/osmosis/tokenfactory/v1beta1/params.proto +++ b/proto/osmosis/tokenfactory/v1beta1/params.proto @@ -9,13 +9,15 @@ import "cosmos/base/v1beta1/coin.proto"; // Params holds parameters for the tokenfactory module message Params { - // DenomCreationFee is the fee required to create a new denom using the tokenfactory module - repeated cosmos.base.v1beta1.Coin denom_creation_fee = 1 [ - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", - (gogoproto.moretags) = "yaml:\"denom_creation_fee\"", - (gogoproto.nullable) = false - ]; + // DenomCreationFee is the fee required to create a new denom using the + // tokenfactory module + repeated cosmos.base.v1beta1.Coin denom_creation_fee = 1 [ + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"denom_creation_fee\"", + (gogoproto.nullable) = false + ]; - // FeeCollectorAddress is the address where fees collected from denom creation are sent to - string fee_collector_address = 2; + // FeeCollectorAddress is the address where fees collected from denom creation + // are sent to + string fee_collector_address = 2; } diff --git a/proto/osmosis/tokenfactory/v1beta1/query.proto b/proto/osmosis/tokenfactory/v1beta1/query.proto index a4ee8b4d6..e97978408 100644 --- a/proto/osmosis/tokenfactory/v1beta1/query.proto +++ b/proto/osmosis/tokenfactory/v1beta1/query.proto @@ -18,7 +18,8 @@ service Query { rpc DenomAuthorityMetadata(QueryDenomAuthorityMetadataRequest) returns (QueryDenomAuthorityMetadataResponse) { option (google.api.http).get = - "/osmosis/tokenfactory/v1beta1/denoms/factory/{creator}/{subdenom}/authority_metadata"; + "/osmosis/tokenfactory/v1beta1/denoms/factory/{creator}/{subdenom}/" + "authority_metadata"; } rpc DenomsFromCreator(QueryDenomsFromCreatorRequest) diff --git a/testutil/mocks/interchaintxs/types/expected_keepers.go b/testutil/mocks/interchaintxs/types/expected_keepers.go index 0c903226c..2fd4582de 100644 --- a/testutil/mocks/interchaintxs/types/expected_keepers.go +++ b/testutil/mocks/interchaintxs/types/expected_keepers.go @@ -127,6 +127,20 @@ func (mr *MockContractManagerKeeperMockRecorder) AddContractFailure(ctx, channel return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContractFailure", reflect.TypeOf((*MockContractManagerKeeper)(nil).AddContractFailure), ctx, channelID, address, ackID, ackType) } +// GetParams mocks base method. +func (m *MockContractManagerKeeper) GetParams(ctx types.Context) types4.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types4.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockContractManagerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockContractManagerKeeper)(nil).GetParams), ctx) +} + // HasContractInfo mocks base method. func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { m.ctrl.T.Helper() diff --git a/testutil/mocks/transfer/types/expected_keepers.go b/testutil/mocks/transfer/types/expected_keepers.go index e7c4dfa3b..49699d9e2 100644 --- a/testutil/mocks/transfer/types/expected_keepers.go +++ b/testutil/mocks/transfer/types/expected_keepers.go @@ -11,7 +11,8 @@ import ( types0 "github.com/cosmos/cosmos-sdk/x/auth/types" types1 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" gomock "github.com/golang/mock/gomock" - types2 "github.com/neutron-org/neutron/x/feerefunder/types" + types2 "github.com/neutron-org/neutron/x/contractmanager/types" + types3 "github.com/neutron-org/neutron/x/feerefunder/types" ) // MockContractManagerKeeper is a mock of ContractManagerKeeper interface. @@ -49,6 +50,20 @@ func (mr *MockContractManagerKeeperMockRecorder) AddContractFailure(ctx, channel return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContractFailure", reflect.TypeOf((*MockContractManagerKeeper)(nil).AddContractFailure), ctx, channelID, address, ackID, ackType) } +// GetParams mocks base method. +func (m *MockContractManagerKeeper) GetParams(ctx types.Context) types2.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types2.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockContractManagerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockContractManagerKeeper)(nil).GetParams), ctx) +} + // HasContractInfo mocks base method. func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { m.ctrl.T.Helper() @@ -132,7 +147,7 @@ func (m *MockFeeRefunderKeeper) EXPECT() *MockFeeRefunderKeeperMockRecorder { } // DistributeAcknowledgementFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types2.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types3.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeAcknowledgementFee", ctx, receiver, packetID) } @@ -144,7 +159,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeAcknowledgementFee(ctx, r } // DistributeTimeoutFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types2.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types3.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeTimeoutFee", ctx, receiver, packetID) } @@ -156,7 +171,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeTimeoutFee(ctx, receiver, } // LockFees mocks base method. -func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types2.PacketID, fee types2.Fee) error { +func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types3.PacketID, fee types3.Fee) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LockFees", ctx, payer, packetID, fee) ret0, _ := ret[0].(error) diff --git a/x/contractmanager/keeper/params.go b/x/contractmanager/keeper/params.go index b2e6f7687..4aea5124d 100644 --- a/x/contractmanager/keeper/params.go +++ b/x/contractmanager/keeper/params.go @@ -7,8 +7,15 @@ import ( ) // GetParams get all parameters as types.Params -func (k Keeper) GetParams(_ sdk.Context) types.Params { - return types.NewParams() +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 set the params diff --git a/x/contractmanager/types/params.go b/x/contractmanager/types/params.go index 357196ad6..5c77ce196 100644 --- a/x/contractmanager/types/params.go +++ b/x/contractmanager/types/params.go @@ -7,6 +7,8 @@ import ( var _ paramtypes.ParamSet = (*Params)(nil) +const DefaultSudoCallGasLimit = uint64(1_000_000) + // ParamKeyTable the param key table for launch module func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) @@ -14,7 +16,9 @@ func ParamKeyTable() paramtypes.KeyTable { // NewParams creates a new Params instance func NewParams() Params { - return Params{} + return Params{ + SudoCallGasLimit: DefaultSudoCallGasLimit, + } } // DefaultParams returns a default set of parameters diff --git a/x/contractmanager/types/params.pb.go b/x/contractmanager/types/params.pb.go index a2edd6ade..39cec5b65 100644 --- a/x/contractmanager/types/params.pb.go +++ b/x/contractmanager/types/params.pb.go @@ -25,6 +25,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { + SudoCallGasLimit uint64 `protobuf:"varint,1,opt,name=sudo_call_gas_limit,json=sudoCallGasLimit,proto3" json:"sudo_call_gas_limit,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -59,6 +60,13 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo +func (m *Params) GetSudoCallGasLimit() uint64 { + if m != nil { + return m.SudoCallGasLimit + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "neutron.contractmanager.Params") } @@ -68,17 +76,20 @@ func init() { } var fileDescriptor_121b05e48c7a8737 = []byte{ - // 158 bytes of a gzipped FileDescriptorProto + // 201 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc9, 0x4b, 0x2d, 0x2d, 0x29, 0xca, 0xcf, 0xd3, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0xc9, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x87, 0xaa, 0xd2, 0x43, 0x53, 0x25, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa3, - 0x0f, 0x62, 0x41, 0x94, 0x2b, 0xf1, 0x71, 0xb1, 0x05, 0x80, 0xb5, 0x5b, 0xb1, 0xcc, 0x58, 0x20, - 0xcf, 0xe0, 0x14, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, - 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x66, 0xe9, - 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0x3b, 0x74, 0xf3, 0x8b, 0xd2, - 0x61, 0x6c, 0xfd, 0x0a, 0x0c, 0x77, 0x95, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x2d, 0x32, - 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x69, 0x27, 0xc9, 0x25, 0xbf, 0x00, 0x00, 0x00, + 0x0f, 0x62, 0x41, 0x94, 0x2b, 0xd9, 0x72, 0xb1, 0x05, 0x80, 0xb5, 0x0b, 0xe9, 0x72, 0x09, 0x17, + 0x97, 0xa6, 0xe4, 0xc7, 0x27, 0x27, 0xe6, 0xe4, 0xc4, 0xa7, 0x27, 0x16, 0xc7, 0xe7, 0x64, 0xe6, + 0x66, 0x96, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x04, 0x09, 0x80, 0xa4, 0x9c, 0x13, 0x73, 0x72, + 0xdc, 0x13, 0x8b, 0x7d, 0x40, 0xe2, 0x56, 0x2c, 0x33, 0x16, 0xc8, 0x33, 0x38, 0x05, 0x9c, 0x78, + 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, + 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x59, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, + 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0x49, 0xba, 0xf9, 0x45, 0xe9, 0x30, 0xb6, 0x7e, 0x05, 0x86, + 0x37, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0xee, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, + 0xff, 0xc5, 0xbf, 0xd1, 0xee, 0xee, 0x00, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -101,6 +112,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.SudoCallGasLimit != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.SudoCallGasLimit)) + i-- + dAtA[i] = 0x8 + } return len(dAtA) - i, nil } @@ -121,6 +137,9 @@ func (m *Params) Size() (n int) { } var l int _ = l + if m.SudoCallGasLimit != 0 { + n += 1 + sovParams(uint64(m.SudoCallGasLimit)) + } return n } @@ -159,6 +178,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SudoCallGasLimit", wireType) + } + m.SudoCallGasLimit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SudoCallGasLimit |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 8b851dd69..096248786 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -1,7 +1,7 @@ package keeper import ( - "strings" + "fmt" "time" "cosmossdk.io/errors" @@ -21,63 +21,6 @@ const ( GasReserve = 15000 ) -func (k *Keeper) outOfGasRecovery( - ctx sdk.Context, - gasMeter sdk.GasMeter, - senderAddress sdk.AccAddress, - packet channeltypes.Packet, - failureAckType string, -) { - if r := recover(); r != nil { - _, ok := r.(sdk.ErrorOutOfGas) - if !ok || !gasMeter.IsOutOfGas() { - panic(r) - } - - k.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String()) - k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureAckType) - } -} - -// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. -// If there is an error during Sudo call, we can safely revert changes made in cached context. -func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { - gasMeter := ctx.GasMeter() - // determines type of gas meter by its prefix: - // * BasicGasMeter - basic gas meter which is used for processing tx directly in block; - // * InfiniteGasMeter - is used to process txs during simulation calls. We don't need to create a limit for such meter, - // since it's infinite. - gasMeterIsLimited := strings.HasPrefix(ctx.GasMeter().String(), "BasicGasMeter") - - cacheCtx, writeFn := ctx.CacheContext() - - // if gas meter is limited: - // 1. calculate how much free gas left we have for a Sudo call; - // 2. If gasLeft less than reserved gas (GasReserved), we set gas limit for cached context to zero, meaning we can't - // process Sudo call; - // 3. If we have more gas left than reserved gas (GasReserved) for Sudo call, we set gas limit for cached context to - // difference between gas left and reserved gas: (gasLeft - GasReserve); - // - // GasReserve is the amount of gas on the context gas meter we need to reserve in order to add contract failure to keeper - // and process failed Sudo call - if gasMeterIsLimited { - gasLeft := gasMeter.Limit() - gasMeter.GasConsumed() - - var newLimit uint64 - if gasLeft < GasReserve { - newLimit = 0 - } else { - newLimit = gasLeft - GasReserve - } - - gasMeter = sdk.NewGasMeter(newLimit) - } - - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - - return cacheCtx, writeFn, gasMeter -} - // HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleAcknowledgment) @@ -113,18 +56,16 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, icaOwner.GetContract().String(), packet.GetSequence(), "ack") k.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) } else { - ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumed(), "consume from cached context") - + // consume all the gas from the cached context + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") return nil } // HandleTimeout passes the timeout data to the appropriate contract via a Sudo call. // Since all ICA channels are ORDERED, a single timeout shuts down a channel. -// The affected zone should be paused after a timeout. func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleTimeout) @@ -145,12 +86,11 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, icaOwner.GetContract().String(), packet.GetSequence(), "timeout") k.Logger(ctx).Error("HandleTimeout: failed to Sudo contract on packet timeout", "error", err) } else { - ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumed(), "consume from cached context") - + // consume all the gas from the cached context + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") return nil } @@ -187,3 +127,47 @@ func (k *Keeper) HandleChanOpenAck( return nil } + +func (k *Keeper) outOfGasRecovery( + ctx sdk.Context, + gasMeter sdk.GasMeter, + senderAddress sdk.AccAddress, + packet channeltypes.Packet, + failureAckType string, +) { + if r := recover(); r != nil { + _, ok := r.(sdk.ErrorOutOfGas) + if !ok || !gasMeter.IsOutOfGas() { + panic(r) + } + + k.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String()) + k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureAckType) + } +} + +// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. +// If there is an error during Sudo call, we can safely revert changes made in cached context. +// panics if there is no enough gas for sudoCall + reserve +func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { + cacheCtx, writeFn := ctx.CacheContext() + + sudoLimit := k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit + if ctx.GasMeter().GasRemaining() < getGasReserve()+sudoLimit { + panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) + } + + gasMeter := sdk.NewGasMeter(sudoLimit) + + cacheCtx = cacheCtx.WithGasMeter(gasMeter) + + return cacheCtx, writeFn, gasMeter +} + +// TODO: calculate gas reserve in according to failure ack + packet size +// getGasReserve calculates the gas amount required to +// 1) Save failure ack, in case there is OutOfGas error or a regular error during sudoCall +// 2) Distribute ack fees +func getGasReserve() uint64 { + return GasReserve +} diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index b02a7b6fb..21e3d9ce4 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -71,12 +71,14 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 }).Return(nil, fmt.Errorf("SudoResponse error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + // response spent only 2990, but we consume all 4000 defined by params + require.Equal(t, uint64(4000), ctx.GasMeter().GasConsumed()) // error during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -84,12 +86,14 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) }).Return(nil, fmt.Errorf("SudoError error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + // TODO: add test to check gas consumption by AddFailure + require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) // success during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -97,23 +101,28 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror"))) + require.Equal(t, uint64(6000), ctx.GasMeter().GasConsumed()) // out of gas during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") }).Return(nil, fmt.Errorf("SudoError error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + // TODO: add test to check gas consumption by AddFailure + // TODO: consume sudogas even on out of gas from the contract? require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // due to out of gas recovery we consume 0 with a SudoError handler // check we have ReserveGas reserved and @@ -122,32 +131,25 @@ func TestHandleAcknowledgement(t *testing.T) { ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) gasReserved := false cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+keeper.GasReserve { + if cachedCtx.GasMeter().Limit() == 8000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.True(t, gasReserved) - require.Equal(t, uint64(3140), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(8000), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse"))) - // not enough gas to reserve + not enough to make AddContractFailure failure after panic recover + // not enough gas to reserve, tx aborted ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(keeper.GasReserve - 1)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1, "Sudo response consumption") - }).Return(nil, nil) - feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().AddContractFailure(lowGasCtx, "channel-0", contractAddress.String(), p.GetSequence(), "ack").Do(func(ctx sdk.Context, channelId, address string, ackID uint64, ackType string) { - ctx.GasMeter().ConsumeGas(keeper.GasReserve, "out of gas") - }) - require.Panics(t, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(keeper.GasReserve + 9000 - 1)) + cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "9000gas - reserve for sudo call"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } @@ -175,16 +177,17 @@ func TestHandleTimeout(t *testing.T) { gasReserved := false ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+keeper.GasReserve { + if cachedCtx.GasMeter().Limit() == 4000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudotimeout"), ShouldBeWritten) // consumes 3110 gas, 2000 flat write + 30 every byte of key+value }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) require.True(t, gasReserved) - require.Equal(t, uint64(3110), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(4000), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout"))) require.NoError(t, err) @@ -194,9 +197,11 @@ func TestHandleTimeout(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) }).Return(nil, fmt.Errorf("SudoTimeout error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) + require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) @@ -205,11 +210,13 @@ func TestHandleTimeout(t *testing.T) { cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(6001, "out of gas test") }).Return(nil, fmt.Errorf("SudoTimeout error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } diff --git a/x/interchaintxs/types/expected_keepers.go b/x/interchaintxs/types/expected_keepers.go index 7097b0ed4..c663fc168 100644 --- a/x/interchaintxs/types/expected_keepers.go +++ b/x/interchaintxs/types/expected_keepers.go @@ -31,6 +31,7 @@ type ContractManagerKeeper interface { SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, details string) ([]byte, error) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) SudoOnChanOpenAck(ctx sdk.Context, contractAddress sdk.AccAddress, details contractmanagertypes.OpenAckDetails) ([]byte, error) + GetParams(ctx sdk.Context) (params contractmanagertypes.Params) } type ICAControllerKeeper interface { diff --git a/x/tokenfactory/types/params.pb.go b/x/tokenfactory/types/params.pb.go index 3ca6e158d..08379c672 100644 --- a/x/tokenfactory/types/params.pb.go +++ b/x/tokenfactory/types/params.pb.go @@ -28,9 +28,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params holds parameters for the tokenfactory module type Params struct { - // DenomCreationFee is the fee required to create a new denom using the tokenfactory module + // DenomCreationFee is the fee required to create a new denom using the + // tokenfactory module DenomCreationFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=denom_creation_fee,json=denomCreationFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"denom_creation_fee" yaml:"denom_creation_fee"` - // FeeCollectorAddress is the address where fees collected from denom creation are sent to + // FeeCollectorAddress is the address where fees collected from denom creation + // are sent to FeeCollectorAddress string `protobuf:"bytes,2,opt,name=fee_collector_address,json=feeCollectorAddress,proto3" json:"fee_collector_address,omitempty"` } diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index fe6db47d5..d4e96d6e8 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -1,9 +1,8 @@ package transfer import ( - "strings" - "cosmossdk.io/errors" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -19,67 +18,9 @@ const ( GasReserve = 15000 ) -func (im IBCModule) outOfGasRecovery( - ctx sdk.Context, - gasMeter sdk.GasMeter, - senderAddress sdk.AccAddress, - packet channeltypes.Packet, - data transfertypes.FungibleTokenPacketData, - failureType string, -) { - if r := recover(); r != nil { - _, ok := r.(sdk.ErrorOutOfGas) - if !ok || !gasMeter.IsOutOfGas() { - panic(r) - } - - im.keeper.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String(), "Packet data", data) - im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureType) - // FIXME: add distribution call - } -} - -// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. -// If there is an error during Sudo call, we can safely revert changes made in cached context. -func (im *IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { - gasMeter := ctx.GasMeter() - // determines type of gas meter by its prefix: - // * BasicGasMeter - basic gas meter which is used for processing tx directly in block; - // * InfiniteGasMeter - is used to process txs during simulation calls. We don't need to create a limit for such meter, - // since it's infinite. - gasMeterIsLimited := strings.HasPrefix(ctx.GasMeter().String(), "BasicGasMeter") - - cacheCtx, writeFn := ctx.CacheContext() - - // if gas meter is limited: - // 1. calculate how much free gas left we have for a Sudo call; - // 2. If gasLeft less than reserved gas (GasReserved), we set gas limit for cached context to zero, meaning we can't - // process Sudo call; - // 3. If we have more gas left than reserved gas (GasReserved) for Sudo call, we set gas limit for cached context to - // difference between gas left and reserved gas: (gasLeft - GasReserve); - // - // GasReserve is the amount of gas on the context gas meter we need to reserve in order to add contract failure to keeper - // and process failed Sudo call - if gasMeterIsLimited { - gasLeft := gasMeter.Limit() - gasMeter.GasConsumed() - - var newLimit uint64 - if gasLeft < GasReserve { - newLimit = 0 - } else { - newLimit = gasLeft - GasReserve - } - - gasMeter = sdk.NewGasMeter(newLimit) - } - - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - - return cacheCtx, writeFn, gasMeter -} - // HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + // TODO: why do ever need whole logic for non contract, why dont exit early var ack channeltypes.Acknowledgement if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) @@ -110,16 +51,16 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), "ack") im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet acknowledgement", err) } else { - ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumed(), "consume from cached context") im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) // distribute fees only if the sender is a contract if im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + // consume all the gas from the cached context + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") } return nil @@ -147,16 +88,61 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), "timeout") im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet timeout", err) } else { - ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) writeFn() } // distribute fee only if the sender is a contract if im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + // consume all the gas from the cached context + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumed(), "consume from cached context") - return nil } + +func (im IBCModule) outOfGasRecovery( + ctx sdk.Context, + gasMeter sdk.GasMeter, + senderAddress sdk.AccAddress, + packet channeltypes.Packet, + data transfertypes.FungibleTokenPacketData, + failureType string, +) { + if r := recover(); r != nil { + _, ok := r.(sdk.ErrorOutOfGas) + if !ok || !gasMeter.IsOutOfGas() { + panic(r) + } + + im.keeper.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String(), "Packet data", data) + im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureType) + // FIXME: add distribution call + } +} + +// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. +// If there is an error during Sudo call, we can safely revert changes made in cached context. +// panics if there is no enough gas for sudoCall + reserve +func (im *IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { + cacheCtx, writeFn := ctx.CacheContext() + + sudoLimit := im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit + if ctx.GasMeter().GasRemaining() < getGasReserve()+sudoLimit { + panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) + } + + gasMeter := sdk.NewGasMeter(sudoLimit) + + cacheCtx = cacheCtx.WithGasMeter(gasMeter) + + return cacheCtx, writeFn, gasMeter +} + +// TODO: calculate gas reserve in according to failure ack + packet size +// getGasReserve calculates the gas amount required to +// 1) Save failure ack, in case there is OutOfGas error or a regular error during sudoCall +// 2) Distribute ack fees +func getGasReserve() uint64 { + return GasReserve +} diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 024d17989..8e8fa3bb4 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -2,6 +2,7 @@ package transfer_test import ( "fmt" + "github.com/neutron-org/neutron/x/contractmanager/types" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,7 +13,6 @@ import ( mock_types "github.com/neutron-org/neutron/testutil/mocks/transfer/types" testkeeper "github.com/neutron-org/neutron/testutil/transfer/keeper" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" - "github.com/neutron-org/neutron/x/interchaintxs/keeper" ictxtypes "github.com/neutron-org/neutron/x/interchaintxs/types" "github.com/neutron-org/neutron/x/transfer" "github.com/stretchr/testify/require" @@ -100,12 +100,13 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 }).Return(nil, fmt.Errorf("SudoResponse error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoResponse contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -113,13 +114,14 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 }).Return(nil, fmt.Errorf("SudoResponse error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) // error during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -127,13 +129,14 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 }).Return(nil, fmt.Errorf("SudoError error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoError contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -141,13 +144,14 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 }).Return(nil, fmt.Errorf("SudoError error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) // success during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -155,10 +159,12 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoerror_non_contract"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror_non_contract"))) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // success during SudoError contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -166,40 +172,46 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoerror_contract"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 9000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror_contract"))) + require.Equal(t, uint64(9000), ctx.GasMeter().GasConsumed()) // recoverable out of gas during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(10001, "out of gas test") }).Return(nil, fmt.Errorf("SudoError error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 10000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") // FIXME: fix distribution during outofgas // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // recoverable out of gas during SudoError contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(11001, "out of gas test") }).Return(nil, fmt.Errorf("SudoError error")) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 11000}) // FIXME: fix distribution during outofgas // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // check we have ReserveGas reserved and // check gas consumption from cachedCtx has added to the main ctx @@ -208,50 +220,45 @@ func TestHandleAcknowledgement(t *testing.T) { ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) gasReserved := false cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+transfer.GasReserve { + if cachedCtx.GasMeter().Limit() == 12000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoresponse_non_contract_success"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 12000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.True(t, gasReserved) - require.Equal(t, uint64(3770), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_non_contract_success"))) // contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) gasReserved = false cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+transfer.GasReserve { + if cachedCtx.GasMeter().Limit() == 13000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoresponse_contract_success"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 13000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.True(t, gasReserved) - require.Equal(t, uint64(3650), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(13000), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_contract_success"))) // not enough gas to reserve + not enough to make AddContractFailure failure after panic recover - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(keeper.GasReserve - 1)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1, "Sudo response consumption") - }).Return(nil, nil) + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) // feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().AddContractFailure(lowGasCtx, "channel-0", contractAddress.String(), p.GetSequence(), "ack").Do(func(ctx sdk.Context, channelId, address string, ackID uint64, ackType string) { - ctx.GasMeter().ConsumeGas(keeper.GasReserve, "out of gas") - }) - require.Panics(t, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "14000gas - reserve for sudo call"}, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } @@ -305,16 +312,17 @@ func TestHandleTimeout(t *testing.T) { p.Data = tokenBz gasReserved := false cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+keeper.GasReserve { + if cachedCtx.GasMeter().Limit() == 5000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudotimeout_non_contract_success"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.True(t, gasReserved) - require.Equal(t, uint64(3740), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout_non_contract_success"))) @@ -322,18 +330,19 @@ func TestHandleTimeout(t *testing.T) { ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) gasReserved = false cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if ctx.GasMeter().Limit() == cachedCtx.GasMeter().Limit()+keeper.GasReserve { + if cachedCtx.GasMeter().Limit() == 5000 { gasReserved = true } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudotimeout_contract_success"), ShouldBeWritten) }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.True(t, gasReserved) require.NoError(t, err) - require.Equal(t, uint64(3620), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout_contract_success"))) // error during SudoTimeOut non contract @@ -342,11 +351,13 @@ func TestHandleTimeout(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) }).Return(nil, fmt.Errorf("SudoTimeout error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoTimeOut contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -354,38 +365,44 @@ func TestHandleTimeout(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) }).Return(nil, fmt.Errorf("SudoTimeout error")) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) // out of gas during SudoTimeOut non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(8001, "out of gas test") }).Return(nil, fmt.Errorf("SudoTimeout error")) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // out of gas during SudoTimeOut contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(cachedCtx.GasMeter().Limit()+1, "out of gas test") + cachedCtx.GasMeter().ConsumeGas(8001, "out of gas test") }).Return(nil, fmt.Errorf("SudoTimeout error")) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) // FIXME: make DistributeTimeoutFee during out of gas // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) // feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) } diff --git a/x/transfer/types/expected_keepers.go b/x/transfer/types/expected_keepers.go index 3dd233a34..5d6068a68 100644 --- a/x/transfer/types/expected_keepers.go +++ b/x/transfer/types/expected_keepers.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" ) @@ -15,6 +16,7 @@ type ContractManagerKeeper interface { SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) ([]byte, error) SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, details string) ([]byte, error) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) + GetParams(ctx sdk.Context) (params contractmanagertypes.Params) } type FeeRefunderKeeper interface { From 4c25c383a9661a703e5dbbfe9360c4841a3ab22d Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 22 Aug 2023 12:31:28 +0300 Subject: [PATCH 02/18] sudo call and test rework --- network/hermes/config.toml | 12 +-- x/interchaintxs/keeper/ibc_handlers.go | 33 +++--- x/interchaintxs/keeper/ibc_handlers_test.go | 21 ++-- x/transfer/ibc_handlers.go | 51 ++++------ x/transfer/ibc_handlers_test.go | 105 ++++---------------- 5 files changed, 71 insertions(+), 151 deletions(-) diff --git a/network/hermes/config.toml b/network/hermes/config.toml index 680206388..f7f2418cc 100644 --- a/network/hermes/config.toml +++ b/network/hermes/config.toml @@ -93,7 +93,7 @@ port = 3001 id = 'test-1' rpc_addr = 'http://127.0.0.1:26657' grpc_addr = 'http://127.0.0.1:8090' -websocket_addr = 'ws://127.0.0.1:26657/websocket' +event_source = { mode = 'push', url = 'ws://127.0.0.1:26657/websocket', batch_delay = '200ms' } rpc_timeout = '10s' account_prefix = 'neutron' key_name = 'testkey_1' @@ -101,21 +101,21 @@ store_prefix = 'ibc' default_gas = 100000 max_gas = 3000000 gas_price = { price = 0.0025, denom = 'untrn' } -gas_multiplier = 1.1 +gas_multiplier = 1.5 max_msg_num = 30 max_tx_size = 2097152 clock_drift = '5s' max_block_time = '10s' trusting_period = '14days' +ccv_consumer_chain = true trust_threshold = { numerator = '1', denominator = '3' } -unbonding_period = '20days' address_type = { derivation = 'cosmos' } [[chains]] id = 'test-2' rpc_addr = 'http://127.0.0.1:16657' grpc_addr = 'http://127.0.0.1:9090' -websocket_addr = 'ws://127.0.0.1:16657/websocket' +event_source = { mode = 'push', url = 'ws://127.0.0.1:16657/websocket', batch_delay = '200ms' } rpc_timeout = '10s' account_prefix = 'cosmos' key_name = 'testkey_2' @@ -123,11 +123,11 @@ store_prefix = 'ibc' default_gas = 100000 max_gas = 3000000 gas_price = { price = 0.0025, denom = 'uatom' } -gas_multiplier = 1.1 +gas_multiplier = 1.5 max_msg_num = 30 max_tx_size = 2097152 clock_drift = '5s' max_block_time = '10s' trusting_period = '14days' trust_threshold = { numerator = '1', denominator = '3' } -address_type = { derivation = 'cosmos' } +address_type = { derivation = 'cosmos' } \ No newline at end of file diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 096248786..213334e51 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -16,11 +16,6 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) -const ( - // GasReserve is the amount of gas on the context gas meter we need to reserve in order to add contract failure to keeper - GasReserve = 15000 -) - // HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleAcknowledgment) @@ -39,6 +34,9 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack } cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) + // consume all the gas from the cached context + // we call the function this place function because we want to consume all the gas even in case of panic in SudoError/SudoResponse + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") defer k.outOfGasRecovery(ctx, newGasMeter, icaOwner.GetContract(), packet, "ack") k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) @@ -59,8 +57,6 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack writeFn() } - // consume all the gas from the cached context - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") return nil } @@ -77,6 +73,9 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela } cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) + // consume all the gas from the cached context + // we call the function this place function because we want to consume all the gas even in case of panic in SudoTimeout + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") defer k.outOfGasRecovery(ctx, newGasMeter, icaOwner.GetContract(), packet, "timeout") k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) @@ -89,8 +88,6 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela writeFn() } - // consume all the gas from the cached context - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") return nil } @@ -148,12 +145,16 @@ func (k *Keeper) outOfGasRecovery( // createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. // If there is an error during Sudo call, we can safely revert changes made in cached context. -// panics if there is no enough gas for sudoCall + reserve +// panics if there is no enough gas for sudoCall func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { cacheCtx, writeFn := ctx.CacheContext() - + sudoLimit := k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit - if ctx.GasMeter().GasRemaining() < getGasReserve()+sudoLimit { + // NOTE: not sure that we really need this special check and panic + // with this kind of panic its clear what is going on by error text + // with this check, handle flow is not changed, but we get general panic during + // call ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") + if ctx.GasMeter().GasRemaining() < sudoLimit { panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) } @@ -163,11 +164,3 @@ func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk. return cacheCtx, writeFn, gasMeter } - -// TODO: calculate gas reserve in according to failure ack + packet size -// getGasReserve calculates the gas amount required to -// 1) Save failure ack, in case there is OutOfGas error or a regular error during sudoCall -// 2) Distribute ack fees -func getGasReserve() uint64 { - return GasReserve -} diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 21e3d9ce4..1096a70b0 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -15,7 +15,6 @@ import ( mock_types "github.com/neutron-org/neutron/testutil/mocks/interchaintxs/types" "github.com/neutron-org/neutron/x/contractmanager/types" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" - "github.com/neutron-org/neutron/x/interchaintxs/keeper" ) var ( @@ -92,7 +91,6 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - // TODO: add test to check gas consumption by AddFailure require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) // success during SudoError @@ -114,18 +112,16 @@ func TestHandleAcknowledgement(t *testing.T) { store := cachedCtx.KVStore(storeKey) store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoError error")) + }) cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - // TODO: add test to check gas consumption by AddFailure - // TODO: consume sudogas even on out of gas from the contract? - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // due to out of gas recovery we consume 0 with a SudoError handler + require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) - // check we have ReserveGas reserved and + // check we have SudoCallGasLimit reserved and // check gas consumption from cachedCtx has added to the main ctx // one of the ways to check it - make the check during SudoResponse call ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -147,10 +143,9 @@ func TestHandleAcknowledgement(t *testing.T) { // not enough gas to reserve, tx aborted ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(keeper.GasReserve + 9000 - 1)) + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(9000 - 1)) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "9000gas - reserve for sudo call"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } func TestHandleTimeout(t *testing.T) { @@ -216,9 +211,15 @@ func TestHandleTimeout(t *testing.T) { cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(6000), ctx.GasMeter().GasConsumed()) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + + // not enough gas to reserve, tx aborted + ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(9000 - 1)) + cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "9000gas - reserve for sudo call"}, func() { icak.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleChanOpenAck(t *testing.T) { diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index d4e96d6e8..939c788fa 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -13,14 +13,8 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) -const ( - // We need to reserve this amount of gas on the context gas meter in order to add contract failure to keeper - GasReserve = 15000 -) - // HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { - // TODO: why do ever need whole logic for non contract, why dont exit early var ack channeltypes.Acknowledgement if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) @@ -34,10 +28,19 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P if err != nil { return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err) } + if !im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { + return nil + } cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) + // consume all the gas from the cached context + // we call the function this place function because we want to consume all the gas even in case of panic in SudoResponse/SudoError + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") defer im.outOfGasRecovery(ctx, newGasMeter, senderAddress, packet, data, "ack") + // distribute fee + im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + if ack.Success() { _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) } else { @@ -56,13 +59,6 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) - // distribute fees only if the sender is a contract - if im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { - im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - // consume all the gas from the cached context - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - } - return nil } @@ -79,10 +75,19 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r if err != nil { return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err) } + if !im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { + return nil + } cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) + // consume all the gas from the cached context + // we call the function this place function because we want to consume all the gas even in case of panic in SudoTimeout + ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") defer im.outOfGasRecovery(ctx, newGasMeter, senderAddress, packet, data, "timeout") + // distribute fee + im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) if err != nil { im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), "timeout") @@ -91,13 +96,6 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r writeFn() } - // distribute fee only if the sender is a contract - if im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { - im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - // consume all the gas from the cached context - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - } - return nil } @@ -117,18 +115,17 @@ func (im IBCModule) outOfGasRecovery( im.keeper.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String(), "Packet data", data) im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureType) - // FIXME: add distribution call } } // createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. // If there is an error during Sudo call, we can safely revert changes made in cached context. -// panics if there is no enough gas for sudoCall + reserve +// panics if there is no enough gas for sudoCall func (im *IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { cacheCtx, writeFn := ctx.CacheContext() sudoLimit := im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit - if ctx.GasMeter().GasRemaining() < getGasReserve()+sudoLimit { + if ctx.GasMeter().GasRemaining() < sudoLimit { panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) } @@ -138,11 +135,3 @@ func (im *IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), return cacheCtx, writeFn, gasMeter } - -// TODO: calculate gas reserve in according to failure ack + packet size -// getGasReserve calculates the gas amount required to -// 1) Save failure ack, in case there is OutOfGas error or a regular error during sudoCall -// 2) Distribute ack fees -func getGasReserve() uint64 { - return GasReserve -} diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 8e8fa3bb4..5f82e3789 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -94,14 +94,8 @@ func TestHandleAcknowledgement(t *testing.T) { require.NoError(t, err) p.Data = tokenBz - // error during SudoResponse non contract + //// error during SudoResponse non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 - }).Return(nil, fmt.Errorf("SudoResponse error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) - cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) @@ -125,14 +119,7 @@ func TestHandleAcknowledgement(t *testing.T) { // error during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 - }).Return(nil, fmt.Errorf("SudoError error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) - cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) @@ -155,15 +142,9 @@ func TestHandleAcknowledgement(t *testing.T) { // success during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoerror_non_contract"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror_non_contract"))) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // success during SudoError contract @@ -182,18 +163,7 @@ func TestHandleAcknowledgement(t *testing.T) { // recoverable out of gas during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(10001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoError error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 10000}) - cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") - // FIXME: fix distribution during outofgas - // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // recoverable out of gas during SudoError contract @@ -206,38 +176,26 @@ func TestHandleAcknowledgement(t *testing.T) { cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "ack") cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 11000}) // FIXME: fix distribution during outofgas - // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(11000), ctx.GasMeter().GasConsumed()) // check we have ReserveGas reserved and // check gas consumption from cachedCtx has added to the main ctx // one of the ways to check it - make the check during SudoResponse call // non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved := false - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if cachedCtx.GasMeter().Limit() == 12000 { - gasReserved = true - } - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoresponse_non_contract_success"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 12000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - // feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - require.True(t, gasReserved) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_non_contract_success"))) // contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved = false + gasReserved := false cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { if cachedCtx.GasMeter().Limit() == 13000 { gasReserved = true @@ -254,12 +212,11 @@ func TestHandleAcknowledgement(t *testing.T) { require.Equal(t, uint64(13000), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_contract_success"))) - // not enough gas to reserve + not enough to make AddContractFailure failure after panic recover + // not enough gas to reserve SudoCallGasLimit + not enough to make AddContractFailure failure after panic recover lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) - // feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "14000gas - reserve for sudo call"}, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } func TestHandleTimeout(t *testing.T) { @@ -299,8 +256,6 @@ func TestHandleTimeout(t *testing.T) { err = txModule.HandleTimeout(ctx, p, relayerAddress) require.ErrorContains(t, err, "failed to decode address from bech32") - // success non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) token = transfertypes.FungibleTokenPacketData{ Denom: "stake", Amount: "1000", @@ -310,25 +265,17 @@ func TestHandleTimeout(t *testing.T) { tokenBz, err = ictxtypes.ModuleCdc.MarshalJSON(&token) require.NoError(t, err) p.Data = tokenBz - gasReserved := false - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if cachedCtx.GasMeter().Limit() == 5000 { - gasReserved = true - } - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudotimeout_non_contract_success"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) + + // success non contract + ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.True(t, gasReserved) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) require.NoError(t, err) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout_non_contract_success"))) // success contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved = false + gasReserved := false cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { if cachedCtx.GasMeter().Limit() == 5000 { gasReserved = true @@ -347,16 +294,9 @@ func TestHandleTimeout(t *testing.T) { // error during SudoTimeOut non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) - cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoTimeOut contract @@ -376,17 +316,9 @@ func TestHandleTimeout(t *testing.T) { // out of gas during SudoTimeOut non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(8001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) - // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // out of gas during SudoTimeOut contract @@ -398,11 +330,16 @@ func TestHandleTimeout(t *testing.T) { }).Return(nil, fmt.Errorf("SudoTimeout error")) cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) - // FIXME: make DistributeTimeoutFee during out of gas - // cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - // feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(8000), ctx.GasMeter().GasConsumed()) + + // not enough gas to reserve SudoCallGasLimit + not enough to make AddContractFailure failure after panic recover + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) + cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "14000gas - reserve for sudo call"}, func() { txModule.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a test } From 805576207f819761f2ed424865aba592e8783fd9 Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 22 Aug 2023 16:34:31 +0300 Subject: [PATCH 03/18] go mod tidy --- go.sum | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index ca3af2c87..1f759e18a 100644 --- a/go.sum +++ b/go.sum @@ -807,7 +807,6 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= @@ -921,7 +920,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= @@ -942,6 +940,9 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= github.com/neutron-org/admin-module v0.0.0-20230705134325-b23404470a1d h1:oexw79znoA0TEo7CGdWHrolbvZqCDD3aI+031CbOq9Y= github.com/neutron-org/admin-module v0.0.0-20230705134325-b23404470a1d/go.mod h1:QuxQ7FJlEAFMRssyEYOrR9ORnYQvBFMTlO8BXny6ntw= +github.com/neutron-org/cosmos-sdk v0.47.5-0.20230808152221-a0e301227957 h1:cl6UqD18qV/QXgT6Yjo2TelfbaOqQGuvL1yX0cWguSs= +github.com/neutron-org/cosmos-sdk v0.47.5-0.20230808152221-a0e301227957/go.mod h1:4xMyIVekAs2OEUz/yh9JwzhLBMk+olM2sxgKuQdlhLg= +github.com/neutron-org/wasmd v0.40.0-rc.0.0.20230808084410-6083b888424e h1:uVJCBWf1vcCYY0pzOA2SCPIZT8WsR8fsOxs57mnJbM4= github.com/neutron-org/wasmd v0.40.0-rc.0.0.20230808084410-6083b888424e/go.mod h1:Oagy36cU49438NzxKG/gmGTG903tiAI7LIUdH7x2qNY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= From fd889e8a3abec527ca2d0be93f978b921d66a4de Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 29 Aug 2023 10:39:30 +0300 Subject: [PATCH 04/18] icatx sudocall rework --- x/interchaintxs/keeper/ibc_handlers.go | 67 +++++++++----------- x/interchaintxs/keeper/ibc_handlers_test.go | 46 +++++++------- x/transfer/ibc_handlers.go | 70 +++++++++------------ x/transfer/ibc_handlers_test.go | 36 ++++++++--- 4 files changed, 112 insertions(+), 107 deletions(-) diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 213334e51..0035629a2 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -1,7 +1,6 @@ package keeper import ( - "fmt" "time" "cosmossdk.io/errors" @@ -34,29 +33,33 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack } cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) - // consume all the gas from the cached context - // we call the function this place function because we want to consume all the gas even in case of panic in SudoError/SudoResponse - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - defer k.outOfGasRecovery(ctx, newGasMeter, icaOwner.GetContract(), packet, "ack") k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - // Actually we have only one kind of error returned from acknowledgement - // maybe later we'll retrieve actual errors from events - errorText := ack.GetError() - if errorText != "" { - _, err = k.contractManagerKeeper.SudoError(cacheCtx, icaOwner.GetContract(), packet, errorText) - } else { - _, err = k.contractManagerKeeper.SudoResponse(cacheCtx, icaOwner.GetContract(), packet, ack.GetResult()) - } + func() { + // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered + // if SudoError/SudoResponse successful, then `err` is set to `nil` + defer k.outOfGasRecovery(newGasMeter, &err) + // Actually we have only one kind of error returned from acknowledgement + // maybe later we'll retrieve actual errors from events + errorText := ack.GetError() + if errorText != "" { + _, err = k.contractManagerKeeper.SudoError(cacheCtx, icaOwner.GetContract(), packet, errorText) + } else { + _, err = k.contractManagerKeeper.SudoResponse(cacheCtx, icaOwner.GetContract(), packet, ack.GetResult()) + } + }() if err != nil { + // the contract either returned an error or panicked with `out of gas` k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, icaOwner.GetContract().String(), packet.GetSequence(), "ack") k.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) } else { writeFn() } + ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + return nil } @@ -73,21 +76,26 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela } cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) - // consume all the gas from the cached context - // we call the function this place function because we want to consume all the gas even in case of panic in SudoTimeout - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - defer k.outOfGasRecovery(ctx, newGasMeter, icaOwner.GetContract(), packet, "timeout") k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) + func() { + // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered + // if SudoTimeout successful, then `err` is set to `nil` + defer k.outOfGasRecovery(newGasMeter, &err) + _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) + }() + if err != nil { + // the contract either returned an error or panicked with `out of gas` k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, icaOwner.GetContract().String(), packet.GetSequence(), "timeout") k.Logger(ctx).Error("HandleTimeout: failed to Sudo contract on packet timeout", "error", err) } else { writeFn() } + ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + return nil } @@ -125,42 +133,27 @@ func (k *Keeper) HandleChanOpenAck( return nil } +// outOfGasRecovery converts `out of gas` panic into an error +// leave unprocessed any other kinds of panics func (k *Keeper) outOfGasRecovery( - ctx sdk.Context, gasMeter sdk.GasMeter, - senderAddress sdk.AccAddress, - packet channeltypes.Packet, - failureAckType string, + err *error, ) { if r := recover(); r != nil { _, ok := r.(sdk.ErrorOutOfGas) if !ok || !gasMeter.IsOutOfGas() { panic(r) } - - k.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String()) - k.contractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureAckType) + *err = errors.Wrapf(errors.ErrPanic, "%v", r) } } // createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. // If there is an error during Sudo call, we can safely revert changes made in cached context. -// panics if there is no enough gas for sudoCall func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { cacheCtx, writeFn := ctx.CacheContext() - sudoLimit := k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit - // NOTE: not sure that we really need this special check and panic - // with this kind of panic its clear what is going on by error text - // with this check, handle flow is not changed, but we get general panic during - // call ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - if ctx.GasMeter().GasRemaining() < sudoLimit { - panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) - } - gasMeter := sdk.NewGasMeter(sudoLimit) - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn, gasMeter } diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 1096a70b0..330666a76 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -76,8 +76,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - // response spent only 2990, but we consume all 4000 defined by params - require.Equal(t, uint64(4000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) // error during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -91,7 +90,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) // success during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -104,7 +103,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror"))) - require.Equal(t, uint64(6000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) // out of gas during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -121,15 +120,9 @@ func TestHandleAcknowledgement(t *testing.T) { require.Empty(t, store.Get(ShouldNotBeWrittenKey)) require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) - // check we have SudoCallGasLimit reserved and - // check gas consumption from cachedCtx has added to the main ctx - // one of the ways to check it - make the check during SudoResponse call + // success during SudoResponse ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved := false cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if cachedCtx.GasMeter().Limit() == 8000 { - gasReserved = true - } store := cachedCtx.KVStore(storeKey) store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value }).Return(nil, nil) @@ -137,15 +130,20 @@ func TestHandleAcknowledgement(t *testing.T) { feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - require.True(t, gasReserved) - require.Equal(t, uint64(8000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3140), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse"))) - // not enough gas to reserve, tx aborted + // not enough gas provided by relayer for SudoCallGasLimit ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(9000 - 1)) + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + store := cachedCtx.KVStore(storeKey) + store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") + }) + feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "9000gas - reserve for sudo call"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleTimeout(t *testing.T) { @@ -182,7 +180,7 @@ func TestHandleTimeout(t *testing.T) { feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) require.True(t, gasReserved) - require.Equal(t, uint64(4000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3110), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout"))) require.NoError(t, err) @@ -196,7 +194,7 @@ func TestHandleTimeout(t *testing.T) { cmKeeper.EXPECT().AddContractFailure(ctx, "channel-0", contractAddress.String(), p.GetSequence(), "timeout") feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) err = icak.HandleTimeout(ctx, p, relayerAddress) - require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) @@ -215,11 +213,17 @@ func TestHandleTimeout(t *testing.T) { require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - // not enough gas to reserve, tx aborted + // not enough gas provided by relayer for SudoCallGasLimit ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(9000 - 1)) + lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { + store := cachedCtx.KVStore(storeKey) + store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") + }).Return(nil, nil) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "9000gas - reserve for sudo call"}, func() { icak.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a panic test + feeKeeper.EXPECT().DistributeTimeoutFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleChanOpenAck(t *testing.T) { diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index 939c788fa..c987d9dce 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -2,8 +2,6 @@ package transfer import ( "cosmossdk.io/errors" - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -33,38 +31,40 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P } cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) - // consume all the gas from the cached context - // we call the function this place function because we want to consume all the gas even in case of panic in SudoResponse/SudoError - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - defer im.outOfGasRecovery(ctx, newGasMeter, senderAddress, packet, data, "ack") // distribute fee im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - - if ack.Success() { - _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) - } else { - // Actually we have only one kind of error returned from acknowledgement - // maybe later we'll retrieve actual errors from events - im.keeper.Logger(cacheCtx).Debug(ack.GetError(), "CheckTx", cacheCtx.IsCheckTx()) - _, err = im.ContractManagerKeeper.SudoError(cacheCtx, senderAddress, packet, ack.GetError()) - } + + func() { + // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered + // if SudoResponse/SudoError successful, then `err` is set to `nil` + defer im.outOfGasRecovery(newGasMeter, &err) + if ack.Success() { + _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) + } else { + // Actually we have only one kind of error returned from acknowledgement + // maybe later we'll retrieve actual errors from events + im.keeper.Logger(cacheCtx).Debug(ack.GetError(), "CheckTx", cacheCtx.IsCheckTx()) + _, err = im.ContractManagerKeeper.SudoError(cacheCtx, senderAddress, packet, ack.GetError()) + } + }() if err != nil { + // the contract either returned an error or panicked with `out of gas` im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), "ack") im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet acknowledgement", err) } else { writeFn() } + ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) return nil } // HandleTimeout passes the timeout data to the appropriate contract via a Sudo call. -// Since all ICA channels are ORDERED, a single timeout shuts down a channel. -// The affected zone should be paused after a timeout. func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { var data transfertypes.FungibleTokenPacketData if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { @@ -80,58 +80,50 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r } cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) - // consume all the gas from the cached context - // we call the function this place function because we want to consume all the gas even in case of panic in SudoTimeout - ctx.GasMeter().ConsumeGas(newGasMeter.Limit(), "consume full gas from cached context") - defer im.outOfGasRecovery(ctx, newGasMeter, senderAddress, packet, data, "timeout") // distribute fee im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + func() { + // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered + // if SudoTimeout successful, then `err` is set to `nil` + defer im.outOfGasRecovery(newGasMeter, &err) + _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) + }() - _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) if err != nil { + // the contract either returned an error or panicked with `out of gas` im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), "timeout") im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet timeout", err) } else { writeFn() } + ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + return nil } +// outOfGasRecovery converts `out of gas` panic into an error +// leave unprocessed any other kinds of panics func (im IBCModule) outOfGasRecovery( - ctx sdk.Context, gasMeter sdk.GasMeter, - senderAddress sdk.AccAddress, - packet channeltypes.Packet, - data transfertypes.FungibleTokenPacketData, - failureType string, + err *error, ) { if r := recover(); r != nil { _, ok := r.(sdk.ErrorOutOfGas) if !ok || !gasMeter.IsOutOfGas() { panic(r) } - - im.keeper.Logger(ctx).Debug("Out of gas", "Gas meter", gasMeter.String(), "Packet data", data) - im.ContractManagerKeeper.AddContractFailure(ctx, packet.SourceChannel, senderAddress.String(), packet.GetSequence(), failureType) + *err = errors.Wrapf(errors.ErrPanic, "%v", r) } } // createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. // If there is an error during Sudo call, we can safely revert changes made in cached context. -// panics if there is no enough gas for sudoCall -func (im *IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { +func (im IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { cacheCtx, writeFn := ctx.CacheContext() - sudoLimit := im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit - if ctx.GasMeter().GasRemaining() < sudoLimit { - panic(sdk.ErrorOutOfGas{Descriptor: fmt.Sprintf("%dgas - reserve for sudo call", sudoLimit)}) - } - gasMeter := sdk.NewGasMeter(sudoLimit) - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn, gasMeter } diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 5f82e3789..9fd012e48 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -115,7 +115,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) // error during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -138,7 +138,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) // success during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -159,7 +159,7 @@ func TestHandleAcknowledgement(t *testing.T) { err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror_contract"))) - require.Equal(t, uint64(9000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3320), ctx.GasMeter().GasConsumed()) // recoverable out of gas during SudoError non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -209,14 +209,22 @@ func TestHandleAcknowledgement(t *testing.T) { err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) require.True(t, gasReserved) - require.Equal(t, uint64(13000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3650), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_contract_success"))) - // not enough gas to reserve SudoCallGasLimit + not enough to make AddContractFailure failure after panic recover + // not enough gas provided by relayer SudoCallGasLimit lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + store := cachedCtx.KVStore(storeKey) + store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") + }).Return(nil, nil) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "14000gas - reserve for sudo call"}, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test + feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test + // NOTE: looks its impossible to test store reset after panic, because test `require.PanicsWithValue` recovers the panic + // require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } func TestHandleTimeout(t *testing.T) { @@ -289,7 +297,7 @@ func TestHandleTimeout(t *testing.T) { err = txModule.HandleTimeout(ctx, p, relayerAddress) require.True(t, gasReserved) require.NoError(t, err) - require.Equal(t, uint64(5000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(3620), ctx.GasMeter().GasConsumed()) require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout_contract_success"))) // error during SudoTimeOut non contract @@ -312,7 +320,7 @@ func TestHandleTimeout(t *testing.T) { err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) + require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) // out of gas during SudoTimeOut non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) @@ -337,9 +345,17 @@ func TestHandleTimeout(t *testing.T) { require.Empty(t, store.Get(ShouldNotBeWrittenKey)) require.Equal(t, uint64(8000), ctx.GasMeter().GasConsumed()) - // not enough gas to reserve SudoCallGasLimit + not enough to make AddContractFailure failure after panic recover + // not enough gas provided by relayer for SudoCallGasLimit lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { + store := cachedCtx.KVStore(storeKey) + store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") + }).Return(nil, nil) cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "14000gas - reserve for sudo call"}, func() { txModule.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a test + feeKeeper.EXPECT().DistributeTimeoutFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { txModule.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a test + // NOTE: looks its impossible to test store reset after panic, because test `require.PanicsWithValue` recovers the panic + // require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } From 8dd3c7a415b9a9b7e10defbbb8982e5dfee6f36e Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 29 Aug 2023 15:58:19 +0300 Subject: [PATCH 05/18] extracted some common code --- neutronutils/errors/errors.go | 21 ++++++++++++++++ neutronutils/utils.go | 11 ++++++++ x/interchaintxs/keeper/ibc_handlers.go | 35 +++++--------------------- x/transfer/ibc_handlers.go | 35 +++++--------------------- 4 files changed, 44 insertions(+), 58 deletions(-) create mode 100644 neutronutils/errors/errors.go create mode 100644 neutronutils/utils.go diff --git a/neutronutils/errors/errors.go b/neutronutils/errors/errors.go new file mode 100644 index 000000000..7aecf5e13 --- /dev/null +++ b/neutronutils/errors/errors.go @@ -0,0 +1,21 @@ +package errors + +import ( + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// OutOfGasRecovery converts `out of gas` panic into an error +// leave unprocessed any other kinds of panics +func OutOfGasRecovery( + gasMeter sdk.GasMeter, + err *error, +) { + if r := recover(); r != nil { + _, ok := r.(sdk.ErrorOutOfGas) + if !ok || !gasMeter.IsOutOfGas() { + panic(r) + } + *err = errors.Wrapf(errors.ErrPanic, "%v", r) + } +} diff --git a/neutronutils/utils.go b/neutronutils/utils.go new file mode 100644 index 000000000..0e67d48dd --- /dev/null +++ b/neutronutils/utils.go @@ -0,0 +1,11 @@ +package neutronutils + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// CreateCachedContext creates a cached context for with a limited gas meter. +func CreateCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func(), sdk.GasMeter) { + cacheCtx, writeFn := ctx.CacheContext() + gasMeter := sdk.NewGasMeter(gasLimit) + cacheCtx = cacheCtx.WithGasMeter(gasMeter) + return cacheCtx, writeFn, gasMeter +} diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 0035629a2..79b79cad5 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -1,6 +1,8 @@ package keeper import ( + "github.com/neutron-org/neutron/neutronutils" + neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" "time" "cosmossdk.io/errors" @@ -32,14 +34,14 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-27 packet acknowledgement: %v", err) } - cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) + cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered // if SudoError/SudoResponse successful, then `err` is set to `nil` - defer k.outOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events errorText := ack.GetError() @@ -75,14 +77,14 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela return errors.Wrap(err, "failed to get ica owner from port") } - cacheCtx, writeFn, newGasMeter := k.createCachedContext(ctx) + cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered // if SudoTimeout successful, then `err` is set to `nil` - defer k.outOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) }() @@ -132,28 +134,3 @@ func (k *Keeper) HandleChanOpenAck( return nil } - -// outOfGasRecovery converts `out of gas` panic into an error -// leave unprocessed any other kinds of panics -func (k *Keeper) outOfGasRecovery( - gasMeter sdk.GasMeter, - err *error, -) { - if r := recover(); r != nil { - _, ok := r.(sdk.ErrorOutOfGas) - if !ok || !gasMeter.IsOutOfGas() { - panic(r) - } - *err = errors.Wrapf(errors.ErrPanic, "%v", r) - } -} - -// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. -// If there is an error during Sudo call, we can safely revert changes made in cached context. -func (k *Keeper) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { - cacheCtx, writeFn := ctx.CacheContext() - sudoLimit := k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit - gasMeter := sdk.NewGasMeter(sudoLimit) - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn, gasMeter -} diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index c987d9dce..37834eada 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -6,7 +6,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/neutron-org/neutron/neutronutils" + neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" "github.com/neutron-org/neutron/x/interchaintxs/types" ) @@ -30,7 +32,7 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P return nil } - cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) + cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) // distribute fee im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) @@ -38,7 +40,7 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P func() { // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered // if SudoResponse/SudoError successful, then `err` is set to `nil` - defer im.outOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) if ack.Success() { _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) } else { @@ -79,14 +81,14 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r return nil } - cacheCtx, writeFn, newGasMeter := im.createCachedContext(ctx) + cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) // distribute fee im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered // if SudoTimeout successful, then `err` is set to `nil` - defer im.outOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) }() @@ -102,28 +104,3 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r return nil } - -// outOfGasRecovery converts `out of gas` panic into an error -// leave unprocessed any other kinds of panics -func (im IBCModule) outOfGasRecovery( - gasMeter sdk.GasMeter, - err *error, -) { - if r := recover(); r != nil { - _, ok := r.(sdk.ErrorOutOfGas) - if !ok || !gasMeter.IsOutOfGas() { - panic(r) - } - *err = errors.Wrapf(errors.ErrPanic, "%v", r) - } -} - -// createCachedContext creates a cached context for handling Sudo calls to CosmWasm smart-contracts. -// If there is an error during Sudo call, we can safely revert changes made in cached context. -func (im IBCModule) createCachedContext(ctx sdk.Context) (sdk.Context, func(), sdk.GasMeter) { - cacheCtx, writeFn := ctx.CacheContext() - sudoLimit := im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit - gasMeter := sdk.NewGasMeter(sudoLimit) - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn, gasMeter -} From b34a26e9a303c1406cb3b91601b33549a3988a16 Mon Sep 17 00:00:00 2001 From: swelf Date: Wed, 30 Aug 2023 12:36:10 +0300 Subject: [PATCH 06/18] removed outdated comments --- x/interchaintxs/keeper/ibc_handlers.go | 4 ---- x/transfer/ibc_handlers.go | 4 ---- 2 files changed, 8 deletions(-) diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 79b79cad5..535556800 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -39,8 +39,6 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { - // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered - // if SudoError/SudoResponse successful, then `err` is set to `nil` defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events @@ -82,8 +80,6 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { - // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered - // if SudoTimeout successful, then `err` is set to `nil` defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) }() diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index 37834eada..73fac3316 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -38,8 +38,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)) func() { - // early error initialisation, to choose a correct `if` branch right after the closure in case of successfully `out of gas` panic recovered - // if SudoResponse/SudoError successful, then `err` is set to `nil` defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) if ack.Success() { _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) @@ -86,8 +84,6 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r // distribute fee im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) func() { - // early error initialisation, to choose a correct `if` branch right after the closure, in case of successfully `out of gas` panic recovered - // if SudoTimeout successful, then `err` is set to `nil` defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) }() From 07944be3626e3e23a0cf2769fcef4e00edcaa814 Mon Sep 17 00:00:00 2001 From: swelf Date: Thu, 31 Aug 2023 16:40:57 +0300 Subject: [PATCH 07/18] merge conflicts --- x/transfer/ibc_handlers.go | 2 -- x/transfer/ibc_handlers_test.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index 85ff59d22..550c7bb37 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -1,8 +1,6 @@ package transfer import ( - "strings" - contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" "cosmossdk.io/errors" diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 68f6dfa64..294654283 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -5,8 +5,6 @@ import ( "github.com/neutron-org/neutron/x/contractmanager/types" "testing" - "github.com/neutron-org/neutron/x/contractmanager/types" - sdk "github.com/cosmos/cosmos-sdk/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" From 7693c89f79785a6a7e14440c8b1288f60f959ff2 Mon Sep 17 00:00:00 2001 From: swelf19 <62722506+swelf19@users.noreply.github.com> Date: Thu, 31 Aug 2023 19:04:26 +0300 Subject: [PATCH 08/18] Update neutronutils/utils.go Co-authored-by: Dmitry Kolupaev --- neutronutils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutronutils/utils.go b/neutronutils/utils.go index 0e67d48dd..fb621d701 100644 --- a/neutronutils/utils.go +++ b/neutronutils/utils.go @@ -2,7 +2,7 @@ package neutronutils import sdk "github.com/cosmos/cosmos-sdk/types" -// CreateCachedContext creates a cached context for with a limited gas meter. +// CreateCachedContext creates a cached context with a limited gas meter. func CreateCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func(), sdk.GasMeter) { cacheCtx, writeFn := ctx.CacheContext() gasMeter := sdk.NewGasMeter(gasLimit) From 50b8c25f0496f391d0276820976f3858154a347d Mon Sep 17 00:00:00 2001 From: swelf Date: Thu, 31 Aug 2023 19:09:09 +0300 Subject: [PATCH 09/18] review fixes --- network/init-neutrond.sh | 3 ++- x/contractmanager/types/params.go | 6 +++--- x/transfer/ibc_handlers_test.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/network/init-neutrond.sh b/network/init-neutrond.sh index 037d2ab7c..390284816 100755 --- a/network/init-neutrond.sh +++ b/network/init-neutrond.sh @@ -637,7 +637,8 @@ set_genesis_param signed_blocks_window "\"$SLASHING_SIGNED_BLO set_genesis_param min_signed_per_window "\"$SLASHING_MIN_SIGNED\"," # slashing set_genesis_param slash_fraction_double_sign "\"$SLASHING_FRACTION_DOUBLE_SIGN\"," # slashing set_genesis_param slash_fraction_downtime "\"$SLASHING_FRACTION_DOWNTIME\"" # slashing -set_genesis_param minimum_gas_prices "$MIN_GAS_PRICES," # globalfeeset_genesis_param max_total_bypass_min_fee_msg_gas_usage "\"$MAX_TOTAL_BYPASS_MIN_FEE_MSG_GAS_USAGE\"" # globalfee +set_genesis_param minimum_gas_prices "$MIN_GAS_PRICES," # globalfee +set_genesis_param max_total_bypass_min_fee_msg_gas_usage "\"$MAX_TOTAL_BYPASS_MIN_FEE_MSG_GAS_USAGE\"" # globalfee set_genesis_param_jq ".app_state.globalfee.params.bypass_min_fee_msg_types" "$BYPASS_MIN_FEE_MSG_TYPES" # globalfee set_genesis_param proposer_fee "\"0.25\"" # builder(POB) set_genesis_param escrow_account_address "\"$DAO_CONTRACT_ADDRESS_B64\"," # builder(POB) diff --git a/x/contractmanager/types/params.go b/x/contractmanager/types/params.go index 5c77ce196..5a2a9c291 100644 --- a/x/contractmanager/types/params.go +++ b/x/contractmanager/types/params.go @@ -15,15 +15,15 @@ func ParamKeyTable() paramtypes.KeyTable { } // NewParams creates a new Params instance -func NewParams() Params { +func NewParams(sudoCallGasLimit uint64) Params { return Params{ - SudoCallGasLimit: DefaultSudoCallGasLimit, + SudoCallGasLimit: sudoCallGasLimit, } } // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams() + return NewParams(DefaultSudoCallGasLimit) } // ParamSetPairs get the params.ParamSet diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 294654283..191bbfa8d 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -94,7 +94,7 @@ func TestHandleAcknowledgement(t *testing.T) { require.NoError(t, err) p.Data = tokenBz - //// error during SudoResponse non contract + // error during SudoResponse non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) From 788b4d77a48f150046b1acb15ff326c01883d1b3 Mon Sep 17 00:00:00 2001 From: swelf Date: Thu, 31 Aug 2023 19:13:18 +0300 Subject: [PATCH 10/18] module alias alter --- app/upgrades/sdk47/upgrades.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/upgrades/sdk47/upgrades.go b/app/upgrades/sdk47/upgrades.go index 9c92d08f0..3beeb8e82 100644 --- a/app/upgrades/sdk47/upgrades.go +++ b/app/upgrades/sdk47/upgrades.go @@ -12,7 +12,7 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" v6 "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/migrations/v6" "github.com/neutron-org/neutron/app/upgrades" - contractmanagermoduletypes "github.com/neutron-org/neutron/x/contractmanager/types" + contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" crontypes "github.com/neutron-org/neutron/x/cron/types" feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" @@ -93,7 +93,7 @@ func CreateUpgradeHandler( } ctx.Logger().Info("Setting sudo callback limit...") - cmParams := contractmanagermoduletypes.Params{ + cmParams := contractmanagertypes.Params{ SudoCallGasLimit: 1_000_000, } err = keepers.ContractManager.SetParams(ctx, cmParams) From c288fa96a94d662a07af7c4f8eae607f15698f06 Mon Sep 17 00:00:00 2001 From: swelf Date: Wed, 6 Sep 2023 16:34:31 +0300 Subject: [PATCH 11/18] review fixes --- app/upgrades/sdk47/upgrades.go | 49 ++++++++++++++++---------- neutronutils/errors/errors.go | 2 +- neutronutils/utils.go | 4 +-- x/interchaintxs/keeper/ibc_handlers.go | 16 ++++----- x/transfer/ibc_handlers.go | 19 ++++------ 5 files changed, 46 insertions(+), 44 deletions(-) diff --git a/app/upgrades/sdk47/upgrades.go b/app/upgrades/sdk47/upgrades.go index 3beeb8e82..f0cfaa7e3 100644 --- a/app/upgrades/sdk47/upgrades.go +++ b/app/upgrades/sdk47/upgrades.go @@ -12,13 +12,16 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" v6 "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/migrations/v6" "github.com/neutron-org/neutron/app/upgrades" + contractmanagerkeeper "github.com/neutron-org/neutron/x/contractmanager/keeper" contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" crontypes "github.com/neutron-org/neutron/x/cron/types" + feeburnerkeeper "github.com/neutron-org/neutron/x/feeburner/keeper" feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" icqtypes "github.com/neutron-org/neutron/x/interchainqueries/types" interchaintxstypes "github.com/neutron-org/neutron/x/interchaintxs/types" tokenfactorytypes "github.com/neutron-org/neutron/x/tokenfactory/types" + builderkeeper "github.com/skip-mev/pob/x/builder/keeper" buildertypes "github.com/skip-mev/pob/x/builder/types" ) @@ -73,30 +76,13 @@ func CreateUpgradeHandler( } ctx.Logger().Info("Setting pob params...") - treasury := keepers.FeeBurnerKeeper.GetParams(ctx).TreasuryAddress - _, data, err := bech32.DecodeAndConvert(treasury) - if err != nil { - return nil, err - } - - builderParams := buildertypes.Params{ - MaxBundleSize: 2, - EscrowAccountAddress: data, - ReserveFee: sdk.Coin{Denom: "untrn", Amount: sdk.NewInt(1_000_000)}, - MinBidIncrement: sdk.Coin{Denom: "untrn", Amount: sdk.NewInt(1_000_000)}, - FrontRunningProtection: true, - ProposerFee: math.LegacyNewDecWithPrec(25, 2), - } - err = keepers.BuilderKeeper.SetParams(ctx, builderParams) + err = setPobParams(ctx, keepers.FeeBurnerKeeper, keepers.BuilderKeeper) if err != nil { return nil, err } ctx.Logger().Info("Setting sudo callback limit...") - cmParams := contractmanagertypes.Params{ - SudoCallGasLimit: 1_000_000, - } - err = keepers.ContractManager.SetParams(ctx, cmParams) + err = setContractManagerParams(ctx, keepers.ContractManager) if err != nil { return nil, err } @@ -106,6 +92,31 @@ func CreateUpgradeHandler( } } +func setPobParams(ctx sdk.Context, feeBurnerKeeper *feeburnerkeeper.Keeper, builderKeeper builderkeeper.Keeper) error { + treasury := feeBurnerKeeper.GetParams(ctx).TreasuryAddress + _, data, err := bech32.DecodeAndConvert(treasury) + if err != nil { + return err + } + + builderParams := buildertypes.Params{ + MaxBundleSize: 2, + EscrowAccountAddress: data, + ReserveFee: sdk.Coin{Denom: "untrn", Amount: sdk.NewInt(1_000_000)}, + MinBidIncrement: sdk.Coin{Denom: "untrn", Amount: sdk.NewInt(1_000_000)}, + FrontRunningProtection: true, + ProposerFee: math.LegacyNewDecWithPrec(25, 2), + } + return builderKeeper.SetParams(ctx, builderParams) +} + +func setContractManagerParams(ctx sdk.Context, keeper contractmanagerkeeper.Keeper) error { + cmParams := contractmanagertypes.Params{ + SudoCallGasLimit: contractmanagertypes.DefaultSudoCallGasLimit, + } + return keeper.SetParams(ctx, cmParams) +} + func migrateCronParams(ctx sdk.Context, paramsKeepers paramskeeper.Keeper, storeKey storetypes.StoreKey, codec codec.Codec) error { store := ctx.KVStore(storeKey) var currParams crontypes.Params diff --git a/neutronutils/errors/errors.go b/neutronutils/errors/errors.go index 7aecf5e13..9dd7574d9 100644 --- a/neutronutils/errors/errors.go +++ b/neutronutils/errors/errors.go @@ -6,7 +6,7 @@ import ( ) // OutOfGasRecovery converts `out of gas` panic into an error -// leave unprocessed any other kinds of panics +// leaving unprocessed any other kinds of panics func OutOfGasRecovery( gasMeter sdk.GasMeter, err *error, diff --git a/neutronutils/utils.go b/neutronutils/utils.go index fb621d701..2308c2a0f 100644 --- a/neutronutils/utils.go +++ b/neutronutils/utils.go @@ -3,9 +3,9 @@ package neutronutils import sdk "github.com/cosmos/cosmos-sdk/types" // CreateCachedContext creates a cached context with a limited gas meter. -func CreateCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func(), sdk.GasMeter) { +func CreateCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func()) { cacheCtx, writeFn := ctx.CacheContext() gasMeter := sdk.NewGasMeter(gasLimit) cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn, gasMeter + return cacheCtx, writeFn } diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 688150859..d1deab8fa 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -34,12 +34,11 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-27 packet acknowledgement: %v", err) } - cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) func() { - defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events if ack.GetError() != "" { @@ -49,7 +48,6 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack _, err = k.contractManagerKeeper.SudoResponse(cacheCtx, icaOwner.GetContract(), packet, ack.GetResult()) } }() - if err != nil { // the contract either returned an error or panicked with `out of gas` k.contractManagerKeeper.AddContractFailure(ctx, &packet, icaOwner.GetContract().String(), contractmanagertypes.Ack, &ack) @@ -58,7 +56,7 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") return nil } @@ -75,15 +73,13 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela return errors.Wrap(err, "failed to get ica owner from port") } - cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) func() { - defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) }() - if err != nil { // the contract either returned an error or panicked with `out of gas` k.contractManagerKeeper.AddContractFailure(ctx, &packet, icaOwner.GetContract().String(), contractmanagertypes.Timeout, nil) @@ -92,7 +88,7 @@ func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, rela writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") return nil } diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index 550c7bb37..a3a7b72ed 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -34,13 +34,11 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P return nil } - cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - - // distribute fee im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) func() { - defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) if ack.Success() { _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) } else { @@ -50,7 +48,6 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P _, err = im.ContractManagerKeeper.SudoError(cacheCtx, senderAddress, packet, ack.GetError()) } }() - if err != nil { // the contract either returned an error or panicked with `out of gas` im.ContractManagerKeeper.AddContractFailure(ctx, &packet, senderAddress.String(), contractmanagertypes.Ack, &ack) @@ -59,7 +56,7 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) @@ -81,15 +78,13 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r return nil } - cacheCtx, writeFn, newGasMeter := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - - // distribute fee im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) func() { - defer neutronerrors.OutOfGasRecovery(newGasMeter, &err) + defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) }() - if err != nil { // the contract either returned an error or panicked with `out of gas` im.ContractManagerKeeper.AddContractFailure(ctx, &packet, senderAddress.String(), contractmanagertypes.Timeout, nil) @@ -98,7 +93,7 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r writeFn() } - ctx.GasMeter().ConsumeGas(newGasMeter.GasConsumedToLimit(), "consume gas from cached context") + ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") return nil } From b9b41aeae99eb1601fc2ddee1cc8f5737d76a834 Mon Sep 17 00:00:00 2001 From: swelf Date: Fri, 8 Sep 2023 11:02:32 +0300 Subject: [PATCH 12/18] extracted "an executing sudo in cached limited context" into middleware --- app/app.go | 4 +- .../contractmanager/types/expected_keepers.go | 4 +- .../types/expected_keepers.go | 46 ---- .../interchaintxs/types/expected_keepers.go | 16 +- .../mocks/transfer/types/expected_keepers.go | 51 +---- wasmbinding/test/custom_message_test.go | 4 +- x/contractmanager/ibc_middleware.go | 79 +++++++ x/contractmanager/ibc_middleware_test.go | 82 +++++++ x/contractmanager/keeper/failure.go | 4 +- x/contractmanager/keeper/failure_test.go | 12 +- x/contractmanager/keeper/sudo.go | 78 +++---- x/contractmanager/keeper/sudo_test.go | 73 +----- x/contractmanager/types/module.go | 16 ++ x/contractmanager/types/sudo.go | 22 +- x/interchainqueries/types/expected_keepers.go | 4 - x/interchaintxs/keeper/ibc_handlers.go | 72 +++--- x/interchaintxs/keeper/ibc_handlers_test.go | 209 ++++++++---------- x/interchaintxs/types/expected_keepers.go | 6 +- x/transfer/ibc_handlers.go | 53 +---- x/transfer/ibc_handlers_test.go | 188 ++-------------- x/transfer/types/expected_keepers.go | 8 +- 21 files changed, 413 insertions(+), 618 deletions(-) create mode 100644 x/contractmanager/ibc_middleware.go create mode 100644 x/contractmanager/ibc_middleware_test.go create mode 100644 x/contractmanager/types/module.go diff --git a/app/app.go b/app/app.go index aaebe1645..c6dd3a84e 100644 --- a/app/app.go +++ b/app/app.go @@ -563,7 +563,7 @@ func New( app.BankKeeper, scopedTransferKeeper, app.FeeKeeper, - app.ContractManagerKeeper, + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper), ) app.RouterKeeper.SetTransferKeeper(app.TransferKeeper.Keeper) @@ -660,7 +660,7 @@ func New( memKeys[interchaintxstypes.MemStoreKey], app.IBCKeeper.ChannelKeeper, app.ICAControllerKeeper, - app.ContractManagerKeeper, + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper), app.FeeKeeper, ) diff --git a/testutil/mocks/contractmanager/types/expected_keepers.go b/testutil/mocks/contractmanager/types/expected_keepers.go index 463ef15c4..40b855702 100644 --- a/testutil/mocks/contractmanager/types/expected_keepers.go +++ b/testutil/mocks/contractmanager/types/expected_keepers.go @@ -51,7 +51,7 @@ func (mr *MockWasmKeeperMockRecorder) HasContractInfo(ctx, contractAddress inter // Sudo mocks base method. func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddress, msg []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Sudo", ctx, contractAddress, msg) + ret := m.ctrl.Call(m, "sudo", ctx, contractAddress, msg) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 @@ -60,5 +60,5 @@ func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddres // Sudo indicates an expected call of Sudo. func (mr *MockWasmKeeperMockRecorder) Sudo(ctx, contractAddress, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) } diff --git a/testutil/mocks/interchainqueries/types/expected_keepers.go b/testutil/mocks/interchainqueries/types/expected_keepers.go index 435a54b05..029ca0135 100644 --- a/testutil/mocks/interchainqueries/types/expected_keepers.go +++ b/testutil/mocks/interchainqueries/types/expected_keepers.go @@ -10,7 +10,6 @@ import ( types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/auth/types" types1 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - types2 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" gomock "github.com/golang/mock/gomock" ) @@ -153,21 +152,6 @@ func (mr *MockContractManagerKeeperMockRecorder) HasContractInfo(ctx, contractAd return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasContractInfo", reflect.TypeOf((*MockContractManagerKeeper)(nil).HasContractInfo), ctx, contractAddress) } -// SudoError mocks base method. -func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types2.Packet, details string) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, details) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoError indicates an expected call of SudoError. -func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, details interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, details) -} - // SudoKVQueryResult mocks base method. func (m *MockContractManagerKeeper) SudoKVQueryResult(ctx types.Context, contractAddress types.AccAddress, queryID uint64) ([]byte, error) { m.ctrl.T.Helper() @@ -183,36 +167,6 @@ func (mr *MockContractManagerKeeperMockRecorder) SudoKVQueryResult(ctx, contract return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoKVQueryResult", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoKVQueryResult), ctx, contractAddress, queryID) } -// SudoResponse mocks base method. -func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types2.Packet, msg []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, msg) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoResponse indicates an expected call of SudoResponse. -func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, msg interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, msg) -} - -// SudoTimeout mocks base method. -func (m *MockContractManagerKeeper) SudoTimeout(ctx types.Context, senderAddress types.AccAddress, request types2.Packet) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoTimeout", ctx, senderAddress, request) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoTimeout indicates an expected call of SudoTimeout. -func (mr *MockContractManagerKeeperMockRecorder) SudoTimeout(ctx, senderAddress, request interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoTimeout", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoTimeout), ctx, senderAddress, request) -} - // SudoTxQueryResult mocks base method. func (m *MockContractManagerKeeper) SudoTxQueryResult(ctx types.Context, contractAddress types.AccAddress, queryID uint64, height types1.Height, data []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/testutil/mocks/interchaintxs/types/expected_keepers.go b/testutil/mocks/interchaintxs/types/expected_keepers.go index e3d9c948a..85fa21af1 100644 --- a/testutil/mocks/interchaintxs/types/expected_keepers.go +++ b/testutil/mocks/interchaintxs/types/expected_keepers.go @@ -156,18 +156,18 @@ func (mr *MockContractManagerKeeperMockRecorder) HasContractInfo(ctx, contractAd } // SudoError mocks base method. -func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, details string) ([]byte, error) { +func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, ack types3.Acknowledgement) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, details) + ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, ack) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // SudoError indicates an expected call of SudoError. -func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, details interface{}) *gomock.Call { +func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, ack interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, details) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, ack) } // SudoOnChanOpenAck mocks base method. @@ -186,18 +186,18 @@ func (mr *MockContractManagerKeeperMockRecorder) SudoOnChanOpenAck(ctx, contract } // SudoResponse mocks base method. -func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, msg []byte) ([]byte, error) { +func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, ack types3.Acknowledgement) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, msg) + ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, ack) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // SudoResponse indicates an expected call of SudoResponse. -func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, msg interface{}) *gomock.Call { +func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, ack interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, ack) } // SudoTimeout mocks base method. diff --git a/testutil/mocks/transfer/types/expected_keepers.go b/testutil/mocks/transfer/types/expected_keepers.go index f5c4d2ab1..122edaeb2 100644 --- a/testutil/mocks/transfer/types/expected_keepers.go +++ b/testutil/mocks/transfer/types/expected_keepers.go @@ -11,8 +11,7 @@ import ( types0 "github.com/cosmos/cosmos-sdk/x/auth/types" types1 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" gomock "github.com/golang/mock/gomock" - types2 "github.com/neutron-org/neutron/x/contractmanager/types" - types3 "github.com/neutron-org/neutron/x/feerefunder/types" + types2 "github.com/neutron-org/neutron/x/feerefunder/types" ) // MockContractManagerKeeper is a mock of ContractManagerKeeper interface. @@ -38,32 +37,6 @@ func (m *MockContractManagerKeeper) EXPECT() *MockContractManagerKeeperMockRecor return m.recorder } -// AddContractFailure mocks base method. -func (m *MockContractManagerKeeper) AddContractFailure(ctx types.Context, packet *types1.Packet, address, ackType string, ack *types1.Acknowledgement) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "AddContractFailure", ctx, packet, address, ackType, ack) -} - -// AddContractFailure indicates an expected call of AddContractFailure. -func (mr *MockContractManagerKeeperMockRecorder) AddContractFailure(ctx, packet, address, ackType, ack interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContractFailure", reflect.TypeOf((*MockContractManagerKeeper)(nil).AddContractFailure), ctx, packet, address, ackType, ack) -} - -// GetParams mocks base method. -func (m *MockContractManagerKeeper) GetParams(ctx types.Context) types2.Params { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetParams", ctx) - ret0, _ := ret[0].(types2.Params) - return ret0 -} - -// GetParams indicates an expected call of GetParams. -func (mr *MockContractManagerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockContractManagerKeeper)(nil).GetParams), ctx) -} - // HasContractInfo mocks base method. func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { m.ctrl.T.Helper() @@ -79,33 +52,33 @@ func (mr *MockContractManagerKeeperMockRecorder) HasContractInfo(ctx, contractAd } // SudoError mocks base method. -func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, details string) ([]byte, error) { +func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, ack types1.Acknowledgement) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, details) + ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, ack) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // SudoError indicates an expected call of SudoError. -func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, details interface{}) *gomock.Call { +func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, ack interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, details) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, ack) } // SudoResponse mocks base method. -func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, msg []byte) ([]byte, error) { +func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, ack types1.Acknowledgement) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, msg) + ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, ack) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // SudoResponse indicates an expected call of SudoResponse. -func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, msg interface{}) *gomock.Call { +func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, ack interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, ack) } // SudoTimeout mocks base method. @@ -147,7 +120,7 @@ func (m *MockFeeRefunderKeeper) EXPECT() *MockFeeRefunderKeeperMockRecorder { } // DistributeAcknowledgementFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types3.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types2.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeAcknowledgementFee", ctx, receiver, packetID) } @@ -159,7 +132,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeAcknowledgementFee(ctx, r } // DistributeTimeoutFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types3.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types2.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeTimeoutFee", ctx, receiver, packetID) } @@ -171,7 +144,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeTimeoutFee(ctx, receiver, } // LockFees mocks base method. -func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types3.PacketID, fee types3.Fee) error { +func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types2.PacketID, fee types2.Fee) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LockFees", ctx, payer, packetID, fee) ret0, _ := ret[0].(error) diff --git a/wasmbinding/test/custom_message_test.go b/wasmbinding/test/custom_message_test.go index d802dbe44..384b84cd6 100644 --- a/wasmbinding/test/custom_message_test.go +++ b/wasmbinding/test/custom_message_test.go @@ -640,7 +640,7 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureTimeout() { // Add failure packet := ibcchanneltypes.Packet{} ack := ibcchanneltypes.Acknowledgement{ - Response: &ibcchanneltypes.Acknowledgement_Error{Error: "Error"}, + Response: &ibcchanneltypes.Acknowledgement_Error{Error: "ErrorSudoPayload"}, } failureID := suite.messenger.ContractmanagerKeeper.GetNextFailureIDKey(suite.ctx, suite.contractAddress.String()) suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, &packet, suite.contractAddress.String(), "timeout", &ack) @@ -673,7 +673,7 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureFromDifferentContract( // Add failure packet := ibcchanneltypes.Packet{} ack := ibcchanneltypes.Acknowledgement{ - Response: &ibcchanneltypes.Acknowledgement_Error{Error: "Error"}, + Response: &ibcchanneltypes.Acknowledgement_Error{Error: "ErrorSudoPayload"}, } failureID := suite.messenger.ContractmanagerKeeper.GetNextFailureIDKey(suite.ctx, testutil.TestOwnerAddress) suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, &packet, testutil.TestOwnerAddress, contractmanagertypes.Ack, &ack) diff --git a/x/contractmanager/ibc_middleware.go b/x/contractmanager/ibc_middleware.go new file mode 100644 index 000000000..237c7f731 --- /dev/null +++ b/x/contractmanager/ibc_middleware.go @@ -0,0 +1,79 @@ +package contractmanager + +import ( + "fmt" + "github.com/cometbft/cometbft/libs/log" + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/neutron-org/neutron/neutronutils" + neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" + contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" +) + +type SudoLimitWrapper struct { + contractmanagertypes.ContractManagerWrapper +} + +// NewSudoLimitWrapper suppresses an error from a sudo contract handler and saves it to a store +func NewSudoLimitWrapper(keeper contractmanagertypes.ContractManagerWrapper) contractmanagertypes.ContractManagerWrapper { + return SudoLimitWrapper{ + keeper, + } +} + +func (k SudoLimitWrapper) SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) { + err := k.sudo(ctx, senderAddress, request, &ack) + if err != nil { + k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "Result") + } + return nil, nil +} + +func (k SudoLimitWrapper) SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) { + err := k.sudo(ctx, senderAddress, request, &ack) + if err != nil { + k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "ErrorSudoPayload") + } + return nil, nil +} + +func (k SudoLimitWrapper) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) { + err := k.sudo(ctx, senderAddress, request, nil) + if err != nil { + k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "Timeout") + } + return nil, nil +} + +// sudo calls underlying sudo handlers with a limited amount of gas +// in case of `out of gas` panic it converts the panic into an error and stops `out of gas` panic propagation +func (k SudoLimitWrapper) sudo(ctx sdk.Context, sender sdk.AccAddress, packet channeltypes.Packet, ack *channeltypes.Acknowledgement) (err error) { + ackType := contractmanagertypes.Ack + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.ContractManagerWrapper.GetParams(ctx).SudoCallGasLimit) + func() { + defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) + // Actually we have only one kind of error returned from acknowledgement + // maybe later we'll retrieve actual errors from events + if ack == nil { + ackType = contractmanagertypes.Timeout + _, err = k.ContractManagerWrapper.SudoTimeout(cacheCtx, sender, packet) + } else if ack.GetError() != "" { + _, err = k.ContractManagerWrapper.SudoError(cacheCtx, sender, packet, *ack) + } else { + _, err = k.ContractManagerWrapper.SudoResponse(cacheCtx, sender, packet, *ack) + } + }() + if err != nil { + // the contract either returned an error or panicked with `out of gas` + k.ContractManagerWrapper.AddContractFailure(ctx, &packet, sender.String(), ackType, ack) + } else { + writeFn() + } + + ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") + return +} + +func (k SudoLimitWrapper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", contractmanagertypes.ModuleName)) +} diff --git a/x/contractmanager/ibc_middleware_test.go b/x/contractmanager/ibc_middleware_test.go new file mode 100644 index 000000000..57012be71 --- /dev/null +++ b/x/contractmanager/ibc_middleware_test.go @@ -0,0 +1,82 @@ +package contractmanager + +import ( + tmdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/golang/mock/gomock" + mock_types "github.com/neutron-org/neutron/testutil/mocks/interchaintxs/types" + "github.com/neutron-org/neutron/x/contractmanager/types" + "github.com/stretchr/testify/require" + "testing" +) + +var ( + ShouldNotBeWrittenKey = []byte("shouldnotkey") + ShouldNotBeWritten = []byte("should not be written") + ShouldBeWritten = []byte("should be written") + TestOwnerAddress = "neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh" +) + +func ShouldBeWrittenKey(suffix string) []byte { + return append([]byte("shouldkey"), []byte(suffix)...) +} + +func NewSudoLimitMiddleware(t testing.TB, cm types.ContractManagerWrapper) (SudoLimitWrapper, sdk.Context, *storetypes.KVStoreKey) { + storeKey := sdk.NewKVStoreKey(types.StoreKey) + + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + k := SudoLimitWrapper{ContractManagerWrapper: cm} + + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + return k, ctx, storeKey +} + +func TestSudo(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + middleware, infCtx, storeKey := NewSudoLimitMiddleware(t, cmKeeper) + st := infCtx.KVStore(storeKey) + + p := channeltypes.Packet{ + Sequence: 100, + SourcePort: icatypes.ControllerPortPrefix + TestOwnerAddress + ".ica0", + SourceChannel: "channel-0", + } + contractAddress := sdk.AccAddress{} + errACK := channeltypes.Acknowledgement{ + Response: &channeltypes.Acknowledgement_Error{ + Error: "error", + }, + } + //errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) + //require.NoError(t, err) + //resACK := channeltypes.Acknowledgement{ + // Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, + //} + //resAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&resACK) + //require.NoError(t, err) + + // success during SudoError + ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, errAck channeltypes.Acknowledgement) { + st := cachedCtx.KVStore(storeKey) + st.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) + }).Return(nil, nil) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) + err := middleware.sudo(ctx, contractAddress, p, &errACK) + require.NoError(t, err) + require.Equal(t, ShouldBeWritten, st.Get(ShouldBeWrittenKey("sudoerror"))) + require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) +} diff --git a/x/contractmanager/keeper/failure.go b/x/contractmanager/keeper/failure.go index 308decb34..581b4f19c 100644 --- a/x/contractmanager/keeper/failure.go +++ b/x/contractmanager/keeper/failure.go @@ -81,11 +81,11 @@ func (k Keeper) ResubmitFailure(ctx sdk.Context, contractAddr sdk.AccAddress, fa return errors.Wrapf(types.IncorrectFailureToResubmit, "cannot resubmit failure without acknowledgement; failureId = %d", failure.Id) } if failure.GetAck().GetError() == "" { - if _, err := k.SudoResponse(ctx, contractAddr, *failure.Packet, failure.Ack.GetResult()); err != nil { + if _, err := k.SudoResponse(ctx, contractAddr, *failure.Packet, *failure.Ack); err != nil { return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack response; failureId = %d; err = %s", failure.Id, err) } } else { - if _, err := k.SudoError(ctx, contractAddr, *failure.Packet, failure.Ack.GetError()); err != nil { + if _, err := k.SudoError(ctx, contractAddr, *failure.Packet, *failure.Ack); err != nil { return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack error; failureId = %d; err = %s", failure.Id, err) } } diff --git a/x/contractmanager/keeper/failure_test.go b/x/contractmanager/keeper/failure_test.go index 606db61c9..aa00b4c91 100644 --- a/x/contractmanager/keeper/failure_test.go +++ b/x/contractmanager/keeper/failure_test.go @@ -138,7 +138,6 @@ func TestResubmitFailure(t *testing.T) { require.NoError(t, err) // case: successful resubmit with ack and ack = response - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgSuc).Return([]byte{}, nil) failure, err := k.GetFailure(ctx, contractAddr, failureID) @@ -153,8 +152,7 @@ func TestResubmitFailure(t *testing.T) { failureID2 := k.GetNextFailureIDKey(ctx, contractAddr.String()) k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ack) - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) - wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgSuc).Return(nil, fmt.Errorf("failed to Sudo")) + wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgSuc).Return(nil, fmt.Errorf("failed to sudo")) failure2, err := k.GetFailure(ctx, contractAddr, failureID2) require.NoError(t, err) @@ -170,7 +168,6 @@ func TestResubmitFailure(t *testing.T) { failureID3 := k.GetNextFailureIDKey(ctx, contractAddr.String()) k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ackError) - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgErr).Return([]byte{}, nil) failure3, err := k.GetFailure(ctx, contractAddr, failureID3) @@ -185,8 +182,7 @@ func TestResubmitFailure(t *testing.T) { failureID4 := k.GetNextFailureIDKey(ctx, contractAddr.String()) k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ackError) - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) - wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgErr).Return(nil, fmt.Errorf("failed to Sudo")) + wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgErr).Return(nil, fmt.Errorf("failed to sudo")) failure4, err := k.GetFailure(ctx, contractAddr, failureID4) require.NoError(t, err) @@ -202,7 +198,6 @@ func TestResubmitFailure(t *testing.T) { failureID5 := k.GetNextFailureIDKey(ctx, contractAddr.String()) k.AddContractFailure(ctx, &packet, contractAddr.String(), "timeout", nil) - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgTimeout).Return([]byte{}, nil) failure5, err := k.GetFailure(ctx, contractAddr, failureID5) @@ -217,8 +212,7 @@ func TestResubmitFailure(t *testing.T) { failureID6 := k.GetNextFailureIDKey(ctx, contractAddr.String()) k.AddContractFailure(ctx, &packet, contractAddr.String(), "timeout", nil) - wk.EXPECT().HasContractInfo(gomock.AssignableToTypeOf(ctx), contractAddr).Return(true) - wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgTimeout).Return(nil, fmt.Errorf("failed to Sudo")) + wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgTimeout).Return(nil, fmt.Errorf("failed to sudo")) failure6, err := k.GetFailure(ctx, contractAddr, failureID6) require.NoError(t, err) diff --git a/x/contractmanager/keeper/sudo.go b/x/contractmanager/keeper/sudo.go index 2feba728a..1b33639cb 100644 --- a/x/contractmanager/keeper/sudo.go +++ b/x/contractmanager/keeper/sudo.go @@ -20,26 +20,25 @@ func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) return k.wasmKeeper.HasContractInfo(ctx, contractAddress) } +func prepareSudoCallbackMessage(request channeltypes.Packet, ack *channeltypes.Acknowledgement) types.MessageSudoCallback { + m := types.MessageSudoCallback{ + Response: nil, + Error: nil, + Timeout: nil, + } + return m +} + func (k Keeper) SudoResponse( ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, - msg []byte, + ack channeltypes.Acknowledgement, ) ([]byte, error) { - k.Logger(ctx).Debug("SudoResponse", "senderAddress", senderAddress, "request", request, "msg", msg) - - if !k.wasmKeeper.HasContractInfo(ctx, senderAddress) { - if request.SourcePort == types.TransferPort { - // we want to allow non contract account to send the assets via IBC Transfer module - // we can determine the originating module by the source port of the request packet - return nil, nil - } - k.Logger(ctx).Debug("SudoResponse: contract not found", "senderAddress", senderAddress) - return nil, fmt.Errorf("%s is not a contract address and not the Transfer module", senderAddress) - } + k.Logger(ctx).Debug("SudoResponse", "senderAddress", senderAddress, "request", request, "msg", ack.GetResult()) x := types.MessageResponse{} - x.Response.Data = msg + x.Response.Data = ack.GetResult() x.Response.Request = request m, err := json.Marshal(x) if err != nil { @@ -50,9 +49,9 @@ func (k Keeper) SudoResponse( resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoResponse: failed to Sudo", + k.Logger(ctx).Debug("SudoResponse: failed to sudo", "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil @@ -65,16 +64,6 @@ func (k Keeper) SudoTimeout( ) ([]byte, error) { k.Logger(ctx).Info("SudoTimeout", "senderAddress", senderAddress, "request", request) - if !k.wasmKeeper.HasContractInfo(ctx, senderAddress) { - if request.SourcePort == types.TransferPort { - // we want to allow non contract account to send the assets via IBC Transfer module - // we can determine the originating module by the source port of the request packet - return nil, nil - } - k.Logger(ctx).Debug("SudoTimeout: contract not found", "senderAddress", senderAddress) - return nil, fmt.Errorf("%s is not a contract address and not the Transfer module", senderAddress) - } - x := types.MessageTimeout{} x.Timeout.Request = request m, err := json.Marshal(x) @@ -88,9 +77,9 @@ func (k Keeper) SudoTimeout( resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoTimeout: failed to Sudo", + k.Logger(ctx).Debug("SudoTimeout: failed to sudo", "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil @@ -100,23 +89,13 @@ func (k Keeper) SudoError( ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, - details string, + ack channeltypes.Acknowledgement, ) ([]byte, error) { k.Logger(ctx).Debug("SudoError", "senderAddress", senderAddress, "request", request) - if !k.wasmKeeper.HasContractInfo(ctx, senderAddress) { - if request.SourcePort == types.TransferPort { - // we want to allow non contract account to send the assets via IBC Transfer module - // we can determine the originating module by the source port of the request packet - return nil, nil - } - k.Logger(ctx).Debug("SudoError: contract not found", "senderAddress", senderAddress) - return nil, fmt.Errorf("%s is not a contract address and not the Transfer module", senderAddress) - } - x := types.MessageError{} x.Error.Request = request - x.Error.Details = details + x.Error.Details = ack.GetError() m, err := json.Marshal(x) if err != nil { k.Logger(ctx).Error("SudoError: failed to marshal MessageError message", @@ -126,9 +105,9 @@ func (k Keeper) SudoError( resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoError: failed to Sudo", + k.Logger(ctx).Debug("SudoError: failed to sudo", "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil @@ -141,11 +120,6 @@ func (k Keeper) SudoOnChanOpenAck( ) ([]byte, error) { k.Logger(ctx).Debug("SudoOnChanOpenAck", "contractAddress", contractAddress) - if !k.wasmKeeper.HasContractInfo(ctx, contractAddress) { - k.Logger(ctx).Debug("SudoOnChanOpenAck: contract not found", "contractAddress", contractAddress) - return nil, fmt.Errorf("%s is not a contract address", contractAddress) - } - x := types.MessageOnChanOpenAck{} x.OpenAck = details m, err := json.Marshal(x) @@ -158,9 +132,9 @@ func (k Keeper) SudoOnChanOpenAck( resp, err := k.wasmKeeper.Sudo(ctx, contractAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoOnChanOpenAck: failed to Sudo", + k.Logger(ctx).Debug("SudoOnChanOpenAck: failed to sudo", "error", err, "contract_address", contractAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil @@ -198,9 +172,9 @@ func (k Keeper) SudoTxQueryResult( resp, err := k.wasmKeeper.Sudo(ctx, contractAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoTxQueryResult: failed to Sudo", + k.Logger(ctx).Debug("SudoTxQueryResult: failed to sudo", "error", err, "contract_address", contractAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil @@ -232,9 +206,9 @@ func (k Keeper) SudoKVQueryResult( resp, err := k.wasmKeeper.Sudo(ctx, contractAddress, m) if err != nil { - k.Logger(ctx).Debug("SudoKVQueryResult: failed to Sudo", + k.Logger(ctx).Debug("SudoKVQueryResult: failed to sudo", "error", err, "contract_address", contractAddress) - return nil, fmt.Errorf("failed to Sudo: %v", err) + return nil, fmt.Errorf("failed to sudo: %v", err) } return resp, nil diff --git a/x/contractmanager/keeper/sudo_test.go b/x/contractmanager/keeper/sudo_test.go index b4adc4103..8767acd93 100644 --- a/x/contractmanager/keeper/sudo_test.go +++ b/x/contractmanager/keeper/sudo_test.go @@ -48,34 +48,18 @@ func TestSudoResponse(t *testing.T) { sudoErrorMsg := types.MessageResponse{} p := channeltypes.Packet{} - sudoErrorMsg.Response.Data = []byte("data") + a := channeltypes.Acknowledgement{Response: &channeltypes.Acknowledgement_Result{Result: []byte("data")}} + sudoErrorMsg.Response.Data = a.GetResult() sudoErrorMsg.Response.Request = p wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return([]byte("success"), nil) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) - resp, err := k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, sudoErrorMsg.Response.Data) + resp, err := k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, a) require.NoError(t, err) require.Equal(t, []byte("success"), resp) wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return(nil, fmt.Errorf("internal contract error")) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) - resp, err = k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, sudoErrorMsg.Response.Data) + resp, err = k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, a) require.Nil(t, resp) require.ErrorContains(t, err, "internal contract error") - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoResponse(ctx, address, channeltypes.Packet{}, nil) - require.Nil(t, resp) - require.ErrorContains(t, err, "is not a contract address and not the Transfer module") - - sudoResponseTransport := types.MessageResponse{} - p = channeltypes.Packet{SourcePort: types.TransferPort} - sudoResponseTransport.Response.Data = []byte("data") - sudoResponseTransport.Response.Request = p - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - _, err = k.SudoResponse(ctx, address, sudoResponseTransport.Response.Request, sudoResponseTransport.Response.Data) - require.Nil(t, err) - require.NoError(t, err) } func TestSudoError(t *testing.T) { @@ -88,34 +72,20 @@ func TestSudoError(t *testing.T) { sudoErrorMsg := types.MessageError{} p := channeltypes.Packet{} - sudoErrorMsg.Error.Details = "details" + a := channeltypes.Acknowledgement{Response: &channeltypes.Acknowledgement_Error{ + Error: "details", + }} + sudoErrorMsg.Error.Details = a.GetError() sudoErrorMsg.Error.Request = p wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return([]byte("success"), nil) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) - resp, err := k.SudoError(ctx, address, sudoErrorMsg.Error.Request, sudoErrorMsg.Error.Details) + resp, err := k.SudoError(ctx, address, sudoErrorMsg.Error.Request, a) require.NoError(t, err) require.Equal(t, []byte("success"), resp) wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return(nil, fmt.Errorf("internal contract error")) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) - resp, err = k.SudoError(ctx, address, sudoErrorMsg.Error.Request, sudoErrorMsg.Error.Details) + resp, err = k.SudoError(ctx, address, sudoErrorMsg.Error.Request, a) require.Nil(t, resp) require.ErrorContains(t, err, "internal contract error") - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoError(ctx, address, channeltypes.Packet{}, "") - require.Nil(t, resp) - require.ErrorContains(t, err, "is not a contract address and not the Transfer module") - - sudoErrorTransport := types.MessageError{} - p = channeltypes.Packet{SourcePort: types.TransferPort} - sudoErrorTransport.Error.Details = "details" - sudoErrorTransport.Error.Request = p - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoError(ctx, address, sudoErrorTransport.Error.Request, sudoErrorTransport.Error.Details) - require.Nil(t, resp) - require.NoError(t, err) } func TestSudoTimeout(t *testing.T) { @@ -130,30 +100,14 @@ func TestSudoTimeout(t *testing.T) { p := channeltypes.Packet{} sudoTimeoutMsg.Timeout.Request = p wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoTimeoutMsg)).Return([]byte("success"), nil) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) resp, err := k.SudoTimeout(ctx, address, sudoTimeoutMsg.Timeout.Request) require.NoError(t, err) require.Equal(t, []byte("success"), resp) wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoTimeoutMsg)).Return(nil, fmt.Errorf("internal contract error")) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) resp, err = k.SudoTimeout(ctx, address, sudoTimeoutMsg.Timeout.Request) require.Nil(t, resp) require.ErrorContains(t, err, "internal contract error") - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoTimeout(ctx, address, channeltypes.Packet{}) - require.Nil(t, resp) - require.ErrorContains(t, err, "is not a contract address and not the Transfer module") - - sudoTimeoutTransport := types.MessageTimeout{} - p = channeltypes.Packet{SourcePort: types.TransferPort} - sudoTimeoutTransport.Timeout.Request = p - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoTimeout(ctx, address, sudoTimeoutTransport.Timeout.Request) - require.Nil(t, resp) - require.NoError(t, err) } func TestSudoOnChanOpen(t *testing.T) { @@ -166,21 +120,14 @@ func TestSudoOnChanOpen(t *testing.T) { sudoOpenAckMsg := types.MessageOnChanOpenAck{} wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoOpenAckMsg)).Return([]byte("success"), nil) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) resp, err := k.SudoOnChanOpenAck(ctx, address, sudoOpenAckMsg.OpenAck) require.NoError(t, err) require.Equal(t, []byte("success"), resp) wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoOpenAckMsg)).Return(nil, fmt.Errorf("internal contract error")) - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) resp, err = k.SudoOnChanOpenAck(ctx, address, sudoOpenAckMsg.OpenAck) require.Nil(t, resp) require.ErrorContains(t, err, "internal contract error") - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - resp, err = k.SudoOnChanOpenAck(ctx, address, sudoOpenAckMsg.OpenAck) - require.Nil(t, resp) - require.ErrorContains(t, err, "is not a contract address") } func TestSudoTxQueryResult(t *testing.T) { diff --git a/x/contractmanager/types/module.go b/x/contractmanager/types/module.go new file mode 100644 index 000000000..540b2afd3 --- /dev/null +++ b/x/contractmanager/types/module.go @@ -0,0 +1,16 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" +) + +type ContractManagerWrapper interface { + HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool + AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) + SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) + SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) + SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) + SudoOnChanOpenAck(ctx sdk.Context, contractAddress sdk.AccAddress, details OpenAckDetails) ([]byte, error) + GetParams(ctx sdk.Context) (params Params) +} diff --git a/x/contractmanager/types/sudo.go b/x/contractmanager/types/sudo.go index c0564d82a..8b2d0144d 100644 --- a/x/contractmanager/types/sudo.go +++ b/x/contractmanager/types/sudo.go @@ -5,8 +5,6 @@ import ( channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ) -const TransferPort = "transfer" - // MessageTxQueryResult is passed to a contract's sudo() entrypoint when a tx was submitted // for a transaction query. type MessageTxQueryResult struct { @@ -25,6 +23,26 @@ type MessageKVQueryResult struct { } `json:"kv_query_result"` } +type MessageSudoCallback struct { + Response *ResponseSudoPayload `json:"response"` + Error *ErrorSudoPayload `json:"error"` + Timeout *TimeoutPayload `json:"timeout"` +} + +type ResponseSudoPayload struct { + Request channeltypes.Packet `json:"request"` + Data []byte `json:"data"` // Message data +} + +type ErrorSudoPayload struct { + Request channeltypes.Packet `json:"request"` + Details string `json:"details"` +} + +type TimeoutPayload struct { + Request channeltypes.Packet `json:"request"` +} + // MessageTimeout is passed to a contract's sudo() entrypoint when an interchain // transaction failed with a timeout. type MessageTimeout struct { diff --git a/x/interchainqueries/types/expected_keepers.go b/x/interchainqueries/types/expected_keepers.go index d0cb0332f..820259470 100644 --- a/x/interchainqueries/types/expected_keepers.go +++ b/x/interchainqueries/types/expected_keepers.go @@ -4,7 +4,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -23,9 +22,6 @@ type BankKeeper interface { type ContractManagerKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, details string) ([]byte, error) - SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) SudoKVQueryResult(ctx sdk.Context, contractAddress sdk.AccAddress, queryID uint64) ([]byte, error) SudoTxQueryResult(ctx sdk.Context, contractAddress sdk.AccAddress, queryID uint64, height ibcclienttypes.Height, data []byte) ([]byte, error) } diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index d1deab8fa..4fe930e1f 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -1,8 +1,6 @@ package keeper import ( - "github.com/neutron-org/neutron/neutronutils" - neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" "time" "cosmossdk.io/errors" @@ -17,7 +15,7 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) -// HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. +// HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a sudo call. func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleAcknowledgment) @@ -34,63 +32,48 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-27 packet acknowledgement: %v", err) } + if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { + //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) + return nil + } + k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - func() { - defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) - // Actually we have only one kind of error returned from acknowledgement - // maybe later we'll retrieve actual errors from events - if ack.GetError() != "" { - - _, err = k.contractManagerKeeper.SudoError(cacheCtx, icaOwner.GetContract(), packet, ack.GetError()) - } else { - _, err = k.contractManagerKeeper.SudoResponse(cacheCtx, icaOwner.GetContract(), packet, ack.GetResult()) - } - }() - if err != nil { - // the contract either returned an error or panicked with `out of gas` - k.contractManagerKeeper.AddContractFailure(ctx, &packet, icaOwner.GetContract().String(), contractmanagertypes.Ack, &ack) - k.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) + // Actually we have only one kind of error returned from acknowledgement + // maybe later we'll retrieve actual errors from events + // `err` value from `SudoError/SudoResponse` should always be nil, since we contractmanager wrapped by `SudoLimitWrapper` + if ack.GetError() != "" { + _, err = k.contractManagerKeeper.SudoError(ctx, icaOwner.GetContract(), packet, ack) } else { - writeFn() + _, err = k.contractManagerKeeper.SudoResponse(ctx, icaOwner.GetContract(), packet, ack) } - ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") - - return nil + return err } -// HandleTimeout passes the timeout data to the appropriate contract via a Sudo call. +// HandleTimeout passes the timeout data to the appropriate contract via a sudo call. // Since all ICA channels are ORDERED, a single timeout shuts down a channel. func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleTimeout) + k.Logger(ctx).Debug("HandleTimeout") icaOwner, err := types.ICAOwnerFromPort(packet.SourcePort) - k.Logger(ctx).Debug("HandleTimeout") if err != nil { k.Logger(ctx).Error("HandleTimeout: failed to get ica owner from source port", "error", err) return errors.Wrap(err, "failed to get ica owner from port") } - k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - func() { - defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) - _, err = k.contractManagerKeeper.SudoTimeout(cacheCtx, icaOwner.GetContract(), packet) - }() - if err != nil { - // the contract either returned an error or panicked with `out of gas` - k.contractManagerKeeper.AddContractFailure(ctx, &packet, icaOwner.GetContract().String(), contractmanagertypes.Timeout, nil) - k.Logger(ctx).Error("HandleTimeout: failed to Sudo contract on packet timeout", "error", err) - } else { - writeFn() + if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { + //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) + return nil } - ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") + k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) + + // `err` value from `SudoTimeout` should always be nil, since we contractmanager wrapped by `SudoLimitWrapper` + _, err = k.contractManagerKeeper.SudoTimeout(ctx, icaOwner.GetContract(), packet) - return nil + return err } // HandleChanOpenAck passes the data about a successfully created channel to the appropriate contract @@ -113,6 +96,11 @@ func (k *Keeper) HandleChanOpenAck( return errors.Wrap(err, "failed to get ica owner from port") } + if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { + //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) + return nil + } + _, err = k.contractManagerKeeper.SudoOnChanOpenAck(ctx, icaOwner.GetContract(), contractmanagertypes.OpenAckDetails{ PortID: portID, ChannelID: channelID, @@ -120,8 +108,8 @@ func (k *Keeper) HandleChanOpenAck( CounterpartyVersion: counterpartyVersion, }) if err != nil { - k.Logger(ctx).Debug("HandleChanOpenAck: failed to Sudo contract on packet timeout", "error", err) - return errors.Wrap(err, "failed to Sudo the contract OnChanOpenAck") + k.Logger(ctx).Debug("HandleChanOpenAck: failed to sudo contract on packet timeout", "error", err) + return errors.Wrap(err, "failed to sudo the contract OnChanOpenAck") } return nil diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 85f435d4b..50e74b2f0 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -17,25 +17,15 @@ import ( feetypes "github.com/neutron-org/neutron/x/feerefunder/types" ) -var ( - ShouldNotBeWrittenKey = []byte("shouldnotkey") - ShouldNotBeWritten = []byte("should not be written") - ShouldBeWritten = []byte("should be written") -) - -func ShouldBeWrittenKey(suffix string) []byte { - return append([]byte("shouldkey"), []byte(suffix)...) -} - func TestHandleAcknowledgement(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, storeKey := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) + icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - store := ctx.KVStore(storeKey) + //store := ctx.KVStore(storeKey) errACK := channeltypes.Acknowledgement{ Response: &channeltypes.Acknowledgement_Error{ @@ -64,86 +54,96 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, nil, relayerAddress) require.ErrorContains(t, err, "cannot unmarshal ICS-27 packet acknowledgement") - // error during SudoResponse + // success contract SudoResponse ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 - }).Return(nil, fmt.Errorf("SudoResponse error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &resACK) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) - // error during SudoError + // success contract SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - }).Return(nil, fmt.Errorf("SudoError error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) - // success during SudoError + // error contract SudoResponse ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror"))) - require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) + cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK).Return(nil, fmt.Errorf("error sudoResponse")) + err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) + require.Error(t, err) - // out of gas during SudoError + // error contract SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") - }) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK).Return(nil, fmt.Errorf("error sudoError")) err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) + require.Error(t, err) - // success during SudoResponse + // no contract SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - require.Equal(t, uint64(3140), ctx.GasMeter().GasConsumed()) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse"))) - // not enough gas provided by relayer for SudoCallGasLimit - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") - }) - feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test + //// success during SudoError + //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + //cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { + // store := cachedCtx.KVStore(storeKey) + // store.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) + //}).Return(nil, nil) + //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) + //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) + //require.NoError(t, err) + //require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror"))) + //require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) + // + //// out of gas during SudoError + //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + //cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { + // store := cachedCtx.KVStore(storeKey) + // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + // cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") + //}) + //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) + //cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) + //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) + //require.NoError(t, err) + //require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + //require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) + // + //// success during SudoResponse + //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + //cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + // store := cachedCtx.KVStore(storeKey) + // store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value + //}).Return(nil, nil) + //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) + //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + //err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) + //require.NoError(t, err) + //require.Equal(t, uint64(3140), ctx.GasMeter().GasConsumed()) + //require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse"))) + // + //// not enough gas provided by relayer for SudoCallGasLimit + //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + //lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) + //cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + // store := cachedCtx.KVStore(storeKey) + // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + // cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") + //}) + //feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + //cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) + //require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleTimeout(t *testing.T) { @@ -152,9 +152,8 @@ func TestHandleTimeout(t *testing.T) { icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, storeKey := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) + icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - store := ctx.KVStore(storeKey) contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" relayerAddress := sdk.MustAccAddressFromBech32(relayerBech32) @@ -167,63 +166,27 @@ func TestHandleTimeout(t *testing.T) { err := icak.HandleTimeout(ctx, channeltypes.Packet{}, relayerAddress) require.ErrorContains(t, err, "failed to get ica owner from port") - gasReserved := false + // contract success ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if cachedCtx.GasMeter().Limit() == 4000 { - gasReserved = true - } - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudotimeout"), ShouldBeWritten) // consumes 3110 gas, 2000 flat write + 30 every byte of key+value - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 4000}) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p) err = icak.HandleTimeout(ctx, p, relayerAddress) - require.True(t, gasReserved) - require.Equal(t, uint64(3110), ctx.GasMeter().GasConsumed()) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout"))) require.NoError(t, err) - // error during SudoTimeOut + // contract error ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Timeout, nil) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, fmt.Errorf("SudoTimeout error")) err = icak.HandleTimeout(ctx, p, relayerAddress) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Error(t, err) - // out of gas during SudoTimeOut + // no contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(6001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Timeout, nil) - feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = icak.HandleTimeout(ctx, p, relayerAddress) - require.Equal(t, uint64(6000), ctx.GasMeter().GasConsumed()) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - - // not enough gas provided by relayer for SudoCallGasLimit - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) - feeKeeper.EXPECT().DistributeTimeoutFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleChanOpenAck(t *testing.T) { @@ -239,6 +202,7 @@ func TestHandleChanOpenAck(t *testing.T) { err := icak.HandleChanOpenAck(ctx, "", channelID, counterpartyChannelID, "1") require.ErrorContains(t, err, "failed to get ica owner from port") + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) cmKeeper.EXPECT().SudoOnChanOpenAck(ctx, contractAddress, types.OpenAckDetails{ PortID: portID, ChannelID: channelID, @@ -246,8 +210,9 @@ func TestHandleChanOpenAck(t *testing.T) { CounterpartyVersion: "1", }).Return(nil, fmt.Errorf("SudoOnChanOpenAck error")) err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") - require.ErrorContains(t, err, "failed to Sudo the contract OnChanOpenAck") + require.ErrorContains(t, err, "failed to sudo the contract OnChanOpenAck") + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) cmKeeper.EXPECT().SudoOnChanOpenAck(ctx, contractAddress, types.OpenAckDetails{ PortID: portID, ChannelID: channelID, @@ -256,4 +221,8 @@ func TestHandleChanOpenAck(t *testing.T) { }).Return(nil, nil) err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") require.NoError(t, err) + + cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") + require.NoError(t, err) } diff --git a/x/interchaintxs/types/expected_keepers.go b/x/interchaintxs/types/expected_keepers.go index a9be5877b..13d7f7cf3 100644 --- a/x/interchaintxs/types/expected_keepers.go +++ b/x/interchaintxs/types/expected_keepers.go @@ -26,11 +26,11 @@ type BankKeeper interface { type ContractManagerKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, details string) ([]byte, error) + SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) + SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) SudoOnChanOpenAck(ctx sdk.Context, contractAddress sdk.AccAddress, details contractmanagertypes.OpenAckDetails) ([]byte, error) + AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) GetParams(ctx sdk.Context) (params contractmanagertypes.Params) } diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index a3a7b72ed..efecb0cff 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -1,21 +1,16 @@ package transfer import ( - contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" - "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/neutron-org/neutron/neutronutils" - - neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" "github.com/neutron-org/neutron/x/interchaintxs/types" ) -// HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a Sudo call. +// HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a sudo call. func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { var ack channeltypes.Acknowledgement if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { @@ -36,34 +31,21 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - func() { - defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) - if ack.Success() { - _, err = im.ContractManagerKeeper.SudoResponse(cacheCtx, senderAddress, packet, ack.GetResult()) - } else { - // Actually we have only one kind of error returned from acknowledgement - // maybe later we'll retrieve actual errors from events - im.keeper.Logger(cacheCtx).Debug(ack.GetError(), "CheckTx", cacheCtx.IsCheckTx()) - _, err = im.ContractManagerKeeper.SudoError(cacheCtx, senderAddress, packet, ack.GetError()) - } - }() - if err != nil { - // the contract either returned an error or panicked with `out of gas` - im.ContractManagerKeeper.AddContractFailure(ctx, &packet, senderAddress.String(), contractmanagertypes.Ack, &ack) - im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet acknowledgement", err) + if ack.Success() { + _, err = im.ContractManagerKeeper.SudoResponse(ctx, senderAddress, packet, ack) } else { - writeFn() + // Actually we have only one kind of error returned from acknowledgement + // maybe later we'll retrieve actual errors from events + im.keeper.Logger(ctx).Debug(ack.GetError(), "CheckTx", ctx.IsCheckTx()) + _, err = im.ContractManagerKeeper.SudoError(ctx, senderAddress, packet, ack) } - ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") - im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) - return nil + return err } -// HandleTimeout passes the timeout data to the appropriate contract via a Sudo call. +// HandleTimeout passes the timeout data to the appropriate contract via a sudo call. func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { var data transfertypes.FungibleTokenPacketData if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { @@ -80,20 +62,7 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, im.ContractManagerKeeper.GetParams(ctx).SudoCallGasLimit) - func() { - defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) - _, err = im.ContractManagerKeeper.SudoTimeout(cacheCtx, senderAddress, packet) - }() - if err != nil { - // the contract either returned an error or panicked with `out of gas` - im.ContractManagerKeeper.AddContractFailure(ctx, &packet, senderAddress.String(), contractmanagertypes.Timeout, nil) - im.keeper.Logger(ctx).Debug("failed to Sudo contract on packet timeout", err) - } else { - writeFn() - } - - ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context") + _, err = im.ContractManagerKeeper.SudoTimeout(ctx, senderAddress, packet) - return nil + return err } diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index 191bbfa8d..e93d91c97 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -2,7 +2,6 @@ package transfer_test import ( "fmt" - "github.com/neutron-org/neutron/x/contractmanager/types" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -39,10 +38,9 @@ func TestHandleAcknowledgement(t *testing.T) { authKeeper := mock_types.NewMockAccountKeeper(ctrl) // required to initialize keeper authKeeper.EXPECT().GetModuleAddress(transfertypes.ModuleName).Return([]byte("address")) - txKeeper, infCtx, storeKey := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) + txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) txModule := transfer.NewIBCModule(*txKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - store := ctx.KVStore(storeKey) errACK := channeltypes.Acknowledgement{ Response: &channeltypes.Acknowledgement_Error{ @@ -94,137 +92,43 @@ func TestHandleAcknowledgement(t *testing.T) { require.NoError(t, err) p.Data = tokenBz - // error during SudoResponse non contract + // non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoResponse contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 - }).Return(nil, fmt.Errorf("SudoResponse error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &resACK) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK).Return(nil, fmt.Errorf("SudoResponse error")) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) - - // error during SudoError non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) + require.Error(t, err) // error during SudoError contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // consumes 2990 - }).Return(nil, fmt.Errorf("SudoError error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK).Return(nil, fmt.Errorf("SudoError error")) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) + require.Error(t, err) - // success during SudoError non contract + // success during SudoError ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // success during SudoError contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoerror_contract"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 9000}) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.NoError(t, err) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror_contract"))) - require.Equal(t, uint64(3320), ctx.GasMeter().GasConsumed()) - - // recoverable out of gas during SudoError non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - require.NoError(t, err) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // recoverable out of gas during SudoError contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(11001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoError error")) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 11000}) - // FIXME: fix distribution during outofgas cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK) err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(11000), ctx.GasMeter().GasConsumed()) - // check we have ReserveGas reserved and - // check gas consumption from cachedCtx has added to the main ctx - // one of the ways to check it - make the check during SudoResponse call - // non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) - require.NoError(t, err) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // contract + // success during SudoError contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved := false - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - if cachedCtx.GasMeter().Limit() == 13000 { - gasReserved = true - } - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudoresponse_contract_success"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 13000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - require.True(t, gasReserved) - require.Equal(t, uint64(3650), ctx.GasMeter().GasConsumed()) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse_contract_success"))) - - // not enough gas provided by relayer SudoCallGasLimit - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) - cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { txModule.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a test - // NOTE: looks its impossible to test store reset after panic, because test `require.PanicsWithValue` recovers the panic - // require.Empty(t, store.Get(ShouldNotBeWrittenKey)) } func TestHandleTimeout(t *testing.T) { @@ -236,10 +140,9 @@ func TestHandleTimeout(t *testing.T) { authKeeper := mock_types.NewMockAccountKeeper(ctrl) // required to initialize keeper authKeeper.EXPECT().GetModuleAddress(transfertypes.ModuleName).Return([]byte("address")) - txKeeper, infCtx, storeKey := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) + txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) txModule := transfer.NewIBCModule(*txKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - store := ctx.KVStore(storeKey) contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" relayerAddress := sdk.MustAccAddressFromBech32(relayerBech32) @@ -278,84 +181,21 @@ func TestHandleTimeout(t *testing.T) { ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) require.NoError(t, err) // success contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - gasReserved := false - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - if cachedCtx.GasMeter().Limit() == 5000 { - gasReserved = true - } - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldBeWrittenKey("sudotimeout_contract_success"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 5000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, nil) err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.True(t, gasReserved) require.NoError(t, err) - require.Equal(t, uint64(3620), ctx.GasMeter().GasConsumed()) - require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudotimeout_contract_success"))) - - // error during SudoTimeOut non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.NoError(t, err) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) // error during SudoTimeOut contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Timeout, nil) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(2990), ctx.GasMeter().GasConsumed()) - - // out of gas during SudoTimeOut non contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.NoError(t, err) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // out of gas during SudoTimeOut contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(ctx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(8001, "out of gas test") - }).Return(nil, fmt.Errorf("SudoTimeout error")) - cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Timeout, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) + cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, fmt.Errorf("SudoTimeout error")) err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.NoError(t, err) - require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - require.Equal(t, uint64(8000), ctx.GasMeter().GasConsumed()) - - // not enough gas provided by relayer for SudoCallGasLimit - lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - cmKeeper.EXPECT().SudoTimeout(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) { - store := cachedCtx.KVStore(storeKey) - store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 14000}) - cmKeeper.EXPECT().HasContractInfo(lowGasCtx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeTimeoutFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { txModule.HandleTimeout(lowGasCtx, p, relayerAddress) }) //nolint:errcheck // this is a test - // NOTE: looks its impossible to test store reset after panic, because test `require.PanicsWithValue` recovers the panic - // require.Empty(t, store.Get(ShouldNotBeWrittenKey)) + require.Error(t, err) } diff --git a/x/transfer/types/expected_keepers.go b/x/transfer/types/expected_keepers.go index af269e1d8..774daf841 100644 --- a/x/transfer/types/expected_keepers.go +++ b/x/transfer/types/expected_keepers.go @@ -4,19 +4,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" - feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" ) // ContractManagerKeeper defines the expected interface needed to add ack information about sudo failure. type ContractManagerKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, details string) ([]byte, error) + SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) + SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) - GetParams(ctx sdk.Context) (params contractmanagertypes.Params) } type FeeRefunderKeeper interface { From 5bdacb494033abc0e2518459899321df214d68e0 Mon Sep 17 00:00:00 2001 From: swelf Date: Thu, 14 Sep 2023 12:33:58 +0300 Subject: [PATCH 13/18] combined SudoResponse/Error/Timeout into Sudo --- Makefile | 2 +- app/app.go | 9 +- proto/neutron/contractmanager/failure.proto | 8 +- .../interchaintxs/keeper/interchaintxs.go | 2 +- .../contractmanager/types/expected_keepers.go | 4 +- .../interchaintxs/types/expected_keepers.go | 120 +++-------- .../mocks/transfer/types/expected_keepers.go | 70 ++----- testutil/transfer/keeper/keeper.go | 2 +- wasmbinding/test/custom_message_test.go | 18 +- .../client/cli/query_failure_test.go | 6 +- x/contractmanager/genesis.go | 2 +- x/contractmanager/genesis_test.go | 33 +-- x/contractmanager/ibc_middleware.go | 56 ++--- x/contractmanager/ibc_middleware_test.go | 161 +++++++-------- x/contractmanager/keeper/failure.go | 37 +--- x/contractmanager/keeper/failure_test.go | 76 +++---- x/contractmanager/keeper/sudo.go | 104 ++-------- x/contractmanager/keeper/sudo_test.go | 110 ---------- x/contractmanager/types/failure.pb.go | 191 ++++-------------- x/contractmanager/types/module.go | 11 +- x/contractmanager/types/sudo.go | 34 +--- x/interchaintxs/keeper/ibc_handlers.go | 46 ++--- x/interchaintxs/keeper/ibc_handlers_test.go | 108 ++++------ x/interchaintxs/keeper/keeper.go | 30 +-- x/interchaintxs/keeper/msg_server.go | 4 +- x/interchaintxs/keeper/msg_server_test.go | 30 +-- x/interchaintxs/types/expected_keepers.go | 10 +- x/transfer/ibc_handlers.go | 34 ++-- x/transfer/ibc_handlers_test.go | 79 +++----- x/transfer/keeper/keeper.go | 14 +- x/transfer/module.go | 16 +- x/transfer/types/expected_keepers.go | 7 +- 32 files changed, 454 insertions(+), 980 deletions(-) diff --git a/Makefile b/Makefile index 6af490db8..1bdcceca1 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ build-static-linux-amd64: go.sum $(BUILDDIR)/ $(DOCKER) cp neutronbinary:/bin/neutrond $(BUILDDIR)/neutrond-linux-amd64 $(DOCKER) rm -f neutronbinary -install-test-binary: check_version go.sum +install-test-binary: go.sum go install -mod=readonly $(BUILD_FLAGS_TEST_BINARY) ./cmd/neutrond ######################################## diff --git a/app/app.go b/app/app.go index c6dd3a84e..6804471df 100644 --- a/app/app.go +++ b/app/app.go @@ -563,7 +563,7 @@ func New( app.BankKeeper, scopedTransferKeeper, app.FeeKeeper, - contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper), + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), ) app.RouterKeeper.SetTransferKeeper(app.TransferKeeper.Keeper) @@ -660,7 +660,7 @@ func New( memKeys[interchaintxstypes.MemStoreKey], app.IBCKeeper.ChannelKeeper, app.ICAControllerKeeper, - contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper), + contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), app.FeeKeeper, ) @@ -699,7 +699,10 @@ func New( if len(enabledProposals) != 0 { app.AdminmoduleKeeper.Router().AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) } - transferIBCModule := transferSudo.NewIBCModule(app.TransferKeeper) + 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 diff --git a/proto/neutron/contractmanager/failure.proto b/proto/neutron/contractmanager/failure.proto index 2ac24a5a6..d18386884 100644 --- a/proto/neutron/contractmanager/failure.proto +++ b/proto/neutron/contractmanager/failure.proto @@ -13,10 +13,6 @@ message Failure { string address = 1; // Id of the failure under specific address uint64 id = 2; - // Acknowledgement type - string ack_type = 3; - // IBC Packet - ibc.core.channel.v1.Packet packet = 4; - // Acknowledgement - ibc.core.channel.v1.Acknowledgement ack = 5; + // Serialized MessageSudoCallback with Packet and Ack(if exists) + bytes sudo_payload = 3; } diff --git a/testutil/interchaintxs/keeper/interchaintxs.go b/testutil/interchaintxs/keeper/interchaintxs.go index 55c9b43a9..4961a46f9 100644 --- a/testutil/interchaintxs/keeper/interchaintxs.go +++ b/testutil/interchaintxs/keeper/interchaintxs.go @@ -17,7 +17,7 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) -func InterchainTxsKeeper(t testing.TB, managerKeeper types.ContractManagerKeeper, refunderKeeper types.FeeRefunderKeeper, icaControllerKeeper types.ICAControllerKeeper, channelKeeper types.ChannelKeeper) (*keeper.Keeper, sdk.Context, *storetypes.KVStoreKey) { +func InterchainTxsKeeper(t testing.TB, managerKeeper types.WasmKeeper, refunderKeeper types.FeeRefunderKeeper, icaControllerKeeper types.ICAControllerKeeper, channelKeeper types.ChannelKeeper) (*keeper.Keeper, sdk.Context, *storetypes.KVStoreKey) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) diff --git a/testutil/mocks/contractmanager/types/expected_keepers.go b/testutil/mocks/contractmanager/types/expected_keepers.go index 40b855702..463ef15c4 100644 --- a/testutil/mocks/contractmanager/types/expected_keepers.go +++ b/testutil/mocks/contractmanager/types/expected_keepers.go @@ -51,7 +51,7 @@ func (mr *MockWasmKeeperMockRecorder) HasContractInfo(ctx, contractAddress inter // Sudo mocks base method. func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddress, msg []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "sudo", ctx, contractAddress, msg) + ret := m.ctrl.Call(m, "Sudo", ctx, contractAddress, msg) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 @@ -60,5 +60,5 @@ func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddres // Sudo indicates an expected call of Sudo. func (mr *MockWasmKeeperMockRecorder) Sudo(ctx, contractAddress, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) } diff --git a/testutil/mocks/interchaintxs/types/expected_keepers.go b/testutil/mocks/interchaintxs/types/expected_keepers.go index 85fa21af1..d42f778a0 100644 --- a/testutil/mocks/interchaintxs/types/expected_keepers.go +++ b/testutil/mocks/interchaintxs/types/expected_keepers.go @@ -14,8 +14,7 @@ import ( types3 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" - types4 "github.com/neutron-org/neutron/x/contractmanager/types" - types5 "github.com/neutron-org/neutron/x/feerefunder/types" + types4 "github.com/neutron-org/neutron/x/feerefunder/types" ) // MockAccountKeeper is a mock of AccountKeeper interface. @@ -92,57 +91,31 @@ func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) } -// MockContractManagerKeeper is a mock of ContractManagerKeeper interface. -type MockContractManagerKeeper struct { +// MockWasmKeeper is a mock of WasmKeeper interface. +type MockWasmKeeper struct { ctrl *gomock.Controller - recorder *MockContractManagerKeeperMockRecorder + recorder *MockWasmKeeperMockRecorder } -// MockContractManagerKeeperMockRecorder is the mock recorder for MockContractManagerKeeper. -type MockContractManagerKeeperMockRecorder struct { - mock *MockContractManagerKeeper +// MockWasmKeeperMockRecorder is the mock recorder for MockWasmKeeper. +type MockWasmKeeperMockRecorder struct { + mock *MockWasmKeeper } -// NewMockContractManagerKeeper creates a new mock instance. -func NewMockContractManagerKeeper(ctrl *gomock.Controller) *MockContractManagerKeeper { - mock := &MockContractManagerKeeper{ctrl: ctrl} - mock.recorder = &MockContractManagerKeeperMockRecorder{mock} +// NewMockWasmKeeper creates a new mock instance. +func NewMockWasmKeeper(ctrl *gomock.Controller) *MockWasmKeeper { + mock := &MockWasmKeeper{ctrl: ctrl} + mock.recorder = &MockWasmKeeperMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockContractManagerKeeper) EXPECT() *MockContractManagerKeeperMockRecorder { +func (m *MockWasmKeeper) EXPECT() *MockWasmKeeperMockRecorder { return m.recorder } -// AddContractFailure mocks base method. -func (m *MockContractManagerKeeper) AddContractFailure(ctx types.Context, packet *types3.Packet, address, ackType string, ack *types3.Acknowledgement) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "AddContractFailure", ctx, packet, address, ackType, ack) -} - -// AddContractFailure indicates an expected call of AddContractFailure. -func (mr *MockContractManagerKeeperMockRecorder) AddContractFailure(ctx, packet, address, ackType, ack interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContractFailure", reflect.TypeOf((*MockContractManagerKeeper)(nil).AddContractFailure), ctx, packet, address, ackType, ack) -} - -// GetParams mocks base method. -func (m *MockContractManagerKeeper) GetParams(ctx types.Context) types4.Params { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetParams", ctx) - ret0, _ := ret[0].(types4.Params) - return ret0 -} - -// GetParams indicates an expected call of GetParams. -func (mr *MockContractManagerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockContractManagerKeeper)(nil).GetParams), ctx) -} - // HasContractInfo mocks base method. -func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { +func (m *MockWasmKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasContractInfo", ctx, contractAddress) ret0, _ := ret[0].(bool) @@ -150,69 +123,24 @@ func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractA } // HasContractInfo indicates an expected call of HasContractInfo. -func (mr *MockContractManagerKeeperMockRecorder) HasContractInfo(ctx, contractAddress interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasContractInfo", reflect.TypeOf((*MockContractManagerKeeper)(nil).HasContractInfo), ctx, contractAddress) -} - -// SudoError mocks base method. -func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, ack types3.Acknowledgement) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, ack) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoError indicates an expected call of SudoError. -func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, ack interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, ack) -} - -// SudoOnChanOpenAck mocks base method. -func (m *MockContractManagerKeeper) SudoOnChanOpenAck(ctx types.Context, contractAddress types.AccAddress, details types4.OpenAckDetails) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoOnChanOpenAck", ctx, contractAddress, details) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoOnChanOpenAck indicates an expected call of SudoOnChanOpenAck. -func (mr *MockContractManagerKeeperMockRecorder) SudoOnChanOpenAck(ctx, contractAddress, details interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoOnChanOpenAck", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoOnChanOpenAck), ctx, contractAddress, details) -} - -// SudoResponse mocks base method. -func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types3.Packet, ack types3.Acknowledgement) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, ack) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoResponse indicates an expected call of SudoResponse. -func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, ack interface{}) *gomock.Call { +func (mr *MockWasmKeeperMockRecorder) HasContractInfo(ctx, contractAddress interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, ack) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasContractInfo", reflect.TypeOf((*MockWasmKeeper)(nil).HasContractInfo), ctx, contractAddress) } -// SudoTimeout mocks base method. -func (m *MockContractManagerKeeper) SudoTimeout(ctx types.Context, senderAddress types.AccAddress, request types3.Packet) ([]byte, error) { +// Sudo mocks base method. +func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddress, msg []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoTimeout", ctx, senderAddress, request) + ret := m.ctrl.Call(m, "Sudo", ctx, contractAddress, msg) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } -// SudoTimeout indicates an expected call of SudoTimeout. -func (mr *MockContractManagerKeeperMockRecorder) SudoTimeout(ctx, senderAddress, request interface{}) *gomock.Call { +// Sudo indicates an expected call of Sudo. +func (mr *MockWasmKeeperMockRecorder) Sudo(ctx, contractAddress, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoTimeout", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoTimeout), ctx, senderAddress, request) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) } // MockICAControllerKeeper is a mock of ICAControllerKeeper interface. @@ -321,7 +249,7 @@ func (m *MockFeeRefunderKeeper) EXPECT() *MockFeeRefunderKeeperMockRecorder { } // DistributeAcknowledgementFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types5.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types4.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeAcknowledgementFee", ctx, receiver, packetID) } @@ -333,7 +261,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeAcknowledgementFee(ctx, r } // DistributeTimeoutFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types5.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types4.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeTimeoutFee", ctx, receiver, packetID) } @@ -345,7 +273,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeTimeoutFee(ctx, receiver, } // LockFees mocks base method. -func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types5.PacketID, fee types5.Fee) error { +func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types4.PacketID, fee types4.Fee) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LockFees", ctx, payer, packetID, fee) ret0, _ := ret[0].(error) diff --git a/testutil/mocks/transfer/types/expected_keepers.go b/testutil/mocks/transfer/types/expected_keepers.go index 122edaeb2..3123b6eb5 100644 --- a/testutil/mocks/transfer/types/expected_keepers.go +++ b/testutil/mocks/transfer/types/expected_keepers.go @@ -14,31 +14,31 @@ import ( types2 "github.com/neutron-org/neutron/x/feerefunder/types" ) -// MockContractManagerKeeper is a mock of ContractManagerKeeper interface. -type MockContractManagerKeeper struct { +// MockWasmKeeper is a mock of WasmKeeper interface. +type MockWasmKeeper struct { ctrl *gomock.Controller - recorder *MockContractManagerKeeperMockRecorder + recorder *MockWasmKeeperMockRecorder } -// MockContractManagerKeeperMockRecorder is the mock recorder for MockContractManagerKeeper. -type MockContractManagerKeeperMockRecorder struct { - mock *MockContractManagerKeeper +// MockWasmKeeperMockRecorder is the mock recorder for MockWasmKeeper. +type MockWasmKeeperMockRecorder struct { + mock *MockWasmKeeper } -// NewMockContractManagerKeeper creates a new mock instance. -func NewMockContractManagerKeeper(ctrl *gomock.Controller) *MockContractManagerKeeper { - mock := &MockContractManagerKeeper{ctrl: ctrl} - mock.recorder = &MockContractManagerKeeperMockRecorder{mock} +// NewMockWasmKeeper creates a new mock instance. +func NewMockWasmKeeper(ctrl *gomock.Controller) *MockWasmKeeper { + mock := &MockWasmKeeper{ctrl: ctrl} + mock.recorder = &MockWasmKeeperMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockContractManagerKeeper) EXPECT() *MockContractManagerKeeperMockRecorder { +func (m *MockWasmKeeper) EXPECT() *MockWasmKeeperMockRecorder { return m.recorder } // HasContractInfo mocks base method. -func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { +func (m *MockWasmKeeper) HasContractInfo(ctx types.Context, contractAddress types.AccAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasContractInfo", ctx, contractAddress) ret0, _ := ret[0].(bool) @@ -46,54 +46,24 @@ func (m *MockContractManagerKeeper) HasContractInfo(ctx types.Context, contractA } // HasContractInfo indicates an expected call of HasContractInfo. -func (mr *MockContractManagerKeeperMockRecorder) HasContractInfo(ctx, contractAddress interface{}) *gomock.Call { +func (mr *MockWasmKeeperMockRecorder) HasContractInfo(ctx, contractAddress interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasContractInfo", reflect.TypeOf((*MockContractManagerKeeper)(nil).HasContractInfo), ctx, contractAddress) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasContractInfo", reflect.TypeOf((*MockWasmKeeper)(nil).HasContractInfo), ctx, contractAddress) } -// SudoError mocks base method. -func (m *MockContractManagerKeeper) SudoError(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, ack types1.Acknowledgement) ([]byte, error) { +// Sudo mocks base method. +func (m *MockWasmKeeper) Sudo(ctx types.Context, contractAddress types.AccAddress, msg []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoError", ctx, senderAddress, request, ack) + ret := m.ctrl.Call(m, "Sudo", ctx, contractAddress, msg) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } -// SudoError indicates an expected call of SudoError. -func (mr *MockContractManagerKeeperMockRecorder) SudoError(ctx, senderAddress, request, ack interface{}) *gomock.Call { +// Sudo indicates an expected call of Sudo. +func (mr *MockWasmKeeperMockRecorder) Sudo(ctx, contractAddress, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoError", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoError), ctx, senderAddress, request, ack) -} - -// SudoResponse mocks base method. -func (m *MockContractManagerKeeper) SudoResponse(ctx types.Context, senderAddress types.AccAddress, request types1.Packet, ack types1.Acknowledgement) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoResponse", ctx, senderAddress, request, ack) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoResponse indicates an expected call of SudoResponse. -func (mr *MockContractManagerKeeperMockRecorder) SudoResponse(ctx, senderAddress, request, ack interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoResponse", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoResponse), ctx, senderAddress, request, ack) -} - -// SudoTimeout mocks base method. -func (m *MockContractManagerKeeper) SudoTimeout(ctx types.Context, senderAddress types.AccAddress, request types1.Packet) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SudoTimeout", ctx, senderAddress, request) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SudoTimeout indicates an expected call of SudoTimeout. -func (mr *MockContractManagerKeeperMockRecorder) SudoTimeout(ctx, senderAddress, request interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SudoTimeout", reflect.TypeOf((*MockContractManagerKeeper)(nil).SudoTimeout), ctx, senderAddress, request) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) } // MockFeeRefunderKeeper is a mock of FeeRefunderKeeper interface. diff --git a/testutil/transfer/keeper/keeper.go b/testutil/transfer/keeper/keeper.go index 8f35aa8a6..532701629 100644 --- a/testutil/transfer/keeper/keeper.go +++ b/testutil/transfer/keeper/keeper.go @@ -22,7 +22,7 @@ import ( "github.com/neutron-org/neutron/x/transfer/types" ) -func TransferKeeper(t testing.TB, managerKeeper types.ContractManagerKeeper, refunderKeeper types.FeeRefunderKeeper, channelKeeper types.ChannelKeeper, authKeeper types.AccountKeeper) (*keeper.KeeperTransferWrapper, sdk.Context, *storetypes.KVStoreKey) { +func TransferKeeper(t testing.TB, managerKeeper types.WasmKeeper, refunderKeeper types.FeeRefunderKeeper, channelKeeper types.ChannelKeeper, authKeeper types.AccountKeeper) (*keeper.KeeperTransferWrapper, sdk.Context, *storetypes.KVStoreKey) { storeKey := sdk.NewKVStoreKey(transfertypes.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey("mem_" + transfertypes.StoreKey) diff --git a/wasmbinding/test/custom_message_test.go b/wasmbinding/test/custom_message_test.go index 384b84cd6..1f5d11d5b 100644 --- a/wasmbinding/test/custom_message_test.go +++ b/wasmbinding/test/custom_message_test.go @@ -3,10 +3,9 @@ package test import ( "encoding/json" "fmt" + keeper2 "github.com/neutron-org/neutron/x/contractmanager/keeper" "testing" - contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" - ibcchanneltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/stretchr/testify/suite" @@ -609,8 +608,10 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureAck() { ack := ibcchanneltypes.Acknowledgement{ Response: &ibcchanneltypes.Acknowledgement_Result{Result: []byte("Result")}, } + payload, err := keeper2.PrepareSudoCallbackMessage(packet, &ack) + require.NoError(suite.T(), err) failureID := suite.messenger.ContractmanagerKeeper.GetNextFailureIDKey(suite.ctx, suite.contractAddress.String()) - suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, &packet, suite.contractAddress.String(), contractmanagertypes.Ack, &ack) + suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, suite.contractAddress.String(), payload) // Craft message msg, err := json.Marshal(bindings.NeutronMsg{ @@ -639,11 +640,10 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureTimeout() { // Add failure packet := ibcchanneltypes.Packet{} - ack := ibcchanneltypes.Acknowledgement{ - Response: &ibcchanneltypes.Acknowledgement_Error{Error: "ErrorSudoPayload"}, - } + payload, err := keeper2.PrepareSudoCallbackMessage(packet, nil) + require.NoError(suite.T(), err) failureID := suite.messenger.ContractmanagerKeeper.GetNextFailureIDKey(suite.ctx, suite.contractAddress.String()) - suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, &packet, suite.contractAddress.String(), "timeout", &ack) + suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, suite.contractAddress.String(), payload) // Craft message msg, err := json.Marshal(bindings.NeutronMsg{ @@ -676,7 +676,9 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureFromDifferentContract( Response: &ibcchanneltypes.Acknowledgement_Error{Error: "ErrorSudoPayload"}, } failureID := suite.messenger.ContractmanagerKeeper.GetNextFailureIDKey(suite.ctx, testutil.TestOwnerAddress) - suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, &packet, testutil.TestOwnerAddress, contractmanagertypes.Ack, &ack) + payload, err := keeper2.PrepareSudoCallbackMessage(packet, &ack) + require.NoError(suite.T(), err) + suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, testutil.TestOwnerAddress, payload) // Craft message msg, err := json.Marshal(bindings.NeutronMsg{ diff --git a/x/contractmanager/client/cli/query_failure_test.go b/x/contractmanager/client/cli/query_failure_test.go index f76d69a44..d590d39e3 100644 --- a/x/contractmanager/client/cli/query_failure_test.go +++ b/x/contractmanager/client/cli/query_failure_test.go @@ -6,8 +6,6 @@ import ( "strconv" "testing" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/neutron-org/neutron/app" tmcli "github.com/cometbft/cometbft/libs/cli" @@ -40,8 +38,8 @@ func networkWithFailureObjects(t *testing.T, n int) (*network.Network, []types.F require.NoError(t, err) acc := sdktypes.AccAddress(pub.Address()) failure := types.Failure{ - Address: acc.String(), - Packet: &channeltypes.Packet{}, + Address: acc.String(), + SudoPayload: []byte("&channeltypes.Packet{}"), } nullify.Fill(&failure) state.FailuresList = append(state.FailuresList, failure) diff --git a/x/contractmanager/genesis.go b/x/contractmanager/genesis.go index 35604dfe4..fd5751c6b 100644 --- a/x/contractmanager/genesis.go +++ b/x/contractmanager/genesis.go @@ -11,7 +11,7 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { // Set all the failure for _, elem := range genState.FailuresList { - k.AddContractFailure(ctx, elem.Packet, elem.Address, elem.AckType, elem.Ack) + k.AddContractFailure(ctx, elem.Address, elem.SudoPayload) } // this line is used by starport scaffolding # genesis/module/init err := k.SetParams(ctx, genState.Params) diff --git a/x/contractmanager/genesis_test.go b/x/contractmanager/genesis_test.go index feb3ff00a..ddfa24682 100644 --- a/x/contractmanager/genesis_test.go +++ b/x/contractmanager/genesis_test.go @@ -1,6 +1,7 @@ package contractmanager_test import ( + "github.com/neutron-org/neutron/x/contractmanager/keeper" "testing" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" @@ -14,25 +15,33 @@ import ( ) func TestGenesis(t *testing.T) { + payload1, err := keeper.PrepareSudoCallbackMessage( + channeltypes.Packet{ + Sequence: 1, + }, + &channeltypes.Acknowledgement{ + Response: &channeltypes.Acknowledgement_Result{ + Result: []byte("Result"), + }, + }) + require.NoError(t, err) + payload2, err := keeper.PrepareSudoCallbackMessage( + channeltypes.Packet{ + Sequence: 2, + }, nil) genesisState := types.GenesisState{ Params: types.DefaultParams(), FailuresList: []types.Failure{ { - Address: "address1", - Id: 1, - AckType: types.Ack, - Packet: &channeltypes.Packet{ - Sequence: 1, - }, + Address: "address1", + Id: 1, + SudoPayload: payload1, }, { - Address: "address1", - Id: 2, - AckType: "timeout", - Packet: &channeltypes.Packet{ - Sequence: 2, - }, + Address: "address1", + Id: 2, + SudoPayload: payload2, }, }, // this line is used by starport scaffolding # genesis/test/state diff --git a/x/contractmanager/ibc_middleware.go b/x/contractmanager/ibc_middleware.go index 237c7f731..841897459 100644 --- a/x/contractmanager/ibc_middleware.go +++ b/x/contractmanager/ibc_middleware.go @@ -4,68 +4,40 @@ import ( "fmt" "github.com/cometbft/cometbft/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/neutron-org/neutron/neutronutils" neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" + "github.com/neutron-org/neutron/x/contractmanager/keeper" contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" ) type SudoLimitWrapper struct { - contractmanagertypes.ContractManagerWrapper + contractManager keeper.Keeper + contractmanagertypes.WasmKeeper } -// NewSudoLimitWrapper suppresses an error from a sudo contract handler and saves it to a store -func NewSudoLimitWrapper(keeper contractmanagertypes.ContractManagerWrapper) contractmanagertypes.ContractManagerWrapper { +// NewSudoLimitWrapper suppresses an error from a Sudo contract handler and saves it to a store +func NewSudoLimitWrapper(contractManager keeper.Keeper, sudoKeeper contractmanagertypes.WasmKeeper) contractmanagertypes.WasmKeeper { return SudoLimitWrapper{ - keeper, + contractManager, + sudoKeeper, } } -func (k SudoLimitWrapper) SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) { - err := k.sudo(ctx, senderAddress, request, &ack) - if err != nil { - k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "Result") - } - return nil, nil -} - -func (k SudoLimitWrapper) SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) { - err := k.sudo(ctx, senderAddress, request, &ack) - if err != nil { - k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "ErrorSudoPayload") - } - return nil, nil -} - -func (k SudoLimitWrapper) SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) { - err := k.sudo(ctx, senderAddress, request, nil) - if err != nil { - k.Logger(ctx).Debug("SudoLimitWrapper: failed to sudo contract", "error", err, "ackType", "Timeout") - } - return nil, nil -} - -// sudo calls underlying sudo handlers with a limited amount of gas +// Sudo calls underlying Sudo handlers with a limited amount of gas // in case of `out of gas` panic it converts the panic into an error and stops `out of gas` panic propagation -func (k SudoLimitWrapper) sudo(ctx sdk.Context, sender sdk.AccAddress, packet channeltypes.Packet, ack *channeltypes.Acknowledgement) (err error) { - ackType := contractmanagertypes.Ack - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.ContractManagerWrapper.GetParams(ctx).SudoCallGasLimit) +// if error happens during the Sudo call, we store the data that raised the error, and return the error +func (k SudoLimitWrapper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) (resp []byte, err error) { + cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManager.GetParams(ctx).SudoCallGasLimit) func() { defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events - if ack == nil { - ackType = contractmanagertypes.Timeout - _, err = k.ContractManagerWrapper.SudoTimeout(cacheCtx, sender, packet) - } else if ack.GetError() != "" { - _, err = k.ContractManagerWrapper.SudoError(cacheCtx, sender, packet, *ack) - } else { - _, err = k.ContractManagerWrapper.SudoResponse(cacheCtx, sender, packet, *ack) - } + resp, err = k.WasmKeeper.Sudo(cacheCtx, contractAddress, msg) + }() if err != nil { // the contract either returned an error or panicked with `out of gas` - k.ContractManagerWrapper.AddContractFailure(ctx, &packet, sender.String(), ackType, ack) + k.contractManager.AddContractFailure(ctx, contractAddress.String(), msg) } else { writeFn() } diff --git a/x/contractmanager/ibc_middleware_test.go b/x/contractmanager/ibc_middleware_test.go index 57012be71..b1b964fbf 100644 --- a/x/contractmanager/ibc_middleware_test.go +++ b/x/contractmanager/ibc_middleware_test.go @@ -1,82 +1,83 @@ package contractmanager -import ( - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/golang/mock/gomock" - mock_types "github.com/neutron-org/neutron/testutil/mocks/interchaintxs/types" - "github.com/neutron-org/neutron/x/contractmanager/types" - "github.com/stretchr/testify/require" - "testing" -) - -var ( - ShouldNotBeWrittenKey = []byte("shouldnotkey") - ShouldNotBeWritten = []byte("should not be written") - ShouldBeWritten = []byte("should be written") - TestOwnerAddress = "neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh" -) - -func ShouldBeWrittenKey(suffix string) []byte { - return append([]byte("shouldkey"), []byte(suffix)...) -} - -func NewSudoLimitMiddleware(t testing.TB, cm types.ContractManagerWrapper) (SudoLimitWrapper, sdk.Context, *storetypes.KVStoreKey) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) - stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - require.NoError(t, stateStore.LoadLatestVersion()) - - k := SudoLimitWrapper{ContractManagerWrapper: cm} - - ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) - - return k, ctx, storeKey -} - -func TestSudo(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) - middleware, infCtx, storeKey := NewSudoLimitMiddleware(t, cmKeeper) - st := infCtx.KVStore(storeKey) - - p := channeltypes.Packet{ - Sequence: 100, - SourcePort: icatypes.ControllerPortPrefix + TestOwnerAddress + ".ica0", - SourceChannel: "channel-0", - } - contractAddress := sdk.AccAddress{} - errACK := channeltypes.Acknowledgement{ - Response: &channeltypes.Acknowledgement_Error{ - Error: "error", - }, - } - //errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) - //require.NoError(t, err) - //resACK := channeltypes.Acknowledgement{ - // Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, - //} - //resAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&resACK) - //require.NoError(t, err) - - // success during SudoError - ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, errAck channeltypes.Acknowledgement) { - st := cachedCtx.KVStore(storeKey) - st.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) - }).Return(nil, nil) - cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) - err := middleware.sudo(ctx, contractAddress, p, &errACK) - require.NoError(t, err) - require.Equal(t, ShouldBeWritten, st.Get(ShouldBeWrittenKey("sudoerror"))) - require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) -} +// +//import ( +// tmdb "github.com/cometbft/cometbft-db" +// "github.com/cometbft/cometbft/libs/log" +// tmproto "github.com/cometbft/cometbft/proto/tendermint/types" +// "github.com/cosmos/cosmos-sdk/store" +// storetypes "github.com/cosmos/cosmos-sdk/store/types" +// sdk "github.com/cosmos/cosmos-sdk/types" +// icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" +// channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" +// "github.com/golang/mock/gomock" +// mock_types "github.com/neutron-org/neutron/testutil/mocks/interchaintxs/types" +// "github.com/neutron-org/neutron/x/contractmanager/types" +// "github.com/stretchr/testify/require" +// "testing" +//) +// +//var ( +// ShouldNotBeWrittenKey = []byte("shouldnotkey") +// ShouldNotBeWritten = []byte("should not be written") +// ShouldBeWritten = []byte("should be written") +// TestOwnerAddress = "neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh" +//) +// +//func ShouldBeWrittenKey(suffix string) []byte { +// return append([]byte("shouldkey"), []byte(suffix)...) +//} +// +//func NewSudoLimitMiddleware(t testing.TB, cm types.SudoWrapper) (SudoLimitWrapper, sdk.Context, *storetypes.KVStoreKey) { +// storeKey := sdk.NewKVStoreKey(types.StoreKey) +// +// db := tmdb.NewMemDB() +// stateStore := store.NewCommitMultiStore(db) +// stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) +// require.NoError(t, stateStore.LoadLatestVersion()) +// +// k := SudoLimitWrapper{SudoWrapper: cm} +// +// ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) +// +// return k, ctx, storeKey +//} +// +//func TestSudo(t *testing.T) { +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) +// middleware, infCtx, storeKey := NewSudoLimitMiddleware(t, cmKeeper) +// st := infCtx.KVStore(storeKey) +// +// p := channeltypes.Packet{ +// Sequence: 100, +// SourcePort: icatypes.ControllerPortPrefix + TestOwnerAddress + ".ica0", +// SourceChannel: "channel-0", +// } +// contractAddress := sdk.AccAddress{} +// errACK := channeltypes.Acknowledgement{ +// Response: &channeltypes.Acknowledgement_Error{ +// Error: "error", +// }, +// } +// //errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) +// //require.NoError(t, err) +// //resACK := channeltypes.Acknowledgement{ +// // Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, +// //} +// //resAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&resACK) +// //require.NoError(t, err) +// +// // success during SudoError +// ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) +// cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, errAck channeltypes.Acknowledgement) { +// st := cachedCtx.KVStore(storeKey) +// st.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) +// }).Return(nil, nil) +// cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) +// err := middleware.Sudo(ctx, contractAddress, p, &errACK) +// require.NoError(t, err) +// require.Equal(t, ShouldBeWritten, st.Get(ShouldBeWrittenKey("sudoerror"))) +// require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) +//} diff --git a/x/contractmanager/keeper/failure.go b/x/contractmanager/keeper/failure.go index 581b4f19c..038227591 100644 --- a/x/contractmanager/keeper/failure.go +++ b/x/contractmanager/keeper/failure.go @@ -5,17 +5,14 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - ibcchanneltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/neutron-org/neutron/x/contractmanager/types" ) // AddContractFailure adds a specific failure to the store using address as the key -func (k Keeper) AddContractFailure(ctx sdk.Context, packet *ibcchanneltypes.Packet, address, ackType string, ack *ibcchanneltypes.Acknowledgement) { +func (k Keeper) AddContractFailure(ctx sdk.Context, address string, sudoPayload []byte) { failure := types.Failure{ - Address: address, - AckType: ackType, - Packet: packet, - Ack: ack, + Address: address, + SudoPayload: sudoPayload, } nextFailureID := k.GetNextFailureIDKey(ctx, failure.GetAddress()) failure.Id = nextFailureID @@ -40,7 +37,7 @@ func (k Keeper) GetNextFailureIDKey(ctx sdk.Context, address string) uint64 { return 0 } -// GetAllFailure returns all failures +// GetAllFailures returns all failures func (k Keeper) GetAllFailures(ctx sdk.Context) (list []types.Failure) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractFailuresKey) iterator := sdk.KVStorePrefixIterator(store, []byte{}) @@ -71,30 +68,12 @@ func (k Keeper) GetFailure(ctx sdk.Context, contractAddr sdk.AccAddress, id uint // ResubmitFailure tries to call sudo handler for contract with same parameters as initially. func (k Keeper) ResubmitFailure(ctx sdk.Context, contractAddr sdk.AccAddress, failure *types.Failure) error { - if failure.Packet == nil { - return errors.Wrapf(types.IncorrectFailureToResubmit, "cannot resubmit failure without packet info; failureId = %d", failure.Id) + if failure.SudoPayload == nil { + return errors.Wrapf(types.IncorrectFailureToResubmit, "cannot resubmit failure without sudo payload; failureId = %d", failure.Id) } - switch failure.GetAckType() { - case types.Ack: - if failure.GetAck() == nil { - return errors.Wrapf(types.IncorrectFailureToResubmit, "cannot resubmit failure without acknowledgement; failureId = %d", failure.Id) - } - if failure.GetAck().GetError() == "" { - if _, err := k.SudoResponse(ctx, contractAddr, *failure.Packet, *failure.Ack); err != nil { - return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack response; failureId = %d; err = %s", failure.Id, err) - } - } else { - if _, err := k.SudoError(ctx, contractAddr, *failure.Packet, *failure.Ack); err != nil { - return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack error; failureId = %d; err = %s", failure.Id, err) - } - } - case types.Timeout: - if _, err := k.SudoTimeout(ctx, contractAddr, *failure.Packet); err != nil { - return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack timeout; failureId = %d; err = %s", failure.Id, err) - } - default: - return errors.Wrapf(types.IncorrectAckType, "cannot resubmit failure with incorrect ackType = %s", failure.GetAckType()) + if _, err := k.wasmKeeper.Sudo(ctx, contractAddr, failure.SudoPayload); err != nil { + return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack; failureId = %d; err = %s", failure.Id, err) } // Cleanup failure since we resubmitted it successfully diff --git a/x/contractmanager/keeper/failure_test.go b/x/contractmanager/keeper/failure_test.go index aa00b4c91..5edd064c3 100644 --- a/x/contractmanager/keeper/failure_test.go +++ b/x/contractmanager/keeper/failure_test.go @@ -26,7 +26,7 @@ import ( // Prevent strconv unused error var _ = strconv.IntSize -func createNFailure(keeper *keeper.Keeper, ctx sdk.Context, addresses, failures int) [][]types.Failure { +func createNFailure(k *keeper.Keeper, ctx sdk.Context, addresses, failures int) [][]types.Failure { pubBz := make([]byte, ed25519.PubKeySize) pub := &ed25519.PubKey{Key: pubBz} @@ -43,9 +43,12 @@ func createNFailure(keeper *keeper.Keeper, ctx sdk.Context, addresses, failures } items[i][c].Address = acc.String() items[i][c].Id = uint64(c) - items[i][c].Packet = &p - items[i][c].Ack = nil - keeper.AddContractFailure(ctx, &p, items[i][c].Address, "", nil) + sudo, err := keeper.PrepareSudoCallbackMessage(p, nil) + if err != nil { + panic(err) + } + items[i][c].SudoPayload = sudo + k.AddContractFailure(ctx, items[i][c].Address, sudo) } } return items @@ -83,11 +86,12 @@ func TestAddGetFailure(t *testing.T) { contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) k, ctx := keepertest.ContractManagerKeeper(t, nil) failureID := k.GetNextFailureIDKey(ctx, contractAddress.String()) - k.AddContractFailure(ctx, &channeltypes.Packet{}, contractAddress.String(), types.Ack, &channeltypes.Acknowledgement{}) + sudoPayload := []byte("payload") + k.AddContractFailure(ctx, contractAddress.String(), sudoPayload) failure, err := k.GetFailure(ctx, contractAddress, failureID) require.NoError(t, err) require.Equal(t, failureID, failure.Id) - require.Equal(t, types.Ack, failure.AckType) + require.Equal(t, sudoPayload, failure.SudoPayload) // non-existent id _, err = k.GetFailure(ctx, contractAddress, failureID+1) @@ -117,28 +121,31 @@ func TestResubmitFailure(t *testing.T) { // add ack failure packet := channeltypes.Packet{} failureID := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ack) + payload, err := keeper.PrepareSudoCallbackMessage(packet, &ack) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) // success response - xSuc := types.MessageResponse{} - xSuc.Response.Data = data - xSuc.Response.Request = channeltypes.Packet{} + xSuc := types.MessageSudoCallback{Response: &types.ResponseSudoPayload{ + Request: channeltypes.Packet{}, + Data: data, + }} msgSuc, err := json.Marshal(xSuc) require.NoError(t, err) // error response - xErr := types.MessageError{} - xErr.Error.Request = channeltypes.Packet{} - xErr.Error.Details = "not able to do IBC tx" + xErr := types.MessageSudoCallback{Error: &types.ErrorSudoPayload{ + Request: channeltypes.Packet{}, + Details: "not able to do IBC tx", + }} msgErr, err := json.Marshal(xErr) require.NoError(t, err) // timeout response - xTimeout := types.MessageTimeout{} - xTimeout.Timeout.Request = channeltypes.Packet{} + xTimeout := types.MessageSudoCallback{Timeout: &types.TimeoutPayload{Request: channeltypes.Packet{}}} msgTimeout, err := json.Marshal(xTimeout) require.NoError(t, err) // case: successful resubmit with ack and ack = response - wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgSuc).Return([]byte{}, nil) + wk.EXPECT().Sudo(ctx, contractAddr, msgSuc).Return([]byte{}, nil) failure, err := k.GetFailure(ctx, contractAddr, failureID) require.NoError(t, err) @@ -150,14 +157,16 @@ func TestResubmitFailure(t *testing.T) { // case: failed resubmit with ack and ack = response failureID2 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ack) + payload, err = keeper.PrepareSudoCallbackMessage(packet, &ack) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) - wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgSuc).Return(nil, fmt.Errorf("failed to sudo")) + wk.EXPECT().Sudo(ctx, contractAddr, msgSuc).Return(nil, fmt.Errorf("failed to sudo")) failure2, err := k.GetFailure(ctx, contractAddr, failureID2) require.NoError(t, err) err = k.ResubmitFailure(ctx, contractAddr, failure2) - require.ErrorContains(t, err, "cannot resubmit failure ack response") + require.ErrorContains(t, err, "cannot resubmit failure ack") // failure is still there failureAfter2, err := k.GetFailure(ctx, contractAddr, failureID2) require.NoError(t, err) @@ -166,7 +175,9 @@ func TestResubmitFailure(t *testing.T) { // case: successful resubmit with ack and ack = error // add error failure failureID3 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ackError) + payload, err = keeper.PrepareSudoCallbackMessage(packet, &ackError) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgErr).Return([]byte{}, nil) @@ -180,14 +191,16 @@ func TestResubmitFailure(t *testing.T) { // case: failed resubmit with ack and ack = error failureID4 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, &ackError) + payload, err = keeper.PrepareSudoCallbackMessage(packet, &ackError) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgErr).Return(nil, fmt.Errorf("failed to sudo")) failure4, err := k.GetFailure(ctx, contractAddr, failureID4) require.NoError(t, err) err = k.ResubmitFailure(ctx, contractAddr, failure4) - require.ErrorContains(t, err, "cannot resubmit failure ack error") + require.ErrorContains(t, err, "cannot resubmit failure ack") // failure is still there failureAfter4, err := k.GetFailure(ctx, contractAddr, failureID4) require.NoError(t, err) @@ -196,7 +209,9 @@ func TestResubmitFailure(t *testing.T) { // case: successful resubmit with timeout // add error failure failureID5 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), "timeout", nil) + payload, err = keeper.PrepareSudoCallbackMessage(packet, nil) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgTimeout).Return([]byte{}, nil) @@ -210,25 +225,18 @@ func TestResubmitFailure(t *testing.T) { // case: failed resubmit with timeout failureID6 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), "timeout", nil) + payload, err = keeper.PrepareSudoCallbackMessage(packet, nil) + require.NoError(t, err) + k.AddContractFailure(ctx, contractAddr.String(), payload) wk.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddr, msgTimeout).Return(nil, fmt.Errorf("failed to sudo")) failure6, err := k.GetFailure(ctx, contractAddr, failureID6) require.NoError(t, err) err = k.ResubmitFailure(ctx, contractAddr, failure6) - require.ErrorContains(t, err, "cannot resubmit failure ack timeout") + require.ErrorContains(t, err, "cannot resubmit failure ack") // failure is still there failureAfter6, err := k.GetFailure(ctx, contractAddr, failureID6) require.NoError(t, err) require.Equal(t, failureAfter6.Id, failure6.Id) - - // no Failure.Ack field found for ackType = 'ack' - failureID7 := k.GetNextFailureIDKey(ctx, contractAddr.String()) - k.AddContractFailure(ctx, &packet, contractAddr.String(), types.Ack, nil) - - failure7, err := k.GetFailure(ctx, contractAddr, failureID7) - require.NoError(t, err) - err = k.ResubmitFailure(ctx, contractAddr, failure7) - require.ErrorContains(t, err, "cannot resubmit failure without acknowledgement") } diff --git a/x/contractmanager/keeper/sudo.go b/x/contractmanager/keeper/sudo.go index 1b33639cb..4c5a24402 100644 --- a/x/contractmanager/keeper/sudo.go +++ b/x/contractmanager/keeper/sudo.go @@ -20,97 +20,37 @@ func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) return k.wasmKeeper.HasContractInfo(ctx, contractAddress) } -func prepareSudoCallbackMessage(request channeltypes.Packet, ack *channeltypes.Acknowledgement) types.MessageSudoCallback { - m := types.MessageSudoCallback{ - Response: nil, - Error: nil, - Timeout: nil, +func PrepareSudoCallbackMessage(request channeltypes.Packet, ack *channeltypes.Acknowledgement) ([]byte, error) { + m := types.MessageSudoCallback{} + if ack != nil && ack.GetError() == "" { + m.Response = &types.ResponseSudoPayload{ + Data: ack.GetResult(), + Request: request, + } + } else if ack != nil { + m.Error = &types.ErrorSudoPayload{ + Request: request, + Details: ack.GetError(), + } + } else { + m.Timeout = &types.TimeoutPayload{Request: request} } - return m -} - -func (k Keeper) SudoResponse( - ctx sdk.Context, - senderAddress sdk.AccAddress, - request channeltypes.Packet, - ack channeltypes.Acknowledgement, -) ([]byte, error) { - k.Logger(ctx).Debug("SudoResponse", "senderAddress", senderAddress, "request", request, "msg", ack.GetResult()) - - x := types.MessageResponse{} - x.Response.Data = ack.GetResult() - x.Response.Request = request - m, err := json.Marshal(x) - if err != nil { - k.Logger(ctx).Error("SudoResponse: failed to marshal MessageResponse message", - "error", err, "request", request, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to marshal MessageResponse: %v", err) - } - - resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) + data, err := json.Marshal(m) if err != nil { - k.Logger(ctx).Debug("SudoResponse: failed to sudo", - "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to sudo: %v", err) + return nil, fmt.Errorf("failed to marshal MessageSudoCallback: %v", err) } - - return resp, nil + return data, nil } -func (k Keeper) SudoTimeout( - ctx sdk.Context, - senderAddress sdk.AccAddress, - request channeltypes.Packet, -) ([]byte, error) { - k.Logger(ctx).Info("SudoTimeout", "senderAddress", senderAddress, "request", request) - - x := types.MessageTimeout{} - x.Timeout.Request = request - m, err := json.Marshal(x) - if err != nil { - k.Logger(ctx).Error("failed to marshal MessageTimeout message", - "error", err, "request", request, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to marshal MessageTimeout: %v", err) - } - - k.Logger(ctx).Info("SudoTimeout sending request", "data", string(m)) - - resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) - if err != nil { - k.Logger(ctx).Debug("SudoTimeout: failed to sudo", - "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to sudo: %v", err) +func PrepareOpenAckCallbackMessage(details types.OpenAckDetails) ([]byte, error) { + x := types.MessageOnChanOpenAck{ + OpenAck: details, } - - return resp, nil -} - -func (k Keeper) SudoError( - ctx sdk.Context, - senderAddress sdk.AccAddress, - request channeltypes.Packet, - ack channeltypes.Acknowledgement, -) ([]byte, error) { - k.Logger(ctx).Debug("SudoError", "senderAddress", senderAddress, "request", request) - - x := types.MessageError{} - x.Error.Request = request - x.Error.Details = ack.GetError() m, err := json.Marshal(x) if err != nil { - k.Logger(ctx).Error("SudoError: failed to marshal MessageError message", - "error", err, "contract_address", senderAddress, "request", request) - return nil, fmt.Errorf("failed to marshal MessageError: %v", err) - } - - resp, err := k.wasmKeeper.Sudo(ctx, senderAddress, m) - if err != nil { - k.Logger(ctx).Debug("SudoError: failed to sudo", - "error", err, "contract_address", senderAddress) - return nil, fmt.Errorf("failed to sudo: %v", err) + return nil, fmt.Errorf("failed to marshal MessageOnChanOpenAck: %v", err) } - - return resp, nil + return m, nil } func (k Keeper) SudoOnChanOpenAck( diff --git a/x/contractmanager/keeper/sudo_test.go b/x/contractmanager/keeper/sudo_test.go index 8767acd93..12862b4de 100644 --- a/x/contractmanager/keeper/sudo_test.go +++ b/x/contractmanager/keeper/sudo_test.go @@ -6,7 +6,6 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -21,115 +20,6 @@ func init() { app.GetDefaultConfig() } -func TestSudoHasAddress(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wk := mock_types.NewMockWasmKeeper(ctrl) - - k, ctx := keepertest.ContractManagerKeeper(t, wk) - address := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(true) - has := k.HasContractInfo(ctx, address) - require.Equal(t, true, has) - - wk.EXPECT().HasContractInfo(gomock.Any(), address).Return(false) - has = k.HasContractInfo(ctx, address) - require.Equal(t, false, has) -} - -func TestSudoResponse(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wk := mock_types.NewMockWasmKeeper(ctrl) - - k, ctx := keepertest.ContractManagerKeeper(t, wk) - address := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) - - sudoErrorMsg := types.MessageResponse{} - p := channeltypes.Packet{} - a := channeltypes.Acknowledgement{Response: &channeltypes.Acknowledgement_Result{Result: []byte("data")}} - sudoErrorMsg.Response.Data = a.GetResult() - sudoErrorMsg.Response.Request = p - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return([]byte("success"), nil) - resp, err := k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, a) - require.NoError(t, err) - require.Equal(t, []byte("success"), resp) - - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return(nil, fmt.Errorf("internal contract error")) - resp, err = k.SudoResponse(ctx, address, sudoErrorMsg.Response.Request, a) - require.Nil(t, resp) - require.ErrorContains(t, err, "internal contract error") -} - -func TestSudoError(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wk := mock_types.NewMockWasmKeeper(ctrl) - - k, ctx := keepertest.ContractManagerKeeper(t, wk) - address := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) - - sudoErrorMsg := types.MessageError{} - p := channeltypes.Packet{} - a := channeltypes.Acknowledgement{Response: &channeltypes.Acknowledgement_Error{ - Error: "details", - }} - sudoErrorMsg.Error.Details = a.GetError() - sudoErrorMsg.Error.Request = p - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return([]byte("success"), nil) - resp, err := k.SudoError(ctx, address, sudoErrorMsg.Error.Request, a) - require.NoError(t, err) - require.Equal(t, []byte("success"), resp) - - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoErrorMsg)).Return(nil, fmt.Errorf("internal contract error")) - resp, err = k.SudoError(ctx, address, sudoErrorMsg.Error.Request, a) - require.Nil(t, resp) - require.ErrorContains(t, err, "internal contract error") -} - -func TestSudoTimeout(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wk := mock_types.NewMockWasmKeeper(ctrl) - - k, ctx := keepertest.ContractManagerKeeper(t, wk) - address := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) - - sudoTimeoutMsg := types.MessageTimeout{} - p := channeltypes.Packet{} - sudoTimeoutMsg.Timeout.Request = p - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoTimeoutMsg)).Return([]byte("success"), nil) - resp, err := k.SudoTimeout(ctx, address, sudoTimeoutMsg.Timeout.Request) - require.NoError(t, err) - require.Equal(t, []byte("success"), resp) - - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoTimeoutMsg)).Return(nil, fmt.Errorf("internal contract error")) - resp, err = k.SudoTimeout(ctx, address, sudoTimeoutMsg.Timeout.Request) - require.Nil(t, resp) - require.ErrorContains(t, err, "internal contract error") -} - -func TestSudoOnChanOpen(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wk := mock_types.NewMockWasmKeeper(ctrl) - - k, ctx := keepertest.ContractManagerKeeper(t, wk) - address := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) - - sudoOpenAckMsg := types.MessageOnChanOpenAck{} - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoOpenAckMsg)).Return([]byte("success"), nil) - resp, err := k.SudoOnChanOpenAck(ctx, address, sudoOpenAckMsg.OpenAck) - require.NoError(t, err) - require.Equal(t, []byte("success"), resp) - - wk.EXPECT().Sudo(gomock.Any(), address, mustJSON(sudoOpenAckMsg)).Return(nil, fmt.Errorf("internal contract error")) - resp, err = k.SudoOnChanOpenAck(ctx, address, sudoOpenAckMsg.OpenAck) - require.Nil(t, resp) - require.ErrorContains(t, err, "internal contract error") -} - func TestSudoTxQueryResult(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/x/contractmanager/types/failure.pb.go b/x/contractmanager/types/failure.pb.go index abc19b1a4..063afa74f 100644 --- a/x/contractmanager/types/failure.pb.go +++ b/x/contractmanager/types/failure.pb.go @@ -6,7 +6,7 @@ package types import ( fmt "fmt" proto "github.com/cosmos/gogoproto/proto" - types "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + _ "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" io "io" math "math" math_bits "math/bits" @@ -31,12 +31,8 @@ type Failure struct { Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // Id of the failure under specific address Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` - // Acknowledgement type - AckType string `protobuf:"bytes,3,opt,name=ack_type,json=ackType,proto3" json:"ack_type,omitempty"` - // IBC Packet - Packet *types.Packet `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"` - // Acknowledgement - Ack *types.Acknowledgement `protobuf:"bytes,5,opt,name=ack,proto3" json:"ack,omitempty"` + // Serialized MessageSudoCallback with Packet and Ack(if exists) + SudoPayload []byte `protobuf:"bytes,3,opt,name=sudo_payload,json=sudoPayload,proto3" json:"sudo_payload,omitempty"` } func (m *Failure) Reset() { *m = Failure{} } @@ -86,23 +82,9 @@ func (m *Failure) GetId() uint64 { return 0 } -func (m *Failure) GetAckType() string { +func (m *Failure) GetSudoPayload() []byte { if m != nil { - return m.AckType - } - return "" -} - -func (m *Failure) GetPacket() *types.Packet { - if m != nil { - return m.Packet - } - return nil -} - -func (m *Failure) GetAck() *types.Acknowledgement { - if m != nil { - return m.Ack + return m.SudoPayload } return nil } @@ -116,25 +98,22 @@ func init() { } var fileDescriptor_fba0c26e85dad46e = []byte{ - // 288 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xeb, 0x30, - 0x14, 0x86, 0xeb, 0xb6, 0xb7, 0xbd, 0x18, 0x89, 0xc1, 0x0b, 0x06, 0x24, 0xab, 0x20, 0x90, 0xba, - 0x60, 0xab, 0x54, 0xea, 0x0e, 0x03, 0x73, 0x15, 0x31, 0xb1, 0x20, 0xe7, 0xe4, 0x90, 0x5a, 0x69, - 0xed, 0xc8, 0x75, 0x0b, 0x7d, 0x0b, 0xde, 0x87, 0x17, 0x60, 0xec, 0xc8, 0x88, 0x9a, 0x17, 0x41, - 0x49, 0x93, 0x05, 0xd8, 0x7e, 0x4b, 0xdf, 0xe7, 0x73, 0xce, 0x4f, 0xaf, 0x2c, 0xae, 0x82, 0x77, - 0x56, 0x81, 0xb3, 0xc1, 0x6b, 0x08, 0x0b, 0x6d, 0x75, 0x8a, 0x5e, 0x3d, 0x6b, 0x33, 0x5f, 0x79, - 0x94, 0xb9, 0x77, 0xc1, 0xb1, 0xe3, 0x1a, 0x93, 0x3f, 0xb0, 0xd3, 0x73, 0x13, 0x83, 0x02, 0xe7, - 0x51, 0xc1, 0x4c, 0x5b, 0x8b, 0x73, 0xb5, 0x1e, 0x35, 0x71, 0xef, 0x5e, 0xbc, 0x13, 0xda, 0xbf, - 0xdf, 0xff, 0xc6, 0x38, 0xed, 0xeb, 0x24, 0xf1, 0xb8, 0x5c, 0x72, 0x32, 0x20, 0xc3, 0x83, 0xa8, - 0x79, 0xb2, 0x23, 0xda, 0x36, 0x09, 0x6f, 0x0f, 0xc8, 0xb0, 0x1b, 0xb5, 0x4d, 0xc2, 0x4e, 0xe8, - 0x7f, 0x0d, 0xd9, 0x53, 0xd8, 0xe4, 0xc8, 0x3b, 0x35, 0x0a, 0xd9, 0xc3, 0x26, 0x47, 0x36, 0xa6, - 0xbd, 0x5c, 0x43, 0x86, 0x81, 0x77, 0x07, 0x64, 0x78, 0x78, 0x73, 0x26, 0x4d, 0x0c, 0xb2, 0x5c, - 0x42, 0x36, 0x93, 0xd7, 0x23, 0x39, 0xad, 0x90, 0xa8, 0x46, 0xd9, 0x84, 0x76, 0x34, 0x64, 0xfc, - 0x5f, 0x65, 0x5c, 0xfe, 0x69, 0xdc, 0x42, 0x66, 0xdd, 0xcb, 0x1c, 0x93, 0x14, 0x17, 0x68, 0x43, - 0x54, 0x0a, 0x77, 0xd3, 0x8f, 0x9d, 0x20, 0xdb, 0x9d, 0x20, 0x5f, 0x3b, 0x41, 0xde, 0x0a, 0xd1, - 0xda, 0x16, 0xa2, 0xf5, 0x59, 0x88, 0xd6, 0xe3, 0x24, 0x35, 0x61, 0xb6, 0x8a, 0x25, 0xb8, 0x85, - 0xaa, 0xeb, 0xb9, 0x76, 0x3e, 0x6d, 0xb2, 0x7a, 0xfd, 0xd5, 0x69, 0x79, 0xcc, 0x32, 0xee, 0x55, - 0xb5, 0x8c, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x40, 0x42, 0xfd, 0x80, 0x7b, 0x01, 0x00, 0x00, + // 230 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, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0xc9, 0x4d, 0xcc, 0x4b, + 0x4c, 0x4f, 0x2d, 0xd2, 0x4f, 0x4b, 0xcc, 0xcc, 0x29, 0x2d, 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x12, 0x87, 0x2a, 0xd3, 0x43, 0x53, 0x26, 0xa5, 0x98, 0x99, 0x94, 0xac, 0x9f, 0x9c, + 0x5f, 0x94, 0xaa, 0x9f, 0x9c, 0x91, 0x98, 0x97, 0x97, 0x9a, 0xa3, 0x5f, 0x66, 0x08, 0x63, 0x42, + 0xf4, 0x2a, 0x85, 0x71, 0xb1, 0xbb, 0x41, 0x0c, 0x13, 0x92, 0xe0, 0x62, 0x4f, 0x4c, 0x49, 0x29, + 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, 0x85, 0xf8, 0xb8, 0x98, + 0x32, 0x53, 0x24, 0x98, 0x14, 0x18, 0x35, 0x58, 0x82, 0x98, 0x32, 0x53, 0x84, 0x14, 0xb9, 0x78, + 0x8a, 0x4b, 0x53, 0xf2, 0xe3, 0x0b, 0x12, 0x2b, 0x73, 0xf2, 0x13, 0x53, 0x24, 0x98, 0x15, 0x18, + 0x35, 0x78, 0x82, 0xb8, 0x41, 0x62, 0x01, 0x10, 0x21, 0xa7, 0x80, 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, 0x32, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, + 0xd5, 0x87, 0x3a, 0x5c, 0x37, 0xbf, 0x28, 0x1d, 0xc6, 0xd6, 0xaf, 0xc0, 0xf0, 0x6d, 0x49, 0x65, + 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0xc1, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xf7, + 0xe5, 0xa7, 0x15, 0x01, 0x00, 0x00, } func (m *Failure) Marshal() (dAtA []byte, err error) { @@ -157,34 +136,10 @@ func (m *Failure) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Ack != nil { - { - size, err := m.Ack.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintFailure(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if m.Packet != nil { - { - size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintFailure(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if len(m.AckType) > 0 { - i -= len(m.AckType) - copy(dAtA[i:], m.AckType) - i = encodeVarintFailure(dAtA, i, uint64(len(m.AckType))) + if len(m.SudoPayload) > 0 { + i -= len(m.SudoPayload) + copy(dAtA[i:], m.SudoPayload) + i = encodeVarintFailure(dAtA, i, uint64(len(m.SudoPayload))) i-- dAtA[i] = 0x1a } @@ -227,18 +182,10 @@ func (m *Failure) Size() (n int) { if m.Id != 0 { n += 1 + sovFailure(uint64(m.Id)) } - l = len(m.AckType) + l = len(m.SudoPayload) if l > 0 { n += 1 + l + sovFailure(uint64(l)) } - if m.Packet != nil { - l = m.Packet.Size() - n += 1 + l + sovFailure(uint64(l)) - } - if m.Ack != nil { - l = m.Ack.Size() - n += 1 + l + sovFailure(uint64(l)) - } return n } @@ -330,77 +277,9 @@ func (m *Failure) Unmarshal(dAtA []byte) error { } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AckType", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFailure - } - 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 ErrInvalidLengthFailure - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthFailure - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AckType = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFailure - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthFailure - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthFailure - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Packet == nil { - m.Packet = &types.Packet{} - } - if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SudoPayload", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFailure @@ -410,26 +289,24 @@ func (m *Failure) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthFailure } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthFailure } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Ack == nil { - m.Ack = &types.Acknowledgement{} - } - if err := m.Ack.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.SudoPayload = append(m.SudoPayload[:0], dAtA[iNdEx:postIndex]...) + if m.SudoPayload == nil { + m.SudoPayload = []byte{} } iNdEx = postIndex default: diff --git a/x/contractmanager/types/module.go b/x/contractmanager/types/module.go index 540b2afd3..e7efcf2d9 100644 --- a/x/contractmanager/types/module.go +++ b/x/contractmanager/types/module.go @@ -2,15 +2,8 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ) -type ContractManagerWrapper interface { - HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) - SudoOnChanOpenAck(ctx sdk.Context, contractAddress sdk.AccAddress, details OpenAckDetails) ([]byte, error) - GetParams(ctx sdk.Context) (params Params) +type Sudo interface { + Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) } diff --git a/x/contractmanager/types/sudo.go b/x/contractmanager/types/sudo.go index 8b2d0144d..a5745fcbb 100644 --- a/x/contractmanager/types/sudo.go +++ b/x/contractmanager/types/sudo.go @@ -23,10 +23,12 @@ type MessageKVQueryResult struct { } `json:"kv_query_result"` } +// MessageSudoCallback is passed to a contract's sudo() entrypoint when an interchain +// transaction failed with a timeout. type MessageSudoCallback struct { - Response *ResponseSudoPayload `json:"response"` - Error *ErrorSudoPayload `json:"error"` - Timeout *TimeoutPayload `json:"timeout"` + Response *ResponseSudoPayload `json:"response,omitempty"` + Error *ErrorSudoPayload `json:"error,omitempty"` + Timeout *TimeoutPayload `json:"timeout,omitempty"` } type ResponseSudoPayload struct { @@ -43,32 +45,6 @@ type TimeoutPayload struct { Request channeltypes.Packet `json:"request"` } -// MessageTimeout is passed to a contract's sudo() entrypoint when an interchain -// transaction failed with a timeout. -type MessageTimeout struct { - Timeout struct { - Request channeltypes.Packet `json:"request"` - } `json:"timeout"` -} - -// MessageResponse is passed to a contract's sudo() entrypoint when an interchain -// transaction was executed successfully. -type MessageResponse struct { - Response struct { - Request channeltypes.Packet `json:"request"` - Data []byte `json:"data"` // Message data - } `json:"response"` -} - -// MessageError is passed to a contract's sudo() entrypoint when an interchain -// transaction was executed with an error. -type MessageError struct { - Error struct { - Request channeltypes.Packet `json:"request"` - Details string `json:"details"` - } `json:"error"` -} - // MessageOnChanOpenAck is passed to a contract's sudo() entrypoint when an interchain // account was successfully registered. type MessageOnChanOpenAck struct { diff --git a/x/interchaintxs/keeper/ibc_handlers.go b/x/interchaintxs/keeper/ibc_handlers.go index 4fe930e1f..1540a8c9a 100644 --- a/x/interchaintxs/keeper/ibc_handlers.go +++ b/x/interchaintxs/keeper/ibc_handlers.go @@ -1,6 +1,7 @@ package keeper import ( + "github.com/neutron-org/neutron/x/contractmanager/keeper" "time" "cosmossdk.io/errors" @@ -18,7 +19,6 @@ import ( // HandleAcknowledgement passes the acknowledgement data to the appropriate contract via a sudo call. func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleAcknowledgment) - k.Logger(ctx).Debug("Handling acknowledgement") icaOwner, err := types.ICAOwnerFromPort(packet.SourcePort) if err != nil { @@ -31,24 +31,21 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack k.Logger(ctx).Error("HandleAcknowledgement: cannot unmarshal ICS-27 packet acknowledgement", "error", err) return errors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-27 packet acknowledgement: %v", err) } - - if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { - //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) - return nil + msg, err := keeper.PrepareSudoCallbackMessage(packet, &ack) + if err != nil { + return errors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal Packet/Acknowledgment: %v", err) } k.feeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events - // `err` value from `SudoError/SudoResponse` should always be nil, since we contractmanager wrapped by `SudoLimitWrapper` - if ack.GetError() != "" { - _, err = k.contractManagerKeeper.SudoError(ctx, icaOwner.GetContract(), packet, ack) - } else { - _, err = k.contractManagerKeeper.SudoResponse(ctx, icaOwner.GetContract(), packet, ack) + _, err = k.sudoKeeper.Sudo(ctx, icaOwner.GetContract(), msg) + if err != nil { + k.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) } - return err + return nil } // HandleTimeout passes the timeout data to the appropriate contract via a sudo call. @@ -56,24 +53,25 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack func (k *Keeper) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), LabelHandleTimeout) k.Logger(ctx).Debug("HandleTimeout") - icaOwner, err := types.ICAOwnerFromPort(packet.SourcePort) if err != nil { k.Logger(ctx).Error("HandleTimeout: failed to get ica owner from source port", "error", err) return errors.Wrap(err, "failed to get ica owner from port") } - if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { - //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) - return nil + msg, err := keeper.PrepareSudoCallbackMessage(packet, nil) + if err != nil { + return errors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal Packet: %v", err) } k.feeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - // `err` value from `SudoTimeout` should always be nil, since we contractmanager wrapped by `SudoLimitWrapper` - _, err = k.contractManagerKeeper.SudoTimeout(ctx, icaOwner.GetContract(), packet) + _, err = k.sudoKeeper.Sudo(ctx, icaOwner.GetContract(), msg) + if err != nil { + k.Logger(ctx).Debug("HandleTimeout: failed to Sudo contract on packet timeout", "error", err) + } - return err + return nil } // HandleChanOpenAck passes the data about a successfully created channel to the appropriate contract @@ -96,20 +94,16 @@ func (k *Keeper) HandleChanOpenAck( return errors.Wrap(err, "failed to get ica owner from port") } - if !k.contractManagerKeeper.HasContractInfo(ctx, icaOwner.GetContract()) { - //return fmt.Errorf("%s is not a contract address", icaOwner.GetContract()) - return nil - } - - _, err = k.contractManagerKeeper.SudoOnChanOpenAck(ctx, icaOwner.GetContract(), contractmanagertypes.OpenAckDetails{ + payload, err := keeper.PrepareOpenAckCallbackMessage(contractmanagertypes.OpenAckDetails{ PortID: portID, ChannelID: channelID, CounterpartyChannelID: counterpartyChannelID, CounterpartyVersion: counterpartyVersion, }) + + _, err = k.sudoKeeper.Sudo(ctx, icaOwner.GetContract(), payload) if err != nil { - k.Logger(ctx).Debug("HandleChanOpenAck: failed to sudo contract on packet timeout", "error", err) - return errors.Wrap(err, "failed to sudo the contract OnChanOpenAck") + k.Logger(ctx).Debug("HandleChanOpenAck: failed to sudo contract on channel open acknowledgement", "error", err) } return nil diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 50e74b2f0..3785f7906 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "fmt" + "github.com/neutron-org/neutron/x/contractmanager/keeper" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,19 +22,11 @@ func TestHandleAcknowledgement(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) + icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //store := ctx.KVStore(storeKey) - errACK := channeltypes.Acknowledgement{ - Response: &channeltypes.Acknowledgement_Error{ - Error: "error", - }, - } - errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) - require.NoError(t, err) resACK := channeltypes.Acknowledgement{ Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, } @@ -54,51 +47,30 @@ func TestHandleAcknowledgement(t *testing.T) { err = icak.HandleAcknowledgement(ctx, p, nil, relayerAddress) require.ErrorContains(t, err, "cannot unmarshal ICS-27 packet acknowledgement") - // success contract SudoResponse - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK) - err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) + msgAck, err := keeper.PrepareSudoCallbackMessage(p, &resACK) require.NoError(t, err) - // success contract SudoError + // success contract SudoResponse ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK) - err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck) + err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) // error contract SudoResponse ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK).Return(nil, fmt.Errorf("error sudoResponse")) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck).Return(nil, fmt.Errorf("error sudoResponse")) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) - require.Error(t, err) - - // error contract SudoError - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK).Return(nil, fmt.Errorf("error sudoError")) - err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.Error(t, err) - - // no contract SudoError - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) - err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) //// success during SudoError //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { + //wmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { // store := cachedCtx.KVStore(storeKey) // store.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) //}).Return(nil, nil) - //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) + //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) //require.NoError(t, err) @@ -107,13 +79,13 @@ func TestHandleAcknowledgement(t *testing.T) { // //// out of gas during SudoError //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { + //wmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { // store := cachedCtx.KVStore(storeKey) // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") //}) - //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) - //cmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) + //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) + //wmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) //require.NoError(t, err) @@ -122,11 +94,11 @@ func TestHandleAcknowledgement(t *testing.T) { // //// success during SudoResponse //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + //wmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { // store := cachedCtx.KVStore(storeKey) // store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value //}).Return(nil, nil) - //cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) + //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) //err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) //require.NoError(t, err) @@ -136,13 +108,13 @@ func TestHandleAcknowledgement(t *testing.T) { //// not enough gas provided by relayer for SudoCallGasLimit //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) //lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - //cmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { + //wmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { // store := cachedCtx.KVStore(storeKey) // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) // cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") //}) //feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - //cmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) + //wmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) //require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test } @@ -150,9 +122,9 @@ func TestHandleTimeout(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, feeKeeper, icaKeeper, nil) + icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" @@ -163,37 +135,33 @@ func TestHandleTimeout(t *testing.T) { SourceChannel: "channel-0", } - err := icak.HandleTimeout(ctx, channeltypes.Packet{}, relayerAddress) + msgAck, err := keeper.PrepareSudoCallbackMessage(p, nil) + require.NoError(t, err) + + err = icak.HandleTimeout(ctx, channeltypes.Packet{}, relayerAddress) require.ErrorContains(t, err, "failed to get ica owner from port") // contract success ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck) err = icak.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) // contract error ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, fmt.Errorf("SudoTimeout error")) - err = icak.HandleTimeout(ctx, p, relayerAddress) - require.Error(t, err) - - // no contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck).Return(nil, fmt.Errorf("SudoTimeout error")) err = icak.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) + } func TestHandleChanOpenAck(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, nil, nil, nil) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) + icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, nil, nil) portID := icatypes.ControllerPortPrefix + testutil.TestOwnerAddress + ".ica0" contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) channelID := "channel-0" @@ -202,27 +170,21 @@ func TestHandleChanOpenAck(t *testing.T) { err := icak.HandleChanOpenAck(ctx, "", channelID, counterpartyChannelID, "1") require.ErrorContains(t, err, "failed to get ica owner from port") - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - cmKeeper.EXPECT().SudoOnChanOpenAck(ctx, contractAddress, types.OpenAckDetails{ + msg, err := keeper.PrepareOpenAckCallbackMessage(types.OpenAckDetails{ PortID: portID, ChannelID: channelID, CounterpartyChannelID: counterpartyChannelID, CounterpartyVersion: "1", - }).Return(nil, fmt.Errorf("SudoOnChanOpenAck error")) - err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") - require.ErrorContains(t, err, "failed to sudo the contract OnChanOpenAck") + }) + require.NoError(t, err) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - cmKeeper.EXPECT().SudoOnChanOpenAck(ctx, contractAddress, types.OpenAckDetails{ - PortID: portID, - ChannelID: channelID, - CounterpartyChannelID: counterpartyChannelID, - CounterpartyVersion: "1", - }).Return(nil, nil) + // sudo error + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msg).Return(nil, fmt.Errorf("SudoOnChanOpenAck error")) err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") require.NoError(t, err) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + // sudo success + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msg) err = icak.HandleChanOpenAck(ctx, portID, channelID, counterpartyChannelID, "1") require.NoError(t, err) } diff --git a/x/interchaintxs/keeper/keeper.go b/x/interchaintxs/keeper/keeper.go index a1032c24c..737a874b5 100644 --- a/x/interchaintxs/keeper/keeper.go +++ b/x/interchaintxs/keeper/keeper.go @@ -20,13 +20,13 @@ const ( type ( Keeper struct { - Codec codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - channelKeeper types.ChannelKeeper - feeKeeper types.FeeRefunderKeeper - icaControllerKeeper types.ICAControllerKeeper - contractManagerKeeper types.ContractManagerKeeper + Codec codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey + channelKeeper types.ChannelKeeper + feeKeeper types.FeeRefunderKeeper + icaControllerKeeper types.ICAControllerKeeper + sudoKeeper types.WasmKeeper } ) @@ -36,17 +36,17 @@ func NewKeeper( memKey storetypes.StoreKey, channelKeeper types.ChannelKeeper, icaControllerKeeper types.ICAControllerKeeper, - contractManagerKeeper types.ContractManagerKeeper, + contractManagerKeeper types.WasmKeeper, feeKeeper types.FeeRefunderKeeper, ) *Keeper { return &Keeper{ - Codec: cdc, - storeKey: storeKey, - memKey: memKey, - channelKeeper: channelKeeper, - icaControllerKeeper: icaControllerKeeper, - contractManagerKeeper: contractManagerKeeper, - feeKeeper: feeKeeper, + Codec: cdc, + storeKey: storeKey, + memKey: memKey, + channelKeeper: channelKeeper, + icaControllerKeeper: icaControllerKeeper, + sudoKeeper: contractManagerKeeper, + feeKeeper: feeKeeper, } } diff --git a/x/interchaintxs/keeper/msg_server.go b/x/interchaintxs/keeper/msg_server.go index 62d3cfe5a..efc3a42d9 100644 --- a/x/interchaintxs/keeper/msg_server.go +++ b/x/interchaintxs/keeper/msg_server.go @@ -43,7 +43,7 @@ func (k Keeper) RegisterInterchainAccount(goCtx context.Context, msg *ictxtypes. return nil, errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to parse address: %s", msg.FromAddress) } - if !k.contractManagerKeeper.HasContractInfo(ctx, senderAddr) { + if !k.sudoKeeper.HasContractInfo(ctx, senderAddr) { k.Logger(ctx).Debug("RegisterInterchainAccount: contract not found", "from_address", msg.FromAddress) return nil, errors.Wrapf(ictxtypes.ErrNotContract, "%s is not a contract address", msg.FromAddress) } @@ -79,7 +79,7 @@ func (k Keeper) SubmitTx(goCtx context.Context, msg *ictxtypes.MsgSubmitTx) (*ic return nil, errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to parse address: %s", msg.FromAddress) } - if !k.contractManagerKeeper.HasContractInfo(ctx, senderAddr) { + if !k.sudoKeeper.HasContractInfo(ctx, senderAddr) { k.Logger(ctx).Debug("SubmitTx: contract not found", "from_address", msg.FromAddress) return nil, errors.Wrapf(ictxtypes.ErrNotContract, "%s is not a contract address", msg.FromAddress) } diff --git a/x/interchaintxs/keeper/msg_server_test.go b/x/interchaintxs/keeper/msg_server_test.go index 3879525ac..c662dd8da 100644 --- a/x/interchaintxs/keeper/msg_server_test.go +++ b/x/interchaintxs/keeper/msg_server_test.go @@ -25,8 +25,8 @@ func TestRegisterInterchainAccount(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, nil, icaKeeper, nil) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) + icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, icaKeeper, nil) goCtx := sdk.WrapSDKContext(ctx) msgRegAcc := types.MsgRegisterInterchainAccount{ @@ -41,18 +41,18 @@ func TestRegisterInterchainAccount(t *testing.T) { require.ErrorContains(t, err, "failed to parse address") require.Nil(t, resp) - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(false) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(false) resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) require.ErrorContains(t, err, "is not a contract address") require.Nil(t, resp) - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().RegisterInterchainAccount(ctx, msgRegAcc.ConnectionId, icaOwner.String(), "").Return(fmt.Errorf("failed to register ica")) resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) require.ErrorContains(t, err, "failed to RegisterInterchainAccount") require.Nil(t, resp) - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().RegisterInterchainAccount(ctx, msgRegAcc.ConnectionId, icaOwner.String(), "").Return(nil) resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) require.NoError(t, err) @@ -63,10 +63,10 @@ func TestSubmitTx(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) refundKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) channelKeeper := mock_types.NewMockChannelKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, cmKeeper, refundKeeper, icaKeeper, channelKeeper) + icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, refundKeeper, icaKeeper, channelKeeper) goCtx := sdk.WrapSDKContext(ctx) cosmosMsg := codectypes.Any{ @@ -97,7 +97,7 @@ func TestSubmitTx(t *testing.T) { require.Nil(t, resp) require.ErrorContains(t, err, "failed to parse address") - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(false) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(false) resp, err = icak.SubmitTx(goCtx, &submitMsg) require.Nil(t, resp) require.ErrorContains(t, err, "is not a contract address") @@ -105,21 +105,21 @@ func TestSubmitTx(t *testing.T) { params := icak.GetParams(ctx) maxMsgs := params.GetMsgSubmitTxMaxMessages() submitMsg.Msgs = make([]*codectypes.Any, maxMsgs+1) - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) resp, err = icak.SubmitTx(goCtx, &submitMsg) require.Nil(t, resp) require.ErrorContains(t, err, "MsgSubmitTx contains more messages than allowed") submitMsg.Msgs = []*codectypes.Any{&cosmosMsg} portID := "icacontroller-" + testutil.TestOwnerAddress + ".ica0" - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return("", false) resp, err = icak.SubmitTx(goCtx, &submitMsg) require.Nil(t, resp) require.ErrorContains(t, err, "failed to GetActiveChannelID for port") activeChannel := "channel-0" - // cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + // wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) // icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return(activeChannel, true) // currCodec := icak.Codec // icak.Codec = &codec.AminoCodec{} @@ -128,7 +128,7 @@ func TestSubmitTx(t *testing.T) { // require.Nil(t, resp) // require.ErrorContains(t, err, "only ProtoCodec is supported for receiving messages on the host chain") - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return(activeChannel, true) channelKeeper.EXPECT().GetNextSequenceSend(ctx, portID, activeChannel).Return(uint64(0), false) resp, err = icak.SubmitTx(goCtx, &submitMsg) @@ -136,7 +136,7 @@ func TestSubmitTx(t *testing.T) { require.ErrorContains(t, err, "sequence send not found") sequence := uint64(100) - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return(activeChannel, true) channelKeeper.EXPECT().GetNextSequenceSend(ctx, portID, activeChannel).Return(sequence, true) refundKeeper.EXPECT().LockFees(ctx, contractAddress, feerefundertypes.NewPacketID(portID, activeChannel, sequence), submitMsg.Fee).Return(fmt.Errorf("failed to lock fees")) @@ -153,7 +153,7 @@ func TestSubmitTx(t *testing.T) { } timeoutTimestamp := ctx.BlockTime().Add(time.Duration(submitMsg.Timeout) * time.Second).UnixNano() - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return(activeChannel, true) channelKeeper.EXPECT().GetNextSequenceSend(ctx, portID, activeChannel).Return(sequence, true) refundKeeper.EXPECT().LockFees(ctx, contractAddress, feerefundertypes.NewPacketID(portID, activeChannel, sequence), submitMsg.Fee).Return(nil) @@ -162,7 +162,7 @@ func TestSubmitTx(t *testing.T) { require.Nil(t, resp) require.ErrorContains(t, err, "failed to SendTx") - cmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) icaKeeper.EXPECT().GetActiveChannelID(ctx, "connection-0", portID).Return(activeChannel, true) channelKeeper.EXPECT().GetNextSequenceSend(ctx, portID, activeChannel).Return(sequence, true) refundKeeper.EXPECT().LockFees(ctx, contractAddress, feerefundertypes.NewPacketID(portID, activeChannel, sequence), submitMsg.Fee).Return(nil) diff --git a/x/interchaintxs/types/expected_keepers.go b/x/interchaintxs/types/expected_keepers.go index 13d7f7cf3..3e57253ed 100644 --- a/x/interchaintxs/types/expected_keepers.go +++ b/x/interchaintxs/types/expected_keepers.go @@ -8,7 +8,6 @@ import ( channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" ) @@ -24,14 +23,9 @@ type BankKeeper interface { // Methods imported from bank should be defined here } -type ContractManagerKeeper interface { +type WasmKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) - SudoOnChanOpenAck(ctx sdk.Context, contractAddress sdk.AccAddress, details contractmanagertypes.OpenAckDetails) ([]byte, error) - AddContractFailure(ctx sdk.Context, packet *channeltypes.Packet, address, ackType string, ack *channeltypes.Acknowledgement) - GetParams(ctx sdk.Context) (params contractmanagertypes.Params) + Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) } type ICAControllerKeeper interface { diff --git a/x/transfer/ibc_handlers.go b/x/transfer/ibc_handlers.go index efecb0cff..30ad94af3 100644 --- a/x/transfer/ibc_handlers.go +++ b/x/transfer/ibc_handlers.go @@ -6,6 +6,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/neutron-org/neutron/x/contractmanager/keeper" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" "github.com/neutron-org/neutron/x/interchaintxs/types" ) @@ -25,24 +26,25 @@ func (im IBCModule) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.P if err != nil { return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err) } - if !im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { + if !im.sudoKeeper.HasContractInfo(ctx, senderAddress) { return nil } im.wrappedKeeper.FeeKeeper.DistributeAcknowledgementFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - if ack.Success() { - _, err = im.ContractManagerKeeper.SudoResponse(ctx, senderAddress, packet, ack) - } else { - // Actually we have only one kind of error returned from acknowledgement - // maybe later we'll retrieve actual errors from events - im.keeper.Logger(ctx).Debug(ack.GetError(), "CheckTx", ctx.IsCheckTx()) - _, err = im.ContractManagerKeeper.SudoError(ctx, senderAddress, packet, ack) + msg, err := keeper.PrepareSudoCallbackMessage(packet, &ack) + if err != nil { + return errors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal Packet/Acknowledgment: %v", err) + } + + _, err = im.sudoKeeper.Sudo(ctx, senderAddress, msg) + if err != nil { + im.keeper.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) } im.keeper.Logger(ctx).Debug("acknowledgement received", "Packet data", data, "CheckTx", ctx.IsCheckTx()) - return err + return nil } // HandleTimeout passes the timeout data to the appropriate contract via a sudo call. @@ -56,13 +58,21 @@ func (im IBCModule) HandleTimeout(ctx sdk.Context, packet channeltypes.Packet, r if err != nil { return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err) } - if !im.ContractManagerKeeper.HasContractInfo(ctx, senderAddress) { + if !im.sudoKeeper.HasContractInfo(ctx, senderAddress) { return nil } + msg, err := keeper.PrepareSudoCallbackMessage(packet, nil) + if err != nil { + return errors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal Packet: %v", err) + } + im.wrappedKeeper.FeeKeeper.DistributeTimeoutFee(ctx, relayer, feetypes.NewPacketID(packet.SourcePort, packet.SourceChannel, packet.Sequence)) - _, err = im.ContractManagerKeeper.SudoTimeout(ctx, senderAddress, packet) + _, err = im.sudoKeeper.Sudo(ctx, senderAddress, msg) + if err != nil { + im.keeper.Logger(ctx).Debug("HandleAcknowledgement: failed to Sudo contract on packet acknowledgement", "error", err) + } - return err + return nil } diff --git a/x/transfer/ibc_handlers_test.go b/x/transfer/ibc_handlers_test.go index e93d91c97..03ebab6f7 100644 --- a/x/transfer/ibc_handlers_test.go +++ b/x/transfer/ibc_handlers_test.go @@ -2,6 +2,7 @@ package transfer_test import ( "fmt" + "github.com/neutron-org/neutron/x/contractmanager/keeper" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -19,36 +20,19 @@ import ( const TestCosmosAddress = "cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw" -var ( - ShouldNotBeWrittenKey = []byte("shouldnotkey") - ShouldNotBeWritten = []byte("should not be written") - ShouldBeWritten = []byte("should be written") -) - -func ShouldBeWrittenKey(suffix string) []byte { - return append([]byte("shouldkey"), []byte(suffix)...) -} - func TestHandleAcknowledgement(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) chanKeeper := mock_types.NewMockChannelKeeper(ctrl) authKeeper := mock_types.NewMockAccountKeeper(ctrl) // required to initialize keeper authKeeper.EXPECT().GetModuleAddress(transfertypes.ModuleName).Return([]byte("address")) - txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) - txModule := transfer.NewIBCModule(*txKeeper) + txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, wmKeeper, feeKeeper, chanKeeper, authKeeper) + txModule := transfer.NewIBCModule(*txKeeper, wmKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - errACK := channeltypes.Acknowledgement{ - Response: &channeltypes.Acknowledgement_Error{ - Error: "error", - }, - } - errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) - require.NoError(t, err) resACK := channeltypes.Acknowledgement{ Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, } @@ -59,6 +43,7 @@ func TestHandleAcknowledgement(t *testing.T) { SourcePort: "transfer", SourceChannel: "channel-0", } + contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" relayerAddress := sdk.MustAccAddressFromBech32(relayerBech32) @@ -92,41 +77,28 @@ func TestHandleAcknowledgement(t *testing.T) { require.NoError(t, err) p.Data = tokenBz + msgAck, err := keeper.PrepareSudoCallbackMessage(p, &resACK) + require.NoError(t, err) + // non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - // error during SudoResponse contract + // error during Sudo contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK).Return(nil, fmt.Errorf("SudoResponse error")) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck).Return(nil, fmt.Errorf("SudoResponse error")) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) - require.Error(t, err) - - // error during SudoError contract - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK).Return(nil, fmt.Errorf("SudoError error")) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - require.Error(t, err) - - // success during SudoError - ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) - feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoError(ctx, contractAddress, p, errACK) - err = txModule.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) require.NoError(t, err) - // success during SudoError contract + // success during Sudo contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoResponse(ctx, contractAddress, p, resACK) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck) err = txModule.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) } @@ -134,14 +106,14 @@ func TestHandleAcknowledgement(t *testing.T) { func TestHandleTimeout(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) chanKeeper := mock_types.NewMockChannelKeeper(ctrl) authKeeper := mock_types.NewMockAccountKeeper(ctrl) // required to initialize keeper authKeeper.EXPECT().GetModuleAddress(transfertypes.ModuleName).Return([]byte("address")) - txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, cmKeeper, feeKeeper, chanKeeper, authKeeper) - txModule := transfer.NewIBCModule(*txKeeper) + txKeeper, infCtx, _ := testkeeper.TransferKeeper(t, wmKeeper, feeKeeper, chanKeeper, authKeeper) + txModule := transfer.NewIBCModule(*txKeeper, wmKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" @@ -177,25 +149,28 @@ func TestHandleTimeout(t *testing.T) { require.NoError(t, err) p.Data = tokenBz + msg, err := keeper.PrepareSudoCallbackMessage(p, nil) + require.NoError(t, err) + // success non contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(false) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) // success contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, nil) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msg).Return(nil, nil) err = txModule.HandleTimeout(ctx, p, relayerAddress) require.NoError(t, err) // error during SudoTimeOut contract ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - cmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) + wmKeeper.EXPECT().HasContractInfo(ctx, sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress)).Return(true) feeKeeper.EXPECT().DistributeTimeoutFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - cmKeeper.EXPECT().SudoTimeout(ctx, contractAddress, p).Return(nil, fmt.Errorf("SudoTimeout error")) + wmKeeper.EXPECT().Sudo(ctx, contractAddress, msg).Return(nil, fmt.Errorf("SudoTimeout error")) err = txModule.HandleTimeout(ctx, p, relayerAddress) - require.Error(t, err) + require.NoError(t, err) } diff --git a/x/transfer/keeper/keeper.go b/x/transfer/keeper/keeper.go index 90f672939..50729540a 100644 --- a/x/transfer/keeper/keeper.go +++ b/x/transfer/keeper/keeper.go @@ -22,9 +22,9 @@ import ( // KeeperTransferWrapper is a wrapper for original ibc keeper to override response for "Transfer" method type KeeperTransferWrapper struct { keeper.Keeper - channelKeeper wrappedtypes.ChannelKeeper - FeeKeeper wrappedtypes.FeeRefunderKeeper - ContractManagerKeeper wrappedtypes.ContractManagerKeeper + channelKeeper wrappedtypes.ChannelKeeper + FeeKeeper wrappedtypes.FeeRefunderKeeper + SudoKeeper wrappedtypes.WasmKeeper } func (k KeeperTransferWrapper) Transfer(goCtx context.Context, msg *wrappedtypes.MsgTransfer) (*wrappedtypes.MsgTransferResponse, error) { @@ -46,7 +46,7 @@ func (k KeeperTransferWrapper) Transfer(goCtx context.Context, msg *wrappedtypes // if the sender is a contract, lock fees. // Because contracts are required to pay fees for the acknowledgements - if k.ContractManagerKeeper.HasContractInfo(ctx, senderAddr) { + if k.SudoKeeper.HasContractInfo(ctx, senderAddr) { if err := k.FeeKeeper.LockFees(ctx, senderAddr, feetypes.NewPacketID(msg.SourcePort, msg.SourceChannel, sequence), msg.Fee); err != nil { return nil, errors.Wrapf(err, "failed to lock fees to pay for transfer msg: %v", msg) } @@ -70,13 +70,13 @@ func NewKeeper( ics4Wrapper porttypes.ICS4Wrapper, channelKeeper wrappedtypes.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, feeKeeper wrappedtypes.FeeRefunderKeeper, - contractManagerKeeper wrappedtypes.ContractManagerKeeper, + sudoKeeper wrappedtypes.WasmKeeper, ) KeeperTransferWrapper { return KeeperTransferWrapper{ channelKeeper: channelKeeper, Keeper: keeper.NewKeeper(cdc, key, paramSpace, ics4Wrapper, channelKeeper, portKeeper, authKeeper, bankKeeper, scopedKeeper), - FeeKeeper: feeKeeper, - ContractManagerKeeper: contractManagerKeeper, + FeeKeeper: feeKeeper, + SudoKeeper: sudoKeeper, } } diff --git a/x/transfer/module.go b/x/transfer/module.go index 967fe459e..5b6bd8d82 100644 --- a/x/transfer/module.go +++ b/x/transfer/module.go @@ -27,19 +27,19 @@ import ( */ type IBCModule struct { - wrappedKeeper wrapkeeper.KeeperTransferWrapper - keeper keeper.Keeper - ContractManagerKeeper neutrontypes.ContractManagerKeeper + wrappedKeeper wrapkeeper.KeeperTransferWrapper + keeper keeper.Keeper + sudoKeeper neutrontypes.WasmKeeper transfer.IBCModule } // NewIBCModule creates a new IBCModule given the keeper -func NewIBCModule(k wrapkeeper.KeeperTransferWrapper) IBCModule { +func NewIBCModule(k wrapkeeper.KeeperTransferWrapper, sudoKeeper neutrontypes.WasmKeeper) IBCModule { return IBCModule{ - wrappedKeeper: k, - keeper: k.Keeper, - ContractManagerKeeper: k.ContractManagerKeeper, - IBCModule: transfer.NewIBCModule(k.Keeper), + wrappedKeeper: k, + keeper: k.Keeper, + sudoKeeper: sudoKeeper, + IBCModule: transfer.NewIBCModule(k.Keeper), } } diff --git a/x/transfer/types/expected_keepers.go b/x/transfer/types/expected_keepers.go index 774daf841..854b8e490 100644 --- a/x/transfer/types/expected_keepers.go +++ b/x/transfer/types/expected_keepers.go @@ -7,12 +7,9 @@ import ( feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" ) -// ContractManagerKeeper defines the expected interface needed to add ack information about sudo failure. -type ContractManagerKeeper interface { +type WasmKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool - SudoResponse(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoError(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, ack channeltypes.Acknowledgement) ([]byte, error) - SudoTimeout(ctx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet) ([]byte, error) + Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) } type FeeRefunderKeeper interface { From 7db0608a02672c38b79f791902a59123b3b22515 Mon Sep 17 00:00:00 2001 From: swelf Date: Thu, 14 Sep 2023 19:51:50 +0300 Subject: [PATCH 14/18] added fee charging for ica creation --- app/app.go | 2 + app/upgrades/sdk47/upgrades.go | 3 +- proto/neutron/interchaintxs/v1/params.proto | 3 + proto/neutron/interchaintxs/v1/tx.proto | 5 + .../interchaintxs/keeper/interchaintxs.go | 13 +- .../interchaintxs/types/expected_keepers.go | 60 ++++++- wasmbinding/bindings/msg.go | 6 +- wasmbinding/message_plugin.go | 1 + wasmbinding/test/custom_message_test.go | 13 ++ x/interchaintxs/genesis_test.go | 2 +- .../grpc_query_interchainaccount_test.go | 2 +- .../keeper/grpc_query_params_test.go | 2 +- x/interchaintxs/keeper/ibc_handlers_test.go | 12 +- x/interchaintxs/keeper/keeper.go | 30 ++++ x/interchaintxs/keeper/msg_server.go | 4 + x/interchaintxs/keeper/msg_server_test.go | 38 ++++- x/interchaintxs/keeper/params_test.go | 2 +- x/interchaintxs/types/expected_keepers.go | 7 +- x/interchaintxs/types/params.go | 21 +-- x/interchaintxs/types/params.pb.go | 98 +++++++++-- x/interchaintxs/types/tx.pb.go | 158 ++++++++++++------ 21 files changed, 386 insertions(+), 96 deletions(-) diff --git a/app/app.go b/app/app.go index 6804471df..5a5e71871 100644 --- a/app/app.go +++ b/app/app.go @@ -662,6 +662,8 @@ func New( app.ICAControllerKeeper, contractmanager.NewSudoLimitWrapper(app.ContractManagerKeeper, &app.WasmKeeper), app.FeeKeeper, + app.BankKeeper, + app.FeeBurnerKeeper, ) app.CronKeeper = *cronkeeper.NewKeeper(appCodec, keys[crontypes.StoreKey], keys[crontypes.MemStoreKey], app.AccountKeeper) diff --git a/app/upgrades/sdk47/upgrades.go b/app/upgrades/sdk47/upgrades.go index f0cfaa7e3..54529d9d6 100644 --- a/app/upgrades/sdk47/upgrades.go +++ b/app/upgrades/sdk47/upgrades.go @@ -135,7 +135,7 @@ func migrateCronParams(ctx sdk.Context, paramsKeepers paramskeeper.Keeper, store func migrateFeeRefunderParams(ctx sdk.Context, paramsKeepers paramskeeper.Keeper, storeKey storetypes.StoreKey, codec codec.Codec) error { store := ctx.KVStore(storeKey) var currParams feerefundertypes.Params - subspace, _ := paramsKeepers.GetSubspace(crontypes.StoreKey) + subspace, _ := paramsKeepers.GetSubspace(feerefundertypes.StoreKey) subspace.GetParamSet(ctx, &currParams) if err := currParams.Validate(); err != nil { @@ -197,6 +197,7 @@ func migrateInterchainTxsParams(ctx sdk.Context, paramsKeepers paramskeeper.Keep var currParams interchaintxstypes.Params subspace, _ := paramsKeepers.GetSubspace(interchaintxstypes.StoreKey) subspace.GetParamSet(ctx, &currParams) + currParams.RegisterFee = interchaintxstypes.DefaultRegisterFee if err := currParams.Validate(); err != nil { return err diff --git a/proto/neutron/interchaintxs/v1/params.proto b/proto/neutron/interchaintxs/v1/params.proto index c25893eef..734e7cb2e 100644 --- a/proto/neutron/interchaintxs/v1/params.proto +++ b/proto/neutron/interchaintxs/v1/params.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package neutron.interchaintxs; import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; option go_package = "github.com/neutron-org/neutron/x/interchaintxs/types"; @@ -10,4 +11,6 @@ message Params { option (gogoproto.goproto_stringer) = false; // Defines maximum amount of messages to be passed in MsgSubmitTx uint64 msg_submit_tx_max_messages = 1; + // Defines a minimum fee required to register interchain account + repeated cosmos.base.v1beta1.Coin register_fee = 2 [ (gogoproto.nullable) = false ]; } diff --git a/proto/neutron/interchaintxs/v1/tx.proto b/proto/neutron/interchaintxs/v1/tx.proto index c871accd2..8fd53b3f4 100644 --- a/proto/neutron/interchaintxs/v1/tx.proto +++ b/proto/neutron/interchaintxs/v1/tx.proto @@ -4,6 +4,7 @@ package neutron.interchaintxs.v1; option go_package = "github.com/neutron-org/neutron/x/interchaintxs/types"; import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; import "gogoproto/gogo.proto"; import "google/api/http.proto"; import "google/api/annotations.proto"; @@ -26,6 +27,10 @@ message MsgRegisterInterchainAccount { string connection_id = 2 [ (gogoproto.moretags) = "yaml:\"connection_id\"" ]; string interchain_account_id = 3 [ (gogoproto.moretags) = "yaml:\"interchain_account_id\"" ]; + repeated cosmos.base.v1beta1.Coin register_fee = 4 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; } // MsgRegisterInterchainAccountResponse is the response type for diff --git a/testutil/interchaintxs/keeper/interchaintxs.go b/testutil/interchaintxs/keeper/interchaintxs.go index 4961a46f9..51d23413f 100644 --- a/testutil/interchaintxs/keeper/interchaintxs.go +++ b/testutil/interchaintxs/keeper/interchaintxs.go @@ -17,7 +17,14 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) -func InterchainTxsKeeper(t testing.TB, managerKeeper types.WasmKeeper, refunderKeeper types.FeeRefunderKeeper, icaControllerKeeper types.ICAControllerKeeper, channelKeeper types.ChannelKeeper) (*keeper.Keeper, sdk.Context, *storetypes.KVStoreKey) { +func InterchainTxsKeeper( + t testing.TB, + managerKeeper types.WasmKeeper, + refunderKeeper types.FeeRefunderKeeper, + icaControllerKeeper types.ICAControllerKeeper, + channelKeeper types.ChannelKeeper, + bankKeeper types.BankKeeper, + feeburnerKeeper types.FeeBurnerKeeper) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -38,6 +45,8 @@ func InterchainTxsKeeper(t testing.TB, managerKeeper types.WasmKeeper, refunderK icaControllerKeeper, managerKeeper, refunderKeeper, + bankKeeper, + feeburnerKeeper, ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) @@ -46,5 +55,5 @@ func InterchainTxsKeeper(t testing.TB, managerKeeper types.WasmKeeper, refunderK err := k.SetParams(ctx, types.DefaultParams()) require.NoError(t, err) - return k, ctx, storeKey + return k, ctx } diff --git a/testutil/mocks/interchaintxs/types/expected_keepers.go b/testutil/mocks/interchaintxs/types/expected_keepers.go index d42f778a0..97633afb9 100644 --- a/testutil/mocks/interchaintxs/types/expected_keepers.go +++ b/testutil/mocks/interchaintxs/types/expected_keepers.go @@ -14,7 +14,8 @@ import ( types3 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" - types4 "github.com/neutron-org/neutron/x/feerefunder/types" + types4 "github.com/neutron-org/neutron/x/feeburner/types" + types5 "github.com/neutron-org/neutron/x/feerefunder/types" ) // MockAccountKeeper is a mock of AccountKeeper interface. @@ -77,6 +78,20 @@ func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { return m.recorder } +// SendCoins mocks base method. +func (m *MockBankKeeper) SendCoins(ctx types.Context, fromAddr, toAddr types.AccAddress, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoins indicates an expected call of SendCoins. +func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoins", reflect.TypeOf((*MockBankKeeper)(nil).SendCoins), ctx, fromAddr, toAddr, amt) +} + // SpendableCoins mocks base method. func (m *MockBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { m.ctrl.T.Helper() @@ -249,7 +264,7 @@ func (m *MockFeeRefunderKeeper) EXPECT() *MockFeeRefunderKeeperMockRecorder { } // DistributeAcknowledgementFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types4.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeAcknowledgementFee(ctx types.Context, receiver types.AccAddress, packetID types5.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeAcknowledgementFee", ctx, receiver, packetID) } @@ -261,7 +276,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeAcknowledgementFee(ctx, r } // DistributeTimeoutFee mocks base method. -func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types4.PacketID) { +func (m *MockFeeRefunderKeeper) DistributeTimeoutFee(ctx types.Context, receiver types.AccAddress, packetID types5.PacketID) { m.ctrl.T.Helper() m.ctrl.Call(m, "DistributeTimeoutFee", ctx, receiver, packetID) } @@ -273,7 +288,7 @@ func (mr *MockFeeRefunderKeeperMockRecorder) DistributeTimeoutFee(ctx, receiver, } // LockFees mocks base method. -func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types4.PacketID, fee types4.Fee) error { +func (m *MockFeeRefunderKeeper) LockFees(ctx types.Context, payer types.AccAddress, packetID types5.PacketID, fee types5.Fee) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LockFees", ctx, payer, packetID, fee) ret0, _ := ret[0].(error) @@ -353,3 +368,40 @@ func (mr *MockChannelKeeperMockRecorder) GetNextSequenceSend(ctx, portID, channe mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNextSequenceSend", reflect.TypeOf((*MockChannelKeeper)(nil).GetNextSequenceSend), ctx, portID, channelID) } + +// MockFeeBurnerKeeper is a mock of FeeBurnerKeeper interface. +type MockFeeBurnerKeeper struct { + ctrl *gomock.Controller + recorder *MockFeeBurnerKeeperMockRecorder +} + +// MockFeeBurnerKeeperMockRecorder is the mock recorder for MockFeeBurnerKeeper. +type MockFeeBurnerKeeperMockRecorder struct { + mock *MockFeeBurnerKeeper +} + +// NewMockFeeBurnerKeeper creates a new mock instance. +func NewMockFeeBurnerKeeper(ctrl *gomock.Controller) *MockFeeBurnerKeeper { + mock := &MockFeeBurnerKeeper{ctrl: ctrl} + mock.recorder = &MockFeeBurnerKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFeeBurnerKeeper) EXPECT() *MockFeeBurnerKeeperMockRecorder { + return m.recorder +} + +// GetParams mocks base method. +func (m *MockFeeBurnerKeeper) GetParams(ctx types.Context) types4.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types4.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockFeeBurnerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockFeeBurnerKeeper)(nil).GetParams), ctx) +} diff --git a/wasmbinding/bindings/msg.go b/wasmbinding/bindings/msg.go index 13793b11b..b2fd53f51 100644 --- a/wasmbinding/bindings/msg.go +++ b/wasmbinding/bindings/msg.go @@ -4,6 +4,7 @@ package bindings import ( "cosmossdk.io/math" cosmostypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" paramChange "github.com/cosmos/cosmos-sdk/x/params/types/proposal" feetypes "github.com/neutron-org/neutron/x/feerefunder/types" @@ -72,8 +73,9 @@ type SubmitTxResponse struct { // RegisterInterchainAccount creates account on remote chain. type RegisterInterchainAccount struct { - ConnectionId string `json:"connection_id"` - InterchainAccountId string `json:"interchain_account_id"` + ConnectionId string `json:"connection_id"` + InterchainAccountId string `json:"interchain_account_id"` + RegisterFee sdk.Coins `json:"register_fee"` } // RegisterInterchainAccountResponse holds response for RegisterInterchainAccount. diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index 2768ba7a3..383ac6d48 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -691,6 +691,7 @@ func (m *CustomMessenger) performRegisterInterchainAccount(ctx sdk.Context, cont FromAddress: contractAddr.String(), ConnectionId: reg.ConnectionId, InterchainAccountId: reg.InterchainAccountId, + RegisterFee: reg.RegisterFee, } if err := msg.ValidateBasic(); err != nil { return nil, errors.Wrap(err, "failed to validate incoming RegisterInterchainAccount message") diff --git a/wasmbinding/test/custom_message_test.go b/wasmbinding/test/custom_message_test.go index 1f5d11d5b..5346823d7 100644 --- a/wasmbinding/test/custom_message_test.go +++ b/wasmbinding/test/custom_message_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" keeper2 "github.com/neutron-org/neutron/x/contractmanager/keeper" + feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" "testing" ibcchanneltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" @@ -76,15 +77,27 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccount() { suite.contractAddress = suite.InstantiateReflectContract(suite.ctx, suite.contractOwner, codeID) suite.Require().NotEmpty(suite.contractAddress) + err := suite.neutron.FeeBurnerKeeper.SetParams(suite.ctx, feeburnertypes.Params{ + NeutronDenom: "untrn", + TreasuryAddress: "neutron13jrwrtsyjjuynlug65r76r2zvfw5xjcq6532h2", + }) + suite.Require().NoError(err) + // Craft RegisterInterchainAccount message msg, err := json.Marshal(bindings.NeutronMsg{ RegisterInterchainAccount: &bindings.RegisterInterchainAccount{ ConnectionId: suite.Path.EndpointA.ConnectionID, InterchainAccountId: testutil.TestInterchainID, + RegisterFee: sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1000))), }, }) suite.NoError(err) + bankKeeper := suite.neutron.BankKeeper + senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() + err = bankKeeper.SendCoins(suite.ctx, senderAddress, suite.contractAddress, sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1000)))) + suite.NoError(err) + // Dispatch RegisterInterchainAccount message events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ Custom: msg, diff --git a/x/interchaintxs/genesis_test.go b/x/interchaintxs/genesis_test.go index 88cb68297..2adc3476d 100644 --- a/x/interchaintxs/genesis_test.go +++ b/x/interchaintxs/genesis_test.go @@ -16,7 +16,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx, _ := keepertest.InterchainTxsKeeper(t, nil, nil, nil, nil) + k, ctx := keepertest.InterchainTxsKeeper(t, nil, nil, nil, nil, nil, nil) interchaintxs.InitGenesis(ctx, *k, genesisState) got := interchaintxs.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/interchaintxs/keeper/grpc_query_interchainaccount_test.go b/x/interchaintxs/keeper/grpc_query_interchainaccount_test.go index 1c631682f..5de39ad40 100644 --- a/x/interchaintxs/keeper/grpc_query_interchainaccount_test.go +++ b/x/interchaintxs/keeper/grpc_query_interchainaccount_test.go @@ -20,7 +20,7 @@ func TestKeeper_InterchainAccountAddress(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) - keeper, ctx, _ := testkeeper.InterchainTxsKeeper(t, nil, nil, icaKeeper, nil) + keeper, ctx := testkeeper.InterchainTxsKeeper(t, nil, nil, icaKeeper, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) resp, err := keeper.InterchainAccountAddress(wctx, nil) diff --git a/x/interchaintxs/keeper/grpc_query_params_test.go b/x/interchaintxs/keeper/grpc_query_params_test.go index 90df327c3..5fa31e0c3 100644 --- a/x/interchaintxs/keeper/grpc_query_params_test.go +++ b/x/interchaintxs/keeper/grpc_query_params_test.go @@ -11,7 +11,7 @@ import ( ) func TestParamsQuery(t *testing.T) { - keeper, ctx, _ := testkeeper.InterchainTxsKeeper(t, nil, nil, nil, nil) + keeper, ctx := testkeeper.InterchainTxsKeeper(t, nil, nil, nil, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 3785f7906..50cc29fd8 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -24,7 +24,9 @@ func TestHandleAcknowledgement(t *testing.T) { icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil) + bankKeeper := mock_types.NewMockBankKeeper(ctrl) + feeburnerKeeper := mock_types.NewMockFeeBurnerKeeper(ctrl) + icak, infCtx := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil, bankKeeper, feeburnerKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) resACK := channeltypes.Acknowledgement{ @@ -124,7 +126,9 @@ func TestHandleTimeout(t *testing.T) { icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) wmKeeper := mock_types.NewMockWasmKeeper(ctrl) feeKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) - icak, infCtx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil) + bankKeeper := mock_types.NewMockBankKeeper(ctrl) + feeburnerKeeper := mock_types.NewMockFeeBurnerKeeper(ctrl) + icak, infCtx := testkeeper.InterchainTxsKeeper(t, wmKeeper, feeKeeper, icaKeeper, nil, bankKeeper, feeburnerKeeper) ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) relayerBech32 := "neutron1fxudpred77a0grgh69u0j7y84yks5ev4n5050z45kecz792jnd6scqu98z" @@ -161,7 +165,9 @@ func TestHandleChanOpenAck(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() wmKeeper := mock_types.NewMockWasmKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, nil, nil) + bankKeeper := mock_types.NewMockBankKeeper(ctrl) + feeburnerKeeper := mock_types.NewMockFeeBurnerKeeper(ctrl) + icak, ctx := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, nil, nil, bankKeeper, feeburnerKeeper) portID := icatypes.ControllerPortPrefix + testutil.TestOwnerAddress + ".ica0" contractAddress := sdk.MustAccAddressFromBech32(testutil.TestOwnerAddress) channelID := "channel-0" diff --git a/x/interchaintxs/keeper/keeper.go b/x/interchaintxs/keeper/keeper.go index 737a874b5..e27d294d2 100644 --- a/x/interchaintxs/keeper/keeper.go +++ b/x/interchaintxs/keeper/keeper.go @@ -1,7 +1,9 @@ package keeper import ( + "cosmossdk.io/errors" "fmt" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" @@ -27,6 +29,8 @@ type ( feeKeeper types.FeeRefunderKeeper icaControllerKeeper types.ICAControllerKeeper sudoKeeper types.WasmKeeper + bankKeeper types.BankKeeper + feeBurnerKeeper types.FeeBurnerKeeper } ) @@ -38,6 +42,8 @@ func NewKeeper( icaControllerKeeper types.ICAControllerKeeper, contractManagerKeeper types.WasmKeeper, feeKeeper types.FeeRefunderKeeper, + bankKeeper types.BankKeeper, + feeBurnerKeeper types.FeeBurnerKeeper, ) *Keeper { return &Keeper{ Codec: cdc, @@ -47,9 +53,33 @@ func NewKeeper( icaControllerKeeper: icaControllerKeeper, sudoKeeper: contractManagerKeeper, feeKeeper: feeKeeper, + bankKeeper: bankKeeper, + feeBurnerKeeper: feeBurnerKeeper, } } func (k *Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } + +func (k Keeper) ChargeFee(ctx sdk.Context, payer sdk.AccAddress, fee sdk.Coins) error { + k.Logger(ctx).Debug("Trying to change fees", "payer", payer, "fee", fee) + + params := k.GetParams(ctx) + + if !fee.IsAnyGTE(params.RegisterFee) { + return errors.Wrapf(sdkerrors.ErrInsufficientFee, "provided fee is less than min governance set ack fee: %v < %v", fee, params.RegisterFee) + } + + treasury := k.feeBurnerKeeper.GetParams(ctx).TreasuryAddress + treasuryAddress, err := sdk.AccAddressFromBech32(treasury) + if err != nil { + return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to convert treasury, bech32 to AccAddress: %v: %v", treasury, err) + } + + err = k.bankKeeper.SendCoins(ctx, payer, treasuryAddress, fee) + if err != nil { + return errors.Wrapf(err, "failed send fee(%v) from %v to %v", fee, payer, treasury) + } + return nil +} diff --git a/x/interchaintxs/keeper/msg_server.go b/x/interchaintxs/keeper/msg_server.go index efc3a42d9..4c23bfbef 100644 --- a/x/interchaintxs/keeper/msg_server.go +++ b/x/interchaintxs/keeper/msg_server.go @@ -48,6 +48,10 @@ func (k Keeper) RegisterInterchainAccount(goCtx context.Context, msg *ictxtypes. return nil, errors.Wrapf(ictxtypes.ErrNotContract, "%s is not a contract address", msg.FromAddress) } + if err := k.ChargeFee(ctx, senderAddr, msg.RegisterFee); err != nil { + return nil, errors.Wrapf(err, "failed to charge fees to pay for RegisterInterchainAccount msg: %s", msg) + } + icaOwner := ictxtypes.NewICAOwnerFromAddress(senderAddr, msg.InterchainAccountId) // FIXME: empty version string doesn't look good diff --git a/x/interchaintxs/keeper/msg_server_test.go b/x/interchaintxs/keeper/msg_server_test.go index c662dd8da..635464474 100644 --- a/x/interchaintxs/keeper/msg_server_test.go +++ b/x/interchaintxs/keeper/msg_server_test.go @@ -2,6 +2,8 @@ package keeper_test import ( "fmt" + "github.com/neutron-org/neutron/app/params" + feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" "testing" "time" @@ -21,12 +23,16 @@ import ( "github.com/neutron-org/neutron/x/interchaintxs/types" ) +const TestTreasury = "neutron1dua3d89szsmd3vwg0y5a2689ah0g4x68ps8vew" + func TestRegisterInterchainAccount(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() icaKeeper := mock_types.NewMockICAControllerKeeper(ctrl) wmKeeper := mock_types.NewMockWasmKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, icaKeeper, nil) + bankKeeper := mock_types.NewMockBankKeeper(ctrl) + feeburnerKeeper := mock_types.NewMockFeeBurnerKeeper(ctrl) + icak, ctx := testkeeper.InterchainTxsKeeper(t, wmKeeper, nil, icaKeeper, nil, bankKeeper, feeburnerKeeper) goCtx := sdk.WrapSDKContext(ctx) msgRegAcc := types.MsgRegisterInterchainAccount{ @@ -47,12 +53,38 @@ func TestRegisterInterchainAccount(t *testing.T) { require.Nil(t, resp) wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) + require.ErrorContains(t, err, "failed to charge fees to pay for RegisterInterchainAccount msg") + require.Nil(t, resp) + + msgRegAcc.RegisterFee = sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1000))) + + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + feeburnerKeeper.EXPECT().GetParams(ctx).Return(feeburnertypes.Params{ + TreasuryAddress: TestTreasury, + }) + bankKeeper.EXPECT().SendCoins(ctx, sdk.MustAccAddressFromBech32(msgRegAcc.FromAddress), sdk.MustAccAddressFromBech32(TestTreasury), msgRegAcc.RegisterFee) icaKeeper.EXPECT().RegisterInterchainAccount(ctx, msgRegAcc.ConnectionId, icaOwner.String(), "").Return(fmt.Errorf("failed to register ica")) resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) require.ErrorContains(t, err, "failed to RegisterInterchainAccount") require.Nil(t, resp) wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + feeburnerKeeper.EXPECT().GetParams(ctx).Return(feeburnertypes.Params{ + TreasuryAddress: TestTreasury, + }) + bankKeeper.EXPECT(). + SendCoins(ctx, sdk.MustAccAddressFromBech32(msgRegAcc.FromAddress), sdk.MustAccAddressFromBech32(TestTreasury), msgRegAcc.RegisterFee). + Return(fmt.Errorf("failed to send coins")) + resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) + require.ErrorContains(t, err, "failed to send coins") + require.Nil(t, resp) + + wmKeeper.EXPECT().HasContractInfo(ctx, contractAddress).Return(true) + feeburnerKeeper.EXPECT().GetParams(ctx).Return(feeburnertypes.Params{ + TreasuryAddress: TestTreasury, + }) + bankKeeper.EXPECT().SendCoins(ctx, sdk.MustAccAddressFromBech32(msgRegAcc.FromAddress), sdk.MustAccAddressFromBech32(TestTreasury), msgRegAcc.RegisterFee) icaKeeper.EXPECT().RegisterInterchainAccount(ctx, msgRegAcc.ConnectionId, icaOwner.String(), "").Return(nil) resp, err = icak.RegisterInterchainAccount(goCtx, &msgRegAcc) require.NoError(t, err) @@ -66,7 +98,9 @@ func TestSubmitTx(t *testing.T) { wmKeeper := mock_types.NewMockWasmKeeper(ctrl) refundKeeper := mock_types.NewMockFeeRefunderKeeper(ctrl) channelKeeper := mock_types.NewMockChannelKeeper(ctrl) - icak, ctx, _ := testkeeper.InterchainTxsKeeper(t, wmKeeper, refundKeeper, icaKeeper, channelKeeper) + bankKeeper := mock_types.NewMockBankKeeper(ctrl) + feeburnerKeeper := mock_types.NewMockFeeBurnerKeeper(ctrl) + icak, ctx := testkeeper.InterchainTxsKeeper(t, wmKeeper, refundKeeper, icaKeeper, channelKeeper, bankKeeper, feeburnerKeeper) goCtx := sdk.WrapSDKContext(ctx) cosmosMsg := codectypes.Any{ diff --git a/x/interchaintxs/keeper/params_test.go b/x/interchaintxs/keeper/params_test.go index 0ecf910fc..0899ac765 100644 --- a/x/interchaintxs/keeper/params_test.go +++ b/x/interchaintxs/keeper/params_test.go @@ -10,7 +10,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx, _ := testkeeper.InterchainTxsKeeper(t, nil, nil, nil, nil) + k, ctx := testkeeper.InterchainTxsKeeper(t, nil, nil, nil, nil, nil, nil) params := types.DefaultParams() err := k.SetParams(ctx, params) diff --git a/x/interchaintxs/types/expected_keepers.go b/x/interchaintxs/types/expected_keepers.go index 3e57253ed..23648d88d 100644 --- a/x/interchaintxs/types/expected_keepers.go +++ b/x/interchaintxs/types/expected_keepers.go @@ -7,6 +7,7 @@ import ( icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + feeburnertypes "github.com/neutron-org/neutron/x/feeburner/types" feerefundertypes "github.com/neutron-org/neutron/x/feerefunder/types" ) @@ -20,7 +21,7 @@ type AccountKeeper interface { // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here + SendCoins(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error } type WasmKeeper interface { @@ -47,3 +48,7 @@ type ChannelKeeper interface { GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) GetConnection(ctx sdk.Context, connectionID string) (ibcexported.ConnectionI, error) } + +type FeeBurnerKeeper interface { + GetParams(ctx sdk.Context) feeburnertypes.Params +} diff --git a/x/interchaintxs/types/params.go b/x/interchaintxs/types/params.go index c9216156e..7a9f54638 100644 --- a/x/interchaintxs/types/params.go +++ b/x/interchaintxs/types/params.go @@ -2,8 +2,10 @@ package types import ( "fmt" - + sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/neutron-org/neutron/app/params" + "gopkg.in/yaml.v2" ) @@ -12,29 +14,20 @@ var _ paramtypes.ParamSet = (*Params)(nil) var ( KeyMsgSubmitTxMaxMessages = []byte("MsgSubmitTxMaxMessages") DefaultMsgSubmitTxMaxMessages = uint64(16) + DefaultRegisterFee = sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1000))) ) -// ParamKeyTable the param key table for launch module -func ParamKeyTable() paramtypes.KeyTable { - return paramtypes.NewKeyTable( - paramtypes.NewParamSetPair( - KeyMsgSubmitTxMaxMessages, - DefaultMsgSubmitTxMaxMessages, - validateMsgSubmitTxMaxMessages, - ), - ) -} - // NewParams creates a new Params instance -func NewParams(msgSubmitTxMaxMessages uint64) Params { +func NewParams(msgSubmitTxMaxMessages uint64, registerFee sdk.Coins) Params { return Params{ MsgSubmitTxMaxMessages: msgSubmitTxMaxMessages, + RegisterFee: registerFee, } } // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams(DefaultMsgSubmitTxMaxMessages) + return NewParams(DefaultMsgSubmitTxMaxMessages, DefaultRegisterFee) } // ParamSetPairs get the params.ParamSet diff --git a/x/interchaintxs/types/params.pb.go b/x/interchaintxs/types/params.pb.go index e43702998..00f396fe7 100644 --- a/x/interchaintxs/types/params.pb.go +++ b/x/interchaintxs/types/params.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -27,6 +28,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Params struct { // Defines maximum amount of messages to be passed in MsgSubmitTx MsgSubmitTxMaxMessages uint64 `protobuf:"varint,1,opt,name=msg_submit_tx_max_messages,json=msgSubmitTxMaxMessages,proto3" json:"msg_submit_tx_max_messages,omitempty"` + // Defines a minimum fee required to register interchain account + RegisterFee []types.Coin `protobuf:"bytes,2,rep,name=register_fee,json=registerFee,proto3" json:"register_fee"` } func (m *Params) Reset() { *m = Params{} } @@ -68,6 +71,13 @@ func (m *Params) GetMsgSubmitTxMaxMessages() uint64 { return 0 } +func (m *Params) GetRegisterFee() []types.Coin { + if m != nil { + return m.RegisterFee + } + return nil +} + func init() { proto.RegisterType((*Params)(nil), "neutron.interchaintxs.Params") } @@ -77,21 +87,25 @@ func init() { } var fileDescriptor_52b0ced89d3fa9c6 = []byte{ - // 211 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, 0xcc, 0x2b, 0x49, 0x2d, 0x4a, 0xce, 0x48, 0xcc, 0xcc, 0x2b, 0xa9, - 0x28, 0xd6, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, - 0xc9, 0x17, 0x12, 0x85, 0x2a, 0xd3, 0x43, 0x51, 0x26, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, - 0xa1, 0x0f, 0x62, 0x41, 0x14, 0x2b, 0x79, 0x71, 0xb1, 0x05, 0x80, 0x35, 0x0b, 0x59, 0x71, 0x49, - 0xe5, 0x16, 0xa7, 0xc7, 0x17, 0x97, 0x26, 0xe5, 0x66, 0x96, 0xc4, 0x97, 0x54, 0xc4, 0xe7, 0x26, - 0x56, 0xc4, 0xe7, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0x16, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xb0, - 0x04, 0x89, 0xe5, 0x16, 0xa7, 0x07, 0x83, 0x15, 0x84, 0x54, 0xf8, 0x26, 0x56, 0xf8, 0x42, 0x65, - 0xad, 0x58, 0x66, 0x2c, 0x90, 0x67, 0x70, 0xf2, 0x3b, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, - 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, - 0x39, 0x86, 0x28, 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, - 0xeb, 0x74, 0xf3, 0x8b, 0xd2, 0x61, 0x6c, 0xfd, 0x0a, 0x34, 0x2f, 0x95, 0x54, 0x16, 0xa4, 0x16, - 0x27, 0xb1, 0x81, 0x9d, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x61, 0xb1, 0xdd, 0xe6, 0xf8, - 0x00, 0x00, 0x00, + // 285 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0x31, 0x4b, 0x03, 0x31, + 0x18, 0x86, 0x2f, 0x5a, 0x3a, 0x5c, 0x9d, 0x8a, 0x4a, 0xed, 0x90, 0x16, 0x41, 0xe8, 0x62, 0x42, + 0xd5, 0xa9, 0x63, 0x05, 0xb7, 0x8a, 0x54, 0x27, 0x97, 0x23, 0x77, 0x7c, 0xa6, 0x19, 0x92, 0x1c, + 0xf9, 0x72, 0x25, 0xfe, 0x09, 0x71, 0x74, 0xf4, 0xe7, 0x74, 0xec, 0xe8, 0x24, 0x72, 0xf7, 0x47, + 0xa4, 0x77, 0xd7, 0x41, 0xb7, 0x17, 0xde, 0x27, 0x79, 0xf8, 0xde, 0xf8, 0xc2, 0x40, 0xe1, 0x9d, + 0x35, 0x5c, 0x19, 0x0f, 0x2e, 0x5b, 0x09, 0x65, 0x7c, 0x40, 0xbe, 0x9e, 0xf2, 0x5c, 0x38, 0xa1, + 0x91, 0xe5, 0xce, 0x7a, 0xdb, 0x3f, 0x69, 0x31, 0xf6, 0x07, 0x1b, 0x1e, 0x4b, 0x2b, 0x6d, 0x4d, + 0xf0, 0x5d, 0x6a, 0xe0, 0x21, 0xcd, 0x2c, 0x6a, 0x8b, 0x3c, 0x15, 0x08, 0x7c, 0x3d, 0x4d, 0xc1, + 0x8b, 0x29, 0xcf, 0xac, 0x32, 0x4d, 0x7f, 0xfe, 0x46, 0xe2, 0xee, 0x43, 0xfd, 0x7b, 0x7f, 0x16, + 0x0f, 0x35, 0xca, 0x04, 0x8b, 0x54, 0x2b, 0x9f, 0xf8, 0x90, 0x68, 0x11, 0x12, 0x0d, 0x88, 0x42, + 0x02, 0x0e, 0xc8, 0x98, 0x4c, 0x3a, 0xcb, 0x53, 0x8d, 0xf2, 0xb1, 0x06, 0x9e, 0xc2, 0x42, 0x84, + 0x45, 0xdb, 0xf6, 0xe7, 0xf1, 0x91, 0x03, 0xa9, 0xd0, 0x83, 0x4b, 0x5e, 0x00, 0x06, 0x07, 0xe3, + 0xc3, 0x49, 0xef, 0xea, 0x8c, 0x35, 0x76, 0xb6, 0xb3, 0xb3, 0xd6, 0xce, 0x6e, 0xad, 0x32, 0xf3, + 0xce, 0xe6, 0x7b, 0x14, 0x2d, 0x7b, 0xfb, 0x47, 0x77, 0x00, 0xb3, 0xce, 0xc7, 0xe7, 0x28, 0x9a, + 0xdf, 0x6f, 0x4a, 0x4a, 0xb6, 0x25, 0x25, 0x3f, 0x25, 0x25, 0xef, 0x15, 0x8d, 0xb6, 0x15, 0x8d, + 0xbe, 0x2a, 0x1a, 0x3d, 0xdf, 0x48, 0xe5, 0x57, 0x45, 0xca, 0x32, 0xab, 0x79, 0x3b, 0xc1, 0xa5, + 0x75, 0x72, 0x9f, 0x79, 0xf8, 0xb7, 0x9b, 0x7f, 0xcd, 0x01, 0xd3, 0x6e, 0x7d, 0xe7, 0xf5, 0x6f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x81, 0x7b, 0xd6, 0x7d, 0x5d, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -114,6 +128,20 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.RegisterFee) > 0 { + for iNdEx := len(m.RegisterFee) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RegisterFee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } if m.MsgSubmitTxMaxMessages != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MsgSubmitTxMaxMessages)) i-- @@ -142,6 +170,12 @@ func (m *Params) Size() (n int) { if m.MsgSubmitTxMaxMessages != 0 { n += 1 + sovParams(uint64(m.MsgSubmitTxMaxMessages)) } + if len(m.RegisterFee) > 0 { + for _, e := range m.RegisterFee { + l = e.Size() + n += 1 + l + sovParams(uint64(l)) + } + } return n } @@ -199,6 +233,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegisterFee", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegisterFee = append(m.RegisterFee, types.Coin{}) + if err := m.RegisterFee[len(m.RegisterFee)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/interchaintxs/types/tx.pb.go b/x/interchaintxs/types/tx.pb.go index 41bf28b30..80aa5ff45 100644 --- a/x/interchaintxs/types/tx.pb.go +++ b/x/interchaintxs/types/tx.pb.go @@ -7,11 +7,13 @@ import ( context "context" fmt "fmt" _ "github.com/cosmos/cosmos-proto" - types "github.com/cosmos/cosmos-sdk/codec/types" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" - types1 "github.com/neutron-org/neutron/x/feerefunder/types" + types2 "github.com/neutron-org/neutron/x/feerefunder/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -34,9 +36,10 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgRegisterInterchainAccount is used to register an account on a remote zone. type MsgRegisterInterchainAccount struct { - FromAddress string `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` - ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` - InterchainAccountId string `protobuf:"bytes,3,opt,name=interchain_account_id,json=interchainAccountId,proto3" json:"interchain_account_id,omitempty" yaml:"interchain_account_id"` + FromAddress string `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` + InterchainAccountId string `protobuf:"bytes,3,opt,name=interchain_account_id,json=interchainAccountId,proto3" json:"interchain_account_id,omitempty" yaml:"interchain_account_id"` + RegisterFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=register_fee,json=registerFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"register_fee"` } func (m *MsgRegisterInterchainAccount) Reset() { *m = MsgRegisterInterchainAccount{} } @@ -117,13 +120,13 @@ type MsgSubmitTx struct { // lido/kava. This allows contracts to have more than one interchain accounts // on remote zone This identifier will be a part of the portID that we'll // claim our capability for. - InterchainAccountId string `protobuf:"bytes,2,opt,name=interchain_account_id,json=interchainAccountId,proto3" json:"interchain_account_id,omitempty"` - ConnectionId string `protobuf:"bytes,3,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` - Msgs []*types.Any `protobuf:"bytes,4,rep,name=msgs,proto3" json:"msgs,omitempty"` - Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` + InterchainAccountId string `protobuf:"bytes,2,opt,name=interchain_account_id,json=interchainAccountId,proto3" json:"interchain_account_id,omitempty"` + ConnectionId string `protobuf:"bytes,3,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + Msgs []*types1.Any `protobuf:"bytes,4,rep,name=msgs,proto3" json:"msgs,omitempty"` + Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` // timeout in seconds after which the packet times out Timeout uint64 `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` - Fee types1.Fee `protobuf:"bytes,7,opt,name=fee,proto3" json:"fee"` + Fee types2.Fee `protobuf:"bytes,7,opt,name=fee,proto3" json:"fee"` } func (m *MsgSubmitTx) Reset() { *m = MsgSubmitTx{} } @@ -224,43 +227,48 @@ func init() { func init() { proto.RegisterFile("neutron/interchaintxs/v1/tx.proto", fileDescriptor_50f087790e59c806) } var fileDescriptor_50f087790e59c806 = []byte{ - // 576 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0xbd, 0x6f, 0xda, 0x4e, - 0x18, 0xb6, 0x03, 0xbf, 0x24, 0xbf, 0x23, 0x5d, 0x2e, 0x89, 0x64, 0x10, 0xb5, 0x89, 0xfb, 0x21, - 0x96, 0x9c, 0x1b, 0x5a, 0x75, 0x88, 0xd4, 0x4a, 0x30, 0x54, 0x62, 0xa0, 0xaa, 0xdc, 0x4c, 0x5d, - 0x90, 0xb1, 0x5f, 0x0e, 0x4b, 0xf8, 0x8e, 0xfa, 0xce, 0x11, 0x8c, 0xdd, 0x3a, 0x76, 0xe9, 0x58, - 0x29, 0x7f, 0x4e, 0xc6, 0x8c, 0x9d, 0x50, 0x04, 0x4b, 0xe7, 0xfc, 0x05, 0x95, 0xbf, 0x80, 0xa0, - 0x80, 0xa2, 0x6e, 0xef, 0xc7, 0x73, 0xcf, 0x3d, 0xef, 0x73, 0xaf, 0x8d, 0x4e, 0x18, 0x44, 0x32, - 0xe4, 0xcc, 0xf2, 0x99, 0x84, 0xd0, 0x1d, 0x38, 0x3e, 0x93, 0x63, 0x61, 0x5d, 0x9e, 0x59, 0x72, - 0x4c, 0x46, 0x21, 0x97, 0x1c, 0x6b, 0x19, 0x84, 0xdc, 0x83, 0x90, 0xcb, 0xb3, 0x4a, 0xd9, 0xe5, - 0x22, 0xe0, 0xa2, 0x9b, 0xe0, 0xac, 0x34, 0x49, 0x0f, 0x55, 0x8e, 0x28, 0xa7, 0x3c, 0xad, 0xc7, - 0x51, 0x56, 0x3d, 0xa6, 0x9c, 0xd3, 0x21, 0x58, 0xce, 0xc8, 0xb7, 0x06, 0x52, 0x8e, 0xb2, 0x72, - 0x75, 0xa5, 0xec, 0x30, 0xc6, 0xa5, 0x23, 0x7d, 0xce, 0x72, 0xaa, 0x72, 0xd6, 0x4d, 0xb2, 0x5e, - 0xd4, 0xb7, 0x1c, 0x36, 0xc9, 0x5a, 0x4f, 0x73, 0xf5, 0x7d, 0x80, 0x10, 0xfa, 0x11, 0xf3, 0x20, - 0x8c, 0xe3, 0xb4, 0x6d, 0xde, 0xaa, 0xa8, 0xda, 0x11, 0xd4, 0x06, 0xea, 0x0b, 0x09, 0x61, 0x7b, - 0xa1, 0xbf, 0xe9, 0xba, 0x3c, 0x62, 0x12, 0x9f, 0xa0, 0x83, 0x7e, 0xc8, 0x83, 0xae, 0xe3, 0x79, - 0x21, 0x08, 0xa1, 0xa9, 0x35, 0xb5, 0xfe, 0xbf, 0x5d, 0x8a, 0x6b, 0xcd, 0xb4, 0x84, 0xdf, 0xa1, - 0x27, 0x2e, 0x67, 0x0c, 0xdc, 0x58, 0x52, 0xd7, 0xf7, 0xb4, 0x9d, 0x18, 0xd3, 0xd2, 0xee, 0xa6, - 0xc6, 0xd1, 0xc4, 0x09, 0x86, 0xe7, 0xe6, 0xbd, 0xb6, 0x69, 0x1f, 0x2c, 0xf3, 0xb6, 0x87, 0x2f, - 0xd0, 0xf1, 0xd2, 0xb6, 0xae, 0x93, 0xde, 0x1b, 0xd3, 0x14, 0x12, 0x9a, 0xda, 0xdd, 0xd4, 0xa8, - 0xa6, 0x34, 0x0f, 0xc2, 0x4c, 0xfb, 0xd0, 0x5f, 0x57, 0xdd, 0xf6, 0xce, 0xf7, 0xbf, 0x5f, 0x19, - 0xca, 0x9f, 0x2b, 0x43, 0x31, 0x5f, 0xa2, 0xe7, 0xdb, 0x26, 0xb4, 0x41, 0x8c, 0x38, 0x13, 0x60, - 0xfe, 0xda, 0x41, 0xa5, 0x8e, 0xa0, 0x9f, 0xa3, 0x5e, 0xe0, 0xcb, 0x8b, 0xf1, 0x63, 0x26, 0x6f, - 0x6c, 0x92, 0x9e, 0x38, 0xf0, 0xa0, 0x30, 0xfc, 0x6c, 0xdd, 0xad, 0x64, 0xcc, 0x35, 0x4f, 0xea, - 0xa8, 0x18, 0x08, 0x2a, 0xb4, 0x62, 0xad, 0x50, 0x2f, 0x35, 0x8e, 0x48, 0xfa, 0xbe, 0x24, 0x7f, - 0x5f, 0xd2, 0x64, 0x13, 0x3b, 0x41, 0x60, 0x8c, 0x8a, 0x01, 0x04, 0x5c, 0xfb, 0x2f, 0x61, 0x49, - 0x62, 0xac, 0xa1, 0x3d, 0xe9, 0x07, 0xc0, 0x23, 0xa9, 0xed, 0xd6, 0xd4, 0x7a, 0xd1, 0xce, 0x53, - 0xfc, 0x0a, 0x15, 0xfa, 0x00, 0xda, 0x5e, 0x4d, 0xad, 0x97, 0x1a, 0x1a, 0xc9, 0xd7, 0x76, 0x65, - 0x37, 0xc8, 0x07, 0x80, 0x56, 0xf1, 0x7a, 0x6a, 0x28, 0x76, 0x0c, 0x5d, 0xf1, 0xf1, 0x13, 0x3a, - 0x5c, 0xb1, 0x27, 0xb7, 0x0d, 0x1b, 0xa8, 0x24, 0xe0, 0x6b, 0x04, 0xcc, 0x85, 0x78, 0x1a, 0x35, - 0xb9, 0x10, 0xe5, 0xa5, 0xb6, 0x17, 0xab, 0x71, 0x07, 0x0e, 0x63, 0x30, 0xcc, 0x6c, 0xc9, 0xd3, - 0xc6, 0xb7, 0x1d, 0x54, 0xe8, 0x08, 0x8a, 0x7f, 0xaa, 0xa8, 0xbc, 0x79, 0x03, 0xdf, 0x92, 0x4d, - 0x5f, 0x17, 0xd9, 0xf6, 0xae, 0x95, 0xf7, 0xff, 0x76, 0x6e, 0xb1, 0x0f, 0x0a, 0xee, 0xa1, 0xfd, - 0xc5, 0x36, 0xbc, 0xd8, 0xca, 0x96, 0xc3, 0x2a, 0xa7, 0x8f, 0x82, 0x2d, 0xef, 0x68, 0x7d, 0xbc, - 0x9e, 0xe9, 0xea, 0xcd, 0x4c, 0x57, 0x6f, 0x67, 0xba, 0xfa, 0x63, 0xae, 0x2b, 0x37, 0x73, 0x5d, - 0xf9, 0x3d, 0xd7, 0x95, 0x2f, 0x6f, 0xa8, 0x2f, 0x07, 0x51, 0x8f, 0xb8, 0x3c, 0xb0, 0x32, 0xd2, - 0x53, 0x1e, 0xd2, 0x3c, 0xb6, 0xc6, 0x6b, 0x3f, 0x24, 0x39, 0x19, 0x81, 0xe8, 0xed, 0x26, 0x3b, - 0xf2, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x30, 0x09, 0xc8, 0x02, 0xb6, 0x04, 0x00, 0x00, + // 645 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0xbf, 0x4f, 0xdb, 0x40, + 0x14, 0xb6, 0x93, 0x14, 0xe8, 0x85, 0x2e, 0x06, 0x24, 0x27, 0xa2, 0x76, 0x48, 0x7f, 0xc8, 0x0b, + 0x67, 0x92, 0x56, 0x1d, 0x90, 0x5a, 0x89, 0x54, 0x42, 0xca, 0x40, 0x55, 0xb9, 0x4c, 0x5d, 0x22, + 0xff, 0x78, 0x71, 0xac, 0xe2, 0xbb, 0xd4, 0x77, 0x46, 0xc9, 0xd8, 0xad, 0x63, 0x97, 0x8e, 0x95, + 0x98, 0xfb, 0x97, 0x30, 0x32, 0x76, 0xa2, 0x15, 0x2c, 0x9d, 0xf9, 0x0b, 0xaa, 0x3b, 0x9f, 0x21, + 0x44, 0x80, 0x50, 0x27, 0xdf, 0x7b, 0xef, 0x7b, 0xef, 0xdd, 0xfb, 0xde, 0x77, 0x46, 0x1b, 0x04, + 0x72, 0x9e, 0x51, 0xe2, 0x26, 0x84, 0x43, 0x16, 0x8e, 0xfc, 0x84, 0xf0, 0x09, 0x73, 0x0f, 0x3b, + 0x2e, 0x9f, 0xe0, 0x71, 0x46, 0x39, 0x35, 0x4c, 0x05, 0xc1, 0xd7, 0x20, 0xf8, 0xb0, 0xd3, 0x6c, + 0x84, 0x94, 0xa5, 0x94, 0x0d, 0x24, 0xce, 0x2d, 0x8c, 0x22, 0xa9, 0x69, 0x15, 0x96, 0x1b, 0xf8, + 0x0c, 0xdc, 0xc3, 0x4e, 0x00, 0xdc, 0xef, 0xb8, 0x21, 0x4d, 0x88, 0x8a, 0xaf, 0xc6, 0x34, 0xa6, + 0x45, 0x9e, 0x38, 0x29, 0xef, 0x5a, 0x4c, 0x69, 0x7c, 0x00, 0xae, 0x3f, 0x4e, 0xdc, 0x11, 0xe7, + 0x63, 0xe5, 0x5e, 0x9f, 0x71, 0xfb, 0x84, 0x50, 0xee, 0xf3, 0x84, 0x92, 0xb2, 0x55, 0x43, 0x45, + 0xa5, 0x15, 0xe4, 0x43, 0xd7, 0x27, 0x53, 0x15, 0x7a, 0x5c, 0x4e, 0x37, 0x04, 0xc8, 0x60, 0x98, + 0x93, 0x08, 0x32, 0x71, 0x2e, 0xc2, 0xed, 0x93, 0x0a, 0x5a, 0xdf, 0x63, 0xb1, 0x07, 0x71, 0xc2, + 0x38, 0x64, 0xfd, 0xcb, 0xf9, 0x76, 0xc2, 0x90, 0xe6, 0x84, 0x1b, 0x1b, 0x68, 0x79, 0x98, 0xd1, + 0x74, 0xe0, 0x47, 0x51, 0x06, 0x8c, 0x99, 0x7a, 0x4b, 0x77, 0x1e, 0x7a, 0x75, 0xe1, 0xdb, 0x29, + 0x5c, 0xc6, 0x6b, 0xf4, 0x28, 0xa4, 0x84, 0x40, 0x28, 0xae, 0x34, 0x48, 0x22, 0xb3, 0x22, 0x30, + 0x3d, 0xf3, 0xe2, 0xd4, 0x5e, 0x9d, 0xfa, 0xe9, 0xc1, 0x76, 0xfb, 0x5a, 0xb8, 0xed, 0x2d, 0x5f, + 0xd9, 0xfd, 0xc8, 0xd8, 0x47, 0x6b, 0x57, 0xb4, 0x0e, 0xfc, 0xa2, 0xaf, 0x28, 0x53, 0x95, 0x65, + 0x5a, 0x17, 0xa7, 0xf6, 0x7a, 0x51, 0xe6, 0x46, 0x58, 0xdb, 0x5b, 0x49, 0xe6, 0x6f, 0xdd, 0x8f, + 0x0c, 0x82, 0x96, 0x33, 0x35, 0xd4, 0x60, 0x08, 0x60, 0xd6, 0x5a, 0x55, 0xa7, 0xde, 0x6d, 0x60, + 0xb5, 0x22, 0xb1, 0x14, 0xac, 0x96, 0x82, 0xdf, 0xd2, 0x84, 0xf4, 0xb6, 0x8e, 0x4f, 0x6d, 0xed, + 0xe7, 0x6f, 0xdb, 0x89, 0x13, 0x3e, 0xca, 0x03, 0x1c, 0xd2, 0x54, 0xed, 0x53, 0x7d, 0x36, 0x59, + 0xf4, 0xc9, 0xe5, 0xd3, 0x31, 0x30, 0x99, 0xc0, 0xbc, 0x7a, 0xd9, 0x60, 0x17, 0x60, 0x7b, 0xe9, + 0xeb, 0x91, 0xad, 0xfd, 0x3d, 0xb2, 0xb5, 0xf6, 0x73, 0xf4, 0xf4, 0x2e, 0x46, 0x3d, 0x60, 0x63, + 0x4a, 0x18, 0xb4, 0x7f, 0x54, 0x50, 0x7d, 0x8f, 0xc5, 0x1f, 0xf2, 0x20, 0x4d, 0xf8, 0xfe, 0xe4, + 0x3e, 0x4c, 0x77, 0x6f, 0xa3, 0x4a, 0x32, 0x7e, 0x33, 0x11, 0x4f, 0xe6, 0xb7, 0x23, 0x69, 0x9d, + 0xdb, 0x81, 0x83, 0x6a, 0x29, 0x8b, 0x99, 0x62, 0x69, 0x15, 0x17, 0x7a, 0xc2, 0xa5, 0x9e, 0xf0, + 0x0e, 0x99, 0x7a, 0x12, 0x61, 0x18, 0xa8, 0x96, 0x42, 0x4a, 0xcd, 0x07, 0xb2, 0x8a, 0x3c, 0x1b, + 0x26, 0x5a, 0xe4, 0x49, 0x0a, 0x34, 0xe7, 0xe6, 0x42, 0x4b, 0x77, 0x6a, 0x5e, 0x69, 0x1a, 0x5b, + 0xa8, 0x2a, 0xc8, 0x5f, 0x6c, 0xe9, 0x4e, 0xbd, 0x6b, 0xe2, 0xf2, 0x19, 0xcd, 0x68, 0x11, 0xef, + 0x02, 0xf4, 0x6a, 0x82, 0x7b, 0x4f, 0x40, 0x67, 0x78, 0x7c, 0x8f, 0x56, 0x66, 0xe8, 0x29, 0x69, + 0x33, 0x6c, 0x54, 0x67, 0xf0, 0x39, 0x07, 0x12, 0x82, 0x98, 0x46, 0x97, 0x0d, 0x51, 0xe9, 0xea, + 0x47, 0xe2, 0x36, 0xe1, 0xc8, 0x27, 0x04, 0x0e, 0x14, 0x2d, 0xa5, 0xd9, 0xfd, 0x52, 0x41, 0xd5, + 0x3d, 0x16, 0x1b, 0xdf, 0x75, 0xd4, 0xb8, 0x5d, 0xf1, 0xaf, 0xf0, 0x6d, 0xaf, 0x1d, 0xdf, 0xb5, + 0xd7, 0xe6, 0x9b, 0xff, 0xcb, 0xbb, 0xd4, 0x83, 0x66, 0x04, 0x68, 0xe9, 0x52, 0x0d, 0xcf, 0xee, + 0xac, 0x56, 0xc2, 0x9a, 0x9b, 0xf7, 0x82, 0x5d, 0xf5, 0xe8, 0xbd, 0x3b, 0x3e, 0xb3, 0xf4, 0x93, + 0x33, 0x4b, 0xff, 0x73, 0x66, 0xe9, 0xdf, 0xce, 0x2d, 0xed, 0xe4, 0xdc, 0xd2, 0x7e, 0x9d, 0x5b, + 0xda, 0xc7, 0x97, 0x33, 0xc2, 0x57, 0x45, 0x37, 0x69, 0x16, 0x97, 0x67, 0x77, 0x32, 0xf7, 0x83, + 0x94, 0x4f, 0x21, 0x58, 0x90, 0x1a, 0x79, 0xf1, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x12, 0xe8, + 0x47, 0x46, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -399,6 +407,20 @@ func (m *MsgRegisterInterchainAccount) MarshalToSizedBuffer(dAtA []byte) (int, e _ = i var l int _ = l + if len(m.RegisterFee) > 0 { + for iNdEx := len(m.RegisterFee) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RegisterFee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } if len(m.InterchainAccountId) > 0 { i -= len(m.InterchainAccountId) copy(dAtA[i:], m.InterchainAccountId) @@ -590,6 +612,12 @@ func (m *MsgRegisterInterchainAccount) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + if len(m.RegisterFee) > 0 { + for _, e := range m.RegisterFee { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } return n } @@ -785,6 +813,40 @@ func (m *MsgRegisterInterchainAccount) Unmarshal(dAtA []byte) error { } m.InterchainAccountId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegisterFee", 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 + } + m.RegisterFee = append(m.RegisterFee, types.Coin{}) + if err := m.RegisterFee[len(m.RegisterFee)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -1010,7 +1072,7 @@ func (m *MsgSubmitTx) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Msgs = append(m.Msgs, &types.Any{}) + m.Msgs = append(m.Msgs, &types1.Any{}) if err := m.Msgs[len(m.Msgs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } From 70c599554f42beac36f4b49f6b8c167c82349699 Mon Sep 17 00:00:00 2001 From: swelf Date: Fri, 15 Sep 2023 16:49:36 +0300 Subject: [PATCH 15/18] review fixes: extra tests, removed commented/dead code --- Makefile | 2 +- neutronutils/errors/errors.go | 21 --- neutronutils/utils.go | 11 -- .../interchaintxs/keeper/sudo_middleware.go | 27 ++++ .../contractmanager/types/expected_keepers.go | 50 ++++++ x/contractmanager/ibc_middleware.go | 35 +++- x/contractmanager/ibc_middleware_test.go | 153 ++++++++---------- x/contractmanager/types/expected_keepers.go | 5 + x/interchaintxs/handler.go | 34 ---- x/interchaintxs/keeper/ibc_handlers_test.go | 53 ------ 10 files changed, 182 insertions(+), 209 deletions(-) delete mode 100644 neutronutils/errors/errors.go delete mode 100644 neutronutils/utils.go create mode 100644 testutil/interchaintxs/keeper/sudo_middleware.go delete mode 100644 x/interchaintxs/handler.go diff --git a/Makefile b/Makefile index 1bdcceca1..6af490db8 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ build-static-linux-amd64: go.sum $(BUILDDIR)/ $(DOCKER) cp neutronbinary:/bin/neutrond $(BUILDDIR)/neutrond-linux-amd64 $(DOCKER) rm -f neutronbinary -install-test-binary: go.sum +install-test-binary: check_version go.sum go install -mod=readonly $(BUILD_FLAGS_TEST_BINARY) ./cmd/neutrond ######################################## diff --git a/neutronutils/errors/errors.go b/neutronutils/errors/errors.go deleted file mode 100644 index 9dd7574d9..000000000 --- a/neutronutils/errors/errors.go +++ /dev/null @@ -1,21 +0,0 @@ -package errors - -import ( - "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// OutOfGasRecovery converts `out of gas` panic into an error -// leaving unprocessed any other kinds of panics -func OutOfGasRecovery( - gasMeter sdk.GasMeter, - err *error, -) { - if r := recover(); r != nil { - _, ok := r.(sdk.ErrorOutOfGas) - if !ok || !gasMeter.IsOutOfGas() { - panic(r) - } - *err = errors.Wrapf(errors.ErrPanic, "%v", r) - } -} diff --git a/neutronutils/utils.go b/neutronutils/utils.go deleted file mode 100644 index 2308c2a0f..000000000 --- a/neutronutils/utils.go +++ /dev/null @@ -1,11 +0,0 @@ -package neutronutils - -import sdk "github.com/cosmos/cosmos-sdk/types" - -// CreateCachedContext creates a cached context with a limited gas meter. -func CreateCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func()) { - cacheCtx, writeFn := ctx.CacheContext() - gasMeter := sdk.NewGasMeter(gasLimit) - cacheCtx = cacheCtx.WithGasMeter(gasMeter) - return cacheCtx, writeFn -} diff --git a/testutil/interchaintxs/keeper/sudo_middleware.go b/testutil/interchaintxs/keeper/sudo_middleware.go new file mode 100644 index 000000000..5d586f342 --- /dev/null +++ b/testutil/interchaintxs/keeper/sudo_middleware.go @@ -0,0 +1,27 @@ +package keeper + +import ( + tmdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/x/contractmanager" + "github.com/neutron-org/neutron/x/contractmanager/types" + "github.com/stretchr/testify/require" + "testing" +) + +func NewSudoLimitWrapper(t testing.TB, cmKeeper types.ContractManagerKeeper, wasmKeeper types.WasmKeeper) (types.WasmKeeper, sdk.Context, *storetypes.KVStoreKey) { + storeKey := sdk.NewKVStoreKey(types.StoreKey) + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + limitWrapper := contractmanager.NewSudoLimitWrapper(cmKeeper, wasmKeeper) + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + return limitWrapper, ctx, storeKey +} diff --git a/testutil/mocks/contractmanager/types/expected_keepers.go b/testutil/mocks/contractmanager/types/expected_keepers.go index 463ef15c4..d27f64e5f 100644 --- a/testutil/mocks/contractmanager/types/expected_keepers.go +++ b/testutil/mocks/contractmanager/types/expected_keepers.go @@ -9,6 +9,7 @@ import ( types "github.com/cosmos/cosmos-sdk/types" gomock "github.com/golang/mock/gomock" + types0 "github.com/neutron-org/neutron/x/contractmanager/types" ) // MockWasmKeeper is a mock of WasmKeeper interface. @@ -62,3 +63,52 @@ func (mr *MockWasmKeeperMockRecorder) Sudo(ctx, contractAddress, msg interface{} mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sudo", reflect.TypeOf((*MockWasmKeeper)(nil).Sudo), ctx, contractAddress, msg) } + +// MockContractManagerKeeper is a mock of ContractManagerKeeper interface. +type MockContractManagerKeeper struct { + ctrl *gomock.Controller + recorder *MockContractManagerKeeperMockRecorder +} + +// MockContractManagerKeeperMockRecorder is the mock recorder for MockContractManagerKeeper. +type MockContractManagerKeeperMockRecorder struct { + mock *MockContractManagerKeeper +} + +// NewMockContractManagerKeeper creates a new mock instance. +func NewMockContractManagerKeeper(ctrl *gomock.Controller) *MockContractManagerKeeper { + mock := &MockContractManagerKeeper{ctrl: ctrl} + mock.recorder = &MockContractManagerKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockContractManagerKeeper) EXPECT() *MockContractManagerKeeperMockRecorder { + return m.recorder +} + +// AddContractFailure mocks base method. +func (m *MockContractManagerKeeper) AddContractFailure(ctx types.Context, address string, sudoPayload []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddContractFailure", ctx, address, sudoPayload) +} + +// AddContractFailure indicates an expected call of AddContractFailure. +func (mr *MockContractManagerKeeperMockRecorder) AddContractFailure(ctx, address, sudoPayload interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContractFailure", reflect.TypeOf((*MockContractManagerKeeper)(nil).AddContractFailure), ctx, address, sudoPayload) +} + +// GetParams mocks base method. +func (m *MockContractManagerKeeper) GetParams(ctx types.Context) types0.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types0.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockContractManagerKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockContractManagerKeeper)(nil).GetParams), ctx) +} diff --git a/x/contractmanager/ibc_middleware.go b/x/contractmanager/ibc_middleware.go index 841897459..739de0860 100644 --- a/x/contractmanager/ibc_middleware.go +++ b/x/contractmanager/ibc_middleware.go @@ -1,22 +1,20 @@ package contractmanager import ( + "cosmossdk.io/errors" "fmt" "github.com/cometbft/cometbft/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/neutron-org/neutron/neutronutils" - neutronerrors "github.com/neutron-org/neutron/neutronutils/errors" - "github.com/neutron-org/neutron/x/contractmanager/keeper" contractmanagertypes "github.com/neutron-org/neutron/x/contractmanager/types" ) type SudoLimitWrapper struct { - contractManager keeper.Keeper + contractManager contractmanagertypes.ContractManagerKeeper contractmanagertypes.WasmKeeper } // NewSudoLimitWrapper suppresses an error from a Sudo contract handler and saves it to a store -func NewSudoLimitWrapper(contractManager keeper.Keeper, sudoKeeper contractmanagertypes.WasmKeeper) contractmanagertypes.WasmKeeper { +func NewSudoLimitWrapper(contractManager contractmanagertypes.ContractManagerKeeper, sudoKeeper contractmanagertypes.WasmKeeper) contractmanagertypes.WasmKeeper { return SudoLimitWrapper{ contractManager, sudoKeeper, @@ -27,9 +25,9 @@ func NewSudoLimitWrapper(contractManager keeper.Keeper, sudoKeeper contractmanag // in case of `out of gas` panic it converts the panic into an error and stops `out of gas` panic propagation // if error happens during the Sudo call, we store the data that raised the error, and return the error func (k SudoLimitWrapper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) (resp []byte, err error) { - cacheCtx, writeFn := neutronutils.CreateCachedContext(ctx, k.contractManager.GetParams(ctx).SudoCallGasLimit) + cacheCtx, writeFn := createCachedContext(ctx, k.contractManager.GetParams(ctx).SudoCallGasLimit) func() { - defer neutronerrors.OutOfGasRecovery(cacheCtx.GasMeter(), &err) + defer outOfGasRecovery(cacheCtx.GasMeter(), &err) // Actually we have only one kind of error returned from acknowledgement // maybe later we'll retrieve actual errors from events resp, err = k.WasmKeeper.Sudo(cacheCtx, contractAddress, msg) @@ -49,3 +47,26 @@ func (k SudoLimitWrapper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, func (k SudoLimitWrapper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", contractmanagertypes.ModuleName)) } + +// outOfGasRecovery converts `out of gas` panic into an error +// leaving unprocessed any other kinds of panics +func outOfGasRecovery( + gasMeter sdk.GasMeter, + err *error, +) { + if r := recover(); r != nil { + _, ok := r.(sdk.ErrorOutOfGas) + if !ok || !gasMeter.IsOutOfGas() { + panic(r) + } + *err = errors.Wrapf(errors.ErrPanic, "%v", r) + } +} + +// createCachedContext creates a cached context with a limited gas meter. +func createCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func()) { + cacheCtx, writeFn := ctx.CacheContext() + gasMeter := sdk.NewGasMeter(gasLimit) + cacheCtx = cacheCtx.WithGasMeter(gasMeter) + return cacheCtx, writeFn +} diff --git a/x/contractmanager/ibc_middleware_test.go b/x/contractmanager/ibc_middleware_test.go index b1b964fbf..0815be992 100644 --- a/x/contractmanager/ibc_middleware_test.go +++ b/x/contractmanager/ibc_middleware_test.go @@ -1,83 +1,72 @@ -package contractmanager +package contractmanager_test -// -//import ( -// tmdb "github.com/cometbft/cometbft-db" -// "github.com/cometbft/cometbft/libs/log" -// tmproto "github.com/cometbft/cometbft/proto/tendermint/types" -// "github.com/cosmos/cosmos-sdk/store" -// storetypes "github.com/cosmos/cosmos-sdk/store/types" -// sdk "github.com/cosmos/cosmos-sdk/types" -// icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" -// channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" -// "github.com/golang/mock/gomock" -// mock_types "github.com/neutron-org/neutron/testutil/mocks/interchaintxs/types" -// "github.com/neutron-org/neutron/x/contractmanager/types" -// "github.com/stretchr/testify/require" -// "testing" -//) -// -//var ( -// ShouldNotBeWrittenKey = []byte("shouldnotkey") -// ShouldNotBeWritten = []byte("should not be written") -// ShouldBeWritten = []byte("should be written") -// TestOwnerAddress = "neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh" -//) -// -//func ShouldBeWrittenKey(suffix string) []byte { -// return append([]byte("shouldkey"), []byte(suffix)...) -//} -// -//func NewSudoLimitMiddleware(t testing.TB, cm types.SudoWrapper) (SudoLimitWrapper, sdk.Context, *storetypes.KVStoreKey) { -// storeKey := sdk.NewKVStoreKey(types.StoreKey) -// -// db := tmdb.NewMemDB() -// stateStore := store.NewCommitMultiStore(db) -// stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) -// require.NoError(t, stateStore.LoadLatestVersion()) -// -// k := SudoLimitWrapper{SudoWrapper: cm} -// -// ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) -// -// return k, ctx, storeKey -//} -// -//func TestSudo(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) -// middleware, infCtx, storeKey := NewSudoLimitMiddleware(t, cmKeeper) -// st := infCtx.KVStore(storeKey) -// -// p := channeltypes.Packet{ -// Sequence: 100, -// SourcePort: icatypes.ControllerPortPrefix + TestOwnerAddress + ".ica0", -// SourceChannel: "channel-0", -// } -// contractAddress := sdk.AccAddress{} -// errACK := channeltypes.Acknowledgement{ -// Response: &channeltypes.Acknowledgement_Error{ -// Error: "error", -// }, -// } -// //errAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&errACK) -// //require.NoError(t, err) -// //resACK := channeltypes.Acknowledgement{ -// // Response: &channeltypes.Acknowledgement_Result{Result: []byte("Result")}, -// //} -// //resAckData, err := channeltypes.SubModuleCdc.MarshalJSON(&resACK) -// //require.NoError(t, err) -// -// // success during SudoError -// ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) -// cmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, errAck channeltypes.Acknowledgement) { -// st := cachedCtx.KVStore(storeKey) -// st.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) -// }).Return(nil, nil) -// cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) -// err := middleware.Sudo(ctx, contractAddress, p, &errACK) -// require.NoError(t, err) -// require.Equal(t, ShouldBeWritten, st.Get(ShouldBeWrittenKey("sudoerror"))) -// require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) -//} +import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + test_keeper "github.com/neutron-org/neutron/testutil/interchaintxs/keeper" + mock_types "github.com/neutron-org/neutron/testutil/mocks/contractmanager/types" + "github.com/neutron-org/neutron/x/contractmanager/types" + "github.com/stretchr/testify/require" + "testing" +) + +var ( + ShouldNotBeWrittenKey = []byte("shouldnotkey") + ShouldNotBeWritten = []byte("should not be written") + ShouldBeWritten = []byte("should be written") + TestOwnerAddress = "neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh" +) + +func ShouldBeWrittenKey(suffix string) []byte { + return append([]byte("shouldkey"), []byte(suffix)...) +} + +func TestSudo(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cmKeeper := mock_types.NewMockContractManagerKeeper(ctrl) + wmKeeper := mock_types.NewMockWasmKeeper(ctrl) + middleware, infCtx, storeKey := test_keeper.NewSudoLimitWrapper(t, cmKeeper, wmKeeper) + st := infCtx.KVStore(storeKey) + + // at this point the payload struct does not matter + msg := []byte("sudo_payload") + contractAddress := sdk.AccAddress{} + + // success during Sudo + ctx := infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 10000}) + wmKeeper.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddress, msg).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, msg []byte) { + st := cachedCtx.KVStore(storeKey) + st.Set(ShouldBeWrittenKey("sudo"), ShouldBeWritten) + }).Return(nil, nil) + _, err := middleware.Sudo(ctx, contractAddress, msg) + require.NoError(t, err) + require.Equal(t, ShouldBeWritten, st.Get(ShouldBeWrittenKey("sudo"))) + + // error during Sudo + ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 10000}) + cmKeeper.EXPECT().AddContractFailure(ctx, contractAddress.String(), msg) + wmKeeper.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddress, msg).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, msg []byte) { + st := cachedCtx.KVStore(storeKey) + st.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + }).Return(nil, fmt.Errorf("sudo error")) + _, err = middleware.Sudo(ctx, contractAddress, msg) + require.Error(t, err) + require.Nil(t, st.Get(ShouldNotBeWrittenKey)) + + // ou of gas during Sudo + ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) + cmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 10000}) + cmKeeper.EXPECT().AddContractFailure(ctx, contractAddress.String(), msg) + wmKeeper.EXPECT().Sudo(gomock.AssignableToTypeOf(ctx), contractAddress, msg).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, msg []byte) { + st := cachedCtx.KVStore(storeKey) + st.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) + cachedCtx.GasMeter().ConsumeGas(10001, "heavy calculations") + }) + _, err = middleware.Sudo(ctx, contractAddress, msg) + require.ErrorContains(t, err, "{heavy calculations}: panic") + require.Nil(t, st.Get(ShouldNotBeWrittenKey)) +} diff --git a/x/contractmanager/types/expected_keepers.go b/x/contractmanager/types/expected_keepers.go index a30614c63..88db20887 100644 --- a/x/contractmanager/types/expected_keepers.go +++ b/x/contractmanager/types/expected_keepers.go @@ -9,3 +9,8 @@ type WasmKeeper interface { HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) } + +type ContractManagerKeeper interface { + AddContractFailure(ctx sdk.Context, address string, sudoPayload []byte) + GetParams(ctx sdk.Context) (params Params) +} diff --git a/x/interchaintxs/handler.go b/x/interchaintxs/handler.go deleted file mode 100644 index 30a574948..000000000 --- a/x/interchaintxs/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package interchaintxs - -import ( - "fmt" - - "cosmossdk.io/errors" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/neutron-org/neutron/x/interchaintxs/keeper" - "github.com/neutron-org/neutron/x/interchaintxs/types" -) - -func NewHandler(k keeper.Keeper) sdk.Handler { - msgServer := keeper.NewMsgServerImpl(k) - - return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - ctx = ctx.WithEventManager(sdk.NewEventManager()) - - switch msg := msg.(type) { - case *types.MsgRegisterInterchainAccount: - res, err := msgServer.RegisterInterchainAccount(sdk.WrapSDKContext(ctx), msg) - return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgSubmitTx: - res, err := msgServer.SubmitTx(sdk.WrapSDKContext(ctx), msg) - return sdk.WrapServiceResult(ctx, res, err) - - default: - errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) - return nil, errors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) - } - } -} diff --git a/x/interchaintxs/keeper/ibc_handlers_test.go b/x/interchaintxs/keeper/ibc_handlers_test.go index 50cc29fd8..c0a8c40b6 100644 --- a/x/interchaintxs/keeper/ibc_handlers_test.go +++ b/x/interchaintxs/keeper/ibc_handlers_test.go @@ -65,59 +65,6 @@ func TestHandleAcknowledgement(t *testing.T) { wmKeeper.EXPECT().Sudo(ctx, contractAddress, msgAck).Return(nil, fmt.Errorf("error sudoResponse")) err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) require.NoError(t, err) - - //// success during SudoError - //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //wmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, err string) { - // store := cachedCtx.KVStore(storeKey) - // store.Set(ShouldBeWrittenKey("sudoerror"), ShouldBeWritten) - //}).Return(nil, nil) - //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 6000}) - //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - //require.NoError(t, err) - //require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoerror"))) - //require.Equal(t, uint64(3050), ctx.GasMeter().GasConsumed()) - // - //// out of gas during SudoError - //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //wmKeeper.EXPECT().SudoError(gomock.AssignableToTypeOf(ctx), contractAddress, p, errACK.GetError()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, error string) { - // store := cachedCtx.KVStore(storeKey) - // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - // cachedCtx.GasMeter().ConsumeGas(7001, "out of gas test") - //}) - //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 7000}) - //wmKeeper.EXPECT().AddContractFailure(ctx, &p, contractAddress.String(), types.Ack, &errACK) - //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - //err = icak.HandleAcknowledgement(ctx, p, errAckData, relayerAddress) - //require.NoError(t, err) - //require.Empty(t, store.Get(ShouldNotBeWrittenKey)) - //require.Equal(t, uint64(7000), ctx.GasMeter().GasConsumed()) - // - //// success during SudoResponse - //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //wmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(ctx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - // store := cachedCtx.KVStore(storeKey) - // store.Set(ShouldBeWrittenKey("sudoresponse"), ShouldBeWritten) // consumes 3140 gas, 2000 flat write + 30 every byte of key+value - //}).Return(nil, nil) - //wmKeeper.EXPECT().GetParams(ctx).Return(types.Params{SudoCallGasLimit: 8000}) - //feeKeeper.EXPECT().DistributeAcknowledgementFee(ctx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - //err = icak.HandleAcknowledgement(ctx, p, resAckData, relayerAddress) - //require.NoError(t, err) - //require.Equal(t, uint64(3140), ctx.GasMeter().GasConsumed()) - //require.Equal(t, ShouldBeWritten, store.Get(ShouldBeWrittenKey("sudoresponse"))) - // - //// not enough gas provided by relayer for SudoCallGasLimit - //ctx = infCtx.WithGasMeter(sdk.NewGasMeter(1_000_000_000_000)) - //lowGasCtx := infCtx.WithGasMeter(sdk.NewGasMeter(1000)) - //wmKeeper.EXPECT().SudoResponse(gomock.AssignableToTypeOf(lowGasCtx), contractAddress, p, resACK.GetResult()).Do(func(cachedCtx sdk.Context, senderAddress sdk.AccAddress, request channeltypes.Packet, msg []byte) { - // store := cachedCtx.KVStore(storeKey) - // store.Set(ShouldNotBeWrittenKey, ShouldNotBeWritten) - // cachedCtx.GasMeter().ConsumeGas(1001, "out of gas test") - //}) - //feeKeeper.EXPECT().DistributeAcknowledgementFee(lowGasCtx, relayerAddress, feetypes.NewPacketID(p.SourcePort, p.SourceChannel, p.Sequence)) - //wmKeeper.EXPECT().GetParams(lowGasCtx).Return(types.Params{SudoCallGasLimit: 9000}) - //require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "consume gas from cached context"}, func() { icak.HandleAcknowledgement(lowGasCtx, p, resAckData, relayerAddress) }) //nolint:errcheck // this is a panic test } func TestHandleTimeout(t *testing.T) { From 0c26b8e4febf36517626a221dc267fcce12e09e5 Mon Sep 17 00:00:00 2001 From: swelf Date: Fri, 15 Sep 2023 19:20:47 +0300 Subject: [PATCH 16/18] comment typo --- x/contractmanager/types/sudo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/contractmanager/types/sudo.go b/x/contractmanager/types/sudo.go index a5745fcbb..8951b7c7d 100644 --- a/x/contractmanager/types/sudo.go +++ b/x/contractmanager/types/sudo.go @@ -24,7 +24,7 @@ type MessageKVQueryResult struct { } // MessageSudoCallback is passed to a contract's sudo() entrypoint when an interchain -// transaction failed with a timeout. +// transaction failed. type MessageSudoCallback struct { Response *ResponseSudoPayload `json:"response,omitempty"` Error *ErrorSudoPayload `json:"error,omitempty"` From 3dfb8deaed971c257788d6843003bced535184a2 Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 19 Sep 2023 11:26:22 +0300 Subject: [PATCH 17/18] review fixes --- x/contractmanager/keeper/failure.go | 2 +- x/contractmanager/types/module.go | 9 --------- x/contractmanager/types/sudo.go | 2 +- x/interchaintxs/keeper/keeper.go | 10 +++++----- 4 files changed, 7 insertions(+), 16 deletions(-) delete mode 100644 x/contractmanager/types/module.go diff --git a/x/contractmanager/keeper/failure.go b/x/contractmanager/keeper/failure.go index 038227591..02e653655 100644 --- a/x/contractmanager/keeper/failure.go +++ b/x/contractmanager/keeper/failure.go @@ -73,7 +73,7 @@ func (k Keeper) ResubmitFailure(ctx sdk.Context, contractAddr sdk.AccAddress, fa } if _, err := k.wasmKeeper.Sudo(ctx, contractAddr, failure.SudoPayload); err != nil { - return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure ack; failureId = %d; err = %s", failure.Id, err) + return errors.Wrapf(types.FailedToResubmitFailure, "cannot resubmit failure; failureId = %d; err = %s", failure.Id, err) } // Cleanup failure since we resubmitted it successfully diff --git a/x/contractmanager/types/module.go b/x/contractmanager/types/module.go deleted file mode 100644 index e7efcf2d9..000000000 --- a/x/contractmanager/types/module.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type Sudo interface { - Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) -} diff --git a/x/contractmanager/types/sudo.go b/x/contractmanager/types/sudo.go index 8951b7c7d..0670a1696 100644 --- a/x/contractmanager/types/sudo.go +++ b/x/contractmanager/types/sudo.go @@ -24,7 +24,7 @@ type MessageKVQueryResult struct { } // MessageSudoCallback is passed to a contract's sudo() entrypoint when an interchain -// transaction failed. +// transaction ended up with Success/Error or timed out. type MessageSudoCallback struct { Response *ResponseSudoPayload `json:"response,omitempty"` Error *ErrorSudoPayload `json:"error,omitempty"` diff --git a/x/interchaintxs/keeper/keeper.go b/x/interchaintxs/keeper/keeper.go index e27d294d2..0cf14c884 100644 --- a/x/interchaintxs/keeper/keeper.go +++ b/x/interchaintxs/keeper/keeper.go @@ -40,7 +40,7 @@ func NewKeeper( memKey storetypes.StoreKey, channelKeeper types.ChannelKeeper, icaControllerKeeper types.ICAControllerKeeper, - contractManagerKeeper types.WasmKeeper, + sudoKeeper types.WasmKeeper, feeKeeper types.FeeRefunderKeeper, bankKeeper types.BankKeeper, feeBurnerKeeper types.FeeBurnerKeeper, @@ -51,7 +51,7 @@ func NewKeeper( memKey: memKey, channelKeeper: channelKeeper, icaControllerKeeper: icaControllerKeeper, - sudoKeeper: contractManagerKeeper, + sudoKeeper: sudoKeeper, feeKeeper: feeKeeper, bankKeeper: bankKeeper, feeBurnerKeeper: feeBurnerKeeper, @@ -68,18 +68,18 @@ func (k Keeper) ChargeFee(ctx sdk.Context, payer sdk.AccAddress, fee sdk.Coins) params := k.GetParams(ctx) if !fee.IsAnyGTE(params.RegisterFee) { - return errors.Wrapf(sdkerrors.ErrInsufficientFee, "provided fee is less than min governance set ack fee: %v < %v", fee, params.RegisterFee) + return errors.Wrapf(sdkerrors.ErrInsufficientFee, "provided fee is less than min governance set ack fee: %s < %s", fee, params.RegisterFee) } treasury := k.feeBurnerKeeper.GetParams(ctx).TreasuryAddress treasuryAddress, err := sdk.AccAddressFromBech32(treasury) if err != nil { - return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to convert treasury, bech32 to AccAddress: %v: %v", treasury, err) + return errors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to convert treasury, bech32 to AccAddress: %s: %w", treasury, err) } err = k.bankKeeper.SendCoins(ctx, payer, treasuryAddress, fee) if err != nil { - return errors.Wrapf(err, "failed send fee(%v) from %v to %v", fee, payer, treasury) + return errors.Wrapf(err, "failed send fee(%s) from %s to %s", fee, payer, treasury) } return nil } From 2331798dc85dbaa0353b364f556dd87d48afa351 Mon Sep 17 00:00:00 2001 From: swelf Date: Tue, 19 Sep 2023 12:16:58 +0300 Subject: [PATCH 18/18] removed unused method --- x/contractmanager/keeper/sudo.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/x/contractmanager/keeper/sudo.go b/x/contractmanager/keeper/sudo.go index 4c5a24402..13a5e9e50 100644 --- a/x/contractmanager/keeper/sudo.go +++ b/x/contractmanager/keeper/sudo.go @@ -53,33 +53,6 @@ func PrepareOpenAckCallbackMessage(details types.OpenAckDetails) ([]byte, error) return m, nil } -func (k Keeper) SudoOnChanOpenAck( - ctx sdk.Context, - contractAddress sdk.AccAddress, - details types.OpenAckDetails, -) ([]byte, error) { - k.Logger(ctx).Debug("SudoOnChanOpenAck", "contractAddress", contractAddress) - - x := types.MessageOnChanOpenAck{} - x.OpenAck = details - m, err := json.Marshal(x) - if err != nil { - k.Logger(ctx).Error("SudoOnChanOpenAck: failed to marshal MessageOnChanOpenAck message", - "error", err, "contract_address", contractAddress) - return nil, fmt.Errorf("failed to marshal MessageOnChanOpenAck: %v", err) - } - k.Logger(ctx).Info("SudoOnChanOpenAck sending request", "data", string(m)) - - resp, err := k.wasmKeeper.Sudo(ctx, contractAddress, m) - if err != nil { - k.Logger(ctx).Debug("SudoOnChanOpenAck: failed to sudo", - "error", err, "contract_address", contractAddress) - return nil, fmt.Errorf("failed to sudo: %v", err) - } - - return resp, nil -} - // SudoTxQueryResult is used to pass a tx query result to the contract that registered the query // to: // 1. check whether the transaction actually satisfies the initial query arguments;