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