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 }