Charge gas for custom event attributes and messages (#539)

* Charge gas for custom event attributes

* Introduce gas register for gas costs

* Review feedback

* Tests and minor updates

* Godoc
This commit is contained in:
Alexander Peters 2021-06-25 10:00:46 +02:00 committed by GitHub
parent e8797cbfcd
commit c05df881fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 854 additions and 282 deletions

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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
})
}

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)
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)
}
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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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)