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

     1  package keeper_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  
     7  	sdk "github.com/Finschia/finschia-sdk/types"
     8  	"github.com/Finschia/finschia-sdk/x/collection"
     9  )
    10  
    11  func (s *KeeperTestSuite) TestCreateContract() {
    12  	ctx, _ := s.ctx.CacheContext()
    13  
    14  	input := collection.Contract{
    15  		Name: "tibetian fox",
    16  		Meta: "Tibetian Fox",
    17  		Uri:  "file:///tibetian_fox.png",
    18  	}
    19  	id := s.keeper.CreateContract(ctx, s.vendor, input)
    20  	s.Require().NotEmpty(id)
    21  
    22  	output, err := s.keeper.GetContract(ctx, id)
    23  	s.Require().NoError(err)
    24  	s.Require().NotNil(output)
    25  
    26  	s.Require().Equal(id, output.Id)
    27  	s.Require().Equal(input.Name, output.Name)
    28  	s.Require().Equal(input.Meta, output.Meta)
    29  	s.Require().Equal(input.Uri, output.Uri)
    30  }
    31  
    32  func (s *KeeperTestSuite) TestCreateTokenClass() {
    33  	testCases := map[string]struct {
    34  		contractID string
    35  		class      collection.TokenClass
    36  		err        error
    37  	}{
    38  		"valid fungible token class": {
    39  			contractID: s.contractID,
    40  			class:      &collection.FTClass{},
    41  		},
    42  		"valid non-fungible token class": {
    43  			contractID: s.contractID,
    44  			class:      &collection.NFTClass{},
    45  		},
    46  	}
    47  
    48  	for name, tc := range testCases {
    49  		s.Run(name, func() {
    50  			ctx, _ := s.ctx.CacheContext()
    51  
    52  			id, err := s.keeper.CreateTokenClass(ctx, tc.contractID, tc.class)
    53  			s.Require().ErrorIs(err, tc.err)
    54  			if tc.err != nil {
    55  				s.Require().Nil(id)
    56  				return
    57  			}
    58  			s.Require().NotNil(id)
    59  
    60  			class, err := s.keeper.GetTokenClass(ctx, tc.contractID, *id)
    61  			s.Require().NoError(err)
    62  			s.Require().NoError(class.ValidateBasic())
    63  		})
    64  	}
    65  }
    66  
    67  func (s *KeeperTestSuite) TestMintFT() {
    68  	testCases := map[string]struct {
    69  		contractID string
    70  		amount     collection.Coin
    71  		err        error
    72  	}{
    73  		"valid request": {
    74  			contractID: s.contractID,
    75  			amount:     collection.NewFTCoin(s.ftClassID, sdk.OneInt()),
    76  		},
    77  		"invalid token id": {
    78  			contractID: s.contractID,
    79  			amount:     collection.NewNFTCoin(s.ftClassID, 1),
    80  			err:        collection.ErrTokenNotExist,
    81  		},
    82  		"class not found": {
    83  			contractID: s.contractID,
    84  			amount:     collection.NewFTCoin("00bab10c", sdk.OneInt()),
    85  			err:        collection.ErrTokenNotExist,
    86  		},
    87  		"not a class id of ft": {
    88  			contractID: s.contractID,
    89  			amount:     collection.NewFTCoin(s.nftClassID, sdk.OneInt()),
    90  			err:        collection.ErrTokenNotMintable,
    91  		},
    92  	}
    93  
    94  	for name, tc := range testCases {
    95  		s.Run(name, func() {
    96  			ctx, _ := s.ctx.CacheContext()
    97  
    98  			// gather state
    99  			classID := collection.SplitTokenID(tc.amount.TokenId)
   100  			balanceBefore := s.keeper.GetBalance(ctx, tc.contractID, s.stranger, collection.NewFTID(classID))
   101  			supplyBefore := s.keeper.GetSupply(ctx, tc.contractID, classID)
   102  			mintedBefore := s.keeper.GetMinted(ctx, tc.contractID, classID)
   103  			burntBefore := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   104  
   105  			err := s.keeper.MintFT(ctx, tc.contractID, s.stranger, collection.NewCoins(tc.amount))
   106  			s.Require().ErrorIs(err, tc.err)
   107  			if tc.err != nil {
   108  				return
   109  			}
   110  
   111  			amount := tc.amount.Amount
   112  			balanceAfter := s.keeper.GetBalance(ctx, tc.contractID, s.stranger, collection.NewFTID(classID))
   113  			s.Require().Equal(balanceBefore.Add(amount), balanceAfter)
   114  			supplyAfter := s.keeper.GetSupply(ctx, tc.contractID, classID)
   115  			s.Require().Equal(supplyBefore.Add(amount), supplyAfter)
   116  			mintedAfter := s.keeper.GetMinted(ctx, tc.contractID, classID)
   117  			s.Require().Equal(mintedBefore.Add(amount), mintedAfter)
   118  			burntAfter := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   119  			s.Require().Equal(burntBefore, burntAfter)
   120  		})
   121  	}
   122  
   123  	// accumulation test
   124  	s.Run("accumulation test", func() {
   125  		ctx, _ := s.ctx.CacheContext()
   126  		numMints := int64(100)
   127  		contractID := s.contractID
   128  		classID := s.ftClassID
   129  		for i := int64(1); i <= numMints; i++ {
   130  			amount := sdk.NewInt(rand.Int63())
   131  
   132  			// gather state
   133  			balanceBefore := s.keeper.GetBalance(ctx, s.contractID, s.stranger, collection.NewFTID(s.ftClassID))
   134  			supplyBefore := s.keeper.GetSupply(ctx, contractID, classID)
   135  			mintedBefore := s.keeper.GetMinted(ctx, contractID, classID)
   136  			burntBefore := s.keeper.GetBurnt(ctx, contractID, classID)
   137  
   138  			s.keeper.MintFT(ctx, s.contractID, s.stranger, collection.NewCoins(collection.NewFTCoin(s.ftClassID, amount)))
   139  
   140  			balanceAfter := s.keeper.GetBalance(ctx, s.contractID, s.stranger, collection.NewFTID(s.ftClassID))
   141  			s.Require().Equal(balanceBefore.Add(amount), balanceAfter)
   142  			supplyAfter := s.keeper.GetSupply(ctx, contractID, classID)
   143  			s.Require().Equal(supplyBefore.Add(amount), supplyAfter)
   144  			mintedAfter := s.keeper.GetMinted(ctx, contractID, classID)
   145  			s.Require().Equal(mintedBefore.Add(amount), mintedAfter)
   146  			burntAfter := s.keeper.GetBurnt(ctx, contractID, classID)
   147  			s.Require().Equal(burntBefore, burntAfter)
   148  		}
   149  	})
   150  }
   151  
   152  func (s *KeeperTestSuite) TestMintNFT() {
   153  	testCases := map[string]struct {
   154  		contractID string
   155  		param      collection.MintNFTParam
   156  		err        error
   157  	}{
   158  		"valid request": {
   159  			contractID: s.contractID,
   160  			param:      collection.MintNFTParam{TokenType: s.nftClassID},
   161  		},
   162  		"class not found": {
   163  			contractID: s.contractID,
   164  			param:      collection.MintNFTParam{TokenType: "deadbeef"},
   165  			err:        collection.ErrTokenTypeNotExist,
   166  		},
   167  		"not a class id of nft": {
   168  			contractID: s.contractID,
   169  			param:      collection.MintNFTParam{TokenType: s.ftClassID},
   170  			err:        collection.ErrTokenTypeNotExist,
   171  		},
   172  	}
   173  
   174  	for name, tc := range testCases {
   175  		s.Run(name, func() {
   176  			ctx, _ := s.ctx.CacheContext()
   177  
   178  			// gather state
   179  			classID := tc.param.TokenType
   180  			supplyBefore := s.keeper.GetSupply(ctx, tc.contractID, classID)
   181  			mintedBefore := s.keeper.GetMinted(ctx, tc.contractID, classID)
   182  			burntBefore := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   183  
   184  			tokens, err := s.keeper.MintNFT(ctx, tc.contractID, s.stranger, []collection.MintNFTParam{tc.param})
   185  			s.Require().ErrorIs(err, tc.err)
   186  			if tc.err != nil {
   187  				return
   188  			}
   189  
   190  			amount := sdk.OneInt()
   191  			s.Require().Len(tokens, 1)
   192  			tokenID := tokens[0].TokenId
   193  			balanceAfter := s.keeper.GetBalance(ctx, tc.contractID, s.stranger, tokenID)
   194  			s.Require().Equal(amount, balanceAfter)
   195  			supplyAfter := s.keeper.GetSupply(ctx, tc.contractID, classID)
   196  			s.Require().Equal(supplyBefore.Add(amount), supplyAfter)
   197  			mintedAfter := s.keeper.GetMinted(ctx, tc.contractID, classID)
   198  			s.Require().Equal(mintedBefore.Add(amount), mintedAfter)
   199  			burntAfter := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   200  			s.Require().Equal(burntBefore, burntAfter)
   201  		})
   202  	}
   203  }
   204  
   205  func (s *KeeperTestSuite) TestBurnCoins() {
   206  	testCases := map[string]struct {
   207  		contractID string
   208  		amount     collection.Coin
   209  		err        error
   210  	}{
   211  		"valid request": {
   212  			contractID: s.contractID,
   213  			amount:     collection.NewFTCoin(s.ftClassID, sdk.OneInt()),
   214  		},
   215  		"insufficient tokens": {
   216  			contractID: s.contractID,
   217  			amount:     collection.NewFTCoin("00bab10c", sdk.OneInt()),
   218  			err:        collection.ErrInsufficientToken,
   219  		},
   220  	}
   221  
   222  	for name, tc := range testCases {
   223  		s.Run(name, func() {
   224  			ctx, _ := s.ctx.CacheContext()
   225  
   226  			// gather state
   227  			classID := collection.SplitTokenID(tc.amount.TokenId)
   228  			balanceBefore := s.keeper.GetBalance(ctx, tc.contractID, s.vendor, collection.NewFTID(classID))
   229  			supplyBefore := s.keeper.GetSupply(ctx, tc.contractID, classID)
   230  			mintedBefore := s.keeper.GetMinted(ctx, tc.contractID, classID)
   231  			burntBefore := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   232  
   233  			_, err := s.keeper.BurnCoins(ctx, tc.contractID, s.vendor, collection.NewCoins(tc.amount))
   234  			s.Require().ErrorIs(err, tc.err)
   235  			if tc.err != nil {
   236  				return
   237  			}
   238  
   239  			amount := tc.amount.Amount
   240  			balanceAfter := s.keeper.GetBalance(ctx, tc.contractID, s.vendor, collection.NewFTID(classID))
   241  			s.Require().Equal(balanceBefore.Sub(amount), balanceAfter)
   242  			supplyAfter := s.keeper.GetSupply(ctx, tc.contractID, classID)
   243  			s.Require().Equal(supplyBefore.Sub(amount), supplyAfter)
   244  			mintedAfter := s.keeper.GetMinted(ctx, tc.contractID, classID)
   245  			s.Require().Equal(mintedBefore, mintedAfter)
   246  			burntAfter := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   247  			s.Require().Equal(burntBefore.Add(amount), burntAfter)
   248  		})
   249  	}
   250  }
   251  
   252  func (s *KeeperTestSuite) TestModifyContract() {
   253  	contractDescriptions := map[string]string{
   254  		s.contractID: "valid",
   255  		"deadbeef":   "not-exist",
   256  	}
   257  	changes := []collection.Attribute{
   258  		{Key: collection.AttributeKeyName.String(), Value: "fox"},
   259  		{Key: collection.AttributeKeyURI.String(), Value: "file:///fox.png"},
   260  		{Key: collection.AttributeKeyMeta.String(), Value: "Fox"},
   261  	}
   262  
   263  	for contractID, contractDesc := range contractDescriptions {
   264  		name := fmt.Sprintf("Contract: %s", contractDesc)
   265  		s.Run(name, func() {
   266  			ctx, _ := s.ctx.CacheContext()
   267  
   268  			call := func() {
   269  				s.keeper.ModifyContract(ctx, contractID, s.vendor, changes)
   270  			}
   271  
   272  			if contractID != s.contractID {
   273  				s.Require().Panics(call)
   274  				return
   275  			}
   276  			call()
   277  
   278  			contract, err := s.keeper.GetContract(ctx, contractID)
   279  			s.Require().NoError(err)
   280  
   281  			s.Require().Equal(changes[0].Value, contract.Name)
   282  			s.Require().Equal(changes[1].Value, contract.Uri)
   283  			s.Require().Equal(changes[2].Value, contract.Meta)
   284  		})
   285  	}
   286  }
   287  
   288  func (s *KeeperTestSuite) TestModifyTokenClass() {
   289  	classDescriptions := map[string]string{
   290  		s.nftClassID: "valid",
   291  		"deadbeef":   "not-exist",
   292  	}
   293  	changes := []collection.Attribute{
   294  		{Key: collection.AttributeKeyName.String(), Value: "arctic fox"},
   295  		{Key: collection.AttributeKeyMeta.String(), Value: "Arctic Fox"},
   296  	}
   297  
   298  	for classID, classDesc := range classDescriptions {
   299  		name := fmt.Sprintf("Class: %s", classDesc)
   300  		s.Run(name, func() {
   301  			ctx, _ := s.ctx.CacheContext()
   302  
   303  			err := s.keeper.ModifyTokenClass(ctx, s.contractID, classID, s.vendor, changes)
   304  			if classID != s.nftClassID {
   305  				s.Require().ErrorIs(err, collection.ErrTokenTypeNotExist)
   306  				return
   307  			}
   308  			s.Require().NoError(err)
   309  
   310  			class, err := s.keeper.GetTokenClass(ctx, s.contractID, classID)
   311  			s.Require().NoError(err)
   312  
   313  			nftClass, ok := class.(*collection.NFTClass)
   314  			s.Require().True(ok)
   315  
   316  			s.Require().Equal(changes[0].Value, nftClass.Name)
   317  			s.Require().Equal(changes[1].Value, nftClass.Meta)
   318  		})
   319  	}
   320  }
   321  
   322  func (s *KeeperTestSuite) TestModifyNFT() {
   323  	validTokenID := collection.NewNFTID(s.nftClassID, 1)
   324  	tokenDescriptions := map[string]string{
   325  		validTokenID:                       "valid",
   326  		collection.NewNFTID("deadbeef", 1): "not-exist",
   327  	}
   328  	changes := []collection.Attribute{
   329  		{Key: collection.AttributeKeyName.String(), Value: "fennec fox 1"},
   330  		{Key: collection.AttributeKeyMeta.String(), Value: "Fennec Fox 1"},
   331  	}
   332  
   333  	for tokenID, tokenDesc := range tokenDescriptions {
   334  		name := fmt.Sprintf("Token: %s", tokenDesc)
   335  		s.Run(name, func() {
   336  			ctx, _ := s.ctx.CacheContext()
   337  
   338  			err := s.keeper.ModifyNFT(ctx, s.contractID, tokenID, s.vendor, changes)
   339  			if tokenID != validTokenID {
   340  				s.Require().ErrorIs(err, collection.ErrTokenNotExist)
   341  				return
   342  			}
   343  			s.Require().NoError(err)
   344  
   345  			nft, err := s.keeper.GetNFT(ctx, s.contractID, tokenID)
   346  			s.Require().NoError(err)
   347  
   348  			s.Require().Equal(changes[0].Value, nft.Name)
   349  			s.Require().Equal(changes[1].Value, nft.Meta)
   350  		})
   351  	}
   352  }