Skip to content

Commit 773b96a

Browse files
erikzhangshargonvncoelhoAshuaidehaoJim8y
authored
Check ABI for notifications (#2810)
* Check ABI for notifications * Patch checkabi (#2884) * Remove extra space (#2878) * Add Hardfork for StrictMode (#2881) * init * rename * hardfork * Update ApplicationEngine.Runtime.cs --------- Co-authored-by: Shargon <shargon@gmail.com> * Check UTF8 string * Cleaner way * Fix Buffer * Update src/Neo/SmartContract/ApplicationEngine.Runtime.cs Co-authored-by: Anna Shaleva <anna@nspcc.ru> --------- Co-authored-by: Shargon <shargon@gmail.com> Co-authored-by: Vitor Nazário Coelho <vncoelho@gmail.com> Co-authored-by: Shine Li <bfshm@qq.com> Co-authored-by: Jimmy <jinghui@wayne.edu> Co-authored-by: Anna Shaleva <anna@nspcc.ru>
1 parent d63eb1d commit 773b96a

File tree

3 files changed

+169
-13
lines changed

3 files changed

+169
-13
lines changed

src/Neo/SmartContract/ApplicationEngine.Runtime.cs

+88-5
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
// Redistribution and use in source and binary forms with or without
99
// modifications are permitted.
1010

11+
using System;
12+
using System.Collections.Generic;
13+
using System.IO;
14+
using System.Linq;
15+
using System.Numerics;
1116
using Neo.Cryptography.ECC;
1217
using Neo.IO;
1318
using Neo.Network.P2P.Payloads;
1419
using Neo.SmartContract.Native;
1520
using Neo.VM;
1621
using Neo.VM.Types;
17-
using System;
18-
using System.Collections.Generic;
19-
using System.IO;
20-
using System.Linq;
21-
using System.Numerics;
2222
using Array = Neo.VM.Types.Array;
2323

2424
namespace Neo.SmartContract
@@ -332,6 +332,35 @@ protected internal void RuntimeLog(byte[] state)
332332
/// <param name="eventName">The name of the event.</param>
333333
/// <param name="state">The arguments of the event.</param>
334334
protected internal void RuntimeNotify(byte[] eventName, Array state)
335+
{
336+
if (!IsHardforkEnabled(Hardfork.HF_Basilisk))
337+
{
338+
RuntimeNotifyV1(eventName, state);
339+
return;
340+
}
341+
if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName));
342+
string name = Utility.StrictUTF8.GetString(eventName);
343+
ContractState contract = CurrentContext.GetState<ExecutionContextState>().Contract;
344+
if (contract is null)
345+
throw new InvalidOperationException("Notifications are not allowed in dynamic scripts.");
346+
var @event = contract.Manifest.Abi.Events.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.Ordinal));
347+
if (@event is null)
348+
throw new InvalidOperationException($"Event `{name}` does not exist.");
349+
if (@event.Parameters.Length != state.Count)
350+
throw new InvalidOperationException("The number of the arguments does not match the formal parameters of the event.");
351+
for (int i = 0; i < @event.Parameters.Length; i++)
352+
{
353+
var p = @event.Parameters[i];
354+
if (!CheckItemType(state[i], p.Type))
355+
throw new InvalidOperationException($"The type of the argument `{p.Name}` does not match the formal parameter.");
356+
}
357+
using MemoryStream ms = new(MaxNotificationSize);
358+
using BinaryWriter writer = new(ms, Utility.StrictUTF8, true);
359+
BinarySerializer.Serialize(writer, state, MaxNotificationSize);
360+
SendNotification(CurrentScriptHash, name, state);
361+
}
362+
363+
protected internal void RuntimeNotifyV1(byte[] eventName, Array state)
335364
{
336365
if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName));
337366
if (CurrentContext.GetState<ExecutionContextState>().Contract is null)
@@ -384,5 +413,59 @@ protected internal void BurnGas(long gas)
384413
throw new InvalidOperationException("GAS must be positive.");
385414
AddGas(gas);
386415
}
416+
417+
private static bool CheckItemType(StackItem item, ContractParameterType type)
418+
{
419+
StackItemType aType = item.Type;
420+
if (aType == StackItemType.Pointer) return false;
421+
switch (type)
422+
{
423+
case ContractParameterType.Any:
424+
return true;
425+
case ContractParameterType.Boolean:
426+
return aType == StackItemType.Boolean;
427+
case ContractParameterType.Integer:
428+
return aType == StackItemType.Integer;
429+
case ContractParameterType.ByteArray:
430+
return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer;
431+
case ContractParameterType.String:
432+
{
433+
if (aType is StackItemType.ByteString or StackItemType.Buffer)
434+
{
435+
try
436+
{
437+
_ = Utility.StrictUTF8.GetString(item.GetSpan()); // Prevent any non-UTF8 string
438+
return true;
439+
}
440+
catch { }
441+
}
442+
return false;
443+
}
444+
case ContractParameterType.Hash160:
445+
if (aType == StackItemType.Any) return true;
446+
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
447+
return item.GetSpan().Length == UInt160.Length;
448+
case ContractParameterType.Hash256:
449+
if (aType == StackItemType.Any) return true;
450+
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
451+
return item.GetSpan().Length == UInt256.Length;
452+
case ContractParameterType.PublicKey:
453+
if (aType == StackItemType.Any) return true;
454+
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
455+
return item.GetSpan().Length == 33;
456+
case ContractParameterType.Signature:
457+
if (aType == StackItemType.Any) return true;
458+
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
459+
return item.GetSpan().Length == 64;
460+
case ContractParameterType.Array:
461+
return aType is StackItemType.Any or StackItemType.Array or StackItemType.Struct;
462+
case ContractParameterType.Map:
463+
return aType is StackItemType.Any or StackItemType.Map;
464+
case ContractParameterType.InteropInterface:
465+
return aType is StackItemType.Any or StackItemType.InteropInterface;
466+
default:
467+
return false;
468+
}
469+
}
387470
}
388471
}

tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs

+31-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
using Microsoft.VisualStudio.TestTools.UnitTesting;
33
using Neo.Network.P2P.Payloads;
44
using Neo.SmartContract;
5+
using Neo.SmartContract.Manifest;
56
using System;
67
using System.Numerics;
8+
using System.Text;
79

810
namespace Neo.UnitTests.SmartContract
911
{
@@ -24,36 +26,60 @@ public void TestNotSupportedNotification()
2426
{
2527
using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000);
2628
engine.LoadScript(Array.Empty<byte>());
27-
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();
29+
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new()
30+
{
31+
Manifest = new()
32+
{
33+
Abi = new()
34+
{
35+
Events = new[]
36+
{
37+
new ContractEventDescriptor
38+
{
39+
Name = "e1",
40+
Parameters = new[]
41+
{
42+
new ContractParameterDefinition
43+
{
44+
Type = ContractParameterType.Array
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
};
2852

2953
// circular
3054

3155
VM.Types.Array array = new();
3256
array.Add(array);
3357

34-
Assert.ThrowsException<NotSupportedException>(() => engine.RuntimeNotify(new byte[] { 0x01 }, array));
58+
Assert.ThrowsException<NotSupportedException>(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array));
3559

3660
// Buffer
3761

3862
array.Clear();
3963
array.Add(new VM.Types.Buffer(1));
64+
engine.CurrentContext.GetState<ExecutionContextState>().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.ByteArray;
4065

41-
engine.RuntimeNotify(new byte[] { 0x01 }, array);
66+
engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array);
4267
engine.Notifications[0].State[0].Type.Should().Be(VM.Types.StackItemType.ByteString);
4368

4469
// Pointer
4570

4671
array.Clear();
4772
array.Add(new VM.Types.Pointer(Array.Empty<byte>(), 1));
4873

49-
Assert.ThrowsException<NotSupportedException>(() => engine.RuntimeNotify(new byte[] { 0x01 }, array));
74+
Assert.ThrowsException<InvalidOperationException>(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array));
5075

5176
// InteropInterface
5277

5378
array.Clear();
5479
array.Add(new VM.Types.InteropInterface(new object()));
80+
engine.CurrentContext.GetState<ExecutionContextState>().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.InteropInterface;
5581

56-
Assert.ThrowsException<NotSupportedException>(() => engine.RuntimeNotify(new byte[] { 0x01 }, array));
82+
Assert.ThrowsException<NotSupportedException>(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array));
5783
}
5884

5985
[TestMethod]

tests/Neo.UnitTests/SmartContract/UT_InteropService.cs

+50-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,22 @@ public void Runtime_GetNotifications_Test()
4545
scriptHash2 = script.ToArray().ToScriptHash();
4646

4747
snapshot.DeleteContract(scriptHash2);
48-
snapshot.AddContract(scriptHash2, TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)));
48+
ContractState contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer));
49+
contract.Manifest.Abi.Events = new[]
50+
{
51+
new ContractEventDescriptor
52+
{
53+
Name = "testEvent2",
54+
Parameters = new[]
55+
{
56+
new ContractParameterDefinition
57+
{
58+
Type = ContractParameterType.Any
59+
}
60+
}
61+
}
62+
};
63+
snapshot.AddContract(scriptHash2, contract);
4964
}
5065

5166
// Wrong length
@@ -93,7 +108,23 @@ public void Runtime_GetNotifications_Test()
93108
// Execute
94109

95110
engine.LoadScript(script.ToArray());
96-
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();
111+
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new()
112+
{
113+
Manifest = new()
114+
{
115+
Abi = new()
116+
{
117+
Events = new[]
118+
{
119+
new ContractEventDescriptor
120+
{
121+
Name = "testEvent1",
122+
Parameters = System.Array.Empty<ContractParameterDefinition>()
123+
}
124+
}
125+
}
126+
}
127+
};
97128
var currentScriptHash = engine.EntryScriptHash;
98129

99130
Assert.AreEqual(VMState.HALT, engine.Execute());
@@ -146,7 +177,23 @@ public void Runtime_GetNotifications_Test()
146177
// Execute
147178

148179
engine.LoadScript(script.ToArray());
149-
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();
180+
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new()
181+
{
182+
Manifest = new()
183+
{
184+
Abi = new()
185+
{
186+
Events = new[]
187+
{
188+
new ContractEventDescriptor
189+
{
190+
Name = "testEvent1",
191+
Parameters = System.Array.Empty<ContractParameterDefinition>()
192+
}
193+
}
194+
}
195+
}
196+
};
150197
var currentScriptHash = engine.EntryScriptHash;
151198

152199
Assert.AreEqual(VMState.HALT, engine.Execute());

0 commit comments

Comments
 (0)