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) { foreach (var purchaseInfo in purchaseInfos) { Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); states = states .SetState(shardedShopAddress, MarkChanged) .SetState(purchaseInfo.sellerAvatarAddress, MarkChanged) .MarkBalanceChanged( GoldCurrencyMock, ctx.Signer, purchaseInfo.sellerAgentAddress, GoldCurrencyState.Address); } return(states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(Addresses.Shop, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the buyer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } List <Buy7.PurchaseResult> purchaseResults = new List <Buy7.PurchaseResult>(); List <Buy7.SellerResult> sellerResults = new List <Buy7.SellerResult>(); MaterialItemSheet materialSheet = states.GetSheet <MaterialItemSheet>(); buyerMultipleResult = new Buy7.BuyerMultipleResult(); sellerMultipleResult = new Buy7.SellerMultipleResult(); foreach (var purchaseInfo in purchaseInfos) { Buy7.PurchaseResult purchaseResult = new Buy7.PurchaseResult(purchaseInfo.productId); Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); Address sellerAgentAddress = purchaseInfo.sellerAgentAddress; Address sellerAvatarAddress = purchaseInfo.sellerAvatarAddress; Guid productId = purchaseInfo.productId; purchaseResults.Add(purchaseResult); if (purchaseInfo.sellerAgentAddress == ctx.Signer) { purchaseResult.errorCode = ErrorCodeInvalidAddress; continue; } if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) { ShardedShopState shardedShopState = new ShardedShopState(shardedShopAddress); shopStateDict = (Dictionary)shardedShopState.Serialize(); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose( "{AddressesHex}Execute Buy; buyer: {Buyer} seller: {Seller}", addressesHex, buyerAvatarAddress, sellerAvatarAddress); // Find product from ShardedShopState. List products = (List)shopStateDict[ProductsKey]; IValue productIdSerialized = productId.Serialize(); IValue sellerAgentSerialized = purchaseInfo.sellerAgentAddress.Serialize(); Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized) && p[LegacySellerAgentAddressKey].Equals(sellerAgentSerialized)); bool fromLegacy = false; if (productSerialized.Equals(Dictionary.Empty)) { if (purchaseInfo.itemSubType == ItemSubType.Hourglass || purchaseInfo.itemSubType == ItemSubType.ApStone) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } // 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)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } productSerialized = (Dictionary)legacyProducts[productKey]; legacyProducts = (Dictionary)legacyProducts.Remove(productKey); legacyShopDict = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts); states = states.SetState(Addresses.Shop, legacyShopDict); fromLegacy = true; } } ShopItem shopItem = new ShopItem(productSerialized); if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex) { purchaseResult.errorCode = ErrorCodeShopItemExpired; continue; } if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) { purchaseResult.errorCode = ErrorCodeFailedLoadingState; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // Check Balance. FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { purchaseResult.errorCode = ErrorCodeInsufficientBalance; continue; } var tax = shopItem.Price.DivRem(100, out _) * TaxRate; var taxedPrice = shopItem.Price - tax; // Transfer tax. states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // Transfer seller. states = states.TransferAsset( context.Signer, sellerAgentAddress, taxedPrice ); products = (List)products.Remove(productSerialized); shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); ITradableItem tradableItem; int count = 1; if (!(shopItem.ItemUsable is null)) { tradableItem = shopItem.ItemUsable; }
public void Execute( ItemType itemType, bool shopItemExist, long blockIndex, int itemCount, int prevCount, int expectedProductsCount ) { var avatarState = _initialState.GetAvatarState(_avatarAddress); ITradableItem tradableItem; switch (itemType) { case ItemType.Consumable: tradableItem = ItemFactory.CreateItemUsable( _tableSheets.ConsumableItemSheet.First, Guid.NewGuid(), 0); break; case ItemType.Costume: tradableItem = ItemFactory.CreateCostume( _tableSheets.CostumeItemSheet.First, Guid.NewGuid()); break; case ItemType.Equipment: tradableItem = ItemFactory.CreateItemUsable( _tableSheets.EquipmentItemSheet.First, Guid.NewGuid(), 0); break; case ItemType.Material: var tradableMaterialRow = _tableSheets.MaterialItemSheet.OrderedList .First(row => row.ItemSubType == ItemSubType.Hourglass); tradableItem = ItemFactory.CreateTradableMaterial(tradableMaterialRow); break; default: throw new ArgumentOutOfRangeException(nameof(itemType), itemType, null); } Assert.Equal(0, tradableItem.RequiredBlockIndex); avatarState.inventory.AddItem2((ItemBase)tradableItem, itemCount); var previousStates = _initialState; previousStates = previousStates.SetState(_avatarAddress, avatarState.Serialize()); var currencyState = previousStates.GetGoldCurrency(); var price = new FungibleAssetValue(currencyState, ProductPrice, 0); var expectedProductId = new Guid("6f460c1a755d48e4ad6765d5f519dbc8"); var productId = new Guid("229e5f8c-fabe-4c04-bab9-45325cfa69a4"); var shardedShopAddress = ShardedShopState.DeriveAddress( tradableItem.ItemSubType, expectedProductId); if (shopItemExist) { tradableItem.RequiredBlockIndex = blockIndex; Assert.Equal(blockIndex, tradableItem.RequiredBlockIndex); var shopItem = new ShopItem( _agentAddress, _avatarAddress, productId, new FungibleAssetValue(currencyState, 1, 0), blockIndex, tradableItem, prevCount ); var shardedShopState = new ShardedShopState(shardedShopAddress); shardedShopState.Register(shopItem); Assert.Single(shardedShopState.Products); previousStates = previousStates.SetState( shardedShopAddress, shardedShopState.Serialize()); } else { Assert.Null(previousStates.GetState(shardedShopAddress)); } var sellAction = new Sell6 { sellerAvatarAddress = _avatarAddress, tradableId = tradableItem.TradableId, count = itemCount, price = price, itemSubType = tradableItem.ItemSubType, }; var nextState = sellAction.Execute(new ActionContext { BlockIndex = 1, PreviousStates = previousStates, Rehearsal = false, Signer = _agentAddress, Random = new TestRandom(), }); const long expiredBlockIndex = Sell6.ExpiredBlockIndex + 1; // Check AvatarState and Inventory var nextAvatarState = nextState.GetAvatarState(_avatarAddress); Assert.Single(nextAvatarState.inventory.Items); Assert.True(nextAvatarState.inventory.TryGetTradableItems( tradableItem.TradableId, expiredBlockIndex, 1, out var inventoryItems)); Assert.Single(inventoryItems); ITradableItem nextTradableItem = (ITradableItem)inventoryItems.First().item; Assert.Equal(expiredBlockIndex, nextTradableItem.RequiredBlockIndex); // Check ShardedShopState and ShopItem var nextSerializedShardedShopState = nextState.GetState(shardedShopAddress); Assert.NotNull(nextSerializedShardedShopState); var nextShardedShopState = new ShardedShopState((Dictionary)nextSerializedShardedShopState); Assert.Equal(expectedProductsCount, nextShardedShopState.Products.Count); var nextShopItem = nextShardedShopState.Products.Values.First(s => s.ExpiredBlockIndex == expiredBlockIndex); ITradableItem nextTradableItemInShopItem; switch (itemType) { case ItemType.Consumable: case ItemType.Equipment: nextTradableItemInShopItem = nextShopItem.ItemUsable; break; case ItemType.Costume: nextTradableItemInShopItem = nextShopItem.Costume; break; case ItemType.Material: nextTradableItemInShopItem = nextShopItem.TradableFungibleItem; break; default: throw new ArgumentOutOfRangeException(nameof(itemType), itemType, null); } Assert.Equal(price, nextShopItem.Price); Assert.Equal(expectedProductId, nextShopItem.ProductId); Assert.Equal(expiredBlockIndex, nextShopItem.ExpiredBlockIndex); Assert.Equal(_agentAddress, nextShopItem.SellerAgentAddress); Assert.Equal(_avatarAddress, nextShopItem.SellerAvatarAddress); Assert.Equal(expiredBlockIndex, nextTradableItemInShopItem.RequiredBlockIndex); var mailList = nextAvatarState.mailBox.Where(m => m is SellCancelMail).ToList(); Assert.Single(mailList); var mail = mailList.First() as SellCancelMail; Assert.NotNull(mail); Assert.Equal(expiredBlockIndex, mail.requiredBlockIndex); ITradableItem attachmentItem; int attachmentCount = 0; switch (itemType) { case ItemType.Consumable: case ItemType.Equipment: Assert.NotNull(mail.attachment.itemUsable); attachmentItem = mail.attachment.itemUsable; Assert.Equal(tradableItem, mail.attachment.itemUsable); break; case ItemType.Costume: Assert.NotNull(mail.attachment.costume); attachmentItem = mail.attachment.costume; Assert.Equal(tradableItem, mail.attachment.costume); break; case ItemType.Material: Assert.NotNull(mail.attachment.tradableFungibleItem); attachmentItem = mail.attachment.tradableFungibleItem; attachmentCount = mail.attachment.tradableFungibleItemCount; break; default: throw new ArgumentOutOfRangeException(nameof(itemType), itemType, null); } Assert.Equal(attachmentCount, nextShopItem.TradableFungibleItemCount); Assert.Equal(nextTradableItem, attachmentItem); Assert.Equal(nextTradableItemInShopItem, attachmentItem); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { foreach (var purchaseInfo in purchaseInfos) { Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); states = states .SetState(shardedShopAddress, MarkChanged) .SetState(purchaseInfo.sellerAvatarAddress, MarkChanged) .MarkBalanceChanged( GoldCurrencyMock, ctx.Signer, purchaseInfo.sellerAgentAddress, GoldCurrencyState.Address); } return(states .SetState(buyerAvatarAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .SetState(Addresses.Shop, MarkChanged)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress); var sw = new Stopwatch(); sw.Start(); var started = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy exec started", addressesHex); if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState)) { throw new FailedLoadStateException( $"{addressesHex}Aborted as the avatar state of the buyer was failed to load."); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) { buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, GameConfig.RequireClearedStageLevel.ActionsInShop, current); } List <Buy7.PurchaseResult> purchaseResults = new List <Buy7.PurchaseResult>(); List <Buy7.SellerResult> sellerResults = new List <Buy7.SellerResult>(); MaterialItemSheet materialSheet = states.GetSheet <MaterialItemSheet>(); buyerMultipleResult = new Buy7.BuyerMultipleResult(); sellerMultipleResult = new Buy7.SellerMultipleResult(); foreach (var purchaseInfo in purchaseInfos) { Buy7.PurchaseResult purchaseResult = new Buy7.PurchaseResult(purchaseInfo.productId); Address shardedShopAddress = ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId); Address sellerAgentAddress = purchaseInfo.sellerAgentAddress; Address sellerAvatarAddress = purchaseInfo.sellerAvatarAddress; Guid productId = purchaseInfo.productId; purchaseResults.Add(purchaseResult); if (purchaseInfo.sellerAgentAddress == ctx.Signer) { purchaseResult.errorCode = ErrorCodeInvalidAddress; continue; } if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) { ShardedShopState shardedShopState = new ShardedShopState(shardedShopAddress); shopStateDict = (Dictionary)shardedShopState.Serialize(); } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); Log.Verbose( "{AddressesHex}Execute Buy; buyer: {Buyer} seller: {Seller}", addressesHex, buyerAvatarAddress, sellerAvatarAddress); // Find product from ShardedShopState. List products = (List)shopStateDict[ProductsKey]; IValue productIdSerialized = productId.Serialize(); Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized)); // Since Bencodex 0.4, Dictionary/List are reference types; so their default values // are not a empty container, but a null reference: productSerialized = productSerialized ?? Dictionary.Empty; bool fromLegacy = 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)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } productSerialized = (Dictionary)legacyProducts[productKey]; legacyProducts = (Dictionary)legacyProducts.Remove(productKey); legacyShopDict = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts); states = states.SetState(Addresses.Shop, legacyShopDict); fromLegacy = true; } } ShopItem shopItem = new ShopItem(productSerialized); if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress)) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Item: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex) { purchaseResult.errorCode = ErrorCodeShopItemExpired; continue; } if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) { purchaseResult.errorCode = ErrorCodeFailedLoadingState; continue; } sw.Stop(); Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // Check Balance. FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { purchaseResult.errorCode = ErrorCodeInsufficientBalance; continue; } var tax = shopItem.Price.DivRem(100, out _) * TaxRate; var taxedPrice = shopItem.Price - tax; // Transfer tax. states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // Transfer seller. states = states.TransferAsset( context.Signer, sellerAgentAddress, taxedPrice ); products = (List)products.Remove(productSerialized); shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); INonFungibleItem nonFungibleItem = (INonFungibleItem)shopItem.ItemUsable ?? shopItem.Costume; if (!sellerAvatarState.inventory.RemoveNonFungibleItem(nonFungibleItem) && !fromLegacy) { purchaseResult.errorCode = ErrorCodeItemDoesNotExist; continue; } nonFungibleItem.RequiredBlockIndex = context.BlockIndex; // Send result mail for buyer, seller. purchaseResult.shopItem = shopItem; purchaseResult.itemUsable = shopItem.ItemUsable; purchaseResult.costume = shopItem.Costume; var buyerMail = new BuyerMail(purchaseResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); purchaseResult.id = buyerMail.id; var sellerResult = new Buy7.SellerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable, costume = shopItem.Costume, gold = taxedPrice }; var sellerMail = new SellerMail(sellerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); sellerResult.id = sellerMail.id; sellerResults.Add(sellerResult); buyerAvatarState.Update(buyerMail); if (purchaseResult.itemUsable != null) { buyerAvatarState.UpdateFromAddItem2(purchaseResult.itemUsable, false); } if (purchaseResult.costume != null) { buyerAvatarState.UpdateFromAddCostume(purchaseResult.costume, false); } sellerAvatarState.Update(sellerMail); // Update quest. buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, shopItem.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.Price); sellerAvatarState.updatedAt = ctx.BlockIndex; sellerAvatarState.blockIndex = ctx.BlockIndex; buyerAvatarState.UpdateQuestRewards2(materialSheet); sellerAvatarState.UpdateQuestRewards2(materialSheet); states = states.SetState(sellerAvatarAddress, sellerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Seller AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); states = states.SetState(shardedShopAddress, shopStateDict); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set ShopState: {Elapsed}", addressesHex, sw.Elapsed); } buyerMultipleResult.purchaseResults = purchaseResults; sellerMultipleResult.sellerResults = sellerResults; buyerAvatarState.updatedAt = ctx.BlockIndex; buyerAvatarState.blockIndex = ctx.BlockIndex; states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize()); sw.Stop(); Log.Verbose("{AddressesHex}Buy Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); var ended = DateTimeOffset.UtcNow; Log.Verbose("{AddressesHex}Buy Total Executed Time: {Elapsed}", addressesHex, ended - started); return(states); }
public void Execute_20210604( long blockIndex, ItemSubType itemSubType, string agentAddressHex, string avatarAddressHex, long shopExpiredBlockIndex, long expectedBlockIndex, int shopItemCount, int itemCount, int shopPrice, int expectedPrice) { var avatarState = _initialState.GetAvatarState(_avatarAddress); var sellerAgentAddress = new Address(agentAddressHex); var sellerAvatarAddress = new Address(avatarAddressHex); ITradableItem tradableItem; switch (itemSubType) { case ItemSubType.Weapon: tradableItem = ItemFactory.CreateItemUsable( _tableSheets.EquipmentItemSheet.OrderedList.First(row => row.ItemSubType == ItemSubType.Weapon), Guid.NewGuid(), 1); break; case ItemSubType.Hourglass: var tradableMaterialRow = _tableSheets.MaterialItemSheet.OrderedList .First(row => row.ItemSubType == ItemSubType.Hourglass); tradableItem = ItemFactory.CreateTradableMaterial(tradableMaterialRow); tradableItem.RequiredBlockIndex = 1; break; default: throw new ArgumentOutOfRangeException(nameof(itemSubType), itemSubType, null); } Assert.Equal(1, tradableItem.RequiredBlockIndex); avatarState.inventory.AddItem2((ItemBase)tradableItem, itemCount); var previousStates = _initialState; previousStates = previousStates.SetState(_avatarAddress, avatarState.Serialize()); var currencyState = previousStates.GetGoldCurrency(); var price = new FungibleAssetValue(currencyState, expectedPrice, 0); var productId = new Guid("6f460c1a755d48e4ad6765d5f519dbc8"); var shardedShopAddress = ShardedShopState.DeriveAddress( tradableItem.ItemSubType, productId); Assert.Equal(1, tradableItem.RequiredBlockIndex); var shopItem = new ShopItem( sellerAgentAddress, sellerAvatarAddress, Guid.NewGuid(), new FungibleAssetValue(currencyState, shopPrice, 0), shopExpiredBlockIndex, tradableItem, shopItemCount ); var shardedShopState = new ShardedShopState(shardedShopAddress); shardedShopState.Register(shopItem); Assert.Single(shardedShopState.Products); previousStates = previousStates.SetState( shardedShopAddress, shardedShopState.Serialize()); var sellAction = new Sell6 { sellerAvatarAddress = _avatarAddress, tradableId = tradableItem.TradableId, count = itemCount, price = price, itemSubType = tradableItem.ItemSubType, }; var nextState = sellAction.Execute(new ActionContext { BlockIndex = blockIndex, PreviousStates = previousStates, Rehearsal = false, Signer = _agentAddress, Random = new TestRandom(), }); // Check AvatarState and Inventory var nextAvatarState = nextState.GetAvatarState(_avatarAddress); Assert.Single(nextAvatarState.inventory.Items); Assert.True(nextAvatarState.inventory.TryGetTradableItems( tradableItem.TradableId, expectedBlockIndex, itemCount, out var inventoryItems)); Assert.Single(inventoryItems); ITradableItem nextTradableItem = (ITradableItem)inventoryItems.First().item; Assert.Equal(expectedBlockIndex, nextTradableItem.RequiredBlockIndex); // Check ShardedShopState and ShopItem var nextSerializedShardedShopState = nextState.GetState(shardedShopAddress); Assert.NotNull(nextSerializedShardedShopState); var nextShardedShopState = new ShardedShopState((Dictionary)nextSerializedShardedShopState); Assert.Equal(2, nextShardedShopState.Products.Count); Assert.Single(nextShardedShopState.Products.Values.Where(s => s.Equals(shopItem))); Assert.Single(nextShardedShopState.Products.Values.Where(s => !s.Equals(shopItem))); ShopItem nextShopItem = nextShardedShopState.Products.Values.First(s => !s.Equals(shopItem)); ITradableItem innerShopItem = nextShopItem.TradableFungibleItem; int fungibleCount = itemCount; if (itemSubType == ItemSubType.Weapon) { innerShopItem = nextShopItem.ItemUsable; fungibleCount = 0; } Assert.Equal(price, nextShopItem.Price); Assert.Equal(expectedBlockIndex, nextShopItem.ExpiredBlockIndex); Assert.Equal(_agentAddress, nextShopItem.SellerAgentAddress); Assert.Equal(_avatarAddress, nextShopItem.SellerAvatarAddress); Assert.Equal(expectedBlockIndex, innerShopItem.RequiredBlockIndex); Assert.Equal(fungibleCount, nextShopItem.TradableFungibleItemCount); var mailList = nextAvatarState.mailBox.Where(m => m is SellCancelMail).ToList(); Assert.Single(mailList); var mail = mailList.First() as SellCancelMail; Assert.NotNull(mail); Assert.Equal(expectedBlockIndex, mail.requiredBlockIndex); ITradableItem attachmentItem = itemSubType == ItemSubType.Weapon ? (ITradableItem)mail.attachment.itemUsable : mail.attachment.tradableFungibleItem; Assert.Equal(itemSubType == ItemSubType.Weapon, mail.attachment.tradableFungibleItem is null); Assert.Equal(fungibleCount, mail.attachment.tradableFungibleItemCount); Assert.Equal(nextTradableItem, attachmentItem); }
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); }
public override IAccountStateDelta Execute(IActionContext context) { var states = context.PreviousStates; var shardedShopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (context.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(context.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 BxDictionary shopStateDict)) { var shopState = new ShardedShopState(shardedShopAddress); shopStateDict = (BxDictionary)shopState.Serialize(); } sw.Stop(); Log.Verbose("{AddressesHex}Sell Cancel Get ShopState: {Elapsed}", addressesHex, sw.Elapsed); sw.Restart(); // 상점에서 아이템을 빼온다. var products = (BxList)shopStateDict[ProductsKey]; var productIdSerialized = productId.Serialize(); var productSerialized = products .Select(p => (BxDictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized)); var backwardCompatible = false; if (productSerialized.Equals(BxDictionary.Empty)) { if (itemSubType == ItemSubType.Hourglass || itemSubType == ItemSubType.ApStone) { throw new ItemDoesNotExistException( $"{addressesHex}Aborted as the shop item ({productId}) could not be found from the shop."); } // Backward compatibility. var rawShop = states.GetState(Addresses.Shop); if (!(rawShop is null)) { var legacyShopDict = (BxDictionary)rawShop; var legacyProducts = (BxDictionary)legacyShopDict[LegacyProductsKey]; var 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 = (BxDictionary)legacyProducts[productKey]; legacyProducts = (BxDictionary)legacyProducts.Remove(productKey); legacyShopDict = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts); states = states.SetState(Addresses.Shop, legacyShopDict); backwardCompatible = true; } } else { products = (BxList)products.Remove(productSerialized); shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products)); } var 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 != context.Signer) { throw new InvalidAddressException($"{addressesHex}Invalid Avatar Address"); } ITradableItem tradableItem; int itemCount = 1; if (!(shopItem.ItemUsable is null)) { tradableItem = shopItem.ItemUsable; }
public void Execute(ItemType itemType, bool shopItemExist, int blockIndex) { var avatarState = _initialState.GetAvatarState(_avatarAddress); List <Inventory.Item> inventoryItem = avatarState.inventory.Items.Where(i => i.item.ItemType == itemType).ToList(); Assert.Single(inventoryItem); var previousStates = _initialState; var currencyState = previousStates.GetGoldCurrency(); var price = new FungibleAssetValue(currencyState, ProductPrice, 0); INonFungibleItem nonFungibleItem = (INonFungibleItem)inventoryItem.First().item; nonFungibleItem.RequiredBlockIndex = blockIndex; Assert.Equal(blockIndex, nonFungibleItem.RequiredBlockIndex); ItemSubType itemSubType = ItemSubType.Food; Guid productId = new Guid("6f460c1a-755d-48e4-ad67-65d5f519dbc8"); if (nonFungibleItem is ItemUsable itemUsable) { itemSubType = itemUsable.ItemSubType; } else if (nonFungibleItem is Costume costume) { itemSubType = costume.ItemSubType; } Address shopAddress = ShardedShopState.DeriveAddress(itemSubType, productId); if (shopItemExist) { var si = new ShopItem( _agentAddress, _avatarAddress, productId, new FungibleAssetValue(currencyState, 100, 0), blockIndex, nonFungibleItem); ShardedShopState shardedShopState = new ShardedShopState(shopAddress); shardedShopState.Register(si); Assert.Single(shardedShopState.Products); previousStates = previousStates.SetState(shopAddress, shardedShopState.Serialize()); } else { Assert.Null(previousStates.GetState(shopAddress)); } var sellAction = new Sell4 { itemId = nonFungibleItem.NonFungibleId, price = price, sellerAvatarAddress = _avatarAddress, itemSubType = itemSubType, }; var nextState = sellAction.Execute(new ActionContext { BlockIndex = 1, PreviousStates = previousStates, Rehearsal = false, Signer = _agentAddress, Random = new TestRandom(), }); const long expiredBlockIndex = Sell6.ExpiredBlockIndex + 1; var nextAvatarState = nextState.GetAvatarState(_avatarAddress); Assert.True(nextAvatarState.inventory.TryGetNonFungibleItem(nonFungibleItem.NonFungibleId, out var nextItem)); INonFungibleItem nextNonFungibleItem = (INonFungibleItem)nextItem.item; Assert.Equal(expiredBlockIndex, nextNonFungibleItem.RequiredBlockIndex); var nextShopState = new ShardedShopState((Dictionary)nextState.GetState(shopAddress)); Assert.Single(nextShopState.Products); var products = nextShopState.Products.Values; var shopItem = products.First(); INonFungibleItem item = itemType == ItemType.Costume ? (INonFungibleItem)shopItem.Costume : shopItem.ItemUsable; Assert.Equal(price, shopItem.Price); Assert.Equal(expiredBlockIndex, shopItem.ExpiredBlockIndex); Assert.Equal(expiredBlockIndex, item.RequiredBlockIndex); Assert.Equal(_agentAddress, shopItem.SellerAgentAddress); Assert.Equal(_avatarAddress, shopItem.SellerAvatarAddress); var mailList = nextAvatarState.mailBox.Where(m => m is SellCancelMail).ToList(); Assert.Single(mailList); Assert.Equal(expiredBlockIndex, mailList.First().requiredBlockIndex); }