diff --git a/x/wasm/README.md b/x/wasm/README.md index b30bd51..e63b60d 100644 --- a/x/wasm/README.md +++ b/x/wasm/README.md @@ -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" diff --git a/x/wasm/handler.go b/x/wasm/handler.go index c31bffa..4271bd1 100644 --- a/x/wasm/handler.go +++ b/x/wasm/handler.go @@ -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 } diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 190be39..d2fd00c 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -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 } diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 22b3096..df3bbfb 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -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 } diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go index 889ff0a..947a291 100644 --- a/x/wasm/internal/types/types.go +++ b/x/wasm/internal/types/types.go @@ -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), diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index 09dc152..efffafe 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -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)