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 {