Exemple #1
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);
        }
Exemple #2
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);
        }
Exemple #3
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);
        }