Skip to content

Commit 6f3903c

Browse files
Feature/contract call (#220)
* 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 * Clearer error messages with examples * Attempt to recover from improper format * Added SequenceContractCall transaction type for waas and marked delayed encode obsolete in response * Fix test
1 parent ad1197f commit 6f3903c

36 files changed

Lines changed: 907 additions & 20 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using NUnit.Framework;
2+
using Sequence.Contracts;
3+
using Assert = UnityEngine.Assertions.Assert;
4+
5+
namespace Sequence.Ethereum.Tests
6+
{
7+
public class ABIRegexTests
8+
{
9+
[TestCase("", false)]
10+
[TestCase("functionName", true)]
11+
[TestCase("functionName()", false)]
12+
[TestCase("functionName123", true)]
13+
[TestCase("functionName_s", true)]
14+
[TestCase("function-Name", true)]
15+
[TestCase("functionName ", false)]
16+
public void TestMatchesFunctionName(string input, bool expected)
17+
{
18+
bool result = ABIRegex.MatchesFunctionName(input);
19+
Assert.AreEqual(expected, result);
20+
}
21+
22+
[TestCase("", false)]
23+
[TestCase("functionName", false)]
24+
[TestCase("functionName(", false)]
25+
[TestCase("functionName()", true)]
26+
[TestCase("functionName(a)", true)]
27+
[TestCase("functionName(a a)", false)]
28+
[TestCase("functionName(a, a)", true)]
29+
[TestCase("functionName(a,a)", true)]
30+
[TestCase("functionName(,)", false)]
31+
[TestCase("functionName(Aa123)", true)]
32+
[TestCase("functionName(a,)", false)]
33+
[TestCase("functionName() ", false)]
34+
[TestCase("function_-123Name()", true)]
35+
public void TestMatchesFunctionABI(string input, bool expected)
36+
{
37+
bool result = ABIRegex.MatchesFunctionABI(input);
38+
Assert.AreEqual(expected, result);
39+
}
40+
}
41+
}

Assets/SequenceSDK/Ethereum/Tests/ABIRegexTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/SequenceSDK/Ethereum/Tests/ContractIntegrationTests.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,121 @@ public async Task TestComplexContract()
9393
CollectionAssert.AreEqual(expectedMoreBytes.Data, (byte[])resultPart2[3]);
9494
CollectionAssert.AreEqual(expectedAddresses, resultPart2[4].ConvertToTArray<Address, object>());
9595
}
96+
97+
[Test]
98+
public void TestInvalidRegex_noABI()
99+
{
100+
Contract complexContract = new Contract(complexContractAddress);
101+
102+
try
103+
{
104+
complexContract.QueryContract<string>("functionName");
105+
Assert.Fail("Expected exception but none was thrown");
106+
}
107+
catch (ArgumentException e)
108+
{
109+
110+
}
111+
catch (Exception e)
112+
{
113+
Assert.Fail("Expected argument exception");
114+
}
115+
116+
try
117+
{
118+
complexContract.QueryContract<string>("functionName(");
119+
Assert.Fail("Expected exception but none was thrown");
120+
}
121+
catch (ArgumentException e)
122+
{
123+
124+
}
125+
catch (Exception e)
126+
{
127+
Assert.Fail("Expected argument exception");
128+
}
129+
130+
try
131+
{
132+
complexContract.QueryContract<string>("functionName)");
133+
Assert.Fail("Expected exception but none was thrown");
134+
}
135+
catch (ArgumentException e)
136+
{
137+
138+
}
139+
catch (Exception e)
140+
{
141+
Assert.Fail("Expected argument exception");
142+
}
143+
144+
try
145+
{
146+
complexContract.AssembleCallData("functionName(uint banana)", 1);
147+
}
148+
catch (Exception e)
149+
{
150+
Assert.Fail("No exception expected");
151+
}
152+
153+
154+
try
155+
{
156+
complexContract.CallFunction("functionName(uint, uint)", 1, 1);
157+
}
158+
catch (Exception e)
159+
{
160+
Assert.Fail("No exception expected");
161+
}
162+
}
163+
164+
[Test]
165+
public void TestInvalidRegex_withABI()
166+
{
167+
Contract complexContract = new Contract(complexContractAddress, complexContractAbi);
168+
169+
try
170+
{
171+
complexContract.QueryContract<string>("functionName()");
172+
Assert.Fail("Expected exception but none was thrown");
173+
}
174+
catch (ArgumentException e)
175+
{
176+
177+
}
178+
catch (Exception e)
179+
{
180+
Assert.Fail("Expected argument exception");
181+
}
182+
183+
try
184+
{
185+
complexContract.AssembleCallData("functionName(uint banana)");
186+
Assert.Fail("Expected exception but none was thrown");
187+
}
188+
catch (ArgumentException e)
189+
{
190+
191+
}
192+
catch (Exception e)
193+
{
194+
Assert.Fail("Expected argument exception");
195+
}
196+
197+
198+
try
199+
{
200+
complexContract.CallFunction("functionName*");
201+
Assert.Fail("Expected exception but none was thrown");
202+
}
203+
catch (ArgumentException e)
204+
{
205+
206+
}
207+
catch (Exception e)
208+
{
209+
Assert.Fail("Expected argument exception");
210+
}
211+
}
96212
}
97213
}

