From e88062f9d76c3e2ca65f16964d4f90b85166a89f Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 2 Sep 2022 10:13:52 +0800 Subject: [PATCH 1/6] Check ABI for notifications --- .../ApplicationEngine.Runtime.cs | 59 ++++++++++++++++++- .../UT_ApplicationEngine.Runtime.cs | 36 +++++++++-- .../SmartContract/UT_InteropService.cs | 53 ++++++++++++++++- 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index f4f4140d42..6a58678689 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -306,12 +306,25 @@ protected internal void RuntimeLog(byte[] state) protected internal void RuntimeNotify(byte[] eventName, Array state) { if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); - if (CurrentContext.GetState().Contract is null) + string name = Utility.StrictUTF8.GetString(eventName); + ContractState contract = CurrentContext.GetState().Contract; + if (contract is null) throw new InvalidOperationException("Notifications are not allowed in dynamic scripts."); + var @event = contract.Manifest.Abi.Events.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.Ordinal)); + if (@event is null) + throw new InvalidOperationException($"Event `{name}` does not exist."); + if (@event.Parameters.Length != state.Count) + throw new InvalidOperationException("The number of the arguments does not match the formal parameters of the event."); + for (int i = 0; i < @event.Parameters.Length; i++) + { + var p = @event.Parameters[i]; + if (!CheckItemType(state[i], p.Type)) + throw new InvalidOperationException($"The type of the argument `{p.Name}` does not match the formal parameter."); + } using MemoryStream ms = new(MaxNotificationSize); using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); BinarySerializer.Serialize(writer, state, MaxNotificationSize); - SendNotification(CurrentScriptHash, Utility.StrictUTF8.GetString(eventName), state); + SendNotification(CurrentScriptHash, name, state); } /// @@ -356,5 +369,47 @@ protected internal void BurnGas(long gas) throw new InvalidOperationException("GAS must be positive."); AddGas(gas); } + + private static bool CheckItemType(StackItem item, ContractParameterType type) + { + StackItemType aType = item.Type; + if (aType == StackItemType.Pointer) return false; + switch (type) + { + case ContractParameterType.Any: + return true; + case ContractParameterType.Boolean: + return aType == StackItemType.Boolean; + case ContractParameterType.Integer: + return aType == StackItemType.Integer; + case ContractParameterType.ByteArray: + case ContractParameterType.String: + return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; + case ContractParameterType.Hash160: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == UInt160.Length; + case ContractParameterType.Hash256: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == UInt256.Length; + case ContractParameterType.PublicKey: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == 33; + case ContractParameterType.Signature: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == 64; + case ContractParameterType.Array: + return aType is StackItemType.Any or StackItemType.Array or StackItemType.Struct; + case ContractParameterType.Map: + return aType is StackItemType.Any or StackItemType.Map; + case ContractParameterType.InteropInterface: + return aType is StackItemType.Any or StackItemType.InteropInterface; + default: + return false; + } + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs index 61bef60e2c..7d4faee5b5 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs @@ -2,8 +2,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using System; using System.Numerics; +using System.Text; namespace Neo.UnitTests.SmartContract { @@ -24,21 +26,44 @@ public void TestNotSupportedNotification() { using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); engine.LoadScript(Array.Empty()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "e1", + Parameters = new[] + { + new ContractParameterDefinition + { + Type = ContractParameterType.Array + } + } + } + } + } + } + }; // circular VM.Types.Array array = new(); array.Add(array); - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); // Buffer array.Clear(); array.Add(new VM.Types.Buffer(1)); + engine.CurrentContext.GetState().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.ByteArray; - engine.RuntimeNotify(new byte[] { 0x01 }, array); + engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array); engine.Notifications[0].State[0].Type.Should().Be(VM.Types.StackItemType.ByteString); // Pointer @@ -46,14 +71,15 @@ public void TestNotSupportedNotification() array.Clear(); array.Add(new VM.Types.Pointer(Array.Empty(), 1)); - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); // InteropInterface array.Clear(); array.Add(new VM.Types.InteropInterface(new object())); + engine.CurrentContext.GetState().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.InteropInterface; - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 0494ab22e0..081fb87661 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -45,7 +45,22 @@ public void Runtime_GetNotifications_Test() scriptHash2 = script.ToArray().ToScriptHash(); snapshot.DeleteContract(scriptHash2); - snapshot.AddContract(scriptHash2, TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer))); + ContractState contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)); + contract.Manifest.Abi.Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent2", + Parameters = new[] + { + new ContractParameterDefinition + { + Type = ContractParameterType.Any + } + } + } + }; + snapshot.AddContract(scriptHash2, contract); } // Wrong length @@ -93,7 +108,23 @@ public void Runtime_GetNotifications_Test() // Execute engine.LoadScript(script.ToArray()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent1", + Parameters = System.Array.Empty() + } + } + } + } + }; var currentScriptHash = engine.EntryScriptHash; Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -146,7 +177,23 @@ public void Runtime_GetNotifications_Test() // Execute engine.LoadScript(script.ToArray()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent1", + Parameters = System.Array.Empty() + } + } + } + } + }; var currentScriptHash = engine.EntryScriptHash; Assert.AreEqual(VMState.HALT, engine.Execute()); From 75829a9cae0ac1c3d5490c627f66fa6dd0949ca7 Mon Sep 17 00:00:00 2001 From: Shine Li Date: Wed, 9 Aug 2023 16:20:38 +0800 Subject: [PATCH 2/6] Patch checkabi (#2884) * Remove extra space (#2878) * Add Hardfork for StrictMode (#2881) * init * rename * hardfork * Update ApplicationEngine.Runtime.cs --------- Co-authored-by: Shargon --- src/Neo/Hardfork.cs | 3 ++- src/Neo/Network/P2P/Payloads/WitnessScope.cs | 2 +- .../SmartContract/ApplicationEngine.Runtime.cs | 16 ++++++++++++++++ src/Neo/SmartContract/ApplicationEngine.cs | 2 +- .../SmartContract/Native/ContractManagement.cs | 4 ++-- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index bd2c294229..5a9aef6b5c 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -12,6 +12,7 @@ namespace Neo { public enum Hardfork : byte { - HF_Aspidochelone + HF_Aspidochelone, + HF_Basilisk } } diff --git a/src/Neo/Network/P2P/Payloads/WitnessScope.cs b/src/Neo/Network/P2P/Payloads/WitnessScope.cs index 19e7ea82dd..1ac2815af2 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessScope.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessScope.cs @@ -36,7 +36,7 @@ public enum WitnessScope : byte CustomContracts = 0x10, /// - /// Custom pubkey for group members. + /// Custom pubkey for group members. /// CustomGroups = 0x20, diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index 277ec3090f..79709ab524 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -333,6 +333,11 @@ protected internal void RuntimeLog(byte[] state) /// The arguments of the event. protected internal void RuntimeNotify(byte[] eventName, Array state) { + if (!IsHardforkEnabled(Hardfork.HF_Basilisk)) + { + RuntimeNotifyV1(eventName, state); + return; + } if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); string name = Utility.StrictUTF8.GetString(eventName); ContractState contract = CurrentContext.GetState().Contract; @@ -355,6 +360,17 @@ protected internal void RuntimeNotify(byte[] eventName, Array state) SendNotification(CurrentScriptHash, name, state); } + protected internal void RuntimeNotifyV1(byte[] eventName, Array state) + { + if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); + if (CurrentContext.GetState().Contract is null) + throw new InvalidOperationException("Notifications are not allowed in dynamic scripts."); + using MemoryStream ms = new(MaxNotificationSize); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + BinarySerializer.Serialize(writer, state, MaxNotificationSize); + SendNotification(CurrentScriptHash, Utility.StrictUTF8.GetString(eventName), state); + } + /// /// Sends a notification for the specified contract. /// diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index dc31960af6..3f35e39fcb 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -610,7 +610,7 @@ public void SetState(T state) states[typeof(T)] = state; } - private bool IsHardforkEnabled(Hardfork hardfork) + public bool IsHardforkEnabled(Hardfork hardfork) { if (PersistingBlock is null) return true; diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 8cc6b54588..f7ed44542d 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -231,7 +231,7 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ NefFile nef = nefFile.AsSerializable(); ContractManifest parsedManifest = ContractManifest.Parse(manifest); - Helper.Check(new VM.Script(nef.Script, true), parsedManifest.Abi); + Helper.Check(new VM.Script(nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), parsedManifest.Abi); UInt160 hash = Helper.GetContractHash(tx.Sender, nef.CheckSum, parsedManifest.Name); if (Policy.IsBlocked(engine.Snapshot, hash)) @@ -295,7 +295,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man throw new InvalidOperationException($"Invalid Manifest Hash: {contract.Hash}"); contract.Manifest = manifest_new; } - Helper.Check(new VM.Script(contract.Nef.Script, true), contract.Manifest.Abi); + Helper.Check(new VM.Script(contract.Nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), contract.Manifest.Abi); contract.UpdateCounter++; // Increase update counter return OnDeploy(engine, contract, data, true); } From 93b41267fde03fa829bfc63a7c66a0253c5f71f7 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Wed, 16 Aug 2023 12:23:39 +0200 Subject: [PATCH 3/6] Check UTF8 string --- src/Neo/SmartContract/ApplicationEngine.Runtime.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index 79709ab524..d6e5f7d718 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -428,7 +428,8 @@ private static bool CheckItemType(StackItem item, ContractParameterType type) return aType == StackItemType.Integer; case ContractParameterType.ByteArray: case ContractParameterType.String: - return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; + return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer && + Utility.StrictUTF8.GetString(item.GetSpan()) is not null; // Prevent any non-UTF8 string case ContractParameterType.Hash160: if (aType == StackItemType.Any) return true; if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; From be2ef403d0682d46afa028acfca1197109ed97d2 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Wed, 16 Aug 2023 12:25:58 +0200 Subject: [PATCH 4/6] Cleaner way --- .../ApplicationEngine.Runtime.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index d6e5f7d718..cef872718b 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -8,17 +8,17 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; using Array = Neo.VM.Types.Array; namespace Neo.SmartContract @@ -428,8 +428,18 @@ private static bool CheckItemType(StackItem item, ContractParameterType type) return aType == StackItemType.Integer; case ContractParameterType.ByteArray: case ContractParameterType.String: - return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer && - Utility.StrictUTF8.GetString(item.GetSpan()) is not null; // Prevent any non-UTF8 string + { + if (aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer) + { + try + { + _ = Utility.StrictUTF8.GetString(item.GetSpan()); // Prevent any non-UTF8 string + return true; + } + catch { } + } + return false; + } case ContractParameterType.Hash160: if (aType == StackItemType.Any) return true; if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; From 780103a2ed6d3bd7bab666d8e1955d2fff4ba03c Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 16 Aug 2023 06:14:54 -0700 Subject: [PATCH 5/6] Fix Buffer --- src/Neo/SmartContract/ApplicationEngine.Runtime.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index cef872718b..5c187cb4e8 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -427,6 +427,7 @@ private static bool CheckItemType(StackItem item, ContractParameterType type) case ContractParameterType.Integer: return aType == StackItemType.Integer; case ContractParameterType.ByteArray: + return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; case ContractParameterType.String: { if (aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer) From 3f10c964039fc3c8397ecd1ea9fe002d7f4a55b3 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 18 Aug 2023 16:11:43 +0800 Subject: [PATCH 6/6] Update src/Neo/SmartContract/ApplicationEngine.Runtime.cs Co-authored-by: Anna Shaleva --- src/Neo/SmartContract/ApplicationEngine.Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index 5c187cb4e8..88d07349f9 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -430,7 +430,7 @@ private static bool CheckItemType(StackItem item, ContractParameterType type) return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; case ContractParameterType.String: { - if (aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer) + if (aType is StackItemType.ByteString or StackItemType.Buffer) { try {