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 }