From 82148088fd93f8a82b01714fbc378796912673fc Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Fri, 9 Aug 2024 08:03:38 +0900 Subject: [PATCH] Added a new set of test actions --- .../SimpleRPG/Actions/AvatarAction.cs | 46 +++++++ .../SimpleRPG/Actions/FarmAction.cs | 30 +++++ .../SimpleRPG/Actions/InfoAction.cs | 33 +++++ .../SimpleRPG/Actions/InventoryAction.cs | 33 +++++ .../SimpleRPG/Models/Avatar.cs | 14 +++ .../SimpleRPG/Models/Info.cs | 38 ++++++ .../SimpleRPG/Models/Inventory.cs | 31 +++++ .../SimpleRPG/SimpleRPGActionsTest.cs | 118 ++++++++++++++++++ 8 files changed, 343 insertions(+) create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Actions/AvatarAction.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Actions/FarmAction.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InfoAction.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InventoryAction.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Models/Avatar.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Models/Info.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/Models/Inventory.cs create mode 100644 Libplanet.SDK.Action.Tests/SimpleRPG/SimpleRPGActionsTest.cs diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/AvatarAction.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/AvatarAction.cs new file mode 100644 index 0000000000..736b443928 --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/AvatarAction.cs @@ -0,0 +1,46 @@ +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; +using Libplanet.SDK.Action.Tests.SimpleRPG.Models; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Actions +{ + [ActionType("Avatar")] + public class AvatarAction : ActionBase + { + // This has no IAccount associated with its domain. + public override Address StorageAddress => default; + + [Executable] + public void Create(IValue args) + { + string name = (Text)args; + Call("Create", new object?[] { name }); + Call("Create"); + } + + [Callable] + public Avatar GetAvatar(Address address) + { + Info info = Call( + "GetInfo", + new object?[] { address }); + Inventory inventory = Call( + "GetInventory", + new object?[] { address }); + return new Avatar(info, inventory); + } + + [Callable] + public void SetAvatar(Address address, Avatar avatar) + { + Call( + "SetInfo", + new object?[] { address, avatar.Info }); + Call( + "SetInventory", + new object?[] { address, avatar.Inventory }); + } + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/FarmAction.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/FarmAction.cs new file mode 100644 index 0000000000..b4a07532bc --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/FarmAction.cs @@ -0,0 +1,30 @@ +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; +using Libplanet.SDK.Action.Tests.SimpleRPG.Models; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Actions +{ + [ActionType("Farm")] + public class FarmAction : ActionBase + { + public const int ExpPerFarm = 10; + public const int GoldPerFarm = 20; + + // This has no IAccount associated with its domain. + public override Address StorageAddress => default; + + [Executable] + public void Farm(IValue args) + { + // Simple type checking. + _ = (Null)args; + + Avatar avatar = Call("GetAvatar", new object?[] { Signer }); + avatar.Info.AddExp(ExpPerFarm); + avatar.Inventory.AddGold(GoldPerFarm); + Call("SetAvatar", new object?[] { Signer, avatar }); + } + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InfoAction.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InfoAction.cs new file mode 100644 index 0000000000..b308f1373a --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InfoAction.cs @@ -0,0 +1,33 @@ +using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; +using Libplanet.SDK.Action.Tests.SimpleRPG.Models; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Actions +{ + public class InfoAction : ActionBase + { + public override Address StorageAddress => + new Address("0x1000000000000000000000000000000000000001"); + + [Callable] + public Info Create(string name) + { + if (GetState(Signer) is { } value) + { + throw new InvalidOperationException("Info already exists."); + } + + Info info = new Info(name, 0); + SetInfo(Signer, info); + return info; + } + + [Callable] + public Info GetInfo(Address address) => + new Info(GetState(address) ?? throw new NullReferenceException()); + + [Callable] + public void SetInfo(Address address, Info info) => + SetState(address, info.Serialized); + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InventoryAction.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InventoryAction.cs new file mode 100644 index 0000000000..788ab3c11a --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Actions/InventoryAction.cs @@ -0,0 +1,33 @@ +using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; +using Libplanet.SDK.Action.Tests.SimpleRPG.Models; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Actions +{ + public class InventoryAction : ActionBase + { + public override Address StorageAddress => + new Address("0x1000000000000000000000000000000000000002"); + + [Callable] + public Inventory Create() + { + if (GetState(Signer) is { }) + { + throw new InvalidOperationException("Inventory already exists."); + } + + Inventory inventory = new Inventory(); + SetInventory(Signer, inventory); + return inventory; + } + + [Callable] + public Inventory GetInventory(Address address) => + new Inventory(GetState(address) ?? throw new NullReferenceException()); + + [Callable] + public void SetInventory(Address address, Inventory inventory) => + SetState(address, inventory.Serialized); + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Avatar.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Avatar.cs new file mode 100644 index 0000000000..e43a65ef4d --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Avatar.cs @@ -0,0 +1,14 @@ +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Models +{ + public class Avatar + { + public Info Info { get; } + public Inventory Inventory { get; } + + public Avatar(Info info, Inventory inventory) + { + Info = info; + Inventory = inventory; + } + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Info.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Info.cs new file mode 100644 index 0000000000..6f880a3660 --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Info.cs @@ -0,0 +1,38 @@ +using Bencodex.Types; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Models +{ + public class Info + { + public string Name { get; } + + public int Exp { get; private set; } + + public int Level => (Exp / 100); + + public Info(string name) + : this(name, 0) + { + } + + public Info(IValue value) + : this((Text)((List)value)[0], (Integer)((List)value)[1]) + { + } + + public Info(string name, int exp) + { + Name = name; + Exp = exp; + } + + public IValue Serialized => List.Empty + .Add(Name) + .Add(Exp); + + public void AddExp(int exp) + { + Exp = Exp + exp; + } + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Inventory.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Inventory.cs new file mode 100644 index 0000000000..4ad64a1556 --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/Models/Inventory.cs @@ -0,0 +1,31 @@ +using Bencodex.Types; + +namespace Libplanet.SDK.Action.Tests.SimpleRPG.Models +{ + public class Inventory + { + public int Gold { get; private set; } + + public Inventory() + : this(0) + { + } + + public Inventory(IValue value) + : this((int)(Integer)value) + { + } + + public Inventory(int gold) + { + Gold = gold; + } + + public IValue Serialized => new Integer(Gold); + + public void AddGold(int gold) + { + Gold = Gold + gold; + } + } +} diff --git a/Libplanet.SDK.Action.Tests/SimpleRPG/SimpleRPGActionsTest.cs b/Libplanet.SDK.Action.Tests/SimpleRPG/SimpleRPGActionsTest.cs new file mode 100644 index 0000000000..9844746721 --- /dev/null +++ b/Libplanet.SDK.Action.Tests/SimpleRPG/SimpleRPGActionsTest.cs @@ -0,0 +1,118 @@ +using System.Collections.Immutable; +using System.Reflection; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.SDK.Action.Tests.SimpleRPG.Actions; +using Libplanet.SDK.Action.Tests.SimpleRPG.Models; +using Libplanet.Store; +using Libplanet.Store.Trie; +using Libplanet.Types.Blocks; +using Xunit; + +namespace Libplanet.SDK.Action.Tests.Sample +{ + public class SimpleRPGActionsTest + { + private TypedActionLoader _loader; + private IStateStore _stateStore; + private IWorld _world; + + public SimpleRPGActionsTest() + { + _loader = new TypedActionLoader( + ImmutableDictionary.Empty + .Add(new Text("Avatar"), typeof(AvatarAction)) + .Add(new Text("Farm"), typeof(FarmAction))); + + _stateStore = new TrieStateStore(new MemoryKeyValueStore()); + + ITrie trie = _stateStore.GetStateRoot(null); + trie = trie.SetMetadata(new TrieMetadata(Block.CurrentProtocolVersion)); + trie = _stateStore.Commit(trie); + _world = new World(new WorldBaseState(trie, _stateStore)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Scenario(bool commit) + { + IValue plainValue = Dictionary.Empty + .Add("type_id", "Avatar") + .Add("call", "Create") + .Add("args", "Hero"); + IAction action = Assert.IsType(_loader.LoadAction(0, plainValue)); + Address signer = new PrivateKey().Address; + IWorld world = _world; + + world = action.Execute(new MockActionContext(signer, signer, world)); + world = commit ? _stateStore.CommitWorld(world) : world; + Assert.Equal( + new Info("Hero").Serialized, + world + .GetAccountState(new Address("0x1000000000000000000000000000000000000001")) + .GetState(signer)); + Assert.Equal( + new Inventory().Serialized, + world + .GetAccountState(new Address("0x1000000000000000000000000000000000000002")) + .GetState(signer)); + + const int repeat = 3; + foreach (var _ in Enumerable.Range(0, repeat)) + { + plainValue = Dictionary.Empty + .Add("type_id", "Farm") + .Add("call", "Farm") + .Add("args", Null.Value); + action = Assert.IsType(_loader.LoadAction(0, plainValue)); + world = action.Execute(new MockActionContext(signer, signer, world)); + world = commit ? _stateStore.CommitWorld(world) : world; + } + + Assert.Equal( + new Info("Hero", FarmAction.ExpPerFarm * repeat).Serialized, + world + .GetAccountState(new Address("0x1000000000000000000000000000000000000001")) + .GetState(signer)); + Assert.Equal( + new Inventory(FarmAction.GoldPerFarm * repeat).Serialized, + world + .GetAccountState(new Address("0x1000000000000000000000000000000000000002")) + .GetState(signer)); + } + + [Fact] + public void CannotCreateTwice() + { + IValue plainValue = Dictionary.Empty + .Add("type_id", "Avatar") + .Add("call", "Create") + .Add("args", "Hero"); + IAction action = Assert.IsType(_loader.LoadAction(0, plainValue)); + Address signer = new PrivateKey().Address; + IWorld world = _world; + + world = action.Execute(new MockActionContext(signer, signer, world)); + world = _stateStore.CommitWorld(world); + + plainValue = Dictionary.Empty + .Add("type_id", "Avatar") + .Add("call", "Create") + .Add("args", "Princess"); + action = Assert.IsType(_loader.LoadAction(0, plainValue)); + Assert.Contains( + "Info already exists", + Assert.IsType( + Assert.IsType( + Assert.Throws(() => + action.Execute(new MockActionContext(signer, signer, world))) + .InnerException) + .InnerException) + .Message); + } + } +}