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

     1  package keeper
     2  
     3  import (
     4  	gogotypes "github.com/gogo/protobuf/types"
     5  
     6  	sdk "github.com/Finschia/finschia-sdk/types"
     7  	"github.com/Finschia/finschia-sdk/x/collection"
     8  )
     9  
    10  func legacyNFTNotFoundError(k Keeper, ctx sdk.Context, contractID string, tokenID string) error {
    11  	if err2 := collection.ValidateLegacyNFTID(tokenID); err2 == nil /* "==" is intentional */ {
    12  		return collection.ErrTokenNotExist.Wrap(tokenID)
    13  	}
    14  
    15  	if err2 := collection.ValidateFTID(tokenID); err2 != nil {
    16  		return collection.ErrTokenNotExist.Wrap(tokenID)
    17  	}
    18  	classID := collection.SplitTokenID(tokenID)
    19  	if _, err2 := k.GetTokenClass(ctx, contractID, classID); err2 != nil {
    20  		return collection.ErrTokenNotExist.Wrap(tokenID)
    21  	}
    22  
    23  	return collection.ErrTokenNotNFT.Wrap(tokenID)
    24  }
    25  
    26  func (k Keeper) hasNFT(ctx sdk.Context, contractID string, tokenID string) error {
    27  	store := ctx.KVStore(k.storeKey)
    28  	key := nftKey(contractID, tokenID)
    29  	if !store.Has(key) {
    30  		return legacyNFTNotFoundError(k, ctx, contractID, tokenID)
    31  	}
    32  	return nil
    33  }
    34  
    35  func (k Keeper) GetNFT(ctx sdk.Context, contractID string, tokenID string) (*collection.NFT, error) {
    36  	store := ctx.KVStore(k.storeKey)
    37  	key := nftKey(contractID, tokenID)
    38  	bz := store.Get(key)
    39  	if bz == nil {
    40  		return nil, legacyNFTNotFoundError(k, ctx, contractID, tokenID)
    41  	}
    42  
    43  	var token collection.NFT
    44  	k.cdc.MustUnmarshal(bz, &token)
    45  
    46  	return &token, nil
    47  }
    48  
    49  func (k Keeper) setNFT(ctx sdk.Context, contractID string, token collection.NFT) {
    50  	store := ctx.KVStore(k.storeKey)
    51  	key := nftKey(contractID, token.TokenId)
    52  
    53  	bz, err := token.Marshal()
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  	store.Set(key, bz)
    58  }
    59  
    60  func (k Keeper) deleteNFT(ctx sdk.Context, contractID string, tokenID string) {
    61  	store := ctx.KVStore(k.storeKey)
    62  	key := nftKey(contractID, tokenID)
    63  	store.Delete(key)
    64  }
    65  
    66  func (k Keeper) pruneNFT(ctx sdk.Context, contractID string, tokenID string) []string {
    67  	burnt := []string{}
    68  	for _, child := range k.GetChildren(ctx, contractID, tokenID) {
    69  		k.deleteChild(ctx, contractID, tokenID, child)
    70  		k.deleteParent(ctx, contractID, child)
    71  		k.deleteNFT(ctx, contractID, child)
    72  		burnt = append(burnt, child)
    73  
    74  		pruned := k.pruneNFT(ctx, contractID, child)
    75  		burnt = append(burnt, pruned...)
    76  	}
    77  	return burnt
    78  }
    79  
    80  func (k Keeper) Attach(ctx sdk.Context, contractID string, owner sdk.AccAddress, subject, target string) error {
    81  	// validate subject
    82  	if err := k.hasNFT(ctx, contractID, subject); err != nil {
    83  		return err
    84  	}
    85  
    86  	if _, err := k.GetParent(ctx, contractID, subject); err == nil {
    87  		return collection.ErrTokenAlreadyAChild.Wrap(subject)
    88  	}
    89  
    90  	if !k.GetBalance(ctx, contractID, owner, subject).IsPositive() {
    91  		return collection.ErrTokenNotOwnedBy.Wrapf("%s is not owner of %s", owner, subject)
    92  	}
    93  
    94  	// validate target
    95  	if err := k.hasNFT(ctx, contractID, target); err != nil {
    96  		return err
    97  	}
    98  
    99  	root := k.GetRoot(ctx, contractID, target)
   100  	if !owner.Equals(k.getOwner(ctx, contractID, root)) {
   101  		return collection.ErrTokenNotOwnedBy.Wrapf("%s is not owner of %s", owner, target)
   102  	}
   103  	if root == subject {
   104  		return collection.ErrCannotAttachToADescendant.Wrap("cycles not allowed")
   105  	}
   106  
   107  	if err := k.subtractCoins(ctx, contractID, owner, collection.NewCoins(collection.NewCoin(subject, sdk.OneInt()))); err != nil {
   108  		panic(collection.ErrInsufficientToken.Wrapf("%s not owns %s", owner, subject))
   109  	}
   110  
   111  	// update relation
   112  	k.setParent(ctx, contractID, subject, target)
   113  	k.setChild(ctx, contractID, target, subject)
   114  
   115  	// finally, check the invariant
   116  	if err := k.validateDepthAndWidth(ctx, contractID, root); err != nil {
   117  		return err
   118  	}
   119  
   120  	// legacy
   121  	k.iterateDescendants(ctx, contractID, subject, func(descendantID string, _ int) (stop bool) {
   122  		event := collection.EventRootChanged{
   123  			ContractId: contractID,
   124  			TokenId:    descendantID,
   125  			From:       subject,
   126  			To:         root,
   127  		}
   128  		if err := ctx.EventManager().EmitTypedEvent(&event); err != nil {
   129  			panic(err)
   130  		}
   131  		return false
   132  	})
   133  
   134  	return nil
   135  }
   136  
   137  func (k Keeper) Detach(ctx sdk.Context, contractID string, owner sdk.AccAddress, subject string) error {
   138  	if err := k.hasNFT(ctx, contractID, subject); err != nil {
   139  		return err
   140  	}
   141  
   142  	parent, err := k.GetParent(ctx, contractID, subject)
   143  	if err != nil {
   144  		return collection.ErrTokenNotAChild.Wrap(err.Error())
   145  	}
   146  
   147  	if !owner.Equals(k.GetRootOwner(ctx, contractID, subject)) {
   148  		return collection.ErrTokenNotOwnedBy.Wrapf("%s is not owner of %s", owner, subject)
   149  	}
   150  
   151  	k.addCoins(ctx, contractID, owner, collection.NewCoins(collection.NewCoin(subject, sdk.OneInt())))
   152  
   153  	// update relation
   154  	k.deleteParent(ctx, contractID, subject)
   155  	k.deleteChild(ctx, contractID, *parent, subject)
   156  
   157  	// legacy
   158  	root := k.GetRoot(ctx, contractID, *parent)
   159  	k.iterateDescendants(ctx, contractID, subject, func(descendantID string, _ int) (stop bool) {
   160  		event := collection.EventRootChanged{
   161  			ContractId: contractID,
   162  			TokenId:    descendantID,
   163  			From:       root,
   164  			To:         subject,
   165  		}
   166  		if err := ctx.EventManager().EmitTypedEvent(&event); err != nil {
   167  			panic(err)
   168  		}
   169  		return false
   170  	})
   171  
   172  	return nil
   173  }
   174  
   175  func (k Keeper) iterateAncestors(ctx sdk.Context, contractID string, tokenID string, fn func(tokenID string) error) error {
   176  	var err error
   177  	for id := &tokenID; err == nil; id, err = k.GetParent(ctx, contractID, *id) {
   178  		if fnErr := fn(*id); fnErr != nil {
   179  			return fnErr
   180  		}
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (k Keeper) GetRootOwner(ctx sdk.Context, contractID string, tokenID string) sdk.AccAddress {
   187  	rootID := k.GetRoot(ctx, contractID, tokenID)
   188  	return k.getOwner(ctx, contractID, rootID)
   189  }
   190  
   191  func (k Keeper) getOwner(ctx sdk.Context, contractID string, tokenID string) sdk.AccAddress {
   192  	store := ctx.KVStore(k.storeKey)
   193  	key := ownerKey(contractID, tokenID)
   194  	bz := store.Get(key)
   195  	if bz == nil {
   196  		panic("owner must exist")
   197  	}
   198  
   199  	var owner sdk.AccAddress
   200  	if err := owner.Unmarshal(bz); err != nil {
   201  		panic(err)
   202  	}
   203  	return owner
   204  }
   205  
   206  func (k Keeper) setOwner(ctx sdk.Context, contractID string, tokenID string, owner sdk.AccAddress) {
   207  	store := ctx.KVStore(k.storeKey)
   208  	key := ownerKey(contractID, tokenID)
   209  
   210  	bz, err := owner.Marshal()
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  	store.Set(key, bz)
   215  }
   216  
   217  func (k Keeper) deleteOwner(ctx sdk.Context, contractID string, tokenID string) {
   218  	store := ctx.KVStore(k.storeKey)
   219  	key := ownerKey(contractID, tokenID)
   220  	store.Delete(key)
   221  }
   222  
   223  func (k Keeper) GetParent(ctx sdk.Context, contractID string, tokenID string) (*string, error) {
   224  	store := ctx.KVStore(k.storeKey)
   225  	key := parentKey(contractID, tokenID)
   226  	bz := store.Get(key)
   227  	if bz == nil {
   228  		return nil, collection.ErrTokenNotAChild.Wrapf("%s has no parent", tokenID)
   229  	}
   230  
   231  	var parent gogotypes.StringValue
   232  	k.cdc.MustUnmarshal(bz, &parent)
   233  	return &parent.Value, nil
   234  }
   235  
   236  func (k Keeper) setParent(ctx sdk.Context, contractID string, tokenID, parentID string) {
   237  	store := ctx.KVStore(k.storeKey)
   238  	key := parentKey(contractID, tokenID)
   239  
   240  	val := &gogotypes.StringValue{Value: parentID}
   241  	bz := k.cdc.MustMarshal(val)
   242  	store.Set(key, bz)
   243  }
   244  
   245  func (k Keeper) deleteParent(ctx sdk.Context, contractID string, tokenID string) {
   246  	store := ctx.KVStore(k.storeKey)
   247  	key := parentKey(contractID, tokenID)
   248  	store.Delete(key)
   249  }
   250  
   251  func (k Keeper) GetChildren(ctx sdk.Context, contractID string, tokenID string) []string {
   252  	var children []string
   253  	k.iterateChildren(ctx, contractID, tokenID, func(childID string) (stop bool) {
   254  		children = append(children, childID)
   255  		return false
   256  	})
   257  	return children
   258  }
   259  
   260  func (k Keeper) iterateChildren(ctx sdk.Context, contractID string, tokenID string, fn func(childID string) (stop bool)) {
   261  	k.iterateChildrenImpl(ctx, childKeyPrefixByTokenID(contractID, tokenID), func(_ string, _ string, childID string) (stop bool) {
   262  		return fn(childID)
   263  	})
   264  }
   265  
   266  func (k Keeper) iterateDescendants(ctx sdk.Context, contractID string, tokenID string, fn func(descendantID string, depth int) (stop bool)) {
   267  	k.iterateDescendantsImpl(ctx, contractID, tokenID, 1, fn)
   268  }
   269  
   270  func (k Keeper) iterateDescendantsImpl(ctx sdk.Context, contractID string, tokenID string, depth int, fn func(descendantID string, depth int) (stop bool)) {
   271  	k.iterateChildren(ctx, contractID, tokenID, func(childID string) (stop bool) {
   272  		if stop := fn(childID, depth); stop {
   273  			return true
   274  		}
   275  
   276  		k.iterateDescendantsImpl(ctx, contractID, childID, depth+1, fn)
   277  		return false
   278  	})
   279  }
   280  
   281  func (k Keeper) setChild(ctx sdk.Context, contractID string, tokenID, childID string) {
   282  	store := ctx.KVStore(k.storeKey)
   283  	key := childKey(contractID, tokenID, childID)
   284  	store.Set(key, []byte{})
   285  }
   286  
   287  func (k Keeper) deleteChild(ctx sdk.Context, contractID string, tokenID, childID string) {
   288  	store := ctx.KVStore(k.storeKey)
   289  	key := childKey(contractID, tokenID, childID)
   290  	store.Delete(key)
   291  }
   292  
   293  func (k Keeper) GetRoot(ctx sdk.Context, contractID string, tokenID string) string {
   294  	id := tokenID
   295  	k.iterateAncestors(ctx, contractID, tokenID, func(tokenID string) error {
   296  		id = tokenID
   297  		return nil
   298  	})
   299  
   300  	return id
   301  }
   302  
   303  // Deprecated
   304  func (k Keeper) setLegacyToken(ctx sdk.Context, contractID string, tokenID string) {
   305  	store := ctx.KVStore(k.storeKey)
   306  	key := legacyTokenKey(contractID, tokenID)
   307  	store.Set(key, []byte{})
   308  }
   309  
   310  // Deprecated
   311  func (k Keeper) deleteLegacyToken(ctx sdk.Context, contractID string, tokenID string) {
   312  	store := ctx.KVStore(k.storeKey)
   313  	key := legacyTokenKey(contractID, tokenID)
   314  	store.Delete(key)
   315  }
   316  
   317  // Deprecated
   318  func (k Keeper) setLegacyTokenType(ctx sdk.Context, contractID string, tokenType string) {
   319  	store := ctx.KVStore(k.storeKey)
   320  	key := legacyTokenTypeKey(contractID, tokenType)
   321  	store.Set(key, []byte{})
   322  }
   323  
   324  // Deprecated
   325  func (k Keeper) validateDepthAndWidth(ctx sdk.Context, contractID string, tokenID string) error {
   326  	widths := map[int]int{0: 1}
   327  	k.iterateDescendants(ctx, contractID, tokenID, func(descendantID string, depth int) (stop bool) {
   328  		widths[depth]++
   329  		return false
   330  	})
   331  
   332  	params := k.GetParams(ctx)
   333  
   334  	depth := len(widths)
   335  	if legacyDepth := depth - 1; legacyDepth > int(params.DepthLimit) {
   336  		return collection.ErrCompositionTooDeep.Wrapf("resulting depth exceeds its limit: %d", params.DepthLimit)
   337  	}
   338  
   339  	for _, width := range widths {
   340  		if width > int(params.WidthLimit) {
   341  			return collection.ErrCompositionTooWide.Wrapf("resulting width exceeds its limit: %d", params.WidthLimit)
   342  		}
   343  	}
   344  
   345  	return nil
   346  }