diff --git a/x/wasm/keeper/gas_register.go b/x/wasm/keeper/gas_register.go new file mode 100644 index 00000000000..25fb56e8f36 --- /dev/null +++ b/x/wasm/keeper/gas_register.go @@ -0,0 +1,187 @@ +package keeper + +import ( + "github.com/CosmWasm/wasmd/x/wasm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // DefaultGasMultiplier is how many cosmwasm gas points = 1 sdk gas point + // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 + // A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io + // Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read) + // + // Please note that all gas prices returned to the wasmer engine should have this multiplied + DefaultGasMultiplier uint64 = 100 + // DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance. + // Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts. + DefaultInstanceCost uint64 = 40_000 + // DefaultCompileCost is how much SDK gas is charged *per byte* for compiling WASM code. + DefaultCompileCost uint64 = 2 + // DefaultEventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + DefaultEventAttributeDataCost uint64 = 1 + // DefaultContractMessageDataCost is how much SDK gas is charged *per byte* of the message that goes to the contract + // This is used with len(msg) + DefaultContractMessageDataCost uint64 = 1 + // DefaultPerAttributeCost is how much SDK gas we charge per attribute count. + DefaultPerAttributeCost uint64 = 10 + // DefaultEventAttributeDataFreeTier number of bytes of total attribute data we do not charge. + DefaultEventAttributeDataFreeTier = 100 +) + +// GasRegister abstract source for gas costs +type GasRegister interface { + // NewContractInstanceCosts costs to crate a new contract instance from code + NewContractInstanceCosts(pinned bool, msgLen int) sdk.Gas + // CompileCosts costs to persist and "compile" a new wasm contract + CompileCosts(byteLength int) sdk.Gas + // InstantiateContractCosts costs when interacting with a wasm contract + InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas + // ReplyCosts costs to to handle a message reply + ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas + // EventCosts costs to persist an event + EventCosts(evts []wasmvmtypes.EventAttribute) sdk.Gas + // ToWasmVMGas converts from sdk gas to wasmvm gas + ToWasmVMGas(source sdk.Gas) uint64 + // FromWasmVMGas converts from wasmvm gas to sdk gas + FromWasmVMGas(source uint64) sdk.Gas +} + +// WasmGasRegisterConfig config type +type WasmGasRegisterConfig struct { + // InstanceCost costs when interacting with a wasm contract + InstanceCost sdk.Gas + // CompileCosts costs to persist and "compile" a new wasm contract + CompileCost sdk.Gas + // GasMultiplier is how many cosmwasm gas points = 1 sdk gas point + // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 + GasMultiplier sdk.Gas + // EventPerAttributeCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventPerAttributeCost sdk.Gas + // EventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventAttributeDataCost sdk.Gas + // EventAttributeDataFreeTier number of bytes of total attribute data that is free of charge + EventAttributeDataFreeTier int + // ContractMessageDataCost SDK gas charged *per byte* of the message that goes to the contract + // This is used with len(msg) + ContractMessageDataCost sdk.Gas +} + +// DefaultGasRegisterConfig default values +func DefaultGasRegisterConfig() WasmGasRegisterConfig { + return WasmGasRegisterConfig{ + InstanceCost: DefaultInstanceCost, + CompileCost: DefaultCompileCost, + GasMultiplier: DefaultGasMultiplier, + EventPerAttributeCost: DefaultPerAttributeCost, + EventAttributeDataCost: DefaultEventAttributeDataCost, + EventAttributeDataFreeTier: DefaultEventAttributeDataFreeTier, + ContractMessageDataCost: DefaultContractMessageDataCost, + } +} + +// WasmGasRegister implements GasRegister interface +type WasmGasRegister struct { + c WasmGasRegisterConfig +} + +// NewDefaultWasmGasRegister creates instance with default values +func NewDefaultWasmGasRegister() WasmGasRegister { + return NewWasmGasRegister(DefaultGasRegisterConfig()) +} + +// NewWasmGasRegister constructor +func NewWasmGasRegister(c WasmGasRegisterConfig) WasmGasRegister { + if c.GasMultiplier == 0 { + panic(sdkerrors.Wrap(sdkerrors.ErrLogic, "GasMultiplier can not be 0")) + } + return WasmGasRegister{ + c: c, + } +} + +// NewContractInstanceCosts costs to crate a new contract instance from code +func (g WasmGasRegister) NewContractInstanceCosts(pinned bool, msgLen int) storetypes.Gas { + return g.InstantiateContractCosts(pinned, msgLen) +} + +// CompileCosts costs to persist and "compile" a new wasm contract +func (g WasmGasRegister) CompileCosts(byteLength int) storetypes.Gas { + if byteLength < 0 { + panic(sdkerrors.Wrap(types.ErrInvalid, "negative length")) + } + return g.c.CompileCost * uint64(byteLength) +} + +// InstantiateContractCosts costs when interacting with a wasm contract +func (g WasmGasRegister) InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas { + if msgLen < 0 { + panic(sdkerrors.Wrap(types.ErrInvalid, "negative length")) + } + dataCosts := sdk.Gas(msgLen) * g.c.ContractMessageDataCost + if pinned { + return dataCosts + } + return g.c.InstanceCost + dataCosts +} + +// ReplyCosts costs to to handle a message reply +func (g WasmGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas { + var eventGas sdk.Gas + msgLen := len(reply.Result.Err) + if reply.Result.Ok != nil { + msgLen += len(reply.Result.Ok.Data) + var attrs []wasmvmtypes.EventAttribute + for _, e := range reply.Result.Ok.Events { + msgLen += len(e.Type) + attrs = append(e.Attributes) + } + // apply free tier on the whole set not per event + eventGas += g.EventCosts(attrs) + } + return eventGas + g.InstantiateContractCosts(pinned, msgLen) +} + +// EventCosts costs to persist an event +func (g WasmGasRegister) EventCosts(evts []wasmvmtypes.EventAttribute) sdk.Gas { + if len(evts) == 0 { + return 0 + } + var storedBytes int + for _, l := range evts { + storedBytes += len(l.Key) + len(l.Value) + } + // apply free tier + if storedBytes <= g.c.EventAttributeDataFreeTier { + storedBytes = 0 + } else { + storedBytes -= g.c.EventAttributeDataFreeTier + } + // total Length * costs + attribute count * costs + r := sdk.NewIntFromUint64(g.c.EventAttributeDataCost).Mul(sdk.NewIntFromUint64(uint64(storedBytes))). + Add(sdk.NewIntFromUint64(g.c.EventPerAttributeCost).Mul(sdk.NewIntFromUint64(uint64(len(evts))))) + if !r.IsUint64() { + panic(sdk.ErrorOutOfGas{Descriptor: "overflow"}) + } + return r.Uint64() +} + +// ToWasmVMGas convert to wasmVM contract runtime gas unit +func (g WasmGasRegister) ToWasmVMGas(source storetypes.Gas) uint64 { + x := source * g.c.GasMultiplier + if x < source { + panic(sdk.ErrorOutOfGas{Descriptor: "overflow"}) + } + return x +} + +// FromWasmVMGas converts to SDK gas unit +func (g WasmGasRegister) FromWasmVMGas(source uint64) sdk.Gas { + return source / g.c.GasMultiplier +} diff --git a/x/wasm/keeper/gas_register_test.go b/x/wasm/keeper/gas_register_test.go new file mode 100644 index 00000000000..0a54bc9c543 --- /dev/null +++ b/x/wasm/keeper/gas_register_test.go @@ -0,0 +1,385 @@ +package keeper + +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "math" + "strings" + "testing" +) + +func TestCompileCosts(t *testing.T) { + specs := map[string]struct { + srcLen int + srcConfig WasmGasRegisterConfig + exp sdk.Gas + expPanic bool + }{ + "one byte": { + srcLen: 1, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(2), // DefaultCompileCost + }, + "zero byte": { + srcLen: 0, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(0), + }, + "negative len": { + srcLen: -1, + srcConfig: DefaultGasRegisterConfig(), + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + NewWasmGasRegister(spec.srcConfig).CompileCosts(spec.srcLen) + }) + return + } + gotGas := NewWasmGasRegister(spec.srcConfig).CompileCosts(spec.srcLen) + assert.Equal(t, spec.exp, gotGas) + }) + } +} + +func TestNewContractInstanceCosts(t *testing.T) { + specs := map[string]struct { + srcLen int + srcConfig WasmGasRegisterConfig + pinned bool + exp sdk.Gas + expPanic bool + }{ + "small msg - pinned": { + srcLen: 1, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(1), + }, + "big msg - pinned": { + srcLen: math.MaxUint32, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(math.MaxUint32), + }, + "empty msg - pinned": { + srcLen: 0, + pinned: true, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(0), + }, + "small msg - unpinned": { + srcLen: 1, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(1), + }, + "big msg - unpinned": { + srcLen: math.MaxUint32, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(math.MaxUint32 + 40_000), + }, + "empty msg - unpinned": { + srcLen: 0, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000), + }, + + "negative len": { + srcLen: -1, + srcConfig: DefaultGasRegisterConfig(), + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + NewWasmGasRegister(spec.srcConfig).NewContractInstanceCosts(spec.pinned, spec.srcLen) + }) + return + } + gotGas := NewWasmGasRegister(spec.srcConfig).NewContractInstanceCosts(spec.pinned, spec.srcLen) + assert.Equal(t, spec.exp, gotGas) + }) + } +} + +func TestContractInstanceCosts(t *testing.T) { + // same as TestNewContractInstanceCosts currently + specs := map[string]struct { + srcLen int + srcConfig WasmGasRegisterConfig + pinned bool + exp sdk.Gas + expPanic bool + }{ + "small msg - pinned": { + srcLen: 1, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(1), + }, + "big msg - pinned": { + srcLen: math.MaxUint32, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(math.MaxUint32), + }, + "empty msg - pinned": { + srcLen: 0, + pinned: true, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(0), + }, + "small msg - unpinned": { + srcLen: 1, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(1), + }, + "big msg - unpinned": { + srcLen: math.MaxUint32, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(math.MaxUint32 + 40_000), + }, + "empty msg - unpinned": { + srcLen: 0, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000), + }, + + "negative len": { + srcLen: -1, + srcConfig: DefaultGasRegisterConfig(), + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + NewWasmGasRegister(spec.srcConfig).InstantiateContractCosts(spec.pinned, spec.srcLen) + }) + return + } + gotGas := NewWasmGasRegister(spec.srcConfig).InstantiateContractCosts(spec.pinned, spec.srcLen) + assert.Equal(t, spec.exp, gotGas) + }) + } +} + +func TestReplyCost(t *testing.T) { + specs := map[string]struct { + src wasmvmtypes.Reply + srcConfig WasmGasRegisterConfig + pinned bool + exp sdk.Gas + expPanic bool + }{ + "subcall response with events and data - pinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, + }, + Data: []byte{0x1}, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(3 + 10 + 1), // len("foo") + 1 * DefaultPerAttributeCost + len(data) + }, + "subcall response with events - pinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, + }, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(3 + 10), // len("foo") + 1 * DefaultPerAttributeCost + }, + "subcall response with events exceeds free tier- pinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: strings.Repeat("x", DefaultEventAttributeDataFreeTier), Value: "myData"}}}, + }, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(3 + 10 + 6), // len("foo") + 1 * DefaultPerAttributeCost + len("myData") + }, + "subcall response error - pinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Err: "foo", + }, + }, + srcConfig: DefaultGasRegisterConfig(), + pinned: true, + exp: sdk.Gas(3), // len("foo") + }, + "subcall response with events and data - unpinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, + }, + Data: []byte{0x1}, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000 + 3 + 10 + 1), // DefaultInstanceCost len("foo") + 1 * DefaultPerAttributeCost + len(data) + }, + "subcall response with events - unpinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, + }, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000 + 3 + 10), // DefaultInstanceCost + len("foo") + 1 * DefaultPerAttributeCost + }, + "subcall response with events exceeds free tier- unpinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: []wasmvmtypes.Event{ + {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: strings.Repeat("x", DefaultEventAttributeDataFreeTier), Value: "myData"}}}, + }, + }, + }, + }, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000 + 3 + 10 + 6), // DefaultInstanceCost + len("foo") + 1 * DefaultPerAttributeCost + len("myData") + }, + "subcall response error - unpinned": { + src: wasmvmtypes.Reply{ + Result: wasmvmtypes.SubcallResult{ + Err: "foo", + }, + }, + srcConfig: DefaultGasRegisterConfig(), + exp: sdk.Gas(40_000 + 3), // DefaultInstanceCost + len("foo") + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + NewWasmGasRegister(spec.srcConfig).ReplyCosts(spec.pinned, spec.src) + }) + return + } + gotGas := NewWasmGasRegister(spec.srcConfig).ReplyCosts(spec.pinned, spec.src) + assert.Equal(t, spec.exp, gotGas) + }) + } +} + +func TestToWasmVMGasConversion(t *testing.T) { + specs := map[string]struct { + src storetypes.Gas + srcConfig WasmGasRegisterConfig + exp uint64 + expPanic bool + }{ + "0": { + src: 0, + exp: 0, + srcConfig: DefaultGasRegisterConfig(), + }, + "max": { + srcConfig: WasmGasRegisterConfig{ + GasMultiplier: 1, + }, + src: math.MaxUint64, + exp: math.MaxUint64, + }, + "overflow": { + srcConfig: WasmGasRegisterConfig{ + GasMultiplier: 2, + }, + src: math.MaxUint64, + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + r := NewWasmGasRegister(spec.srcConfig) + _ = r.ToWasmVMGas(spec.src) + }) + return + } + r := NewWasmGasRegister(spec.srcConfig) + got := r.ToWasmVMGas(spec.src) + assert.Equal(t, spec.exp, got) + }) + } +} +func TestFromWasmVMGasConversion(t *testing.T) { + specs := map[string]struct { + src uint64 + exp storetypes.Gas + srcConfig WasmGasRegisterConfig + expPanic bool + }{ + "0": { + src: 0, + exp: 0, + srcConfig: DefaultGasRegisterConfig(), + }, + "max": { + srcConfig: WasmGasRegisterConfig{ + GasMultiplier: 1, + }, + src: math.MaxUint64, + exp: math.MaxUint64, + }, + "missconfigured": { + srcConfig: WasmGasRegisterConfig{ + GasMultiplier: 0, + }, + src: 1, + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + if spec.expPanic { + assert.Panics(t, func() { + r := NewWasmGasRegister(spec.srcConfig) + _ = r.FromWasmVMGas(spec.src) + }) + return + } + r := NewWasmGasRegister(spec.srcConfig) + got := r.FromWasmVMGas(spec.src) + assert.Equal(t, spec.exp, got) + }) + } +} diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 9614444daa9..40c66d0ae8a 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -20,21 +20,6 @@ import ( "time" ) -// DefaultGasMultiplier is how many cosmwasm gas points = 1 sdk gas point -// SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 -// A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io -// Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read) -// -// Please note that all gas prices returned to the wasmer engine should have this multiplied -const DefaultGasMultiplier uint64 = 100 - -// DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance. -// Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts. -const DefaultInstanceCost uint64 = 40_000 - -// DefaultCompileCost is how much SDK gas we charge *per byte* for compiling WASM code. -const DefaultCompileCost uint64 = 2 - // contractMemoryLimit is the memory limit of each contract execution (in MiB) // constant value so all nodes run with the same limit. const contractMemoryLimit = 32 @@ -83,9 +68,7 @@ type Keeper struct { // queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract queryGasLimit uint64 paramSpace paramtypes.Subspace - instanceCost uint64 - compileCost uint64 - gasMultiplier uint64 + gasRegister GasRegister } // NewKeeper creates a new contract Keeper instance @@ -129,9 +112,7 @@ func NewKeeper( messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, cdc, portSource), queryGasLimit: wasmConfig.SmartQueryGasLimit, paramSpace: paramSpace, - instanceCost: DefaultInstanceCost, - compileCost: DefaultCompileCost, - gasMultiplier: DefaultGasMultiplier, + gasRegister: NewDefaultWasmGasRegister(), } keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, keeper) @@ -180,7 +161,7 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, if err != nil { return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } - ctx.GasMeter().ConsumeGas(k.compileCost*uint64(len(wasmCode)), "Compiling WASM Bytecode") + ctx.GasMeter().ConsumeGas(k.gasRegister.CompileCosts(len(wasmCode)), "Compiling WASM Bytecode") codeHash, err := k.wasmVM.Create(wasmCode) if err != nil { @@ -227,9 +208,9 @@ func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeIn func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.AccAddress, []byte, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate") - if !k.IsPinnedCode(ctx, codeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: instantiate") - } + + instanceCosts := k.gasRegister.NewContractInstanceCosts(k.IsPinnedCode(ctx, codeID), len(initMsg)) + ctx.GasMeter().ConsumeGas(instanceCosts, "Loading CosmWasm module: instantiate") // create contract address contractAddress := k.generateContractAddress(ctx, codeID) @@ -277,17 +258,13 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A querier := k.newQueryHandler(ctx, contractAddress) // instantiate wasm contract - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, err := k.wasmVM.Instantiate(codeInfo.CodeHash, env, info, initMsg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if err != nil { return contractAddress, nil, sdkerrors.Wrap(types.ErrInstantiateFailed, err.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddress) - ctx.EventManager().EmitEvents(events) - // persist instance first createdAt := types.NewAbsoluteTxPosition(ctx) contractInfo := types.NewContractInfo(codeID, creator, admin, label, createdAt) @@ -313,7 +290,7 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A k.storeContractInfo(ctx, contractAddress, &contractInfo) // dispatch submessages then messages - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res) + data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Data) if err != nil { return nil, nil, sdkerrors.Wrap(err, "dispatch") } @@ -329,9 +306,8 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller return nil, err } - if !k.IsPinnedCode(ctx, contractInfo.CodeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: execute") - } + executeCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg)) + ctx.GasMeter().ConsumeGas(executeCosts, "Loading CosmWasm module: execute") // add more funds if !coins.IsZero() { @@ -345,19 +321,15 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller // prepare querier querier := k.newQueryHandler(ctx, contractAddress) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddress) - ctx.EventManager().EmitEvents(events) - // dispatch submessages then messages - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res) + data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Data) if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -366,9 +338,8 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate") - if !k.IsPinnedCode(ctx, newCodeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: migrate") - } + migrateSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, newCodeID), len(msg)) + ctx.GasMeter().ConsumeGas(migrateSetupCosts, "Loading CosmWasm module: migrate") contractInfo := k.GetContractInfo(ctx, contractAddress) if contractInfo == nil { @@ -406,17 +377,13 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller prefixStoreKey := types.GetContractStorePrefix(contractAddress) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, err := k.wasmVM.Migrate(newCodeInfo.CodeHash, env, msg, &prefixStore, cosmwasmAPI, &querier, k.gasMeter(ctx), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if err != nil { return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error()) } - // emit all events from this contract migration itself - events := types.ParseEvents(res.Attributes, contractAddress) - ctx.EventManager().EmitEvents(events) - // delete old secondary index entry k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress)) // persist migration updates @@ -426,7 +393,7 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller k.storeContractInfo(ctx, contractAddress, contractInfo) // dispatch submessages then messages - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res) + data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Data) if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -443,27 +410,22 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte return nil, err } - if !k.IsPinnedCode(ctx, contractInfo.CodeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: sudo") - } + sudoSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg)) + ctx.GasMeter().ConsumeGas(sudoSetupCosts, "Loading CosmWasm module: sudo") env := types.NewEnv(ctx, contractAddress) // prepare querier querier := k.newQueryHandler(ctx, contractAddress) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.Sudo(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddress) - ctx.EventManager().EmitEvents(events) - // dispatch submessages then messages - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res) + data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Data) if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -478,10 +440,8 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply was return nil, err } - // current thought is to charge gas like a fresh run, we can revisit whether to give it a discount later - if !k.IsPinnedCode(ctx, contractInfo.CodeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: reply") - } + replyCosts := k.gasRegister.ReplyCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), reply) + ctx.GasMeter().ConsumeGas(replyCosts, "Loading CosmWasm module: reply") env := types.NewEnv(ctx, contractAddress) @@ -490,19 +450,15 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply was Ctx: ctx, Plugins: k.wasmVMQueryHandler, } - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.Reply(codeInfo.CodeHash, env, reply, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddress) - ctx.EventManager().EmitEvents(events) - // dispatch submessages then messages - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res) + data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Data) if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -592,16 +548,16 @@ func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []b if err != nil { return nil, err } - if !k.IsPinnedCode(ctx, contractInfo.CodeID) { - ctx.GasMeter().ConsumeGas(k.instanceCost, "Loading CosmWasm module: query") - } + + smartQuerySetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(req)) + ctx.GasMeter().ConsumeGas(smartQuerySetupCosts, "Loading CosmWasm module: query") // prepare querier querier := k.newQueryHandler(ctx, contractAddr) env := types.NewEnv(ctx, contractAddr) - queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), k.gasForContract(ctx)) - k.consumeGas(ctx, gasUsed) + queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), k.runtimeGasForContract(ctx)) + k.consumeRuntimeGas(ctx, gasUsed) if qErr != nil { return nil, sdkerrors.Wrap(types.ErrQueryFailed, qErr.Error()) } @@ -802,22 +758,37 @@ func (k Keeper) setContractInfoExtension(ctx sdk.Context, contractAddr sdk.AccAd return nil } -// handleContractResponse processes the contract response -func (k *Keeper) handleContractResponse(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, res *wasmvmtypes.Response) ([]byte, error) { - return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, res.Submessages, res.Messages, res.Data) +// handleContractResponse processes the contract response data by emitting events and sending sub-/messages. +func (k *Keeper) handleContractResponse( + ctx sdk.Context, + contractAddr sdk.AccAddress, + ibcPort string, + subMsg []wasmvmtypes.SubMsg, + msgs []wasmvmtypes.CosmosMsg, + attrs []wasmvmtypes.EventAttribute, + data []byte, +) ([]byte, error) { + attributeGasCost := k.gasRegister.EventCosts(attrs) + ctx.GasMeter().ConsumeGas(attributeGasCost, "Custom contract event attributes") + // emit all events from this contract itself + events := types.ParseEvents(attrs, contractAddr) + ctx.EventManager().EmitEvents(events) + return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, subMsg, msgs, data) } -func (k Keeper) gasForContract(ctx sdk.Context) uint64 { +func (k Keeper) runtimeGasForContract(ctx sdk.Context) uint64 { meter := ctx.GasMeter() if meter.IsOutOfGas() { return 0 } - remaining := (meter.Limit() - meter.GasConsumedToLimit()) * k.gasMultiplier - return remaining + if meter.Limit() == 0 { // infinite gas meter with limit=0 and not out of gas + return math.MaxUint64 + } + return k.gasRegister.ToWasmVMGas(meter.Limit() - meter.GasConsumedToLimit()) } -func (k Keeper) consumeGas(ctx sdk.Context, gas uint64) { - consumed := gas / k.gasMultiplier +func (k Keeper) consumeRuntimeGas(ctx sdk.Context, gas uint64) { + consumed := k.gasRegister.FromWasmVMGas(gas) ctx.GasMeter().ConsumeGas(consumed, "wasm contract") // throw OutOfGas error if we ran out (got exactly to zero due to better limit enforcing) if ctx.GasMeter().IsOutOfGas() { @@ -903,7 +874,7 @@ func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c * } func (k Keeper) newQueryHandler(ctx sdk.Context, contractAddress sdk.AccAddress) QueryHandler { - return NewQueryHandler(ctx, k.wasmVMQueryHandler, contractAddress, k.gasMultiplier) + return NewQueryHandler(ctx, k.wasmVMQueryHandler, contractAddress, k.gasRegister) } func addrFromUint64(id uint64) sdk.AccAddress { @@ -916,21 +887,21 @@ func addrFromUint64(id uint64) sdk.AccAddress { // MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier type MultipliedGasMeter struct { originalMeter sdk.GasMeter - gasMultiplier uint64 + GasRegister GasRegister } -func NewMultipliedGasMeter(originalMeter sdk.GasMeter, gasMultiplier uint64) MultipliedGasMeter { - return MultipliedGasMeter{originalMeter: originalMeter, gasMultiplier: gasMultiplier} +func NewMultipliedGasMeter(originalMeter sdk.GasMeter, gr GasRegister) MultipliedGasMeter { + return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr} } var _ wasmvm.GasMeter = MultipliedGasMeter{} func (m MultipliedGasMeter) GasConsumed() sdk.Gas { - return m.originalMeter.GasConsumed() * m.gasMultiplier + return m.GasRegister.ToWasmVMGas(m.originalMeter.GasConsumed()) } func (k Keeper) gasMeter(ctx sdk.Context) MultipliedGasMeter { - return NewMultipliedGasMeter(ctx.GasMeter(), k.gasMultiplier) + return NewMultipliedGasMeter(ctx.GasMeter(), k.gasRegister) } // Logger returns a module-specific logger. diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go index e0c951d0a69..08b58442cef 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -284,7 +284,7 @@ func TestInstantiate(t *testing.T) { gasAfter := ctx.GasMeter().GasConsumed() if types.EnableGasVerification { - require.Equal(t, uint64(0x122a0), gasAfter-gasBefore) + require.Equal(t, uint64(0x12324), gasAfter-gasBefore) } // ensure it is stored properly @@ -517,7 +517,7 @@ func TestExecute(t *testing.T) { // make sure gas is properly deducted from ctx gasAfter := ctx.GasMeter().GasConsumed() if types.EnableGasVerification { - require.Equal(t, uint64(0x12917), gasAfter-gasBefore) + require.Equal(t, uint64(0x12939), gasAfter-gasBefore) } // ensure bob now exists and got both payments released bobAcct = accKeeper.GetAccount(ctx, bob) diff --git a/x/wasm/keeper/msg_server.go b/x/wasm/keeper/msg_server.go index b6f2109f129..86a81c6fe8c 100644 --- a/x/wasm/keeper/msg_server.go +++ b/x/wasm/keeper/msg_server.go @@ -66,7 +66,7 @@ func (m msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInst sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeySigner, msg.Sender), sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", msg.CodeID)), - sdk.NewAttribute(types.AttributeKeyContract, contractAddr.String()), + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String()), sdk.NewAttribute(types.AttributeResultDataHex, hex.EncodeToString(data)), )) @@ -96,7 +96,7 @@ func (m msgServer) ExecuteContract(goCtx context.Context, msg *types.MsgExecuteC sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeySigner, msg.Sender), - sdk.NewAttribute(types.AttributeKeyContract, msg.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, msg.Contract), sdk.NewAttribute(types.AttributeResultDataHex, hex.EncodeToString(data)), )) @@ -126,7 +126,7 @@ func (m msgServer) MigrateContract(goCtx context.Context, msg *types.MsgMigrateC sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeySigner, msg.Sender), sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", msg.CodeID)), - sdk.NewAttribute(types.AttributeKeyContract, msg.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, msg.Contract), sdk.NewAttribute(types.AttributeResultDataHex, hex.EncodeToString(data)), )) @@ -158,7 +158,7 @@ func (m msgServer) UpdateAdmin(goCtx context.Context, msg *types.MsgUpdateAdmin) sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeySigner, msg.Sender), - sdk.NewAttribute(types.AttributeKeyContract, msg.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, msg.Contract), )) return &types.MsgUpdateAdminResponse{}, nil @@ -183,7 +183,7 @@ func (m msgServer) ClearAdmin(goCtx context.Context, msg *types.MsgClearAdmin) ( sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeySigner, msg.Sender), - sdk.NewAttribute(types.AttributeKeyContract, msg.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, msg.Contract), )) return &types.MsgClearAdminResponse{}, nil diff --git a/x/wasm/keeper/options.go b/x/wasm/keeper/options.go index 1f340b180cb..c326f49f57d 100644 --- a/x/wasm/keeper/options.go +++ b/x/wasm/keeper/options.go @@ -82,18 +82,12 @@ func WithVMCacheMetrics(r prometheus.Registerer) Option { }) } -// WithCosts sets custom gas costs and multiplier. -// See DefaultCompileCost, DefaultInstanceCost, DefaultGasMultiplier -// Uses WithApiCosts with defaults and given multiplier. -func WithCosts(compile, instance, multiplier uint64) Option { +// WithGasRegister set a new gas register to implement custom gas costs. +// When the "gas multiplier" for wasmvm gas convertion is modified inside the new register, +// make sure to also use `WithApiCosts` option for non default values +func WithGasRegister(x GasRegister) Option { return optsFn(func(k *Keeper) { - k.compileCost = compile - k.instanceCost = instance - k.gasMultiplier = multiplier - WithApiCosts( - DefaultGasCostHumanAddress*multiplier, - DefaultGasCostCanonicalAddress*multiplier, - ).apply(k) + k.gasRegister = x }) } diff --git a/x/wasm/keeper/options_test.go b/x/wasm/keeper/options_test.go index 864ae8a8955..3c45ca804ba 100644 --- a/x/wasm/keeper/options_test.go +++ b/x/wasm/keeper/options_test.go @@ -41,14 +41,9 @@ func TestConstructorOptions(t *testing.T) { }, }, "costs": { - srcOpt: WithCosts(1, 2, 3), + srcOpt: WithGasRegister(&wasmtesting.MockGasRegister{}), verify: func(t *testing.T, k Keeper) { - t.Cleanup(setApiDefaults) - assert.Equal(t, uint64(1), k.compileCost) - assert.Equal(t, uint64(2), k.instanceCost) - assert.Equal(t, uint64(3), k.gasMultiplier) - assert.Equal(t, uint64(15), costHumanize) - assert.Equal(t, uint64(12), costCanonical) + assert.IsType(t, k.gasRegister, &wasmtesting.MockGasRegister{}) }, }, "api costs": { diff --git a/x/wasm/keeper/proposal_handler.go b/x/wasm/keeper/proposal_handler.go index a2a224c2659..c479dec6ec1 100644 --- a/x/wasm/keeper/proposal_handler.go +++ b/x/wasm/keeper/proposal_handler.go @@ -94,7 +94,7 @@ func handleInstantiateProposal(ctx sdk.Context, k types.ContractOpsKeeper, p typ sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", p.CodeID)), - sdk.NewAttribute(types.AttributeKeyContract, contractAddr.String()), + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String()), sdk.NewAttribute(types.AttributeResultDataHex, hex.EncodeToString(data)), ) ctx.EventManager().EmitEvent(ourEvent) @@ -123,7 +123,7 @@ func handleMigrateProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.M sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", p.CodeID)), - sdk.NewAttribute(types.AttributeKeyContract, p.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, p.Contract), sdk.NewAttribute(types.AttributeResultDataHex, hex.EncodeToString(data)), ) ctx.EventManager().EmitEvent(ourEvent) @@ -150,7 +150,7 @@ func handleUpdateAdminProposal(ctx sdk.Context, k types.ContractOpsKeeper, p typ ourEvent := sdk.NewEvent( sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeKeyContract, p.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, p.Contract), ) ctx.EventManager().EmitEvent(ourEvent) return nil @@ -171,7 +171,7 @@ func handleClearAdminProposal(ctx sdk.Context, k types.ContractOpsKeeper, p type ourEvent := sdk.NewEvent( sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeKeyContract, p.Contract), + sdk.NewAttribute(types.AttributeKeyContractAddr, p.Contract), ) ctx.EventManager().EmitEvent(ourEvent) return nil diff --git a/x/wasm/keeper/query_plugins.go b/x/wasm/keeper/query_plugins.go index 6b44ba87ac4..a47fa35f639 100644 --- a/x/wasm/keeper/query_plugins.go +++ b/x/wasm/keeper/query_plugins.go @@ -15,18 +15,18 @@ import ( ) type QueryHandler struct { - Ctx sdk.Context - Plugins WasmVMQueryHandler - Caller sdk.AccAddress - GasMultiplier uint64 + Ctx sdk.Context + Plugins WasmVMQueryHandler + Caller sdk.AccAddress + gasRegister GasRegister } -func NewQueryHandler(ctx sdk.Context, vmQueryHandler WasmVMQueryHandler, caller sdk.AccAddress, gasMultiplier uint64) QueryHandler { +func NewQueryHandler(ctx sdk.Context, vmQueryHandler WasmVMQueryHandler, caller sdk.AccAddress, gasRegister GasRegister) QueryHandler { return QueryHandler{ - Ctx: ctx, - Plugins: vmQueryHandler, - Caller: caller, - GasMultiplier: gasMultiplier, + Ctx: ctx, + Plugins: vmQueryHandler, + Caller: caller, + gasRegister: gasRegister, } } @@ -46,7 +46,7 @@ var _ wasmvmtypes.Querier = QueryHandler{} func (q QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { // set a limit for a subctx - sdkGas := gasLimit / q.GasMultiplier + sdkGas := q.gasRegister.FromWasmVMGas(gasLimit) subctx := q.Ctx.WithGasMeter(sdk.NewGasMeter(sdkGas)) // make sure we charge the higher level context even on panic diff --git a/x/wasm/keeper/recurse_test.go b/x/wasm/keeper/recurse_test.go index b3d553bf121..1508c0849d3 100644 --- a/x/wasm/keeper/recurse_test.go +++ b/x/wasm/keeper/recurse_test.go @@ -57,12 +57,12 @@ func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.Acc func TestGasCostOnQuery(t *testing.T) { const ( - GasNoWork uint64 = 44_072 + GasNoWork uint64 = 44_163 // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork50 uint64 = 49_764 // this is a little shy of 50k gas - to keep an eye on the limit + GasWork50 uint64 = 49_856 // this is a little shy of 50k gas - to keep an eye on the limit - GasReturnUnhashed uint64 = 283 - GasReturnHashed uint64 = 257 + GasReturnUnhashed uint64 = 224 + GasReturnHashed uint64 = 198 ) cases := map[string]struct { @@ -221,9 +221,9 @@ func TestLimitRecursiveQueryGas(t *testing.T) { const ( // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork2k uint64 = 273_567 // = InstanceCost + x // we have 6x gas used in cpu than in the instance + GasWork2k uint64 = 273_661 // = NewContractInstanceCosts + x // we have 6x gas used in cpu than in the instance // This is overhead for calling into a sub-contract - GasReturnHashed uint64 = 262 + GasReturnHashed uint64 = 203 ) cases := map[string]struct { diff --git a/x/wasm/keeper/relay.go b/x/wasm/keeper/relay.go index 090d3e015e4..8d59956ca7f 100644 --- a/x/wasm/keeper/relay.go +++ b/x/wasm/keeper/relay.go @@ -29,9 +29,9 @@ func (k Keeper) OnOpenChannel( env := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) gasUsed, execErr := k.wasmVM.IBCChannelOpen(codeInfo.CodeHash, env, channel, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } @@ -60,21 +60,14 @@ func (k Keeper) OnConnectChannel( env := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.IBCChannelConnect(codeInfo.CodeHash, env, channel, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddr) - ctx.EventManager().EmitEvents(events) - - if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil { - return err - } - return nil + return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) } // OnCloseChannel calls the contract to let it know the IBC channel is closed. @@ -98,21 +91,14 @@ func (k Keeper) OnCloseChannel( params := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.IBCChannelClose(codeInfo.CodeHash, params, channel, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddr) - ctx.EventManager().EmitEvents(events) - - if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil { - return err - } - return nil + return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) } // OnRecvPacket calls the contract to process the incoming IBC packet. The contract fully owns the data processing and @@ -135,17 +121,14 @@ func (k Keeper) OnRecvPacket( env := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.IBCPacketReceive(codeInfo.CodeHash, env, packet, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddr) - ctx.EventManager().EmitEvents(events) - return k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Acknowledgement) + return k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Attributes, res.Acknowledgement) } // OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet @@ -169,21 +152,13 @@ func (k Keeper) OnAckPacket( env := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.IBCPacketAck(codeInfo.CodeHash, env, acknowledgement, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddr) - ctx.EventManager().EmitEvents(events) - - if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil { - return err - } - return nil + return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) } // OnTimeoutPacket calls the contract to let it know the packet was never received on the destination chain within @@ -204,19 +179,17 @@ func (k Keeper) OnTimeoutPacket( env := types.NewEnv(ctx, contractAddr) querier := k.newQueryHandler(ctx, contractAddr) - gas := k.gasForContract(ctx) + gas := k.runtimeGasForContract(ctx) res, gasUsed, execErr := k.wasmVM.IBCPacketTimeout(codeInfo.CodeHash, env, packet, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) - k.consumeGas(ctx, gasUsed) + k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - // emit all events from this contract itself - events := types.ParseEvents(res.Attributes, contractAddr) - ctx.EventManager().EmitEvents(events) + return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) +} - if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil { - return err - } - return nil +func (k Keeper) handleIBCBasicContractResponse(ctx sdk.Context, addr sdk.AccAddress, id string, res *wasmvmtypes.IBCBasicResponse) error { + _, err := k.handleContractResponse(ctx, addr, id, res.Submessages, res.Messages, res.Attributes, nil) + return err } diff --git a/x/wasm/keeper/relay_test.go b/x/wasm/keeper/relay_test.go index b006cf2c76f..c0287ad3d0e 100644 --- a/x/wasm/keeper/relay_test.go +++ b/x/wasm/keeper/relay_test.go @@ -19,24 +19,28 @@ func TestOnOpenChannel(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress contractGas sdk.Gas contractErr error + expGas uint64 expErr bool }{ "consume contract gas": { contractAddr: example.Contract, - contractGas: 10, + contractGas: myContractGas, + expGas: myContractGas, }, "consume max gas": { contractAddr: example.Contract, contractGas: math.MaxUint64 / DefaultGasMultiplier, + expGas: math.MaxUint64 / DefaultGasMultiplier, }, "consume gas on error": { contractAddr: example.Contract, - contractGas: 20, + contractGas: myContractGas, contractErr: errors.New("test, ignore"), expErr: true, }, @@ -53,8 +57,7 @@ func TestOnOpenChannel(t *testing.T) { return spec.contractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() - defer cancel() + ctx, _ := parentCtx.CacheContext() before := ctx.GasMeter().GasConsumed() // when @@ -68,7 +71,7 @@ func TestOnOpenChannel(t *testing.T) { require.NoError(t, err) // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expGas, ctx.GasMeter().GasConsumed()-before-storageCosts) }) } } @@ -79,25 +82,26 @@ func TestOnConnectChannel(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress - contractGas sdk.Gas contractResp *wasmvmtypes.IBCBasicResponse contractErr error overwriteMessenger *wasmtesting.MockMessageHandler + expContractGas sdk.Gas expErr bool expContractEventAttrs int expNoEvents bool }{ "consume contract gas": { - contractAddr: example.Contract, - contractGas: 10, - contractResp: &wasmvmtypes.IBCBasicResponse{}, + contractAddr: example.Contract, + expContractGas: myContractGas, + contractResp: &wasmvmtypes.IBCBasicResponse{}, }, "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - contractGas: 20, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -107,23 +111,23 @@ func TestOnConnectChannel(t *testing.T) { expNoEvents: true, }, "dispatch contract messages on success": { - contractAddr: example.Contract, - contractGas: 30, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, }, }, "emit contract events on success": { - contractAddr: example.Contract, - contractGas: 40, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, expContractEventAttrs: 1, }, "messenger errors returned, events stored": { - contractAddr: example.Contract, - contractGas: 50, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -143,12 +147,12 @@ func TestOnConnectChannel(t *testing.T) { myChannel := wasmvmtypes.IBCChannel{Version: "my test channel"} m.IBCChannelConnectFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, channel wasmvmtypes.IBCChannel, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.IBCBasicResponse, uint64, error) { assert.Equal(t, channel, myChannel) - return spec.contractResp, spec.contractGas * DefaultGasMultiplier, spec.contractErr + return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() + ctx, _ := parentCtx.CacheContext() ctx = ctx.WithEventManager(sdk.NewEventManager()) - defer cancel() + before := ctx.GasMeter().GasConsumed() msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() *messenger = *msger @@ -175,7 +179,7 @@ func TestOnConnectChannel(t *testing.T) { require.NoError(t, err) // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) // verify msgs dispatched assert.Equal(t, spec.contractResp.Messages, *capturedMsgs) assert.Len(t, events[0].Attributes, 1+spec.expContractEventAttrs) @@ -189,25 +193,26 @@ func TestOnCloseChannel(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress - contractGas sdk.Gas contractResp *wasmvmtypes.IBCBasicResponse contractErr error overwriteMessenger *wasmtesting.MockMessageHandler + expContractGas sdk.Gas expErr bool expContractEventAttrs int expNoEvents bool }{ "consume contract gas": { - contractAddr: example.Contract, - contractGas: 10, - contractResp: &wasmvmtypes.IBCBasicResponse{}, + contractAddr: example.Contract, + expContractGas: myContractGas, + contractResp: &wasmvmtypes.IBCBasicResponse{}, }, "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - contractGas: 20, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -217,23 +222,23 @@ func TestOnCloseChannel(t *testing.T) { expNoEvents: true, }, "dispatch contract messages on success": { - contractAddr: example.Contract, - contractGas: 30, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, }, }, "emit contract events on success": { - contractAddr: example.Contract, - contractGas: 40, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, expContractEventAttrs: 1, }, "messenger errors returned, events stored": { - contractAddr: example.Contract, - contractGas: 50, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -253,11 +258,10 @@ func TestOnCloseChannel(t *testing.T) { myChannel := wasmvmtypes.IBCChannel{Version: "my test channel"} m.IBCChannelCloseFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, channel wasmvmtypes.IBCChannel, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.IBCBasicResponse, uint64, error) { assert.Equal(t, channel, myChannel) - return spec.contractResp, spec.contractGas * DefaultGasMultiplier, spec.contractErr + return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() - defer cancel() + ctx, _ := parentCtx.CacheContext() before := ctx.GasMeter().GasConsumed() msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() *messenger = *msger @@ -285,7 +289,7 @@ func TestOnCloseChannel(t *testing.T) { require.NoError(t, err) // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) // verify msgs dispatched assert.Equal(t, spec.contractResp.Messages, *capturedMsgs) require.Len(t, events, 1) @@ -300,32 +304,33 @@ func TestOnRecvPacket(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress - contractGas sdk.Gas contractResp *wasmvmtypes.IBCReceiveResponse contractErr error overwriteMessenger *wasmtesting.MockMessageHandler + expContractGas sdk.Gas expErr bool expContractEventAttrs int expNoEvents bool }{ "consume contract gas": { - contractAddr: example.Contract, - contractGas: 10, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCReceiveResponse{ Acknowledgement: []byte("myAck"), }, }, "can return empty ack": { - contractAddr: example.Contract, - contractGas: 10, - contractResp: &wasmvmtypes.IBCReceiveResponse{}, + contractAddr: example.Contract, + expContractGas: myContractGas, + contractResp: &wasmvmtypes.IBCReceiveResponse{}, }, "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - contractGas: 20, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCReceiveResponse{ Acknowledgement: []byte("myAck"), Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}}, @@ -336,16 +341,16 @@ func TestOnRecvPacket(t *testing.T) { expNoEvents: true, }, "dispatch contract messages on success": { - contractAddr: example.Contract, - contractGas: 30, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCReceiveResponse{ Acknowledgement: []byte("myAck"), Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, }, }, "emit contract events on success": { - contractAddr: example.Contract, - contractGas: 40, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCReceiveResponse{ Acknowledgement: []byte("myAck"), Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -353,8 +358,8 @@ func TestOnRecvPacket(t *testing.T) { expContractEventAttrs: 1, }, "messenger errors returned, events stored": { - contractAddr: example.Contract, - contractGas: 50, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCReceiveResponse{ Acknowledgement: []byte("myAck"), Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, @@ -376,11 +381,10 @@ func TestOnRecvPacket(t *testing.T) { m.IBCPacketReceiveFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, packet wasmvmtypes.IBCPacket, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.IBCReceiveResponse, uint64, error) { assert.Equal(t, myPacket, packet) - return spec.contractResp, spec.contractGas * DefaultGasMultiplier, spec.contractErr + return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() - defer cancel() + ctx, _ := parentCtx.CacheContext() before := ctx.GasMeter().GasConsumed() msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() @@ -411,7 +415,7 @@ func TestOnRecvPacket(t *testing.T) { // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) // verify msgs dispatched assert.Equal(t, spec.contractResp.Messages, *capturedMsgs) require.Len(t, events, 1) @@ -426,25 +430,26 @@ func TestOnAckPacket(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress - contractGas sdk.Gas contractResp *wasmvmtypes.IBCBasicResponse contractErr error overwriteMessenger *wasmtesting.MockMessageHandler + expContractGas sdk.Gas expErr bool expContractEventAttrs int expNoEvents bool }{ "consume contract gas": { - contractAddr: example.Contract, - contractGas: 10, - contractResp: &wasmvmtypes.IBCBasicResponse{}, + contractAddr: example.Contract, + expContractGas: myContractGas, + contractResp: &wasmvmtypes.IBCBasicResponse{}, }, "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - contractGas: 20, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -454,23 +459,23 @@ func TestOnAckPacket(t *testing.T) { expNoEvents: true, }, "dispatch contract messages on success": { - contractAddr: example.Contract, - contractGas: 30, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, }, }, "emit contract events on success": { - contractAddr: example.Contract, - contractGas: 40, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, expContractEventAttrs: 1, }, "messenger errors returned, events stored": { - contractAddr: example.Contract, - contractGas: 50, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -491,11 +496,10 @@ func TestOnAckPacket(t *testing.T) { myAck := wasmvmtypes.IBCAcknowledgement{Acknowledgement: []byte("myAck")} m.IBCPacketAckFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, ack wasmvmtypes.IBCAcknowledgement, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.IBCBasicResponse, uint64, error) { assert.Equal(t, myAck, ack) - return spec.contractResp, spec.contractGas * DefaultGasMultiplier, spec.contractErr + return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() - defer cancel() + ctx, _ := parentCtx.CacheContext() before := ctx.GasMeter().GasConsumed() msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() *messenger = *msger @@ -523,7 +527,7 @@ func TestOnAckPacket(t *testing.T) { require.NoError(t, err) // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) // verify msgs dispatched assert.Equal(t, spec.contractResp.Messages, *capturedMsgs) require.Len(t, events, 1) @@ -538,25 +542,26 @@ func TestOnTimeoutPacket(t *testing.T) { var messenger = &wasmtesting.MockMessageHandler{} parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger)) example := SeedNewContractInstance(t, parentCtx, keepers, &m) + const myContractGas = 40 specs := map[string]struct { contractAddr sdk.AccAddress - contractGas sdk.Gas contractResp *wasmvmtypes.IBCBasicResponse contractErr error overwriteMessenger *wasmtesting.MockMessageHandler + expContractGas sdk.Gas expErr bool expContractEventAttrs int expNoEvents bool }{ "consume contract gas": { - contractAddr: example.Contract, - contractGas: 10, - contractResp: &wasmvmtypes.IBCBasicResponse{}, + contractAddr: example.Contract, + expContractGas: myContractGas, + contractResp: &wasmvmtypes.IBCBasicResponse{}, }, "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - contractGas: 20, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -566,23 +571,23 @@ func TestOnTimeoutPacket(t *testing.T) { expNoEvents: true, }, "dispatch contract messages on success": { - contractAddr: example.Contract, - contractGas: 30, + contractAddr: example.Contract, + expContractGas: myContractGas, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, }, }, "emit contract events on success": { - contractAddr: example.Contract, - contractGas: 40, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, expContractEventAttrs: 1, }, "messenger errors returned, events stored": { - contractAddr: example.Contract, - contractGas: 50, + contractAddr: example.Contract, + expContractGas: myContractGas + 10, contractResp: &wasmvmtypes.IBCBasicResponse{ Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, @@ -602,11 +607,10 @@ func TestOnTimeoutPacket(t *testing.T) { myPacket := wasmvmtypes.IBCPacket{Data: []byte("my test packet")} m.IBCPacketTimeoutFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, packet wasmvmtypes.IBCPacket, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.IBCBasicResponse, uint64, error) { assert.Equal(t, myPacket, packet) - return spec.contractResp, spec.contractGas * DefaultGasMultiplier, spec.contractErr + return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr } - ctx, cancel := parentCtx.CacheContext() - defer cancel() + ctx, _ := parentCtx.CacheContext() before := ctx.GasMeter().GasConsumed() msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() *messenger = *msger @@ -634,7 +638,7 @@ func TestOnTimeoutPacket(t *testing.T) { require.NoError(t, err) // verify gas consumed const storageCosts = sdk.Gas(0xa9d) - assert.Equal(t, spec.contractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) + assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) // verify msgs dispatched assert.Equal(t, spec.contractResp.Messages, *capturedMsgs) require.Len(t, events, 1) diff --git a/x/wasm/keeper/wasmtesting/gas_register.go b/x/wasm/keeper/wasmtesting/gas_register.go new file mode 100644 index 00000000000..a8533709af7 --- /dev/null +++ b/x/wasm/keeper/wasmtesting/gas_register.go @@ -0,0 +1,66 @@ +package wasmtesting + +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MockGasRegister mock that implements keeper.GasRegister +type MockGasRegister struct { + CompileCostFn func(byteLength int) sdk.Gas + NewContractInstanceCostFn func(pinned bool, msgLen int) sdk.Gas + InstantiateContractCostFn func(pinned bool, msgLen int) sdk.Gas + ReplyCostFn func(pinned bool, reply wasmvmtypes.Reply) sdk.Gas + EventCostsFn func(evts []wasmvmtypes.EventAttribute) sdk.Gas + ToWasmVMGasFn func(source sdk.Gas) uint64 + FromWasmVMGasFn func(source uint64) sdk.Gas +} + +func (m MockGasRegister) NewContractInstanceCosts(pinned bool, msgLen int) sdk.Gas { + if m.NewContractInstanceCostFn == nil { + panic("not expected to be called") + } + return m.NewContractInstanceCostFn(pinned, msgLen) +} + +func (m MockGasRegister) CompileCosts(byteLength int) sdk.Gas { + if m.CompileCostFn == nil { + panic("not expected to be called") + } + return m.CompileCostFn(byteLength) +} + +func (m MockGasRegister) InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas { + if m.InstantiateContractCostFn == nil { + panic("not expected to be called") + } + return m.InstantiateContractCostFn(pinned, msgLen) +} + +func (m MockGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas { + if m.ReplyCostFn == nil { + panic("not expected to be called") + } + return m.ReplyCostFn(pinned, reply) +} + +func (m MockGasRegister) EventCosts(evts []wasmvmtypes.EventAttribute) sdk.Gas { + if m.EventCostsFn == nil { + panic("not expected to be called") + } + return m.EventCostsFn(evts) +} + +func (m MockGasRegister) ToWasmVMGas(source sdk.Gas) uint64 { + if m.ToWasmVMGasFn == nil { + panic("not expected to be called") + } + return m.ToWasmVMGasFn(source) +} + +func (m MockGasRegister) FromWasmVMGas(source uint64) sdk.Gas { + if m.FromWasmVMGasFn == nil { + panic("not expected to be called") + } + return m.FromWasmVMGasFn(source) +} diff --git a/x/wasm/types/events.go b/x/wasm/types/events.go index f7e051f498a..4f7e44b4d10 100644 --- a/x/wasm/types/events.go +++ b/x/wasm/types/events.go @@ -1,12 +1,13 @@ package types const ( + CustomEventType = "wasm" EventTypePinCode = "pin_code" EventTypeUnpinCode = "unpin_code" ) const ( // event attributes - AttributeKeyContract = "contract_address" - AttributeKeyCodeID = "code_id" - AttributeKeySigner = "signer" - AttributeResultDataHex = "result" + AttributeKeyContractAddr = "contract_address" + AttributeKeyCodeID = "code_id" + AttributeKeySigner = "signer" + AttributeResultDataHex = "result" ) diff --git a/x/wasm/types/types.go b/x/wasm/types/types.go index 106465fe200..bedd78f7ea6 100644 --- a/x/wasm/types/types.go +++ b/x/wasm/types/types.go @@ -297,14 +297,10 @@ func NewWasmCoins(cosmosCoins sdk.Coins) (wasmCoins []wasmvmtypes.Coin) { return wasmCoins } -const CustomEventType = "wasm" -const AttributeKeyContractAddr = "contract_address" - -// ParseEvents converts wasm LogAttributes into an sdk.Events +// ParseEvents converts wasm LogAttributes into an sdk.Events. Returns events and number of bytes for custom attributes func ParseEvents(wasmOutputAttrs []wasmvmtypes.EventAttribute, contractAddr sdk.AccAddress) sdk.Events { // we always tag with the contract address issuing this event attrs := []sdk.Attribute{sdk.NewAttribute(AttributeKeyContractAddr, contractAddr.String())} - // append attributes from wasm to the sdk.Event for _, l := range wasmOutputAttrs { // and reserve the contract_address key for our use (not contract)