Esempio n. 1
0
        public override ITradableItem Sell(AvatarState avatarState)
        {
            if (avatarState.inventory.TryGetNonFungibleItem(TradableId, out Inventory.Item inventoryItem))
            {
                inventoryItem.LockUp(new OrderLock(OrderId));
                INonFungibleItem nonFungibleItem = (INonFungibleItem)inventoryItem.item;
                nonFungibleItem.RequiredBlockIndex = ExpiredBlockIndex;
                if (nonFungibleItem is IEquippableItem equippableItem)
                {
                    equippableItem.Unequip();
                }

                return(nonFungibleItem);
            }

            throw new ItemDoesNotExistException(
                      $"Aborted because the tradable item({TradableId}) was failed to load from avatar's inventory.");
        }
Esempio n. 2
0
        public IObservable <ActionBase.ActionEvaluation <Sell> > Sell(INonFungibleItem item, FungibleAssetValue price)
        {
            var avatarAddress = States.Instance.CurrentAvatarState.address;

            // NOTE: 장착했는지 안 했는지에 상관없이 해제 플래그를 걸어 둔다.
            LocalLayerModifier.SetItemEquip(avatarAddress, item.ItemId, false, false);

            var action = new Sell
            {
                sellerAvatarAddress = avatarAddress,
                itemId = item.ItemId,
                price  = price
            };

            ProcessAction(action);

            return(_renderer.EveryRender <Sell>()
                   .Where(eval => eval.Action.Id.Equals(action.Id))
                   .Take(1)
                   .Last()
                   .ObserveOnMainThread()
                   .Timeout(ActionTimeout)
                   .DoOnError(e => HandleException(action.Id, e))); // Last() is for completion
        }