Assets/SequenceSDK/Ethereum/Tests/WalletTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public async Task TestChain_ERC20Mock_MockMint_Test()
178178
EOAWallet wallet2 = new EOAWallet("0xabc0000000000000000000000000000000000000000000000000000000000002");
179179
Contract mockERC20 = new Contract(receipt.contractAddress);
180180
string result = await mockERC20.SendTransactionMethod(wallet2, client, 0,
181-
"mockMint(address , uint256)",
181+
"mockMint(address, uint256)",
182182
wallet2.GetAddress(), 1);
183183
Assert.IsNotNull(result);
184184
}

Assets/SequenceSDK/WaaS/Tests/DelayedEncodeJsonSerializationTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,23 @@ public void TestDelayedEncodeDataArgsGetsSerializedAlphabetically()
5252

5353
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);
5454
}
55+
56+
[Test]
57+
public void TestAbiDataArgsGetsSerializedAlphabetically()
58+
{
59+
AbiData testData = new AbiData("testAbi(string,ComplexObjectInNonAlphabeticalOrder,int)",
60+
new object[] { "some string", new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"),
61+
BigInteger.One, 5, new object[] { 1, new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"), null, "banana" },
62+
new ComplexObjectInNonAlphabeticalOrder[] { new ("last", "first", "halfway"), null },
63+
new object[] { 1, new object[] { 2, new ComplexObjectInNonAlphabeticalOrder("last", "first", "halfway"), 3,
64+
new ExtraComplexObjectWithComplexObjectArray("last", new ComplexObjectInNonAlphabeticalOrder[] { null,
65+
new ("last", "first", "halfway")}, "halfway")}, "word"}
66+
});
67+
68+
string json = JsonConvert.SerializeObject(testData);
69+
Debug.Log(json);
70+
71+
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);
72+
}
5573
}
5674
}

Assets/SequenceSDK/WaaS/Tests/WaaSWalletUnitTests.cs

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public async Task TestSendTransactionSuccessEvent()
2929
failedEventHit = true;
3030
};
3131

32-
await wallet.SendTransaction(Chain.None, null);
32+
await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>());
3333

3434
Assert.IsTrue(successEventHit);
3535
Assert.IsFalse(failedEventHit);
@@ -52,12 +52,117 @@ public async Task TestSendTransactionFailedEvent()
5252
failedEventHit = true;
5353
};
5454

55-
await wallet.SendTransaction(Chain.None, null);
55+
await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>());
5656

5757
Assert.IsFalse(successEventHit);
5858
Assert.IsTrue(failedEventHit);
5959
}
6060

