github.com/Finschia/finschia-sdk@v0.49.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  			err := s.keeper.MintFT(ctx, s.contractID, s.stranger, collection.NewCoins(collection.NewFTCoin(s.ftClassID, amount)))
   139  			s.Require().NoError(err)
   140  
   141  			balanceAfter := s.keeper.GetBalance(ctx, s.contractID, s.stranger, collection.NewFTID(s.ftClassID))
   142  			s.Require().Equal(balanceBefore.Add(amount), balanceAfter)
   143  			supplyAfter := s.keeper.GetSupply(ctx, contractID, classID)
   144  			s.Require().Equal(supplyBefore.Add(amount), supplyAfter)
   145  			mintedAfter := s.keeper.GetMinted(ctx, contractID, classID)
   146  			s.Require().Equal(mintedBefore.Add(amount), mintedAfter)
   147  			burntAfter := s.keeper.GetBurnt(ctx, contractID, classID)
   148  			s.Require().Equal(burntBefore, burntAfter)
   149  		}
   150  	})
   151  }
   152  
   153  func (s *KeeperTestSuite) TestMintNFT() {
   154  	testCases := map[string]struct {
   155  		contractID string
   156  		param      collection.MintNFTParam
   157  		err        error
   158  	}{
   159  		"valid request": {
   160  			contractID: s.contractID,
   161  			param:      collection.MintNFTParam{TokenType: s.nftClassID},
   162  		},
   163  		"class not found": {
   164  			contractID: s.contractID,
   165  			param:      collection.MintNFTParam{TokenType: "deadbeef"},
   166  			err:        collection.ErrTokenTypeNotExist,
   167  		},
   168  		"not a class id of nft": {
   169  			contractID: s.contractID,
   170  			param:      collection.MintNFTParam{TokenType: s.ftClassID},
   171  			err:        collection.ErrTokenTypeNotExist,
   172  		},
   173  	}
   174  
   175  	for name, tc := range testCases {
   176  		s.Run(name, func() {
   177  			ctx, _ := s.ctx.CacheContext()
   178  
   179  			// gather state
   180  			classID := tc.param.TokenType
   181  			supplyBefore := s.keeper.GetSupply(ctx, tc.contractID, classID)
   182  			mintedBefore := s.keeper.GetMinted(ctx, tc.contractID, classID)
   183  			burntBefore := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   184  
   185  			tokens, err := s.keeper.MintNFT(ctx, tc.contractID, s.stranger, []collection.MintNFTParam{tc.param})
   186  			s.Require().ErrorIs(err, tc.err)
   187  			if tc.err != nil {
   188  				return
   189  			}
   190  
   191  			amount := sdk.OneInt()
   192  			s.Require().Len(tokens, 1)
   193  			tokenID := tokens[0].TokenId
   194  			balanceAfter := s.keeper.GetBalance(ctx, tc.contractID, s.stranger, tokenID)
   195  			s.Require().Equal(amount, balanceAfter)
   196  			supplyAfter := s.keeper.GetSupply(ctx, tc.contractID, classID)
   197  			s.Require().Equal(supplyBefore.Add(amount), supplyAfter)
   198  			mintedAfter := s.keeper.GetMinted(ctx, tc.contractID, classID)
   199  			s.Require().Equal(mintedBefore.Add(amount), mintedAfter)
   200  			burntAfter := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   201  			s.Require().Equal(burntBefore, burntAfter)
   202  		})
   203  	}
   204  }
   205  
   206  func (s *KeeperTestSuite) TestBurnCoins() {
   207  	testCases := map[string]struct {
   208  		contractID string
   209  		amount     collection.Coin
   210  		err        error
   211  	}{
   212  		"valid request": {
   213  			contractID: s.contractID,
   214  			amount:     collection.NewFTCoin(s.ftClassID, sdk.OneInt()),
   215  		},
   216  		"insufficient tokens": {
   217  			contractID: s.contractID,
   218  			amount:     collection.NewFTCoin("00bab10c", sdk.OneInt()),
   219  			err:        collection.ErrInsufficientToken,
   220  		},
   221  	}
   222  
   223  	for name, tc := range testCases {
   224  		s.Run(name, func() {
   225  			ctx, _ := s.ctx.CacheContext()
   226  
   227  			// gather state
   228  			classID := collection.SplitTokenID(tc.amount.TokenId)
   229  			balanceBefore := s.keeper.GetBalance(ctx, tc.contractID, s.vendor, collection.NewFTID(classID))
   230  			supplyBefore := s.keeper.GetSupply(ctx, tc.contractID, classID)
   231  			mintedBefore := s.keeper.GetMinted(ctx, tc.contractID, classID)
   232  			burntBefore := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   233  
   234  			_, err := s.keeper.BurnCoins(ctx, tc.contractID, s.vendor, collection.NewCoins(tc.amount))
   235  			s.Require().ErrorIs(err, tc.err)
   236  			if tc.err != nil {
   237  				return
   238  			}
   239  
   240  			amount := tc.amount.Amount
   241  			balanceAfter := s.keeper.GetBalance(ctx, tc.contractID, s.vendor, collection.NewFTID(classID))
   242  			s.Require().Equal(balanceBefore.Sub(amount), balanceAfter)
   243  			supplyAfter := s.keeper.GetSupply(ctx, tc.contractID, classID)
   244  			s.Require().Equal(supplyBefore.Sub(amount), supplyAfter)
   245  			mintedAfter := s.keeper.GetMinted(ctx, tc.contractID, classID)
   246  			s.Require().Equal(mintedBefore, mintedAfter)
   247  			burntAfter := s.keeper.GetBurnt(ctx, tc.contractID, classID)
   248  			s.Require().Equal(burntBefore.Add(amount), burntAfter)
   249  		})
   250  	}
   251  }
   252  
   253  func (s *KeeperTestSuite) TestModifyContract() {
   254  	contractDescriptions := map[string]string{
   255  		s.contractID: "valid",
   256  		"deadbeef":   "not-exist",
   257  	}
   258  	changes := []collection.Attribute{
   259  		{Key: collection.AttributeKeyName.String(), Value: "fox"},
   260  		{Key: collection.AttributeKeyURI.String(), Value: "file:///fox.png"},
   261  		{Key: collection.AttributeKeyMeta.String(), Value: "Fox"},
   262  	}
   263  
   264  	for contractID, contractDesc := range contractDescriptions {
   265  		name := fmt.Sprintf("Contract: %s", contractDesc)
   266  		s.Run(name, func() {
   267  			ctx, _ := s.ctx.CacheContext()
   268  
   269  			call := func() {
   270  				err := s.keeper.ModifyContract(ctx, contractID, s.vendor, changes)
   271  				s.Require().NoError(err)
   272  			}
   273  
   274  			if contractID != s.contractID {
   275  				s.Require().Panics(call)
   276  				return
   277  			}
   278  			call()
   279  
   280  			contract, err := s.keeper.GetContract(ctx, contractID)
   281  			s.Require().NoError(err)
   282  
   283  			s.Require().Equal(changes[0].Value, contract.Name)
   284  			s.Require().Equal(changes[1].Value, contract.Uri)
   285  			s.Require().Equal(changes[2].Value, contract.Meta)
   286  		})
   287  	}
   288  }
   289  
   290  func (s *KeeperTestSuite) TestModifyTokenClass() {
   291  	classDescriptions := map[string]string{
   292  		s.nftClassID: "valid",
   293  		"deadbeef":   "not-exist",
   294  	}
   295  	changes := []collection.Attribute{
   296  		{Key: collection.AttributeKeyName.String(), Value: "arctic fox"},
   297  		{Key: collection.AttributeKeyMeta.String(), Value: "Arctic Fox"},
   298  	}
   299  
   300  	for classID, classDesc := range classDescriptions {
   301  		name := fmt.Sprintf("Class: %s", classDesc)
   302  		s.Run(name, func() {
   303  			ctx, _ := s.ctx.CacheContext()
   304  
   305  			err := s.keeper.ModifyTokenClass(ctx, s.contractID, classID, s.vendor, changes)
   306  			if classID != s.nftClassID {
   307  				s.Require().ErrorIs(err, collection.ErrTokenTypeNotExist)
   308  				return
   309  			}
   310  			s.Require().NoError(err)
   311  
   312  			class, err := s.keeper.GetTokenClass(ctx, s.contractID, classID)
   313  			s.Require().NoError(err)
   314  
   315  			nftClass, ok := class.(*collection.NFTClass)
   316  			s.Require().True(ok)
   317  
   318  			s.Require().Equal(changes[0].Value, nftClass.Name)
   319  			s.Require().Equal(changes[1].Value, nftClass.Meta)
   320  		})
   321  	}
   322  }
   323  
   324  func (s *KeeperTestSuite) TestModifyNFT() {
   325  	validTokenID := collection.NewNFTID(s.nftClassID, 1)
   326  	tokenDescriptions := map[string]string{
   327  		validTokenID:                       "valid",
   328  		collection.NewNFTID("deadbeef", 1): "not-exist",
   329  	}
   330  	changes := []collection.Attribute{
   331  		{Key: collection.AttributeKeyName.String(), Value: "fennec fox 1"},
   332  		{Key: collection.AttributeKeyMeta.String(), Value: "Fennec Fox 1"},
   333  	}
   334  
   335  	for tokenID, tokenDesc := range tokenDescriptions {
   336  		name := fmt.Sprintf("Token: %s", tokenDesc)
   337  		s.Run(name, func() {
   338  			ctx, _ := s.ctx.CacheContext()
   339  
   340  			err := s.keeper.ModifyNFT(ctx, s.contractID, tokenID, s.vendor, changes)
   341  			if tokenID != validTokenID {
   342  				s.Require().ErrorIs(err, collection.ErrTokenNotExist)
   343  				return
   344  			}
   345  			s.Require().NoError(err)
   346  
   347  			nft, err := s.keeper.GetNFT(ctx, s.contractID, tokenID)
   348  			s.Require().NoError(err)
   349  
   350  			s.Require().Equal(changes[0].Value, nft.Name)
   351  			s.Require().Equal(changes[1].Value, nft.Meta)
   352  		})
   353  	}
   354  }