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

     1  package keeper
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"strconv"
     8  	"testing"
     9  
    10  	ibcadapter "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/ibc-adapter"
    11  	"github.com/fibonacci-chain/fbc/x/wasm/keeper/testdata"
    12  
    13  	"github.com/fibonacci-chain/fbc/x/wasm/types"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  
    17  	wasmvmtypes "github.com/CosmWasm/wasmvm/types"
    18  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  // test handing of submessages, very closely related to the reflect_test
    23  
    24  // Try a simple send, no gas limit to for a sanity check before trying table tests
    25  func TestDispatchSubMsgSuccessCase(t *testing.T) {
    26  	ctx, keepers := CreateTestInput(t, false, ReflectFeatures)
    27  	accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
    28  
    29  	deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
    30  	contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
    31  
    32  	creator := keepers.Faucet.NewFundedAccount(ctx, deposit...)
    33  	creatorBalance := deposit.Sub(contractStart)
    34  	_, _, fred := keyPubAddr()
    35  
    36  	// upload code
    37  	codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil)
    38  	require.NoError(t, err)
    39  	require.Equal(t, uint64(1), codeID)
    40  
    41  	// creator instantiates a contract and gives it tokens
    42  	contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
    43  	require.NoError(t, err)
    44  	require.NotEmpty(t, contractAddr)
    45  
    46  	// check some account values
    47  	checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart)
    48  	checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance)
    49  	checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil)
    50  
    51  	// creator can send contract's tokens to fred (using SendMsg)
    52  	msg := wasmvmtypes.CosmosMsg{
    53  		Bank: &wasmvmtypes.BankMsg{
    54  			Send: &wasmvmtypes.SendMsg{
    55  				ToAddress: fred.String(),
    56  				Amount: []wasmvmtypes.Coin{{
    57  					Denom:  "denom",
    58  					Amount: "15000000000000000000000",
    59  				}},
    60  			},
    61  		},
    62  	}
    63  	reflectSend := testdata.ReflectHandleMsg{
    64  		ReflectSubMsg: &testdata.ReflectSubPayload{
    65  			Msgs: []wasmvmtypes.SubMsg{{
    66  				ID:      7,
    67  				Msg:     msg,
    68  				ReplyOn: wasmvmtypes.ReplyAlways,
    69  			}},
    70  		},
    71  	}
    72  	reflectSendBz, err := json.Marshal(reflectSend)
    73  	require.NoError(t, err)
    74  	_, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil)
    75  	require.NoError(t, err)
    76  
    77  	// fred got coins
    78  	checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000)))
    79  	// contract lost them
    80  	checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000)))
    81  	checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance)
    82  
    83  	// query the reflect state to ensure the result was stored
    84  	query := testdata.ReflectQueryMsg{
    85  		SubMsgResult: &testdata.SubCall{ID: 7},
    86  	}
    87  	queryBz, err := json.Marshal(query)
    88  	require.NoError(t, err)
    89  	queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
    90  	require.NoError(t, err)
    91  
    92  	var res wasmvmtypes.Reply
    93  	err = json.Unmarshal(queryRes, &res)
    94  	require.NoError(t, err)
    95  	assert.Equal(t, uint64(7), res.ID)
    96  	assert.Empty(t, res.Result.Err)
    97  	require.NotNil(t, res.Result.Ok)
    98  	sub := res.Result.Ok
    99  	// as of v0.28.0 we strip out all events that don't come from wasm contracts. can't trust the sdk.
   100  	require.Len(t, sub.Events, 0)
   101  }
   102  
   103  func TestDispatchSubMsgErrorHandling(t *testing.T) {
   104  	fundedDenom := "funds"
   105  	fundedAmount := 1_000_000
   106  	ctxGasLimit := uint64(1_000_000)
   107  	subGasLimit := uint64(300_000)
   108  
   109  	// prep - create one chain and upload the code
   110  	ctx, keepers := CreateTestInput(t, false, ReflectFeatures)
   111  	ctx.SetGasMeter(sdk.NewInfiniteGasMeter())
   112  	ctx.SetBlockGasMeter(sdk.NewInfiniteGasMeter())
   113  	keeper := keepers.WasmKeeper
   114  	contractStart := sdk.NewCoins(sdk.NewInt64Coin(fundedDenom, int64(fundedAmount)))
   115  	uploader := keepers.Faucet.NewFundedAccount(ctx, contractStart.Add(contractStart...)...)
   116  
   117  	// upload code
   118  	reflectID, err := keepers.ContractKeeper.Create(ctx, uploader, testdata.ReflectContractWasm(), nil)
   119  	require.NoError(t, err)
   120  
   121  	// create hackatom contract for testing (for infinite loop)
   122  	hackatomCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
   123  	require.NoError(t, err)
   124  	hackatomID, err := keepers.ContractKeeper.Create(ctx, uploader, hackatomCode, nil)
   125  	require.NoError(t, err)
   126  	_, _, bob := keyPubAddr()
   127  	_, _, fred := keyPubAddr()
   128  	initMsg := HackatomExampleInitMsg{
   129  		Verifier:    fred,
   130  		Beneficiary: bob,
   131  	}
   132  	initMsgBz, err := json.Marshal(initMsg)
   133  	require.NoError(t, err)
   134  	hackatomAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, hackatomID, uploader, nil, initMsgBz, "hackatom demo", contractStart)
   135  	require.NoError(t, err)
   136  
   137  	validBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
   138  		return wasmvmtypes.CosmosMsg{
   139  			Bank: &wasmvmtypes.BankMsg{
   140  				Send: &wasmvmtypes.SendMsg{
   141  					ToAddress: emptyAccount,
   142  					Amount: []wasmvmtypes.Coin{{
   143  						Denom:  fundedDenom,
   144  						Amount: strconv.Itoa(fundedAmount/2) + "000000000000000000",
   145  					}},
   146  				},
   147  			},
   148  		}
   149  	}
   150  
   151  	invalidBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
   152  		return wasmvmtypes.CosmosMsg{
   153  			Bank: &wasmvmtypes.BankMsg{
   154  				Send: &wasmvmtypes.SendMsg{
   155  					ToAddress: emptyAccount,
   156  					Amount: []wasmvmtypes.Coin{{
   157  						Denom:  fundedDenom,
   158  						Amount: strconv.Itoa(fundedAmount*2) + "000000000000000000",
   159  					}},
   160  				},
   161  			},
   162  		}
   163  	}
   164  
   165  	infiniteLoop := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
   166  		return wasmvmtypes.CosmosMsg{
   167  			Wasm: &wasmvmtypes.WasmMsg{
   168  				Execute: &wasmvmtypes.ExecuteMsg{
   169  					ContractAddr: hackatomAddr.String(),
   170  					Msg:          []byte(`{"cpu_loop":{}}`),
   171  				},
   172  			},
   173  		}
   174  	}
   175  
   176  	instantiateContract := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
   177  		return wasmvmtypes.CosmosMsg{
   178  			Wasm: &wasmvmtypes.WasmMsg{
   179  				Instantiate: &wasmvmtypes.InstantiateMsg{
   180  					CodeID: reflectID,
   181  					Msg:    []byte("{}"),
   182  					Label:  "subcall reflect",
   183  				},
   184  			},
   185  		}
   186  	}
   187  
   188  	type assertion func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult)
   189  
   190  	assertReturnedEvents := func(expectedEvents int) assertion {
   191  		return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) {
   192  			require.Len(t, response.Ok.Events, expectedEvents)
   193  		}
   194  	}
   195  
   196  	assertGasUsed := func(minGas, maxGas uint64) assertion {
   197  		return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) {
   198  			gasUsed := ctx.GasMeter().GasConsumed()
   199  			assert.True(t, gasUsed >= minGas, "Used %d gas (less than expected %d)", gasUsed, minGas)
   200  			assert.True(t, gasUsed <= maxGas, "Used %d gas (more than expected %d)", gasUsed, maxGas)
   201  		}
   202  	}
   203  
   204  	assertErrorString := func(shouldContain string) assertion {
   205  		return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) {
   206  			assert.Contains(t, response.Err, shouldContain)
   207  		}
   208  	}
   209  
   210  	assertGotContractAddr := func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) {
   211  		// should get the events emitted on new contract
   212  		event := response.Ok.Events[0]
   213  		require.Equal(t, event.Type, "instantiate")
   214  		assert.Equal(t, event.Attributes[0].Key, "_contract_address")
   215  		eventAddr := event.Attributes[0].Value
   216  		assert.NotEqual(t, contract, eventAddr)
   217  
   218  		var res types.MsgInstantiateContractResponse
   219  		keepers.EncodingConfig.Marshaler.GetProtocMarshal().MustUnmarshal(response.Ok.Data, &res)
   220  		assert.Equal(t, eventAddr, res.Address)
   221  	}
   222  
   223  	cases := map[string]struct {
   224  		submsgID uint64
   225  		// we will generate message from the
   226  		msg      func(contract, emptyAccount string) wasmvmtypes.CosmosMsg
   227  		gasLimit *uint64
   228  
   229  		// true if we expect this to throw out of gas panic
   230  		isOutOfGasPanic bool
   231  		// true if we expect this execute to return an error (can be false when submessage errors)
   232  		executeError bool
   233  		// true if we expect submessage to return an error (but execute to return success)
   234  		subMsgError bool
   235  		// make assertions after dispatch
   236  		resultAssertions []assertion
   237  	}{
   238  		"send tokens": {
   239  			submsgID:         5,
   240  			msg:              validBankSend,
   241  			resultAssertions: []assertion{assertReturnedEvents(0), assertGasUsed(90000, 96000)},
   242  		},
   243  		"not enough tokens": {
   244  			submsgID:    6,
   245  			msg:         invalidBankSend,
   246  			subMsgError: true,
   247  			// uses less gas than the send tokens (cost of bank transfer)
   248  			resultAssertions: []assertion{assertGasUsed(73000, 79000), assertErrorString("codespace: sdk, code: 5")},
   249  		},
   250  		"out of gas panic with no gas limit": {
   251  			submsgID:        7,
   252  			msg:             infiniteLoop,
   253  			isOutOfGasPanic: true,
   254  		},
   255  
   256  		"send tokens with limit": {
   257  			submsgID: 15,
   258  			msg:      validBankSend,
   259  			gasLimit: &subGasLimit,
   260  			// uses same gas as call without limit (note we do not charge the 40k on reply)
   261  			resultAssertions: []assertion{assertReturnedEvents(0), assertGasUsed(90000, 96000)},
   262  		},
   263  		"not enough tokens with limit": {
   264  			submsgID:    16,
   265  			msg:         invalidBankSend,
   266  			subMsgError: true,
   267  			gasLimit:    &subGasLimit,
   268  			// uses same gas as call without limit (note we do not charge the 40k on reply)
   269  			resultAssertions: []assertion{assertGasUsed(73000, 79000), assertErrorString("codespace: sdk, code: 5")},
   270  		},
   271  		"out of gas caught with gas limit": {
   272  			submsgID:    17,
   273  			msg:         infiniteLoop,
   274  			subMsgError: true,
   275  			gasLimit:    &subGasLimit,
   276  			// uses all the subGasLimit, plus the 52k or so for the main contract
   277  			resultAssertions: []assertion{assertGasUsed(subGasLimit+71000, subGasLimit+77000), assertErrorString("codespace: sdk, code: 11")},
   278  		},
   279  		"instantiate contract gets address in data and events": {
   280  			submsgID:         21,
   281  			msg:              instantiateContract,
   282  			resultAssertions: []assertion{assertReturnedEvents(1), assertGotContractAddr},
   283  		},
   284  	}
   285  	for name, tc := range cases {
   286  		t.Run(name, func(t *testing.T) {
   287  			creator := keepers.Faucet.NewFundedAccount(ctx, contractStart...)
   288  			_, _, empty := keyPubAddr()
   289  
   290  			contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), fmt.Sprintf("contract %s", name), contractStart)
   291  			require.NoError(t, err)
   292  
   293  			msg := tc.msg(contractAddr.String(), empty.String())
   294  			reflectSend := testdata.ReflectHandleMsg{
   295  				ReflectSubMsg: &testdata.ReflectSubPayload{
   296  					Msgs: []wasmvmtypes.SubMsg{{
   297  						ID:       tc.submsgID,
   298  						Msg:      msg,
   299  						GasLimit: tc.gasLimit,
   300  						ReplyOn:  wasmvmtypes.ReplyAlways,
   301  					}},
   302  				},
   303  			}
   304  			reflectSendBz, err := json.Marshal(reflectSend)
   305  			require.NoError(t, err)
   306  
   307  			execCtx := ctx
   308  			execCtx.SetGasMeter(sdk.NewGasMeter(ctxGasLimit))
   309  			defer func() {
   310  				if tc.isOutOfGasPanic {
   311  					r := recover()
   312  					require.NotNil(t, r, "expected panic")
   313  					if _, ok := r.(sdk.ErrorOutOfGas); !ok {
   314  						t.Fatalf("Expected OutOfGas panic, got: %#v\n", r)
   315  					}
   316  				}
   317  			}()
   318  			_, err = keepers.ContractKeeper.Execute(execCtx, contractAddr, creator, reflectSendBz, nil)
   319  
   320  			if tc.executeError {
   321  				require.Error(t, err)
   322  			} else {
   323  				require.NoError(t, err)
   324  
   325  				// query the reply
   326  				query := testdata.ReflectQueryMsg{
   327  					SubMsgResult: &testdata.SubCall{ID: tc.submsgID},
   328  				}
   329  				queryBz, err := json.Marshal(query)
   330  				require.NoError(t, err)
   331  				queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
   332  				require.NoError(t, err)
   333  				var res wasmvmtypes.Reply
   334  				err = json.Unmarshal(queryRes, &res)
   335  				require.NoError(t, err)
   336  				assert.Equal(t, tc.submsgID, res.ID)
   337  
   338  				if tc.subMsgError {
   339  					require.NotEmpty(t, res.Result.Err)
   340  					require.Nil(t, res.Result.Ok)
   341  				} else {
   342  					require.Empty(t, res.Result.Err)
   343  					require.NotNil(t, res.Result.Ok)
   344  				}
   345  
   346  				for _, assertion := range tc.resultAssertions {
   347  					assertion(t, execCtx, contractAddr.String(), empty.String(), res.Result)
   348  				}
   349  
   350  			}
   351  		})
   352  	}
   353  }
   354  
   355  // Test an error case, where the Encoded doesn't return any sdk.Msg and we trigger(ed) a null pointer exception.
   356  // This occurs with the IBC encoder. Test this.
   357  func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) {
   358  	// fake out the bank handle to return success with no data
   359  	nilEncoder := func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]ibcadapter.Msg, error) {
   360  		return nil, nil
   361  	}
   362  	customEncoders := &MessageEncoders{
   363  		Bank: nilEncoder,
   364  	}
   365  
   366  	ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageHandler(NewSDKMessageHandler(nil, customEncoders)))
   367  	keeper := keepers.WasmKeeper
   368  
   369  	deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
   370  	contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
   371  
   372  	creator := keepers.Faucet.NewFundedAccount(ctx, deposit...)
   373  	_, _, fred := keyPubAddr()
   374  
   375  	// upload code
   376  	codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil)
   377  	require.NoError(t, err)
   378  
   379  	// creator instantiates a contract and gives it tokens
   380  	contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
   381  	require.NoError(t, err)
   382  	require.NotEmpty(t, contractAddr)
   383  
   384  	// creator can send contract's tokens to fred (using SendMsg)
   385  	msg := wasmvmtypes.CosmosMsg{
   386  		Bank: &wasmvmtypes.BankMsg{
   387  			Send: &wasmvmtypes.SendMsg{
   388  				ToAddress: fred.String(),
   389  				Amount: []wasmvmtypes.Coin{{
   390  					Denom:  "denom",
   391  					Amount: "15000000000000000000000",
   392  				}},
   393  			},
   394  		},
   395  	}
   396  	reflectSend := testdata.ReflectHandleMsg{
   397  		ReflectSubMsg: &testdata.ReflectSubPayload{
   398  			Msgs: []wasmvmtypes.SubMsg{{
   399  				ID:      7,
   400  				Msg:     msg,
   401  				ReplyOn: wasmvmtypes.ReplyAlways,
   402  			}},
   403  		},
   404  	}
   405  	reflectSendBz, err := json.Marshal(reflectSend)
   406  	require.NoError(t, err)
   407  	_, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil)
   408  	require.NoError(t, err)
   409  
   410  	// query the reflect state to ensure the result was stored
   411  	query := testdata.ReflectQueryMsg{
   412  		SubMsgResult: &testdata.SubCall{ID: 7},
   413  	}
   414  	queryBz, err := json.Marshal(query)
   415  	require.NoError(t, err)
   416  	queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
   417  	require.NoError(t, err)
   418  
   419  	var res wasmvmtypes.Reply
   420  	err = json.Unmarshal(queryRes, &res)
   421  	require.NoError(t, err)
   422  	assert.Equal(t, uint64(7), res.ID)
   423  	assert.Empty(t, res.Result.Err)
   424  	require.NotNil(t, res.Result.Ok)
   425  	sub := res.Result.Ok
   426  	assert.Empty(t, sub.Data)
   427  	require.Len(t, sub.Events, 0)
   428  }
   429  
   430  // Try a simple send, no gas limit to for a sanity check before trying table tests
   431  func TestDispatchSubMsgConditionalReplyOn(t *testing.T) {
   432  	ctx, keepers := CreateTestInput(t, false, ReflectFeatures)
   433  	keeper := keepers.WasmKeeper
   434  
   435  	deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
   436  	contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
   437  
   438  	creator := keepers.Faucet.NewFundedAccount(ctx, deposit...)
   439  	_, _, fred := keyPubAddr()
   440  
   441  	// upload code
   442  	codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil)
   443  	require.NoError(t, err)
   444  
   445  	// creator instantiates a contract and gives it tokens
   446  	contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
   447  	require.NoError(t, err)
   448  
   449  	goodSend := wasmvmtypes.CosmosMsg{
   450  		Bank: &wasmvmtypes.BankMsg{
   451  			Send: &wasmvmtypes.SendMsg{
   452  				ToAddress: fred.String(),
   453  				Amount: []wasmvmtypes.Coin{{
   454  					Denom:  "denom",
   455  					Amount: "1000",
   456  				}},
   457  			},
   458  		},
   459  	}
   460  	failSend := wasmvmtypes.CosmosMsg{
   461  		Bank: &wasmvmtypes.BankMsg{
   462  			Send: &wasmvmtypes.SendMsg{
   463  				ToAddress: fred.String(),
   464  				Amount: []wasmvmtypes.Coin{{
   465  					Denom:  "no-such-token",
   466  					Amount: "777777",
   467  				}},
   468  			},
   469  		},
   470  	}
   471  
   472  	cases := map[string]struct {
   473  		// true for wasmvmtypes.ReplySuccess, false for wasmvmtypes.ReplyError
   474  		replyOnSuccess bool
   475  		msg            wasmvmtypes.CosmosMsg
   476  		// true if the call should return an error (it wasn't handled)
   477  		expectError bool
   478  		// true if the reflect contract wrote the response (success or error) - it was captured
   479  		writeResult bool
   480  	}{
   481  		"all good, reply success": {
   482  			replyOnSuccess: true,
   483  			msg:            goodSend,
   484  			expectError:    false,
   485  			writeResult:    true,
   486  		},
   487  		"all good, reply error": {
   488  			replyOnSuccess: false,
   489  			msg:            goodSend,
   490  			expectError:    false,
   491  			writeResult:    false,
   492  		},
   493  		"bad msg, reply success": {
   494  			replyOnSuccess: true,
   495  			msg:            failSend,
   496  			expectError:    true,
   497  			writeResult:    false,
   498  		},
   499  		"bad msg, reply error": {
   500  			replyOnSuccess: false,
   501  			msg:            failSend,
   502  			expectError:    false,
   503  			writeResult:    true,
   504  		},
   505  	}
   506  
   507  	var id uint64 = 0
   508  	for name, tc := range cases {
   509  		id++
   510  		t.Run(name, func(t *testing.T) {
   511  			subMsg := wasmvmtypes.SubMsg{
   512  				ID:      id,
   513  				Msg:     tc.msg,
   514  				ReplyOn: wasmvmtypes.ReplySuccess,
   515  			}
   516  			if !tc.replyOnSuccess {
   517  				subMsg.ReplyOn = wasmvmtypes.ReplyError
   518  			}
   519  
   520  			reflectSend := testdata.ReflectHandleMsg{
   521  				ReflectSubMsg: &testdata.ReflectSubPayload{
   522  					Msgs: []wasmvmtypes.SubMsg{subMsg},
   523  				},
   524  			}
   525  			reflectSendBz, err := json.Marshal(reflectSend)
   526  			require.NoError(t, err)
   527  			_, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil)
   528  
   529  			if tc.expectError {
   530  				require.Error(t, err)
   531  			} else {
   532  				require.NoError(t, err)
   533  			}
   534  
   535  			// query the reflect state to check if the result was stored
   536  			query := testdata.ReflectQueryMsg{
   537  				SubMsgResult: &testdata.SubCall{ID: id},
   538  			}
   539  			queryBz, err := json.Marshal(query)
   540  			require.NoError(t, err)
   541  			queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
   542  			if tc.writeResult {
   543  				// we got some data for this call
   544  				require.NoError(t, err)
   545  				var res wasmvmtypes.Reply
   546  				err = json.Unmarshal(queryRes, &res)
   547  				require.NoError(t, err)
   548  				require.Equal(t, id, res.ID)
   549  			} else {
   550  				// nothing should be there -> error
   551  				require.Error(t, err)
   552  			}
   553  		})
   554  	}
   555  }