public override IAccountStateDelta Execute(IActionContext context) { var states = context.PreviousStates; if (context.Rehearsal) { states = states.SetState(sellerAvatarAddress, MarkChanged); states = ShardedShopState.AddressKeys.Aggregate( states, (current, addressKey) => current.SetState( ShardedShopState.DeriveAddress(itemSubType, addressKey), MarkChanged)); return(states.SetState(context.Signer, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell exec started", addressesHex); if (price.Sign < 0) { throw new InvalidPriceException( $"{addressesHex}Aborted as the price is less than zero: {price}."); } if (!states.TryGetAgentAvatarStates( context.Signer, sellerAvatarAddress, out _, out var avatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } sw.Stop(); Log.Verbose( "{AddressesHex}Sell Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared( GameConfig.RequireClearedStageLevel.ActionsInShop)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } sw.Stop(); Log.Verbose("{AddressesHex}Sell IsStageCleared: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); switch (itemSubType) { case ItemSubType.EquipmentMaterial: case ItemSubType.FoodMaterial: case ItemSubType.MonsterPart: case ItemSubType.NormalMaterial: throw new InvalidShopItemException( $"{addressesHex}Aborted because {nameof(itemSubType)}({itemSubType}) does not support."); } if (count < 1) { throw new InvalidShopItemException( $"{addressesHex}Aborted because {nameof(count)}({count}) should be greater than or equal to 1."); } if (!avatarState.inventory.TryGetTradableItems(tradableId, context.BlockIndex, count, out List <Inventory.Item> inventoryItems)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted because the tradable item({tradableId}) was failed to load from avatar's inventory."); } IEnumerable <ITradableItem> tradableItems = inventoryItems.Select(i => (ITradableItem)i.item).ToList(); var expiredBlockIndex = context.BlockIndex + ExpiredBlockIndex; foreach (var ti in tradableItems) { if (!ti.ItemSubType.Equals(itemSubType)) { throw new InvalidItemTypeException( $"{addressesHex}Expected ItemSubType: {ti.ItemSubType}. Actual ItemSubType: {itemSubType}"); } if (ti is INonFungibleItem) { if (count != 1) { throw new ArgumentOutOfRangeException( $"{addressesHex}Aborted because {nameof(count)}({count}) should be 1 because {nameof(tradableId)}({tradableId}) is non-fungible item."); } } } ITradableItem tradableItem = avatarState.inventory.SellItem(tradableId, context.BlockIndex, count); var productId = context.Random.GenerateRandomGuid(); var shardedShopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (!states.TryGetState(shardedShopAddress, out BxDictionary serializedSharedShopState)) { var shardedShopState = new ShardedShopState(shardedShopAddress); serializedSharedShopState = (BxDictionary)shardedShopState.Serialize(); } sw.Stop(); Log.Verbose( "{AddressesHex}Sell Get ShardedShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var serializedProductList = (BxList)serializedSharedShopState[ProductsKey]; string productKey; string itemIdKey; string requiredBlockIndexKey; switch (tradableItem.ItemType) { case ItemType.Consumable: case ItemType.Equipment: productKey = LegacyItemUsableKey; itemIdKey = LegacyItemIdKey; requiredBlockIndexKey = LegacyRequiredBlockIndexKey; break; case ItemType.Costume: productKey = LegacyCostumeKey; itemIdKey = LegacyCostumeItemIdKey; requiredBlockIndexKey = RequiredBlockIndexKey; break; case ItemType.Material: productKey = TradableFungibleItemKey; itemIdKey = LegacyCostumeItemIdKey; requiredBlockIndexKey = RequiredBlockIndexKey; break; default: throw new ArgumentOutOfRangeException(); } BxDictionary serializedProductDictionary; if (tradableItem.ItemType == ItemType.Material) { // Find expired TradableMaterial serializedProductDictionary = serializedProductList .Select(p => (BxDictionary)p) .FirstOrDefault(p => { var materialItemId = ((BxDictionary)p[productKey])[itemIdKey].ToItemId(); var requiredBlockIndex = p[ExpiredBlockIndexKey].ToLong(); return(TradableMaterial.DeriveTradableId(materialItemId) .Equals(tradableItem.TradableId) && requiredBlockIndex <= context.BlockIndex); }); } else { var serializedTradeId = tradableItem.TradableId.Serialize(); serializedProductDictionary = serializedProductList .Select(p => (BxDictionary)p) .FirstOrDefault(p => ((BxDictionary)p[productKey])[itemIdKey].Equals(serializedTradeId)); } // Since Bencodex 0.4, Dictionary/List are reference types; so their default values // are not a empty container, but a null reference: serializedProductDictionary = serializedProductDictionary ?? Dictionary.Empty; ShopItem shopItem; // Register new ShopItem if (serializedProductDictionary.Equals(BxDictionary.Empty)) { shopItem = new ShopItem( context.Signer, sellerAvatarAddress, productId, price, expiredBlockIndex, tradableItem, count); var serializedShopItem = shopItem.Serialize(); serializedProductList = serializedProductList.Add(serializedShopItem); } // Update Registered ShopItem else { // Delete current ShopItem serializedProductList = (BxList)serializedProductList.Remove(serializedProductDictionary); // Update ITradableItem.RequiredBlockIndex var inChainShopItem = (BxDictionary)serializedProductDictionary[productKey]; inChainShopItem = inChainShopItem .SetItem(requiredBlockIndexKey, expiredBlockIndex.Serialize()); // Update ShopItem.ExpiredBlockIndex serializedProductDictionary = serializedProductDictionary .SetItem(ExpiredBlockIndexKey, expiredBlockIndex.Serialize()) .SetItem(productKey, inChainShopItem); // Update only Material for backwardCompatible. if (tradableItem.ItemType == ItemType.Material) { serializedProductDictionary = serializedProductDictionary .SetItem(TradableFungibleItemCountKey, count.Serialize()); } serializedProductList = serializedProductList.Add(serializedProductDictionary); shopItem = new ShopItem(serializedProductDictionary); } serializedSharedShopState = serializedSharedShopState.SetItem( ProductsKey, new List <IValue>(serializedProductList)); sw.Stop(); Log.Verbose("{AddressesHex}Sell Get Register Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); avatarState.updatedAt = context.BlockIndex; avatarState.blockIndex = context.BlockIndex; var result = new SellCancellation.Result { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, tradableFungibleItem = shopItem.TradableFungibleItem, tradableFungibleItemCount = shopItem.TradableFungibleItemCount, }; var mail = new SellCancelMail( result, context.BlockIndex, context.Random.GenerateRandomGuid(), expiredBlockIndex); result.id = mail.id; avatarState.Update(mail); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, serializedSharedShopState); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose( "{AddressesHex}Sell Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states.SetState(ShopState.Address, MarkChanged); return(states.SetState(sellerAvatarAddress, MarkChanged)); } var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Cancel exec started", addressesHex); if (!states.TryGetAgentAvatarStates(ctx.Signer, sellerAvatarAddress, out _, out var avatarState)) { return(states); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex( out var world)) { return(states); } if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInShop) { // 스테이지 클리어 부족 에러. return(states); } if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict)) { return(states); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // 상점에서 아이템을 빼온다. Dictionary products = (Dictionary)shopStateDict["products"]; IKey productIdSerialized = (IKey)productId.Serialize(); if (!products.ContainsKey(productIdSerialized)) { return(states); } ShopItem outUnregisteredItem = new ShopItem((Dictionary)products[productIdSerialized]); products = (Dictionary)products.Remove(productIdSerialized); shopStateDict = shopStateDict.SetItem("products", products); sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get Unregister Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); //9c-beta 브랜치에서는 블록 인덱스도 확인 해야함 (이전 블록 유효성 보장) if (outUnregisteredItem.SellerAvatarAddress != sellerAvatarAddress) { Log.Error("{AddressesHex}Invalid Avatar Address", addressesHex); return(states); } // 메일에 아이템을 넣는다. result = new SellCancellation.Result { shopItem = outUnregisteredItem, itemUsable = outUnregisteredItem.ItemUsable, costume = outUnregisteredItem.Costume }; var mail = new SellCancelMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); result.id = mail.id; avatarState.Update(mail); if (result.itemUsable != null) { avatarState.UpdateFromAddItem(result.itemUsable, true); } if (result.costume != null) { avatarState.UpdateFromAddCostume(result.costume, true); } avatarState.updatedAt = ctx.BlockIndex; avatarState.blockIndex = ctx.BlockIndex; sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Update AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(ShopState.Address, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Cancel Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}Sell Cancel Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states.SetState(ShopState.Address, MarkChanged); states = states.SetState(sellerAvatarAddress, MarkChanged); return(states.SetState(ctx.Signer, MarkChanged)); } var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell exec started", addressesHex); if (price.Sign < 0) { throw new InvalidPriceException($"{addressesHex}Aborted as the price is less than zero: {price}."); } if (!states.TryGetAgentAvatarStates(ctx.Signer, sellerAvatarAddress, out AgentState agentState, out AvatarState avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } Log.Verbose("{AddressesHex}Sell IsStageCleared: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the shop state was failed to load."); } Log.Verbose("{AddressesHex}Sell Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose("{AddressesHex}Execute Sell; seller: {SellerAvatarAddress}", addressesHex, sellerAvatarAddress); var productId = context.Random.GenerateRandomGuid(); long expiredBlockIndex = context.BlockIndex + ExpiredBlockIndex; // Select an item to sell from the inventory and adjust the quantity. if (!avatarState.inventory.TryGetNonFungibleItem(itemId, out INonFungibleItem nonFungibleItem)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the NonFungibleItem ({itemId}) was failed to load from avatar's inventory."); } if (nonFungibleItem.RequiredBlockIndex > context.BlockIndex) { throw new RequiredBlockIndexException( $"{addressesHex}Aborted as the itemUsable to sell ({itemId}) is not available yet; it will be available at the block #{nonFungibleItem.RequiredBlockIndex}."); } if (nonFungibleItem is Equipment equipment) { equipment.Unequip(); } nonFungibleItem.Update(expiredBlockIndex); string productKey = nonFungibleItem is ItemUsable ? "itemUsable" : "costume"; string itemIdKey = nonFungibleItem is ItemUsable ? ItemUsable.ItemIdKey : Costume.ItemIdKey; ShopItem shopItem; Dictionary products = (Dictionary)shopStateDict["products"]; #pragma warning disable LAA1002 var productSerialized = products .Select(p => (Dictionary)p.Value) .Where(p => p.ContainsKey(productKey)) .FirstOrDefault(p => ((Dictionary)p[productKey])[itemIdKey].Equals(nonFungibleItem.ItemId.Serialize())); #pragma warning restore LAA1002 // Register new ShopItem if (productSerialized.Equals(Dictionary.Empty)) { shopItem = new ShopItem(ctx.Signer, sellerAvatarAddress, productId, price, expiredBlockIndex, nonFungibleItem); IValue shopItemSerialized = shopItem.Serialize(); IKey productIdSerialized = (IKey)productId.Serialize(); products = (Dictionary)products.Add(productIdSerialized, shopItemSerialized); } // Update Registered ShopItem else { Dictionary item = (Dictionary)productSerialized[productKey]; string updateKey = nonFungibleItem is ItemUsable ? "requiredBlockIndex" : Costume.RequiredBlockIndexKey; item = item.SetItem(updateKey, expiredBlockIndex.Serialize()); productSerialized = productSerialized .SetItem(ShopItem.ExpiredBlockIndexKey, expiredBlockIndex.Serialize()) .SetItem(productKey, item); products = (Dictionary)products.SetItem((IKey)productSerialized["productId"], productSerialized); shopItem = new ShopItem(productSerialized); } shopStateDict = shopStateDict.SetItem("products", products); sw.Stop(); Log.Verbose("{AddressesHex}Sell Get Register Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); avatarState.updatedAt = ctx.BlockIndex; avatarState.blockIndex = ctx.BlockIndex; var result = new SellCancellation.Result { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume }; var mail = new SellCancelMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), expiredBlockIndex); result.id = mail.id; avatarState.UpdateV4(mail, context.BlockIndex); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(ShopState.Address, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}Sell Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { states = states.SetState(sellerAvatarAddress, MarkChanged); states = ShardedShopState.AddressKeys.Aggregate(states, (current, addressKey) => current.SetState(ShardedShopState.DeriveAddress(itemSubType, addressKey), MarkChanged)); return(states .SetState(ctx.Signer, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell exec started", addressesHex); if (price.Sign < 0) { throw new InvalidPriceException($"{addressesHex}Aborted as the price is less than zero: {price}."); } if (!states.TryGetAgentAvatarStates(ctx.Signer, sellerAvatarAddress, out AgentState agentState, out AvatarState avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } Log.Verbose("{AddressesHex}Sell IsStageCleared: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose("{AddressesHex}Execute Sell; seller: {SellerAvatarAddress}", addressesHex, sellerAvatarAddress); var productId = context.Random.GenerateRandomGuid(); long expiredBlockIndex = context.BlockIndex + ExpiredBlockIndex; // Select an item to sell from the inventory and adjust the quantity. if (!avatarState.inventory.TryGetNonFungibleItem(itemId, out INonFungibleItem nonFungibleItem)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the NonFungibleItem ({itemId}) was failed to load from avatar's inventory."); } ItemSubType nonFungibleItemType = nonFungibleItem is Costume costume ? costume.ItemSubType : ((ItemUsable)nonFungibleItem).ItemSubType; if (!nonFungibleItemType.Equals(itemSubType)) { throw new InvalidItemTypeException($"Expected ItemType: {nonFungibleItemType}. Actual ItemType: {itemSubType}"); } if (nonFungibleItem.RequiredBlockIndex > context.BlockIndex) { throw new RequiredBlockIndexException( $"{addressesHex}Aborted as the itemUsable to sell ({itemId}) is not available yet; it will be available at the block #{nonFungibleItem.RequiredBlockIndex}."); } if (nonFungibleItem is Equipment equipment) { equipment.Unequip(); } nonFungibleItem.RequiredBlockIndex = expiredBlockIndex; ShopItem shopItem = new ShopItem(ctx.Signer, sellerAvatarAddress, productId, price, expiredBlockIndex, nonFungibleItem); Address shardedShopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) { ShardedShopState shardedShopState = new ShardedShopState(shardedShopAddress); shopStateDict = (Dictionary)shardedShopState.Serialize(); } Log.Verbose("{AddressesHex}Sell Get ShardedShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); List products = (List)shopStateDict[ProductsKey]; string productKey = LegacyItemUsableKey; string itemIdKey = LegacyItemIdKey; string requiredBlockIndexKey = LegacyRequiredBlockIndexKey; if (nonFungibleItem is Costume) { productKey = LegacyCostumeKey; itemIdKey = LegacyCostumeItemIdKey; requiredBlockIndexKey = RequiredBlockIndexKey; } #pragma warning disable LAA1002 Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => ((Dictionary)p[productKey])[itemIdKey].Equals(nonFungibleItem.NonFungibleId.Serialize())); #pragma warning restore LAA1002 // Register new ShopItem if (productSerialized.Equals(Dictionary.Empty)) { IValue shopItemSerialized = shopItem.Serialize(); products = products.Add(shopItemSerialized); } // Update Registered ShopItem else { // Delete current ShopItem products = (List)products.Remove(productSerialized); // Update INonfungibleItem.RequiredBlockIndex Dictionary item = (Dictionary)productSerialized[productKey]; item = item.SetItem(requiredBlockIndexKey, expiredBlockIndex.Serialize()); // Update ShopItem.ExpiredBlockIndex productSerialized = productSerialized .SetItem(ExpiredBlockIndexKey, expiredBlockIndex.Serialize()) .SetItem(productKey, item); products = products.Add(productSerialized); shopItem = new ShopItem(productSerialized); } shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); sw.Stop(); Log.Verbose("{AddressesHex}Sell Get Register Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); avatarState.updatedAt = ctx.BlockIndex; avatarState.blockIndex = ctx.BlockIndex; var result = new SellCancellation.Result { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume }; var mail = new SellCancelMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), expiredBlockIndex); result.id = mail.id; avatarState.Update(mail); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}Sell Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; Address shardedShopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (ctx.Rehearsal) { states = states.SetState(shardedShopAddress, MarkChanged); return(states .SetState(Addresses.Shop, MarkChanged) .SetState(sellerAvatarAddress, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Cancel exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, sellerAvatarAddress, out var avatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the seller failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } if (!states.TryGetState(shardedShopAddress, out Dictionary shopStateDict)) { ShardedShopState shopState = new ShardedShopState(shardedShopAddress); shopStateDict = (Dictionary)shopState.Serialize(); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // 상점에서 아이템을 빼온다. List products = (List)shopStateDict[ProductsKey]; IValue productIdSerialized = productId.Serialize(); Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized)); bool backwardCompatible = false; if (productSerialized.Equals(Dictionary.Empty)) { // Backward compatibility. IValue rawShop = states.GetState(Addresses.Shop); if (!(rawShop is null)) { Dictionary legacyShopDict = (Dictionary)rawShop; Dictionary legacyProducts = (Dictionary)legacyShopDict[LegacyProductsKey]; IKey productKey = (IKey)productId.Serialize(); // SoldOut if (!legacyProducts.ContainsKey(productKey)) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the shop item ({productId}) could not be found from the legacy shop." ); } productSerialized = (Dictionary)legacyProducts[productKey]; legacyProducts = (Dictionary)legacyProducts.Remove(productKey); legacyShopDict = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts); states = states.SetState(Addresses.Shop, legacyShopDict); backwardCompatible = true; } } else { products = (List)products.Remove(productSerialized); shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); } ShopItem shopItem = new ShopItem(productSerialized); sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get Unregister Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (shopItem.SellerAvatarAddress != sellerAvatarAddress || shopItem.SellerAgentAddress != ctx.Signer) { throw new InvalidAddressException($"{addressesHex}Invalid Avatar Address"); } INonFungibleItem nonFungibleItem = (INonFungibleItem)shopItem.ItemUsable ?? shopItem.Costume; if (avatarState.inventory.TryGetNonFungibleItem(nonFungibleItem.NonFungibleId, out INonFungibleItem outNonFungibleItem)) { outNonFungibleItem.RequiredBlockIndex = ctx.BlockIndex; } nonFungibleItem.RequiredBlockIndex = ctx.BlockIndex; if (backwardCompatible) { switch (nonFungibleItem) { case ItemUsable itemUsable: avatarState.UpdateFromAddItem2(itemUsable, true); break; case Costume costume: avatarState.UpdateFromAddCostume(costume, true); break; } } // 메일에 아이템을 넣는다. result = new SellCancellation.Result { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume }; var mail = new SellCancelMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); result.id = mail.id; avatarState.Update(mail); avatarState.updatedAt = ctx.BlockIndex; avatarState.blockIndex = ctx.BlockIndex; sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Update AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(sellerAvatarAddress, avatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, shopStateDict); sw.Stop(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Sell Cancel Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); Log.Verbose("{AddressesHex}Sell Cancel Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }