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-301] 상품 구매 액션 추가 #159

Merged
merged 2 commits into from
Apr 14, 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
109 changes: 109 additions & 0 deletions backend/app/Savor22b.Tests/Action/BuyTradeGoodTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 BuyTradeGoodTests : ActionTests
{
public BuyTradeGoodTests() { }

[Fact]
public void Execute_Success()
{
var (stateDelta, productId) = CreatePresetStateDelta();
stateDelta = stateDelta.MintAsset(
SignerAddress(),
FungibleAssetValue.Parse(Currencies.KeyCurrency, "10")
);

var action = new BuyTradeGoodAction(
productId
);

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

var afterTradeInventoryState = DeriveTradeInventoryStateDelta(stateDelta);
var afterRootState = DeriveRootStateFromAccountStateDelta(stateDelta);

Assert.Empty(afterTradeInventoryState.TradeGoods);
Assert.True(afterRootState.InventoryState.RefrigeratorStateList.Count == 1);
}

[Fact]
public void Execute_Fail_InsufficientBalance()
{
var (stateDelta, productId) = CreatePresetStateDelta();

var action = new BuyTradeGoodAction(
productId
);

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

private (IAccountStateDelta, Guid) 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();
TradeInventoryState tradeInventoryState = state.GetState(TradeInventoryState.StateAddress) is Bencodex.Types.Dictionary tradeInventoryStateEncoded
? new TradeInventoryState(tradeInventoryStateEncoded)
: new TradeInventoryState();

var foodProductId = Guid.NewGuid();

var food = RefrigeratorState.CreateFood(
Guid.NewGuid(),
1,
"D",
1,
1,
1,
1,
1,
ImmutableList<Guid>.Empty
);

tradeInventoryState = tradeInventoryState.RegisterGood(
new FoodGoodState(
signerAddress,
foodProductId,
FungibleAssetValue.Parse(Currencies.KeyCurrency, "10"),
food)
);
state = state.SetState(TradeInventoryState.StateAddress, tradeInventoryState.Serialize());
return (state.SetState(signerAddress, rootState.Serialize()), foodProductId);
}
}
85 changes: 85 additions & 0 deletions backend/app/Savor22b/Action/BuyTradeGoodAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace Savor22b.Action;

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

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

public BuyTradeGoodAction(Guid productId)
{
ProductId = productId;
}

public Guid ProductId;

protected override IImmutableDictionary<string, IValue> PlainValueInternal =>
new Dictionary<string, IValue>()
{
[nameof(ProductId)] = ProductId.Serialize(),
}.ToImmutableDictionary();

protected override void LoadPlainValueInternal(IImmutableDictionary<string, IValue> plainValue)
{
ProductId = plainValue[nameof(ProductId)].ToGuid();
}

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;

var good = tradeInventoryState.TradeGoods.First(g => g.Key == ProductId);

states = states.TransferAsset(
ctx.Signer,
good.Value.SellerAddress,
good.Value.Price,
allowNegativeBalance: false
);

tradeInventoryState.TradeGoods.Remove(good.Key);

switch (good.Value)
{
case FoodGoodState foodGoodState:
inventoryState = inventoryState.AddRefrigeratorItem(foodGoodState.Food);
break;
case ItemsGoodState itemsGoodState:
foreach (var itemState in itemsGoodState.Items)
{
inventoryState = inventoryState.AddItem(itemState);
}

break;
default:
throw new InvalidValueException("Food or Items required");
}

rootState.SetInventoryState(inventoryState);
states = states.SetState(TradeInventoryState.StateAddress, tradeInventoryState.Serialize());
return states.SetState(ctx.Signer, rootState.Serialize());
}
}
32 changes: 32 additions & 0 deletions backend/app/Savor22b/GraphTypes/Query/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,38 @@ swarm is null
}
);

Field<NonNullGraphType<StringGraphType>>(
"createAction_BuyTradeGoodAction",
description: "무역상점 구매",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "publicKey",
Description = "The base64-encoded public key for Transaction.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

복붙이 넘 티나요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
new QueryArgument<NonNullGraphType<GuidGraphType>>
{
Name = "productId",
Description = "상품 고유 Id",
}
),
resolve: context =>
{
var publicKey = new PublicKey(
ByteUtil.ParseHex(context.GetArgument<string>("publicKey"))
);

var action = new BuyTradeGoodAction(context.GetArgument<Guid>("productId"));

return new GetUnsignedTransactionHex(
action,
publicKey,
_blockChain,
_swarm
).UnsignedTransactionHex;
}
);

Field<NonNullGraphType<StringGraphType>>(
"createAction_RegisterTradeGoodAction",
description: "Register Trade Good to Trade Store",
Expand Down
Loading