public override IAccountStateDelta Execute(IActionContext context) { var sellData = TestbedHelper.LoadData <TestbedSell>("TestbedSell"); var addedItemInfos = sellData.Items .Select(item => new TestbedHelper.AddedItemInfo( context.Random.GenerateRandomGuid(), context.Random.GenerateRandomGuid())) .ToList(); var agentAddress = _privateKey.PublicKey.ToAddress(); var states = context.PreviousStates; var avatarAddress = agentAddress.Derive( string.Format( CultureInfo.InvariantCulture, CreateAvatar.DeriveFormat, _slotIndex ) ); var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); var questListAddress = avatarAddress.Derive(LegacyQuestListKey); var orderReceiptAddress = OrderDigestListState.DeriveAddress(avatarAddress); var weeklyArenaData = TestbedHelper.LoadData <TestbedWeeklyArena>("TestbedWeeklyArena"); var weeklyArenaAvatarNames = weeklyArenaData.Avatars .Distinct() .Select(avatar => avatar.Name) .ToArray(); var weeklyArenaAgentAndAvatarAddresses = weeklyArenaAvatarNames .Select(name => _privateKey.ToAddress().Derive(name)) .Select(e => (agentAddress: e, avatarAddress: e.Derive( string.Format( CultureInfo.InvariantCulture, CreateAvatar.DeriveFormat, 0 ) ))) .ToArray(); if (context.Rehearsal) { states = states.SetState(agentAddress, MarkChanged); for (var i = 0; i < AvatarState.CombinationSlotCapacity; i++) { var slotAddress = avatarAddress.Derive( string.Format(CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, i)); states = states.SetState(slotAddress, MarkChanged); } states = states.SetState(avatarAddress, MarkChanged) .SetState(Addresses.Ranking, MarkChanged) .SetState(worldInformationAddress, MarkChanged) .SetState(questListAddress, MarkChanged) .SetState(inventoryAddress, MarkChanged); for (var i = 0; i < sellData.Items.Length; i++) { var itemAddress = Addresses.GetItemAddress(addedItemInfos[i].TradableId); var orderAddress = Order.DeriveAddress(addedItemInfos[i].OrderId); var shopAddress = ShardedShopStateV2.DeriveAddress( sellData.Items[i].ItemSubType, addedItemInfos[i].OrderId); states = states.SetState(avatarAddress, MarkChanged) .SetState(inventoryAddress, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, agentAddress, GoldCurrencyState.Address) .SetState(orderReceiptAddress, MarkChanged) .SetState(itemAddress, MarkChanged) .SetState(orderAddress, MarkChanged) .SetState(shopAddress, MarkChanged); } states = states.SetState(weeklyArenaAddress, MarkChanged); foreach (var tuple in weeklyArenaAgentAndAvatarAddresses) { states = states .SetState(tuple.agentAddress, MarkChanged) .SetState(tuple.avatarAddress, MarkChanged); } return(states); } // Create Agent and avatar var existingAgentState = states.GetAgentState(agentAddress); var agentState = existingAgentState ?? new AgentState(agentAddress); var avatarState = states.GetAvatarState(avatarAddress); if (!(avatarState is null)) { throw new InvalidAddressException( $"Aborted as there is already an avatar at {avatarAddress}."); } if (agentState.avatarAddresses.ContainsKey(_slotIndex)) { throw new AvatarIndexAlreadyUsedException( $"Aborted as the signer already has an avatar at index #{_slotIndex}."); } agentState.avatarAddresses.Add(_slotIndex, avatarAddress); var rankingState = context.PreviousStates.GetRankingState(); var rankingMapAddress = rankingState.UpdateRankingMap(avatarAddress); avatarState = TestbedHelper.CreateAvatarState(sellData.Avatar.Name, agentAddress, avatarAddress, context.BlockIndex, context.PreviousStates.GetAvatarSheets(), context.PreviousStates.GetSheet <WorldSheet>(), context.PreviousStates.GetGameConfigState(), rankingMapAddress); // Add item var costumeItemSheet = context.PreviousStates.GetSheet <CostumeItemSheet>(); var equipmentItemSheet = context.PreviousStates.GetSheet <EquipmentItemSheet>(); var optionSheet = context.PreviousStates.GetSheet <EquipmentItemOptionSheet>(); var skillSheet = context.PreviousStates.GetSheet <SkillSheet>(); var materialItemSheet = context.PreviousStates.GetSheet <MaterialItemSheet>(); var consumableItemSheet = context.PreviousStates.GetSheet <ConsumableItemSheet>(); for (var i = 0; i < sellData.Items.Length; i++) { TestbedHelper.AddItem(costumeItemSheet, equipmentItemSheet, optionSheet, skillSheet, materialItemSheet, consumableItemSheet, context.Random, sellData.Items[i], addedItemInfos[i], avatarState); } avatarState.Customize(0, 0, 0, 0); foreach (var address in avatarState.combinationSlotAddresses) { var slotState = new CombinationSlotState(address, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); states = states.SetState(address, slotState.Serialize()); } avatarState.UpdateQuestRewards(materialItemSheet); states = states.SetState(agentAddress, agentState.Serialize()) .SetState(Addresses.Ranking, rankingState.Serialize()) .SetState(inventoryAddress, avatarState.inventory.Serialize()) .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) .SetState(questListAddress, avatarState.questList.Serialize()) .SetState(avatarAddress, avatarState.SerializeV2()); // ~Create Agent and avatar && ~Add item // for sell var costumeStatSheet = states.GetSheet <CostumeStatSheet>(); for (var i = 0; i < sellData.Items.Length; i++) { var itemAddress = Addresses.GetItemAddress(addedItemInfos[i].TradableId); var orderAddress = Order.DeriveAddress(addedItemInfos[i].OrderId); var shopAddress = ShardedShopStateV2.DeriveAddress( sellData.Items[i].ItemSubType, addedItemInfos[i].OrderId); var balance = context.PreviousStates.GetBalance(agentAddress, states.GetGoldCurrency()); var price = new FungibleAssetValue(balance.Currency, sellData.Items[i].Price, 0); var order = OrderFactory.Create(agentAddress, avatarAddress, addedItemInfos[i].OrderId, price, addedItemInfos[i].TradableId, context.BlockIndex, sellData.Items[i].ItemSubType, sellData.Items[i].Count); Orders.Add(order); order.Validate(avatarState, sellData.Items[i].Count); var tradableItem = order.Sell(avatarState); var shardedShopState = states.TryGetState(shopAddress, out Dictionary serializedState) ? new ShardedShopStateV2(serializedState) : new ShardedShopStateV2(shopAddress); var orderDigest = order.Digest(avatarState, costumeStatSheet); shardedShopState.Add(orderDigest, context.BlockIndex); var orderReceiptList = states.TryGetState(orderReceiptAddress, out Dictionary receiptDict) ? new OrderDigestListState(receiptDict) : new OrderDigestListState(orderReceiptAddress); orderReceiptList.Add(orderDigest); states = states.SetState(orderReceiptAddress, orderReceiptList.Serialize()) .SetState(inventoryAddress, avatarState.inventory.Serialize()) .SetState(avatarAddress, avatarState.SerializeV2()) .SetState(itemAddress, tradableItem.Serialize()) .SetState(orderAddress, order.Serialize()) .SetState(shopAddress, shardedShopState.Serialize()); } result.SellerAgentAddress = agentAddress; result.SellerAvatarAddress = avatarAddress; result.ItemInfos = new List <ItemInfos>(); for (var i = 0; i < sellData.Items.Length; i++) { result.ItemInfos.Add(new ItemInfos( addedItemInfos[i].OrderId, addedItemInfos[i].TradableId, sellData.Items[i].ItemSubType, sellData.Items[i].Price, sellData.Items[i].Count)); } // NOTE: Update WeeklyArenaState var gameConfigState = states.GetGameConfigState(); var weeklyArenaState = states.GetWeeklyArenaState(weeklyArenaAddress); var characterSheet = states.GetSheet <CharacterSheet>(); // loop for (var i = 0; i < weeklyArenaAvatarNames.Length; i++) { var name = weeklyArenaAvatarNames[i]; var tuple = weeklyArenaAgentAndAvatarAddresses[i]; rankingMapAddress = rankingState.UpdateRankingMap(tuple.avatarAddress); avatarState = new AvatarState( tuple.avatarAddress, tuple.agentAddress, context.BlockIndex, states.GetAvatarSheets(), gameConfigState, rankingMapAddress, name); weeklyArenaState.SetV2(avatarState, characterSheet, costumeStatSheet); states = states .SetState(tuple.avatarAddress, new AgentState(tuple.agentAddress).Serialize()) .SetState(tuple.avatarAddress, avatarState.Serialize()); } // ~loop states = states.SetState(weeklyArenaAddress, weeklyArenaState.Serialize()); // ~Update WeeklyArenaState return(states); }
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)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); 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 (!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 _) * Buy7.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); // 구매자, 판매자에게 결과 메일 전송 buyerResult = new Buy7.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 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; buyerAvatarState.Update(buyerMail); if (buyerResult.itemUsable != null) { buyerAvatarState.UpdateFromAddItem2(buyerResult.itemUsable, false); } if (buyerResult.costume != null) { buyerAvatarState.UpdateFromAddCostume(buyerResult.costume, false); } sellerAvatarState.Update(sellerMail); // 퀘스트 업데이트 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.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(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; var slotAddress = AvatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, SlotIndex ) ); if (ctx.Rehearsal) { return(states .SetState(AvatarAddress, MarkChanged) .SetState(slotAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, BlacksmithAddress)); } if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out var agentState, out var avatarState)) { return(LogError(context, "Aborted as the avatar state of the signer was failed to load.")); } var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); if (slotState is null || !(slotState.Validate(avatarState, ctx.BlockIndex))) { return(LogError( context, "Aborted as the slot state is failed to load or invalid: {@SlotState} @ {SlotIndex}", slotState, SlotIndex )); } var tableSheets = TableSheets.FromActionContext(ctx); var recipeSheet = tableSheets.EquipmentItemRecipeSheet; var materialSheet = tableSheets.MaterialItemSheet; var materials = new Dictionary <Material, int>(); // 레시피 검증 if (!recipeSheet.TryGetValue(RecipeId, out var recipe)) { return(LogError( context, "Aborted as the recipe {RecipeId} was failed to load from the sheet.", RecipeId )); } if (!(SubRecipeId is null)) { if (!recipe.SubRecipeIds.Contains((int)SubRecipeId)) { return(LogError( context, "Aborted as the subrecipe {SubRecipeId} was failed to load from the sheet.", SubRecipeId )); } } // 메인 레시피 해금 검사. if (!avatarState.worldInformation.IsStageCleared(recipe.UnlockStage)) { return(LogError( context, "Aborted as the signer is not cleared the minimum stage level required to use the recipe {@Recipe} yet.", recipe )); } if (!materialSheet.TryGetValue(recipe.MaterialId, out var material)) { return(LogError( context, "Aborted as the material {MaterialId} was failed to load from the sheet.", recipe.MaterialId )); } if (!avatarState.inventory.RemoveMaterial(material.ItemId, recipe.MaterialCount)) { return(LogError( context, "Aborted as the player has no enough material ({Material} * {Quantity})", material, recipe.MaterialCount )); } var equipmentMaterial = ItemFactory.CreateMaterial(materialSheet, material.Id); materials[equipmentMaterial] = recipe.MaterialCount; BigInteger requiredGold = recipe.RequiredGold; var requiredActionPoint = recipe.RequiredActionPoint; // 장비 제작 if (!tableSheets.EquipmentItemSheet.TryGetValue(recipe.ResultEquipmentId, out var equipRow)) { return(LogError( context, "Aborted as the equipment item {EquipmentId} was failed to load from the sheet.", recipe.ResultEquipmentId )); } var requiredBlockIndex = ctx.BlockIndex + recipe.RequiredBlockIndex; var equipment = (Equipment)ItemFactory.CreateItemUsable( equipRow, ctx.Random.GenerateRandomGuid(), requiredBlockIndex ); // 서브 레시피 검증 HashSet <int> optionIds = null; if (SubRecipeId.HasValue) { var subSheet = tableSheets.EquipmentItemSubRecipeSheet; if (!subSheet.TryGetValue((int)SubRecipeId, out var subRecipe)) { return(LogError( context, "Aborted as the subrecipe {SubRecipeId} was failed to load from the subsheet.", SubRecipeId )); } requiredBlockIndex += subRecipe.RequiredBlockIndex; requiredGold += subRecipe.RequiredGold; requiredActionPoint += subRecipe.RequiredActionPoint; foreach (var materialInfo in subRecipe.Materials) { if (!materialSheet.TryGetValue(materialInfo.Id, out var subMaterialRow)) { return(LogError( context, "Aborted as the meterial info {MaterialInfoId} was failed to load from the submaterial sheet.", materialInfo.Id )); } if (!avatarState.inventory.RemoveMaterial(subMaterialRow.ItemId, materialInfo.Count)) { return(LogError( context, "Aborted as the player has no enough material ({Material} * {Quantity})", subMaterialRow, materialInfo.Count )); } var subMaterial = ItemFactory.CreateMaterial(materialSheet, materialInfo.Id); materials[subMaterial] = materialInfo.Count; } optionIds = SelectOption(tableSheets, subRecipe, ctx.Random, equipment); equipment.Update(requiredBlockIndex); } // 자원 검증 FungibleAssetValue agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); if (agentBalance < (states.GetGoldCurrency() * requiredGold) || avatarState.actionPoint < requiredActionPoint) { return(LogError( context, "Aborted due to insufficient action point: {ActionPointBalance} < {ActionCost}", avatarState.actionPoint, requiredActionPoint )); } avatarState.actionPoint -= requiredActionPoint; if (!(optionIds is null)) { foreach (var id in optionIds) { agentState.unlockedOptions.Add(id); } } // FIXME: BlacksmithAddress 계좌로 돈이 쌓이기만 하는데 이걸 어떻게 순환시킬지 기획이 필요. if (requiredGold > 0) { states = states.TransferAsset( ctx.Signer, BlacksmithAddress, states.GetGoldCurrency() * requiredGold ); } var result = new CombinationConsumable.ResultModel { actionPoint = requiredActionPoint, gold = requiredGold, materials = materials, itemUsable = equipment, recipeId = RecipeId, subRecipeId = SubRecipeId, itemType = ItemType.Equipment, }; slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); var mail = new CombinationMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), requiredBlockIndex); result.id = mail.id; avatarState.Update(mail); avatarState.questList.UpdateCombinationEquipmentQuest(RecipeId); avatarState.UpdateFromCombination(equipment); avatarState.UpdateQuestRewards(ctx); return(states .SetState(AvatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()) .SetState(ctx.Signer, agentState.Serialize())); }
public PriceData(Currency currency) { TaxSum = new FungibleAssetValue(currency, 0, 0); TaxedPriceSum = new Dictionary <Address, FungibleAssetValue>(); PriceSum = new FungibleAssetValue(currency, 0, 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.NonFungibleId, 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; } }
public TransferAsset(Address sender, Address recipient, FungibleAssetValue amount) { Sender = sender; Recipient = recipient; Amount = amount; }
private void SetGold(FungibleAssetValue gold) { text.text = gold.GetQuantityString(); }
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 <PurchaseResult> purchaseResults = new List <PurchaseResult>(); List <SellerResult> sellerResults = new List <SellerResult>(); MaterialItemSheet materialSheet = states.GetSheet <MaterialItemSheet>(); buyerMultipleResult = new BuyerMultipleResult(); sellerMultipleResult = new SellerMultipleResult(); foreach (var purchaseInfo in purchaseInfos) { PurchaseResult purchaseResult = new 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(); IValue sellerAvatarSerialized = purchaseInfo.sellerAvatarAddress.Serialize(); Dictionary productSerialized = products .Select(p => (Dictionary)p) .FirstOrDefault(p => p[LegacyProductIdKey].Equals(productIdSerialized) && p[LegacySellerAvatarAddressKey].Equals(sellerAvatarSerialized) && 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 (!shopItem.Price.Equals(purchaseInfo.price)) { purchaseResult.errorCode = ErrorCodeInvalidPrice; 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; } // Check Seller inventory. ITradableItem tradableItem; int count = 1; if (!(shopItem.ItemUsable is null)) { tradableItem = shopItem.ItemUsable; }
public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) => this;
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { return(states.SetState(ctx.Signer, MarkChanged) .SetState(AvatarAddress, MarkChanged) .SetState(WeeklyArenaAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, WeeklyArenaAddress)); } if (AvatarAddress.Equals(EnemyAddress)) { return(LogError(context, "Aborted as the signer tried to battle for themselves.")); } if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out var agentState, out var avatarState)) { return(LogError(context, "Aborted as the avatar state of the signer was failed to load.")); } // 도전자의 장비가 유효한지 검사한다. // 피도전자의 장비도 검사해야 하는가는 모르겠다. 이후에 필요하다면 추가하는 것으로 한다. if (!avatarState.ValidateEquipments(equipmentIds, context.BlockIndex)) { // 장비가 유효하지 않은 에러. return(LogError(context, "Aborted as the equipment is invalid.")); } if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(out var world)) { return(LogError(context, "Aborted as the WorldInformation was failed to load or not cleared yet.")); } if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) { // 스테이지 클리어 부족 에러. return(LogError( context, "Aborted as the signer is not cleared the minimum stage level required to battle with other players yet: {ClearedLevel} < {RequiredLevel}.", world.StageClearedId, GameConfig.RequireClearedStageLevel.ActionsInRankingBoard )); } avatarState.EquipCostumes(costumeIds); avatarState.EquipEquipments(equipmentIds); var enemyAvatarState = states.GetAvatarState(EnemyAddress); if (enemyAvatarState is null) { return(LogError( context, "Aborted as the avatar state of the opponent ({OpponentAddress}) was failed to load.", EnemyAddress )); } var weeklyArenaState = states.GetWeeklyArenaState(WeeklyArenaAddress); //FIXME 오류던지게 고쳐야함 if (weeklyArenaState.Ended) { return(LogError(context, "Aborted as the weekly arena state already ended.")); } if (!weeklyArenaState.ContainsKey(AvatarAddress)) { return(LogError(context, "Aborted as the weekly arena state was failed to load.")); } var arenaInfo = weeklyArenaState[AvatarAddress]; if (arenaInfo.DailyChallengeCount <= 0) { return(LogError(context, "Aborted as the arena state reached the daily limit.")); } if (!arenaInfo.Active) { FungibleAssetValue agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); if (agentBalance >= new FungibleAssetValue(agentBalance.Currency, EntranceFee, 0)) { states = states.TransferAsset( ctx.Signer, WeeklyArenaAddress, new FungibleAssetValue( states.GetGoldCurrency(), EntranceFee, 0 ) ); arenaInfo.Activate(); } else { return(LogError( context, "Aborted as the signer's balance ({Balance}) is insufficient to pay entrance fee/stake ({EntranceFee}).", agentBalance, EntranceFee )); } } if (!weeklyArenaState.ContainsKey(EnemyAddress)) { return(LogError( context, "Aborted as the opponent ({OpponentAddress}) is not registered in the weekly arena state.", EnemyAddress )); } Log.Debug(weeklyArenaState.address.ToHex()); var tableSheetState = TableSheetsState.FromActionContext(ctx); var tableSheets = TableSheets.FromTableSheetsState(tableSheetState); var simulator = new RankingSimulator( ctx.Random, avatarState, enemyAvatarState, consumableIds, tableSheets, StageId, arenaInfo, weeklyArenaState[EnemyAddress]); simulator.Simulate(); Result = simulator.Log; foreach (var itemBase in simulator.Reward) { avatarState.inventory.AddItem(itemBase); } return(states .SetState(ctx.Signer, agentState.Serialize()) .SetState(WeeklyArenaAddress, weeklyArenaState.Serialize()) .SetState(AvatarAddress, avatarState.Serialize())); }
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); }
public override int ValidateTransfer2(AvatarState avatarState, Guid tradableId, FungibleAssetValue price, long blockIndex) { var errorCode = base.ValidateTransfer2(avatarState, tradableId, price, blockIndex); if (errorCode != 0) { return(errorCode); } if (!avatarState.inventory.TryGetNonFungibleItem(TradableId, out INonFungibleItem nonFungibleItem)) { return(Buy.ErrorCodeItemDoesNotExist); } return(!nonFungibleItem.ItemSubType.Equals(ItemSubType) ? Buy.ErrorCodeInvalidItemType : errorCode); }
public override int ValidateTransfer(AvatarState avatarState, Guid tradableId, FungibleAssetValue price, long blockIndex) { var errorCode = base.ValidateTransfer(avatarState, tradableId, price, blockIndex); if (errorCode != 0) { return(errorCode); } if (!avatarState.inventory.TryGetLockedItem(new OrderLock(OrderId), out var inventoryItem)) { return(Buy.ErrorCodeItemDoesNotExist); } if (inventoryItem.item is INonFungibleItem nonFungibleItem) { return(nonFungibleItem.ItemSubType.Equals(ItemSubType) ? errorCode : Buy.ErrorCodeInvalidItemType); } return(Buy.ErrorCodeItemDoesNotExist); }
public void Balance( [Option('v', Description = "Print more logs.")] bool verbose, [Option('s', Description = "Path to the chain store.")] string storePath, [Option( 'b', Description = "Optional block hash/index offset to query balances at. " + "Tip by default.")] string block = null, [Option('c', Description = "Optional chain ID. Default is the canonical chain ID.")] Guid?chainId = null, [Argument(Description = "Account address.")] string address = null ) { using Logger logger = Utils.ConfigureLogger(verbose); TextWriter stderr = Console.Error; (BlockChain <NCAction> chain, IStore store, _, _) = Utils.GetBlockChain(logger, storePath, chainId); Block <NCAction> offset = Utils.ParseBlockOffset(chain, block); stderr.WriteLine("The offset block: #{0} {1}.", offset.Index, offset.Hash); Bencodex.Types.Dictionary goldCurrencyStateDict = (Bencodex.Types.Dictionary) chain.GetState(GoldCurrencyState.Address); GoldCurrencyState goldCurrencyState = new GoldCurrencyState(goldCurrencyStateDict); Currency gold = goldCurrencyState.Currency; if (address is {} addrStr) { Address addr = Utils.ParseAddress(addrStr); FungibleAssetValue balance = chain.GetBalance(addr, gold, offset.Hash); Console.WriteLine("{0}\t{1}", addr, balance); return; } var printed = new HashSet <Address>(); foreach (BlockHash blockHash in chain.BlockHashes) { BlockDigest digest = GetBlockDigest(store, blockHash); stderr.WriteLine("Scanning block #{0} {1}...", digest.Index, digest.Hash); stderr.Flush(); IEnumerable <Address> addrs = digest.TxIds .Select(txId => store.GetTransaction <NCAction>(new TxId(txId.ToArray()))) .SelectMany(tx => tx.Actions .Select(a => a.InnerAction) .SelectMany(a => a is TransferAsset t ? new[] { t.Sender, t.Recipient } : a is InitializeStates i && i.GoldDistributions is Bencodex.Types.List l ? l.OfType <Bencodex.Types.Dictionary>() .Select(d => new GoldDistribution(d).Address) : new Address[0])) .Append(digest.Miner); foreach (Address addr in addrs) { if (!printed.Contains(addr)) { FungibleAssetValue balance = chain.GetBalance(addr, gold, offset.Hash); Console.WriteLine("{0}\t{1}", addr, balance); printed.Add(addr); } } } }
public static IValue Serialize(this FungibleAssetValue value) => new Bencodex.Types.List(new IValue[] { CurrencyExtensions.Serialize(value.Currency), value.RawValue.Serialize(), });
public IAccountStateDelta TransferAsset( Address sender, Address recipient, FungibleAssetValue value, bool allowNegativeBalance = false ) => this;
public void Execute( ItemType itemType, bool orderExist, int itemCount, bool backward ) { var avatarState = _initialState.GetAvatarState(_avatarAddress); ITradableItem tradableItem; switch (itemType) { case ItemType.Consumable: tradableItem = ItemFactory.CreateItemUsable( _tableSheets.ConsumableItemSheet.First, Guid.NewGuid(), 0); break; case ItemType.Costume: tradableItem = ItemFactory.CreateCostume( _tableSheets.CostumeItemSheet.First, Guid.NewGuid()); break; case ItemType.Equipment: tradableItem = ItemFactory.CreateItemUsable( _tableSheets.EquipmentItemSheet.First, Guid.NewGuid(), 0); break; case ItemType.Material: var tradableMaterialRow = _tableSheets.MaterialItemSheet.OrderedList .First(row => row.ItemSubType == ItemSubType.Hourglass); tradableItem = ItemFactory.CreateTradableMaterial(tradableMaterialRow); break; default: throw new ArgumentOutOfRangeException(nameof(itemType), itemType, null); } Assert.Equal(0, tradableItem.RequiredBlockIndex); avatarState.inventory.AddItem2((ItemBase)tradableItem, itemCount); var previousStates = _initialState; if (backward) { previousStates = previousStates.SetState(_avatarAddress, avatarState.Serialize()); } else { previousStates = previousStates .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) .SetState(_avatarAddress, avatarState.SerializeV2()); } var currencyState = previousStates.GetGoldCurrency(); var price = new FungibleAssetValue(currencyState, ProductPrice, 0); var orderId = new Guid("6f460c1a755d48e4ad6765d5f519dbc8"); var existOrderId = new Guid("229e5f8c-fabe-4c04-bab9-45325cfa69a4"); var orderAddress = Order.DeriveAddress(orderId); var shardedShopAddress = ShardedShopStateV2.DeriveAddress( tradableItem.ItemSubType, orderId); long blockIndex = 1; if (orderExist) { var shardedShopState = new ShardedShopStateV2(shardedShopAddress); var existOrder = OrderFactory.Create( _agentAddress, _avatarAddress, existOrderId, price, tradableItem.TradableId, 0, tradableItem.ItemSubType, 1 ); existOrder.Sell2(avatarState); blockIndex = existOrder.ExpiredBlockIndex; shardedShopState.Add(existOrder.Digest2(avatarState, _tableSheets.CostumeStatSheet), blockIndex); Assert.Single(shardedShopState.OrderDigestList); previousStates = previousStates.SetState( shardedShopAddress, shardedShopState.Serialize()); } else { Assert.Null(previousStates.GetState(shardedShopAddress)); } var sellAction = new Sell8 { sellerAvatarAddress = _avatarAddress, tradableId = tradableItem.TradableId, count = itemCount, price = price, itemSubType = tradableItem.ItemSubType, orderId = orderId, }; var nextState = sellAction.Execute(new ActionContext { BlockIndex = blockIndex, PreviousStates = previousStates, Rehearsal = false, Signer = _agentAddress, Random = new TestRandom(), }); long expiredBlockIndex = Order.ExpirationInterval + blockIndex; // Check AvatarState and Inventory var nextAvatarState = nextState.GetAvatarStateV2(_avatarAddress); Assert.Single(nextAvatarState.inventory.Items); Assert.True(nextAvatarState.inventory.TryGetTradableItems( tradableItem.TradableId, expiredBlockIndex, 1, out var inventoryItems)); Assert.Single(inventoryItems); ITradableItem nextTradableItem = (ITradableItem)inventoryItems.First().item; Assert.Equal(expiredBlockIndex, nextTradableItem.RequiredBlockIndex); // Check ShardedShopState var nextSerializedShardedShopState = nextState.GetState(shardedShopAddress); Assert.NotNull(nextSerializedShardedShopState); var nextShardedShopState = new ShardedShopStateV2((Dictionary)nextSerializedShardedShopState); var expectedCount = orderExist ? 2 : 1; Assert.Equal(expectedCount, nextShardedShopState.OrderDigestList.Count); var orderDigest = nextShardedShopState.OrderDigestList.First(o => o.OrderId.Equals(orderId)); Assert.Equal(price, orderDigest.Price); Assert.Equal(blockIndex, orderDigest.StartedBlockIndex); Assert.Equal(expiredBlockIndex, orderDigest.ExpiredBlockIndex); Assert.Equal(((ItemBase)tradableItem).Id, orderDigest.ItemId); Assert.Equal(tradableItem.TradableId, orderDigest.TradableId); var serializedOrder = nextState.GetState(orderAddress); Assert.NotNull(serializedOrder); var serializedItem = nextState.GetState(Addresses.GetItemAddress(tradableItem.TradableId)); Assert.NotNull(serializedItem); var order = OrderFactory.Deserialize((Dictionary)serializedOrder); ITradableItem orderItem = (ITradableItem)ItemFactory.Deserialize((Dictionary)serializedItem); Assert.Equal(price, order.Price); Assert.Equal(orderId, order.OrderId); Assert.Equal(tradableItem.TradableId, order.TradableId); Assert.Equal(blockIndex, order.StartedBlockIndex); Assert.Equal(expiredBlockIndex, order.ExpiredBlockIndex); Assert.Equal(_agentAddress, order.SellerAgentAddress); Assert.Equal(_avatarAddress, order.SellerAvatarAddress); Assert.Equal(expiredBlockIndex, orderItem.RequiredBlockIndex); var receiptDict = nextState.GetState(OrderDigestListState.DeriveAddress(_avatarAddress)); Assert.NotNull(receiptDict); var orderDigestList = new OrderDigestListState((Dictionary)receiptDict); Assert.Single(orderDigestList.OrderDigestList); OrderDigest orderDigest2 = orderDigestList.OrderDigestList.First(); Assert.Equal(orderDigest, orderDigest2); }
public IAccountStateDelta BurnAsset(Address owner, FungibleAssetValue value) => this;
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; var slotAddress = AvatarAddress.Derive( string.Format( CultureInfo.InvariantCulture, CombinationSlotState.DeriveFormat, SlotIndex ) ); if (ctx.Rehearsal) { return(states .SetState(AvatarAddress, MarkChanged) .SetState(slotAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, BlacksmithAddress)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); if (!states.TryGetAgentAvatarStates(ctx.Signer, AvatarAddress, out var agentState, out var avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); if (slotState is null) { throw new FailedLoadStateException($"{addressesHex}Aborted as the slot state is failed to load"); } if (!slotState.Validate(avatarState, ctx.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); } var recipeSheet = states.GetSheet <EquipmentItemRecipeSheet>(); var materialSheet = states.GetSheet <MaterialItemSheet>(); var materials = new Dictionary <Material, int>(); // Validate recipe. if (!recipeSheet.TryGetValue(RecipeId, out var recipe)) { throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemRecipeSheet), RecipeId); } if (!(SubRecipeId is null)) { if (!recipe.SubRecipeIds.Contains((int)SubRecipeId)) { throw new SheetRowColumnException( $"{addressesHex}Aborted as the sub recipe {SubRecipeId} was failed to load from the sheet." ); } } // Validate main recipe is unlocked. if (!avatarState.worldInformation.IsStageCleared(recipe.UnlockStage)) { avatarState.worldInformation.TryGetLastClearedStageId(out var current); throw new NotEnoughClearedStageLevelException(addressesHex, recipe.UnlockStage, current); } if (!materialSheet.TryGetValue(recipe.MaterialId, out var material)) { throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), recipe.MaterialId); } if (!avatarState.inventory.RemoveFungibleItem2(material.ItemId, recipe.MaterialCount)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({material} * {recipe.MaterialCount})" ); } var equipmentMaterial = ItemFactory.CreateMaterial(materialSheet, material.Id); materials[equipmentMaterial] = recipe.MaterialCount; BigInteger requiredGold = recipe.RequiredGold; var requiredActionPoint = recipe.RequiredActionPoint; var equipmentItemSheet = states.GetSheet <EquipmentItemSheet>(); // Validate equipment id. if (!equipmentItemSheet.TryGetValue(recipe.ResultEquipmentId, out var equipRow)) { throw new SheetRowNotFoundException(addressesHex, nameof(equipmentItemSheet), recipe.ResultEquipmentId); } var requiredBlockIndex = ctx.BlockIndex + recipe.RequiredBlockIndex; var equipment = (Equipment)ItemFactory.CreateItemUsable( equipRow, ctx.Random.GenerateRandomGuid(), requiredBlockIndex ); // Validate sub recipe. HashSet <int> optionIds = null; if (SubRecipeId.HasValue) { var subSheet = states.GetSheet <EquipmentItemSubRecipeSheet>(); var subId = (int)SubRecipeId; if (!subSheet.TryGetValue(subId, out var subRecipe)) { throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemSubRecipeSheet), subId); } requiredBlockIndex += subRecipe.RequiredBlockIndex; requiredGold += subRecipe.RequiredGold; requiredActionPoint += subRecipe.RequiredActionPoint; foreach (var materialInfo in subRecipe.Materials) { if (!materialSheet.TryGetValue(materialInfo.Id, out var subMaterialRow)) { throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), materialInfo.Id); } if (!avatarState.inventory.RemoveFungibleItem2(subMaterialRow.ItemId, materialInfo.Count)) { throw new NotEnoughMaterialException( $"{addressesHex}Aborted as the player has no enough material ({subMaterialRow} * {materialInfo.Count})" ); } var subMaterial = ItemFactory.CreateMaterial(materialSheet, materialInfo.Id); materials[subMaterial] = materialInfo.Count; } optionIds = SelectOption(states.GetSheet <EquipmentItemOptionSheet>(), states.GetSheet <SkillSheet>(), subRecipe, ctx.Random, equipment); equipment.Update(requiredBlockIndex); } // Validate NCG. FungibleAssetValue agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); if (agentBalance < states.GetGoldCurrency() * requiredGold) { throw new InsufficientBalanceException( ctx.Signer, agentBalance, $"{addressesHex}Aborted as the agent ({ctx.Signer}) has no sufficient gold: {agentBalance} < {requiredGold}" ); } if (avatarState.actionPoint < requiredActionPoint) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {requiredActionPoint}" ); } avatarState.actionPoint -= requiredActionPoint; if (!(optionIds is null)) { foreach (var id in optionIds.OrderBy(id => id)) { agentState.unlockedOptions.Add(id); } } // FIXME: BlacksmithAddress just accumulate NCG. we need plan how to circulate this. if (requiredGold > 0) { states = states.TransferAsset( ctx.Signer, BlacksmithAddress, states.GetGoldCurrency() * requiredGold ); } var result = new CombinationConsumable5.ResultModel { actionPoint = requiredActionPoint, gold = requiredGold, materials = materials, itemUsable = equipment, recipeId = RecipeId, subRecipeId = SubRecipeId, itemType = ItemType.Equipment, }; slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); var mail = new CombinationMail(result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), requiredBlockIndex); result.id = mail.id; avatarState.Update3(mail); avatarState.questList.UpdateCombinationEquipmentQuest(RecipeId); avatarState.UpdateFromCombination2(equipment); avatarState.UpdateQuestRewards2(materialSheet); return(states .SetState(AvatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()) .SetState(ctx.Signer, agentState.Serialize())); }
public FungibleAssetValue GetBalance(Address address, Currency currency) => _rawStates.TryGetValue(ToBalanceKey(address, currency), out IValue value) ? FungibleAssetValue.FromRawValue(currency, value is Bencodex.Types.Integer i ? i.Value : 0) : currency * 0;
public void Execute(params ShopItemData[] productDatas) { var buyerAvatarState = _initialState.GetAvatarState(_buyerAvatarAddress); var goldCurrency = _initialState.GetGoldCurrency(); var shopState = _initialState.GetShopState(); var buyCount = 0; var itemsToBuy = new List <BuyMultiple.PurchaseInfo>(); foreach (var product in productDatas) { var(sellerAvatarState, sellerAgentState) = CreateAvatarState(product.SellerAgentAddress, product.SellerAvatarAddress); INonFungibleItem nonFungibleItem; if (product.ItemType == ItemType.Equipment) { var itemUsable = ItemFactory.CreateItemUsable( _tableSheets.EquipmentItemSheet.First, product.ItemId, product.RequiredBlockIndex); nonFungibleItem = itemUsable; } else { var costume = ItemFactory.CreateCostume(_tableSheets.CostumeItemSheet.First, product.ItemId); costume.Update(product.RequiredBlockIndex); nonFungibleItem = costume; } // Case for backward compatibility of `Buy` if (product.ContainsInInventory) { sellerAvatarState.inventory.AddItem2((ItemBase)nonFungibleItem); } var shopItemId = Guid.NewGuid(); var shopItem = new ShopItem( sellerAgentState.address, sellerAvatarState.address, shopItemId, new FungibleAssetValue(_goldCurrencyState.Currency, product.Price, 0), product.RequiredBlockIndex, nonFungibleItem); shopState.Register(shopItem); if (product.Buy) { ++buyCount; var purchaseInfo = new BuyMultiple.PurchaseInfo( shopItem.ProductId, shopItem.SellerAgentAddress, shopItem.SellerAvatarAddress); itemsToBuy.Add(purchaseInfo); } } Assert.NotNull(shopState.Products); Assert.Equal(3, shopState.Products.Count); _initialState = _initialState .SetState(_buyerAvatarAddress, buyerAvatarState.Serialize()) .SetState(Addresses.Shop, shopState.Serialize()); var priceData = new PriceData(goldCurrency); foreach (var avatarState in _sellerAgentStateMap.Keys) { var agentState = _sellerAgentStateMap[avatarState]; priceData.TaxedPriceSum[agentState.address] = new FungibleAssetValue(goldCurrency, 0, 0); _initialState = _initialState .SetState(avatarState.address, avatarState.Serialize()); } IAccountStateDelta previousStates = _initialState; var buyerGold = previousStates.GetBalance(_buyerAgentAddress, goldCurrency); var priceSumData = productDatas .Where(i => i.Buy) .Aggregate(priceData, (priceSum, next) => { var price = new FungibleAssetValue(goldCurrency, next.Price, 0); var tax = price.DivRem(100, out _) * Buy.TaxRate; var taxedPrice = price - tax; priceData.TaxSum += tax; var prevSum = priceData.TaxedPriceSum[next.SellerAgentAddress]; priceData.TaxedPriceSum[next.SellerAgentAddress] = prevSum + taxedPrice; priceData.PriceSum += price; return(priceData); }); var buyMultipleAction = new BuyMultiple { buyerAvatarAddress = _buyerAvatarAddress, purchaseInfos = itemsToBuy, }; var nextState = buyMultipleAction.Execute(new ActionContext() { BlockIndex = 1, PreviousStates = previousStates, Random = new TestRandom(), Rehearsal = false, Signer = _buyerAgentAddress, }); var nextShopState = nextState.GetShopState(); Assert.Equal(productDatas.Length - buyCount, nextShopState.Products.Count); var nextBuyerAvatarState = nextState.GetAvatarState(_buyerAvatarAddress); foreach (var product in productDatas.Where(i => i.Buy)) { Assert.True( nextBuyerAvatarState.inventory.TryGetNonFungibleItem( product.ItemId, out INonFungibleItem outNonFungibleItem) ); Assert.Equal(product.RequiredBlockIndex, outNonFungibleItem.RequiredBlockIndex); } Assert.Equal(buyCount, nextBuyerAvatarState.mailBox.Count); goldCurrency = nextState.GetGoldCurrency(); var nextGoldCurrencyGold = nextState.GetBalance(Addresses.GoldCurrency, goldCurrency); Assert.Equal(priceSumData.TaxSum, nextGoldCurrencyGold); foreach (var product in productDatas) { var nextSellerGold = nextState.GetBalance(product.SellerAgentAddress, goldCurrency); Assert.Equal(priceSumData.TaxedPriceSum[product.SellerAgentAddress], nextSellerGold); } var nextBuyerGold = nextState.GetBalance(_buyerAgentAddress, goldCurrency); Assert.Equal(buyerGold - priceSumData.PriceSum, nextBuyerGold); previousStates = nextState; }
.ToImmutableDictionary( pair => pair.Item1, pair => FungibleAssetValue.FromRawValue(pair.Item1, (BigInteger)pair.Item2) ) );
public void Execute( ItemType itemType, string guid, int itemCount, int inventoryCount, int expectedCount, bool fromPreviousAction ) { var avatarState = _initialState.GetAvatarState(_avatarAddress); ITradableItem tradableItem; var itemId = new Guid(guid); var orderId = Guid.NewGuid(); var updateSellOrderId = Guid.NewGuid(); ItemSubType itemSubType; const long requiredBlockIndex = Order.ExpirationInterval; switch (itemType) { case ItemType.Equipment: { var itemUsable = ItemFactory.CreateItemUsable( _tableSheets.EquipmentItemSheet.First, itemId, requiredBlockIndex); tradableItem = itemUsable; itemSubType = itemUsable.ItemSubType; break; } case ItemType.Costume: { var costume = ItemFactory.CreateCostume(_tableSheets.CostumeItemSheet.First, itemId); costume.Update(requiredBlockIndex); tradableItem = costume; itemSubType = costume.ItemSubType; break; } default: { var material = ItemFactory.CreateTradableMaterial( _tableSheets.MaterialItemSheet.OrderedList.First(r => r.ItemSubType == ItemSubType.Hourglass)); itemSubType = material.ItemSubType; material.RequiredBlockIndex = requiredBlockIndex; tradableItem = material; break; } } var shardedShopAddress = ShardedShopStateV2.DeriveAddress(itemSubType, orderId); var shopState = new ShardedShopStateV2(shardedShopAddress); var order = OrderFactory.Create( _agentAddress, _avatarAddress, orderId, new FungibleAssetValue(_goldCurrencyState.Currency, 100, 0), tradableItem.TradableId, requiredBlockIndex, itemSubType, itemCount ); var orderDigestList = new OrderDigestListState(OrderDigestListState.DeriveAddress(_avatarAddress)); var prevState = _initialState; if (inventoryCount > 1) { for (int i = 0; i < inventoryCount; i++) { // Different RequiredBlockIndex for divide inventory slot. if (tradableItem is ITradableFungibleItem tradableFungibleItem) { var tradable = (TradableMaterial)tradableFungibleItem.Clone(); tradable.RequiredBlockIndex = tradableItem.RequiredBlockIndex - i; avatarState.inventory.AddItem(tradable, 2 - i); } } } else { avatarState.inventory.AddItem((ItemBase)tradableItem, itemCount); } var sellItem = order.Sell(avatarState); var orderDigest = order.Digest(avatarState, _tableSheets.CostumeStatSheet); shopState.Add(orderDigest, requiredBlockIndex); orderDigestList.Add(orderDigest); Assert.True(avatarState.inventory.TryGetLockedItem(new OrderLock(orderId), out _)); Assert.Equal(inventoryCount, avatarState.inventory.Items.Count); Assert.Equal(expectedCount, avatarState.inventory.Items.Sum(i => i.count)); Assert.Single(shopState.OrderDigestList); Assert.Single(orderDigestList.OrderDigestList); Assert.Equal(requiredBlockIndex * 2, sellItem.RequiredBlockIndex); if (fromPreviousAction) { prevState = prevState.SetState(_avatarAddress, avatarState.Serialize()); } else { prevState = prevState .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) .SetState(_avatarAddress, avatarState.SerializeV2()); } prevState = prevState .SetState(Addresses.GetItemAddress(itemId), sellItem.Serialize()) .SetState(Order.DeriveAddress(order.OrderId), order.Serialize()) .SetState(orderDigestList.Address, orderDigestList.Serialize()) .SetState(shardedShopAddress, shopState.Serialize()); var currencyState = prevState.GetGoldCurrency(); var price = new FungibleAssetValue(currencyState, ProductPrice, 0); var action = new UpdateSell { orderId = orderId, updateSellOrderId = updateSellOrderId, tradableId = itemId, sellerAvatarAddress = _avatarAddress, itemSubType = itemSubType, price = price, count = itemCount, }; var nextState = action.Execute(new ActionContext { BlockIndex = 101, PreviousStates = prevState, Random = new TestRandom(), Rehearsal = false, Signer = _agentAddress, }); var updateSellShopAddress = ShardedShopStateV2.DeriveAddress(itemSubType, updateSellOrderId); var nextShopState = new ShardedShopStateV2((Dictionary)nextState.GetState(updateSellShopAddress)); Assert.Equal(1, nextShopState.OrderDigestList.Count); Assert.NotEqual(orderId, nextShopState.OrderDigestList.First().OrderId); Assert.Equal(updateSellOrderId, nextShopState.OrderDigestList.First().OrderId); Assert.Equal(itemId, nextShopState.OrderDigestList.First().TradableId); Assert.Equal(requiredBlockIndex + 101, nextShopState.OrderDigestList.First().ExpiredBlockIndex); }
public async Task Transfer(string?memo, bool error) { NineChroniclesNodeService service = StandaloneContextFx.NineChroniclesNodeService !; Currency goldCurrency = new GoldCurrencyState( (Dictionary)BlockChain.GetState(GoldCurrencyState.Address) ).Currency; Address senderAddress = service.MinerPrivateKey !.ToAddress(); var store = service.Store; await BlockChain.MineBlock(service.MinerPrivateKey); await BlockChain.MineBlock(service.MinerPrivateKey); // 10 + 10 (mining rewards) Assert.Equal( 20 * goldCurrency, BlockChain.GetBalance(senderAddress, goldCurrency) ); var recipientKey = new PrivateKey(); Address recipient = recipientKey.ToAddress(); long txNonce = BlockChain.GetNextTxNonce(senderAddress); var args = $"recipient: \"{recipient}\", txNonce: {txNonce}, amount: \"17.5\""; if (!(memo is null)) { args += $"memo: \"{memo}\""; } var query = $"mutation {{ transfer({args}) }}"; ExecutionResult result = await ExecuteQueryAsync(query); if (error) { Assert.NotNull(result.Errors); } else { Assert.Null(result.Errors); var stagedTxIds = BlockChain.GetStagedTransactionIds().ToImmutableList(); Assert.Single(stagedTxIds); string transferTxIdString = stagedTxIds.Single().ToString(); TxId transferTxId = new TxId(ByteUtil.ParseHex(transferTxIdString)); Transaction <NCAction>?tx = BlockChain.StagePolicy.Get(BlockChain, transferTxId, false); Assert.NotNull(tx); Assert.IsType <TransferAsset>(tx !.Actions.Single().InnerAction); TransferAsset transferAsset = (TransferAsset)tx.Actions.Single().InnerAction; Assert.Equal(memo, transferAsset.Memo); var expectedResult = new Dictionary <string, object> { ["transfer"] = transferTxIdString, }; Assert.Equal(expectedResult, result.Data); await BlockChain.MineBlock(recipientKey); // 10 + 10 - 17.5(transfer) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "2.5"), BlockChain.GetBalance(senderAddress, goldCurrency) ); // 0 + 17.5(transfer) + 10(mining reward) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "27.5"), BlockChain.GetBalance(recipient, goldCurrency) ); } }
public SellerResult(Bencodex.Types.Dictionary serialized) : base(serialized) { shopItem = new ShopItem((Bencodex.Types.Dictionary)serialized["shopItem"]); id = serialized["id"].ToGuid(); gold = serialized["gold"].ToFungibleAssetValue(); }
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)); } 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 shopItem)) { 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(); // 돈은 있냐? FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); if (buyerBalance < shopItem.Price) { return(LogError( context, "Aborted as the buyer ({Buyer}) has no sufficient gold: {BuyerBalance} < {ItemPrice}", ctx.Signer, buyerBalance, shopItem.Price )); } var tax = shopItem.Price.DivRem(100, out _) * 8; var taxedPrice = shopItem.Price - tax; // 세금을 송금한다. states = states.TransferAsset( context.Signer, GoldCurrencyState.Address, tax); // 구매자의 돈을 판매자에게 송금한다. states = states.TransferAsset( context.Signer, sellerAgentAddress, taxedPrice ); // 상점에서 구매할 아이템을 제거한다. try { shopState.Unregister(sellerAgentAddress, shopItem); } catch (FailedToUnregisterInShopStateException) { return(LogError( context, "Aborted as the item ({@ProductId}) was failed to unregister from seller ({Seller})'s inventory.", shopItem.ProductId, sellerAgentAddress )); } // 구매자, 판매자에게 결과 메일 전송 buyerResult = new BuyerResult { shopItem = shopItem, itemUsable = shopItem.ItemUsable }; var buyerMail = new BuyerMail(buyerResult, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), ctx.BlockIndex); buyerResult.id = buyerMail.id; sellerResult = new SellerResult { shopItem = shopItem, itemUsable = shopItem.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, shopItem.Price); sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, shopItem.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())); }
protected override void LoadPlainValueInternal(IImmutableDictionary <string, IValue> plainValue) { sellerAvatarAddress = plainValue["sellerAvatarAddress"].ToAddress(); itemId = plainValue["itemId"].ToGuid(); price = plainValue["price"].ToFungibleAssetValue(); }
public override IAccountStateDelta Execute(IActionContext context) { IActionContext ctx = context; var states = ctx.PreviousStates; if (ctx.Rehearsal) { return(states.SetState(ctx.Signer, MarkChanged) .SetState(AvatarAddress, MarkChanged) .SetState(WeeklyArenaAddress, MarkChanged) .SetState(ctx.Signer, MarkChanged) .MarkBalanceChanged(GoldCurrencyMock, ctx.Signer, WeeklyArenaAddress)); } CheckObsolete(BlockChain.Policy.BlockPolicySource.V100080ObsoleteIndex, context); var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress, EnemyAddress); Log.Warning("ranking_battle is deprecated. Please use ranking_battle2"); if (AvatarAddress.Equals(EnemyAddress)) { throw new InvalidAddressException($"{addressesHex}Aborted as the signer tried to battle for themselves."); } if (!states.TryGetAgentAvatarStates( ctx.Signer, AvatarAddress, out var agentState, out var avatarState)) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } var costumes = new HashSet <int>(costumeIds); avatarState.ValidateEquipments(equipmentIds, context.BlockIndex); avatarState.ValidateConsumable(consumableIds, context.BlockIndex); avatarState.ValidateCostume(costumes); if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex(out var world) || world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) { throw new NotEnoughClearedStageLevelException( addressesHex, GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, world.StageClearedId); } avatarState.EquipCostumes(costumes); avatarState.EquipEquipments(equipmentIds); var enemyAvatarState = states.GetAvatarState(EnemyAddress); if (enemyAvatarState is null) { throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the opponent ({EnemyAddress}) was failed to load."); } var weeklyArenaState = states.GetWeeklyArenaState(WeeklyArenaAddress); if (weeklyArenaState.Ended) { throw new WeeklyArenaStateAlreadyEndedException( addressesHex + WeeklyArenaStateAlreadyEndedException.BaseMessage); } if (!weeklyArenaState.ContainsKey(AvatarAddress)) { throw new WeeklyArenaStateNotContainsAvatarAddressException(addressesHex, AvatarAddress); } var arenaInfo = weeklyArenaState[AvatarAddress]; if (arenaInfo.DailyChallengeCount <= 0) { throw new NotEnoughWeeklyArenaChallengeCountException( addressesHex + NotEnoughWeeklyArenaChallengeCountException.BaseMessage); } if (!arenaInfo.Active) { FungibleAssetValue agentBalance = default; try { agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); } catch (InvalidOperationException) { throw new NotEnoughFungibleAssetValueException(addressesHex, EntranceFee, agentBalance); } if (agentBalance >= new FungibleAssetValue(agentBalance.Currency, EntranceFee, 0)) { states = states.TransferAsset( ctx.Signer, WeeklyArenaAddress, new FungibleAssetValue( states.GetGoldCurrency(), EntranceFee, 0 ) ); arenaInfo.Activate(); } else { throw new NotEnoughFungibleAssetValueException(addressesHex, EntranceFee, agentBalance); } } if (!weeklyArenaState.ContainsKey(EnemyAddress)) { throw new WeeklyArenaStateNotContainsAvatarAddressException(addressesHex, EnemyAddress); } Log.Verbose("{WeeklyArenaStateAddress}", weeklyArenaState.address.ToHex()); var simulator = new RankingSimulator( ctx.Random, avatarState, enemyAvatarState, consumableIds, states.GetRankingSimulatorSheets(), StageId, arenaInfo, weeklyArenaState[EnemyAddress]); simulator.SimulateV1(); Result = simulator.Log; foreach (var itemBase in simulator.Reward.OrderBy(i => i.Id)) { avatarState.inventory.AddItem2(itemBase); } return(states .SetState(ctx.Signer, agentState.Serialize()) .SetState(WeeklyArenaAddress, weeklyArenaState.Serialize()) .SetState(AvatarAddress, avatarState.Serialize())); }