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  }