Beispiel #1
0
        public void Read(SellerMail sellerMail)
        {
            var agentAddress  = States.Instance.AgentState.address;
            var avatarAddress = States.Instance.CurrentAvatarState.address;
            var attachment    = (Buy.SellerResult)sellerMail.attachment;

            //TODO 관련 기획이 끝나면 별도 UI를 생성
            LocalLayerModifier.ModifyAgentGold(agentAddress, attachment.gold);
            LocalLayerModifier.RemoveNewAttachmentMail(avatarAddress, sellerMail.id);
        }
Beispiel #2
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);
        }
Beispiel #3
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);

                foreach (var info in purchaseInfos)
                {
                    var sellerAgentAddress  = info.sellerAgentAddress;
                    var sellerAvatarAddress = info.sellerAvatarAddress;

                    states = states.SetState(sellerAvatarAddress, MarkChanged)
                             .MarkBalanceChanged(
                        GoldCurrencyMock,
                        ctx.Signer,
                        sellerAgentAddress,
                        GoldCurrencyState.Address);
                }

                return(states.SetState(ShopState.Address, MarkChanged));
            }

            CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context);

            var availableInfos = purchaseInfos.Where(p => !(p is null));

            var sellerAgentAddresses  = availableInfos.Select(p => p.sellerAgentAddress);
            var sellerAvatarAddresses = availableInfos.Select(p => p.sellerAvatarAddress);
            var avatarAddresses       = sellerAvatarAddresses.Prepend(buyerAvatarAddress);
            var addressesHex          = GetSignerAndOtherAddressesHex(context, avatarAddresses.ToArray());

            if (sellerAgentAddresses.Any(a => a.Equals(ctx.Signer)))
            {
                throw new InvalidAddressException($"{addressesHex}Aborted as the signer is the seller.");
            }

            var sw = new Stopwatch();

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

            Log.Verbose("{Addresses}BuyMultiple 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}BuyMultiple 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}BuyMultiple Get ShopState: {Elapsed}", addressesHex, sw.Elapsed);

            var sellerAvatarAddressesString = string.Join(", ", sellerAvatarAddresses.Select(a => a.ToString()));

            Log.Verbose(
                "{AddressesHex}Execute BuyMultiple; buyer: {Buyer} sellers: {Seller}",
                addressesHex,
                buyerAvatarAddress,
                sellerAvatarAddressesString);

            // Get products in `ShopState`.
            Dictionary productDict = (Dictionary)shopStateDict["products"];

            buyerResult  = new BuyerResult();
            sellerResult = new SellerResult();
            var purchaseResults = new List <PurchaseResult>();
            var sellerResults   = new List <Buy7.SellerResult>();
            var materialSheet   = states.GetSheet <MaterialItemSheet>();

            foreach (var productInfo in purchaseInfos)
            {
                if (productInfo is null)
                {
                    continue;
                }
                var productId      = productInfo.productId;
                var purchaseResult = new PurchaseResult(productId);
                purchaseResults.Add(purchaseResult);


                IKey productIdSerialized = (IKey)productId.Serialize();
                if (!productDict.ContainsKey(productIdSerialized))
                {
                    purchaseResult.errorCode = ERROR_CODE_ITEM_DOES_NOT_EXIST;
                    continue;
                }

                ShopItem shopItem = new ShopItem((Dictionary)productDict[productIdSerialized]);
                purchaseResult.shopItem   = shopItem;
                purchaseResult.itemUsable = shopItem.ItemUsable;
                purchaseResult.costume    = shopItem.Costume;

                sw.Restart();

                var avatarAddress = productInfo.sellerAvatarAddress;
                if (!states.TryGetAvatarState(productInfo.sellerAgentAddress,
                                              avatarAddress,
                                              out var sellerAvatarState))
                {
                    purchaseResult.errorCode = ERROR_CODE_FAILED_LOADING_STATE;
                    continue;
                }

                sw.Stop();
                Log.Verbose("{AddressesHex}BuyMultiple Get Seller AgentAvatarState: {Elapsed}", avatarAddress, sw.Elapsed);
                sw.Restart();

                if (!shopItem.SellerAgentAddress.Equals(productInfo.sellerAgentAddress))
                {
                    purchaseResult.errorCode = ERROR_CODE_ITEM_DOES_NOT_EXIST;
                    continue;
                }
                sw.Stop();
                Log.Verbose("{AddressesHex}BuyMultiple Get Item: {Elapsed}", addressesHex, sw.Elapsed);

                if (0 < shopItem.ExpiredBlockIndex && shopItem.ExpiredBlockIndex < context.BlockIndex)
                {
                    purchaseResult.errorCode = ERROR_CODE_SHOPITEM_EXPIRED;
                    continue;
                }

                // Check buyer's balance
                FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency());
                if (buyerBalance < shopItem.Price)
                {
                    purchaseResult.errorCode = ERROR_CODE_INSUFFICIENT_BALANCE;
                    continue;
                }

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

                // Transfer tax
                states = states.TransferAsset(
                    context.Signer,
                    GoldCurrencyState.Address,
                    tax);

                // Transfer paid money (taxed) to the seller.
                states = states.TransferAsset(
                    context.Signer,
                    productInfo.sellerAgentAddress,
                    taxedPrice
                    );

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

                var buyerMail = new BuyerMail(purchaseResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex);
                purchaseResult.id = buyerMail.id;

                var sellerResultToAdd = new Buy7.SellerResult
                {
                    shopItem   = shopItem,
                    itemUsable = shopItem.ItemUsable,
                    costume    = shopItem.Costume,
                    gold       = taxedPrice
                };
                var sellerMail = new SellerMail(sellerResultToAdd, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(),
                                                ctx.BlockIndex);
                sellerResultToAdd.id = sellerMail.id;
                sellerResults.Add(sellerResultToAdd);

                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;
                sellerAvatarState.UpdateQuestRewards2(materialSheet);

                sw.Restart();
                states = states.SetState(productInfo.sellerAvatarAddress, sellerAvatarState.Serialize());
                sw.Stop();
                Log.Verbose("{AddressesHex}BuyMultiple Set Seller AvatarState: {Elapsed}", addressesHex, sw.Elapsed);
            }

            buyerResult.purchaseResults = purchaseResults;
            sellerResult.sellerResults  = sellerResults;

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

            buyerAvatarState.UpdateQuestRewards2(materialSheet);

            sw.Restart();
            states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize());
            sw.Stop();
            Log.Verbose("{AddressesHex}BuyMultiple Set Buyer AvatarState: {Elapsed}", addressesHex, sw.Elapsed);

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

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

            return(states);
        }
