mirror of https://github.com/certusone/wasmd.git
Properly emit events for every sub-message dispatched by x/wasm
This commit is contained in:
parent
7e0d3202e6
commit
502e02429e
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue