github.com/Finschia/finschia-sdk@v0.48.1/x/collection/keeper/supply.go (about) 1 package keeper 2 3 import ( 4 sdk "github.com/Finschia/finschia-sdk/types" 5 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 6 "github.com/Finschia/finschia-sdk/x/collection" 7 ) 8 9 func (k Keeper) CreateContract(ctx sdk.Context, creator sdk.AccAddress, contract collection.Contract) string { 10 contractID := k.createContract(ctx, contract) 11 12 event := collection.EventCreatedContract{ 13 Creator: creator.String(), 14 ContractId: contractID, 15 Name: contract.Name, 16 Meta: contract.Meta, 17 Uri: contract.Uri, 18 } 19 if err := ctx.EventManager().EmitTypedEvent(&event); err != nil { 20 panic(err) 21 } 22 23 // 0 is "unspecified" 24 for i := 1; i < len(collection.Permission_value); i++ { 25 p := collection.Permission(i) 26 27 k.Grant(ctx, contractID, []byte{}, creator, p) 28 } 29 30 return contractID 31 } 32 33 func (k Keeper) createContract(ctx sdk.Context, contract collection.Contract) string { 34 contractID := k.classKeeper.NewID(ctx) 35 contract.Id = contractID 36 k.setContract(ctx, contract) 37 38 // set the next class ids 39 nextIDs := collection.DefaultNextClassIDs(contractID) 40 k.setNextClassIDs(ctx, nextIDs) 41 42 return contractID 43 } 44 45 func (k Keeper) GetContract(ctx sdk.Context, contractID string) (*collection.Contract, error) { 46 store := ctx.KVStore(k.storeKey) 47 key := contractKey(contractID) 48 bz := store.Get(key) 49 if bz == nil { 50 return nil, collection.ErrCollectionNotExist.Wrapf("no such a contract: %s", contractID) 51 } 52 53 var contract collection.Contract 54 if err := contract.Unmarshal(bz); err != nil { 55 panic(err) 56 } 57 return &contract, nil 58 } 59 60 func (k Keeper) setContract(ctx sdk.Context, contract collection.Contract) { 61 store := ctx.KVStore(k.storeKey) 62 key := contractKey(contract.Id) 63 64 bz, err := contract.Marshal() 65 if err != nil { 66 panic(err) 67 } 68 store.Set(key, bz) 69 } 70 71 func (k Keeper) CreateTokenClass(ctx sdk.Context, contractID string, class collection.TokenClass) (*string, error) { 72 if _, err := k.GetContract(ctx, contractID); err != nil { 73 panic(err) 74 } 75 76 nextClassIDs := k.getNextClassIDs(ctx, contractID) 77 class.SetId(&nextClassIDs) 78 k.setNextClassIDs(ctx, nextClassIDs) 79 80 if err := class.ValidateBasic(); err != nil { 81 return nil, err 82 } 83 k.setTokenClass(ctx, contractID, class) 84 85 if nftClass, ok := class.(*collection.NFTClass); ok { 86 k.setNextTokenID(ctx, contractID, nftClass.Id, sdk.OneUint()) 87 88 // legacy 89 k.setLegacyTokenType(ctx, contractID, nftClass.Id) 90 } 91 92 if ftClass, ok := class.(*collection.FTClass); ok { 93 // legacy 94 k.setLegacyToken(ctx, contractID, collection.NewFTID(ftClass.Id)) 95 } 96 97 id := class.GetId() 98 return &id, nil 99 } 100 101 func (k Keeper) GetTokenClass(ctx sdk.Context, contractID, classID string) (collection.TokenClass, error) { 102 store := ctx.KVStore(k.storeKey) 103 key := classKey(contractID, classID) 104 bz := store.Get(key) 105 if bz == nil { 106 return nil, sdkerrors.ErrNotFound.Wrapf("no such a class in contract %s: %s", contractID, classID) 107 } 108 109 var class collection.TokenClass 110 if err := k.cdc.UnmarshalInterface(bz, &class); err != nil { 111 panic(err) 112 } 113 return class, nil 114 } 115 116 func (k Keeper) setTokenClass(ctx sdk.Context, contractID string, class collection.TokenClass) { 117 store := ctx.KVStore(k.storeKey) 118 key := classKey(contractID, class.GetId()) 119 120 bz, err := k.cdc.MarshalInterface(class) 121 if err != nil { 122 panic(err) 123 } 124 store.Set(key, bz) 125 } 126 127 func (k Keeper) getNextClassIDs(ctx sdk.Context, contractID string) collection.NextClassIDs { 128 store := ctx.KVStore(k.storeKey) 129 key := nextClassIDKey(contractID) 130 bz := store.Get(key) 131 if bz == nil { 132 panic(sdkerrors.ErrNotFound.Wrapf("no next class ids of contract %s", contractID)) 133 } 134 135 var class collection.NextClassIDs 136 if err := class.Unmarshal(bz); err != nil { 137 panic(err) 138 } 139 return class 140 } 141 142 func (k Keeper) setNextClassIDs(ctx sdk.Context, ids collection.NextClassIDs) { 143 store := ctx.KVStore(k.storeKey) 144 key := nextClassIDKey(ids.ContractId) 145 146 bz, err := ids.Marshal() 147 if err != nil { 148 panic(err) 149 } 150 store.Set(key, bz) 151 } 152 153 func (k Keeper) MintFT(ctx sdk.Context, contractID string, to sdk.AccAddress, amount []collection.Coin) error { 154 for _, coin := range amount { 155 if err := collection.ValidateFTID(coin.TokenId); err != nil { 156 // legacy 157 if err := k.hasNFT(ctx, contractID, coin.TokenId); err != nil { 158 return err 159 } 160 161 return collection.ErrTokenNotMintable.Wrap(err.Error()) 162 } 163 164 classID := collection.SplitTokenID(coin.TokenId) 165 class, err := k.GetTokenClass(ctx, contractID, classID) 166 if err != nil { 167 return collection.ErrTokenNotExist.Wrap(err.Error()) 168 } 169 170 ftClass, ok := class.(*collection.FTClass) 171 if !ok { 172 return collection.ErrTokenNotMintable.Wrapf("not a class of fungible token: %s", classID) 173 } 174 175 if !ftClass.Mintable { 176 return collection.ErrTokenNotMintable.Wrapf("class is not mintable") 177 } 178 179 k.mintFT(ctx, contractID, to, classID, coin.Amount) 180 } 181 182 return nil 183 } 184 185 func (k Keeper) mintFT(ctx sdk.Context, contractID string, to sdk.AccAddress, classID string, amount sdk.Int) { 186 coins := collection.NewCoins(collection.NewFTCoin(classID, amount)) 187 k.addCoins(ctx, contractID, to, coins) 188 189 // update statistics 190 supply := k.GetSupply(ctx, contractID, classID) 191 k.setSupply(ctx, contractID, classID, supply.Add(amount)) 192 193 minted := k.GetMinted(ctx, contractID, classID) 194 k.setMinted(ctx, contractID, classID, minted.Add(amount)) 195 } 196 197 func (k Keeper) MintNFT(ctx sdk.Context, contractID string, to sdk.AccAddress, params []collection.MintNFTParam) ([]collection.NFT, error) { 198 tokens := make([]collection.NFT, 0, len(params)) 199 for _, param := range params { 200 classID := param.TokenType 201 class, err := k.GetTokenClass(ctx, contractID, classID) 202 if err != nil { 203 return nil, collection.ErrTokenTypeNotExist.Wrap(err.Error()) 204 } 205 206 if _, ok := class.(*collection.NFTClass); !ok { 207 return nil, collection.ErrTokenTypeNotExist.Wrapf("not a class of non-fungible token: %s", classID) 208 } 209 210 nextTokenID := k.getNextTokenID(ctx, contractID, classID) 211 k.setNextTokenID(ctx, contractID, classID, nextTokenID.Incr()) 212 tokenID := collection.NewNFTID(classID, int(nextTokenID.Uint64())) 213 214 amount := sdk.OneInt() 215 216 k.setBalance(ctx, contractID, to, tokenID, amount) 217 k.setOwner(ctx, contractID, tokenID, to) 218 219 token := collection.NFT{ 220 TokenId: tokenID, 221 Name: param.Name, 222 Meta: param.Meta, 223 } 224 k.setNFT(ctx, contractID, token) 225 226 // update statistics 227 supply := k.GetSupply(ctx, contractID, classID) 228 k.setSupply(ctx, contractID, classID, supply.Add(amount)) 229 230 minted := k.GetMinted(ctx, contractID, classID) 231 k.setMinted(ctx, contractID, classID, minted.Add(amount)) 232 233 tokens = append(tokens, token) 234 235 // legacy 236 k.setLegacyToken(ctx, contractID, tokenID) 237 } 238 239 return tokens, nil 240 } 241 242 func (k Keeper) BurnCoins(ctx sdk.Context, contractID string, from sdk.AccAddress, amount []collection.Coin) ([]collection.Coin, error) { 243 if err := k.subtractCoins(ctx, contractID, from, amount); err != nil { 244 return nil, err 245 } 246 247 burntAmount := []collection.Coin{} 248 for _, coin := range amount { 249 burntAmount = append(burntAmount, coin) 250 if err := collection.ValidateNFTID(coin.TokenId); err == nil { 251 k.deleteNFT(ctx, contractID, coin.TokenId) 252 pruned := k.pruneNFT(ctx, contractID, coin.TokenId) 253 254 for _, id := range pruned { 255 burntAmount = append(burntAmount, collection.NewCoin(id, sdk.OneInt())) 256 } 257 258 // legacy 259 k.deleteLegacyToken(ctx, contractID, coin.TokenId) 260 } 261 } 262 263 // update statistics 264 for _, coin := range burntAmount { 265 classID := collection.SplitTokenID(coin.TokenId) 266 supply := k.GetSupply(ctx, contractID, classID) 267 k.setSupply(ctx, contractID, classID, supply.Sub(coin.Amount)) 268 269 burnt := k.GetBurnt(ctx, contractID, classID) 270 k.setBurnt(ctx, contractID, classID, burnt.Add(coin.Amount)) 271 } 272 273 return burntAmount, nil 274 } 275 276 func (k Keeper) getNextTokenID(ctx sdk.Context, contractID string, classID string) sdk.Uint { 277 store := ctx.KVStore(k.storeKey) 278 key := nextTokenIDKey(contractID, classID) 279 bz := store.Get(key) 280 if bz == nil { 281 panic(sdkerrors.ErrNotFound.Wrapf("no next token id of token class %s", classID)) 282 } 283 284 var id sdk.Uint 285 if err := id.Unmarshal(bz); err != nil { 286 panic(err) 287 } 288 return id 289 } 290 291 func (k Keeper) setNextTokenID(ctx sdk.Context, contractID string, classID string, tokenID sdk.Uint) { 292 store := ctx.KVStore(k.storeKey) 293 key := nextTokenIDKey(contractID, classID) 294 295 bz, err := tokenID.Marshal() 296 if err != nil { 297 panic(err) 298 } 299 store.Set(key, bz) 300 } 301 302 func (k Keeper) ModifyContract(ctx sdk.Context, contractID string, operator sdk.AccAddress, changes []collection.Attribute) error { 303 contract, err := k.GetContract(ctx, contractID) 304 if err != nil { 305 panic(err) 306 } 307 308 modifiers := map[collection.AttributeKey]func(string){ 309 collection.AttributeKeyName: func(name string) { 310 contract.Name = name 311 }, 312 collection.AttributeKeyURI: func(uri string) { 313 contract.Uri = uri 314 }, 315 collection.AttributeKeyMeta: func(meta string) { 316 contract.Meta = meta 317 }, 318 } 319 for _, change := range changes { 320 key := collection.AttributeKeyFromString(change.Key) 321 modifiers[key](change.Value) 322 } 323 324 k.setContract(ctx, *contract) 325 326 return nil 327 } 328 329 func (k Keeper) ModifyTokenClass(ctx sdk.Context, contractID string, classID string, operator sdk.AccAddress, changes []collection.Attribute) error { 330 class, err := k.GetTokenClass(ctx, contractID, classID) 331 if err != nil { 332 // legacy error split 333 if err := collection.ValidateLegacyFTClassID(classID); err == nil { 334 return collection.ErrTokenNotExist.Wrap(collection.NewFTID(classID)) 335 } 336 337 if err := collection.ValidateLegacyNFTClassID(classID); err == nil { 338 return collection.ErrTokenTypeNotExist.Wrap(classID) 339 } 340 341 panic(err) 342 } 343 344 modifiers := map[collection.AttributeKey]func(string){ 345 collection.AttributeKeyName: func(name string) { 346 class.SetName(name) 347 }, 348 collection.AttributeKeyMeta: func(meta string) { 349 class.SetMeta(meta) 350 }, 351 } 352 for _, change := range changes { 353 key := collection.AttributeKeyFromString(change.Key) 354 modifiers[key](change.Value) 355 } 356 357 k.setTokenClass(ctx, contractID, class) 358 359 return nil 360 } 361 362 func (k Keeper) ModifyNFT(ctx sdk.Context, contractID string, tokenID string, operator sdk.AccAddress, changes []collection.Attribute) error { 363 token, err := k.GetNFT(ctx, contractID, tokenID) 364 if err != nil { 365 return err 366 } 367 368 modifiers := map[collection.AttributeKey]func(string){ 369 collection.AttributeKeyName: func(name string) { 370 token.Name = name 371 }, 372 collection.AttributeKeyMeta: func(meta string) { 373 token.Meta = meta 374 }, 375 } 376 for _, change := range changes { 377 key := collection.AttributeKeyFromString(change.Key) 378 modifiers[key](change.Value) 379 } 380 381 k.setNFT(ctx, contractID, *token) 382 383 return nil 384 } 385 386 func (k Keeper) Grant(ctx sdk.Context, contractID string, granter, grantee sdk.AccAddress, permission collection.Permission) { 387 k.grant(ctx, contractID, grantee, permission) 388 389 event := collection.EventGranted{ 390 ContractId: contractID, 391 Granter: granter.String(), 392 Grantee: grantee.String(), 393 Permission: permission, 394 } 395 if err := ctx.EventManager().EmitTypedEvent(&event); err != nil { 396 panic(err) 397 } 398 } 399 400 func (k Keeper) grant(ctx sdk.Context, contractID string, grantee sdk.AccAddress, permission collection.Permission) { 401 k.setGrant(ctx, contractID, grantee, permission) 402 } 403 404 func (k Keeper) Abandon(ctx sdk.Context, contractID string, grantee sdk.AccAddress, permission collection.Permission) { 405 k.deleteGrant(ctx, contractID, grantee, permission) 406 407 event := collection.EventRenounced{ 408 ContractId: contractID, 409 Grantee: grantee.String(), 410 Permission: permission, 411 } 412 if err := ctx.EventManager().EmitTypedEvent(&event); err != nil { 413 panic(err) 414 } 415 } 416 417 func (k Keeper) GetGrant(ctx sdk.Context, contractID string, grantee sdk.AccAddress, permission collection.Permission) (*collection.Grant, error) { 418 store := ctx.KVStore(k.storeKey) 419 if store.Has(grantKey(contractID, grantee, permission)) { 420 return &collection.Grant{ 421 Grantee: grantee.String(), 422 Permission: permission, 423 }, nil 424 } 425 return nil, sdkerrors.ErrNotFound.Wrapf("no %s permission granted on %s", permission, grantee) 426 } 427 428 func (k Keeper) setGrant(ctx sdk.Context, contractID string, grantee sdk.AccAddress, permission collection.Permission) { 429 store := ctx.KVStore(k.storeKey) 430 key := grantKey(contractID, grantee, permission) 431 store.Set(key, []byte{}) 432 } 433 434 func (k Keeper) deleteGrant(ctx sdk.Context, contractID string, grantee sdk.AccAddress, permission collection.Permission) { 435 store := ctx.KVStore(k.storeKey) 436 key := grantKey(contractID, grantee, permission) 437 store.Delete(key) 438 } 439 440 func (k Keeper) getStatistic(ctx sdk.Context, keyPrefix []byte, contractID string, classID string) sdk.Int { 441 store := ctx.KVStore(k.storeKey) 442 amount := sdk.ZeroInt() 443 bz := store.Get(statisticKey(keyPrefix, contractID, classID)) 444 if bz != nil { 445 if err := amount.Unmarshal(bz); err != nil { 446 panic(err) 447 } 448 } 449 450 return amount 451 } 452 453 func (k Keeper) setStatistic(ctx sdk.Context, keyPrefix []byte, contractID string, classID string, amount sdk.Int) { 454 store := ctx.KVStore(k.storeKey) 455 key := statisticKey(keyPrefix, contractID, classID) 456 if amount.IsZero() { 457 store.Delete(key) 458 } else { 459 bz, err := amount.Marshal() 460 if err != nil { 461 panic(err) 462 } 463 store.Set(key, bz) 464 } 465 } 466 467 func (k Keeper) GetSupply(ctx sdk.Context, contractID string, classID string) sdk.Int { 468 return k.getStatistic(ctx, supplyKeyPrefix, contractID, classID) 469 } 470 471 func (k Keeper) GetMinted(ctx sdk.Context, contractID string, classID string) sdk.Int { 472 return k.getStatistic(ctx, mintedKeyPrefix, contractID, classID) 473 } 474 475 func (k Keeper) GetBurnt(ctx sdk.Context, contractID string, classID string) sdk.Int { 476 return k.getStatistic(ctx, burntKeyPrefix, contractID, classID) 477 } 478 479 func (k Keeper) setSupply(ctx sdk.Context, contractID string, classID string, amount sdk.Int) { 480 k.setStatistic(ctx, supplyKeyPrefix, contractID, classID, amount) 481 } 482 483 func (k Keeper) setMinted(ctx sdk.Context, contractID string, classID string, amount sdk.Int) { 484 k.setStatistic(ctx, mintedKeyPrefix, contractID, classID, amount) 485 } 486 487 func (k Keeper) setBurnt(ctx sdk.Context, contractID string, classID string, amount sdk.Int) { 488 k.setStatistic(ctx, burntKeyPrefix, contractID, classID, amount) 489 }