Skip to content

Commit

Permalink
Test events
Browse files Browse the repository at this point in the history
  • Loading branch information
alpe committed Jul 5, 2021
1 parent be0380b commit 4b5b78e
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 53 deletions.
3 changes: 1 addition & 2 deletions x/wasm/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const (
MaxLabelSize = types.MaxLabelSize
BuildTagRegexp = types.BuildTagRegexp
MaxBuildTagSize = types.MaxBuildTagSize
CustomEventType = types.CustomEventType
CustomEventType = types.WasmModuleEventType
AttributeKeyContractAddr = types.AttributeKeyContractAddr
ProposalTypeStoreCode = types.ProposalTypeStoreCode
ProposalTypeInstantiateContract = types.ProposalTypeInstantiateContract
Expand Down Expand Up @@ -53,7 +53,6 @@ var (
NewContractInfo = types.NewContractInfo
NewEnv = types.NewEnv
NewWasmCoins = types.NewWasmCoins
ParseEvents = types.ParseEvents
DefaultWasmConfig = types.DefaultWasmConfig
DefaultParams = types.DefaultParams
InitGenesis = keeper.InitGenesis
Expand Down
59 changes: 59 additions & 0 deletions x/wasm/keeper/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package keeper

import (
"fmt"
"github.com/CosmWasm/wasmd/x/wasm/types"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// newWasmModuleEvent creates with wasm module event for interacting with the given contract. Adds custom attributes
// to this event.
func newWasmModuleEvent(customAttributes []wasmvmtypes.EventAttribute, contractAddr sdk.AccAddress) sdk.Events {
attrs := contractSDKEventAttributes(customAttributes, contractAddr)

// each wasm invocation always returns one sdk.Event
return sdk.Events{sdk.NewEvent(types.WasmModuleEventType, attrs...)}
}

// returns true when a wasm module event was emitted for this contract already
func hasWasmModuleEvent(ctx sdk.Context, contractAddr sdk.AccAddress) bool {
for _, e := range ctx.EventManager().Events() {
if e.Type == types.WasmModuleEventType {
for _, a := range e.Attributes {
if string(a.Key) == types.AttributeKeyContractAddr && string(a.Value) == contractAddr.String() {
return true
}
}
}
}
return false
}

const eventTypeMinLength = 2

// newCustomEvents converts wasmvm events from a contract response to sdk type events
func newCustomEvents(evts wasmvmtypes.Events, contractAddr sdk.AccAddress) sdk.Events {
events := make(sdk.Events, 0, len(evts))
for _, e := range evts {
if len(e.Type) <= eventTypeMinLength {
continue
}
attributes := contractSDKEventAttributes(e.Attributes, contractAddr)
events = append(events, sdk.NewEvent(fmt.Sprintf("%s%s", types.CustomContractEventPrefix, e.Type), attributes...))
}
return events
}

// convert and add contract address issuing this event
func contractSDKEventAttributes(customAttributes []wasmvmtypes.EventAttribute, contractAddr sdk.AccAddress) []sdk.Attribute {
attrs := []sdk.Attribute{sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String())}
// append attributes from wasm to the sdk.Event
for _, l := range customAttributes {
// and reserve the contract_address key for our use (not contract)
if l.Key != types.AttributeKeyContractAddr {
attrs = append(attrs, sdk.NewAttribute(l.Key, l.Value))
}
}
return attrs
}
160 changes: 160 additions & 0 deletions x/wasm/keeper/events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package keeper

import (
"context"
"github.com/CosmWasm/wasmd/x/wasm/types"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"testing"
)

func TestHasWasmModuleEvent(t *testing.T) {
myContractAddr := RandomAccountAddress(t)
specs := map[string]struct {
srcEvents []sdk.Event
exp bool
}{
"event found": {
srcEvents: []sdk.Event{
sdk.NewEvent(types.WasmModuleEventType, sdk.NewAttribute("contract_address", myContractAddr.String())),
},
exp: true,
},
"different event: not found": {
srcEvents: []sdk.Event{
sdk.NewEvent(types.CustomContractEventPrefix, sdk.NewAttribute("contract_address", myContractAddr.String())),
},
exp: false,
},
"event with different address: not found": {
srcEvents: []sdk.Event{
sdk.NewEvent(types.WasmModuleEventType, sdk.NewAttribute("contract_address", RandomBech32AccountAddress(t))),
},
exp: false,
},
"no event": {
srcEvents: []sdk.Event{},
exp: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
em := sdk.NewEventManager()
em.EmitEvents(spec.srcEvents)
ctx := sdk.Context{}.WithContext(context.Background()).WithEventManager(em)

got := hasWasmModuleEvent(ctx, myContractAddr)
assert.Equal(t, spec.exp, got)
})
}
}

func TestNewCustomEvents(t *testing.T) {
myContract := RandomAccountAddress(t)
specs := map[string]struct {
src wasmvmtypes.Events
exp sdk.Events
}{
"all good": {
src: wasmvmtypes.Events{{
Type: "foo",
Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}},
}},
exp: sdk.Events{sdk.NewEvent("wasm-foo",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("myKey", "myVal"))},
},
"multiple attributes": {
src: wasmvmtypes.Events{{
Type: "foo",
Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"},
{Key: "myOtherKey", Value: "myOtherVal"}},
}},
exp: sdk.Events{sdk.NewEvent("wasm-foo",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("myKey", "myVal"),
sdk.NewAttribute("myOtherKey", "myOtherVal"))},
},
"multiple events": {
src: wasmvmtypes.Events{{
Type: "foo",
Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}},
}, {
Type: "bar",
Attributes: []wasmvmtypes.EventAttribute{{Key: "otherKey", Value: "otherVal"}},
}},
exp: sdk.Events{sdk.NewEvent("wasm-foo",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("myKey", "myVal")),
sdk.NewEvent("wasm-bar",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("otherKey", "otherVal"))},
},
"without attributes": {
src: wasmvmtypes.Events{{
Type: "foo",
}},
exp: sdk.Events{sdk.NewEvent("wasm-foo",
sdk.NewAttribute("contract_address", myContract.String()))},
},
"min length not reached": {
src: wasmvmtypes.Events{{
Type: "f",
}},
exp: sdk.Events{},
},
"overwrite contract_address": {
src: wasmvmtypes.Events{{
Type: "foo",
Attributes: []wasmvmtypes.EventAttribute{{Key: "contract_address", Value: RandomBech32AccountAddress(t)}},
}},
exp: sdk.Events{sdk.NewEvent("wasm-foo",
sdk.NewAttribute("contract_address", myContract.String()))},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
gotEvent := newCustomEvents(spec.src, myContract)
assert.Equal(t, spec.exp, gotEvent)
})
}
}