61+
private static (DelayedEncodeData, bool)[] _invalidDelayedEncodeData = new[]
62+
{
63+
(new DelayedEncodeData("functionName", Array.Empty<object>(), "functionName"), false),
64+
(new DelayedEncodeData("functionName()", Array.Empty<object>(), "functionName"), true),
65+
(new DelayedEncodeData("functionName()", Array.Empty<object>(), "functionName()"), false),
66+
(new DelayedEncodeData("functionName(", Array.Empty<object>(), "functionName"), false),
67+
(new DelayedEncodeData("functionName)", Array.Empty<object>(), "functionName"), false),
68+
(new DelayedEncodeData("functionName(uint banana)", new object[] {1}, "functionName"), true),
69+
(new DelayedEncodeData("functionName(uint, uint)", new object[]{1,2}, "functionName"), true),
70+
(new DelayedEncodeData("functionName(uint,uint)", new object[]{1,2}, "functionName(uint,uint)"), false),
71+
(new DelayedEncodeData("functionName(uint banana", new object[] {1}, "functionName"), false),
72+
(new DelayedEncodeData("functionNameuint, uint)", new object[]{1,2}, "functionName"), false),
73+
(new DelayedEncodeData("functionName(uint,uint", new object[]{1,2}, "functionName"), false)
74+
};
75+
76+
[TestCaseSource(nameof(_invalidDelayedEncodeData))]
77+
public async Task TestSendTransactionEvent_delayedEncodeValidation((DelayedEncodeData, bool) args)
78+
{
79+
DelayedEncodeData data = args.Item1;
80+
bool success = args.Item2;
81+
IIntentSender intentSender = new MockIntentSender(new SuccessfulTransactionReturn());
82+
SequenceWallet wallet = new SequenceWallet(address, "", intentSender);
83+
84+
bool successEventHit = false;
85+
wallet.OnSendTransactionComplete += (result)=>
86+
{
87+
successEventHit = true;
88+
};
89+
bool failedEventHit = false;
90+
wallet.OnSendTransactionFailed += (result)=>
91+
{
92+
failedEventHit = true;
93+
};
94+
95+
await wallet.SendTransaction(Chain.None, new Transaction[]
96+
{
97+
new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249"),
98+
new DelayedEncode("0xc683a014955b75F5ECF991d4502427c8fa1Aa249", "0", data),
99+
new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249")
100+
}, waitForReceipt: false);
101+
102+
if (!success)
103+
{
104+
Assert.IsFalse(successEventHit);
105+
Assert.IsTrue(failedEventHit);
106+
}
107+
else
108+
{
109+
Assert.IsTrue(successEventHit);
110+
Assert.IsFalse(failedEventHit);
111+
}
112+
}
113+
114+
private static (AbiData, bool)[] _invalidContractCallData = new[]
115+
{
116+
(new AbiData("functionName", Array.Empty<object>()), false),
117+
(new AbiData("functionName()", Array.Empty<object>()), true),
118+
(new AbiData("functionName(", Array.Empty<object>()), false),
119+
(new AbiData("functionName)", Array.Empty<object>()), false),
120+
(new AbiData("functionName(uint banana)", new object[] {1}), true),
121+
(new AbiData("functionName(uint, uint)", new object[]{1,2}), true),
122+
(new AbiData("functionName(uint,uint)", new object[]{1,2}), true),
123+
(new AbiData("functionName(uint banana", new object[] {1}), false),
124+
(new AbiData("functionNameuint, uint)", new object[]{1,2}), false),
125+
(new AbiData("functionName(uint,uint", new object[]{1,2}), false)
126+
};
127+
128+
[TestCaseSource(nameof(_invalidContractCallData))]
129+
public async Task TestSendTransactionEvent_contractCallValidation((AbiData, bool) args)
130+
{
131+
AbiData data = args.Item1;
132+
bool success = args.Item2;
133+
IIntentSender intentSender = new MockIntentSender(new SuccessfulTransactionReturn());
134+
SequenceWallet wallet = new SequenceWallet(address, "", intentSender);
135+
136+
bool successEventHit = false;
137+
wallet.OnSendTransactionComplete += (result)=>
138+
{
139+
successEventHit = true;
140+
};
141+
bool failedEventHit = false;
142+
wallet.OnSendTransactionFailed += (result)=>
143+
{
144+
failedEventHit = true;
145+
};
146+
147+
await wallet.SendTransaction(Chain.None, new Transaction[]
148+
{
149+
new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249"),
150+
new SequenceContractCall("0xc683a014955b75F5ECF991d4502427c8fa1Aa249", data),
151+
new RawTransaction("0xc683a014955b75F5ECF991d4502427c8fa1Aa249")
152+
}, waitForReceipt: false);
153+
154+
if (!success)
155+
{
156+
Assert.IsFalse(successEventHit);
157+
Assert.IsTrue(failedEventHit);
158+
}
159+
else
160+
{
161+
Assert.IsTrue(successEventHit);
162+
Assert.IsFalse(failedEventHit);
163+
}
164+
}
165+
61166
[Test]
62167
public async Task TestSendTransactionException()
63168
{
@@ -75,7 +180,7 @@ public async Task TestSendTransactionException()
75180
failedEventHit = true;
76181
};
77182

78-
await wallet.SendTransaction(Chain.None, null);
183+
await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>());
79184

80185
Assert.IsFalse(successEventHit);
81186
Assert.IsTrue(failedEventHit);
@@ -343,7 +448,7 @@ public async Task TestWaitForTransactionReceipt(int numberOfFetches)
343448
failedEventHit = true;
344449
};
345450

346-
var result = await wallet.SendTransaction(Chain.None, null);
451+
var result = await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>());
347452

348453
Assert.IsTrue(successEventHit);
349454
Assert.IsFalse(failedEventHit);
@@ -392,7 +497,7 @@ public async Task TestWaitForTransactionReceipt_failedToFetch()
392497

393498
LogAssert.Expect(LogType.Error, "Transaction was successful, but we're unable to obtain the transaction hash. Reason: some random error");
394499

395-
var result = await wallet.SendTransaction(Chain.None, null);
500+
var result = await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>());
396501

397502
Assert.IsTrue(successEventHit);
398503
Assert.IsFalse(failedEventHit);
@@ -438,7 +543,7 @@ public async Task TestDontWaitForTransactionReceipt(int numberOfFetches)
438543
failedEventHit = true;
439544
};
440545

441-
var result = await wallet.SendTransaction(Chain.None, null, false);
546+
var result = await wallet.SendTransaction(Chain.None, Array.Empty<Transaction>(), false);
442547

443548
Assert.IsTrue(successEventHit);
444549
Assert.IsFalse(failedEventHit);

0 commit comments

Comments
 (0)