github.com/Finschia/finschia-sdk@v0.49.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  		},
   627  		"valid request - revoke BURN": {
   628  			contractID: s.contractID,
   629  			from:       s.operator,
   630  			permission: token.LegacyPermissionBurn.String(),
   631  			events: sdk.Events{
   632  				sdk.Event{
   633  					Type: "lbm.token.v1.EventRenounced",
   634  					Attributes: []abci.EventAttribute{
   635  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   636  						{Key: []uint8("grantee"), Value: testutil.W(s.operator), Index: false},
   637  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_BURN"), Index: false},
   638  					},
   639  				},
   640  			},
   641  		},
   642  		"valid request - revoke MODIFY": {
   643  			contractID: s.contractID,
   644  			from:       s.vendor,
   645  			permission: token.LegacyPermissionModify.String(),
   646  			events: sdk.Events{
   647  				sdk.Event{
   648  					Type: "lbm.token.v1.EventRenounced",
   649  					Attributes: []abci.EventAttribute{
   650  						{Key: []uint8("contract_id"), Value: testutil.W("9be17165"), Index: false},
   651  						{Key: []uint8("grantee"), Value: testutil.W(s.vendor), Index: false},
   652  						{Key: []uint8("permission"), Value: testutil.W("PERMISSION_MODIFY"), Index: false},
   653  					},
   654  				},
   655  			},
   656  		},
   657  	}
   658  
   659  	for name, tc := range testCases {
   660  		s.Run(name, func() {
   661  			ctx, _ := s.ctx.CacheContext()
   662  
   663  			req := &token.MsgRevokePermission{
   664  				ContractId: tc.contractID,
   665  				From:       tc.from.String(),
   666  				Permission: tc.permission,
   667  			}
   668  			res, err := s.msgServer.RevokePermission(sdk.WrapSDKContext(ctx), req)
   669  			s.Require().ErrorIs(err, tc.err)
   670  			if tc.err != nil {
   671  				return
   672  			}
   673  
   674  			s.Require().NotNil(res)
   675  			s.Require().Equal(tc.events, ctx.EventManager().Events())
   676  
   677  			// check to remove permission
   678  			per, err := s.queryServer.GranteeGrants(sdk.WrapSDKContext(ctx), &token.QueryGranteeGrantsRequest{
   679  				ContractId: tc.contractID,
   680  				Grantee:    tc.from.String(),
   681  				Pagination: nil,
   682  			})
   683  			s.Require().NoError(err)
   684  			s.Require().NotNil(per)
   685  			expectPermission := token.Grant{
   686  				Grantee:    tc.from.String(),
   687  				Permission: token.Permission(token.LegacyPermissionFromString(tc.permission)),
   688  			}
   689  			s.Require().NotContains(per.Grants, expectPermission)
   690  		})
   691  	}
   692  }
   693  
   694  func (s *KeeperTestSuite) TestMsgMint() {
   695  	testCases := map[string]struct {
   696  		isNegativeCase bool
   697  		req            *token.MsgMint
   698  		expectedEvents sdk.Events
   699  		expectedError  *sdkerrors.Error
   700  	}{
   701  		"mint(contractID, from, to, 10)": {
   702  			req: &token.MsgMint{
   703  				ContractId: s.contractID,
   704  				From:       s.vendor.String(),
   705  				To:         s.customer.String(),
   706  				Amount:     sdk.NewInt(10),
   707  			},
   708  			expectedEvents: sdk.Events{
   709  				sdk.Event{
   710  					Type: "lbm.token.v1.EventMinted",
   711  					Attributes: []abci.EventAttribute{
   712  						{Key: []byte("amount"), Value: testutil.W(sdk.NewInt(10)), Index: false},
   713  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   714  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   715  						{Key: []byte("to"), Value: testutil.W(s.customer), Index: false},
   716  					},
   717  				},
   718  			},
   719  		},
   720  		"mint(contractID, from, from, 10)": {
   721  			req: &token.MsgMint{
   722  				ContractId: s.contractID,
   723  				From:       s.vendor.String(),
   724  				To:         s.customer.String(),
   725  				Amount:     sdk.NewInt(10),
   726  			},
   727  			expectedEvents: sdk.Events{
   728  				sdk.Event{
   729  					Type: "lbm.token.v1.EventMinted",
   730  					Attributes: []abci.EventAttribute{
   731  						{Key: []byte("amount"), Value: testutil.W(sdk.NewInt(10)), Index: false},
   732  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   733  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   734  						{Key: []byte("to"), Value: testutil.W(s.customer), Index: false},
   735  					},
   736  				},
   737  			},
   738  		},
   739  		"mint(contractID, vendor, customer, 1) -> error": {
   740  			isNegativeCase: true,
   741  			req: &token.MsgMint{
   742  				ContractId: s.unmintableContractId,
   743  				From:       s.vendor.String(),
   744  				To:         s.customer.String(),
   745  				Amount:     sdk.OneInt(),
   746  			},
   747  			expectedError: token.ErrTokenNoPermission,
   748  		},
   749  		"mint(nonExistingContractId, from, to, 1) -> error": {
   750  			isNegativeCase: true,
   751  			req: &token.MsgMint{
   752  				ContractId: "fee1dead",
   753  				From:       s.vendor.String(),
   754  				To:         s.customer.String(),
   755  				Amount:     sdk.OneInt(),
   756  			},
   757  			expectedError: class.ErrContractNotExist,
   758  		},
   759  		"mint(contractID, from, unauthorized account, 1) -> error": {
   760  			isNegativeCase: true,
   761  			req: &token.MsgMint{
   762  				ContractId: s.contractID,
   763  				From:       s.stranger.String(),
   764  				To:         s.vendor.String(),
   765  				Amount:     sdk.OneInt(),
   766  			},
   767  			expectedError: token.ErrTokenNoPermission,
   768  		},
   769  	}
   770  
   771  	for name, tc := range testCases {
   772  		s.Run(name, func() {
   773  			// Arrange
   774  			s.Require().NoError(tc.req.ValidateBasic())
   775  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   776  			s.Require().NoError(err)
   777  			to, err := sdk.AccAddressFromBech32(tc.req.To)
   778  			s.Require().NoError(err)
   779  			ctx, _ := s.ctx.CacheContext()
   780  			prevFrom := s.keeper.GetBalance(ctx, tc.req.ContractId, from)
   781  			prevTo := s.keeper.GetBalance(ctx, tc.req.ContractId, to)
   782  			prevMint := s.keeper.GetMinted(ctx, tc.req.ContractId)
   783  			prevSupplyAmount := s.keeper.GetSupply(ctx, tc.req.ContractId)
   784  
   785  			// Act
   786  			res, err := s.msgServer.Mint(sdk.WrapSDKContext(ctx), tc.req)
   787  			if tc.isNegativeCase {
   788  				s.Require().Nil(res)
   789  				s.Require().ErrorIs(err, tc.expectedError)
   790  				s.Require().Equal(0, len(ctx.EventManager().Events()))
   791  				return
   792  			}
   793  			s.Require().NoError(err)
   794  			s.Require().NotNil(res)
   795  
   796  			// Assert
   797  			events := ctx.EventManager().Events()
   798  			s.Require().Equal(tc.expectedEvents, events)
   799  			mintAmount := tc.req.Amount
   800  			curMinted := s.keeper.GetMinted(ctx, tc.req.ContractId)
   801  			curSupply := s.keeper.GetSupply(ctx, tc.req.ContractId)
   802  			curToAmount := s.keeper.GetBalance(ctx, s.contractID, to)
   803  			s.Require().Equal(prevMint.Add(mintAmount), curMinted)
   804  			s.Require().Equal(prevSupplyAmount.Add(mintAmount), curSupply)
   805  			s.Require().Equal(prevTo.Add(mintAmount), curToAmount)
   806  			if !from.Equals(to) {
   807  				curFrom := s.keeper.GetBalance(ctx, s.contractID, from)
   808  				s.Require().Equal(prevFrom, curFrom)
   809  			}
   810  		})
   811  	}
   812  }
   813  
   814  func (s *KeeperTestSuite) TestMsgBurn() {
   815  	testCases := map[string]struct {
   816  		isNegativeCase bool
   817  		req            *token.MsgBurn
   818  		expectedEvents sdk.Events
   819  		expectedError  *sdkerrors.Error
   820  	}{
   821  		"burn(contractID, from, amount)": {
   822  			req: &token.MsgBurn{
   823  				ContractId: s.contractID,
   824  				From:       s.vendor.String(),
   825  				Amount:     sdk.OneInt(),
   826  			},
   827  			expectedEvents: sdk.Events{
   828  				sdk.Event{
   829  					Type: "lbm.token.v1.EventBurned",
   830  					Attributes: []abci.EventAttribute{
   831  						{Key: []byte("amount"), Value: testutil.W(sdk.OneInt()), Index: false},
   832  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   833  						{Key: []byte("from"), Value: testutil.W(s.vendor), Index: false},
   834  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
   835  					},
   836  				},
   837  			},
   838  		},
   839  		"burn(nonExistingContractId, from, 1) -> error": {
   840  			isNegativeCase: true,
   841  			req: &token.MsgBurn{
   842  				ContractId: "fee1dead",
   843  				From:       s.vendor.String(),
   844  				Amount:     sdk.OneInt(),
   845  			},
   846  			expectedError: class.ErrContractNotExist,
   847  		},
   848  		"burn(contractID, from, unauthorized account, 1) -> error": {
   849  			isNegativeCase: true,
   850  			req: &token.MsgBurn{
   851  				ContractId: s.contractID,
   852  				From:       s.stranger.String(),
   853  				Amount:     sdk.OneInt(),
   854  			},
   855  			expectedError: token.ErrTokenNoPermission,
   856  		},
   857  	}
   858  
   859  	for name, tc := range testCases {
   860  		s.Run(name, func() {
   861  			// Arrange
   862  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   863  			s.Require().NoError(err)
   864  			ctx, _ := s.ctx.CacheContext()
   865  			prevFrom := s.keeper.GetBalance(ctx, tc.req.ContractId, from)
   866  			prevBurnt := s.keeper.GetBurnt(ctx, tc.req.ContractId)
   867  			prevSupplyAmount := s.keeper.GetSupply(ctx, tc.req.ContractId)
   868  			s.Require().NoError(tc.req.ValidateBasic())
   869  
   870  			// Act
   871  			res, err := s.msgServer.Burn(sdk.WrapSDKContext(ctx), tc.req)
   872  			if tc.isNegativeCase {
   873  				s.Require().Nil(res)
   874  				s.Require().ErrorIs(err, tc.expectedError)
   875  				s.Require().Equal(0, len(ctx.EventManager().Events()))
   876  				return
   877  			}
   878  			s.Require().NoError(err)
   879  			s.Require().NotNil(res)
   880  
   881  			// Assert
   882  			events := ctx.EventManager().Events()
   883  			s.Require().Equal(tc.expectedEvents, events)
   884  
   885  			curBurnt := s.keeper.GetBurnt(ctx, tc.req.ContractId)
   886  			curSupply := s.keeper.GetSupply(ctx, tc.req.ContractId)
   887  			curFromAmount := s.keeper.GetBalance(ctx, s.contractID, from)
   888  			burnAmount := tc.req.Amount
   889  			s.Require().Equal(prevBurnt.Add(burnAmount), curBurnt)
   890  			s.Require().Equal(prevSupplyAmount.Sub(burnAmount), curSupply)
   891  			s.Require().Equal(prevFrom.Sub(burnAmount), curFromAmount)
   892  		})
   893  	}
   894  }
   895  
   896  func (s *KeeperTestSuite) TestMsgOperatorBurn() {
   897  	testCases := map[string]struct {
   898  		isNegativeCase bool
   899  		req            *token.MsgOperatorBurn
   900  		expectedEvent  sdk.Event
   901  		expectedError  *sdkerrors.Error
   902  	}{
   903  		"operatorBurn(contractID, operator, from, 1)": {
   904  			req: &token.MsgOperatorBurn{
   905  				ContractId: s.contractID,
   906  				Operator:   s.operator.String(),
   907  				From:       s.customer.String(),
   908  				Amount:     sdk.OneInt(),
   909  			},
   910  			expectedEvent: sdk.Event{
   911  				Type: "lbm.token.v1.EventBurned",
   912  				Attributes: []abci.EventAttribute{
   913  					{Key: []byte("amount"), Value: testutil.W(sdk.OneInt()), Index: false},
   914  					{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
   915  					{Key: []byte("from"), Value: testutil.W(s.customer), Index: false},
   916  					{Key: []byte("operator"), Value: testutil.W(s.operator), Index: false},
   917  				},
   918  			},
   919  		},
   920  		"operatorBurn(nonExistingContractId, operator, from, 1) -> error": {
   921  			isNegativeCase: true,
   922  			req: &token.MsgOperatorBurn{
   923  				ContractId: "fee1dead",
   924  				Operator:   s.operator.String(),
   925  				From:       s.customer.String(),
   926  				Amount:     sdk.OneInt(),
   927  			},
   928  			expectedError: class.ErrContractNotExist,
   929  		},
   930  		"operatorBurn(contractID, operator, unauthorized account, 1) -> error": {
   931  			isNegativeCase: true,
   932  			req: &token.MsgOperatorBurn{
   933  				ContractId: s.contractID,
   934  				Operator:   s.operator.String(),
   935  				From:       s.stranger.String(),
   936  				Amount:     sdk.OneInt(),
   937  			},
   938  			expectedError: token.ErrTokenNotApproved,
   939  		},
   940  	}
   941  
   942  	for name, tc := range testCases {
   943  		s.Run(name, func() {
   944  			// Arrange
   945  			operator, err := sdk.AccAddressFromBech32(tc.req.Operator)
   946  			s.Require().NoError(err)
   947  			from, err := sdk.AccAddressFromBech32(tc.req.From)
   948  			s.Require().NoError(err)
   949  			prevOperator := s.keeper.GetBalance(s.ctx, tc.req.ContractId, operator)
   950  			prevFrom := s.keeper.GetBalance(s.ctx, tc.req.ContractId, from)
   951  			prevBurnt := s.keeper.GetBurnt(s.ctx, tc.req.ContractId)
   952  			prevSupplyAmount := s.keeper.GetSupply(s.ctx, tc.req.ContractId)
   953  			s.Require().NoError(tc.req.ValidateBasic())
   954  			prevEvtCnt := len(s.ctx.EventManager().Events())
   955  
   956  			// Act
   957  			res, err := s.msgServer.OperatorBurn(sdk.WrapSDKContext(s.ctx), tc.req)
   958  			if tc.isNegativeCase {
   959  				s.Require().Nil(res)
   960  				s.Require().ErrorIs(err, tc.expectedError)
   961  				s.Require().Equal(prevEvtCnt, len(s.ctx.EventManager().Events()))
   962  				return
   963  			}
   964  			s.Require().NoError(err)
   965  			s.Require().NotNil(res)
   966  
   967  			// Assert
   968  			events := s.ctx.EventManager().Events()
   969  			s.Require().Equal(events[len(events)-1], tc.expectedEvent)
   970  			s.Require().Greater(len(s.ctx.EventManager().Events()), prevEvtCnt)
   971  
   972  			curBurnt := s.keeper.GetBurnt(s.ctx, tc.req.ContractId)
   973  			curSupply := s.keeper.GetSupply(s.ctx, tc.req.ContractId)
   974  			curFromAmount := s.keeper.GetBalance(s.ctx, s.contractID, from)
   975  			burnAmount := tc.req.Amount
   976  			s.Require().Equal(prevBurnt.Add(burnAmount), curBurnt)
   977  			s.Require().Equal(prevSupplyAmount.Sub(burnAmount), curSupply)
   978  			s.Require().Equal(prevFrom.Sub(burnAmount), curFromAmount)
   979  			if !from.Equals(operator) {
   980  				curOperator := s.keeper.GetBalance(s.ctx, s.contractID, operator)
   981  				s.Require().Equal(prevOperator, curOperator)
   982  			}
   983  		})
   984  	}
   985  }
   986  
   987  func (s *KeeperTestSuite) TestMsgModify() {
   988  	testCases := map[string]struct {
   989  		isNegativeCase bool
   990  		req            *token.MsgModify
   991  		expectedEvents sdk.Events
   992  		expectedError  *sdkerrors.Error
   993  	}{
   994  		"modify(contractID, owner, changes:uri,name)": {
   995  			req: &token.MsgModify{
   996  				ContractId: s.contractID,
   997  				Owner:      s.vendor.String(),
   998  				Changes: []token.Attribute{
   999  					{Key: token.AttributeKeyURI.String(), Value: "uri"},
  1000  					{Key: token.AttributeKeyName.String(), Value: "NA<ENDSLSDN"},
  1001  				},
  1002  			},
  1003  			expectedEvents: []sdk.Event{
  1004  				{
  1005  					Type: "lbm.token.v1.EventModified",
  1006  					Attributes: []abci.EventAttribute{
  1007  						{Key: []byte("changes"), Value: testutil.MustJSONMarshal([]token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}, {Key: token.AttributeKeyName.String(), Value: "NA<ENDSLSDN"}}), Index: false},
  1008  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
  1009  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
  1010  					},
  1011  				},
  1012  			},
  1013  		},
  1014  		"modify(contractID, owner, changes:uri)": {
  1015  			req: &token.MsgModify{
  1016  				ContractId: s.contractID,
  1017  				Owner:      s.vendor.String(),
  1018  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri222"}},
  1019  			},
  1020  			expectedEvents: []sdk.Event{
  1021  				{
  1022  					Type: "lbm.token.v1.EventModified",
  1023  					Attributes: []abci.EventAttribute{
  1024  						{Key: []byte("changes"), Value: testutil.MustJSONMarshal([]token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri222"}}), Index: false},
  1025  						{Key: []byte("contract_id"), Value: testutil.W(s.contractID), Index: false},
  1026  						{Key: []byte("operator"), Value: testutil.W(s.vendor), Index: false},
  1027  					},
  1028  				},
  1029  			},
  1030  		},
  1031  		"modify(nonExistingContractId, from, 1) -> error": {
  1032  			isNegativeCase: true,
  1033  			req: &token.MsgModify{
  1034  				ContractId: "fee1dead",
  1035  				Owner:      s.vendor.String(),
  1036  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}},
  1037  			},
  1038  			expectedError: class.ErrContractNotExist,
  1039  		},
  1040  		"modify(contractID, from, unauthorized account, 1) -> error": {
  1041  			isNegativeCase: true,
  1042  			req: &token.MsgModify{
  1043  				ContractId: s.contractID,
  1044  				Owner:      s.stranger.String(),
  1045  				Changes:    []token.Attribute{{Key: token.AttributeKeyURI.String(), Value: "uri"}},
  1046  			},
  1047  			expectedError: token.ErrTokenNoPermission,
  1048  		},
  1049  	}
  1050  
  1051  	for name, tc := range testCases {
  1052  		s.Run(name, func() {
  1053  			// Arrange
  1054  			s.Require().NoError(tc.req.ValidateBasic())
  1055  			ctx, _ := s.ctx.CacheContext()
  1056  
  1057  			// Act
  1058  			res, err := s.msgServer.Modify(sdk.WrapSDKContext(ctx), tc.req)
  1059  			if tc.isNegativeCase {
  1060  				s.Require().Nil(res)
  1061  				s.Require().ErrorIs(err, tc.expectedError)
  1062  				s.Require().Equal(0, len(ctx.EventManager().Events()))
  1063  				return
  1064  			}
  1065  			s.Require().NotNil(res)
  1066  			s.Require().NoError(err)
  1067  
  1068  			// Assert
  1069  			events := ctx.EventManager().Events()
  1070  			s.Require().Equal(tc.expectedEvents, events)
  1071  		})
  1072  	}
  1073  }