func TestNewWasmModuleEvent(t *testing.T) {
myContract := RandomAccountAddress(t)
specs := map[string]struct {
src []wasmvmtypes.EventAttribute
exp sdk.Events
}{
"all good": {
src: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}},
exp: sdk.Events{sdk.NewEvent("wasm",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("myKey", "myVal"))},
},
"multiple attributes": {
src: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"},
{Key: "myOtherKey", Value: "myOtherVal"}},
exp: sdk.Events{sdk.NewEvent("wasm",
sdk.NewAttribute("contract_address", myContract.String()),
sdk.NewAttribute("myKey", "myVal"),
sdk.NewAttribute("myOtherKey", "myOtherVal"))},
},
"without attributes": {
exp: sdk.Events{sdk.NewEvent("wasm",
sdk.NewAttribute("contract_address", myContract.String()))},
},
"overwrite contract_address": {
src: []wasmvmtypes.EventAttribute{{Key: "contract_address", Value: RandomBech32AccountAddress(t)}},
exp: sdk.Events{sdk.NewEvent("wasm",
sdk.NewAttribute("contract_address", myContract.String()))},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
gotEvent := newWasmModuleEvent(spec.src, myContract)
assert.Equal(t, spec.exp, gotEvent)
})
}
}
34 changes: 3 additions & 31 deletions x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,43 +770,15 @@ func (k *Keeper) handleContractResponse(
attributeGasCost := k.gasRegister.EventCosts(attrs, evts)
ctx.GasMeter().ConsumeGas(attributeGasCost, "Custom contract event attributes")
// emit all events from this contract itself
if len(attrs) != 0 || !hasContractEvent(ctx, contractAddr) { // prevent duplicate event on reply
events := types.ParseEvents(attrs, contractAddr)
ctx.EventManager().EmitEvents(events)
if len(attrs) != 0 || !hasWasmModuleEvent(ctx, contractAddr) { // prevent duplicate event on reply
ctx.EventManager().EmitEvents(newWasmModuleEvent(attrs, contractAddr))
}
if len(evts) > 0 {
const eventTypeMinLength = 3
const eventPrefix = "wasm-"
events := make(sdk.Events, len(evts))
for i, e := range evts {
if len(e.Type) < eventTypeMinLength {
continue
}
attributes := make([]sdk.Attribute, len(e.Attributes))
for j, a := range e.Attributes {
attributes[j] = sdk.NewAttribute(a.Key, a.Value)
}
events[i] = sdk.NewEvent(fmt.Sprintf("%s%s", eventPrefix, e.Type), attributes...)
}
// TODO (reviewer): Custom events do not contain any trustworthy id. Should we add the contract as origin?
ctx.EventManager().EmitEvents(events)
ctx.EventManager().EmitEvents(newCustomEvents(evts, contractAddr))
}
return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, msgs, data)
}

func hasContractEvent(ctx sdk.Context, contractAddr sdk.AccAddress) bool {
for _, e := range ctx.EventManager().Events() {
if e.Type == types.CustomEventType {
for _, a := range e.Attributes {
if string(a.Key) == types.AttributeKeyContractAddr && string(a.Value) == contractAddr.String() {
return true
}
}
}
}
return false
}

func (k Keeper) runtimeGasForContract(ctx sdk.Context) uint64 {
meter := ctx.GasMeter()
if meter.IsOutOfGas() {
Expand Down
9 changes: 6 additions & 3 deletions x/wasm/types/events.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package types

const (
CustomEventType = "wasm"
EventTypePinCode = "pin_code"
EventTypeUnpinCode = "unpin_code"
// WasmModuleEventType is stored with any contract TX
WasmModuleEventType = "wasm"
// CustomContractEventPrefix contracts can create custom events. To not mix them with other system events they got the `wasm-` prefix.
CustomContractEventPrefix = "wasm-"
EventTypePinCode = "pin_code"
EventTypeUnpinCode = "unpin_code"
)
const ( // event attributes
AttributeKeyContractAddr = "contract_address"
Expand Down
17 changes: 0 additions & 17 deletions x/wasm/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,23 +297,6 @@ func NewWasmCoins(cosmosCoins sdk.Coins) (wasmCoins []wasmvmtypes.Coin) {
return wasmCoins
}

// 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)
if l.Key != AttributeKeyContractAddr {
attr := sdk.NewAttribute(l.Key, l.Value)
attrs = append(attrs, attr)
}
}

// each wasm invokation always returns one sdk.Event
return sdk.Events{sdk.NewEvent(CustomEventType, attrs...)}
}

// WasmConfig is the extra config required for wasm
type WasmConfig struct {
SmartQueryGasLimit uint64
Expand Down

0 comments on commit 4b5b78e

Please sign in to comment.