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

     1  package keeper
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	ibcadapter "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/ibc-adapter"
     8  
     9  	wasmvmtypes "github.com/CosmWasm/wasmvm/types"
    10  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp"
    11  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    12  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    13  	capabilitytypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/capability/types"
    14  	clienttypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/02-client/types"
    15  	channeltypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types"
    16  	ibcexported "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/exported"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	"github.com/fibonacci-chain/fbc/x/wasm/keeper/wasmtesting"
    21  	"github.com/fibonacci-chain/fbc/x/wasm/types"
    22  )
    23  
    24  func TestMessageHandlerChainDispatch(t *testing.T) {
    25  	capturingHandler, gotMsgs := wasmtesting.NewCapturingMessageHandler()
    26  
    27  	alwaysUnknownMsgHandler := &wasmtesting.MockMessageHandler{
    28  		DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    29  			return nil, nil, types.ErrUnknownMsg
    30  		},
    31  	}
    32  
    33  	assertNotCalledHandler := &wasmtesting.MockMessageHandler{
    34  		DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    35  			t.Fatal("not expected to be called")
    36  			return
    37  		},
    38  	}
    39  
    40  	myMsg := wasmvmtypes.CosmosMsg{Custom: []byte(`{}`)}
    41  	specs := map[string]struct {
    42  		handlers  []Messenger
    43  		expErr    *sdkerrors.Error
    44  		expEvents []sdk.Event
    45  	}{
    46  		"single handler": {
    47  			handlers: []Messenger{capturingHandler},
    48  		},
    49  		"passed to next handler": {
    50  			handlers: []Messenger{alwaysUnknownMsgHandler, capturingHandler},
    51  		},
    52  		"stops iteration when handled": {
    53  			handlers: []Messenger{capturingHandler, assertNotCalledHandler},
    54  		},
    55  		"stops iteration on handler error": {
    56  			handlers: []Messenger{&wasmtesting.MockMessageHandler{
    57  				DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    58  					return nil, nil, types.ErrInvalidMsg
    59  				},
    60  			}, assertNotCalledHandler},
    61  			expErr: types.ErrInvalidMsg,
    62  		},
    63  		"return events when handle": {
    64  			handlers: []Messenger{
    65  				&wasmtesting.MockMessageHandler{
    66  					DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
    67  						_, data, _ = capturingHandler.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg)
    68  						return []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}, data, nil
    69  					},
    70  				},
    71  			},
    72  			expEvents: []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))},
    73  		},
    74  		"return error when none can handle": {
    75  			handlers: []Messenger{alwaysUnknownMsgHandler},
    76  			expErr:   types.ErrUnknownMsg,
    77  		},
    78  	}
    79  	for name, spec := range specs {
    80  		t.Run(name, func(t *testing.T) {
    81  			*gotMsgs = make([]wasmvmtypes.CosmosMsg, 0)
    82  
    83  			// when
    84  			h := MessageHandlerChain{spec.handlers}
    85  			gotEvents, gotData, gotErr := h.DispatchMsg(sdk.Context{}, RandomAccountAddress(t), "anyPort", myMsg)
    86  
    87  			// then
    88  			require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
    89  			if spec.expErr != nil {
    90  				return
    91  			}
    92  			assert.Equal(t, []wasmvmtypes.CosmosMsg{myMsg}, *gotMsgs)
    93  			assert.Equal(t, [][]byte{{1}}, gotData) // {1} is default in capturing handler
    94  			assert.Equal(t, spec.expEvents, gotEvents)
    95  		})
    96  	}
    97  }
    98  
    99  func TestSDKMessageHandlerDispatch(t *testing.T) {
   100  	myEvent := sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))
   101  	const myData = "myData"
   102  	myRouterResult := sdk.Result{
   103  		Data:   []byte(myData),
   104  		Events: sdk.Events{myEvent},
   105  	}
   106  
   107  	//var gotMsg []sdk.Msg
   108  	var gotMsg []string
   109  	capturingMessageRouter := wasmtesting.MessageRouterFunc(func(methodName string) baseapp.MsgServiceHandler {
   110  		return func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) {
   111  			gotMsg = append(gotMsg, methodName)
   112  			return &myRouterResult, nil
   113  		}
   114  	})
   115  	noRouteMessageRouter := wasmtesting.MessageRouterFunc(func(methodName string) baseapp.MsgServiceHandler {
   116  		return nil
   117  	})
   118  	myContractAddr := RandomAccountAddress(t)
   119  	myContractMessage := wasmvmtypes.CosmosMsg{Custom: []byte("{}")}
   120  
   121  	specs := map[string]struct {
   122  		srcRoute         MessageRouter
   123  		srcEncoder       CustomEncoder
   124  		expErr           *sdkerrors.Error
   125  		expMsgDispatched int
   126  	}{
   127  		"all good": {
   128  			srcRoute: capturingMessageRouter,
   129  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   130  				myMsg := types.MsgExecuteContract{
   131  					Sender:   myContractAddr.String(),
   132  					Contract: RandomBech32AccountAddress(t),
   133  					Msg:      []byte("{}"),
   134  				}
   135  				return []ibcadapter.Msg{&myMsg}, nil
   136  			},
   137  			expMsgDispatched: 1,
   138  		},
   139  		"multiple output msgs": {
   140  			srcRoute: capturingMessageRouter,
   141  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   142  				first := &types.MsgExecuteContract{
   143  					Sender:   myContractAddr.String(),
   144  					Contract: RandomBech32AccountAddress(t),
   145  					Msg:      []byte("{}"),
   146  				}
   147  				second := &types.MsgExecuteContract{
   148  					Sender:   myContractAddr.String(),
   149  					Contract: RandomBech32AccountAddress(t),
   150  					Msg:      []byte("{}"),
   151  				}
   152  				return []ibcadapter.Msg{first, second}, nil
   153  			},
   154  			expMsgDispatched: 2,
   155  		},
   156  		"invalid sdk message rejected": {
   157  			srcRoute: capturingMessageRouter,
   158  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   159  				invalidMsg := types.MsgExecuteContract{
   160  					Sender:   myContractAddr.String(),
   161  					Contract: RandomBech32AccountAddress(t),
   162  					Msg:      []byte("INVALID_JSON"),
   163  				}
   164  				return []ibcadapter.Msg{&invalidMsg}, nil
   165  			},
   166  			expErr: types.ErrInvalid,
   167  		},
   168  		"invalid sender rejected": {
   169  			srcRoute: capturingMessageRouter,
   170  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   171  				invalidMsg := types.MsgExecuteContract{
   172  					Sender:   RandomBech32AccountAddress(t),
   173  					Contract: RandomBech32AccountAddress(t),
   174  					Msg:      []byte("{}"),
   175  				}
   176  				return []ibcadapter.Msg{&invalidMsg}, nil
   177  			},
   178  			expErr: sdkerrors.ErrUnauthorized,
   179  		},
   180  		"unroutable message rejected": {
   181  			srcRoute: noRouteMessageRouter,
   182  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   183  				myMsg := types.MsgExecuteContract{
   184  					Sender:   myContractAddr.String(),
   185  					Contract: RandomBech32AccountAddress(t),
   186  					Msg:      []byte("{}"),
   187  				}
   188  				return []ibcadapter.Msg{&myMsg}, nil
   189  			},
   190  			expErr: sdkerrors.ErrUnknownRequest,
   191  		},
   192  		"encoding error passed": {
   193  			srcRoute: capturingMessageRouter,
   194  			srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]ibcadapter.Msg, error) {
   195  				myErr := types.ErrUnpinContractFailed // any error that is not used
   196  				return nil, myErr
   197  			},
   198  			expErr: types.ErrUnpinContractFailed,
   199  		},
   200  	}
   201  	for name, spec := range specs {
   202  		t.Run(name, func(t *testing.T) {
   203  			//gotMsg = make([]sdk.Msg, 0)
   204  			gotMsg = make([]string, 0)
   205  
   206  			// when
   207  			ctx := sdk.Context{}
   208  			h := NewSDKMessageHandler(spec.srcRoute, MessageEncoders{Custom: spec.srcEncoder})
   209  			gotEvents, gotData, gotErr := h.DispatchMsg(ctx, myContractAddr, "myPort", myContractMessage)
   210  
   211  			// then
   212  			require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
   213  			if spec.expErr != nil {
   214  				require.Len(t, gotMsg, 0)
   215  				return
   216  			}
   217  			assert.Len(t, gotMsg, spec.expMsgDispatched)
   218  			for i := 0; i < spec.expMsgDispatched; i++ {
   219  				assert.Equal(t, myEvent, gotEvents[i])
   220  				assert.Equal(t, []byte(myData), gotData[i])
   221  			}
   222  		})
   223  	}
   224  }
   225  
   226  func TestIBCRawPacketHandler(t *testing.T) {
   227  	ibcPort := "contractsIBCPort"
   228  	var ctx sdk.Context
   229  
   230  	var capturedPacket ibcexported.PacketI
   231  
   232  	chanKeeper := &wasmtesting.MockChannelKeeper{
   233  		GetNextSequenceSendFn: func(ctx sdk.Context, portID, channelID string) (uint64, bool) {
   234  			return 1, true
   235  		},
   236  		GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channeltypes.Channel, bool) {
   237  			return channeltypes.Channel{
   238  				Counterparty: channeltypes.NewCounterparty(
   239  					"other-port",
   240  					"other-channel-1",
   241  				),
   242  			}, true
   243  		},
   244  		SendPacketFn: func(ctx sdk.Context, channelCap *capabilitytypes.Capability, packet ibcexported.PacketI) error {
   245  			capturedPacket = packet
   246  			return nil
   247  		},
   248  	}
   249  	capKeeper := &wasmtesting.MockCapabilityKeeper{
   250  		GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) {
   251  			return &capabilitytypes.Capability{}, true
   252  		},
   253  	}
   254  
   255  	specs := map[string]struct {
   256  		srcMsg        wasmvmtypes.SendPacketMsg
   257  		chanKeeper    types.ChannelKeeper
   258  		capKeeper     types.CapabilityKeeper
   259  		expPacketSent channeltypes.Packet
   260  		expErr        *sdkerrors.Error
   261  	}{
   262  		"all good": {
   263  			srcMsg: wasmvmtypes.SendPacketMsg{
   264  				ChannelID: "channel-1",
   265  				Data:      []byte("myData"),
   266  				Timeout:   wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
   267  			},
   268  			chanKeeper: chanKeeper,
   269  			capKeeper:  capKeeper,
   270  			expPacketSent: channeltypes.Packet{
   271  				Sequence:           1,
   272  				SourcePort:         ibcPort,
   273  				SourceChannel:      "channel-1",
   274  				DestinationPort:    "other-port",
   275  				DestinationChannel: "other-channel-1",
   276  				Data:               []byte("myData"),
   277  				TimeoutHeight:      clienttypes.Height{RevisionNumber: 1, RevisionHeight: 2},
   278  			},
   279  		},
   280  		"sequence not found returns error": {
   281  			srcMsg: wasmvmtypes.SendPacketMsg{
   282  				ChannelID: "channel-1",
   283  				Data:      []byte("myData"),
   284  				Timeout:   wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
   285  			},
   286  			chanKeeper: &wasmtesting.MockChannelKeeper{
   287  				GetNextSequenceSendFn: func(ctx sdk.Context, portID, channelID string) (uint64, bool) {
   288  					return 0, false
   289  				},
   290  			},
   291  			expErr: channeltypes.ErrSequenceSendNotFound,
   292  		},
   293  		"capability not found returns error": {
   294  			srcMsg: wasmvmtypes.SendPacketMsg{
   295  				ChannelID: "channel-1",
   296  				Data:      []byte("myData"),
   297  				Timeout:   wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
   298  			},
   299  			chanKeeper: chanKeeper,
   300  			capKeeper: wasmtesting.MockCapabilityKeeper{
   301  				GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) {
   302  					return nil, false
   303  				},
   304  			},
   305  			expErr: channeltypes.ErrChannelCapabilityNotFound,
   306  		},
   307  	}
   308  	for name, spec := range specs {
   309  		t.Run(name, func(t *testing.T) {
   310  			capturedPacket = nil
   311  			// when
   312  			h := NewIBCRawPacketHandler(spec.chanKeeper, spec.capKeeper)
   313  			data, evts, gotErr := h.DispatchMsg(ctx, RandomAccountAddress(t), ibcPort, wasmvmtypes.CosmosMsg{IBC: &wasmvmtypes.IBCMsg{SendPacket: &spec.srcMsg}})
   314  			// then
   315  			require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
   316  			if spec.expErr != nil {
   317  				return
   318  			}
   319  			assert.Nil(t, data)
   320  			assert.Nil(t, evts)
   321  			assert.Equal(t, spec.expPacketSent, capturedPacket)
   322  		})
   323  	}
   324  }
   325  
   326  //func TestBurnCoinMessageHandlerIntegration(t *testing.T) {
   327  //	// testing via full keeper setup so that we are confident the
   328  //	// module permissions are set correct and no other handler
   329  //	// picks the message in the default handler chain
   330  //	ctx, keepers := CreateDefaultTestInput(t)
   331  //	// set some supply
   332  //	keepers.Faucet.NewFundedAccount(ctx, sdk.NewCoin("denom", sdk.NewInt(10_000_000)))
   333  //	k := keepers.WasmKeeper
   334  //
   335  //	example := InstantiateHackatomExampleContract(t, ctx, keepers) // with deposit of 100 stake
   336  //
   337  //	before, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
   338  //	require.NoError(t, err)
   339  //
   340  //	specs := map[string]struct {
   341  //		msg    wasmvmtypes.BurnMsg
   342  //		expErr bool
   343  //	}{
   344  //		"all good": {
   345  //			msg: wasmvmtypes.BurnMsg{
   346  //				Amount: wasmvmtypes.Coins{{
   347  //					Denom:  "denom",
   348  //					Amount: "100",
   349  //				}},
   350  //			},
   351  //		},
   352  //		"not enough funds in contract": {
   353  //			msg: wasmvmtypes.BurnMsg{
   354  //				Amount: wasmvmtypes.Coins{{
   355  //					Denom:  "denom",
   356  //					Amount: "101",
   357  //				}},
   358  //			},
   359  //			expErr: true,
   360  //		},
   361  //		"zero amount rejected": {
   362  //			msg: wasmvmtypes.BurnMsg{
   363  //				Amount: wasmvmtypes.Coins{{
   364  //					Denom:  "denom",
   365  //					Amount: "0",
   366  //				}},
   367  //			},
   368  //			expErr: true,
   369  //		},
   370  //		"unknown denom - insufficient funds": {
   371  //			msg: wasmvmtypes.BurnMsg{
   372  //				Amount: wasmvmtypes.Coins{{
   373  //					Denom:  "unknown",
   374  //					Amount: "1",
   375  //				}},
   376  //			},
   377  //			expErr: true,
   378  //		},
   379  //	}
   380  //	parentCtx := ctx
   381  //	for name, spec := range specs {
   382  //		t.Run(name, func(t *testing.T) {
   383  //			ctx, _ = parentCtx.CacheContext()
   384  //			k.wasmVM = &wasmtesting.MockWasmer{ExecuteFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
   385  //				return &wasmvmtypes.Response{
   386  //					Messages: []wasmvmtypes.SubMsg{
   387  //						{Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{Burn: &spec.msg}}, ReplyOn: wasmvmtypes.ReplyNever},
   388  //					},
   389  //				}, 0, nil
   390  //			}}
   391  //
   392  //			// when
   393  //			_, err = k.execute(ctx, example.Contract, example.CreatorAddr, nil, nil)
   394  //
   395  //			// then
   396  //			if spec.expErr {
   397  //				require.Error(t, err)
   398  //				return
   399  //			}
   400  //			require.NoError(t, err)
   401  //
   402  //			// and total supply reduced by burned amount
   403  //			after, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
   404  //			require.NoError(t, err)
   405  //			diff := before.Supply.Sub(after.Supply)
   406  //			assert.Equal(t, sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(100))), diff)
   407  //		})
   408  //	}
   409  //
   410  //	// test cases:
   411  //	// not enough money to burn
   412  //}