diff --git a/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs new file mode 100644 index 000000000..831848255 --- /dev/null +++ b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; +using Sequence.Contracts; +using Assert = UnityEngine.Assertions.Assert; + +namespace Sequence.Ethereum.Tests +{ + public class ABIRegexTests + { + [TestCase("", false)] + [TestCase("functionName", true)] + [TestCase("functionName()", false)] + [TestCase("functionName123", true)] + [TestCase("functionName_s", true)] + [TestCase("function-Name", true)] + [TestCase("functionName ", false)] + public void TestMatchesFunctionName(string input, bool expected) + { + bool result = ABIRegex.MatchesFunctionName(input); + Assert.AreEqual(expected, result); + } + + [TestCase("", false)] + [TestCase("functionName", false)] + [TestCase("functionName(", false)] + [TestCase("functionName()", true)] + [TestCase("functionName(a)", true)] + [TestCase("functionName(a a)", false)] + [TestCase("functionName(a, a)", true)] + [TestCase("functionName(a,a)", true)] + [TestCase("functionName(,)", false)] + [TestCase("functionName(Aa123)", true)] + [TestCase("functionName(a,)", false)] + [TestCase("functionName() ", false)] + [TestCase("function_-123Name()", true)] + public void TestMatchesFunctionABI(string input, bool expected) + { + bool result = ABIRegex.MatchesFunctionABI(input); + Assert.AreEqual(expected, result); + } + } +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs.meta b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs.meta new file mode 100644 index 000000000..4d92333aa --- /dev/null +++ b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9727550d4e3345baa3db78aa64d3c811 +timeCreated: 1727361762 \ No newline at end of file diff --git a/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs b/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs index 0e0c4e23d..ad3365323 100644 --- a/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs +++ b/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs @@ -93,5 +93,121 @@ public async Task TestComplexContract() CollectionAssert.AreEqual(expectedMoreBytes.Data, (byte[])resultPart2[3]); CollectionAssert.AreEqual(expectedAddresses, resultPart2[4].ConvertToTArray()); } + + [Test] + public void TestInvalidRegex_noABI() + { + Contract complexContract = new Contract(complexContractAddress); + + try + { + complexContract.QueryContract("functionName"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + + try + { + complexContract.QueryContract("functionName("); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + + try + { + complexContract.QueryContract("functionName)"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + + try + { + complexContract.AssembleCallData("functionName(uint banana)", 1); + } + catch (Exception e) + { + Assert.Fail("No exception expected"); + } + + + try + { + complexContract.CallFunction("functionName(uint, uint)", 1, 1); + } + catch (Exception e) + { + Assert.Fail("No exception expected"); + } + } + + [Test] + public void TestInvalidRegex_withABI() + { + Contract complexContract = new Contract(complexContractAddress, complexContractAbi); + + try + { + complexContract.QueryContract("functionName()"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + + try + { + complexContract.AssembleCallData("functionName(uint banana)"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + + + try + { + complexContract.CallFunction("functionName*"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + } } } diff --git a/Assets/SequenceSDK/Ethereum/Tests/WalletTests.cs b/Assets/SequenceSDK/Ethereum/Tests/WalletTests.cs index b35e2f377..516ce7d54 100644 --- a/Assets/SequenceSDK/Ethereum/Tests/WalletTests.cs +++ b/Assets/SequenceSDK/Ethereum/Tests/WalletTests.cs @@ -178,7 +178,7 @@ public async Task TestChain_ERC20Mock_MockMint_Test() EOAWallet wallet2 = new EOAWallet("0xabc0000000000000000000000000000000000000000000000000000000000002"); Contract mockERC20 = new Contract(receipt.contractAddress); string result = await mockERC20.SendTransactionMethod(wallet2, client, 0, - "mockMint(address , uint256)", + "mockMint(address, uint256)", wallet2.GetAddress(), 1); Assert.IsNotNull(result); } diff --git a/Assets/SequenceSDK/WaaS/Tests/DelayedEncodeJsonSerializationTest.cs b/Assets/SequenceSDK/WaaS/Tests/DelayedEncodeJsonSerializationTest.cs index 52ddd529d..c7ddee38c 100644 --- a/Assets/SequenceSDK/WaaS/Tests/DelayedEncodeJsonSerializationTest.cs +++ b/Assets/SequenceSDK/WaaS/Tests/DelayedEncodeJsonSerializationTest.cs @@ -52,5 +52,23 @@ public void TestDelayedEncodeDataArgsGetsSerializedAlphabetically() Assert.AreEqual("{\"abi\":\"testAbi(string,ComplexObjectInNonAlphabeticalOrder,int)\",\"args\":[\"some string\",{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},1,5,[1,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},null,\"banana\"],[{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},null],[1,[2,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},3,{\"ComplexObjectArray\":[null,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"}],\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"}],\"word\"]],\"func\":\"testFunc\"}", json); } + + [Test] + public void TestAbiDataArgsGetsSerializedAlphabetically() + { + AbiData testData = new AbiData("testAbi(string,ComplexObjectInNonAlphabeticalOrder,int)", + new object[] { "some string", new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"), + BigInteger.One, 5, new object[] { 1, new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"), null, "banana" }, + new ComplexObjectInNonAlphabeticalOrder[] { new ("last", "first", "halfway"), null }, + new object[] { 1, new object[] { 2, new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"), 3, + new ExtraComplexObjectWithComplexObjectArray("last", new ComplexObjectInNonAlphabeticalOrder[] { null, + new ("last", "first", "halfway")}, "halfway")}, "word"} + }); + + string json = JsonConvert.SerializeObject(testData); + Debug.Log(json); + + Assert.AreEqual("{\"abi\":\"testAbi(string,ComplexObjectInNonAlphabeticalOrder,int)\",\"args\":[\"some string\",{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},1,5,[1,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},null,\"banana\"],[{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},null],[1,[2,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"},3,{\"ComplexObjectArray\":[null,{\"FirstValue\":\"first\",\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"}],\"HalfwayValue\":\"halfway\",\"LastValue\":\"last\"}],\"word\"]]}", json); + } } } \ No newline at end of file diff --git a/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs b/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs index 364e8fa8a..559451096 100644 --- a/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs +++ b/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs @@ -29,7 +29,7 @@ public async Task TestSendTransactionSuccessEvent() failedEventHit = true; }; - await wallet.SendTransaction(Chain.None, null); + await wallet.SendTransaction(Chain.None, Array.Empty()); Assert.IsTrue(successEventHit); Assert.IsFalse(failedEventHit); @@ -52,12 +52,117 @@ public async Task TestSendTransactionFailedEvent() failedEventHit = true; }; - await wallet.SendTransaction(Chain.None, null); + await wallet.SendTransaction(Chain.None, Array.Empty()); Assert.IsFalse(successEventHit); Assert.IsTrue(failedEventHit); } + private static (DelayedEncodeData, bool)[] _invalidDelayedEncodeData = new[] + { + (new DelayedEncodeData("functionName", Array.Empty(), "functionName"), false), + (new DelayedEncodeData("functionName()", Array.Empty(), "functionName"), true), + (new DelayedEncodeData("functionName()", Array.Empty(), "functionName()"), false), + (new DelayedEncodeData("functionName(", Array.Empty(), "functionName"), false), + (new DelayedEncodeData("functionName)", Array.Empty(), "functionName"), false), + (new DelayedEncodeData("functionName(uint banana)", new object[] {1}, "functionName"), true), + (new DelayedEncodeData("functionName(uint, uint)", new object[]{1,2}, "functionName"), true), + (new DelayedEncodeData("functionName(uint,uint)", new object[]{1,2}, "functionName(uint,uint)"), false), + (new DelayedEncodeData("functionName(uint banana", new object[] {1}, "functionName"), false), + (new DelayedEncodeData("functionNameuint, uint)", new object[]{1,2}, "functionName"), false), + (new DelayedEncodeData("functionName(uint,uint", new object[]{1,2}, "functionName"), false) + }; + + [TestCaseSource(nameof(_invalidDelayedEncodeData))] + public async Task TestSendTransactionEvent_delayedEncodeValidation((DelayedEncodeData, bool) args) + { + DelayedEncodeData data = args.Item1; + bool success = args.Item2; + IIntentSender intentSender = new MockIntentSender(new SuccessfulTransactionReturn()); + SequenceWallet wallet = new SequenceWallet(address, "", intentSender); + + bool successEventHit = false; + wallet.OnSendTransactionComplete += (result)=> + { + successEventHit = true; + }; + bool failedEventHit = false; + wallet.OnSendTransactionFailed += (result)=> + { + failedEventHit = true; + }; + + await wallet.SendTransaction(Chain.None, new Transaction[] + { + new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249"), + new DelayedEncode("0xc683a014955b75F5ECF991d4502427c8fa1Aa249", "0", data), + new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249") + }, waitForReceipt: false); + + if (!success) + { + Assert.IsFalse(successEventHit); + Assert.IsTrue(failedEventHit); + } + else + { + Assert.IsTrue(successEventHit); + Assert.IsFalse(failedEventHit); + } + } + + private static (AbiData, bool)[] _invalidContractCallData = new[] + { + (new AbiData("functionName", Array.Empty()), false), + (new AbiData("functionName()", Array.Empty()), true), + (new AbiData("functionName(", Array.Empty()), false), + (new AbiData("functionName)", Array.Empty()), false), + (new AbiData("functionName(uint banana)", new object[] {1}), true), + (new AbiData("functionName(uint, uint)", new object[]{1,2}), true), + (new AbiData("functionName(uint,uint)", new object[]{1,2}), true), + (new AbiData("functionName(uint banana", new object[] {1}), false), + (new AbiData("functionNameuint, uint)", new object[]{1,2}), false), + (new AbiData("functionName(uint,uint", new object[]{1,2}), false) + }; + + [TestCaseSource(nameof(_invalidContractCallData))] + public async Task TestSendTransactionEvent_contractCallValidation((AbiData, bool) args) + { + AbiData data = args.Item1; + bool success = args.Item2; + IIntentSender intentSender = new MockIntentSender(new SuccessfulTransactionReturn()); + SequenceWallet wallet = new SequenceWallet(address, "", intentSender); + + bool successEventHit = false; + wallet.OnSendTransactionComplete += (result)=> + { + successEventHit = true; + }; + bool failedEventHit = false; + wallet.OnSendTransactionFailed += (result)=> + { + failedEventHit = true; + }; + + await wallet.SendTransaction(Chain.None, new Transaction[] + { + new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249"), + new SequenceContractCall("0xc683a014955b75F5ECF991d4502427c8fa1Aa249", data), + new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249") + }, waitForReceipt: false); + + if (!success) + { + Assert.IsFalse(successEventHit); + Assert.IsTrue(failedEventHit); + } + else + { + Assert.IsTrue(successEventHit); + Assert.IsFalse(failedEventHit); + } + } + [Test] public async Task TestSendTransactionException() { @@ -75,7 +180,7 @@ public async Task TestSendTransactionException() failedEventHit = true; }; - await wallet.SendTransaction(Chain.None, null); + await wallet.SendTransaction(Chain.None, Array.Empty()); Assert.IsFalse(successEventHit); Assert.IsTrue(failedEventHit); @@ -343,7 +448,7 @@ public async Task TestWaitForTransactionReceipt(int numberOfFetches) failedEventHit = true; }; - var result = await wallet.SendTransaction(Chain.None, null); + var result = await wallet.SendTransaction(Chain.None, Array.Empty()); Assert.IsTrue(successEventHit); Assert.IsFalse(failedEventHit); @@ -392,7 +497,7 @@ public async Task TestWaitForTransactionReceipt_failedToFetch() LogAssert.Expect(LogType.Error, "Transaction was successful, but we're unable to obtain the transaction hash. Reason: some random error"); - var result = await wallet.SendTransaction(Chain.None, null); + var result = await wallet.SendTransaction(Chain.None, Array.Empty()); Assert.IsTrue(successEventHit); Assert.IsFalse(failedEventHit); @@ -438,7 +543,7 @@ public async Task TestDontWaitForTransactionReceipt(int numberOfFetches) failedEventHit = true; }; - var result = await wallet.SendTransaction(Chain.None, null, false); + var result = await wallet.SendTransaction(Chain.None, Array.Empty(), false); Assert.IsTrue(successEventHit); Assert.IsFalse(failedEventHit); diff --git a/Assets/SequenceSDK/WaaS/Tests/WalletInteractionsTests.cs b/Assets/SequenceSDK/WaaS/Tests/WalletInteractionsTests.cs index dd5b5434c..aed8db23d 100644 --- a/Assets/SequenceSDK/WaaS/Tests/WalletInteractionsTests.cs +++ b/Assets/SequenceSDK/WaaS/Tests/WalletInteractionsTests.cs @@ -108,6 +108,63 @@ await indexer.GetTokenBalances( await tcs.Task; } + [Test] + public async Task TestContractCall() + { + var tcs = new TaskCompletionSource(); + EndToEndTestHarness testHarness = new EndToEndTestHarness(); + + testHarness.Login(async wallet => + { + try + { + string erc20Address = "0x079294e6ffec16234578c672fa3fbfd4b6c48640"; + ChainIndexer indexer = new ChainIndexer(_chain); + GetTokenBalancesReturn balanceReturn = + await indexer.GetTokenBalances( + new GetTokenBalancesArgs(wallet.GetWalletAddress(), erc20Address)); + BigInteger balance; + if (balanceReturn == null || balanceReturn.balances.Length == 0) + { + balance = 0; + } + else + { + balance = balanceReturn.balances[0].balance; + } + + TransactionReturn transactionReturn = await wallet.SendTransaction(_chain, + new Transaction[] + { + new SequenceContractCall(erc20Address, + new AbiData("mint(address,uint256)", + new object[] + { + wallet.GetWalletAddress().Value, DecimalNormalizer.Normalize(1) + })) + }); + Assert.IsNotNull(transactionReturn); + Assert.IsTrue(transactionReturn is SuccessfulTransactionReturn); + await Task.Delay(5000); // Allow indexer some time to pick up transaction + balanceReturn = + await indexer.GetTokenBalances( + new GetTokenBalancesArgs(wallet.GetWalletAddress(), erc20Address)); + BigInteger balance2 = balanceReturn.balances[0].balance; + Assert.Greater(balance2, balance); + tcs.TrySetResult(true); + } + catch (System.Exception e) + { + tcs.TrySetException(e); + } + }, (error, method, email, methods) => + { + tcs.TrySetException(new Exception(error)); + }); + + await tcs.Task; + } + [Test] public async Task TestSendErc20() { diff --git a/Packages/Sequence-Unity/Sequence/Samples b/Packages/Sequence-Unity/Sequence/Samples deleted file mode 120000 index 7b662d178..000000000 --- a/Packages/Sequence-Unity/Sequence/Samples +++ /dev/null @@ -1 +0,0 @@ -Samples~ \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/Samples~/DemoScene.meta b/Packages/Sequence-Unity/Sequence/Samples/DemoScene.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/DemoScene.meta rename to Packages/Sequence-Unity/Sequence/Samples/DemoScene.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/DemoScene/Demo.unity b/Packages/Sequence-Unity/Sequence/Samples/DemoScene/Demo.unity similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/DemoScene/Demo.unity rename to Packages/Sequence-Unity/Sequence/Samples/DemoScene/Demo.unity diff --git a/Packages/Sequence-Unity/Sequence/Samples~/DemoScene/Demo.unity.meta b/Packages/Sequence-Unity/Sequence/Samples/DemoScene/Demo.unity.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/DemoScene/Demo.unity.meta rename to Packages/Sequence-Unity/Sequence/Samples/DemoScene/Demo.unity.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Scripts.meta b/Packages/Sequence-Unity/Sequence/Samples/Scripts.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Scripts.meta rename to Packages/Sequence-Unity/Sequence/Samples/Scripts.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.cs b/Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.cs similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.cs rename to Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.cs diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.cs.meta b/Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.cs.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.cs.meta rename to Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.cs.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.prefab b/Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.prefab similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.prefab rename to Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.prefab diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.prefab.meta b/Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.prefab.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Scripts/SequenceConnector.prefab.meta rename to Packages/Sequence-Unity/Sequence/Samples/Scripts/SequenceConnector.prefab.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Setup.meta b/Packages/Sequence-Unity/Sequence/Samples/Setup.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Setup.meta rename to Packages/Sequence-Unity/Sequence/Samples/Setup.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources.meta b/Packages/Sequence-Unity/Sequence/Samples/Setup/Resources.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources.meta rename to Packages/Sequence-Unity/Sequence/Samples/Setup/Resources.meta diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources/SequenceConfig.asset b/Packages/Sequence-Unity/Sequence/Samples/Setup/Resources/SequenceConfig.asset similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources/SequenceConfig.asset rename to Packages/Sequence-Unity/Sequence/Samples/Setup/Resources/SequenceConfig.asset diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources/SequenceConfig.asset.meta b/Packages/Sequence-Unity/Sequence/Samples/Setup/Resources/SequenceConfig.asset.meta similarity index 100% rename from Packages/Sequence-Unity/Sequence/Samples~/Setup/Resources/SequenceConfig.asset.meta rename to Packages/Sequence-Unity/Sequence/Samples/Setup/Resources/SequenceConfig.asset.meta diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs new file mode 100644 index 000000000..74b17c843 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using UnityEngine.Scripting; + +namespace Sequence.EmbeddedWallet +{ + [Preserve] + [Serializable] + [JsonConverter(typeof(AbiDataConverter))] + public class AbiData + { + public string abi; + public object[] args; + + public AbiData(string abi, object[] args) + { + this.abi = abi; + this.args = args; + } + } + + public class AbiDataConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AbiData); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject jsonObject = JObject.Load(reader); + + string abi = jsonObject["abi"].ToObject(); + + JArray argsArray = jsonObject["args"].ToObject(); + object[] args = argsArray.ToObject(serializer); + return new AbiData(abi, args); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + AbiData abiData = (AbiData)value; + + JObject jsonObject = new JObject + { + { "abi", JToken.FromObject(abiData.abi) }, + { "args", SerializeArgsWithSortedFields(abiData.args, serializer) }, + }; + + jsonObject.WriteTo(writer); + } + + private JArray SerializeArgsWithSortedFields(object[] args, JsonSerializer serializer) + { + JArray sortedArgsArray = new JArray(); + + foreach (var arg in args) + { + JToken serializedArg = SerializeAlphabetically(arg, serializer); + sortedArgsArray.Add(serializedArg); + } + + return sortedArgsArray; + } + + private JToken SerializeAlphabetically(object arg, JsonSerializer serializer) + { + JToken token = JToken.FromObject(arg, serializer); + + if (token.Type == JTokenType.Array) + { + JArray array = new JArray(); + foreach (var element in token.Children()) + { + array.Add(SerializeAlphabetically(element, serializer)); + } + return array; + } + + if (token.Type == JTokenType.Object) + { + JObject jsonObject = (JObject)token; + JObject sortedObject = new JObject( + jsonObject.Properties() + .OrderBy(p => p.Name) + .Select(p => new JProperty( + p.Name, SerializeAlphabetically(p.Value, serializer))) + ); + return sortedObject; + } + + return token; + } + + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs.meta new file mode 100644 index 000000000..e388a5729 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/AbiData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aeefec83c9be4b18b49185d2d2b527b5 +timeCreated: 1733423116 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/DelayedEncode.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/DelayedEncode.cs index 07e1a7c59..9d4523e6c 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/DelayedEncode.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/DelayedEncode.cs @@ -1,9 +1,11 @@ +using System; using UnityEngine.Scripting; namespace Sequence.EmbeddedWallet { [Preserve] [System.Serializable] + [Obsolete("It is recommended to use SequenceContractCall instead")] public class DelayedEncode : Transaction { public const string TypeIdentifier = "delayedEncode"; diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/IntentDataSendTransaction.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/IntentDataSendTransaction.cs index 96065aefc..7804a6334 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/IntentDataSendTransaction.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/IntentDataSendTransaction.cs @@ -70,6 +70,9 @@ public IntentDataSendTransaction(string code, uint expires, uint issued, string case DelayedEncode.TypeIdentifier: this.transactions[i] = transactions[i].ToObject(); break; + case SequenceContractCall.TypeIdentifier: + this.transactions[i] = transactions[i].ToObject(); + break; default: throw new JsonSerializationException($"Unknown transaction type {typeName} in transaction {i}: {transactions[i]}"); } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs new file mode 100644 index 000000000..413509844 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs @@ -0,0 +1,19 @@ +namespace Sequence.EmbeddedWallet +{ + public class SequenceContractCall : Transaction + { + public const string TypeIdentifier = "contractCall"; + + public AbiData data; + public string to; + public string type = TypeIdentifier; + public string value; + + public SequenceContractCall(string contractAddress, AbiData data, string value = "0") + { + this.to = contractAddress; + this.value = value; + this.data = data; + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs.meta new file mode 100644 index 000000000..3f8c03ca2 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/SequenceContractCall.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 20772fa0084e4acfb5ab720fd37017da +timeCreated: 1733422710 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs new file mode 100644 index 000000000..1f475d1c2 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs @@ -0,0 +1,57 @@ +using System; +using Sequence.ABI; +using Sequence.Contracts; +using UnityEngine; + +namespace Sequence.EmbeddedWallet +{ + public static class TransactionsValidator + { + public static void Validate(Transaction[] transactions) + { + if (transactions == null) + { + throw new ArgumentNullException(nameof(transactions)); + } + int length = transactions.Length; + for (int i = 0; i < length; i++) + { + if (transactions[i] is DelayedEncode delayedEncode) + { + if (!delayedEncode.data.abi.StartsWith('[')) + { + if (!ABIRegex.MatchesFunctionABI(delayedEncode.data.abi)) + { + string message = $"Given {nameof(DelayedEncode)} transaction with function abi {delayedEncode.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""; + Debug.LogWarning(message + "\nAttempting to recover and parse anyways"); + delayedEncode.data.abi = EventParser.ParseEventDef(delayedEncode.data.abi).ToString(); + if (!ABIRegex.MatchesFunctionABI(delayedEncode.data.abi)) + { + throw new ArgumentException(message); + } + } + } + + if (!ABIRegex.MatchesFunctionName(delayedEncode.data.func)) + { + throw new ArgumentException( + $"Given {nameof(DelayedEncode)} transaction with function name {delayedEncode.data.func} that does not match the required regex {ABIRegex.FunctionNameRegex} - for example: \"mint\""); + } + } + else if (transactions[i] is SequenceContractCall contractCall) + { + if (!ABIRegex.MatchesFunctionABI(contractCall.data.abi)) + { + string message = $"Given {nameof(SequenceContractCall)} transaction with function abi {contractCall.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""; + Debug.LogWarning(message + "\nAttempting to recover and parse anyways"); + contractCall.data.abi = EventParser.ParseEventDef(contractCall.data.abi).ToString(); + if (!ABIRegex.MatchesFunctionABI(contractCall.data.abi)) + { + throw new ArgumentException(message); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs.meta new file mode 100644 index 000000000..09cc0de18 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fe15c186a7c149fdb33642024d0debfe +timeCreated: 1727361568 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/EOAWalletToSequenceWalletAdapter.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/EOAWalletToSequenceWalletAdapter.cs index e3798b4a8..aac25bd1a 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/EOAWalletToSequenceWalletAdapter.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/EOAWalletToSequenceWalletAdapter.cs @@ -143,6 +143,11 @@ public async Task SendTransaction(Chain network, Transaction[ } + break; + + case SequenceContractCall tx: + Contract contractToCall = new Contract(tx.to); + transaction = await contractToCall.CallFunction(tx.data.abi, tx.data.args).Create(_client, new ContractCall(_wallet.GetAddress())); break; default: return new FailedTransactionReturn("Error adapting to EthTransaction type. Unable to determine transaction type for:" + transactions[i].ToString(), null); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs index b444f4e3a..67e988b29 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs @@ -87,6 +87,7 @@ public async Task SendTransaction(Chain network, Transaction[ IntentDataSendTransaction args = new IntentDataSendTransaction(_address, network, transactions); try { + TransactionsValidator.Validate(transactions); return await SendTransactionIntent(args, waitForReceipt, timeBeforeExpiry); } catch (Exception e) @@ -265,6 +266,7 @@ public async Task GetFeeOptions(Chain network, Transaction[] IntentDataFeeOptions feeOptions = new IntentDataFeeOptions(network, _address, transactions); try { + TransactionsValidator.Validate(transactions); IntentResponseFeeOptions options = await _intentSender.SendIntent(feeOptions, IntentType.FeeOptions, timeBeforeExpiry); @@ -361,6 +363,7 @@ public async Task SendTransactionWithFeeOptions(Chain network try { + TransactionsValidator.Validate(transactions); int transactionCount = transactions.Length; Transaction[] transactionsWithFeeOption = new Transaction[transactionCount + 1]; transactionsWithFeeOption[0] = feeOption.CreateTransaction(); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs new file mode 100644 index 000000000..f7576ab32 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sequence.ABI +{ + public class EventDef + { + public string TopicHash { get; set; } // the event topic hash, ie. 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + public string Name { get; set; } // the event name, ie. Transfer + public string Sig { get; set; } // the event sig, ie. Transfer(address,address,uint256) + public List ArgTypes { get; set; } = new List(); // the event arg types, ie. [address, address, uint256] + public List ArgNames { get; set; } = new List(); // the event arg names, ie. [from, to, value] or ["","",""] + public List ArgIndexed { get; set; } = new List(); // the event arg indexed flag, ie. [true, false, true] + public int NumIndexed { get; set; } + + public override string ToString() + { + if (!(ArgTypes.Count == ArgIndexed.Count && ArgTypes.Count == ArgNames.Count)) + { + return ""; + } + + var sb = new StringBuilder(); + for (int i = 0; i < ArgTypes.Count; i++) + { + sb.Append(ArgTypes[i]); + if (ArgIndexed[i]) + { + sb.Append(" indexed"); + } + // if (!string.IsNullOrEmpty(ArgNames[i])) + // { + // sb.Append($" {ArgNames[i]}"); + // } + if (i < ArgTypes.Count - 1) + { + sb.Append(","); + } + } + return $"{Name}({sb.ToString()})"; + } + } + + public class EventParser + { + private static Exception ErrInvalid = new ArgumentException("Event format is invalid, expecting Method(arg1,arg2,..)"); + + public static EventDef ParseEventDef(string eventString) + { + var eventDef = new EventDef(); + + if (!eventString.Contains("(") || !eventString.Contains(")")) + { + throw ErrInvalid; + } + + int openParensCount = eventString.Count(c => c == '('); + int closeParensCount = eventString.Count(c => c == ')'); + if (openParensCount != closeParensCount || openParensCount < 1) + { + throw ErrInvalid; + } + + int startIndex = eventString.IndexOf("("); + int endIndex = eventString.LastIndexOf(")"); + + string method = eventString.Substring(0, startIndex).Trim(); + eventDef.Name = method; + + string args = eventString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim(); + + if (string.IsNullOrEmpty(args)) + { + eventDef.Sig = $"{method}()"; + } + else + { + var tree = ParseEventArgs(args, 0); + var (sig, typs, indexed, names) = GroupEventSelectorTree(tree, true); + eventDef.Sig = $"{method}({sig})"; + eventDef.ArgTypes = typs; + eventDef.ArgIndexed = indexed; + eventDef.ArgNames = names.Select((name, index) => string.IsNullOrEmpty(name) ? $"arg{index + 1}" : name).ToList(); + } + + eventDef.NumIndexed = eventDef.ArgIndexed.Count(i => i); + + // Simulating Keccak256 hash and converting it to string. + eventDef.TopicHash = Convert.ToBase64String(Keccak256Hash(Encoding.UTF8.GetBytes(eventDef.Sig))); + + return eventDef; + } + + private static byte[] Keccak256Hash(byte[] input) + { + // Placeholder for Keccak256 hash logic. Use appropriate libraries for real implementation. + return new byte[32]; + } + + private static EventSelectorTree ParseEventArgs(string eventArgs, int iteration) + { + string args = eventArgs.Trim(); + var outTree = new EventSelectorTree(); + + if (string.IsNullOrEmpty(args)) + { + return outTree; + } + + if (!args.EndsWith(",")) + { + args += ","; + } + + int a = args.IndexOf("("); + + string p1 = ""; + string p2 = ""; + string p2ar = ""; + bool p2indexed = false; + string p2name = ""; + string p3 = ""; + + if (a < 0) + { + p1 = args; + } + else + { + p1 = args.Substring(0, a).Trim(); + } + + if (a >= 0) + { + int z = FindParensCloseIndex(args.Substring(a)); + z += a + 1; + + int x = args.Substring(z).IndexOf(","); + if (x > 0) + { + z += x + 1; + } + + p2 = args.Substring(a, z - a).Trim(); + + p3 = args.Substring(z).Trim(); + } + + // Parse p1 (left part) + if (!string.IsNullOrEmpty(p1)) + { + string[] p = p1.Split(','); + string s = ""; + var p1indexed = new List(); + var p1names = new List(); + + foreach (var part1 in p) + { + var arg = part1.Trim().Split(' '); + + if (arg.Length > 3) + { + throw new Exception("Invalid event argument format"); + } + + if (arg.Length == 3 && arg[1] == "indexed") + { + p1indexed.Add(true); + p1names.Add(arg[2]); + } + else if (arg.Length == 3 && arg[1] != "indexed") + { + throw new Exception("Invalid event indexed argument format"); + } + else if (arg.Length == 2 && arg[1] == "indexed") + { + p1indexed.Add(true); + p1names.Add(""); + } + else if (arg.Length > 0 && !string.IsNullOrEmpty(arg[0])) + { + p1indexed.Add(false); + p1names.Add(arg.Length > 1 ? arg[1] : ""); + } + + string typ = arg[0].Trim(); + if (!string.IsNullOrEmpty(typ)) + { + s += typ + ","; + } + } + if (!string.IsNullOrEmpty(s)) + { + s = s.TrimEnd(','); + } + + outTree.Left = s; + outTree.Indexed = p1indexed; + outTree.Names = p1names; + } + + // Parse p2 (tuple) + if (!string.IsNullOrEmpty(p2)) + { + outTree.Tuple.Add(ParseEventArgs(p2, iteration + 1)); + } + + // Parse p3 (right part) + if (!string.IsNullOrEmpty(p3)) + { + outTree.Right.Add(ParseEventArgs(p3, iteration + 1)); + } + + return outTree; + } + + private static (string, List, List, List) GroupEventSelectorTree(EventSelectorTree t, bool include) + { + var outStr = new StringBuilder(); + var typs = new List(); + var indexed = new List(); + var names = new List(); + + if (!string.IsNullOrEmpty(t.Left)) + { + outStr.Append(t.Left + ","); + if (include) + { + typs.AddRange(t.Left.Split(',').Where(s => !string.IsNullOrEmpty(s))); + indexed.AddRange(t.Indexed); + names.AddRange(t.Names); + } + } + + foreach (var child in t.Tuple) + { + var (s, _, _, _) = GroupEventSelectorTree(child, false); + if (!string.IsNullOrEmpty(s)) + { + outStr.Append($"({s}),"); + } + } + + foreach (var child in t.Right) + { + var (s, rTyps, rIndexed, rNames) = GroupEventSelectorTree(child, true); + if (!string.IsNullOrEmpty(s)) + { + outStr.Append(s + ","); + } + + typs.AddRange(rTyps); + indexed.AddRange(rIndexed); + names.AddRange(rNames); + } + + return (outStr.ToString().TrimEnd(','), typs, indexed, names); + } + + private static int FindParensCloseIndex(string args) + { + int n = 0; + for (int i = 0; i < args.Length; i++) + { + if (args[i] == '(') + { + n++; + } + else if (args[i] == ')') + { + n--; + if (n == 0) + { + return i; + } + } + } + throw new Exception("Invalid function args, no closing parenthesis found"); + } + } + + public class EventSelectorTree + { + public string Left { get; set; } + public List Indexed { get; set; } = new List(); + public List Names { get; set; } = new List(); + public List Tuple { get; set; } = new List(); + public string TupleArray { get; set; } + public bool TupleIndexed { get; set; } + public string TupleName { get; set; } + public List Right { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs.meta new file mode 100644 index 000000000..1968b2229 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 130f1231e942448282c18e9e837f76a1 +timeCreated: 1728412559 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs new file mode 100644 index 000000000..ea303d974 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs @@ -0,0 +1,19 @@ +namespace Sequence.Contracts +{ + public static class ABIRegex + { + + public const string FunctionNameRegex = @"^[A-Z|a-z|_,-|0-9]+$"; + public const string FunctionABIRegex = @"^[A-Z|a-z|_,-|0-9]+\(([A-Z|a-z|0-9]+(, *[A-Z|a-z|0-9]+)*)?\)$"; + + public static bool MatchesFunctionName(string input) + { + return System.Text.RegularExpressions.Regex.IsMatch(input, FunctionNameRegex); + } + + public static bool MatchesFunctionABI(string input) + { + return System.Text.RegularExpressions.Regex.IsMatch(input, FunctionABIRegex); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs.meta new file mode 100644 index 000000000..0f33fce37 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ddd744caaf864673b1260bc3dfa06363 +timeCreated: 1727360449 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs index 814aa0a34..e18f51f6b 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs @@ -7,6 +7,7 @@ using Sequence.Provider; using Sequence.Transactions; using System.Text; +using System.Text.RegularExpressions; using Sequence.Wallet; using UnityEngine; @@ -17,8 +18,8 @@ public class Contract Address address; public delegate Task QueryContractMessageSender(IEthClient client); - private string abi; - private FunctionAbi functionAbi; + private string _abi; + private FunctionAbi _functionAbi; /// /// Will throw if given an invalid abi or contractAddress @@ -31,15 +32,15 @@ public class Contract public Contract(string contractAddress, string abi = null) { address = new Address(contractAddress); - this.abi = abi; + this._abi = abi; if (abi == null) { Debug.LogWarning("Creating a contract with a null ABI is not recommended. Note: Using a null abi will require you to provide the full function signature when transacting/querying the contract. Using a null abi will cause all query responses to return as a string."); - this.functionAbi = null; + this._functionAbi = null; } else { - this.functionAbi = ABI.ABI.DecodeAbi(abi); + this._functionAbi = ABI.ABI.DecodeAbi(abi); } } @@ -61,10 +62,37 @@ public async Task Deploy(string bytecode, params object[] constructorArg /// public string AssembleCallData(string functionName, params object[] functionArgs) { + functionName = ValidateFunctionNameRegex(functionName); return GetData(functionName, functionArgs); } - + private string ValidateFunctionNameRegex(string functionName) + { + if (string.IsNullOrWhiteSpace(_abi)) + { + if (!ABIRegex.MatchesFunctionABI(functionName)) + { + string message = + $"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""; + Debug.LogWarning(message + "\nAttempting to recover and parse anyways"); + functionName = EventParser.ParseEventDef(functionName).ToString(); + if (!ABIRegex.MatchesFunctionABI(functionName)) + { + throw new ArgumentException(message); + } + } + } + else + { + if (!ABIRegex.MatchesFunctionName(functionName)) + { + throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionNameRegex} - for example: \"mint\""); + } + } + + return functionName; + } + /// /// Create a CallContractFunction for the given functionName and functionArgs /// @@ -73,6 +101,7 @@ public string AssembleCallData(string functionName, params object[] functionArgs /// public CallContractFunction CallFunction(string functionName, params object[] functionArgs) { + functionName = ValidateFunctionNameRegex(functionName); string callData = GetData(functionName, functionArgs); return new CallContractFunction(callData, address); } @@ -88,7 +117,8 @@ public CallContractFunction CallFunction(string functionName, params object[] fu /// public QueryContractMessageSender QueryContract(string functionName, params object[] args) { - if (this.functionAbi == null) + functionName = ValidateFunctionNameRegex(functionName); + if (this._functionAbi == null) { // Return string, throw exception is anything else is provided as T if (typeof(T) != typeof(string)) @@ -104,7 +134,7 @@ public QueryContractMessageSender QueryContract(string functionName, param { // Instead, get the string response, then use ABI (define a method in FunctionABI) to decode the hex string into the correct datatype string result = await client.CallContract(toSendParams); - return this.functionAbi.DecodeReturnValue(result, functionName, args); + return this._functionAbi.DecodeReturnValue(result, functionName, args); }; } @@ -133,10 +163,10 @@ private object[] CreateParams(string functionName, params object[] args) private string GetData(string functionName, params object[] args) { string data; - if (this.functionAbi != null) + if (this._functionAbi != null) { - int abiIndex = this.functionAbi.GetFunctionAbiIndex(functionName, args); - data = ABI.ABI.Pack(this.functionAbi.GetFunctionSignature(functionName, abiIndex), args); + int abiIndex = this._functionAbi.GetFunctionAbiIndex(functionName, args); + data = ABI.ABI.Pack(this._functionAbi.GetFunctionSignature(functionName, abiIndex), args); } else { diff --git a/Packages/Sequence-Unity/package.json b/Packages/Sequence-Unity/package.json index f4488c268..cd5bbd650 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "3.16.0", + "version": "3.17.0", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for the Sequence WaaS API", "unity": "2021.3",