Esempio n. 1
0
        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);
        }
Esempio n. 2
0
File: Buy6.cs Progetto: dahlia/lib9c
        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;
                }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        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);
        }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
        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);
        }
Esempio n. 7
0
        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);
        }
Esempio n. 8
0
        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;
            }
Esempio n. 9
0
        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);
        }