github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/keeper/msg_dispatcher_test.go (about)

     1  package keeper
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
     9  
    10  	wasmvmtypes "github.com/CosmWasm/wasmvm/types"
    11  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/fibonacci-chain/fbc/x/wasm/keeper/wasmtesting"
    16  )
    17  
    18  func TestDispatchSubmessages(t *testing.T) {
    19  	noReplyCalled := &mockReplyer{}
    20  	var anyGasLimit uint64 = 1
    21  	specs := map[string]struct {
    22  		msgs       []wasmvmtypes.SubMsg
    23  		replyer    *mockReplyer
    24  		msgHandler *wasmtesting.MockMessageHandler
    25  		expErr     bool
    26  		expData    []byte
    27  		expCommits []bool
    28  		expEvents  sdk.Events
    29  	}{
    30  		"no reply on error without error": {
    31  			msgs:    []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyError}},
    32  			replyer: noReplyCalled,
    33  			msgHandler: &wasmtesting.MockMessageHandler{
    34  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    35  					return nil, [][]byte{[]byte("myData")}, nil
    36  				},
    37  			},
    38  			expCommits: []bool{true},
    39  		},
    40  		"no reply on success without success": {
    41  			msgs:    []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplySuccess}},
    42  			replyer: noReplyCalled,
    43  			msgHandler: &wasmtesting.MockMessageHandler{
    44  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    45  					return nil, nil, errors.New("test, ignore")
    46  				},
    47  			},
    48  			expCommits: []bool{false},
    49  			expErr:     true,
    50  		},
    51  		"reply on success - received": {
    52  			msgs: []wasmvmtypes.SubMsg{{
    53  				ReplyOn: wasmvmtypes.ReplySuccess,
    54  			}},
    55  			replyer: &mockReplyer{
    56  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
    57  					return []byte("myReplyData"), nil
    58  				},
    59  			},
    60  			msgHandler: &wasmtesting.MockMessageHandler{
    61  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    62  					return nil, [][]byte{[]byte("myData")}, nil
    63  				},
    64  			},
    65  			expData:    []byte("myReplyData"),
    66  			expCommits: []bool{true},
    67  		},
    68  		"reply on error - handled": {
    69  			msgs: []wasmvmtypes.SubMsg{{
    70  				ReplyOn: wasmvmtypes.ReplyError,
    71  			}},
    72  			replyer: &mockReplyer{
    73  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
    74  					return []byte("myReplyData"), nil
    75  				},
    76  			},
    77  			msgHandler: &wasmtesting.MockMessageHandler{
    78  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    79  					return nil, nil, errors.New("my error")
    80  				},
    81  			},
    82  			expData:    []byte("myReplyData"),
    83  			expCommits: []bool{false},
    84  		},
    85  		"with reply events": {
    86  			msgs: []wasmvmtypes.SubMsg{{
    87  				ReplyOn: wasmvmtypes.ReplySuccess,
    88  			}},
    89  			replyer: &mockReplyer{
    90  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
    91  					ctx.EventManager().EmitEvent(sdk.NewEvent("wasm-reply"))
    92  					return []byte("myReplyData"), nil
    93  				},
    94  			},
    95  			msgHandler: &wasmtesting.MockMessageHandler{
    96  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    97  					myEvents := []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}
    98  					return myEvents, [][]byte{[]byte("myData")}, nil
    99  				},
   100  			},
   101  			expData:    []byte("myReplyData"),
   102  			expCommits: []bool{true},
   103  			expEvents: []sdk.Event{
   104  				sdk.NewEvent(
   105  					"myEvent",
   106  					sdk.NewAttribute("foo", "bar")),
   107  				sdk.NewEvent("wasm-reply"),
   108  			},
   109  		},
   110  		"with context events - released on commit": {
   111  			msgs: []wasmvmtypes.SubMsg{{
   112  				ReplyOn: wasmvmtypes.ReplyNever,
   113  			}},
   114  			replyer: &mockReplyer{},
   115  			msgHandler: &wasmtesting.MockMessageHandler{
   116  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   117  					myEvents := []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}
   118  					ctx.EventManager().EmitEvents(myEvents)
   119  					return nil, nil, nil
   120  				},
   121  			},
   122  			expCommits: []bool{true},
   123  			expEvents:  []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))},
   124  		},
   125  		"with context events - discarded on failure": {
   126  			msgs: []wasmvmtypes.SubMsg{{
   127  				ReplyOn: wasmvmtypes.ReplyNever,
   128  			}},
   129  			replyer: &mockReplyer{},
   130  			msgHandler: &wasmtesting.MockMessageHandler{
   131  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   132  					myEvents := []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}
   133  					ctx.EventManager().EmitEvents(myEvents)
   134  					return nil, nil, errors.New("testing")
   135  				},
   136  			},
   137  			expCommits: []bool{false},
   138  			expErr:     true,
   139  		},
   140  		"reply returns error": {
   141  			msgs: []wasmvmtypes.SubMsg{{
   142  				ReplyOn: wasmvmtypes.ReplySuccess,
   143  			}},
   144  			replyer: &mockReplyer{
   145  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   146  					return nil, errors.New("reply failed")
   147  				},
   148  			},
   149  			msgHandler: &wasmtesting.MockMessageHandler{
   150  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   151  					return nil, nil, nil
   152  				},
   153  			},
   154  			expCommits: []bool{false},
   155  			expErr:     true,
   156  		},
   157  		"with gas limit - out of gas": {
   158  			msgs: []wasmvmtypes.SubMsg{{
   159  				GasLimit: &anyGasLimit,
   160  				ReplyOn:  wasmvmtypes.ReplyError,
   161  			}},
   162  			replyer: &mockReplyer{
   163  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   164  					return []byte("myReplyData"), nil
   165  				},
   166  			},
   167  			msgHandler: &wasmtesting.MockMessageHandler{
   168  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   169  					ctx.GasMeter().ConsumeGas(sdk.Gas(101), "testing")
   170  					return nil, [][]byte{[]byte("someData")}, nil
   171  				},
   172  			},
   173  			expData:    []byte("myReplyData"),
   174  			expCommits: []bool{false},
   175  		},
   176  		"with gas limit - within limit no error": {
   177  			msgs: []wasmvmtypes.SubMsg{{
   178  				GasLimit: &anyGasLimit,
   179  				ReplyOn:  wasmvmtypes.ReplyError,
   180  			}},
   181  			replyer: &mockReplyer{},
   182  			msgHandler: &wasmtesting.MockMessageHandler{
   183  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   184  					ctx.GasMeter().ConsumeGas(sdk.Gas(1), "testing")
   185  					return nil, [][]byte{[]byte("someData")}, nil
   186  				},
   187  			},
   188  			expCommits: []bool{true},
   189  		},
   190  		"never reply - with nil response": {
   191  			msgs:    []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}},
   192  			replyer: &mockReplyer{},
   193  			msgHandler: &wasmtesting.MockMessageHandler{
   194  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   195  					return nil, [][]byte{nil}, nil
   196  				},
   197  			},
   198  			expCommits: []bool{true, true},
   199  		},
   200  		"never reply - with any non nil response": {
   201  			msgs:    []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}},
   202  			replyer: &mockReplyer{},
   203  			msgHandler: &wasmtesting.MockMessageHandler{
   204  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   205  					return nil, [][]byte{{}}, nil
   206  				},
   207  			},
   208  			expCommits: []bool{true, true},
   209  		},
   210  		"never reply - with error": {
   211  			msgs:    []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}},
   212  			replyer: &mockReplyer{},
   213  			msgHandler: &wasmtesting.MockMessageHandler{
   214  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   215  					return nil, [][]byte{{}}, errors.New("testing")
   216  				},
   217  			},
   218  			expCommits: []bool{false, false},
   219  			expErr:     true,
   220  		},
   221  		"multiple msg - last reply returned": {
   222  			msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}},
   223  			replyer: &mockReplyer{
   224  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   225  					return []byte(fmt.Sprintf("myReplyData:%d", reply.ID)), nil
   226  				},
   227  			},
   228  			msgHandler: &wasmtesting.MockMessageHandler{
   229  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   230  					return nil, nil, errors.New("my error")
   231  				},
   232  			},
   233  			expData:    []byte("myReplyData:2"),
   234  			expCommits: []bool{false, false},
   235  		},
   236  		"multiple msg - last non nil reply returned": {
   237  			msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}},
   238  			replyer: &mockReplyer{
   239  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   240  					if reply.ID == 2 {
   241  						return nil, nil
   242  					}
   243  					return []byte("myReplyData:1"), nil
   244  				},
   245  			},
   246  			msgHandler: &wasmtesting.MockMessageHandler{
   247  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   248  					return nil, nil, errors.New("my error")
   249  				},
   250  			},
   251  			expData:    []byte("myReplyData:1"),
   252  			expCommits: []bool{false, false},
   253  		},
   254  		"multiple msg - empty reply can overwrite result": {
   255  			msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}},
   256  			replyer: &mockReplyer{
   257  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   258  					if reply.ID == 2 {
   259  						return []byte{}, nil
   260  					}
   261  					return []byte("myReplyData:1"), nil
   262  				},
   263  			},
   264  			msgHandler: &wasmtesting.MockMessageHandler{
   265  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   266  					return nil, nil, errors.New("my error")
   267  				},
   268  			},
   269  			expData:    []byte{},
   270  			expCommits: []bool{false, false},
   271  		},
   272  		"message event filtered without reply": {
   273  			msgs: []wasmvmtypes.SubMsg{{
   274  				ReplyOn: wasmvmtypes.ReplyNever,
   275  			}},
   276  			replyer: &mockReplyer{
   277  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   278  					return nil, errors.New("should never be called")
   279  				},
   280  			},
   281  			msgHandler: &wasmtesting.MockMessageHandler{
   282  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   283  					myEvents := []sdk.Event{
   284  						sdk.NewEvent("message"),
   285  						sdk.NewEvent("execute", sdk.NewAttribute("foo", "bar")),
   286  					}
   287  					return myEvents, [][]byte{[]byte("myData")}, nil
   288  				},
   289  			},
   290  			expData:    nil,
   291  			expCommits: []bool{true},
   292  			expEvents:  []sdk.Event{sdk.NewEvent("execute", sdk.NewAttribute("foo", "bar"))},
   293  		},
   294  		"wasm reply gets proper events": {
   295  			// put fake wasmmsg in here to show where it comes from
   296  			msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Wasm: &wasmvmtypes.WasmMsg{}}}},
   297  			replyer: &mockReplyer{
   298  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   299  					if reply.Result.Err != "" {
   300  						return nil, errors.New(reply.Result.Err)
   301  					}
   302  					res := reply.Result.Ok
   303  
   304  					// ensure the input events are what we expect
   305  					// I didn't use require.Equal() to act more like a contract... but maybe that would be better
   306  					if len(res.Events) != 2 {
   307  						return nil, fmt.Errorf("event count: %#v", res.Events)
   308  					}
   309  					if res.Events[0].Type != "execute" {
   310  						return nil, fmt.Errorf("event0: %#v", res.Events[0])
   311  					}
   312  					if res.Events[1].Type != "wasm" {
   313  						return nil, fmt.Errorf("event1: %#v", res.Events[1])
   314  					}
   315  
   316  					// let's add a custom event here and see if it makes it out
   317  					ctx.EventManager().EmitEvent(sdk.NewEvent("wasm-reply"))
   318  
   319  					// update data from what we got in
   320  					return res.Data, nil
   321  				},
   322  			},
   323  			msgHandler: &wasmtesting.MockMessageHandler{
   324  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   325  					events = []sdk.Event{
   326  						sdk.NewEvent("message", sdk.NewAttribute("_contract_address", contractAddr.String())),
   327  						// we don't know what the contarctAddr will be so we can't use it in the final tests
   328  						sdk.NewEvent("execute", sdk.NewAttribute("_contract_address", "placeholder-random-addr")),
   329  						sdk.NewEvent("wasm", sdk.NewAttribute("random", "data")),
   330  					}
   331  					return events, [][]byte{[]byte("subData")}, nil
   332  				},
   333  			},
   334  			expData:    []byte("subData"),
   335  			expCommits: []bool{true},
   336  			expEvents: []sdk.Event{
   337  				sdk.NewEvent("execute", sdk.NewAttribute("_contract_address", "placeholder-random-addr")),
   338  				sdk.NewEvent("wasm", sdk.NewAttribute("random", "data")),
   339  				sdk.NewEvent("wasm-reply"),
   340  			},
   341  		},
   342  		"non-wasm reply events get filtered": {
   343  			// show events from a stargate message gets filtered out
   344  			msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Stargate: &wasmvmtypes.StargateMsg{}}}},
   345  			replyer: &mockReplyer{
   346  				replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   347  					if reply.Result.Err != "" {
   348  						return nil, errors.New(reply.Result.Err)
   349  					}
   350  					res := reply.Result.Ok
   351  
   352  					// ensure the input events are what we expect
   353  					// I didn't use require.Equal() to act more like a contract... but maybe that would be better
   354  					if len(res.Events) != 0 {
   355  						return nil, errors.New("events not filtered out")
   356  					}
   357  
   358  					// let's add a custom event here and see if it makes it out
   359  					ctx.EventManager().EmitEvent(sdk.NewEvent("stargate-reply"))
   360  
   361  					// update data from what we got in
   362  					return res.Data, nil
   363  				},
   364  			},
   365  			msgHandler: &wasmtesting.MockMessageHandler{
   366  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
   367  					events = []sdk.Event{
   368  						// this is filtered out
   369  						sdk.NewEvent("message", sdk.NewAttribute("stargate", "something-something")),
   370  						// we still emit this to the client, but not the contract
   371  						sdk.NewEvent("non-determinstic"),
   372  					}
   373  					return events, [][]byte{[]byte("subData")}, nil
   374  				},
   375  			},
   376  			expData:    []byte("subData"),
   377  			expCommits: []bool{true},
   378  			expEvents: []sdk.Event{
   379  				sdk.NewEvent("non-determinstic"),
   380  				// the event from reply is also exposed
   381  				sdk.NewEvent("stargate-reply"),
   382  			},
   383  		},
   384  	}
   385  	for name, spec := range specs {
   386  		t.Run(name, func(t *testing.T) {
   387  			var mockStore wasmtesting.MockCommitMultiStore
   388  			em := sdk.NewEventManager()
   389  			ctx := sdk.Context{}
   390  			ctx.SetMultiStore(&mockStore)
   391  			ctx.SetGasMeter(sdk.NewGasMeter(100))
   392  			ctx.SetEventManager(em)
   393  			ctx.SetLogger(log.TestingLogger())
   394  			d := NewMessageDispatcher(spec.msgHandler, spec.replyer)
   395  			gotData, gotErr := d.DispatchSubmessages(ctx, RandomAccountAddress(t), "any_port", spec.msgs)
   396  			if spec.expErr {
   397  				require.Error(t, gotErr)
   398  				assert.Empty(t, em.Events())
   399  				return
   400  			} else {
   401  				require.NoError(t, gotErr)
   402  				assert.Equal(t, spec.expData, gotData)
   403  			}
   404  			assert.Equal(t, spec.expCommits, mockStore.Committed)
   405  			if len(spec.expEvents) == 0 {
   406  				assert.Empty(t, em.Events())
   407  			} else {
   408  				assert.Equal(t, spec.expEvents, em.Events())
   409  			}
   410  		})
   411  	}
   412  }
   413  
   414  type mockReplyer struct {
   415  	replyFn func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error)
   416  }
   417  
   418  func (m mockReplyer) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
   419  	if m.replyFn == nil {
   420  		panic("not expected to be called")
   421  	}
   422  	return m.replyFn(ctx, contractAddress, reply)
   423  }