diff --git a/backend/app/Savor22b.Tests/Action/SellDungeonConquestTests.cs b/backend/app/Savor22b.Tests/Action/SellDungeonConquestTests.cs new file mode 100644 index 00000000..a9cbb959 --- /dev/null +++ b/backend/app/Savor22b.Tests/Action/SellDungeonConquestTests.cs @@ -0,0 +1,50 @@ + +namespace Savor22b.Tests.Action; + +using Libplanet.State; +using Savor22b.Action; +using Savor22b.States; +using Xunit; + +public class SellDungeonConquestTests : ActionTests +{ + public static int TestDungeonId = 1; + + public SellDungeonConquestTests() { } + + [Fact] + public void Execute_Success_Normal() + { + var beforeState = CreatePresetStateDelta(); + + SellDungeonConquest action = new SellDungeonConquest(TestDungeonId); + + IAccountStateDelta afterState = action.Execute( + new DummyActionContext + { + PreviousStates = beforeState, + Signer = SignerAddress(), + Random = random, + Rehearsal = false, + BlockIndex = 1, + } + ); + + var afterGlobalDungeonState = DeriveGlobalDungeonStateDelta(afterState); + + Assert.False(afterGlobalDungeonState.IsDungeonConquestAddress(TestDungeonId, SignerAddress())); + } + + private IAccountStateDelta CreatePresetStateDelta() + { + IAccountStateDelta state = new DummyState(); + + GlobalDungeonState globalDungeonState = state.GetState(GlobalDungeonState.StateAddress) is Bencodex.Types.Dictionary globalDungeonStateEncoded + ? new GlobalDungeonState(globalDungeonStateEncoded) + : new GlobalDungeonState(); + + globalDungeonState = globalDungeonState.SetDungeonConquestAddress(TestDungeonId, SignerAddress()); + + return state.SetState(GlobalDungeonState.StateAddress, globalDungeonState.Serialize()); + } +} diff --git a/backend/app/Savor22b/Action/SellDungeonConquest.cs b/backend/app/Savor22b/Action/SellDungeonConquest.cs new file mode 100644 index 00000000..ee40678f --- /dev/null +++ b/backend/app/Savor22b/Action/SellDungeonConquest.cs @@ -0,0 +1,73 @@ +namespace Savor22b.Action; + +using System.Collections.Immutable; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Headless.Extensions; +using Libplanet.State; +using Savor22b.Action.Exceptions; +using Savor22b.States; +using Savor22b.Model; +using Libplanet.Assets; + +[ActionType(nameof(SellDungeonConquest))] +public class SellDungeonConquest : SVRAction +{ + public int DungeonId { get; private set; } + + public SellDungeonConquest() { } + + public SellDungeonConquest(int dungeonId) + { + DungeonId = dungeonId; + } + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary() + { + [nameof(DungeonId)] = DungeonId.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + DungeonId = plainValue[nameof(DungeonId)].ToInteger(); + } + + private Dungeon GetDungeonInCsv(int dungeonId) + { + Dungeon? dungeon = CsvDataHelper.GetDungeonById(dungeonId); + + if (dungeon == null) + { + throw new InvalidDungeonException($"Invalid dungeon ID: {dungeonId}"); + } + + return dungeon; + } + + public override IAccountStateDelta Execute(IActionContext ctx) + { + if (ctx.Rehearsal) + { + return ctx.PreviousStates; + } + + IAccountStateDelta states = ctx.PreviousStates; + + GlobalDungeonState globalDungeonState = states.GetState(GlobalDungeonState.StateAddress) is Dictionary globalDungeonStateEncoded + ? new GlobalDungeonState(globalDungeonStateEncoded) + : new GlobalDungeonState(); + + if (!globalDungeonState.IsDungeonConquestAddress(DungeonId, ctx.Signer)) + { + throw new InvalidValueException($"Invalid dungeon ID: {DungeonId}"); + } + + Dungeon dungeon = GetDungeonInCsv(DungeonId); + + globalDungeonState = globalDungeonState.RemoveDungeonConquestAddress(DungeonId); + + states = states.MintAsset(ctx.Signer, FungibleAssetValue.Parse(Currencies.KeyCurrency, dungeon.ReturnDungeonRewardBBG)); + return states.SetState(GlobalDungeonState.StateAddress, globalDungeonState.Serialize()); + } +} diff --git a/backend/app/Savor22b/GraphTypes/Query/Query.cs b/backend/app/Savor22b/GraphTypes/Query/Query.cs index ca4c81a7..adfb90cf 100644 --- a/backend/app/Savor22b/GraphTypes/Query/Query.cs +++ b/backend/app/Savor22b/GraphTypes/Query/Query.cs @@ -545,6 +545,38 @@ swarm is null } ); + Field>( + "createAction_SellDungeonConquest", + description: "던전점령권 시스템에 판매", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "publicKey", + Description = "The base64-encoded public key for Transaction.", + }, + new QueryArgument> + { + Name = "dungeonId", + Description = "던전 Id", + } + ), + resolve: context => + { + var publicKey = new PublicKey( + ByteUtil.ParseHex(context.GetArgument("publicKey")) + ); + + var action = new SellDungeonConquest(context.GetArgument("dungeonId")); + + return new GetUnsignedTransactionHex( + action, + publicKey, + _blockChain, + _swarm + ).UnsignedTransactionHex; + } + ); + Field>( "createAction_CancelRegisteredTradeGoodAction", description: "무역상점 상품 등록 취소",