From 7c29fe1cc97c86d9e5853d7ddce5af42942904e4 Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Thu, 26 Sep 2024 11:47:48 -0400 Subject: [PATCH 1/4] Validate ABI function name and signatures so that we throw an argument exception and make it easier to diagnose a common issue where users were providing an ABI in the wrong format --- .../Ethereum/Tests/ABIRegexTests.cs | 41 ++++++++ .../Ethereum/Tests/ABIRegexTests.cs.meta | 3 + .../Tests/ContractIntegrationTests.cs | 98 +++++++++++++++++++ .../SequenceSDK/Ethereum/Tests/WalletTests.cs | 2 +- .../WaaS/Tests/WaaSWalletUnitTests.cs | 49 ++++++++-- .../ParameterTypes/TransactionsValidator.cs | 36 +++++++ .../TransactionsValidator.cs.meta | 3 + .../EmbeddedWallet/SequenceWallet.cs | 3 + .../SequenceSDK/Ethereum/Contract/ABIRegex.cs | 19 ++++ .../Ethereum/Contract/ABIRegex.cs.meta | 3 + .../SequenceSDK/Ethereum/Contract/Contract.cs | 43 +++++--- Packages/Sequence-Unity/package.json | 2 +- 12 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs create mode 100644 Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs.meta 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 9b8093727..e202fbe91 100644 --- a/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs +++ b/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs @@ -93,5 +93,103 @@ 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.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(uint, uint)"); + Assert.Fail("Expected exception but none was thrown"); + } + catch (ArgumentException e) + { + + } + catch (Exception e) + { + Assert.Fail("Expected argument exception"); + } + } + + [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/WaaSWalletUnitTests.cs b/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs index 364e8fa8a..c83370ebc 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,7 +52,44 @@ 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[] _invalidDelayedEncodeData = new[] + { + new DelayedEncodeData("functionName", Array.Empty(), "functionName"), + new DelayedEncodeData("functionName()", Array.Empty(), "functionName()"), + new DelayedEncodeData("functionName(uint banana)", Array.Empty(), "functionName"), + new DelayedEncodeData("functionName(uint, uint)", Array.Empty(), "functionName"), + new DelayedEncodeData("functionName(uint,uint)", Array.Empty(), "functionName(uint,uint)") + }; + + [TestCaseSource(nameof(_invalidDelayedEncodeData))] + public async Task TestSendTransactionFailedEvent_invalidDelayedEncode(DelayedEncodeData data) + { + IIntentSender intentSender = new MockIntentSender(new FailedTransactionReturn("",null,null)); + 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") + }); Assert.IsFalse(successEventHit); Assert.IsTrue(failedEventHit); @@ -75,7 +112,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 +380,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 +429,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 +475,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/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..24b6cbdd0 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs @@ -0,0 +1,36 @@ +using System; +using Sequence.Contracts; + +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)) + { + throw new ArgumentException($"Given {nameof(DelayedEncode)} transaction with function abi {delayedEncode.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex}"); + } + } + + 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}"); + } + } + } + } + } +} \ 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/SequenceWallet.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs index 19cc86591..878af9044 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceWallet.cs @@ -85,6 +85,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) @@ -263,6 +264,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); @@ -359,6 +361,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/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..9560f9996 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,28 @@ public async Task Deploy(string bytecode, params object[] constructorArg /// public string AssembleCallData(string functionName, params object[] functionArgs) { + ValidateFunctionNameRegex(functionName); return GetData(functionName, functionArgs); } - + private void ValidateFunctionNameRegex(string functionName) + { + if (string.IsNullOrWhiteSpace(_abi)) + { + if (!ABIRegex.MatchesFunctionABI(functionName)) + { + throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionABIRegex}"); + } + } + else + { + if (!ABIRegex.MatchesFunctionName(functionName)) + { + throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionNameRegex}"); + } + } + } + /// /// Create a CallContractFunction for the given functionName and functionArgs /// @@ -73,6 +92,7 @@ public string AssembleCallData(string functionName, params object[] functionArgs /// public CallContractFunction CallFunction(string functionName, params object[] functionArgs) { + ValidateFunctionNameRegex(functionName); string callData = GetData(functionName, functionArgs); return new CallContractFunction(callData, address); } @@ -88,7 +108,8 @@ public CallContractFunction CallFunction(string functionName, params object[] fu /// public QueryContractMessageSender QueryContract(string functionName, params object[] args) { - if (this.functionAbi == null) + ValidateFunctionNameRegex(functionName); + if (this._functionAbi == null) { // Return string, throw exception is anything else is provided as T if (typeof(T) != typeof(string)) @@ -104,7 +125,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 +154,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 74c5c70c8..ca9d07590 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "3.8.0", + "version": "3.8.1", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for the Sequence WaaS API", "unity": "2021.3", From 47b247415f54dfaa154d0da51c1360e5103713a4 Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Thu, 26 Sep 2024 12:34:45 -0400 Subject: [PATCH 2/4] Clearer error messages with examples --- .../DataTypes/ParameterTypes/TransactionsValidator.cs | 4 ++-- .../Sequence/SequenceSDK/Ethereum/Contract/Contract.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs index 24b6cbdd0..ffea4a021 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs @@ -20,14 +20,14 @@ public static void Validate(Transaction[] transactions) { if (!ABIRegex.MatchesFunctionABI(delayedEncode.data.abi)) { - throw new ArgumentException($"Given {nameof(DelayedEncode)} transaction with function abi {delayedEncode.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex}"); + throw new ArgumentException($"Given {nameof(DelayedEncode)} transaction with function abi {delayedEncode.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""); } } 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}"); + $"Given {nameof(DelayedEncode)} transaction with function name {delayedEncode.data.func} that does not match the required regex {ABIRegex.FunctionNameRegex} - for example: \"mint\""); } } } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs index 9560f9996..ed77f3458 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs @@ -72,14 +72,14 @@ private void ValidateFunctionNameRegex(string functionName) { if (!ABIRegex.MatchesFunctionABI(functionName)) { - throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionABIRegex}"); + throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""); } } else { if (!ABIRegex.MatchesFunctionName(functionName)) { - throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionNameRegex}"); + throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionNameRegex} - for example: \"mint\""); } } } From e291767ca2b1cfd681a2a2274085f3fa85e82c7e Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Tue, 8 Oct 2024 16:19:26 -0400 Subject: [PATCH 3/4] Attempt to recover from improper format --- .../Tests/ContractIntegrationTests.cs | 26 +- .../WaaS/Tests/WaaSWalletUnitTests.cs | 40 ++- Packages/Sequence-Unity/Sequence/Samples | 1 - .../{Samples~ => Samples}/DemoScene.meta | 0 .../DemoScene/Demo.unity | 0 .../DemoScene/Demo.unity.meta | 0 .../{Samples~ => Samples}/Scripts.meta | 0 .../Scripts/SequenceConnector.cs | 0 .../Scripts/SequenceConnector.cs.meta | 0 .../Scripts/SequenceConnector.prefab | 0 .../Scripts/SequenceConnector.prefab.meta | 0 .../Sequence/{Samples~ => Samples}/Setup.meta | 0 .../Setup/Resources.meta | 0 .../Setup/Resources/SequenceConfig.asset | 0 .../Setup/Resources/SequenceConfig.asset.meta | 0 .../ParameterTypes/TransactionsValidator.cs | 10 +- .../Ethereum/ABI/ABIInputParser.cs | 295 ++++++++++++++++++ .../Ethereum/ABI/ABIInputParser.cs.meta | 3 + .../SequenceSDK/Ethereum/Contract/Contract.cs | 19 +- 19 files changed, 371 insertions(+), 23 deletions(-) delete mode 120000 Packages/Sequence-Unity/Sequence/Samples rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/DemoScene.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/DemoScene/Demo.unity (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/DemoScene/Demo.unity.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Scripts.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Scripts/SequenceConnector.cs (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Scripts/SequenceConnector.cs.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Scripts/SequenceConnector.prefab (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Scripts/SequenceConnector.prefab.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Setup.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Setup/Resources.meta (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Setup/Resources/SequenceConfig.asset (100%) rename Packages/Sequence-Unity/Sequence/{Samples~ => Samples}/Setup/Resources/SequenceConfig.asset.meta (100%) create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABIInputParser.cs.meta diff --git a/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs b/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs index e202fbe91..6a03b625a 100644 --- a/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs +++ b/Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs @@ -115,7 +115,7 @@ public void TestInvalidRegex_noABI() try { - complexContract.AssembleCallData("functionName(uint banana)"); + complexContract.QueryContract("functionName("); Assert.Fail("Expected exception but none was thrown"); } catch (ArgumentException e) @@ -126,11 +126,10 @@ public void TestInvalidRegex_noABI() { Assert.Fail("Expected argument exception"); } - - + try { - complexContract.CallFunction("functionName(uint, uint)"); + complexContract.QueryContract("functionName)"); Assert.Fail("Expected exception but none was thrown"); } catch (ArgumentException e) @@ -141,6 +140,25 @@ public void TestInvalidRegex_noABI() { 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] diff --git a/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs b/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs index c83370ebc..77c37639d 100644 --- a/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs +++ b/Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs @@ -58,19 +58,27 @@ public async Task TestSendTransactionFailedEvent() Assert.IsTrue(failedEventHit); } - private static DelayedEncodeData[] _invalidDelayedEncodeData = new[] + private static (DelayedEncodeData, bool)[] _invalidDelayedEncodeData = new[] { - new DelayedEncodeData("functionName", Array.Empty(), "functionName"), - new DelayedEncodeData("functionName()", Array.Empty(), "functionName()"), - new DelayedEncodeData("functionName(uint banana)", Array.Empty(), "functionName"), - new DelayedEncodeData("functionName(uint, uint)", Array.Empty(), "functionName"), - new DelayedEncodeData("functionName(uint,uint)", Array.Empty(), "functionName(uint,uint)") + (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 TestSendTransactionFailedEvent_invalidDelayedEncode(DelayedEncodeData data) + public async Task TestSendTransactionEvent_delayedEncodeValidation((DelayedEncodeData, bool) args) { - IIntentSender intentSender = new MockIntentSender(new FailedTransactionReturn("",null,null)); + DelayedEncodeData data = args.Item1; + bool success = args.Item2; + IIntentSender intentSender = new MockIntentSender(new SuccessfulTransactionReturn()); SequenceWallet wallet = new SequenceWallet(address, "", intentSender); bool successEventHit = false; @@ -89,10 +97,18 @@ public async Task TestSendTransactionFailedEvent_invalidDelayedEncode(DelayedEnc new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249"), new DelayedEncode("0xc683a014955b75F5ECF991d4502427c8fa1Aa249", "0", data), new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249") - }); - - Assert.IsFalse(successEventHit); - Assert.IsTrue(failedEventHit); + }, waitForReceipt: false); + + if (!success) + { + Assert.IsFalse(successEventHit); + Assert.IsTrue(failedEventHit); + } + else + { + Assert.IsTrue(successEventHit); + Assert.IsFalse(failedEventHit); + } } [Test] 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/TransactionsValidator.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs index ffea4a021..c90e2a2ba 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/DataTypes/ParameterTypes/TransactionsValidator.cs @@ -1,5 +1,7 @@ using System; +using Sequence.ABI; using Sequence.Contracts; +using UnityEngine; namespace Sequence.EmbeddedWallet { @@ -20,7 +22,13 @@ public static void Validate(Transaction[] transactions) { if (!ABIRegex.MatchesFunctionABI(delayedEncode.data.abi)) { - throw new ArgumentException($"Given {nameof(DelayedEncode)} transaction with function abi {delayedEncode.data.abi} that does not match the required regex {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""); + 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); + } } } 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/Contract.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs index ed77f3458..e18f51f6b 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs @@ -62,17 +62,24 @@ public async Task Deploy(string bytecode, params object[] constructorArg /// public string AssembleCallData(string functionName, params object[] functionArgs) { - ValidateFunctionNameRegex(functionName); + functionName = ValidateFunctionNameRegex(functionName); return GetData(functionName, functionArgs); } - private void ValidateFunctionNameRegex(string functionName) + private string ValidateFunctionNameRegex(string functionName) { if (string.IsNullOrWhiteSpace(_abi)) { if (!ABIRegex.MatchesFunctionABI(functionName)) { - throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionABIRegex} - for example: \"mint(uint256,uint256)\""); + 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 @@ -82,6 +89,8 @@ private void ValidateFunctionNameRegex(string functionName) throw new ArgumentException($"Given invalid {nameof(functionName)}, given: {functionName}; expected to regex match {ABIRegex.FunctionNameRegex} - for example: \"mint\""); } } + + return functionName; } /// @@ -92,7 +101,7 @@ private void ValidateFunctionNameRegex(string functionName) /// public CallContractFunction CallFunction(string functionName, params object[] functionArgs) { - ValidateFunctionNameRegex(functionName); + functionName = ValidateFunctionNameRegex(functionName); string callData = GetData(functionName, functionArgs); return new CallContractFunction(callData, address); } @@ -108,7 +117,7 @@ public CallContractFunction CallFunction(string functionName, params object[] fu /// public QueryContractMessageSender QueryContract(string functionName, params object[] args) { - ValidateFunctionNameRegex(functionName); + functionName = ValidateFunctionNameRegex(functionName); if (this._functionAbi == null) { // Return string, throw exception is anything else is provided as T From 453d52bcd66e264db5d9c21afdbe74bcc324552f Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Fri, 13 Dec 2024 14:24:30 -0500 Subject: [PATCH 4/4] Fix regex --- Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs | 4 ++++ .../Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs index 831848255..a461b4d3d 100644 --- a/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs +++ b/Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs @@ -32,6 +32,10 @@ public void TestMatchesFunctionName(string input, bool expected) [TestCase("functionName(a,)", false)] [TestCase("functionName() ", false)] [TestCase("function_-123Name()", true)] + [TestCase("function_-123Name(int[])", true)] + [TestCase("functionName(a[],a)", true)] + [TestCase("functionName(a[5],a)", true)] + [TestCase("functionName(a[][],a)", true)] public void TestMatchesFunctionABI(string input, bool expected) { bool result = ABIRegex.MatchesFunctionABI(input); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs index ea303d974..0cfbcdf00 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ABIRegex.cs @@ -4,7 +4,7 @@ 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 const string FunctionABIRegex = @"^[A-Z|a-z|_,-|0-9]+\(([A-Za-z0-9\[\]]+(, *[A-Za-z0-9\[\]]+)*)?\)$"; public static bool MatchesFunctionName(string input) {