github.com/Finschia/finschia-sdk@v0.48.1/x/token/keeper/msg_server_test.go (about)

     1  package keeper_test
     2  
     3  import (
     4  	abci "github.com/tendermint/tendermint/abci/types"
     5  
     6  	"github.com/Finschia/finschia-sdk/testutil"
     7  	sdk "github.com/Finschia/finschia-sdk/types"
     8  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
     9  	"github.com/Finschia/finschia-sdk/x/token"
    10  	"github.com/Finschia/finschia-sdk/x/token/class"
    11  )
    12  
    13  func (s *KeeperTestSuite) TestMsgSend() {
    14  	testCases := map[string]struct {
    15  		contractID string
    16  		amount     sdk.Int
    17  		err        error
    18  		events     sdk.Events
    19  	}{
    20  		"valid request": {
    21  			contractID: s.contractID,
    22  			amount:     s.balance,
    23  			events: sdk.Events{
    24  				sdk.Event{
    25  					Type: "lbm.token.v1.EventSent",
    26  					Attributes: []abci.EventAttribute{
    27  						{Key: []byte("amount"), Value: testutil.W(s.balance), Index: false},
    28  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
    29  						{Key: []byte("from"), Value: testutil.W(s.vendor), Index: false},
    30  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
    31  						{Key: []byte("to"), Value: testutil.W(s.customer), Index: false},
    32  					},
    33  				},
    34  			},
    35  		},
    36  		"contract not found": {
    37  			contractID: "fee1dead",
    38  			amount:     sdk.OneInt(),
    39  			err:        class.ErrContractNotExist,
    40  		},
    41  		"insufficient funds": {
    42  			contractID: s.contractID,
    43  			amount:     s.balance.Add(sdk.OneInt()),
    44  			err:        token.ErrInsufficientBalance,
    45  		},
    46  	}
    47  
    48  	for name, tc := range testCases {
    49  		s.Run(name, func() {
    50  			ctx, _ := s.ctx.CacheContext()
    51  
    52  			req := &token.MsgSend{
    53  				ContractId: tc.contractID,
    54  				From:       s.vendor.String(),
    55  				To:         s.customer.String(),
    56  				Amount:     tc.amount,
    57  			}
    58  			res, err := s.msgServer.Send(sdk.WrapSDKContext(ctx), req)
    59  			s.Require().ErrorIs(err, tc.err)
    60  			if tc.err != nil {
    61  				return
    62  			}
    63  
    64  			s.Require().NotNil(res)
    65  			s.Require().Equal(tc.events, ctx.EventManager().Events())
    66  		})
    67  	}
    68  }
    69  
    70  func (s *KeeperTestSuite) TestMsgOperatorSend() {
    71  	testCases := map[string]struct {
    72  		contractID string
    73  		operator   sdk.AccAddress
    74  		from       sdk.AccAddress
    75  		amount     sdk.Int
    76  		err        error
    77  		events     sdk.Events
    78  	}{
    79  		"valid request": {
    80  			contractID: s.contractID,
    81  			operator:   s.operator,
    82  			from:       s.customer,
    83  			amount:     s.balance,
    84  			events: sdk.Events{
    85  				sdk.Event{
    86  					Type: "lbm.token.v1.EventSent",
    87  					Attributes: []abci.EventAttribute{
    88  						{Key: []byte("amount"), Value: testutil.W(s.balance), Index: false},
    89  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
    90  						{Key: []byte("from"), Value: testutil.W(s.customer), Index: false},
    91  						{Key: []byte("operator"), Value: testutil.W(s.operator), Index: false},
    92  						{Key: []byte("to"), Value: testutil.W(s.vendor), Index: false},
    93  					},
    94  				},
    95  			},
    96  		},
    97  		"contract not found": {
    98  			contractID: "fee1dead",
    99  			operator:   s.operator,
   100  			from:       s.customer,
   101  			amount:     s.balance,
   102  			err:        class.ErrContractNotExist,
   103  		},
   104  		"not approved": {
   105  			contractID: s.contractID,
   106  			operator:   s.vendor,
   107  			from:       s.customer,
   108  			amount:     s.balance,
   109  			err:        token.ErrTokenNotApproved,
   110  		},
   111  		"insufficient funds": {
   112  			contractID: s.contractID,
   113  			operator:   s.operator,
   114  			from:       s.customer,
   115  			amount:     s.balance.Add(sdk.OneInt()),
   116  			err:        token.ErrInsufficientBalance,
   117  		},
   118  	}
   119  
   120  	for name, tc := range testCases {
   121  		s.Run(name, func() {
   122  			ctx, _ := s.ctx.CacheContext()
   123  
   124  			req := &token.MsgOperatorSend{
   125  				ContractId: tc.contractID,
   126  				Operator:   tc.operator.String(),
   127  				From:       tc.from.String(),
   128  				To:         s.vendor.String(),
   129  				Amount:     tc.amount,
   130  			}
   131  			res, err := s.msgServer.OperatorSend(sdk.WrapSDKContext(ctx), req)
   132  			s.Require().ErrorIs(err, tc.err)
   133  			if tc.err != nil {
   134  				return
   135  			}
   136  
   137  			s.Require().NotNil(res)
   138  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   139  		})
   140  	}
   141  }
   142  
   143  func (s *KeeperTestSuite) TestMsgRevokeOperator() {
   144  	testCases := map[string]struct {
   145  		contractID string
   146  		holder     sdk.AccAddress
   147  		operator   sdk.AccAddress
   148  		err        error
   149  		events     sdk.Events
   150  	}{
   151  		"valid request": {
   152  			contractID: s.contractID,
   153  			holder:     s.customer,
   154  			operator:   s.operator,
   155  			events: sdk.Events{
   156  				sdk.Event{
   157  					Type: "lbm.token.v1.EventRevokedOperator",
   158  					Attributes: []abci.EventAttribute{
   159  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   160  						{Key: []byte("holder"), Value: testutil.W(s.customer), Index: false},
   161  						{Key: []byte("operator"), Value: testutil.W(s.operator), Index: false},
   162  					},
   163  				},
   164  			},
   165  		},
   166  		"contract not found": {
   167  			contractID: "fee1dead",
   168  			holder:     s.customer,
   169  			operator:   s.operator,
   170  			err:        class.ErrContractNotExist,
   171  		},
   172  		"no authorization": {
   173  			contractID: s.contractID,
   174  			holder:     s.customer,
   175  			operator:   s.vendor,
   176  			err:        token.ErrTokenNotApproved,
   177  		},
   178  	}
   179  
   180  	for name, tc := range testCases {
   181  		s.Run(name, func() {
   182  			ctx, _ := s.ctx.CacheContext()
   183  
   184  			req := &token.MsgRevokeOperator{
   185  				ContractId: tc.contractID,
   186  				Holder:     tc.holder.String(),
   187  				Operator:   tc.operator.String(),
   188  			}
   189  			res, err := s.msgServer.RevokeOperator(sdk.WrapSDKContext(ctx), req)
   190  			s.Require().ErrorIs(err, tc.err)
   191  			if tc.err != nil {
   192  				return
   193  			}
   194  
   195  			s.Require().NotNil(res)
   196  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   197  		})
   198  	}
   199  }
   200  
   201  func (s *KeeperTestSuite) TestMsgAuthorizeOperator() {
   202  	testCases := map[string]struct {
   203  		contractID string
   204  		holder     sdk.AccAddress
   205  		operator   sdk.AccAddress
   206  		err        error
   207  		events     sdk.Events
   208  	}{
   209  		"valid request": {
   210  			contractID: s.contractID,
   211  			holder:     s.customer,
   212  			operator:   s.vendor,
   213  			events: sdk.Events{
   214  				sdk.Event{
   215  					Type: "lbm.token.v1.EventAuthorizedOperator",
   216  					Attributes: []abci.EventAttribute{
   217  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   218  						{Key: []byte("holder"), Value: testutil.W(s.customer), Index: false},
   219  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   220  					},
   221  				},
   222  			},
   223  		},
   224  		"contract not found": {
   225  			contractID: "fee1dead",
   226  			holder:     s.customer,
   227  			operator:   s.vendor,
   228  			err:        class.ErrContractNotExist,
   229  		},
   230  		"already approved": {
   231  			contractID: s.contractID,
   232  			holder:     s.customer,
   233  			operator:   s.operator,
   234  			err:        token.ErrTokenAlreadyApproved,
   235  		},
   236  	}
   237  
   238  	for name, tc := range testCases {
   239  		s.Run(name, func() {
   240  			ctx, _ := s.ctx.CacheContext()
   241  
   242  			req := &token.MsgAuthorizeOperator{
   243  				ContractId: tc.contractID,
   244  				Holder:     tc.holder.String(),
   245  				Operator:   tc.operator.String(),
   246  			}
   247  			res, err := s.msgServer.AuthorizeOperator(sdk.WrapSDKContext(ctx), req)
   248  			s.Require().ErrorIs(err, tc.err)
   249  			if tc.err != nil {
   250  				return
   251  			}
   252  
   253  			s.Require().NotNil(res)
   254  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   255  		})
   256  	}
   257  }
   258  
   259  func (s *KeeperTestSuite) TestMsgIssue() {
   260  	testCases := map[string]struct {
   261  		mintable bool
   262  		amount   sdk.Int
   263  		err      error
   264  		events   sdk.Events
   265  	}{
   266  		"mintable true": {
   267  			mintable: true,
   268  			amount:   sdk.NewInt(10),
   269  			events: sdk.Events{
   270  				sdk.Event{
   271  					Type: "lbm.token.v1.EventIssued",
   272  					Attributes: []abci.EventAttribute{
   273  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   274  						{Key: []uint8("creator"), Value: testutil.W(s.vendor), Index: false},
   275  						{Key: []uint8("decimals"), Value: []byte("0"), Index: false},
   276  						{Key: []uint8("meta"), Value: testutil.W(""), Index: false},
   277  						{Key: []uint8("mintable"), Value: []byte("true"), Index: false},
   278  						{Key: []uint8("name"), Value: testutil.W("test"), Index: false},
   279  						{Key: []uint8("symbol"), Value: testutil.W("TT"), Index: false},
   280  						{Key: []uint8("uri"), Value: testutil.W(""), Index: false},
   281  					},
   282  				},
   283  				sdk.Event{
   284  					Type: "lbm.token.v1.EventGranted",
   285  					Attributes: []abci.EventAttribute{
   286  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   287  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   288  						{Key: []uint8("granter"), Value: testutil.W(""), Index: false},
   289  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MODIFY"), Index: false},
   290  					},
   291  				},
   292  				sdk.Event{
   293  					Type: "lbm.token.v1.EventGranted",
   294  					Attributes: []abci.EventAttribute{
   295  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   296  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   297  						{Key: []uint8("granter"), Value: testutil.W(""), Index: false},
   298  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MINT"), Index: false},
   299  					},
   300  				},
   301  				sdk.Event{
   302  					Type: "lbm.token.v1.EventGranted",
   303  					Attributes: []abci.EventAttribute{
   304  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   305  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   306  						{Key: []uint8("granter"), Value: testutil.W(""), Index: false},
   307  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_BURN"), Index: false},
   308  					},
   309  				},
   310  				sdk.Event{
   311  					Type: "lbm.token.v1.EventMinted",
   312  					Attributes: []abci.EventAttribute{
   313  						{Key: []uint8("amount"), Value: testutil.W("10"), Index: false},
   314  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   315  						{Key: []uint8("operator"), Value: testutil.W(s.vendor), Index: false},
   316  						{Key: []uint8("to"), Value: testutil.W(s.vendor), Index: false},
   317  					},
   318  				},
   319  			},
   320  		},
   321  		"mintable false": {
   322  			mintable: false,
   323  			amount:   sdk.NewInt(10),
   324  			events: sdk.Events{
   325  				sdk.Event{
   326  					Type: "lbm.token.v1.EventIssued",
   327  					Attributes: []abci.EventAttribute{
   328  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   329  						{Key: []uint8("creator"), Value: testutil.W(s.vendor), Index: false},
   330  						{Key: []uint8("decimals"), Value: []byte("0"), Index: false},
   331  						{Key: []uint8("meta"), Value: testutil.W(""), Index: false},
   332  						{Key: []uint8("mintable"), Value: []byte("false"), Index: false},
   333  						{Key: []uint8("name"), Value: testutil.W("test"), Index: false},
   334  						{Key: []uint8("symbol"), Value: testutil.W("TT"), Index: false},
   335  						{Key: []uint8("uri"), Value: testutil.W(""), Index: false},
   336  					},
   337  				},
   338  				sdk.Event{
   339  					Type: "lbm.token.v1.EventGranted",
   340  					Attributes: []abci.EventAttribute{
   341  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   342  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   343  						{Key: []uint8("granter"), Value: testutil.W(""), Index: false},
   344  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MODIFY"), Index: false},
   345  					},
   346  				},
   347  				sdk.Event{
   348  					Type: "lbm.token.v1.EventMinted",
   349  					Attributes: []abci.EventAttribute{
   350  						{Key: []uint8("amount"), Value: testutil.W(sdk.NewInt(10)), Index: false},
   351  						{Key: []uint8("contract_id"), Value: testutil.W("ca8bfd79"), Index: false},
   352  						{Key: []uint8("operator"), Value: testutil.W(s.vendor), Index: false},
   353  						{Key: []uint8("to"), Value: testutil.W(s.vendor), Index: false},
   354  					},
   355  				},
   356  			},
   357  		},
   358  	}
   359  
   360  	// define a function to check MsgIssue result
   361  	checkerIssueResult := func(ctx sdk.Context, contractId string, expectedMintable bool, expectedAmount sdk.Int) {
   362  		// check contract
   363  		contract, err := s.queryServer.Contract(sdk.WrapSDKContext(ctx), &token.QueryContractRequest{ContractId: contractId})
   364  		s.Require().NoError(err)
   365  		s.Require().Equal(expectedMintable, contract.Contract.Mintable)
   366  
   367  		// check supply
   368  		supply, err := s.queryServer.Supply(sdk.WrapSDKContext(ctx), &token.QuerySupplyRequest{ContractId: contractId})
   369  		s.Require().NoError(err)
   370  		s.Require().Equal(expectedAmount, supply.Amount)
   371  
   372  		// check mint
   373  		mint, err := s.queryServer.Minted(sdk.WrapSDKContext(ctx), &token.QueryMintedRequest{ContractId: contractId})
   374  		s.Require().NoError(err)
   375  		s.Require().Equal(expectedAmount, mint.Amount)
   376  
   377  		// check burnt
   378  		burn, err := s.queryServer.Burnt(sdk.WrapSDKContext(ctx), &token.QueryBurntRequest{ContractId: contractId})
   379  		s.Require().NoError(err)
   380  		s.Require().Equal(sdk.ZeroInt(), burn.Amount)
   381  
   382  		// check owner balance
   383  		balance, err := s.queryServer.Balance(sdk.WrapSDKContext(ctx), &token.QueryBalanceRequest{
   384  			ContractId: contractId,
   385  			Address:    s.vendor.String(),
   386  		})
   387  		s.Require().NoError(err)
   388  		s.Require().Equal(expectedAmount, balance.Amount)
   389  	}
   390  
   391  	for name, tc := range testCases {
   392  		s.Run(name, func() {
   393  			ctx, _ := s.ctx.CacheContext()
   394  
   395  			req := &token.MsgIssue{
   396  				Owner:    s.vendor.String(),
   397  				To:       s.vendor.String(),
   398  				Mintable: tc.mintable,
   399  				Name:     "test",
   400  				Symbol:   "TT",
   401  				Amount:   tc.amount,
   402  			}
   403  			res, err := s.msgServer.Issue(sdk.WrapSDKContext(ctx), req)
   404  			s.Require().ErrorIs(err, tc.err)
   405  			if tc.err != nil {
   406  				return
   407  			}
   408  
   409  			s.Require().NotNil(res)
   410  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   411  
   412  			// check result status
   413  			checkerIssueResult(ctx, res.ContractId, tc.mintable, tc.amount)
   414  
   415  			// Second request for the same request
   416  			res2, err := s.msgServer.Issue(sdk.WrapSDKContext(ctx), req)
   417  			s.Require().ErrorIs(err, tc.err)
   418  			if tc.err != nil {
   419  				return
   420  			}
   421  			// check result status
   422  			checkerIssueResult(ctx, res2.ContractId, tc.mintable, tc.amount)
   423  			s.Require().NotEqual(res.ContractId, res2.ContractId)
   424  		})
   425  	}
   426  }
   427  
   428  func (s *KeeperTestSuite) TestMsgGrantPermission() {
   429  	testCases := map[string]struct {
   430  		contractID string
   431  		granter    sdk.AccAddress
   432  		grantee    sdk.AccAddress
   433  		permission string
   434  		err        error
   435  		events     sdk.Events
   436  	}{
   437  		"contract not found": {
   438  			contractID: "fee1dead",
   439  			granter:    s.vendor,
   440  			grantee:    s.operator,
   441  			permission: token.LegacyPermissionModify.String(),
   442  			err:        class.ErrContractNotExist,
   443  		},
   444  		"contract has no permission - MINT": {
   445  			contractID: s.unmintableContractId,
   446  			granter:    s.vendor,
   447  			grantee:    s.operator,
   448  			permission: token.LegacyPermissionMint.String(),
   449  			err:        token.ErrTokenNoPermission,
   450  		},
   451  		"contract has no permission - BURN": {
   452  			contractID: s.unmintableContractId,
   453  			granter:    s.vendor,
   454  			grantee:    s.operator,
   455  			permission: token.LegacyPermissionBurn.String(),
   456  			err:        token.ErrTokenNoPermission,
   457  		},
   458  		"granter has no permission - MINT": {
   459  			contractID: s.contractID,
   460  			granter:    s.customer,
   461  			grantee:    s.stranger,
   462  			permission: token.LegacyPermissionMint.String(),
   463  			err:        token.ErrTokenNoPermission,
   464  		},
   465  		"granter has no permission - BURN": {
   466  			contractID: s.contractID,
   467  			granter:    s.customer,
   468  			grantee:    s.stranger,
   469  			permission: token.LegacyPermissionBurn.String(),
   470  			err:        token.ErrTokenNoPermission,
   471  		},
   472  		"granter has no permission - MODIFY": {
   473  			contractID: s.contractID,
   474  			granter:    s.customer,
   475  			grantee:    s.stranger,
   476  			permission: token.LegacyPermissionModify.String(),
   477  			err:        token.ErrTokenNoPermission,
   478  		},
   479  		"valid request - MINT": {
   480  			contractID: s.contractID,
   481  			granter:    s.vendor,
   482  			grantee:    s.operator,
   483  			permission: token.LegacyPermissionMint.String(),
   484  			events: sdk.Events{
   485  				sdk.Event{
   486  					Type: "lbm.token.v1.EventGranted",
   487  					Attributes: []abci.EventAttribute{
   488  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   489  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   490  						{Key: []uint8("granter"), Value: testutil.W(s.vendor), Index: false},
   491  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MINT"), Index: false},
   492  					},
   493  				},
   494  			},
   495  		},
   496  		"valid request - BURN": {
   497  			contractID: s.contractID,
   498  			granter:    s.vendor,
   499  			grantee:    s.operator,
   500  			permission: token.LegacyPermissionBurn.String(),
   501  			events: sdk.Events{
   502  				sdk.Event{
   503  					Type: "lbm.token.v1.EventGranted",
   504  					Attributes: []abci.EventAttribute{
   505  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   506  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   507  						{Key: []uint8("granter"), Value: testutil.W(s.vendor), Index: false},
   508  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_BURN"), Index: false},
   509  					},
   510  				},
   511  			},
   512  		},
   513  		"valid request - MODIFY": {
   514  			contractID: s.contractID,
   515  			granter:    s.vendor,
   516  			grantee:    s.operator,
   517  			permission: token.LegacyPermissionModify.String(),
   518  			events: sdk.Events{
   519  				sdk.Event{
   520  					Type: "lbm.token.v1.EventGranted",
   521  					Attributes: []abci.EventAttribute{
   522  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   523  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   524  						{Key: []uint8("granter"), Value: testutil.W(s.vendor), Index: false},
   525  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MODIFY"), Index: false},
   526  					},
   527  				},
   528  			},
   529  		},
   530  	}
   531  
   532  	for name, tc := range testCases {
   533  		s.Run(name, func() {
   534  			ctx, _ := s.ctx.CacheContext()
   535  
   536  			req := &token.MsgGrantPermission{
   537  				ContractId: tc.contractID,
   538  				From:       tc.granter.String(),
   539  				To:         tc.grantee.String(),
   540  				Permission: tc.permission,
   541  			}
   542  			res, err := s.msgServer.GrantPermission(sdk.WrapSDKContext(ctx), req)
   543  			s.Require().ErrorIs(err, tc.err)
   544  			if tc.err != nil {
   545  				return
   546  			}
   547  
   548  			s.Require().NotNil(res)
   549  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   550  
   551  			// check to grant permission
   552  			per, err := s.queryServer.GranteeGrants(sdk.WrapSDKContext(ctx), &token.QueryGranteeGrantsRequest{
   553  				ContractId: tc.contractID,
   554  				Grantee:    tc.grantee.String(),
   555  				Pagination: nil,
   556  			})
   557  			s.Require().NoError(err)
   558  			s.Require().NotNil(per)
   559  			expectPermission := token.Grant{
   560  				Grantee:    tc.grantee.String(),
   561  				Permission: token.Permission(token.LegacyPermissionFromString(tc.permission)),
   562  			}
   563  			s.Require().Contains(per.Grants, expectPermission)
   564  		})
   565  	}
   566  }
   567  
   568  func (s *KeeperTestSuite) TestMsgRevokePermission() {
   569  	testCases := map[string]struct {
   570  		contractID string
   571  		from       sdk.AccAddress
   572  		permission string
   573  		err        error
   574  		events     sdk.Events
   575  	}{
   576  		"contract not found": {
   577  			contractID: "fee1dead",
   578  			from:       s.operator,
   579  			permission: token.LegacyPermissionMint.String(),
   580  			err:        class.ErrContractNotExist,
   581  		},
   582  		"contract has no permission - MINT": {
   583  			contractID: s.unmintableContractId,
   584  			from:       s.operator,
   585  			permission: token.LegacyPermissionMint.String(),
   586  			err:        token.ErrTokenNoPermission,
   587  		},
   588  		"contract has no permission - BURN": {
   589  			contractID: s.unmintableContractId,
   590  			from:       s.operator,
   591  			permission: token.LegacyPermissionBurn.String(),
   592  			err:        token.ErrTokenNoPermission,
   593  		},
   594  		"grantee has no permission - MINT": {
   595  			contractID: s.contractID,
   596  			from:       s.customer,
   597  			permission: token.LegacyPermissionMint.String(),
   598  			err:        token.ErrTokenNoPermission,
   599  		},
   600  		"grantee has no permission - BURN": {
   601  			contractID: s.contractID,
   602  			from:       s.customer,
   603  			permission: token.LegacyPermissionBurn.String(),
   604  			err:        token.ErrTokenNoPermission,
   605  		},
   606  		"grantee has no permission - MODIFY": {
   607  			contractID: s.contractID,
   608  			from:       s.customer,
   609  			permission: token.LegacyPermissionModify.String(),
   610  			err:        token.ErrTokenNoPermission,
   611  		},
   612  		"valid request - revoke MINT": {
   613  			contractID: s.contractID,
   614  			from:       s.operator,
   615  			permission: token.LegacyPermissionMint.String(),
   616  			events: sdk.Events{
   617  				sdk.Event{
   618  					Type: "lbm.token.v1.EventRenounced",
   619  					Attributes: []abci.EventAttribute{
   620  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   621  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   622  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MINT"), Index: false},
   623  					},
   624  				}},
   625  		},
   626  		"valid request - revoke BURN": {
   627  			contractID: s.contractID,
   628  			from:       s.operator,
   629  			permission: token.LegacyPermissionBurn.String(),
   630  			events: sdk.Events{
   631  				sdk.Event{
   632  					Type: "lbm.token.v1.EventRenounced",
   633  					Attributes: []abci.EventAttribute{
   634  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   635  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   636  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_BURN"), Index: false},
   637  					},
   638  				}},
   639  		},
   640  		"valid request - revoke MODIFY": {
   641  			contractID: s.contractID,
   642  			from:       s.vendor,
   643  			permission: token.LegacyPermissionModify.String(),
   644  			events: sdk.Events{
   645  				sdk.Event{
   646  					Type: "lbm.token.v1.EventRenounced",
   647  					Attributes: []abci.EventAttribute{
   648  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   649  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   650  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MODIFY"), Index: false},
   651  					},
   652  				}},
   653  		},
   654  	}
   655  
   656  	for name, tc := range testCases {
   657  		s.Run(name, func() {
   658  			ctx, _ := s.ctx.CacheContext()
   659  
   660  			req := &token.MsgRevokePermission{
   661  				ContractId: tc.contractID,
   662  				From:       tc.from.String(),
   663  				Permission: tc.permission,
   664  			}
   665  			res, err := s.msgServer.RevokePermission(sdk.WrapSDKContext(ctx), req)
   666  			s.Require().ErrorIs(err, tc.err)
   667  			if tc.err != nil {
   668  				return
   669  			}
   670  
   671  			s.Require().NotNil(res)
   672  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   673  
   674  			// check to remove permission
   675  			per, err := s.queryServer.GranteeGrants(sdk.WrapSDKContext(ctx), &token.QueryGranteeGrantsRequest{
   676  				ContractId: tc.contractID,
   677  				Grantee:    tc.from.String(),
   678  				Pagination: nil,
   679  			})
   680  			s.Require().NoError(err)
   681  			s.Require().NotNil(per)
   682  			expectPermission := token.Grant{
   683  				Grantee:    tc.from.String(),
   684  				Permission: token.Permission(token.LegacyPermissionFromString(tc.permission)),
   685  			}
   686  			s.Require().NotContains(per.Grants, expectPermission)
   687  		})
   688  	}
   689  }
   690  
   691  func (s *KeeperTestSuite) TestMsgMint() {
   692  	testCases := map[string]struct {
   693  		isNegativeCase bool
   694  		req            *token.MsgMint
   695  		expectedEvents sdk.Events
   696  		expectedError  *sdkerrors.Error
   697  	}{
   698  		"mint(contractID, from, to, 10)": {
   699  			req: &token.MsgMint{
   700  				ContractId: s.contractID,
   701  				From:       s.vendor.String(),
   702  				To:         s.customer.String(),
   703  				Amount:     sdk.NewInt(10),
   704  			},
   705  			expectedEvents: sdk.Events{
   706  				sdk.Event{
   707  					Type: "lbm.token.v1.EventMinted",
   708  					Attributes: []abci.EventAttribute{
   709  						{Key: []byte("amount"), Value: testutil.W(sdk.NewInt(10)), Index: false},
   710  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   711  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   712  						{Key: []byte("to"), Value: testutil.W(s.customer), Index: false},
   713  					},
   714  				},
   715  			},
   716  		},
   717  		"mint(contractID, from, from, 10)": {
   718  			req: &token.MsgMint{
   719  				ContractId: s.contractID,
   720  				From:       s.vendor.String(),
   721  				To:         s.customer.String(),
   722  				Amount:     sdk.NewInt(10),
   723  			},
   724  			expectedEvents: sdk.Events{
   725  				sdk.Event{
   726  					Type: "lbm.token.v1.EventMinted",
   727  					Attributes: []abci.EventAttribute{
   728  						{Key: []byte("amount"), Value: testutil.W(sdk.NewInt(10)), Index: false},
   729  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   730  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   731  						{Key: []byte("to"), Value: testutil.W(s.customer), Index: false},
   732  					},
   733  				},
   734  			},
   735  		},
   736  		"mint(contractID, vendor, customer, 1) -> error": {
   737  			isNegativeCase: true,
   738  			req: &token.MsgMint{
   739  				ContractId: s.unmintableContractId,
   740  				From:       s.vendor.String(),
   741  				To:         s.customer.String(),
   742  				Amount:     sdk.OneInt(),
   743  			},
   744  			expectedError: token.ErrTokenNoPermission,
   745  		},
   746  		"mint(nonExistingContractId, from, to, 1) -> error": {
   747  			isNegativeCase: true,
   748  			req: &token.MsgMint{
   749  				ContractId: "fee1dead",
   750  				From:       s.vendor.String(),
   751  				To:         s.customer.String(),
   752  				Amount:     sdk.OneInt(),
   753  			},
   754  			expectedError: class.ErrContractNotExist,
   755  		},
   756  		"mint(contractID, from, unauthorized account, 1) -> error": {
   757  			isNegativeCase: true,
   758  			req: &token.MsgMint{
   759  				ContractId: s.contractID,
   760  				From:       s.stranger.String(),
   761  				To:         s.vendor.String(),
   762  				Amount:     sdk.OneInt(),
   763  			},
   764  			expectedError: token.ErrTokenNoPermission,
   765  		},
   766  	}
   767  
   768  	for name, tc := range testCases {
   769  		s.Run(name, func() {
   770  			// Arrange
   771  			s.Require().NoError(tc.req.ValidateBasic())
   772  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   773  			s.Require().NoError(err)
   774  			to, err := sdk.AccAddressFromBech32(tc.req.To)
   775  			s.Require().NoError(err)
   776  			ctx, _ := s.ctx.CacheContext()
   777  			prevFrom := s.keeper.GetBalance(ctx, tc.req.ContractId, from)
   778  			prevTo := s.keeper.GetBalance(ctx, tc.req.ContractId, to)
   779  			prevMint := s.keeper.GetMinted(ctx, tc.req.ContractId)
   780  			prevSupplyAmount := s.keeper.GetSupply(ctx, tc.req.ContractId)
   781  
   782  			// Act
   783  			res, err := s.msgServer.Mint(sdk.WrapSDKContext(ctx), tc.req)
   784  			if tc.isNegativeCase {
   785  				s.Require().Nil(res)
   786  				s.Require().ErrorIs(err, tc.expectedError)
   787  				s.Require().Equal(0, len(ctx.EventManager().Events()))
   788  				return
   789  			}
   790  			s.Require().NoError(err)
   791  			s.Require().NotNil(res)
   792  
   793  			// Assert
   794  			events := ctx.EventManager().Events()
   795  			s.Require().Equal(tc.expectedEvents, events)
   796  			mintAmount := tc.req.Amount
   797  			curMinted := s.keeper.GetMinted(ctx, tc.req.ContractId)
   798  			curSupply := s.keeper.GetSupply(ctx, tc.req.ContractId)
   799  			curToAmount := s.keeper.GetBalance(ctx, s.contractID, to)
   800  			s.Require().Equal(prevMint.Add(mintAmount), curMinted)
   801  			s.Require().Equal(prevSupplyAmount.Add(mintAmount), curSupply)
   802  			s.Require().Equal(prevTo.Add(mintAmount), curToAmount)
   803  			if !from.Equals(to) {
   804  				curFrom := s.keeper.GetBalance(ctx, s.contractID, from)
   805  				s.Require().Equal(prevFrom, curFrom)
   806  			}
   807  		})
   808  	}
   809  }
   810  
   811  func (s *KeeperTestSuite) TestMsgBurn() {
   812  	testCases := map[string]struct {
   813  		isNegativeCase bool
   814  		req            *token.MsgBurn
   815  		expectedEvents sdk.Events
   816  		expectedError  *sdkerrors.Error
   817  	}{
   818  		"burn(contractID, from, amount)": {
   819  			req: &token.MsgBurn{
   820  				ContractId: s.contractID,
   821  				From:       s.vendor.String(),
   822  				Amount:     sdk.OneInt(),
   823  			},
   824  			expectedEvents: sdk.Events{
   825  				sdk.Event{
   826  					Type: "lbm.token.v1.EventBurned",
   827  					Attributes: []abci.EventAttribute{
   828  						{Key: []byte("amount"), Value: testutil.W(sdk.OneInt()), Index: false},
   829  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   830  						{Key: []byte("from"), Value: testutil.W(s.vendor), Index: false},
   831  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   832  					},
   833  				},
   834  			},
   835  		},
   836  		"burn(nonExistingContractId, from, 1) -> error": {
   837  			isNegativeCase: true,
   838  			req: &token.MsgBurn{
   839  				ContractId: "fee1dead",
   840  				From:       s.vendor.String(),
   841  				Amount:     sdk.OneInt(),
   842  			},
   843  			expectedError: class.ErrContractNotExist,
   844  		},
   845  		"burn(contractID, from, unauthorized account, 1) -> error": {
   846  			isNegativeCase: true,
   847  			req: &token.MsgBurn{
   848  				ContractId: s.contractID,
   849  				From:       s.stranger.String(),
   850  				Amount:     sdk.OneInt(),
   851  			},
   852  			expectedError: token.ErrTokenNoPermission,
   853  		},
   854  	}
   855  
   856  	for name, tc := range testCases {
   857  		s.Run(name, func() {
   858  			// Arrange
   859  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   860  			s.Require().NoError(err)
   861  			ctx, _ := s.ctx.CacheContext()
   862  			prevFrom := s.keeper.GetBalance(ctx, tc.req.ContractId, from)
   863  			prevBurnt := s.keeper.GetBurnt(ctx, tc.req.ContractId)
   864  			prevSupplyAmount := s.keeper.GetSupply(ctx, tc.req.ContractId)
   865  			s.Require().NoError(tc.req.ValidateBasic())
   866  
   867  			// Act
   868  			res, err := s.msgServer.Burn(sdk.WrapSDKContext(ctx), tc.req)
   869  			if tc.isNegativeCase {
   870  				s.Require().Nil(res)
   871  				s.Require().ErrorIs(err, tc.expectedError)
   872  				s.Require().Equal(0, len(ctx.EventManager().Events()))
   873  				return
   874  			}
   875  			s.Require().NoError(err)
   876  			s.Require().NotNil(res)
   877  
   878  			// Assert
   879  			events := ctx.EventManager().Events()
   880  			s.Require().Equal(tc.expectedEvents, events)
   881  
   882  			curBurnt := s.keeper.GetBurnt(ctx, tc.req.ContractId)
   883  			curSupply := s.keeper.GetSupply(ctx, tc.req.ContractId)
   884  			curFromAmount := s.keeper.GetBalance(ctx, s.contractID, from)
   885  			burnAmount := tc.req.Amount
   886  			s.Require().Equal(prevBurnt.Add(burnAmount), curBurnt)
   887  			s.Require().Equal(prevSupplyAmount.Sub(burnAmount), curSupply)
   888  			s.Require().Equal(prevFrom.Sub(burnAmount), curFromAmount)
   889  		})
   890  	}
   891  }
   892  
   893  func (s *KeeperTestSuite) TestMsgOperatorBurn() {
   894  	testCases := map[string]struct {
   895  		isNegativeCase bool
   896  		req            *token.MsgOperatorBurn
   897  		expectedEvent  sdk.Event
   898  		expectedError  *sdkerrors.Error
   899  	}{
   900  		"operatorBurn(contractID, operator, from, 1)": {
   901  			req: &token.MsgOperatorBurn{
   902  				ContractId: s.contractID,
   903  				Operator:   s.operator.String(),
   904  				From:       s.customer.String(),
   905  				Amount:     sdk.OneInt(),
   906  			},
   907  			expectedEvent: sdk.Event{
   908  				Type: "lbm.token.v1.EventBurned",
   909  				Attributes: []abci.EventAttribute{
   910  					{Key: []byte("amount"), Value: testutil.W(sdk.OneInt()), Index: false},
   911  					{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   912  					{Key: []byte("from"), Value: testutil.W(s.customer), Index: false},
   913  					{Key: []byte("operator"), Value: testutil.W(s.operator), Index: false},
   914  				},
   915  			},
   916  		},
   917  		"operatorBurn(nonExistingContractId, operator, from, 1) -> error": {
   918  			isNegativeCase: true,
   919  			req: &token.MsgOperatorBurn{
   920  				ContractId: "fee1dead",
   921  				Operator:   s.operator.String(),
   922  				From:       s.customer.String(),
   923  				Amount:     sdk.OneInt(),
   924  			},
   925  			expectedError: class.ErrContractNotExist,
   926  		},
   927  		"operatorBurn(contractID, operator, unauthorized account, 1) -> error": {
   928  			isNegativeCase: true,
   929  			req: &token.MsgOperatorBurn{
   930  				ContractId: s.contractID,
   931  				Operator:   s.operator.String(),
   932  				From:       s.stranger.String(),
   933  				Amount:     sdk.OneInt(),
   934  			},
   935  			expectedError: token.ErrTokenNotApproved,
   936  		},
   937  	}
   938  
   939  	for name, tc := range testCases {
   940  		s.Run(name, func() {
   941  			// Arrange
   942  			operator, err := sdk.AccAddressFromBech32(tc.req.Operator)
   943  			s.Require().NoError(err)
   944  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   945  			s.Require().NoError(err)
   946  			prevOperator := s.keeper.GetBalance(s.ctx, tc.req.ContractId, operator)
   947  			prevFrom := s.keeper.GetBalance(s.ctx, tc.req.ContractId, from)
   948  			prevBurnt := s.keeper.GetBurnt(s.ctx, tc.req.ContractId)
   949  			prevSupplyAmount := s.keeper.GetSupply(s.ctx, tc.req.ContractId)
   950  			s.Require().NoError(tc.req.ValidateBasic())
   951  			prevEvtCnt := len(s.ctx.EventManager().Events())
   952  
   953  			// Act
   954  			res, err := s.msgServer.OperatorBurn(sdk.WrapSDKContext(s.ctx), tc.req)
   955  			if tc.isNegativeCase {
   956  				s.Require().Nil(res)
   957  				s.Require().ErrorIs(err, tc.expectedError)
   958  				s.Require().Equal(prevEvtCnt, len(s.ctx.EventManager().Events()))
   959  				return
   960  			}
   961  			s.Require().NoError(err)
   962  			s.Require().NotNil(res)
   963  
   964  			// Assert
   965  			events := s.ctx.EventManager().Events()
   966  			s.Require().Equal(events[len(events)-1], tc.expectedEvent)
   967  			s.Require().Greater(len(s.ctx.EventManager().Events()), prevEvtCnt)
   968  
   969  			curBurnt := s.keeper.GetBurnt(s.ctx, tc.req.ContractId)
   970  			curSupply := s.keeper.GetSupply(s.ctx, tc.req.ContractId)
   971  			curFromAmount := s.keeper.GetBalance(s.ctx, s.contractID, from)
   972  			burnAmount := tc.req.Amount
   973  			s.Require().Equal(prevBurnt.Add(burnAmount), curBurnt)
   974  			s.Require().Equal(prevSupplyAmount.Sub(burnAmount), curSupply)
   975  			s.Require().Equal(prevFrom.Sub(burnAmount), curFromAmount)
   976  			if !from.Equals(operator) {
   977  				curOperator := s.keeper.GetBalance(s.ctx, s.contractID, operator)
   978  				s.Require().Equal(prevOperator, curOperator)
   979  			}
   980  		})
   981  	}
   982  }
   983  
   984  func (s *KeeperTestSuite) TestMsgModify() {
   985  	testCases := map[string]struct {
   986  		isNegativeCase bool
   987  		req            *token.MsgModify
   988  		expectedEvents sdk.Events
   989  		expectedError  *sdkerrors.Error
   990  	}{
   991  		"modify(contractID, owner, changes:uri,name)": {
   992  			req: &token.MsgModify{
   993  				ContractId: s.contractID,
   994  				Owner:      s.vendor.String(),
   995  				Changes: []token.Attribute{
   996  					{Key: token.AttributeKeyURI.String(), Value: "uri"},
   997  					{Key: token.AttributeKeyName.String(), Value: "NA<ENDSLSDN"},
   998  				},
   999  			},
  1000  			expectedEvents: []sdk.Event{
  1001  				{
  1002  					Type: "lbm.token.v1.EventModified",
  1003  					Attributes: []abci.EventAttribute{
  1004  						{Key: []byte("changes"), Value: testutil.MustJSONMarshal([]token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}, {Key: token.AttributeKeyName.String(), Value: "NA<ENDSLSDN"}}), Index: false},
  1005  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
  1006  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
  1007  					},
  1008  				},
  1009  			},
  1010  		},
  1011  		"modify(contractID, owner, changes:uri)": {
  1012  			req: &token.MsgModify{
  1013  				ContractId: s.contractID,
  1014  				Owner:      s.vendor.String(),
  1015  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri222"}},
  1016  			},
  1017  			expectedEvents: []sdk.Event{{
  1018  				Type: "lbm.token.v1.EventModified",
  1019  				Attributes: []abci.EventAttribute{
  1020  					{Key: []byte("changes"), Value: testutil.MustJSONMarshal([]token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri222"}}), Index: false},
  1021  					{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
  1022  					{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
  1023  				}},
  1024  			},
  1025  		},
  1026  		"modify(nonExistingContractId, from, 1) -> error": {
  1027  			isNegativeCase: true,
  1028  			req: &token.MsgModify{
  1029  				ContractId: "fee1dead",
  1030  				Owner:      s.vendor.String(),
  1031  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}},
  1032  			},
  1033  			expectedError: class.ErrContractNotExist,
  1034  		},
  1035  		"modify(contractID, from, unauthorized account, 1) -> error": {
  1036  			isNegativeCase: true,
  1037  			req: &token.MsgModify{
  1038  				ContractId: s.contractID,
  1039  				Owner:      s.stranger.String(),
  1040  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}},
  1041  			},
  1042  			expectedError: token.ErrTokenNoPermission,
  1043  		},
  1044  	}
  1045  
  1046  	for name, tc := range testCases {
  1047  		s.Run(name, func() {
  1048  			// Arrange
  1049  			s.Require().NoError(tc.req.ValidateBasic())
  1050  			ctx, _ := s.ctx.CacheContext()
  1051  
  1052  			// Act
  1053  			res, err := s.msgServer.Modify(sdk.WrapSDKContext(ctx), tc.req)
  1054  			if tc.isNegativeCase {
  1055  				s.Require().Nil(res)
  1056  				s.Require().ErrorIs(err, tc.expectedError)
  1057  				s.Require().Equal(0, len(ctx.EventManager().Events()))
  1058  				return
  1059  			}
  1060  			s.Require().NotNil(res)
  1061  			s.Require().NoError(err)
  1062  
  1063  			// Assert
  1064  			events := ctx.EventManager().Events()
  1065  			s.Require().Equal(tc.expectedEvents, events)
  1066  		})
  1067  	}
  1068  }