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 }