Esempio n. 3
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            IActionContext ctx    = context;
            var            states = ctx.PreviousStates;

            if (ctx.Rehearsal)
            {
                states = states.SetState(ShopState.Address, MarkChanged);
                return(states.SetState(sellerAvatarAddress, MarkChanged));
            }

            var addressesHex = GetSignerAndOtherAddressesHex(context, sellerAvatarAddress);

            var sw = new Stopwatch();

            sw.Start();
            var started = DateTimeOffset.UtcNow;

            Log.Verbose("{AddressesHex}Sell Cancel exec started", addressesHex);

            if (!states.TryGetAgentAvatarStates(ctx.Signer, sellerAvatarAddress, out _, out var avatarState))
            {
                return(states);
            }
            sw.Stop();
            Log.Verbose("{AddressesHex}Sell Cancel Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(
                    out var world))
            {
                return(states);
            }

            if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInShop)
            {
                // 스테이지 클리어 부족 에러.
                return(states);
            }

            if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict))
            {
                return(states);
            }
            sw.Stop();
            Log.Verbose("{AddressesHex}Sell Cancel Get ShopState: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            // 상점에서 아이템을 빼온다.
            Dictionary products = (Dictionary)shopStateDict["products"];

            IKey productIdSerialized = (IKey)productId.Serialize();

            if (!products.ContainsKey(productIdSerialized))
            {
                return(states);
            }

            ShopItem outUnregisteredItem = new ShopItem((Dictionary)products[productIdSerialized]);

            products      = (Dictionary)products.Remove(productIdSerialized);
            shopStateDict = shopStateDict.SetItem("products", products);

            sw.Stop();
            Log.Verbose("{AddressesHex}Sell Cancel Get Unregister Item: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            //9c-beta 브랜치에서는 블록 인덱스도 확인 해야함 (이전 블록 유효성 보장)
            if (outUnregisteredItem.SellerAvatarAddress != sellerAvatarAddress)
            {
                Log.Error("{AddressesHex}Invalid Avatar Address", addressesHex);
                return(states);
            }

            INonFungibleItem nonFungibleItem    = (INonFungibleItem)outUnregisteredItem.ItemUsable ?? outUnregisteredItem.Costume;
            bool             backWardCompatible = false;

            if (!avatarState.inventory.TryGetNonFungibleItem(nonFungibleItem.ItemId, out INonFungibleItem outNonFungibleItem))
            {
                if (nonFungibleItem.RequiredBlockIndex != 0)
                {
                    throw new ItemDoesNotExistException(
                              $"{addressesHex}Aborted as the NonFungibleItem ({nonFungibleItem.ItemId}) was failed to load from avatar's inventory."
                              );
                }

                // Backward compatible for old actions.
                backWardCompatible = true;
            }
            else
            {
                outNonFungibleItem.Update(ctx.BlockIndex);
            }
            nonFungibleItem.Update(ctx.BlockIndex);

            if (backWardCompatible)
            {
                switch (nonFungibleItem)
                {
                case ItemUsable itemUsable:
                    avatarState.UpdateFromAddItem(itemUsable, true);
                    break;

                case Costume costume:
                    avatarState.UpdateFromAddCostume(costume, true);
                    break;
                }
            }
            // 메일에 아이템을 넣는다.
            result = new Result
            {
                shopItem   = outUnregisteredItem,
                itemUsable = outUnregisteredItem.ItemUsable,
                costume    = outUnregisteredItem.Costume
            };
            var mail = new SellCancelMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex);

            result.id = mail.id;

            avatarState.UpdateV4(mail, context.BlockIndex);
            avatarState.updatedAt  = ctx.BlockIndex;
            avatarState.blockIndex = ctx.BlockIndex;

            sw.Stop();
            Log.Verbose("{AddressesHex}Sell Cancel Update AvatarState: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            states = states.SetState(sellerAvatarAddress, avatarState.Serialize());
            sw.Stop();
            Log.Verbose("{AddressesHex}Sell Cancel Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            states = states.SetState(ShopState.Address, shopStateDict);
            sw.Stop();
            var ended = DateTimeOffset.UtcNow;

            Log.Verbose("{AddressesHex}Sell Cancel Set ShopState: {Elapsed}", addressesHex, sw.Elapsed);
            Log.Verbose("{AddressesHex}Sell Cancel Total Executed Time: {Elapsed}", addressesHex, ended - started);
            return(states);
        }
Esempio n. 4
0
        public void Execute()
        {
            var shopState = _initialState.GetShopState();

            Assert.Empty(shopState.Products);

            var avatarState = _initialState.GetAvatarState(_avatarAddress);

            Assert.Single(avatarState.inventory.Equipments);

            var equipment = avatarState.inventory.Equipments.FirstOrDefault();

            Assert.NotNull(equipment);

            var consumable = avatarState.inventory.Consumables.FirstOrDefault();

            Assert.NotNull(equipment);

            var costume = avatarState.inventory.Costumes.FirstOrDefault();

            Assert.NotNull(costume);

            var items = new INonFungibleItem[] { equipment, consumable, costume };

            var previousStates = _initialState;
            var currencyState  = previousStates.GetGoldCurrency();
            var price          = new FungibleAssetValue(currencyState, ProductPrice, 0);

            var productCount = 0;
            var random       = new TestRandom();

            foreach (var nonFungibleItem in items)
            {
                var sellAction = new Sell3
                {
                    itemId = nonFungibleItem.ItemId,
                    price  = price,
                    sellerAvatarAddress = _avatarAddress,
                };

                var nextState = sellAction.Execute(new ActionContext
                {
                    BlockIndex     = 0,
                    PreviousStates = previousStates,
                    Rehearsal      = false,
                    Signer         = _agentAddress,
                    Random         = random,
                });

                productCount++;

                var nextAvatarState = nextState.GetAvatarState(_avatarAddress);
                Assert.Empty(nextAvatarState.inventory.Equipments);

                var nextShopState = nextState.GetShopState();

                Assert.Equal(productCount, nextShopState.Products.Count);

                var products = nextShopState.Products.Values;
                Assert.NotNull(products);

                var shopItem = nonFungibleItem is Costume?
                               products.First(x => x.Costume != null) :
                                   products.First(x => x.ItemUsable != null);

                Assert.Equal(price, shopItem.Price);
                Assert.Equal(_agentAddress, shopItem.SellerAgentAddress);
                Assert.Equal(_avatarAddress, shopItem.SellerAvatarAddress);

                previousStates = nextState;
            }
        }
Esempio n. 5
0
        public override IAccountStateDelta Execute(IActionContext context)
        {
            IActionContext ctx    = context;
            var            states = ctx.PreviousStates;

            if (ctx.Rehearsal)
            {
                states = states
                         .SetState(buyerAvatarAddress, MarkChanged)
                         .SetState(ctx.Signer, MarkChanged)
                         .SetState(sellerAvatarAddress, MarkChanged)
                         .MarkBalanceChanged(
                    GoldCurrencyMock,
                    ctx.Signer,
                    sellerAgentAddress,
                    GoldCurrencyState.Address);
                return(states.SetState(ShopState.Address, MarkChanged));
            }

            var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress, sellerAvatarAddress);

            if (ctx.Signer.Equals(sellerAgentAddress))
            {
                throw new InvalidAddressException($"{addressesHex}Aborted as the signer is the seller.");
            }

            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);
            }

            if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary shopStateDict))
            {
                throw new FailedLoadStateException($"{addressesHex}Aborted as the shop state was failed to load.");
            }

            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);
            // 상점에서 구매할 아이템을 찾는다.
            Dictionary products = (Dictionary)shopStateDict["products"];

            IKey productIdSerialized = (IKey)productId.Serialize();

            if (!products.ContainsKey(productIdSerialized))
            {
                throw new ItemDoesNotExistException(
                          $"{addressesHex}Aborted as the shop item ({productId}) was failed to get from the shop."
                          );
            }

            ShopItem shopItem = new ShopItem((Dictionary)products[productIdSerialized]);

            if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress))
            {
                throw new ItemDoesNotExistException(
                          $"{addressesHex}Aborted as the shop item ({productId}) of seller ({shopItem.SellerAgentAddress}) is different from ({sellerAgentAddress})."
                          );
            }
            sw.Stop();
            Log.Verbose("{AddressesHex}Buy Get Item: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex)
            {
                throw new ShopItemExpiredException(
                          $"{addressesHex}Aborted as the shop item ({productId}) already expired on # ({shopItem.ExpiredBlockIndex}).");
            }
            if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState))
            {
                throw new FailedLoadStateException(
                          $"{addressesHex}Aborted as the seller agent/avatar was failed to load from {sellerAgentAddress}/{sellerAvatarAddress}."
                          );
            }
            sw.Stop();
            Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            // 돈은 있냐?
            FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency());

            if (buyerBalance < shopItem.Price)
            {
                throw new InsufficientBalanceException(
                          ctx.Signer,
                          buyerBalance,
                          $"{addressesHex}Aborted as the buyer ({ctx.Signer}) has no sufficient gold: {buyerBalance} < {shopItem.Price}"
                          );
            }

            var tax        = shopItem.Price.DivRem(100, out _) * TaxRate;
            var taxedPrice = shopItem.Price - tax;

            // 세금을 송금한다.
            states = states.TransferAsset(
                context.Signer,
                GoldCurrencyState.Address,
                tax);

            // 구매자의 돈을 판매자에게 송금한다.
            states = states.TransferAsset(
                context.Signer,
                sellerAgentAddress,
                taxedPrice
                );

            products      = (Dictionary)products.Remove(productIdSerialized);
            shopStateDict = shopStateDict.SetItem("products", products);

            INonFungibleItem nonFungibleItem = (INonFungibleItem)shopItem.ItemUsable ?? shopItem.Costume;

            if (!sellerAvatarState.inventory.RemoveNonFungibleItem(nonFungibleItem))
            {
                if (nonFungibleItem.RequiredBlockIndex != 0)
                {
                    throw new ItemDoesNotExistException(
                              $"{addressesHex}Aborted as the {nameof(nonFungibleItem)} ({nonFungibleItem.ItemId}) was failed to get from the sellerAvatar."
                              );
                }
            }
            nonFungibleItem.Update(context.BlockIndex);

            // 구매자, 판매자에게 결과 메일 전송
            buyerResult = new BuyerResult
            {
                shopItem   = shopItem,
                itemUsable = shopItem.ItemUsable,
                costume    = shopItem.Costume
            };
            var buyerMail = new BuyerMail(buyerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex);

            buyerResult.id = buyerMail.id;

            sellerResult = new 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;

            buyerAvatarState.UpdateV4(buyerMail, context.BlockIndex);
            if (buyerResult.itemUsable != null)
            {
                buyerAvatarState.UpdateFromAddItem(buyerResult.itemUsable, false);
            }

            if (buyerResult.costume != null)
            {
                buyerAvatarState.UpdateFromAddCostume(buyerResult.costume, false);
            }
            sellerAvatarState.UpdateV4(sellerMail, context.BlockIndex);

            // 퀘스트 업데이트
            buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, shopItem.Price);
            sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.Price);

            buyerAvatarState.updatedAt   = ctx.BlockIndex;
            buyerAvatarState.blockIndex  = ctx.BlockIndex;
            sellerAvatarState.updatedAt  = ctx.BlockIndex;
            sellerAvatarState.blockIndex = ctx.BlockIndex;

            var materialSheet = states.GetSheet <MaterialItemSheet>();

            buyerAvatarState.UpdateQuestRewards(materialSheet);
            sellerAvatarState.UpdateQuestRewards(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(buyerAvatarAddress, buyerAvatarState.Serialize());
            sw.Stop();
            Log.Verbose("{AddressesHex}Buy Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            states = states.SetState(ShopState.Address, shopStateDict);
            sw.Stop();
            var ended = DateTimeOffset.UtcNow;

            Log.Verbose("{AddressesHex}Buy Set ShopState: {Elapsed}", addressesHex, sw.Elapsed);
            Log.Verbose("{AddressesHex}Buy Total Executed Time: {Elapsed}", addressesHex, ended - started);

            return(states);
        }
Esempio n. 6
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. 7
0
 public bool RemoveNonFungibleItem(INonFungibleItem nonFungibleItem)
 {
     return(RemoveNonFungibleItem(nonFungibleItem.ItemId));
 }
Esempio n. 8
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. 9
0
        public void Execute(ItemType itemType, bool shopItemExist, int blockIndex)
        {
            var shopState = _initialState.GetShopState();

            Assert.Empty(shopState.Products);

            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.Update(blockIndex);
            Assert.Equal(blockIndex, nonFungibleItem.RequiredBlockIndex);

            if (shopItemExist)
            {
                var si = new ShopItem(
                    _agentAddress,
                    _avatarAddress,
                    Guid.NewGuid(),
                    new FungibleAssetValue(currencyState, 100, 0),
                    blockIndex,
                    nonFungibleItem);
                shopState.Register(si);
                previousStates = previousStates.SetState(Addresses.Shop, shopState.Serialize());
                Assert.Single(shopState.Products);
            }
            else
            {
                Assert.Empty(shopState.Products);
            }

            var sellAction = new Sell
            {
                itemId = nonFungibleItem.ItemId,
                price  = price,
                sellerAvatarAddress = _avatarAddress,
            };

            var nextState = sellAction.Execute(new ActionContext
            {
                BlockIndex     = 1,
                PreviousStates = previousStates,
                Rehearsal      = false,
                Signer         = _agentAddress,
                Random         = new TestRandom(),
            });

            const long expiredBlockIndex = Sell.ExpiredBlockIndex + 1;
            var        nextAvatarState   = nextState.GetAvatarState(_avatarAddress);

            Assert.True(nextAvatarState.inventory.TryGetNonFungibleItem(nonFungibleItem.ItemId, out var nextItem));
            INonFungibleItem nextNonFungibleItem = (INonFungibleItem)nextItem.item;

            Assert.Equal(expiredBlockIndex, nextNonFungibleItem.RequiredBlockIndex);

            var nextShopState = nextState.GetShopState();

            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);
        }
Esempio n. 10
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);
        }