From 30d224a2ef86aa61f9f4211867feda815935a29c Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Fri, 9 Aug 2024 07:32:22 +0900 Subject: [PATCH] Added GeneratePlainValue() method --- .../Sample/Actions/InvalidAction.cs | 31 ++++++++++ .../Sample/Actions/NumberAction.cs | 6 ++ .../Sample/Actions/TextAction.cs | 2 +- .../Sample/SampleActionsTest.cs | 46 +++++++++++++-- Libplanet.SDK.Action/Action/ActionBase.API.cs | 56 +++++++++++++++---- 5 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 Libplanet.SDK.Action.Tests/Sample/Actions/InvalidAction.cs diff --git a/Libplanet.SDK.Action.Tests/Sample/Actions/InvalidAction.cs b/Libplanet.SDK.Action.Tests/Sample/Actions/InvalidAction.cs new file mode 100644 index 0000000000..a19714bb3a --- /dev/null +++ b/Libplanet.SDK.Action.Tests/Sample/Actions/InvalidAction.cs @@ -0,0 +1,31 @@ +using Bencodex.Types; +using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; + +namespace Libplanet.SDK.Action.Tests.Sample.Actions +{ + public class InvalidAction : ActionBase + { + public override Address StorageAddress => + new Address("0x1000000000000000000000000000000000000000"); + + [Executable] + public void Add(IValue args) + { + Integer operand = (Integer)args; + Integer stored = GetState(Signer) is IValue value + ? (Integer)value + : new Integer(0); + SetState(Signer, new Integer(stored.Value + operand.Value)); + } + + public void Subtract(IValue args) + { + Integer operand = (Integer)args; + Integer stored = GetState(Signer) is IValue value + ? (Integer)value + : new Integer(0); + SetState(Signer, new Integer(stored.Value - operand.Value)); + } + } +} diff --git a/Libplanet.SDK.Action.Tests/Sample/Actions/NumberAction.cs b/Libplanet.SDK.Action.Tests/Sample/Actions/NumberAction.cs index 1fba97e2b2..73560d4779 100644 --- a/Libplanet.SDK.Action.Tests/Sample/Actions/NumberAction.cs +++ b/Libplanet.SDK.Action.Tests/Sample/Actions/NumberAction.cs @@ -43,5 +43,11 @@ public void Multiply(IValue args) Call(nameof(NumberLogAction.Multiply), new object?[] { operand }); SetState(Signer, new Integer(stored.Value * operand.Value)); } + + // Just some random public method for testing. + public void DoNothing() + { + return; + } } } diff --git a/Libplanet.SDK.Action.Tests/Sample/Actions/TextAction.cs b/Libplanet.SDK.Action.Tests/Sample/Actions/TextAction.cs index ef47792d53..a784ac9137 100644 --- a/Libplanet.SDK.Action.Tests/Sample/Actions/TextAction.cs +++ b/Libplanet.SDK.Action.Tests/Sample/Actions/TextAction.cs @@ -3,7 +3,7 @@ using Libplanet.Crypto; using Libplanet.SDK.Action.Attributes; -namespace Libplanet.SDK.Action.Tests +namespace Libplanet.SDK.Action.Tests.Sample.Actions { [ActionType("Text")] public class TextAction : ActionBase diff --git a/Libplanet.SDK.Action.Tests/Sample/SampleActionsTest.cs b/Libplanet.SDK.Action.Tests/Sample/SampleActionsTest.cs index 50d3a9fdb4..1349b9900c 100644 --- a/Libplanet.SDK.Action.Tests/Sample/SampleActionsTest.cs +++ b/Libplanet.SDK.Action.Tests/Sample/SampleActionsTest.cs @@ -5,6 +5,7 @@ using Libplanet.Action.Loader; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.SDK.Action.Attributes; using Libplanet.SDK.Action.Tests.Sample.Actions; using Libplanet.Store; using Libplanet.Store.Trie; @@ -109,23 +110,23 @@ public void TextAppend(bool commit) [Fact] public void InvalidPlainValueForLoading() { - IValue plainValue = Dictionary.Empty // Invalid type_id + IValue plainValue = Dictionary.Empty // Invalid type_id .Add("type_id", "Run") .Add("call", "Append") .Add("args", "Hello"); Assert.Throws(() => _loader.LoadAction(0, plainValue)); - plainValue = Dictionary.Empty // Missing type_id + plainValue = Dictionary.Empty // Missing type_id .Add("call", "Append") .Add("args", "Hello"); Assert.Throws(() => _loader.LoadAction(0, plainValue)); - plainValue = Dictionary.Empty // Missing call + plainValue = Dictionary.Empty // Missing call .Add("type_id", "Number") .Add("args", 5); Assert.Throws(() => _loader.LoadAction(0, plainValue)); - plainValue = Dictionary.Empty // Missing args + plainValue = Dictionary.Empty // Missing args .Add("type_id", "Number") .Add("call", "Add"); Assert.Throws(() => _loader.LoadAction(0, plainValue)); @@ -134,7 +135,7 @@ public void InvalidPlainValueForLoading() [Fact] public void InvalidPlainValueForExecution() { - IValue plainValue = Dictionary.Empty // Invalid call + IValue plainValue = Dictionary.Empty // Invalid call .Add("type_id", "Number") .Add("call", "Divide") .Add("args", 5); @@ -143,7 +144,7 @@ public void InvalidPlainValueForExecution() Assert.Throws(() => action.Execute(new MockActionContext(address, address, _world))); - plainValue = Dictionary.Empty // Invalid args + plainValue = Dictionary.Empty // Invalid args .Add("type_id", "Number") .Add("call", "Add") .Add("args", "Hello"); @@ -170,5 +171,38 @@ public void CallableAttributeIsRequired() action.Execute(new MockActionContext(signer, signer, world))) .InnerException); } + + [Fact] + public void GeneratePlainValue() + { + IValue expected = Dictionary.Empty + .Add("type_id", "Number") + .Add("call", "Add") + .Add("args", 5); + IValue generated = ActionBase.GeneratePlainValue( + "Add", new Integer(5)); + Assert.Equal(expected, generated); + + expected = Dictionary.Empty + .Add("type_id", "Text") + .Add("call", "Append") + .Add("args", "Hello"); + generated = ActionBase.GeneratePlainValue( + "Append", new Text("Hello")); + Assert.Equal(expected, generated); + + Assert.Contains( + $"{nameof(ActionTypeAttribute)}", + Assert.Throws(() => + ActionBase.GeneratePlainValue("Add", new Integer(5))).Message); + Assert.Contains( + $"cannot be found", + Assert.Throws(() => + ActionBase.GeneratePlainValue("Divide", new Integer(5))).Message); + Assert.Contains( + $"{nameof(ExecutableAttribute)}", + Assert.Throws(() => + ActionBase.GeneratePlainValue("DoNothing", new Integer(5))).Message); + } } } diff --git a/Libplanet.SDK.Action/Action/ActionBase.API.cs b/Libplanet.SDK.Action/Action/ActionBase.API.cs index be06a6b416..7602ebce77 100644 --- a/Libplanet.SDK.Action/Action/ActionBase.API.cs +++ b/Libplanet.SDK.Action/Action/ActionBase.API.cs @@ -1,5 +1,6 @@ using System.Reflection; using Bencodex.Types; +using Libplanet.Action; using Libplanet.Crypto; using Libplanet.SDK.Action.Attributes; @@ -7,6 +8,30 @@ namespace Libplanet.SDK.Action { public partial class ActionBase { + public static IValue GeneratePlainValue(string methodName, IValue args) + where T : ActionBase + { + ActionTypeAttribute actionType = typeof(T).GetCustomAttribute() ?? + throw new ArgumentException( + $"Type is missing a {nameof(ActionTypeAttribute)}."); + + MethodInfo methodInfo = typeof(T).GetMethod(methodName) ?? + throw new ArgumentException( + $"Method named {methodName} cannot be found for {typeof(T)}.", + nameof(methodName)); + if (methodInfo.GetCustomAttribute() is null) + { + throw new ArgumentException( + $"Target method is missing a {nameof(ExecutableAttribute)}.", + nameof(methodName)); + } + + return Dictionary.Empty + .Add("type_id", actionType.TypeIdentifier) + .Add("call", methodName) + .Add("args", args); + } + protected IValue? GetState(Address address) => World.GetAccount(StorageAddress).GetState(address); @@ -28,11 +53,12 @@ protected void Call(string methodName, object?[]? args = null) calledAction.LoadContext(ActionContext, World); MethodInfo methodInfo = typeof(T).GetMethod(methodName) ?? - throw new Exception("Method cannot be found."); + throw new ArgumentException( + $"Method named {methodName} cannot be found."); if (methodInfo.GetCustomAttribute() is null) { - throw new Exception( - $"Target method is missing a {nameof(CallableAttribute)}"); + throw new ArgumentException( + $"Target method {methodName} is missing a {nameof(CallableAttribute)}"); } methodInfo.Invoke(calledAction, args); @@ -41,28 +67,36 @@ protected void Call(string methodName, object?[]? args = null) _actionContext = calledAction._actionContext; } - protected U Call(string methodName, object?[]? args = null) - where T : ActionBase + protected TR Call(string methodName, object?[]? args = null) + where TA : ActionBase { - if (Activator.CreateInstance(typeof(T)) is not T calledAction) + if (Activator.CreateInstance(typeof(TA)) is not TA calledAction) { throw new Exception("Action cannot be found."); } calledAction.LoadContext(ActionContext, World); - MethodInfo methodInfo = typeof(T).GetMethod(methodName) ?? - throw new Exception("Method cannot be found."); + MethodInfo methodInfo = typeof(TA).GetMethod(methodName) ?? + throw new ArgumentException( + $"Method named {methodName} cannot be found."); + if (methodInfo.GetCustomAttribute() is null) + { + throw new ArgumentException( + $"Target method {methodName} is missing a {nameof(CallableAttribute)}"); + } - if (methodInfo.Invoke(calledAction, args) is not U result) + var result = methodInfo.Invoke(calledAction, args); + if (result is not TR typedResult) { - throw new Exception("Return type doesn't match."); + throw new Exception( + $"Return type is expected to be {typeof(TR)}: {result?.GetType()}"); } _world = calledAction._world; _actionContext = calledAction._actionContext; - return result; + return typedResult; } } }