Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SVR-302] 무역상점 아이템 등록 기능 #156

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/app/Libplanet.Headless/Extensions/BencodexExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ public static IValue Serialize<T>(Func<T, IValue> serializer, T? value)
return serialized is Null ? (T?)null : deserializer(serialized);
}

public static IValue Serialize<T>(this IEnumerable<T> values)
where T : IValue
{
return new List(values.Cast<IValue>());
}

public static IEnumerable<T> ToEnumerable<T>(this IValue serialized, Func<IValue, T> deserializer)
{
return ((List)serialized).Select(deserializer);
}

public static T[] ToArray<T>(this IValue serialized, Func<IValue, T> deserializer)
{
return serialized.ToEnumerable(deserializer).ToArray();
}

public static List<T> ToList<T>(this IValue serialized, Func<IValue, T> deserializer)
{
return serialized.ToEnumerable(deserializer).ToList();
}

public static IValue ToBencodex(this FungibleAssetValue fav) =>
new List(fav.Currency.Serialize(), (Integer)fav.RawValue);

Expand Down
159 changes: 159 additions & 0 deletions backend/app/Savor22b.Tests/Action/RegisterTradeGoodActionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
namespace Savor22b.Tests.Action;

using System;
using System.Collections.Immutable;
using Libplanet;
using Libplanet.Assets;
using Libplanet.State;
using Savor22b.Action;
using Savor22b.States;
using Savor22b.States.Trade;
using Xunit;

public class RegisterTradeGoodActionTests : ActionTests
{
private const int LifeStoneItemId = 3;

public RegisterTradeGoodActionTests() { }

[Fact]
public void RegisterTradeGoodActionExecute_Success_Food()
Atralupus marked this conversation as resolved.
Show resolved Hide resolved
{

var stateDelta = CreatePresetStateDelta();
var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta);

var beforeFood = beforeState.InventoryState.RefrigeratorStateList[0];

var action = new RegisterTradeGoodAction(
FungibleAssetValue.Parse(
Currencies.KeyCurrency,
"10"
),
beforeFood.StateID
);

stateDelta = action.Execute(
new DummyActionContext
{
PreviousStates = stateDelta,
Signer = SignerAddress(),
Random = random,
Rehearsal = false,
BlockIndex = 1,
}
);

var afterState = DeriveRootStateFromAccountStateDelta(stateDelta);

var afterTradeInventoryStateEncoded = stateDelta.GetState(TradeInventoryState.StateAddress);
TradeInventoryState afterTradeInventoryState = afterTradeInventoryStateEncoded is Bencodex.Types.Dictionary bdict
? new TradeInventoryState(bdict)
: throw new Exception();

Assert.True(afterState.InventoryState.RefrigeratorStateList.Count == 1);

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);
}
else
{
throw new Exception();
}
}

[Fact]
public void RegisterTradeGoodActionExecute_Success_Items()
{
var stateDelta = CreatePresetStateDelta();
var beforeState = DeriveRootStateFromAccountStateDelta(stateDelta);

var beforeItems = beforeState.InventoryState.ItemStateList;

var action = new RegisterTradeGoodAction(
FungibleAssetValue.Parse(
Currencies.KeyCurrency,
"10"
),
beforeItems.Select(i => i.StateID).ToImmutableList()
);

stateDelta = action.Execute(
new DummyActionContext
{
PreviousStates = stateDelta,
Signer = SignerAddress(),
Random = random,
Rehearsal = false,
BlockIndex = 1,
}
);

var afterState = DeriveRootStateFromAccountStateDelta(stateDelta);

var afterTradeInventoryStateEncoded = stateDelta.GetState(TradeInventoryState.StateAddress);
TradeInventoryState afterTradeInventoryState = afterTradeInventoryStateEncoded is Bencodex.Types.Dictionary bdict
? new TradeInventoryState(bdict)
: throw new Exception();

Assert.Empty(afterState.InventoryState.ItemStateList);

var tradeGood = afterTradeInventoryState.TradeGoods.First(g => g.Value.SellerAddress == SignerAddress()).Value;

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()
{
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));

var food = RefrigeratorState.CreateFood(
Guid.NewGuid(),
1,
"D",
1,
1,
1,
1,
1,
ImmutableList<Guid>.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());
}
}
11 changes: 11 additions & 0 deletions backend/app/Savor22b/Action/Exceptions/InvalidValueException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Savor22b.Action.Exceptions;


[Serializable]
public class InvalidValueException : ActionException
{
public InvalidValueException(string message, int? errorCode = null)
: base(message, "InvalidValueException", errorCode)
{
}
}
105 changes: 105 additions & 0 deletions backend/app/Savor22b/Action/RegisterTradeGoodAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
namespace Savor22b.Action;

using System;
using System.Collections.Immutable;
using Bencodex.Types;
using Libplanet.Action;
using Libplanet.Assets;
using Libplanet.Headless.Extensions;
using Libplanet.State;
using Savor22b.States;
using Savor22b.States.Trade;
using Savor22b.Action.Exceptions;

[ActionType(nameof(RegisterTradeGoodAction))]
public class RegisterTradeGoodAction : SVRAction
{
public RegisterTradeGoodAction() { }

public RegisterTradeGoodAction(FungibleAssetValue price, Guid foodStateId)
{
Price = price;
FoodStateId = foodStateId;
ItemStateIds = null;
}

public RegisterTradeGoodAction(FungibleAssetValue price, ImmutableList<Guid> itemStateIds)
{
Price = price;
FoodStateId = null;
ItemStateIds = itemStateIds;
}

public FungibleAssetValue Price;

public Guid? FoodStateId;

public ImmutableList<Guid>? ItemStateIds;

protected override IImmutableDictionary<string, IValue> PlainValueInternal =>
new Dictionary<string, IValue>()
{
[nameof(Price)] = Price.ToBencodex(),
[nameof(FoodStateId)] = FoodStateId.Serialize(),
[nameof(ItemStateIds)] = ItemStateIds is null ? Null.Value : (List)ItemStateIds.Select(i => i.Serialize()),
}.ToImmutableDictionary();

protected override void LoadPlainValueInternal(IImmutableDictionary<string, IValue> plainValue)
{
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();
}

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();
TradeInventoryState tradeInventoryState = states.GetState(TradeInventoryState.StateAddress) is Dictionary tradeInventoryStateEncoded
? new TradeInventoryState(tradeInventoryStateEncoded)
: new TradeInventoryState();

var inventoryState = rootState.InventoryState;

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<ItemState>();
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
{
throw new InvalidValueException($"ItemStateIds or FoodStateId required");
}

rootState.SetInventoryState(inventoryState);
states = states.SetState(TradeInventoryState.StateAddress, tradeInventoryState.Serialize());
return states.SetState(ctx.Signer, rootState.Serialize());
}
}
8 changes: 8 additions & 0 deletions backend/app/Savor22b/Constants/Addresses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Loading
Loading