Example #1
0
File: Buy6.cs Project: dahlia/lib9c
        public override IAccountStateDelta Execute(IActionContext context)
        {
            IActionContext ctx    = context;
            var            states = ctx.PreviousStates;

            if (ctx.Rehearsal)
            {
                foreach (var purchaseInfo in purchaseInfos)
                {
                    Address shardedShopAddress =
                        ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId);
                    states = states
                             .SetState(shardedShopAddress, MarkChanged)
                             .SetState(purchaseInfo.sellerAvatarAddress, MarkChanged)
                             .MarkBalanceChanged(
                        GoldCurrencyMock,
                        ctx.Signer,
                        purchaseInfo.sellerAgentAddress,
                        GoldCurrencyState.Address);
                }
                return(states
                       .SetState(buyerAvatarAddress, MarkChanged)
                       .SetState(ctx.Signer, MarkChanged)
                       .SetState(Addresses.Shop, MarkChanged));
            }

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

            var addressesHex = GetSignerAndOtherAddressesHex(context, buyerAvatarAddress);

            var sw = new Stopwatch();

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

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

            if (!states.TryGetAvatarState(ctx.Signer, buyerAvatarAddress, out var buyerAvatarState))
            {
                throw new FailedLoadStateException(
                          $"{addressesHex}Aborted as the avatar state of the buyer was failed to load.");
            }

            sw.Stop();
            Log.Verbose("{AddressesHex}Buy Get Buyer AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed);
            sw.Restart();

            if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop))
            {
                buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current);
                throw new NotEnoughClearedStageLevelException(addressesHex,
                                                              GameConfig.RequireClearedStageLevel.ActionsInShop, current);
            }

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

            buyerMultipleResult  = new Buy7.BuyerMultipleResult();
            sellerMultipleResult = new Buy7.SellerMultipleResult();

            foreach (var purchaseInfo in purchaseInfos)
            {
                Buy7.PurchaseResult purchaseResult     = new Buy7.PurchaseResult(purchaseInfo.productId);
                Address             shardedShopAddress =
                    ShardedShopState.DeriveAddress(purchaseInfo.itemSubType, purchaseInfo.productId);
                Address sellerAgentAddress  = purchaseInfo.sellerAgentAddress;
                Address sellerAvatarAddress = purchaseInfo.sellerAvatarAddress;
                Guid    productId           = purchaseInfo.productId;

                purchaseResults.Add(purchaseResult);

                if (purchaseInfo.sellerAgentAddress == ctx.Signer)
                {
                    purchaseResult.errorCode = ErrorCodeInvalidAddress;
                    continue;
                }

                if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict))
                {
                    ShardedShopState shardedShopState = new ShardedShopState(shardedShopAddress);
                    shopStateDict = (Dictionary)shardedShopState.Serialize();
                }

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

                Log.Verbose(
                    "{AddressesHex}Execute Buy; buyer: {Buyer} seller: {Seller}",
                    addressesHex,
                    buyerAvatarAddress,
                    sellerAvatarAddress);
                // Find product from ShardedShopState.
                List       products              = (List)shopStateDict[ProductsKey];
                IValue     productIdSerialized   = productId.Serialize();
                IValue     sellerAgentSerialized = purchaseInfo.sellerAgentAddress.Serialize();
                Dictionary productSerialized     = products
                                                   .Select(p => (Dictionary)p)
                                                   .FirstOrDefault(p =>
                                                                   p[LegacyProductIdKey].Equals(productIdSerialized) &&
                                                                   p[LegacySellerAgentAddressKey].Equals(sellerAgentSerialized));

                bool fromLegacy = false;
                if (productSerialized.Equals(Dictionary.Empty))
                {
                    if (purchaseInfo.itemSubType == ItemSubType.Hourglass ||
                        purchaseInfo.itemSubType == ItemSubType.ApStone)
                    {
                        purchaseResult.errorCode = ErrorCodeItemDoesNotExist;
                        continue;
                    }
                    // Backward compatibility.
                    IValue rawShop = states.GetState(Addresses.Shop);
                    if (!(rawShop is null))
                    {
                        Dictionary legacyShopDict = (Dictionary)rawShop;
                        Dictionary legacyProducts = (Dictionary)legacyShopDict[LegacyProductsKey];
                        IKey       productKey     = (IKey)productId.Serialize();
                        // SoldOut
                        if (!legacyProducts.ContainsKey(productKey))
                        {
                            purchaseResult.errorCode = ErrorCodeItemDoesNotExist;
                            continue;
                        }

                        productSerialized = (Dictionary)legacyProducts[productKey];
                        legacyProducts    = (Dictionary)legacyProducts.Remove(productKey);
                        legacyShopDict    = legacyShopDict.SetItem(LegacyProductsKey, legacyProducts);
                        states            = states.SetState(Addresses.Shop, legacyShopDict);
                        fromLegacy        = true;
                    }
                }

                ShopItem shopItem = new ShopItem(productSerialized);
                if (!shopItem.SellerAgentAddress.Equals(sellerAgentAddress))
                {
                    purchaseResult.errorCode = ErrorCodeItemDoesNotExist;
                    continue;
                }

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

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

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

                sw.Stop();
                Log.Verbose("{AddressesHex}Buy Get Seller AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed);
                sw.Restart();

                // Check Balance.
                FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency());
                if (buyerBalance < shopItem.Price)
                {
                    purchaseResult.errorCode = ErrorCodeInsufficientBalance;
                    continue;
                }

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

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

                // Transfer seller.
                states = states.TransferAsset(
                    context.Signer,
                    sellerAgentAddress,
                    taxedPrice
                    );

                products      = (List)products.Remove(productSerialized);
                shopStateDict = shopStateDict.SetItem(ProductsKey, new List <IValue>(products));

                ITradableItem tradableItem;
                int           count = 1;
                if (!(shopItem.ItemUsable is null))
                {
                    tradableItem = shopItem.ItemUsable;
                }
Example #2
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);
        }