From c3aab26877e16d2f3ec3e8eff12b4b41edb8bebd Mon Sep 17 00:00:00 2001 From: Atralupus Date: Sun, 17 Mar 2024 04:24:49 +0900 Subject: [PATCH 1/5] Add list item in trade store action test --- .../Extensions/BencodexExtensions.cs | 21 +++ .../Action/ListItemInTradeStoreActionTests.cs | 134 ++++++++++++++++++ .../Action/ListItemInTradeStoreAction.cs | 89 ++++++++++++ backend/app/Savor22b/Constants/Addresses.cs | 8 ++ backend/app/Savor22b/States/TradeItem.cs | 67 +++++++++ .../app/Savor22b/States/TradeStoreState.cs | 38 +++++ 6 files changed, 357 insertions(+) create mode 100644 backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs create mode 100644 backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs create mode 100644 backend/app/Savor22b/States/TradeItem.cs create mode 100644 backend/app/Savor22b/States/TradeStoreState.cs diff --git a/backend/app/Libplanet.Headless/Extensions/BencodexExtensions.cs b/backend/app/Libplanet.Headless/Extensions/BencodexExtensions.cs index 17377326..8ad0b7a4 100644 --- a/backend/app/Libplanet.Headless/Extensions/BencodexExtensions.cs +++ b/backend/app/Libplanet.Headless/Extensions/BencodexExtensions.cs @@ -19,6 +19,27 @@ public static IValue Serialize(Func serializer, T? value) return serialized is Null ? (T?)null : deserializer(serialized); } + public static IValue Serialize(this IEnumerable values) + where T : IValue + { + return new List(values.Cast()); + } + + public static IEnumerable ToEnumerable(this IValue serialized, Func deserializer) + { + return ((List)serialized).Select(deserializer); + } + + public static T[] ToArray(this IValue serialized, Func deserializer) + { + return serialized.ToEnumerable(deserializer).ToArray(); + } + + public static List ToList(this IValue serialized, Func deserializer) + { + return serialized.ToEnumerable(deserializer).ToList(); + } + public static IValue ToBencodex(this FungibleAssetValue fav) => new List(fav.Currency.Serialize(), (Integer)fav.RawValue); diff --git a/backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs b/backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs new file mode 100644 index 00000000..b042a026 --- /dev/null +++ b/backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs @@ -0,0 +1,134 @@ +namespace Savor22b.Tests.Action; + +using System; +using System.Collections.Immutable; +using Libplanet; +using Libplanet.Assets; +using Libplanet.State; +using Savor22b.Action; +using Savor22b.Action.Exceptions; +using Savor22b.States; +using Xunit; + +public class ListItemInTradeStoreActionTests : ActionTests +{ + private static readonly int LifeStoneItemId = 3; + + public ListItemInTradeStoreActionTests() { } + + [Fact] + public void ListItemInTradeStoreActionExecute_Success_Food() + { + + var stateDelta = CreatePresetStateDelta(); + var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta); + + var beforeFood = beforeState.InventoryState.RefrigeratorStateList[0]; + + var action = new ListItemInTradeStoreAction( + Guid.NewGuid(), + beforeFood, + FungibleAssetValue.Parse( + Currencies.KeyCurrency, + "10" + )); + + stateDelta = action.Execute( + new DummyActionContext + { + PreviousStates = stateDelta, + Signer = SignerAddress(), + Random = random, + Rehearsal = false, + BlockIndex = 1, + } + ); + + var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); + + var afterTradeStoreStateEncoded = stateDelta.GetState(SignerAddress()); + TradeStoreState afterTradeStoreState = afterTradeStoreStateEncoded is Bencodex.Types.Dictionary bdict + ? new TradeStoreState(bdict) + : throw new Exception(); + + Assert.Empty(afterState.InventoryState.RefrigeratorStateList); + Assert.Equal(afterTradeStoreState.TradItems.First(item => item.Value.SellerAddress == SignerAddress()).Value.Price, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); + Assert.Contains(afterTradeStoreState.TradItems, item => item.Value.Food?.StateID == beforeFood.StateID); + } + + // [Fact] + // public void ListItemInTradeStoreActionExecute_Success_Items() + // { + + // var stateDelta = CreatePresetStateDelta(); + // var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta); + + // var beforeItems = beforeState.InventoryState.ItemStateList; + + // var action = new ListItemInTradeStoreAction(beforeItems.Select(i => i.StateID).ToList(), 10); + + // stateDelta = action.Execute( + // new DummyActionContext + // { + // PreviousStates = stateDelta, + // Signer = SignerAddress(), + // Random = random, + // Rehearsal = false, + // BlockIndex = 1, + // } + // ); + + // var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); + // var afterTradeStoreStateEncoded = stateDelta.GetState(SignerAddress()); + // TradeStoreState afterTradeStoreState = afterTradeStoreStateEncoded is Bencodex.Types.Dictionary bdict + // ? new TradeStoreState(bdict) + // : throw new Exception(); + + // Assert.Empty(afterState.InventoryState.ItemStateList); + // Assert.Contains(afterTradeStoreState.ItemList, item => item.Item1 == beforeItems.Select(i => i.StateID).ToList()); + // Assert.Equal(afterTradeStoreState.ItemList.First(item => item.Item1 == beforeItems.Select(i => i.StateID).ToList()).Item2, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); + // } + + private IAccountStateDelta CreatePresetStateDelta() + { + IAccountStateDelta state = new DummyState(); + Address signerAddress = SignerAddress(); + + var rootStateEncoded = state.GetState(signerAddress); + RootState rootState = rootStateEncoded is Bencodex.Types.Dictionary bdict + ? new RootState(bdict) + : new RootState(); + + InventoryState inventoryState = rootState.InventoryState; + + inventoryState = inventoryState.AddItem(new ItemState(Guid.NewGuid(), LifeStoneItemId)); + inventoryState = inventoryState.AddItem(new ItemState(Guid.NewGuid(), LifeStoneItemId)); + + var food = RefrigeratorState.CreateFood( + Guid.NewGuid(), + 1, + "D", + 1, + 1, + 1, + 1, + 1, + ImmutableList.Empty + ); + var ingredient = RefrigeratorState.CreateIngredient( + Guid.NewGuid(), + 1, + "D", + 1, + 1, + 1, + 1 + ); + inventoryState = inventoryState.AddRefrigeratorItem(food); + inventoryState = inventoryState.AddRefrigeratorItem(ingredient); + + rootState.SetInventoryState(inventoryState); + + return state.SetState(signerAddress, rootState.Serialize()); + } +} diff --git a/backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs b/backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs new file mode 100644 index 00000000..bc133f79 --- /dev/null +++ b/backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs @@ -0,0 +1,89 @@ +namespace Savor22b.Action; + +using System; +using System.Collections.Immutable; +using Bencodex.Types; +using Libplanet; +using Libplanet.Action; +using Libplanet.Assets; +using Libplanet.Headless.Extensions; +using Libplanet.State; +using Savor22b.Action.Exceptions; +using Savor22b.Constants; +using Savor22b.Helpers; +using Savor22b.Model; +using Savor22b.States; + +[ActionType(nameof(ListItemInTradeStoreAction))] +public class ListItemInTradeStoreAction : SVRAction +{ + public ListItemInTradeStoreAction() { } + + public ListItemInTradeStoreAction(Guid tradeItemStateId, RefrigeratorState food, FungibleAssetValue price) + { + TradeItemStateId = tradeItemStateId; + Price = price; + Food = food; + Items = null; + } + + public ListItemInTradeStoreAction(Guid tradeItemStateId, List items, FungibleAssetValue price) + { + TradeItemStateId = tradeItemStateId; + Price = price; + Food = null; + Items = items.ToImmutableList(); + } + + public Guid TradeItemStateId; + + public FungibleAssetValue Price; + + public RefrigeratorState? Food; + + public ImmutableList? Items; + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary() + { + [nameof(TradeItemStateId)] = TradeItemStateId.Serialize(), + [nameof(Price)] = Price.ToBencodex(), + [nameof(Food)] = Food is null ? Null.Value : Food.Serialize(), + [nameof(Items)] = Items is null ? Null.Value : (Bencodex.Types.List)Items.Select(i => i.Serialize()), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + TradeItemStateId = plainValue[nameof(TradeItemStateId)].ToGuid(); + Price = plainValue[nameof(Price)].ToFungibleAssetValue(); + } + + public override IAccountStateDelta Execute(IActionContext ctx) + { + if (ctx.Rehearsal) + { + return ctx.PreviousStates; + } + + IAccountStateDelta states = ctx.PreviousStates; + + RootState rootState = states.GetState(ctx.Signer) is Dictionary rootStateEncoded + ? new RootState(rootStateEncoded) + : new RootState(); + // var inventoryState = rootState.InventoryState; + + // var desiredEquipment = FindRandomSeedItem(); + // var itemState = new ItemState(ItemStateID, desiredEquipment.ID); + + // states = states.TransferAsset( + // ctx.Signer, + // Recipient, + // desiredEquipment.PriceToFungibleAssetValue(), + // allowNegativeBalance: false + // ); + // inventoryState = inventoryState.AddItem(itemState); + // rootState.SetInventoryState(inventoryState); + + return states.SetState(ctx.Signer, rootState.Serialize()); + } +} diff --git a/backend/app/Savor22b/Constants/Addresses.cs b/backend/app/Savor22b/Constants/Addresses.cs index 63415344..945c1202 100644 --- a/backend/app/Savor22b/Constants/Addresses.cs +++ b/backend/app/Savor22b/Constants/Addresses.cs @@ -7,10 +7,18 @@ public static class Addresses public static readonly Address ShopVaultAddress = new Address( "0000000000000000000000000000000000000000" ); + public static readonly Address UserHouseDataAddress = new Address( "0000000000000000000000000000000000000001" ); + public static readonly Address DungeonDataAddress = new Address( "0000000000000000000000000000000000000002" ); + + public static readonly Address TradeStoreAddress = new Address( + "0000000000000000000000000000000000000002"); + + public static readonly Address TradeStoreVaultAddress = new Address( + "0000000000000000000000000000000000000003"); } diff --git a/backend/app/Savor22b/States/TradeItem.cs b/backend/app/Savor22b/States/TradeItem.cs new file mode 100644 index 00000000..967b97df --- /dev/null +++ b/backend/app/Savor22b/States/TradeItem.cs @@ -0,0 +1,67 @@ +namespace Savor22b.States; + +using System; +using Bencodex.Types; +using Libplanet.Assets; +using Libplanet.Headless.Extensions; +using Libplanet; + +[Serializable] +public class TradeItem +{ + public readonly Address SellerAddress; + + public readonly Guid ProductId; + + public readonly FungibleAssetValue Price; + + public readonly RefrigeratorState? Food; + + public readonly List? Items; + + public TradeItem( + Address sellerAddress, + Guid productId, + FungibleAssetValue price, + RefrigeratorState food) + { + SellerAddress = sellerAddress; + ProductId = productId; + Price = price; + Food = food; + Items = null; + } + + public TradeItem( + Address sellerAddress, + Guid productId, + FungibleAssetValue price, + List items) + { + SellerAddress = sellerAddress; + ProductId = productId; + Price = price; + Food = null; + Items = items; + } + + public TradeItem(Dictionary serialized) + { + SellerAddress = serialized[nameof(SellerAddress)].ToAddress(); + ProductId = serialized[nameof(ProductId)].ToGuid(); + Price = serialized[nameof(Price)].ToFungibleAssetValue(); + } + + public IValue Serialize() + { + var pairs = new[] + { + new KeyValuePair((Text)nameof(SellerAddress), SellerAddress.ToBencodex()), + new KeyValuePair((Text)nameof(ProductId), ProductId.Serialize()), + new KeyValuePair((Text)nameof(Price), Price.ToBencodex()), + new KeyValuePair((Text)nameof(Food), Food is null ? Null.Value : Food.Serialize()), + new KeyValuePair((Text)nameof(Items), Items is null ? Null.Value : (Bencodex.Types.List)Items.Select(i => i.Serialize())), + }; + return new Dictionary(pairs); + } +} diff --git a/backend/app/Savor22b/States/TradeStoreState.cs b/backend/app/Savor22b/States/TradeStoreState.cs new file mode 100644 index 00000000..865259d8 --- /dev/null +++ b/backend/app/Savor22b/States/TradeStoreState.cs @@ -0,0 +1,38 @@ +namespace Savor22b.States; + +using System; +using Bencodex.Types; +using Libplanet.Headless.Extensions; +using Libplanet; +using Savor22b.Constants; + +public class TradeStoreState : State +{ + public static readonly Address StateAddress = Addresses.TradeStoreAddress; + + public TradeStoreState(Dictionary encoded) + { + TradItems = ((List)encoded[nameof(TradItems)]).Select(item => new TradeItem((Dictionary)item)).ToDictionary(i => i.ProductId, i => i); + } + + public Dictionary TradItems { get; private set; } + + public IValue Serialize() + { + var tradItemsPairs = TradItems.Select(item => + new KeyValuePair( + (Text)item.Key.Serialize(), + item.Value.Serialize() + ) + ); + + var tradItemsDict = new Dictionary(tradItemsPairs); + + var statePairs = new[] + { + new KeyValuePair((Text)nameof(TradItems), tradItemsDict), + }; + + return new Dictionary(statePairs); + } +} From 2efeaa60588398fc3b2a69d98a9c4654a3ecfbbd Mon Sep 17 00:00:00 2001 From: Atralupus Date: Mon, 8 Apr 2024 02:44:03 +0900 Subject: [PATCH 2/5] Renaming and Restructuring --- ...sts.cs => RegisterTradeGoodActionTests.cs} | 43 +++++++----- ...reAction.cs => RegisterTradeGoodAction.cs} | 42 +++++------- .../Savor22b/States/Trade/FoodGoodState.cs | 35 ++++++++++ .../Savor22b/States/Trade/ItemGoodState.cs | 37 ++++++++++ .../app/Savor22b/States/Trade/TradeGood.cs | 50 ++++++++++++++ .../Savor22b/States/Trade/TradeGoodFactory.cs | 48 +++++++++++++ .../States/Trade/TradeInventoryState.cs | 33 +++++++++ backend/app/Savor22b/States/TradeItem.cs | 67 ------------------- .../app/Savor22b/States/TradeStoreState.cs | 38 ----------- 9 files changed, 245 insertions(+), 148 deletions(-) rename backend/app/Savor22b.Tests/Action/{ListItemInTradeStoreActionTests.cs => RegisterTradeGoodActionTests.cs} (74%) rename backend/app/Savor22b/Action/{ListItemInTradeStoreAction.cs => RegisterTradeGoodAction.cs} (59%) create mode 100644 backend/app/Savor22b/States/Trade/FoodGoodState.cs create mode 100644 backend/app/Savor22b/States/Trade/ItemGoodState.cs create mode 100644 backend/app/Savor22b/States/Trade/TradeGood.cs create mode 100644 backend/app/Savor22b/States/Trade/TradeGoodFactory.cs create mode 100644 backend/app/Savor22b/States/Trade/TradeInventoryState.cs delete mode 100644 backend/app/Savor22b/States/TradeItem.cs delete mode 100644 backend/app/Savor22b/States/TradeStoreState.cs diff --git a/backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs similarity index 74% rename from backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs rename to backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs index b042a026..78920de7 100644 --- a/backend/app/Savor22b.Tests/Action/ListItemInTradeStoreActionTests.cs +++ b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs @@ -6,18 +6,18 @@ namespace Savor22b.Tests.Action; using Libplanet.Assets; using Libplanet.State; using Savor22b.Action; -using Savor22b.Action.Exceptions; using Savor22b.States; +using Savor22b.States.Trade; using Xunit; -public class ListItemInTradeStoreActionTests : ActionTests +public class RegisterTradeGoodActionTests : ActionTests { - private static readonly int LifeStoneItemId = 3; + private const int LifeStoneItemId = 3; - public ListItemInTradeStoreActionTests() { } + public RegisterTradeGoodActionTests() { } [Fact] - public void ListItemInTradeStoreActionExecute_Success_Food() + public void RegisterTradeGoodActionExecute_Success_Food() { var stateDelta = CreatePresetStateDelta(); @@ -25,13 +25,14 @@ public void ListItemInTradeStoreActionExecute_Success_Food() var beforeFood = beforeState.InventoryState.RefrigeratorStateList[0]; - var action = new ListItemInTradeStoreAction( - Guid.NewGuid(), - beforeFood, + var action = new RegisterTradeGoodAction( + nameof(FoodGoodState), FungibleAssetValue.Parse( Currencies.KeyCurrency, "10" - )); + ), + beforeFood + ); stateDelta = action.Execute( new DummyActionContext @@ -46,18 +47,28 @@ public void ListItemInTradeStoreActionExecute_Success_Food() var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); - var afterTradeStoreStateEncoded = stateDelta.GetState(SignerAddress()); - TradeStoreState afterTradeStoreState = afterTradeStoreStateEncoded is Bencodex.Types.Dictionary bdict - ? new TradeStoreState(bdict) + var afterTradeInventoryStateEncoded = stateDelta.GetState(SignerAddress()); + TradeInventoryState afterTradeInventoryState = afterTradeInventoryStateEncoded is Bencodex.Types.Dictionary bdict + ? new TradeInventoryState(bdict) : throw new Exception(); Assert.Empty(afterState.InventoryState.RefrigeratorStateList); - Assert.Equal(afterTradeStoreState.TradItems.First(item => item.Value.SellerAddress == SignerAddress()).Value.Price, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); - Assert.Contains(afterTradeStoreState.TradItems, item => item.Value.Food?.StateID == beforeFood.StateID); + + var tradeGood = afterTradeInventoryState.TradeGoods.First(item => item.Value.SellerAddress == SignerAddress()).Value; + + if (tradeGood is FoodGoodState foodGoodState) + { + Assert.Equal(foodGoodState.Price, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); + Assert.Equal(foodGoodState.Food?.StateID, beforeFood.StateID); + } + else + { + throw new Exception(); + } } // [Fact] - // public void ListItemInTradeStoreActionExecute_Success_Items() + // public void RegisterTradeGoodActionExecute_Success_Items() // { // var stateDelta = CreatePresetStateDelta(); @@ -65,7 +76,7 @@ public void ListItemInTradeStoreActionExecute_Success_Food() // var beforeItems = beforeState.InventoryState.ItemStateList; - // var action = new ListItemInTradeStoreAction(beforeItems.Select(i => i.StateID).ToList(), 10); + // var action = new RegisterTradeGoodAction(beforeItems.Select(i => i.StateID).ToList(), 10); // stateDelta = action.Execute( // new DummyActionContext diff --git a/backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs similarity index 59% rename from backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs rename to backend/app/Savor22b/Action/RegisterTradeGoodAction.cs index bc133f79..9ae4e149 100644 --- a/backend/app/Savor22b/Action/ListItemInTradeStoreAction.cs +++ b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs @@ -3,58 +3,46 @@ namespace Savor22b.Action; using System; using System.Collections.Immutable; using Bencodex.Types; -using Libplanet; using Libplanet.Action; using Libplanet.Assets; using Libplanet.Headless.Extensions; using Libplanet.State; -using Savor22b.Action.Exceptions; -using Savor22b.Constants; -using Savor22b.Helpers; -using Savor22b.Model; using Savor22b.States; +using Savor22b.States.Trade; -[ActionType(nameof(ListItemInTradeStoreAction))] -public class ListItemInTradeStoreAction : SVRAction +[ActionType(nameof(RegisterTradeGoodAction))] +public class RegisterTradeGoodAction : SVRAction { - public ListItemInTradeStoreAction() { } + public RegisterTradeGoodAction() { } - public ListItemInTradeStoreAction(Guid tradeItemStateId, RefrigeratorState food, FungibleAssetValue price) + public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, RefrigeratorState foodState) { - TradeItemStateId = tradeItemStateId; + GoodType = goodType; Price = price; - Food = food; - Items = null; + FoodState = foodState; + ItemStates = null; } - public ListItemInTradeStoreAction(Guid tradeItemStateId, List items, FungibleAssetValue price) - { - TradeItemStateId = tradeItemStateId; - Price = price; - Food = null; - Items = items.ToImmutableList(); - } - - public Guid TradeItemStateId; + public string GoodType; public FungibleAssetValue Price; - public RefrigeratorState? Food; + public RefrigeratorState? FoodState; - public ImmutableList? Items; + public ImmutableList? ItemStates; protected override IImmutableDictionary PlainValueInternal => new Dictionary() { - [nameof(TradeItemStateId)] = TradeItemStateId.Serialize(), + [nameof(GoodType)] = GoodType.Serialize(), [nameof(Price)] = Price.ToBencodex(), - [nameof(Food)] = Food is null ? Null.Value : Food.Serialize(), - [nameof(Items)] = Items is null ? Null.Value : (Bencodex.Types.List)Items.Select(i => i.Serialize()), + [nameof(FoodState)] = FoodState is null ? Null.Value : FoodState.Serialize(), + [nameof(ItemStates)] = ItemStates is null ? Null.Value : (Bencodex.Types.List)ItemStates.Select(i => i.Serialize()), }.ToImmutableDictionary(); protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) { - TradeItemStateId = plainValue[nameof(TradeItemStateId)].ToGuid(); + GoodType = plainValue[nameof(GoodType)].ToString(); Price = plainValue[nameof(Price)].ToFungibleAssetValue(); } diff --git a/backend/app/Savor22b/States/Trade/FoodGoodState.cs b/backend/app/Savor22b/States/Trade/FoodGoodState.cs new file mode 100644 index 00000000..3b439bce --- /dev/null +++ b/backend/app/Savor22b/States/Trade/FoodGoodState.cs @@ -0,0 +1,35 @@ +namespace Savor22b.States.Trade; + +using System; +using Bencodex.Types; +using Libplanet.Assets; +using Libplanet; + +public class FoodGoodState : TradeGood +{ + public RefrigeratorState Food { get; private set; } + + public FoodGoodState( + Address sellerAddress, + Guid productId, + FungibleAssetValue price, + RefrigeratorState food) + : base(sellerAddress, productId, price, nameof(FoodGoodState)) + { + Food = food; + } + + public FoodGoodState(Dictionary serialized) + : base(serialized) + { + Food = new RefrigeratorState((Dictionary)serialized[nameof(Food)]); + } + + public IValue Serialize() + { + var baseSerialized = base.Serialize() as Dictionary; + baseSerialized = baseSerialized.Add((Text)nameof(Food), Food.Serialize()); + + return baseSerialized; + } +} diff --git a/backend/app/Savor22b/States/Trade/ItemGoodState.cs b/backend/app/Savor22b/States/Trade/ItemGoodState.cs new file mode 100644 index 00000000..5ad7b346 --- /dev/null +++ b/backend/app/Savor22b/States/Trade/ItemGoodState.cs @@ -0,0 +1,37 @@ +namespace Savor22b.States.Trade; + +using System; +using System.Collections.Immutable; +using Bencodex.Types; +using Libplanet.Assets; +using Libplanet; + +public class ItemGood : TradeGood +{ + public ImmutableList Items { get; private set; } + + public ItemGood( + Address sellerAddress, + Guid productId, + FungibleAssetValue price, + ImmutableList items) + : base(sellerAddress, productId, price, nameof(FoodGoodState)) + { + Items = items; + } + + public ItemGood(Dictionary serialized) + : base(serialized) + { + Items = ((List)serialized["Items"]).Select(dict => new ItemState((Dictionary)dict)).ToImmutableList(); + } + + public IValue Serialize() + { + var baseSerialized = base.Serialize() as Dictionary; + var itemsSerialized = Items.Select(item => item.Serialize()).ToList(); + + baseSerialized = baseSerialized.Add((Text)nameof(Items), (List)Items.Select(i => i.Serialize())); + return baseSerialized; + } +} diff --git a/backend/app/Savor22b/States/Trade/TradeGood.cs b/backend/app/Savor22b/States/Trade/TradeGood.cs new file mode 100644 index 00000000..d21b98a2 --- /dev/null +++ b/backend/app/Savor22b/States/Trade/TradeGood.cs @@ -0,0 +1,50 @@ +namespace Savor22b.States.Trade; + +using System; +using Bencodex.Types; +using Libplanet.Assets; +using Libplanet.Headless.Extensions; +using Libplanet; + +public abstract class TradeGood +{ + public Address SellerAddress { get; private set; } + + public Guid ProductStateId { get; private set; } + + public FungibleAssetValue Price { get; private set; } + + public string Type { get; private set; } + + public TradeGood( + Address sellerAddress, + Guid productStateId, + FungibleAssetValue price, + string type) + { + SellerAddress = sellerAddress; + ProductStateId = productStateId; + Price = price; + Type = type; + } + + public TradeGood(Dictionary serialized) + { + SellerAddress = serialized[nameof(SellerAddress)].ToAddress(); + ProductStateId = serialized[nameof(ProductStateId)].ToGuid(); + Price = serialized[nameof(Price)].ToFungibleAssetValue(); + Type = serialized[nameof(Type)].ToString(); + } + + public IValue Serialize() + { + var pairs = new[] + { + new KeyValuePair((Text)nameof(SellerAddress), SellerAddress.ToBencodex()), + new KeyValuePair((Text)nameof(ProductStateId), ProductStateId.Serialize()), + new KeyValuePair((Text)nameof(Price), Price.ToBencodex()), + new KeyValuePair((Text)nameof(Type), Type.Serialize()), + }; + return new Dictionary(pairs); + } +} diff --git a/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs b/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs new file mode 100644 index 00000000..58139ee9 --- /dev/null +++ b/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs @@ -0,0 +1,48 @@ +namespace Savor22b.States.Trade; + +using System; +using Bencodex.Types; +using System.Collections.Immutable; +using Libplanet.Assets; +using Libplanet; +using Libplanet.Headless.Extensions; + +public static class TradeGoodFactory +{ + public static TradeGood CreateInstance(Dictionary serialized) + { + string type = serialized["Type"].ToString(); + + switch (type) + { + case nameof(FoodGoodState): + return CreateFoodGood(serialized); + case nameof(ItemGood): + return CreateItemGood(serialized); + default: + throw new ArgumentException($"Unsupported TradeGood type: {type}"); + } + } + + private static TradeGood CreateFoodGood(Dictionary serialized) + { + Address sellerAddress = serialized[nameof(TradeGood.SellerAddress)].ToAddress(); + Guid productId = serialized[nameof(TradeGood.ProductStateId)].ToGuid(); + FungibleAssetValue price = serialized[nameof(TradeGood.Price)].ToFungibleAssetValue(); + + var food = new RefrigeratorState((Dictionary)serialized[nameof(FoodGoodState)]); + + return new FoodGoodState(sellerAddress, productId, price, food); + } + + private static TradeGood CreateItemGood(Dictionary serialized) + { + Address sellerAddress = serialized[nameof(TradeGood.SellerAddress)].ToAddress(); + Guid productId = serialized[nameof(TradeGood.ProductStateId)].ToGuid(); + FungibleAssetValue price = serialized[nameof(TradeGood.Price)].ToFungibleAssetValue(); + + var items = ((List)serialized["Items"]).Select(dict => new ItemState((Dictionary)dict)).ToImmutableList(); + + return new ItemGood(sellerAddress, productId, price, items); + } +} diff --git a/backend/app/Savor22b/States/Trade/TradeInventoryState.cs b/backend/app/Savor22b/States/Trade/TradeInventoryState.cs new file mode 100644 index 00000000..a01face7 --- /dev/null +++ b/backend/app/Savor22b/States/Trade/TradeInventoryState.cs @@ -0,0 +1,33 @@ +namespace Savor22b.States.Trade; + +using System; +using Bencodex.Types; +using Libplanet; +using Savor22b.Constants; + +public class TradeInventoryState : State +{ + public static readonly Address StateAddress = Addresses.TradeStoreAddress; + + public TradeInventoryState(Dictionary encoded) + { + TradeGoods = ((List)encoded[nameof(TradeGoods)]) + .Select(item => TradeGoodFactory.CreateInstance((Dictionary)item)) + .ToDictionary(good => good.ProductStateId, good => good); + } + + public Dictionary TradeGoods { get; private set; } + + public IValue Serialize() + { + var tradeGoodsPairs = TradeGoods.Values + .Select(good => new KeyValuePair( + (Text)good.ProductStateId.ToString(), good.Serialize())); + + var tradeGoodsDict = new Dictionary(tradeGoodsPairs); + + return new Dictionary(new[] { + new KeyValuePair((Text)nameof(TradeGoods), tradeGoodsDict) + }); + } +} diff --git a/backend/app/Savor22b/States/TradeItem.cs b/backend/app/Savor22b/States/TradeItem.cs deleted file mode 100644 index 967b97df..00000000 --- a/backend/app/Savor22b/States/TradeItem.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace Savor22b.States; - -using System; -using Bencodex.Types; -using Libplanet.Assets; -using Libplanet.Headless.Extensions; -using Libplanet; - -[Serializable] -public class TradeItem -{ - public readonly Address SellerAddress; - - public readonly Guid ProductId; - - public readonly FungibleAssetValue Price; - - public readonly RefrigeratorState? Food; - - public readonly List? Items; - - public TradeItem( - Address sellerAddress, - Guid productId, - FungibleAssetValue price, - RefrigeratorState food) - { - SellerAddress = sellerAddress; - ProductId = productId; - Price = price; - Food = food; - Items = null; - } - - public TradeItem( - Address sellerAddress, - Guid productId, - FungibleAssetValue price, - List items) - { - SellerAddress = sellerAddress; - ProductId = productId; - Price = price; - Food = null; - Items = items; - } - - public TradeItem(Dictionary serialized) - { - SellerAddress = serialized[nameof(SellerAddress)].ToAddress(); - ProductId = serialized[nameof(ProductId)].ToGuid(); - Price = serialized[nameof(Price)].ToFungibleAssetValue(); - } - - public IValue Serialize() - { - var pairs = new[] - { - new KeyValuePair((Text)nameof(SellerAddress), SellerAddress.ToBencodex()), - new KeyValuePair((Text)nameof(ProductId), ProductId.Serialize()), - new KeyValuePair((Text)nameof(Price), Price.ToBencodex()), - new KeyValuePair((Text)nameof(Food), Food is null ? Null.Value : Food.Serialize()), - new KeyValuePair((Text)nameof(Items), Items is null ? Null.Value : (Bencodex.Types.List)Items.Select(i => i.Serialize())), - }; - return new Dictionary(pairs); - } -} diff --git a/backend/app/Savor22b/States/TradeStoreState.cs b/backend/app/Savor22b/States/TradeStoreState.cs deleted file mode 100644 index 865259d8..00000000 --- a/backend/app/Savor22b/States/TradeStoreState.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Savor22b.States; - -using System; -using Bencodex.Types; -using Libplanet.Headless.Extensions; -using Libplanet; -using Savor22b.Constants; - -public class TradeStoreState : State -{ - public static readonly Address StateAddress = Addresses.TradeStoreAddress; - - public TradeStoreState(Dictionary encoded) - { - TradItems = ((List)encoded[nameof(TradItems)]).Select(item => new TradeItem((Dictionary)item)).ToDictionary(i => i.ProductId, i => i); - } - - public Dictionary TradItems { get; private set; } - - public IValue Serialize() - { - var tradItemsPairs = TradItems.Select(item => - new KeyValuePair( - (Text)item.Key.Serialize(), - item.Value.Serialize() - ) - ); - - var tradItemsDict = new Dictionary(tradItemsPairs); - - var statePairs = new[] - { - new KeyValuePair((Text)nameof(TradItems), tradItemsDict), - }; - - return new Dictionary(statePairs); - } -} From 866f806c8c80416468acb7686faa3a0280811ca0 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Fri, 12 Apr 2024 11:02:50 +0900 Subject: [PATCH 3/5] Implement register trade good action --- .../Action/RegisterTradeGoodActionTests.cs | 78 +++++++++++-------- .../Exceptions/InvalidValueException.cs | 11 +++ .../Action/RegisterTradeGoodAction.cs | 57 +++++++++++--- .../Savor22b/States/Trade/FoodGoodState.cs | 2 +- .../{ItemGoodState.cs => ItemsGoodState.cs} | 15 ++-- .../app/Savor22b/States/Trade/TradeGood.cs | 2 +- .../Savor22b/States/Trade/TradeGoodFactory.cs | 8 +- .../States/Trade/TradeInventoryState.cs | 21 ++++- 8 files changed, 134 insertions(+), 60 deletions(-) create mode 100644 backend/app/Savor22b/Action/Exceptions/InvalidValueException.cs rename backend/app/Savor22b/States/Trade/{ItemGoodState.cs => ItemsGoodState.cs} (57%) diff --git a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs index 78920de7..5884622c 100644 --- a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs +++ b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs @@ -47,19 +47,19 @@ public void RegisterTradeGoodActionExecute_Success_Food() var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); - var afterTradeInventoryStateEncoded = stateDelta.GetState(SignerAddress()); + var afterTradeInventoryStateEncoded = stateDelta.GetState(TradeInventoryState.StateAddress); TradeInventoryState afterTradeInventoryState = afterTradeInventoryStateEncoded is Bencodex.Types.Dictionary bdict ? new TradeInventoryState(bdict) : throw new Exception(); - Assert.Empty(afterState.InventoryState.RefrigeratorStateList); + Assert.True(afterState.InventoryState.RefrigeratorStateList.Count == 1); - var tradeGood = afterTradeInventoryState.TradeGoods.First(item => item.Value.SellerAddress == SignerAddress()).Value; + var tradeGood = afterTradeInventoryState.TradeGoods.First(g => g.Value.SellerAddress == SignerAddress()).Value; if (tradeGood is FoodGoodState foodGoodState) { Assert.Equal(foodGoodState.Price, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); - Assert.Equal(foodGoodState.Food?.StateID, beforeFood.StateID); + Assert.Equal(foodGoodState.Food.StateID, beforeFood.StateID); } else { @@ -67,38 +67,55 @@ public void RegisterTradeGoodActionExecute_Success_Food() } } - // [Fact] - // public void RegisterTradeGoodActionExecute_Success_Items() - // { + [Fact] + public void RegisterTradeGoodActionExecute_Success_Items() + { + var stateDelta = CreatePresetStateDelta(); + var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta); + + var beforeItems = beforeState.InventoryState.ItemStateList; - // var stateDelta = CreatePresetStateDelta(); - // var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta); + var action = new RegisterTradeGoodAction( + nameof(ItemsGoodState), + FungibleAssetValue.Parse( + Currencies.KeyCurrency, + "10" + ), + beforeItems + ); + + stateDelta = action.Execute( + new DummyActionContext + { + PreviousStates = stateDelta, + Signer = SignerAddress(), + Random = random, + Rehearsal = false, + BlockIndex = 1, + } + ); - // var beforeItems = beforeState.InventoryState.ItemStateList; + var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); - // var action = new RegisterTradeGoodAction(beforeItems.Select(i => i.StateID).ToList(), 10); + var afterTradeInventoryStateEncoded = stateDelta.GetState(TradeInventoryState.StateAddress); + TradeInventoryState afterTradeInventoryState = afterTradeInventoryStateEncoded is Bencodex.Types.Dictionary bdict + ? new TradeInventoryState(bdict) + : throw new Exception(); - // stateDelta = action.Execute( - // new DummyActionContext - // { - // PreviousStates = stateDelta, - // Signer = SignerAddress(), - // Random = random, - // Rehearsal = false, - // BlockIndex = 1, - // } - // ); + Assert.Empty(afterState.InventoryState.ItemStateList); - // var afterState = DeriveRootStateFromAccountStateDelta(stateDelta); - // var afterTradeStoreStateEncoded = stateDelta.GetState(SignerAddress()); - // TradeStoreState afterTradeStoreState = afterTradeStoreStateEncoded is Bencodex.Types.Dictionary bdict - // ? new TradeStoreState(bdict) - // : throw new Exception(); + var tradeGood = afterTradeInventoryState.TradeGoods.First(g => g.Value.SellerAddress == SignerAddress()).Value; - // Assert.Empty(afterState.InventoryState.ItemStateList); - // Assert.Contains(afterTradeStoreState.ItemList, item => item.Item1 == beforeItems.Select(i => i.StateID).ToList()); - // Assert.Equal(afterTradeStoreState.ItemList.First(item => item.Item1 == beforeItems.Select(i => i.StateID).ToList()).Item2, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); - // } + if (tradeGood is ItemsGoodState itemsGoodState) + { + Assert.Equal(itemsGoodState.Price, FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")); + Assert.Equal(itemsGoodState.Items[0].StateID, beforeItems[0].StateID); + } + else + { + throw new Exception(); + } + } private IAccountStateDelta CreatePresetStateDelta() { @@ -112,7 +129,6 @@ private IAccountStateDelta CreatePresetStateDelta() InventoryState inventoryState = rootState.InventoryState; - inventoryState = inventoryState.AddItem(new ItemState(Guid.NewGuid(), LifeStoneItemId)); inventoryState = inventoryState.AddItem(new ItemState(Guid.NewGuid(), LifeStoneItemId)); var food = RefrigeratorState.CreateFood( diff --git a/backend/app/Savor22b/Action/Exceptions/InvalidValueException.cs b/backend/app/Savor22b/Action/Exceptions/InvalidValueException.cs new file mode 100644 index 00000000..ad942ed7 --- /dev/null +++ b/backend/app/Savor22b/Action/Exceptions/InvalidValueException.cs @@ -0,0 +1,11 @@ +namespace Savor22b.Action.Exceptions; + + +[Serializable] +public class InvalidValueException : ActionException +{ + public InvalidValueException(string message, int? errorCode = null) + : base(message, "InvalidValueException", errorCode) + { + } +} diff --git a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs index 9ae4e149..897c5148 100644 --- a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs +++ b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs @@ -9,6 +9,7 @@ namespace Savor22b.Action; using Libplanet.State; using Savor22b.States; using Savor22b.States.Trade; +using Savor22b.Action.Exceptions; [ActionType(nameof(RegisterTradeGoodAction))] public class RegisterTradeGoodAction : SVRAction @@ -23,6 +24,15 @@ public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, Refrig ItemStates = null; } + public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, ImmutableList itemStates) + { + GoodType = goodType; + Price = price; + FoodState = null; + ItemStates = itemStates; + } + + public string GoodType; public FungibleAssetValue Price; @@ -44,6 +54,8 @@ protected override void LoadPlainValueInternal(IImmutableDictionary new ItemState((Dictionary)e)).ToImmutableList(); } public override IAccountStateDelta Execute(IActionContext ctx) @@ -58,20 +70,43 @@ public override IAccountStateDelta Execute(IActionContext ctx) RootState rootState = states.GetState(ctx.Signer) is Dictionary rootStateEncoded ? new RootState(rootStateEncoded) : new RootState(); - // var inventoryState = rootState.InventoryState; + TradeInventoryState tradeInventoryState = states.GetState(TradeInventoryState.StateAddress) is Dictionary tradeInventoryStateEncoded + ? new TradeInventoryState(tradeInventoryStateEncoded) + : new TradeInventoryState(); + + var inventoryState = rootState.InventoryState; - // var desiredEquipment = FindRandomSeedItem(); - // var itemState = new ItemState(ItemStateID, desiredEquipment.ID); + switch (GoodType) + { + case nameof(FoodGoodState): + if (FoodState is null) + { + throw new InvalidValueException($"FoodState required"); + } - // states = states.TransferAsset( - // ctx.Signer, - // Recipient, - // desiredEquipment.PriceToFungibleAssetValue(), - // allowNegativeBalance: false - // ); - // inventoryState = inventoryState.AddItem(itemState); - // rootState.SetInventoryState(inventoryState); + inventoryState = inventoryState.RemoveRefrigeratorItem(FoodState.StateID); + var foodGoodState = new FoodGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, FoodState); + tradeInventoryState = tradeInventoryState.RegisterGood(foodGoodState); + break; + case nameof(ItemsGoodState): + if (ItemStates is null) + { + throw new InvalidValueException($"ItemStates required"); + } + + foreach (var item in ItemStates) + { + inventoryState = inventoryState.RemoveItem(item.StateID); + } + var itemsGoodState = new ItemsGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, ItemStates); + tradeInventoryState = tradeInventoryState.RegisterGood(itemsGoodState); + break; + default: + throw new ArgumentException($"Unsupported TradeGood type: {GoodType}"); + } + rootState.SetInventoryState(inventoryState); + states = states.SetState(TradeInventoryState.StateAddress, tradeInventoryState.Serialize()); return states.SetState(ctx.Signer, rootState.Serialize()); } } diff --git a/backend/app/Savor22b/States/Trade/FoodGoodState.cs b/backend/app/Savor22b/States/Trade/FoodGoodState.cs index 3b439bce..1ca8484e 100644 --- a/backend/app/Savor22b/States/Trade/FoodGoodState.cs +++ b/backend/app/Savor22b/States/Trade/FoodGoodState.cs @@ -25,7 +25,7 @@ public FoodGoodState(Dictionary serialized) Food = new RefrigeratorState((Dictionary)serialized[nameof(Food)]); } - public IValue Serialize() + public override IValue Serialize() { var baseSerialized = base.Serialize() as Dictionary; baseSerialized = baseSerialized.Add((Text)nameof(Food), Food.Serialize()); diff --git a/backend/app/Savor22b/States/Trade/ItemGoodState.cs b/backend/app/Savor22b/States/Trade/ItemsGoodState.cs similarity index 57% rename from backend/app/Savor22b/States/Trade/ItemGoodState.cs rename to backend/app/Savor22b/States/Trade/ItemsGoodState.cs index 5ad7b346..429b31e7 100644 --- a/backend/app/Savor22b/States/Trade/ItemGoodState.cs +++ b/backend/app/Savor22b/States/Trade/ItemsGoodState.cs @@ -6,32 +6,31 @@ namespace Savor22b.States.Trade; using Libplanet.Assets; using Libplanet; -public class ItemGood : TradeGood +public class ItemsGoodState : TradeGood { public ImmutableList Items { get; private set; } - public ItemGood( + public ItemsGoodState( Address sellerAddress, Guid productId, FungibleAssetValue price, ImmutableList items) - : base(sellerAddress, productId, price, nameof(FoodGoodState)) + : base(sellerAddress, productId, price, nameof(ItemsGoodState)) { Items = items; } - public ItemGood(Dictionary serialized) + public ItemsGoodState(Dictionary serialized) : base(serialized) { - Items = ((List)serialized["Items"]).Select(dict => new ItemState((Dictionary)dict)).ToImmutableList(); + Items = ((List)serialized[nameof(Items)]).Select(dict => new ItemState((Dictionary)dict)).ToImmutableList(); } - public IValue Serialize() + public override IValue Serialize() { var baseSerialized = base.Serialize() as Dictionary; - var itemsSerialized = Items.Select(item => item.Serialize()).ToList(); - baseSerialized = baseSerialized.Add((Text)nameof(Items), (List)Items.Select(i => i.Serialize())); + baseSerialized = baseSerialized.Add((Text)nameof(Items), new List(Items.Select(item => item.Serialize()))); return baseSerialized; } } diff --git a/backend/app/Savor22b/States/Trade/TradeGood.cs b/backend/app/Savor22b/States/Trade/TradeGood.cs index d21b98a2..3ffb07cc 100644 --- a/backend/app/Savor22b/States/Trade/TradeGood.cs +++ b/backend/app/Savor22b/States/Trade/TradeGood.cs @@ -36,7 +36,7 @@ public TradeGood(Dictionary serialized) Type = serialized[nameof(Type)].ToString(); } - public IValue Serialize() + public virtual IValue Serialize() { var pairs = new[] { diff --git a/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs b/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs index 58139ee9..6686b53b 100644 --- a/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs +++ b/backend/app/Savor22b/States/Trade/TradeGoodFactory.cs @@ -11,13 +11,13 @@ public static class TradeGoodFactory { public static TradeGood CreateInstance(Dictionary serialized) { - string type = serialized["Type"].ToString(); + string type = serialized["Type"].ToDotnetString(); switch (type) { case nameof(FoodGoodState): return CreateFoodGood(serialized); - case nameof(ItemGood): + case nameof(ItemsGoodState): return CreateItemGood(serialized); default: throw new ArgumentException($"Unsupported TradeGood type: {type}"); @@ -30,7 +30,7 @@ private static TradeGood CreateFoodGood(Dictionary serialized) Guid productId = serialized[nameof(TradeGood.ProductStateId)].ToGuid(); FungibleAssetValue price = serialized[nameof(TradeGood.Price)].ToFungibleAssetValue(); - var food = new RefrigeratorState((Dictionary)serialized[nameof(FoodGoodState)]); + var food = new RefrigeratorState((Dictionary)serialized["Food"]); return new FoodGoodState(sellerAddress, productId, price, food); } @@ -43,6 +43,6 @@ private static TradeGood CreateItemGood(Dictionary serialized) var items = ((List)serialized["Items"]).Select(dict => new ItemState((Dictionary)dict)).ToImmutableList(); - return new ItemGood(sellerAddress, productId, price, items); + return new ItemsGoodState(sellerAddress, productId, price, items); } } diff --git a/backend/app/Savor22b/States/Trade/TradeInventoryState.cs b/backend/app/Savor22b/States/Trade/TradeInventoryState.cs index a01face7..02ee1c44 100644 --- a/backend/app/Savor22b/States/Trade/TradeInventoryState.cs +++ b/backend/app/Savor22b/States/Trade/TradeInventoryState.cs @@ -3,17 +3,24 @@ namespace Savor22b.States.Trade; using System; using Bencodex.Types; using Libplanet; +using Libplanet.Headless.Extensions; using Savor22b.Constants; public class TradeInventoryState : State { public static readonly Address StateAddress = Addresses.TradeStoreAddress; + public TradeInventoryState() + { + TradeGoods = new Dictionary(); + } + public TradeInventoryState(Dictionary encoded) { - TradeGoods = ((List)encoded[nameof(TradeGoods)]) - .Select(item => TradeGoodFactory.CreateInstance((Dictionary)item)) - .ToDictionary(good => good.ProductStateId, good => good); + TradeGoods = ((Dictionary)encoded[nameof(TradeGoods)]).ToDictionary( + pair => pair.Key.ToGuid(), + pair => TradeGoodFactory.CreateInstance((Dictionary)pair.Value) + ); } public Dictionary TradeGoods { get; private set; } @@ -22,7 +29,7 @@ public IValue Serialize() { var tradeGoodsPairs = TradeGoods.Values .Select(good => new KeyValuePair( - (Text)good.ProductStateId.ToString(), good.Serialize())); + (Binary)good.ProductStateId.Serialize(), good.Serialize())); var tradeGoodsDict = new Dictionary(tradeGoodsPairs); @@ -30,4 +37,10 @@ public IValue Serialize() new KeyValuePair((Text)nameof(TradeGoods), tradeGoodsDict) }); } + + public TradeInventoryState RegisterGood(TradeGood good) + { + TradeGoods.Add(good.ProductStateId, good); + return new TradeInventoryState((Dictionary)Serialize()); + } } From fd5692fe08c1413a5e95cb73994694642e0f94ae Mon Sep 17 00:00:00 2001 From: Atralupus Date: Sat, 13 Apr 2024 00:41:39 +0900 Subject: [PATCH 4/5] Change action input to guid --- .../Action/RegisterTradeGoodActionTests.cs | 4 +- .../Action/RegisterTradeGoodAction.cs | 47 +++++++++++-------- backend/app/Savor22b/States/InventoryState.cs | 5 ++ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs index 5884622c..ef8a2c0a 100644 --- a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs +++ b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs @@ -31,7 +31,7 @@ public void RegisterTradeGoodActionExecute_Success_Food() Currencies.KeyCurrency, "10" ), - beforeFood + beforeFood.StateID ); stateDelta = action.Execute( @@ -81,7 +81,7 @@ public void RegisterTradeGoodActionExecute_Success_Items() Currencies.KeyCurrency, "10" ), - beforeItems + beforeItems.Select(i => i.StateID).ToImmutableList() ); stateDelta = action.Execute( diff --git a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs index 897c5148..41688c85 100644 --- a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs +++ b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs @@ -16,46 +16,45 @@ public class RegisterTradeGoodAction : SVRAction { public RegisterTradeGoodAction() { } - public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, RefrigeratorState foodState) + public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, Guid foodStateId) { GoodType = goodType; Price = price; - FoodState = foodState; - ItemStates = null; + FoodStateId = foodStateId; + ItemStateIds = null; } - public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, ImmutableList itemStates) + public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, ImmutableList itemStateIds) { GoodType = goodType; Price = price; - FoodState = null; - ItemStates = itemStates; + FoodStateId = null; + ItemStateIds = itemStateIds; } - public string GoodType; public FungibleAssetValue Price; - public RefrigeratorState? FoodState; + public Guid? FoodStateId; - public ImmutableList? ItemStates; + public ImmutableList? ItemStateIds; protected override IImmutableDictionary PlainValueInternal => new Dictionary() { [nameof(GoodType)] = GoodType.Serialize(), [nameof(Price)] = Price.ToBencodex(), - [nameof(FoodState)] = FoodState is null ? Null.Value : FoodState.Serialize(), - [nameof(ItemStates)] = ItemStates is null ? Null.Value : (Bencodex.Types.List)ItemStates.Select(i => i.Serialize()), + [nameof(FoodStateId)] = FoodStateId.Serialize(), + [nameof(ItemStateIds)] = ItemStateIds is null ? Null.Value : (List)ItemStateIds.Select(i => i.Serialize()), }.ToImmutableDictionary(); protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) { GoodType = plainValue[nameof(GoodType)].ToString(); Price = plainValue[nameof(Price)].ToFungibleAssetValue(); - FoodState = plainValue[nameof(FoodState)] is Null ? null : new RefrigeratorState((Dictionary)plainValue[nameof(FoodState)]); - ItemStates = plainValue[nameof(ItemStates)] is Null ? null : ((List)plainValue[nameof(ItemStates)]).Select(e => new ItemState((Dictionary)e)).ToImmutableList(); + FoodStateId = plainValue[nameof(FoodStateId)].ToGuid(); + ItemStateIds = plainValue[nameof(ItemStateIds)] is Null ? null : ((List)plainValue[nameof(ItemStateIds)]).Select(e => e.ToGuid()).ToImmutableList(); } public override IAccountStateDelta Execute(IActionContext ctx) @@ -79,26 +78,34 @@ public override IAccountStateDelta Execute(IActionContext ctx) switch (GoodType) { case nameof(FoodGoodState): - if (FoodState is null) + if (FoodStateId is null) { throw new InvalidValueException($"FoodState required"); } - inventoryState = inventoryState.RemoveRefrigeratorItem(FoodState.StateID); - var foodGoodState = new FoodGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, FoodState); + var foodState = inventoryState.GetRefrigeratorItem(FoodStateId.Value); + var foodGoodState = new FoodGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, foodState); + inventoryState = inventoryState.RemoveRefrigeratorItem(FoodStateId.Value); tradeInventoryState = tradeInventoryState.RegisterGood(foodGoodState); break; case nameof(ItemsGoodState): - if (ItemStates is null) + if (ItemStateIds is null) { throw new InvalidValueException($"ItemStates required"); } - foreach (var item in ItemStates) + var itemStates = new List(); + foreach (var itemStateId in ItemStateIds) { - inventoryState = inventoryState.RemoveItem(item.StateID); + itemStates.Add(inventoryState.GetItem(itemStateId)); + inventoryState = inventoryState.RemoveItem(itemStateId); } - var itemsGoodState = new ItemsGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, ItemStates); + var itemsGoodState = new ItemsGoodState( + ctx.Signer, + ctx.Random.GenerateRandomGuid(), + Price, + itemStates.ToImmutableList()); + tradeInventoryState = tradeInventoryState.RegisterGood(itemsGoodState); break; default: diff --git a/backend/app/Savor22b/States/InventoryState.cs b/backend/app/Savor22b/States/InventoryState.cs index 3bb778dd..eb45a9f4 100644 --- a/backend/app/Savor22b/States/InventoryState.cs +++ b/backend/app/Savor22b/States/InventoryState.cs @@ -178,6 +178,11 @@ public InventoryState RemoveKitchenEquipmentItem(Guid stateID) return new InventoryState(SeedStateList, RefrigeratorStateList, stateList, ItemStateList); } + public ItemState? GetItem(Guid stateID) + { + return ItemStateList.Find(i => i.StateID == stateID); + } + public InventoryState AddItem(ItemState item) { var itemStateList = ItemStateList.Add(item); From 9ecaff94afac18db46dc3d5afee5067693bcfae6 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Sat, 13 Apr 2024 01:23:43 +0900 Subject: [PATCH 5/5] Add RegisterTradeGoodAction to action query --- .../Action/RegisterTradeGoodActionTests.cs | 2 - .../Action/RegisterTradeGoodAction.cs | 70 ++++++++----------- .../app/Savor22b/GraphTypes/Query/Query.cs | 64 +++++++++++++++++ 3 files changed, 92 insertions(+), 44 deletions(-) diff --git a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs index ef8a2c0a..825ceadc 100644 --- a/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs +++ b/backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs @@ -26,7 +26,6 @@ public void RegisterTradeGoodActionExecute_Success_Food() var beforeFood = beforeState.InventoryState.RefrigeratorStateList[0]; var action = new RegisterTradeGoodAction( - nameof(FoodGoodState), FungibleAssetValue.Parse( Currencies.KeyCurrency, "10" @@ -76,7 +75,6 @@ public void RegisterTradeGoodActionExecute_Success_Items() var beforeItems = beforeState.InventoryState.ItemStateList; var action = new RegisterTradeGoodAction( - nameof(ItemsGoodState), FungibleAssetValue.Parse( Currencies.KeyCurrency, "10" diff --git a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs index 41688c85..884c33e8 100644 --- a/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs +++ b/backend/app/Savor22b/Action/RegisterTradeGoodAction.cs @@ -16,24 +16,20 @@ public class RegisterTradeGoodAction : SVRAction { public RegisterTradeGoodAction() { } - public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, Guid foodStateId) + public RegisterTradeGoodAction(FungibleAssetValue price, Guid foodStateId) { - GoodType = goodType; Price = price; FoodStateId = foodStateId; ItemStateIds = null; } - public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, ImmutableList itemStateIds) + public RegisterTradeGoodAction(FungibleAssetValue price, ImmutableList itemStateIds) { - GoodType = goodType; Price = price; FoodStateId = null; ItemStateIds = itemStateIds; } - public string GoodType; - public FungibleAssetValue Price; public Guid? FoodStateId; @@ -43,7 +39,6 @@ public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, Immuta protected override IImmutableDictionary PlainValueInternal => new Dictionary() { - [nameof(GoodType)] = GoodType.Serialize(), [nameof(Price)] = Price.ToBencodex(), [nameof(FoodStateId)] = FoodStateId.Serialize(), [nameof(ItemStateIds)] = ItemStateIds is null ? Null.Value : (List)ItemStateIds.Select(i => i.Serialize()), @@ -51,7 +46,6 @@ public RegisterTradeGoodAction(string goodType, FungibleAssetValue price, Immuta protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) { - GoodType = plainValue[nameof(GoodType)].ToString(); Price = plainValue[nameof(Price)].ToFungibleAssetValue(); FoodStateId = plainValue[nameof(FoodStateId)].ToGuid(); ItemStateIds = plainValue[nameof(ItemStateIds)] is Null ? null : ((List)plainValue[nameof(ItemStateIds)]).Select(e => e.ToGuid()).ToImmutableList(); @@ -75,41 +69,33 @@ public override IAccountStateDelta Execute(IActionContext ctx) var inventoryState = rootState.InventoryState; - switch (GoodType) + if (FoodStateId is not null) + { + var foodState = inventoryState.GetRefrigeratorItem(FoodStateId.Value); + var foodGoodState = new FoodGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, foodState); + inventoryState = inventoryState.RemoveRefrigeratorItem(FoodStateId.Value); + tradeInventoryState = tradeInventoryState.RegisterGood(foodGoodState); + } + else if (ItemStateIds is not null) + { + var itemStates = new List(); + foreach (var itemStateId in ItemStateIds) + { + itemStates.Add(inventoryState.GetItem(itemStateId)); + inventoryState = inventoryState.RemoveItem(itemStateId); + } + + var itemsGoodState = new ItemsGoodState( + ctx.Signer, + ctx.Random.GenerateRandomGuid(), + Price, + itemStates.ToImmutableList()); + + tradeInventoryState = tradeInventoryState.RegisterGood(itemsGoodState); + } + else { - case nameof(FoodGoodState): - if (FoodStateId is null) - { - throw new InvalidValueException($"FoodState required"); - } - - var foodState = inventoryState.GetRefrigeratorItem(FoodStateId.Value); - var foodGoodState = new FoodGoodState(ctx.Signer, ctx.Random.GenerateRandomGuid(), Price, foodState); - inventoryState = inventoryState.RemoveRefrigeratorItem(FoodStateId.Value); - tradeInventoryState = tradeInventoryState.RegisterGood(foodGoodState); - break; - case nameof(ItemsGoodState): - if (ItemStateIds is null) - { - throw new InvalidValueException($"ItemStates required"); - } - - var itemStates = new List(); - foreach (var itemStateId in ItemStateIds) - { - itemStates.Add(inventoryState.GetItem(itemStateId)); - inventoryState = inventoryState.RemoveItem(itemStateId); - } - var itemsGoodState = new ItemsGoodState( - ctx.Signer, - ctx.Random.GenerateRandomGuid(), - Price, - itemStates.ToImmutableList()); - - tradeInventoryState = tradeInventoryState.RegisterGood(itemsGoodState); - break; - default: - throw new ArgumentException($"Unsupported TradeGood type: {GoodType}"); + throw new InvalidValueException($"ItemStateIds or FoodStateId required"); } rootState.SetInventoryState(inventoryState); diff --git a/backend/app/Savor22b/GraphTypes/Query/Query.cs b/backend/app/Savor22b/GraphTypes/Query/Query.cs index 16136e63..b06ad91a 100644 --- a/backend/app/Savor22b/GraphTypes/Query/Query.cs +++ b/backend/app/Savor22b/GraphTypes/Query/Query.cs @@ -1,5 +1,6 @@ namespace Savor22b.GraphTypes.Query; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using GraphQL; using GraphQL.Types; @@ -528,6 +529,69 @@ swarm is null } ); + Field>( + "createAction_RegisterTradeGoodAction", + description: "Register Trade Good to Trade Store", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "publicKey", + Description = "The base64-encoded public key for Transaction.", + }, + new QueryArgument> + { + Name = "price", + Description = "Price for good", + }, + new QueryArgument + { + Name = "foodStateId", + Description = "Food state Id (Guid)", + }, + new QueryArgument> + { + Name = "itemStateIds", + Description = "Item state Ids (Guid)", + } + ), + resolve: context => + { + var publicKey = new PublicKey( + ByteUtil.ParseHex(context.GetArgument("publicKey")) + ); + var foodStateId = context.GetArgument("foodStateId"); + var itemStateIds = context.GetArgument?>("itemStateIds"); + var price = FungibleAssetValue.Parse(Currencies.KeyCurrency, context.GetArgument("price").ToString()); + + if (foodStateId is not null) + { + return new GetUnsignedTransactionHex( + new RegisterTradeGoodAction( + price, + foodStateId.Value), + publicKey, + _blockChain, + _swarm + ).UnsignedTransactionHex; + } + else if (itemStateIds is not null) + { + return new GetUnsignedTransactionHex( + new RegisterTradeGoodAction( + price, + itemStateIds.ToImmutableList()), + publicKey, + _blockChain, + _swarm + ).UnsignedTransactionHex; + } + else + { + throw new ArgumentException("foodStateId or itemStateIds required"); + } + } + ); + AddField(new DungeonExplorationQuery(blockChain, swarm)); AddField(new CalculateRelocationCostQuery()); AddField(new DungeonReturnRewardQuery());