Properly emit events for every sub-message dispatched by x/wasm

This commit is contained in:
Ethan Frey 2020-02-27 15:46:08 +01:00
parent 7e0d3202e6
commit 502e02429e
6 changed files with 78 additions and 24 deletions

View File

@ -19,8 +19,9 @@ lru_size = 0
A number of events are returned to allow good indexing of the transactions from smart contracts.
Every call to Init or Execute will be tagged with the info on the contract that was executed and who executed it.
It should look something like this (with different addresses). The module is always wasm.
Every call to Instantiate or Execute will be tagged with the info on the contract that was executed and who executed it.
It should look something like this (with different addresses). The module is always `wasm`, and `code_id` is only present
when Instantiating a contract, so you can subscribe to new instances, it is omitted on Execute:
```json
{
@ -78,15 +79,20 @@ This is actually not very ergonomic, as the "sender" (account that sent the fund
events, and this may cause confusion, especially if the sender moves funds to the contract and the contract to another recipient in the
same transaction.
Finally, the contract itself can emit a "custom event". One per contract, so if one contract calls a second contract, you may receive
one event for the original contract and one for the re-invoked contract. They are all tagged with the contract address that emitted this
event (which the contract cannot override). Here is an example from the escrow contract successfully releasing funds to the destination
address.
Finally, the contract itself can emit a "custom event" on Execute only (not on Init).
There is one event per contract, so if one contract calls a second contract, you may receive
one event for the original contract and one for the re-invoked contract. All attributes from the contract are passed through verbatim,
and we add a `contract_address` attribute that contains the actual contract that emitted that event.
Here is an example from the escrow contract successfully releasing funds to the destination address:
```json
{
"Type": "wasm",
"Attr": [
{
"key": "contract_address",
"value": "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"
},
{
"key": "action",
"value": "release"

View File

@ -106,6 +106,6 @@ func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Res
),
)
res.Events = append(res.Events, ctx.EventManager().Events()...)
res.Events = ctx.EventManager().Events()
return &res, nil
}

View File

@ -141,6 +141,10 @@ func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator sdk.AccAddre
}
consumeGas(ctx, res.GasUsed)
// emit all events from this contract itself
value := types.CosmosResult(*res, contractAddress)
ctx.EventManager().EmitEvents(value.Events)
err = k.dispatchMessages(ctx, contractAccount, res.Messages)
if err != nil {
return nil, err
@ -180,12 +184,18 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
}
consumeGas(ctx, res.GasUsed)
// emit all events from this contract itself
value := types.CosmosResult(*res, contractAddress)
ctx.EventManager().EmitEvents(value.Events)
value.Events = nil
// TODO: capture events here as well
err = k.dispatchMessages(ctx, contractAccount, res.Messages)
if err != nil {
return sdk.Result{}, err
}
return types.CosmosResult(*res), nil
return value, nil
}
// QuerySmart queries the smart contract itself.
@ -406,11 +416,17 @@ func (k Keeper) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg
if h == nil {
return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, msg.Route())
}
// TODO: use this return value somehow (log/data)
_, err := h(ctx, msg)
res, err := h(ctx, msg)
if err != nil {
return err
}
// redispatch all events, except for type sdk.EventTypeMessage (we just want our original type)
for _, evt := range res.Events {
if evt.Type != sdk.EventTypeMessage {
ctx.EventManager().EmitEvent(evt)
}
}
return nil
}

View File

@ -2,7 +2,6 @@ package keeper
import (
"encoding/json"
"fmt"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -75,7 +74,6 @@ func queryContractInfo(ctx sdk.Context, bech string, req abci.RequestQuery, keep
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
fmt.Println(string(bz))
return bz, nil
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
tmBytes "github.com/tendermint/tendermint/libs/bytes"
kv "github.com/tendermint/tendermint/libs/kv"
wasmTypes "github.com/confio/go-cosmwasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -89,23 +88,23 @@ func NewContractInfo(codeID uint64, creator sdk.AccAddress, initMsg []byte, labe
}
}
const CustomEventType = "wasm"
const AttributeKeyContractAddr = "contract_address"
// CosmosResult converts from a Wasm Result type
func CosmosResult(wasmResult wasmTypes.Result) sdk.Result {
// TODO: bring in events
func CosmosResult(wasmResult wasmTypes.Result, contractAddr sdk.AccAddress) sdk.Result {
var events []sdk.Event
if len(wasmResult.Log) > 0 {
var attrs []kv.Pair
// we always tag with the contract address issuing this event
attrs := []sdk.Attribute{sdk.NewAttribute(AttributeKeyContractAddr, contractAddr.String())}
for _, l := range wasmResult.Log {
attr := kv.Pair{
Key: []byte(l.Key),
Value: []byte(l.Value),
// 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)
}
attrs = append(attrs, attr)
}
events = sdk.Events{{
Type: "wasm",
Attributes: attrs,
}}
events = []sdk.Event{sdk.NewEvent(CustomEventType, attrs...)}
}
return sdk.Result{
Data: []byte(wasmResult.Data),

View File

@ -17,6 +17,7 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/kv"
"github.com/cosmwasm/wasmd/x/wasm/internal/keeper"
)
@ -169,6 +170,8 @@ func TestHandleInstantiate(t *testing.T) {
require.NoError(t, err)
contractAddr := sdk.AccAddress(res.Data)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
// this should be standard x/wasm init event, nothing from contract
assert.Equal(t, 0, len(res.Events), prettyEvents(res.Events))
assertCodeList(t, q, data.ctx, 1)
assertCodeBytes(t, q, data.ctx, 1, testContract)
@ -220,6 +223,8 @@ func TestHandleExecute(t *testing.T) {
require.NoError(t, err)
contractAddr := sdk.AccAddress(res.Data)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
// this should be standard x/wasm init event, plus a bank send event, with no custom contract events
assert.Equal(t, 2, len(res.Events), prettyEvents(res.Events))
// ensure bob doesn't exist
bobAcct := data.acctKeeper.GetAccount(data.ctx, bob)
@ -244,6 +249,8 @@ func TestHandleExecute(t *testing.T) {
}
res, err = h(data.ctx, execCmd)
require.NoError(t, err)
// this should be standard x/wasm init event, plus a bank send event, plus a special event from the contract
assert.Equal(t, 3, len(res.Events), prettyEvents(res.Events))
// ensure bob now exists and got both payments released
bobAcct = data.acctKeeper.GetAccount(data.ctx, bob)
@ -349,6 +356,34 @@ func TestHandleExecuteEscrow(t *testing.T) {
// })
}
type prettyEvent struct {
Type string
Attr []sdk.Attribute
}
func prettyEvents(evts sdk.Events) string {
res := make([]prettyEvent, len(evts))
for i, e := range evts {
res[i] = prettyEvent{
Type: e.Type,
Attr: prettyAttrs(e.Attributes),
}
}
bz, err := json.MarshalIndent(res, "", " ")
if err != nil {
panic(err)
}
return string(bz)
}
func prettyAttrs(attrs []kv.Pair) []sdk.Attribute {
pretty := make([]sdk.Attribute, len(attrs))
for i, a := range attrs {
pretty[i] = sdk.NewAttribute(string(a.Key), string(a.Value))
}
return pretty
}
func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum int) {
bz, sdkerr := q(ctx, []string{QueryListCode}, abci.RequestQuery{})
require.NoError(t, sdkerr)