Beispiel #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);
        }
Beispiel #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);
                return(states.SetState(ShopState.Address, MarkChanged));
            }

            if (ctx.Signer.Equals(sellerAgentAddress))
            {
                return(LogError(context, "Aborted as the signer is the seller."));
            }

            var sw = new Stopwatch();

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

            Log.Debug("Buy exec started.");

            if (!states.TryGetAgentAvatarStates(ctx.Signer, buyerAvatarAddress, out var buyerAgentState, out var buyerAvatarState))
            {
                return(LogError(context, "Aborted as the avatar state of the buyer was failed to load."));
            }
            sw.Stop();
            Log.Debug("Buy Get Buyer AgentAvatarStates: {Elapsed}", sw.Elapsed);
            sw.Restart();

            if (!buyerAvatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(out var world))
            {
                return(LogError(context, "Aborted as the WorldInformation was failed to load."));
            }

            if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInShop)
            {
                // 스테이지 클리어 부족 에러.
                return(LogError(
                           context,
                           "Aborted as the signer is not cleared the minimum stage level required to use shop yet: {ClearedLevel} < {RequiredLevel}.",
                           world.StageClearedId,
                           GameConfig.RequireClearedStageLevel.ActionsInShop
                           ));
            }

            if (!states.TryGetState(ShopState.Address, out Bencodex.Types.Dictionary d))
            {
                return(LogError(context, "Aborted as the shop state was failed to load."));
            }

            var shopState = new ShopState(d);

            sw.Stop();
            Log.Debug("Buy Get ShopState: {Elapsed}", sw.Elapsed);
            sw.Restart();

            Log.Debug("Execute Buy; buyer: {Buyer} seller: {Seller}", buyerAvatarAddress, sellerAvatarAddress);
            // 상점에서 구매할 아이템을 찾는다.
            if (!shopState.TryGet(sellerAgentAddress, productId, out var outPair))
            {
                return(LogError(
                           context,
                           "Aborted as the shop item ({ProductId}) was failed to get from the seller agent ({SellerAgent}).",
                           productId,
                           sellerAgentAddress
                           ));
            }
            sw.Stop();
            Log.Debug($"Buy Get Item: {sw.Elapsed}");
            sw.Restart();

            if (!states.TryGetAgentAvatarStates(sellerAgentAddress, sellerAvatarAddress, out var sellerAgentState, out var sellerAvatarState))
            {
                return(LogError(
                           context,
                           "Aborted as the seller agent/avatar was filed to load from {SellerAgent}/{SellerAvatar}.",
                           sellerAgentAddress,
                           sellerAvatarAddress
                           ));
            }
            sw.Stop();
            Log.Debug($"Buy Get Seller AgentAvatarStates: {sw.Elapsed}");
            sw.Restart();

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

            if (buyerBalance < outPair.Value.Price)
            {
                return(LogError(
                           context,
                           "Aborted as the buyer ({Buyer}) has no sufficient gold: {BuyerBalance} < {ItemPrice}",
                           ctx.Signer,
                           buyerBalance,
                           outPair.Value.Price
                           ));
            }

            var taxedPrice = (BigInteger)decimal.Round((decimal)outPair.Value.Price * 0.92m);

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

            // 상점에서 구매할 아이템을 제거한다.
            if (!shopState.Unregister(sellerAgentAddress, outPair.Value))
            {
                return(LogError(
                           context,
                           "Aborted as the item ({@ProductId}) was failed to unregister from seller ({Seller})'s inventory.",
                           outPair.Value.ProductId,
                           sellerAgentAddress
                           ));
            }

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

            buyerResult.id = buyerMail.id;

            sellerResult = new SellerResult
            {
                shopItem   = outPair.Value,
                itemUsable = outPair.Value.ItemUsable,
                gold       = taxedPrice
            };
            var sellerMail = new SellerMail(sellerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(),
                                            ctx.BlockIndex);

            sellerResult.id = sellerMail.id;

            buyerAvatarState.Update(buyerMail);
            buyerAvatarState.UpdateFromAddItem(buyerResult.itemUsable, false);
            sellerAvatarState.Update(sellerMail);

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

            var timestamp = DateTimeOffset.UtcNow;

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

            buyerAvatarState.UpdateQuestRewards(ctx);
            sellerAvatarState.UpdateQuestRewards(ctx);

            states = states.SetState(sellerAvatarAddress, sellerAvatarState.Serialize());
            sw.Stop();
            Log.Debug("Buy Set Seller AvatarState: {Elapsed}", sw.Elapsed);
            sw.Restart();

            states = states.SetState(buyerAvatarAddress, buyerAvatarState.Serialize());
            sw.Stop();
            Log.Debug("Buy Set Buyer AvatarState: {Elapsed}", sw.Elapsed);
            sw.Restart();

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

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

            return(states.SetState(ctx.Signer, buyerAgentState.Serialize()));
        }