From ea39390d08c79ffea57b742c955f26641c554919 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 13:41:31 +0200 Subject: [PATCH 01/21] Update VM to last changes --- neo/SmartContract/ApplicationEngine.cs | 16 ++++++++-------- neo/neo.csproj | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index d211d7aac8..9223fcb50d 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -36,8 +36,6 @@ public ApplicationEngine(TriggerType trigger, IVerifiable container, Snapshot sn this.Trigger = trigger; this.ScriptContainer = container; this.Snapshot = snapshot; - ContextLoaded += ApplicationEngine_ContextLoaded; - ContextUnloaded += ApplicationEngine_ContextUnloaded; } internal T AddDisposable(T disposable) where T : IDisposable @@ -52,14 +50,16 @@ private bool AddGas(long gas) return testMode || GasConsumed <= gas_amount; } - private void ApplicationEngine_ContextLoaded(object sender, ExecutionContext e) - { - hashes.Push(((byte[])e.Script).ToScriptHash()); + protected override void ContextUnloaded(ExecutionContext context) + { + hashes.Pop(); + base.ContextUnloaded(context); } - private void ApplicationEngine_ContextUnloaded(object sender, ExecutionContext e) - { - hashes.Pop(); + protected override void LoadContext(ExecutionContext context) + { + hashes.Push(((byte[])context.Script).ToScriptHash()); + base.LoadContext(context); } public override void Dispose() diff --git a/neo/neo.csproj b/neo/neo.csproj index df29d088f2..66e85016bc 100644 --- a/neo/neo.csproj +++ b/neo/neo.csproj @@ -29,7 +29,7 @@ - + From 86aff3ee91d52e4e446c41db567d2181912757ec Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 21:55:15 +0200 Subject: [PATCH 02/21] Remove hashes --- neo/SmartContract/ApplicationEngine.cs | 21 ++++++++++----------- neo/SmartContract/ExecutionContextState.cs | 10 ++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 neo/SmartContract/ExecutionContextState.cs diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 9223fcb50d..45763ed30a 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -15,7 +15,6 @@ public partial class ApplicationEngine : ExecutionEngine public const long GasFree = 0; private readonly long gas_amount; private readonly bool testMode; - private readonly RandomAccessStack hashes = new RandomAccessStack(); private readonly List notifications = new List(); private readonly List disposables = new List(); @@ -23,9 +22,9 @@ public partial class ApplicationEngine : ExecutionEngine public IVerifiable ScriptContainer { get; } public Snapshot Snapshot { get; } public long GasConsumed { get; private set; } = 0; - public UInt160 CurrentScriptHash => hashes.Count > 0 ? hashes.Peek() : null; - public UInt160 CallingScriptHash => hashes.Count > 1 ? hashes.Peek(1) : null; - public UInt160 EntryScriptHash => hashes.Count > 0 ? hashes.Peek(hashes.Count - 1) : null; + public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; + public UInt160 CallingScriptHash => InvocationStack.Count > 1 ? InvocationStack.Peek(1).GetState().ScriptHash : null; + public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; public IReadOnlyList Notifications => notifications; internal Dictionary InvocationCounter { get; } = new Dictionary(); @@ -50,15 +49,15 @@ private bool AddGas(long gas) return testMode || GasConsumed <= gas_amount; } - protected override void ContextUnloaded(ExecutionContext context) - { - hashes.Pop(); - base.ContextUnloaded(context); - } - protected override void LoadContext(ExecutionContext context) { - hashes.Push(((byte[])context.Script).ToScriptHash()); + // Set default execution context state + + context.SetState(new ExecutionContextState() + { + ScriptHash = ((byte[])context.Script).ToScriptHash() + }); + base.LoadContext(context); } diff --git a/neo/SmartContract/ExecutionContextState.cs b/neo/SmartContract/ExecutionContextState.cs new file mode 100644 index 0000000000..7895d943a2 --- /dev/null +++ b/neo/SmartContract/ExecutionContextState.cs @@ -0,0 +1,10 @@ +namespace Neo.SmartContract +{ + public class ExecutionContextState + { + /// + /// Script hash + /// + public UInt160 ScriptHash { get; set; } + } +} \ No newline at end of file From fa580d28994c399320cebb56c9c1999499b5e485 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:14:42 +0200 Subject: [PATCH 03/21] ReadOnly --- neo/SmartContract/ExecutionContextState.cs | 5 +++++ neo/SmartContract/InteropService.NEO.cs | 4 ++++ neo/SmartContract/InteropService.cs | 9 ++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/neo/SmartContract/ExecutionContextState.cs b/neo/SmartContract/ExecutionContextState.cs index 7895d943a2..ec47902e5e 100644 --- a/neo/SmartContract/ExecutionContextState.cs +++ b/neo/SmartContract/ExecutionContextState.cs @@ -6,5 +6,10 @@ public class ExecutionContextState /// Script hash /// public UInt160 ScriptHash { get; set; } + + /// + /// Is read only + /// + public bool ReadOnly { get; set; } = false; } } \ No newline at end of file diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 91b941649f..833b14720d 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -70,6 +70,8 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; + if (engine.CurrentContext.GetState().ReadOnly) return false; + foreach (NativeContract contract in NativeContract.Contracts) { engine.Snapshot.Contracts.Add(contract.Hash, new ContractState @@ -242,6 +244,7 @@ private static bool Account_IsStandard(ApplicationEngine engine) private static bool Contract_Create(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; @@ -266,6 +269,7 @@ private static bool Contract_Create(ApplicationEngine engine) private static bool Contract_Update(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 9fbb5c6675..866ab39d1e 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -471,6 +471,7 @@ private static bool Transaction_GetHash(ApplicationEngine engine) private static bool Storage_GetContext(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { ScriptHash = engine.CurrentScriptHash, @@ -537,9 +538,10 @@ private static bool Contract_Call(ApplicationEngine engine) StackItem method = engine.CurrentContext.EvaluationStack.Pop(); StackItem args = engine.CurrentContext.EvaluationStack.Pop(); + string methodStr = method.GetString(); ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest; - if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method.GetString())) + if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, methodStr)) return false; if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter)) @@ -552,6 +554,9 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); + context_new.GetState().ReadOnly = + currentManifest.SafeMethods.IsWildcard || currentManifest.SafeMethods.Contains(methodStr); + context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); return true; @@ -559,6 +564,8 @@ private static bool Contract_Call(ApplicationEngine engine) private static bool Contract_Destroy(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; + UInt160 hash = engine.CurrentScriptHash; ContractState contract = engine.Snapshot.Contracts.TryGet(hash); if (contract == null) return true; From 1e0496a5ba0e981c8881db3a5b1b06b455fed849 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:22:41 +0200 Subject: [PATCH 04/21] Rename to ReadOnly --- .../Manifest/UT_ContractManifest.cs | 16 ++++++++-------- neo/SmartContract/InteropService.cs | 2 +- neo/SmartContract/Manifest/ContractManifest.cs | 12 ++++++------ .../Native/ContractMethodAttribute.cs | 2 +- neo/SmartContract/Native/NativeContract.cs | 8 ++++---- neo/SmartContract/Native/PolicyContract.cs | 8 ++++---- neo/SmartContract/Native/Tokens/GasToken.cs | 2 +- neo/SmartContract/Native/Tokens/NeoToken.cs | 8 ++++---- neo/SmartContract/Native/Tokens/Nep5Token.cs | 10 +++++----- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index bbb613c2be..b2a1019fb1 100644 --- a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -10,7 +10,7 @@ public class UT_ContractManifest [TestMethod] public void ParseFromJson_Default() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -21,7 +21,7 @@ public void ParseFromJson_Default() [TestMethod] public void ParseFromJson_Features() { - var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToJson().ToString(), json); @@ -33,7 +33,7 @@ public void ParseFromJson_Features() [TestMethod] public void ParseFromJson_Permissions() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -50,21 +50,21 @@ public void ParseFromJson_Permissions() } [TestMethod] - public void ParseFromJson_SafeMethods() + public void ParseFromJson_ReadOnlyMethods() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[""balanceOf""]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[""balanceOf""]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); var check = ContractManifest.CreateDefault(UInt160.Zero); - check.SafeMethods = WildCardContainer.Create("balanceOf"); + check.ReadOnlyMethods = WildCardContainer.Create("balanceOf"); Assert.AreEqual(manifest.ToString(), check.ToString()); } [TestMethod] public void ParseFromJson_Trust() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -76,7 +76,7 @@ public void ParseFromJson_Trust() [TestMethod] public void ParseFromJson_Groups() { - var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 866ab39d1e..f7c01bb1f8 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -555,7 +555,7 @@ private static bool Contract_Call(ApplicationEngine engine) ExecutionContext context_new = engine.LoadScript(contract.Script, 1); context_new.GetState().ReadOnly = - currentManifest.SafeMethods.IsWildcard || currentManifest.SafeMethods.Contains(methodStr); + currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr); context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs index 967a10e856..06f4996753 100644 --- a/neo/SmartContract/Manifest/ContractManifest.cs +++ b/neo/SmartContract/Manifest/ContractManifest.cs @@ -53,10 +53,10 @@ public class ContractManifest : ISerializable public WildCardContainer Trusts { get; set; } /// - /// The safemethods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe. - /// If a method is marked as safe, the user interface will not give any warnings when it is called by any other contract. + /// The read only methods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe. + /// If a method is marked as read only, the user interface will not give any warnings when it is called by any other contract. /// - public WildCardContainer SafeMethods { get; set; } + public WildCardContainer ReadOnlyMethods { get; set; } /// /// Create Default Contract manifest @@ -77,7 +77,7 @@ public static ContractManifest CreateDefault(UInt160 hash) }, Features = ContractFeatures.NoProperty, Groups = new ContractGroup[0], - SafeMethods = WildCardContainer.Create(), + ReadOnlyMethods = WildCardContainer.Create(), Trusts = WildCardContainer.Create() }; } @@ -127,7 +127,7 @@ public JObject ToJson() json["abi"] = Abi.ToJson(); json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray(); json["trusts"] = Trusts.ToJson(); - json["safeMethods"] = SafeMethods.ToJson(); + json["readOnlyMethods"] = ReadOnlyMethods.ToJson(); return json; } @@ -161,7 +161,7 @@ private void DeserializeFromJson(JObject json) Features = ContractFeatures.NoProperty; Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson(u)).ToArray(); Trusts = WildCardContainer.FromJson(json["trusts"], u => UInt160.Parse(u.AsString())); - SafeMethods = WildCardContainer.FromJson(json["safeMethods"], u => u.AsString()); + ReadOnlyMethods = WildCardContainer.FromJson(json["readOnlyMethods"], u => u.AsString()); if (json["features"]["storage"].AsBoolean()) Features |= ContractFeatures.HasStorage; if (json["features"]["payable"].AsBoolean()) Features |= ContractFeatures.Payable; diff --git a/neo/SmartContract/Native/ContractMethodAttribute.cs b/neo/SmartContract/Native/ContractMethodAttribute.cs index 393737d92e..5f6d120e93 100644 --- a/neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/neo/SmartContract/Native/ContractMethodAttribute.cs @@ -10,7 +10,7 @@ internal class ContractMethodAttribute : Attribute public ContractParameterType ReturnType { get; } public ContractParameterType[] ParameterTypes { get; set; } = new ContractParameterType[0]; public string[] ParameterNames { get; set; } = new string[0]; - public bool SafeMethod { get; set; } = false; + public bool ReadOnly { get; set; } = false; public ContractMethodAttribute(long price, ContractParameterType returnType) { diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 08971dcf6b..603973806a 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -41,7 +41,7 @@ protected NativeContract() this.Hash = Script.ToScriptHash(); this.Manifest = ContractManifest.CreateDefault(this.Hash); List descriptors = new List(); - List safeMethods = new List(); + List readOnlyMethods = new List(); foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { ContractMethodAttribute attribute = method.GetCustomAttribute(); @@ -53,7 +53,7 @@ protected NativeContract() ReturnType = attribute.ReturnType, Parameters = attribute.ParameterTypes.Zip(attribute.ParameterNames, (t, n) => new ContractParameterDefinition { Type = t, Name = n }).ToArray() }); - if (attribute.SafeMethod) safeMethods.Add(name); + if (attribute.ReadOnly) readOnlyMethods.Add(name); methods.Add(name, new ContractMethodMetadata { Delegate = (Func)method.CreateDelegate(typeof(Func), this), @@ -61,7 +61,7 @@ protected NativeContract() }); } this.Manifest.Abi.Methods = descriptors.ToArray(); - this.Manifest.SafeMethods = WildCardContainer.Create(safeMethods.ToArray()); + this.Manifest.ReadOnlyMethods = WildCardContainer.Create(readOnlyMethods.ToArray()); contracts.Add(this); } @@ -120,7 +120,7 @@ protected virtual bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0, ContractParameterType.Array, Name = "supportedStandards", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.Array, Name = "supportedStandards", ReadOnly = true)] protected StackItem SupportedStandardsMethod(ApplicationEngine engine, VMArray args) { return SupportedStandards.Select(p => (StackItem)p).ToList(); diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index c5820cfd15..583f038e81 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -65,7 +65,7 @@ internal override bool Initialize(ApplicationEngine engine) return true; } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray args) { return GetMaxTransactionsPerBlock(engine.Snapshot); @@ -76,7 +76,7 @@ public uint GetMaxTransactionsPerBlock(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxTransactionsPerBlock)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetMaxBlockSize(ApplicationEngine engine, VMArray args) { return GetMaxBlockSize(engine.Snapshot); @@ -87,7 +87,7 @@ public uint GetMaxBlockSize(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxBlockSize)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetFeePerByte(ApplicationEngine engine, VMArray args) { return GetFeePerByte(engine.Snapshot); @@ -98,7 +98,7 @@ public long GetFeePerByte(Snapshot snapshot) return BitConverter.ToInt64(snapshot.Storages[CreateStorageKey(Prefix_FeePerByte)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetBlockedAccounts(ApplicationEngine engine, VMArray args) { return GetBlockedAccounts(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index fd39979670..c36e08e481 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -52,7 +52,7 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, ReadOnly = true)] private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) { uint index = (uint)args[0].GetBigInteger(); diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index 7b28bd0923..493e615cbd 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -116,7 +116,7 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0_03000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "end" }, SafeMethod = true)] + [ContractMethod(0_03000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "end" }, ReadOnly = true)] private StackItem UnclaimedGas(ApplicationEngine engine, VMArray args) { UInt160 account = new UInt160(args[0].GetByteArray()); @@ -193,7 +193,7 @@ private StackItem Vote(ApplicationEngine engine, VMArray args) return true; } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args) { return GetRegisteredValidators(engine.Snapshot).Select(p => new Struct(new StackItem[] { p.PublicKey.ToArray(), p.Votes })).ToArray(); @@ -209,7 +209,7 @@ private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args )); } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetValidators(ApplicationEngine engine, VMArray args) { return GetValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); @@ -234,7 +234,7 @@ public ECPoint[] GetValidators(Snapshot snapshot) return GetRegisteredValidators(snapshot).Where(p => (p.Votes.Sign > 0) || sv.Contains(p.PublicKey)).OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(count).OrderBy(p => p).ToArray(); } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetNextBlockValidators(ApplicationEngine engine, VMArray args) { return GetNextBlockValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index 6a2e1c78b3..b770f3925d 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -113,25 +113,25 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, engine.SendNotification(Hash, new StackItem[] { "Transfer", account.ToArray(), StackItem.Null, amount }); } - [ContractMethod(0, ContractParameterType.String, Name = "name", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.String, Name = "name", ReadOnly = true)] protected StackItem NameMethod(ApplicationEngine engine, VMArray args) { return Name; } - [ContractMethod(0, ContractParameterType.String, Name = "symbol", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.String, Name = "symbol", ReadOnly = true)] protected StackItem SymbolMethod(ApplicationEngine engine, VMArray args) { return Symbol; } - [ContractMethod(0, ContractParameterType.Integer, Name = "decimals", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.Integer, Name = "decimals", ReadOnly = true)] protected StackItem DecimalsMethod(ApplicationEngine engine, VMArray args) { return (uint)Decimals; } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] protected StackItem TotalSupply(ApplicationEngine engine, VMArray args) { return TotalSupply(engine.Snapshot); @@ -144,7 +144,7 @@ public virtual BigInteger TotalSupply(Snapshot snapshot) return new BigInteger(storage.Value); } - [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, ReadOnly = true)] protected StackItem BalanceOf(ApplicationEngine engine, VMArray args) { return BalanceOf(engine.Snapshot, new UInt160(args[0].GetByteArray())); From 6759e5af3107ab528799aba059f194704700033d Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:24:33 +0200 Subject: [PATCH 05/21] Fix unit tests --- neo/SmartContract/InteropService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index f7c01bb1f8..d724656b14 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -554,8 +554,8 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = - currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr); + context_new.GetState().ReadOnly = currentManifest != null && + (currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr)); context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); From be5d7a117fffe087d00b025826e0c89d01bbdd94 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:42:30 +0200 Subject: [PATCH 06/21] Refactor readOnly check --- neo/SmartContract/InteropDescriptor.cs | 12 +++++++----- neo/SmartContract/InteropService.NEO.cs | 9 +++------ neo/SmartContract/InteropService.cs | 17 ++++++++--------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/neo/SmartContract/InteropDescriptor.cs b/neo/SmartContract/InteropDescriptor.cs index c283e474de..6e5d48ea5a 100644 --- a/neo/SmartContract/InteropDescriptor.cs +++ b/neo/SmartContract/InteropDescriptor.cs @@ -11,25 +11,27 @@ internal class InteropDescriptor public long Price { get; } public Func, long> PriceCalculator { get; } public TriggerType AllowedTriggers { get; } + public bool RequireWriteAccess { get; } - public InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + public InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers, bool requireWriteAccess) + : this(method, handler, allowedTriggers, requireWriteAccess) { this.Price = price; } - public InteropDescriptor(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + public InteropDescriptor(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess) + : this(method, handler, allowedTriggers, requireWriteAccess) { this.PriceCalculator = priceCalculator; } - private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers) + private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers, bool requireWriteAccess) { this.Method = method; this.Hash = method.ToInteropMethodHash(); this.Handler = handler; this.AllowedTriggers = allowedTriggers; + this.RequireWriteAccess = requireWriteAccess; } public long GetPrice(RandomAccessStack stack) diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 833b14720d..0e64c74e10 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -17,7 +17,7 @@ namespace Neo.SmartContract { static partial class InteropService { - public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application); + public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application, true); public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000, TriggerType.All); public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice, TriggerType.All); public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400, TriggerType.Application); @@ -27,8 +27,8 @@ static partial class InteropService public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000, TriggerType.All); public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400, TriggerType.All); public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000, TriggerType.All); - public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application); - public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application); + public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application, true); + public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application, true); public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400, TriggerType.Application); public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400, TriggerType.Application); public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application); @@ -70,7 +70,6 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; - if (engine.CurrentContext.GetState().ReadOnly) return false; foreach (NativeContract contract in NativeContract.Contracts) { @@ -244,7 +243,6 @@ private static bool Account_IsStandard(ApplicationEngine engine) private static bool Contract_Create(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; @@ -269,7 +267,6 @@ private static bool Contract_Create(ApplicationEngine engine) private static bool Contract_Update(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index d724656b14..8696f22d77 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -55,8 +55,8 @@ public static partial class InteropService public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 0_00000400, TriggerType.Application); public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 0_00000400, TriggerType.All); public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application); - public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application); - public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application); + public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application, true); + public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application, true); public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application); public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application); public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application); @@ -88,19 +88,21 @@ internal static bool Invoke(ApplicationEngine engine, uint method) return false; if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger)) return false; + if (descriptor.RequireWriteAccess && engine.CurrentContext.GetState().ReadOnly) + return false; return descriptor.Handler(engine); } - private static uint Register(string method, Func handler, long price, TriggerType allowedTriggers) + private static uint Register(string method, Func handler, long price, TriggerType allowedTriggers, bool requireWriteAccess = false) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers, requireWriteAccess); methods.Add(descriptor.Hash, descriptor); return descriptor.Hash; } - private static uint Register(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers) + private static uint Register(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess = false) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requireWriteAccess); methods.Add(descriptor.Hash, descriptor); return descriptor.Hash; } @@ -471,7 +473,6 @@ private static bool Transaction_GetHash(ApplicationEngine engine) private static bool Storage_GetContext(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { ScriptHash = engine.CurrentScriptHash, @@ -564,8 +565,6 @@ private static bool Contract_Call(ApplicationEngine engine) private static bool Contract_Destroy(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; - UInt160 hash = engine.CurrentScriptHash; ContractState contract = engine.Snapshot.Contracts.TryGet(hash); if (contract == null) return true; From aa3dce2a35565cc0b22608c97d4b5fa5fd337064 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:43:48 +0200 Subject: [PATCH 07/21] Reduce changes --- neo/SmartContract/InteropService.NEO.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 0e64c74e10..036b700ccb 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -70,7 +70,6 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; - foreach (NativeContract contract in NativeContract.Contracts) { engine.Snapshot.Contracts.Add(contract.Hash, new ContractState From 6c6ab1a8dd4bb94be9e00d7206bbe74a1930a0c4 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 13:41:31 +0200 Subject: [PATCH 08/21] Update VM to last changes --- neo/SmartContract/ApplicationEngine.cs | 129 +------------------------ 1 file changed, 1 insertion(+), 128 deletions(-) diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 45763ed30a..b58c16d9b3 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -1,53 +1,3 @@ -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.VM; -using System; -using System.Collections.Generic; - -namespace Neo.SmartContract -{ - public partial class ApplicationEngine : ExecutionEngine - { - public static event EventHandler Notify; - public static event EventHandler Log; - - public const long GasFree = 0; - private readonly long gas_amount; - private readonly bool testMode; - private readonly List notifications = new List(); - private readonly List disposables = new List(); - - public TriggerType Trigger { get; } - public IVerifiable ScriptContainer { get; } - public Snapshot Snapshot { get; } - public long GasConsumed { get; private set; } = 0; - public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; - public UInt160 CallingScriptHash => InvocationStack.Count > 1 ? InvocationStack.Peek(1).GetState().ScriptHash : null; - public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; - public IReadOnlyList Notifications => notifications; - internal Dictionary InvocationCounter { get; } = new Dictionary(); - - public ApplicationEngine(TriggerType trigger, IVerifiable container, Snapshot snapshot, long gas, bool testMode = false) - { - this.gas_amount = GasFree + gas; - this.testMode = testMode; - this.Trigger = trigger; - this.ScriptContainer = container; - this.Snapshot = snapshot; - } - - internal T AddDisposable(T disposable) where T : IDisposable - { - disposables.Add(disposable); - return disposable; - } - - private bool AddGas(long gas) - { - GasConsumed = checked(GasConsumed + gas); - return testMode || GasConsumed <= gas_amount; - } protected override void LoadContext(ExecutionContext context) { @@ -58,81 +8,4 @@ protected override void LoadContext(ExecutionContext context) ScriptHash = ((byte[])context.Script).ToScriptHash() }); - base.LoadContext(context); - } - - public override void Dispose() - { - foreach (IDisposable disposable in disposables) - disposable.Dispose(); - disposables.Clear(); - base.Dispose(); - } - - protected override bool OnSysCall(uint method) - { - if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack))) - return false; - return InteropService.Invoke(this, method); - } - - protected override bool PreExecuteInstruction() - { - if (CurrentContext.InstructionPointer >= CurrentContext.Script.Length) - return true; - return AddGas(OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); - } - - private static Block CreateDummyBlock(Snapshot snapshot) - { - var currentBlock = snapshot.Blocks[snapshot.CurrentBlockHash]; - return new Block - { - Version = 0, - PrevHash = snapshot.CurrentBlockHash, - MerkleRoot = new UInt256(), - Timestamp = currentBlock.Timestamp + Blockchain.MillisecondsPerBlock, - Index = snapshot.Height + 1, - NextConsensus = currentBlock.NextConsensus, - Witness = new Witness - { - InvocationScript = new byte[0], - VerificationScript = new byte[0] - }, - ConsensusData = new ConsensusData(), - Transactions = new Transaction[0] - }; - } - - public static ApplicationEngine Run(byte[] script, Snapshot snapshot, - IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) - { - snapshot.PersistingBlock = persistingBlock ?? snapshot.PersistingBlock ?? CreateDummyBlock(snapshot); - ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, snapshot, extraGAS, testMode); - engine.LoadScript(script); - engine.Execute(); - return engine; - } - - public static ApplicationEngine Run(byte[] script, IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) - { - using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) - { - return Run(script, snapshot, container, persistingBlock, testMode, extraGAS); - } - } - - internal void SendLog(UInt160 script_hash, string message) - { - LogEventArgs log = new LogEventArgs(ScriptContainer, script_hash, message); - Log?.Invoke(this, log); - } - - internal void SendNotification(UInt160 script_hash, StackItem state) - { - NotifyEventArgs notification = new NotifyEventArgs(ScriptContainer, script_hash, state); - Notify?.Invoke(this, notification); - notifications.Add(notification); - } - } -} + base.LoadContext(context); \ No newline at end of file From 3bc6e80f3d2cafbfcd9d22a3b7e284ba4947efb6 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:14:42 +0200 Subject: [PATCH 09/21] ReadOnly --- neo/SmartContract/ExecutionContextState.cs | 5 +++++ neo/SmartContract/InteropService.NEO.cs | 4 ++++ neo/SmartContract/InteropService.cs | 9 ++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/neo/SmartContract/ExecutionContextState.cs b/neo/SmartContract/ExecutionContextState.cs index 7895d943a2..ec47902e5e 100644 --- a/neo/SmartContract/ExecutionContextState.cs +++ b/neo/SmartContract/ExecutionContextState.cs @@ -6,5 +6,10 @@ public class ExecutionContextState /// Script hash /// public UInt160 ScriptHash { get; set; } + + /// + /// Is read only + /// + public bool ReadOnly { get; set; } = false; } } \ No newline at end of file diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 91b941649f..833b14720d 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -70,6 +70,8 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; + if (engine.CurrentContext.GetState().ReadOnly) return false; + foreach (NativeContract contract in NativeContract.Contracts) { engine.Snapshot.Contracts.Add(contract.Hash, new ContractState @@ -242,6 +244,7 @@ private static bool Account_IsStandard(ApplicationEngine engine) private static bool Contract_Create(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; @@ -266,6 +269,7 @@ private static bool Contract_Create(ApplicationEngine engine) private static bool Contract_Update(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 9fbb5c6675..866ab39d1e 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -471,6 +471,7 @@ private static bool Transaction_GetHash(ApplicationEngine engine) private static bool Storage_GetContext(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { ScriptHash = engine.CurrentScriptHash, @@ -537,9 +538,10 @@ private static bool Contract_Call(ApplicationEngine engine) StackItem method = engine.CurrentContext.EvaluationStack.Pop(); StackItem args = engine.CurrentContext.EvaluationStack.Pop(); + string methodStr = method.GetString(); ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest; - if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method.GetString())) + if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, methodStr)) return false; if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter)) @@ -552,6 +554,9 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); + context_new.GetState().ReadOnly = + currentManifest.SafeMethods.IsWildcard || currentManifest.SafeMethods.Contains(methodStr); + context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); return true; @@ -559,6 +564,8 @@ private static bool Contract_Call(ApplicationEngine engine) private static bool Contract_Destroy(ApplicationEngine engine) { + if (engine.CurrentContext.GetState().ReadOnly) return false; + UInt160 hash = engine.CurrentScriptHash; ContractState contract = engine.Snapshot.Contracts.TryGet(hash); if (contract == null) return true; From cab7f016fd95f069ab46adc56903ce2350160319 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:22:41 +0200 Subject: [PATCH 10/21] Rename to ReadOnly --- .../Manifest/UT_ContractManifest.cs | 16 ++++++++-------- neo/SmartContract/InteropService.cs | 2 +- neo/SmartContract/Manifest/ContractManifest.cs | 12 ++++++------ .../Native/ContractMethodAttribute.cs | 2 +- neo/SmartContract/Native/NativeContract.cs | 8 ++++---- neo/SmartContract/Native/PolicyContract.cs | 8 ++++---- neo/SmartContract/Native/Tokens/GasToken.cs | 2 +- neo/SmartContract/Native/Tokens/NeoToken.cs | 8 ++++---- neo/SmartContract/Native/Tokens/Nep5Token.cs | 10 +++++----- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index bbb613c2be..b2a1019fb1 100644 --- a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -10,7 +10,7 @@ public class UT_ContractManifest [TestMethod] public void ParseFromJson_Default() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -21,7 +21,7 @@ public void ParseFromJson_Default() [TestMethod] public void ParseFromJson_Features() { - var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToJson().ToString(), json); @@ -33,7 +33,7 @@ public void ParseFromJson_Features() [TestMethod] public void ParseFromJson_Permissions() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -50,21 +50,21 @@ public void ParseFromJson_Permissions() } [TestMethod] - public void ParseFromJson_SafeMethods() + public void ParseFromJson_ReadOnlyMethods() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[""balanceOf""]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[""balanceOf""]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); var check = ContractManifest.CreateDefault(UInt160.Zero); - check.SafeMethods = WildCardContainer.Create("balanceOf"); + check.ReadOnlyMethods = WildCardContainer.Create("balanceOf"); Assert.AreEqual(manifest.ToString(), check.ToString()); } [TestMethod] public void ParseFromJson_Trust() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safeMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -76,7 +76,7 @@ public void ParseFromJson_Trust() [TestMethod] public void ParseFromJson_Groups() { - var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}"; + var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 866ab39d1e..f7c01bb1f8 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -555,7 +555,7 @@ private static bool Contract_Call(ApplicationEngine engine) ExecutionContext context_new = engine.LoadScript(contract.Script, 1); context_new.GetState().ReadOnly = - currentManifest.SafeMethods.IsWildcard || currentManifest.SafeMethods.Contains(methodStr); + currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr); context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs index 967a10e856..06f4996753 100644 --- a/neo/SmartContract/Manifest/ContractManifest.cs +++ b/neo/SmartContract/Manifest/ContractManifest.cs @@ -53,10 +53,10 @@ public class ContractManifest : ISerializable public WildCardContainer Trusts { get; set; } /// - /// The safemethods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe. - /// If a method is marked as safe, the user interface will not give any warnings when it is called by any other contract. + /// The read only methods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe. + /// If a method is marked as read only, the user interface will not give any warnings when it is called by any other contract. /// - public WildCardContainer SafeMethods { get; set; } + public WildCardContainer ReadOnlyMethods { get; set; } /// /// Create Default Contract manifest @@ -77,7 +77,7 @@ public static ContractManifest CreateDefault(UInt160 hash) }, Features = ContractFeatures.NoProperty, Groups = new ContractGroup[0], - SafeMethods = WildCardContainer.Create(), + ReadOnlyMethods = WildCardContainer.Create(), Trusts = WildCardContainer.Create() }; } @@ -127,7 +127,7 @@ public JObject ToJson() json["abi"] = Abi.ToJson(); json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray(); json["trusts"] = Trusts.ToJson(); - json["safeMethods"] = SafeMethods.ToJson(); + json["readOnlyMethods"] = ReadOnlyMethods.ToJson(); return json; } @@ -161,7 +161,7 @@ private void DeserializeFromJson(JObject json) Features = ContractFeatures.NoProperty; Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson(u)).ToArray(); Trusts = WildCardContainer.FromJson(json["trusts"], u => UInt160.Parse(u.AsString())); - SafeMethods = WildCardContainer.FromJson(json["safeMethods"], u => u.AsString()); + ReadOnlyMethods = WildCardContainer.FromJson(json["readOnlyMethods"], u => u.AsString()); if (json["features"]["storage"].AsBoolean()) Features |= ContractFeatures.HasStorage; if (json["features"]["payable"].AsBoolean()) Features |= ContractFeatures.Payable; diff --git a/neo/SmartContract/Native/ContractMethodAttribute.cs b/neo/SmartContract/Native/ContractMethodAttribute.cs index 393737d92e..5f6d120e93 100644 --- a/neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/neo/SmartContract/Native/ContractMethodAttribute.cs @@ -10,7 +10,7 @@ internal class ContractMethodAttribute : Attribute public ContractParameterType ReturnType { get; } public ContractParameterType[] ParameterTypes { get; set; } = new ContractParameterType[0]; public string[] ParameterNames { get; set; } = new string[0]; - public bool SafeMethod { get; set; } = false; + public bool ReadOnly { get; set; } = false; public ContractMethodAttribute(long price, ContractParameterType returnType) { diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 08971dcf6b..603973806a 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -41,7 +41,7 @@ protected NativeContract() this.Hash = Script.ToScriptHash(); this.Manifest = ContractManifest.CreateDefault(this.Hash); List descriptors = new List(); - List safeMethods = new List(); + List readOnlyMethods = new List(); foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { ContractMethodAttribute attribute = method.GetCustomAttribute(); @@ -53,7 +53,7 @@ protected NativeContract() ReturnType = attribute.ReturnType, Parameters = attribute.ParameterTypes.Zip(attribute.ParameterNames, (t, n) => new ContractParameterDefinition { Type = t, Name = n }).ToArray() }); - if (attribute.SafeMethod) safeMethods.Add(name); + if (attribute.ReadOnly) readOnlyMethods.Add(name); methods.Add(name, new ContractMethodMetadata { Delegate = (Func)method.CreateDelegate(typeof(Func), this), @@ -61,7 +61,7 @@ protected NativeContract() }); } this.Manifest.Abi.Methods = descriptors.ToArray(); - this.Manifest.SafeMethods = WildCardContainer.Create(safeMethods.ToArray()); + this.Manifest.ReadOnlyMethods = WildCardContainer.Create(readOnlyMethods.ToArray()); contracts.Add(this); } @@ -120,7 +120,7 @@ protected virtual bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0, ContractParameterType.Array, Name = "supportedStandards", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.Array, Name = "supportedStandards", ReadOnly = true)] protected StackItem SupportedStandardsMethod(ApplicationEngine engine, VMArray args) { return SupportedStandards.Select(p => (StackItem)p).ToList(); diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index c5820cfd15..583f038e81 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -65,7 +65,7 @@ internal override bool Initialize(ApplicationEngine engine) return true; } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray args) { return GetMaxTransactionsPerBlock(engine.Snapshot); @@ -76,7 +76,7 @@ public uint GetMaxTransactionsPerBlock(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxTransactionsPerBlock)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetMaxBlockSize(ApplicationEngine engine, VMArray args) { return GetMaxBlockSize(engine.Snapshot); @@ -87,7 +87,7 @@ public uint GetMaxBlockSize(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxBlockSize)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] private StackItem GetFeePerByte(ApplicationEngine engine, VMArray args) { return GetFeePerByte(engine.Snapshot); @@ -98,7 +98,7 @@ public long GetFeePerByte(Snapshot snapshot) return BitConverter.ToInt64(snapshot.Storages[CreateStorageKey(Prefix_FeePerByte)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetBlockedAccounts(ApplicationEngine engine, VMArray args) { return GetBlockedAccounts(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index fd39979670..c36e08e481 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -52,7 +52,7 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, ReadOnly = true)] private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) { uint index = (uint)args[0].GetBigInteger(); diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index 7b28bd0923..493e615cbd 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -116,7 +116,7 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0_03000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "end" }, SafeMethod = true)] + [ContractMethod(0_03000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "end" }, ReadOnly = true)] private StackItem UnclaimedGas(ApplicationEngine engine, VMArray args) { UInt160 account = new UInt160(args[0].GetByteArray()); @@ -193,7 +193,7 @@ private StackItem Vote(ApplicationEngine engine, VMArray args) return true; } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args) { return GetRegisteredValidators(engine.Snapshot).Select(p => new Struct(new StackItem[] { p.PublicKey.ToArray(), p.Votes })).ToArray(); @@ -209,7 +209,7 @@ private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args )); } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetValidators(ApplicationEngine engine, VMArray args) { return GetValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); @@ -234,7 +234,7 @@ public ECPoint[] GetValidators(Snapshot snapshot) return GetRegisteredValidators(snapshot).Where(p => (p.Votes.Sign > 0) || sv.Contains(p.PublicKey)).OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(count).OrderBy(p => p).ToArray(); } - [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + [ContractMethod(1_00000000, ContractParameterType.Array, ReadOnly = true)] private StackItem GetNextBlockValidators(ApplicationEngine engine, VMArray args) { return GetNextBlockValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index 6a2e1c78b3..b770f3925d 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -113,25 +113,25 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, engine.SendNotification(Hash, new StackItem[] { "Transfer", account.ToArray(), StackItem.Null, amount }); } - [ContractMethod(0, ContractParameterType.String, Name = "name", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.String, Name = "name", ReadOnly = true)] protected StackItem NameMethod(ApplicationEngine engine, VMArray args) { return Name; } - [ContractMethod(0, ContractParameterType.String, Name = "symbol", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.String, Name = "symbol", ReadOnly = true)] protected StackItem SymbolMethod(ApplicationEngine engine, VMArray args) { return Symbol; } - [ContractMethod(0, ContractParameterType.Integer, Name = "decimals", SafeMethod = true)] + [ContractMethod(0, ContractParameterType.Integer, Name = "decimals", ReadOnly = true)] protected StackItem DecimalsMethod(ApplicationEngine engine, VMArray args) { return (uint)Decimals; } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ReadOnly = true)] protected StackItem TotalSupply(ApplicationEngine engine, VMArray args) { return TotalSupply(engine.Snapshot); @@ -144,7 +144,7 @@ public virtual BigInteger TotalSupply(Snapshot snapshot) return new BigInteger(storage.Value); } - [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, SafeMethod = true)] + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, ReadOnly = true)] protected StackItem BalanceOf(ApplicationEngine engine, VMArray args) { return BalanceOf(engine.Snapshot, new UInt160(args[0].GetByteArray())); From fe0986ac1b958d873da2d63cd3ba57dd74e426bc Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:24:33 +0200 Subject: [PATCH 11/21] Fix unit tests --- neo/SmartContract/InteropService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index f7c01bb1f8..d724656b14 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -554,8 +554,8 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = - currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr); + context_new.GetState().ReadOnly = currentManifest != null && + (currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr)); context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); From dd6537816cae92e0ad58e457e43f94a704784d3b Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:42:30 +0200 Subject: [PATCH 12/21] Refactor readOnly check --- neo/SmartContract/InteropDescriptor.cs | 12 +++++++----- neo/SmartContract/InteropService.NEO.cs | 9 +++------ neo/SmartContract/InteropService.cs | 17 ++++++++--------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/neo/SmartContract/InteropDescriptor.cs b/neo/SmartContract/InteropDescriptor.cs index c283e474de..6e5d48ea5a 100644 --- a/neo/SmartContract/InteropDescriptor.cs +++ b/neo/SmartContract/InteropDescriptor.cs @@ -11,25 +11,27 @@ internal class InteropDescriptor public long Price { get; } public Func, long> PriceCalculator { get; } public TriggerType AllowedTriggers { get; } + public bool RequireWriteAccess { get; } - public InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + public InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers, bool requireWriteAccess) + : this(method, handler, allowedTriggers, requireWriteAccess) { this.Price = price; } - public InteropDescriptor(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + public InteropDescriptor(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess) + : this(method, handler, allowedTriggers, requireWriteAccess) { this.PriceCalculator = priceCalculator; } - private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers) + private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers, bool requireWriteAccess) { this.Method = method; this.Hash = method.ToInteropMethodHash(); this.Handler = handler; this.AllowedTriggers = allowedTriggers; + this.RequireWriteAccess = requireWriteAccess; } public long GetPrice(RandomAccessStack stack) diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 833b14720d..0e64c74e10 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -17,7 +17,7 @@ namespace Neo.SmartContract { static partial class InteropService { - public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application); + public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application, true); public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000, TriggerType.All); public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice, TriggerType.All); public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400, TriggerType.Application); @@ -27,8 +27,8 @@ static partial class InteropService public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000, TriggerType.All); public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400, TriggerType.All); public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000, TriggerType.All); - public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application); - public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application); + public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application, true); + public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application, true); public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400, TriggerType.Application); public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400, TriggerType.Application); public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application); @@ -70,7 +70,6 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; - if (engine.CurrentContext.GetState().ReadOnly) return false; foreach (NativeContract contract in NativeContract.Contracts) { @@ -244,7 +243,6 @@ private static bool Account_IsStandard(ApplicationEngine engine) private static bool Contract_Create(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; @@ -269,7 +267,6 @@ private static bool Contract_Create(ApplicationEngine engine) private static bool Contract_Update(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index d724656b14..8696f22d77 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -55,8 +55,8 @@ public static partial class InteropService public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 0_00000400, TriggerType.Application); public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 0_00000400, TriggerType.All); public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application); - public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application); - public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application); + public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application, true); + public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application, true); public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application); public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application); public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application); @@ -88,19 +88,21 @@ internal static bool Invoke(ApplicationEngine engine, uint method) return false; if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger)) return false; + if (descriptor.RequireWriteAccess && engine.CurrentContext.GetState().ReadOnly) + return false; return descriptor.Handler(engine); } - private static uint Register(string method, Func handler, long price, TriggerType allowedTriggers) + private static uint Register(string method, Func handler, long price, TriggerType allowedTriggers, bool requireWriteAccess = false) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers, requireWriteAccess); methods.Add(descriptor.Hash, descriptor); return descriptor.Hash; } - private static uint Register(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers) + private static uint Register(string method, Func handler, Func, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess = false) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requireWriteAccess); methods.Add(descriptor.Hash, descriptor); return descriptor.Hash; } @@ -471,7 +473,6 @@ private static bool Transaction_GetHash(ApplicationEngine engine) private static bool Storage_GetContext(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { ScriptHash = engine.CurrentScriptHash, @@ -564,8 +565,6 @@ private static bool Contract_Call(ApplicationEngine engine) private static bool Contract_Destroy(ApplicationEngine engine) { - if (engine.CurrentContext.GetState().ReadOnly) return false; - UInt160 hash = engine.CurrentScriptHash; ContractState contract = engine.Snapshot.Contracts.TryGet(hash); if (contract == null) return true; From 288c9ffe5e0e3b3af7ef4fc5c8fc3fdf0163736e Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 22 Aug 2019 22:43:48 +0200 Subject: [PATCH 13/21] Reduce changes --- neo/SmartContract/InteropService.NEO.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 0e64c74e10..036b700ccb 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -70,7 +70,6 @@ private static long GetDeploymentPrice(RandomAccessStack stack) private static bool Native_Deploy(ApplicationEngine engine) { if (engine.Snapshot.PersistingBlock.Index != 0) return false; - foreach (NativeContract contract in NativeContract.Contracts) { engine.Snapshot.Contracts.Add(contract.Hash, new ContractState From a7e4485ffbe42584314f8f75398252d0b6471ab6 Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 23 Aug 2019 15:17:33 +0200 Subject: [PATCH 14/21] Merge --- neo/SmartContract/ApplicationEngine.cs | 131 ++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index b58c16d9b3..3b55cc5414 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -1,4 +1,54 @@ - +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.VM; +using System; +using System.Collections.Generic; + +namespace Neo.SmartContract +{ + public partial class ApplicationEngine : ExecutionEngine + { + public static event EventHandler Notify; + public static event EventHandler Log; + + public const long GasFree = 0; + private readonly long gas_amount; + private readonly bool testMode; + private readonly List notifications = new List(); + private readonly List disposables = new List(); + + public TriggerType Trigger { get; } + public IVerifiable ScriptContainer { get; } + public Snapshot Snapshot { get; } + public long GasConsumed { get; private set; } = 0; + public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; + public UInt160 CallingScriptHash => InvocationStack.Count > 1 ? InvocationStack.Peek(1).GetState().ScriptHash : null; + public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; + public IReadOnlyList Notifications => notifications; + internal Dictionary InvocationCounter { get; } = new Dictionary(); + + public ApplicationEngine(TriggerType trigger, IVerifiable container, Snapshot snapshot, long gas, bool testMode = false) + { + this.gas_amount = GasFree + gas; + this.testMode = testMode; + this.Trigger = trigger; + this.ScriptContainer = container; + this.Snapshot = snapshot; + } + + internal T AddDisposable(T disposable) where T : IDisposable + { + disposables.Add(disposable); + return disposable; + } + + private bool AddGas(long gas) + { + GasConsumed = checked(GasConsumed + gas); + return testMode || GasConsumed <= gas_amount; + } + protected override void LoadContext(ExecutionContext context) { // Set default execution context state @@ -8,4 +58,81 @@ protected override void LoadContext(ExecutionContext context) ScriptHash = ((byte[])context.Script).ToScriptHash() }); - base.LoadContext(context); \ No newline at end of file + base.LoadContext(context); + } + + public override void Dispose() + { + foreach (IDisposable disposable in disposables) + disposable.Dispose(); + disposables.Clear(); + base.Dispose(); + } + + protected override bool OnSysCall(uint method) + { + if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack))) + return false; + return InteropService.Invoke(this, method); + } + + protected override bool PreExecuteInstruction() + { + if (CurrentContext.InstructionPointer >= CurrentContext.Script.Length) + return true; + return AddGas(OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); + } + + private static Block CreateDummyBlock(Snapshot snapshot) + { + var currentBlock = snapshot.Blocks[snapshot.CurrentBlockHash]; + return new Block + { + Version = 0, + PrevHash = snapshot.CurrentBlockHash, + MerkleRoot = new UInt256(), + Timestamp = currentBlock.Timestamp + Blockchain.MillisecondsPerBlock, + Index = snapshot.Height + 1, + NextConsensus = currentBlock.NextConsensus, + Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + }, + ConsensusData = new ConsensusData(), + Transactions = new Transaction[0] + }; + } + + public static ApplicationEngine Run(byte[] script, Snapshot snapshot, + IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) + { + snapshot.PersistingBlock = persistingBlock ?? snapshot.PersistingBlock ?? CreateDummyBlock(snapshot); + ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, snapshot, extraGAS, testMode); + engine.LoadScript(script); + engine.Execute(); + return engine; + } + + public static ApplicationEngine Run(byte[] script, IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) + { + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + { + return Run(script, snapshot, container, persistingBlock, testMode, extraGAS); + } + } + + internal void SendLog(UInt160 script_hash, string message) + { + LogEventArgs log = new LogEventArgs(ScriptContainer, script_hash, message); + Log?.Invoke(this, log); + } + + internal void SendNotification(UInt160 script_hash, StackItem state) + { + NotifyEventArgs notification = new NotifyEventArgs(ScriptContainer, script_hash, state); + Notify?.Invoke(this, notification); + notifications.Add(notification); + } + } +} \ No newline at end of file From a2e48d2db18b2123a253306d420be76ff79dce66 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sat, 24 Aug 2019 09:05:44 +0200 Subject: [PATCH 15/21] Remove change file --- neo/SmartContract/ApplicationEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 59e5f72257..45763ed30a 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -135,4 +135,4 @@ internal void SendNotification(UInt160 script_hash, StackItem state) notifications.Add(notification); } } -} \ No newline at end of file +} From 38e2a1090b25105beadd7b0771194859c2a8765c Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 25 Aug 2019 21:46:39 +0200 Subject: [PATCH 16/21] Fix contract manifest --- neo/SmartContract/InteropService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index da76b544e7..7b3f65c705 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -555,8 +555,8 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = currentManifest != null && - (currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr)); + context_new.GetState().ReadOnly = + (contract.Manifest.ReadOnlyMethods.IsWildcard || contract.Manifest.ReadOnlyMethods.Contains(methodStr)); context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); From 1ea153851caa86bfd53ce03255b516027905e523 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 25 Aug 2019 21:47:52 +0200 Subject: [PATCH 17/21] Add null check --- neo/SmartContract/InteropService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 7b3f65c705..85add93fd8 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -535,7 +535,7 @@ private static bool Contract_Call(ApplicationEngine engine) contract = _interface; else contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractOrHash.GetByteArray())); - if (contract is null) return false; + if (contract == null || contract.Manifest == null) return false; StackItem method = engine.CurrentContext.EvaluationStack.Pop(); StackItem args = engine.CurrentContext.EvaluationStack.Pop(); @@ -555,7 +555,7 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = + context_new.GetState().ReadOnly = (contract.Manifest.ReadOnlyMethods.IsWildcard || contract.Manifest.ReadOnlyMethods.Contains(methodStr)); context_new.EvaluationStack.Push(args); From e8ab57d2d3bc5692b58c61737aab1e01dc411b2e Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 25 Aug 2019 21:51:27 +0200 Subject: [PATCH 18/21] Prevent null manifest contracts --- neo.UnitTests/SmartContract/UT_Syscalls.cs | 5 +++++ neo/SmartContract/InteropService.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/neo.UnitTests/SmartContract/UT_Syscalls.cs b/neo.UnitTests/SmartContract/UT_Syscalls.cs index c80899b8f8..6a366b0877 100644 --- a/neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Ledger; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using Neo.VM; using System.Linq; @@ -27,6 +28,10 @@ public void System_Runtime_GetInvocationCounter() var contractB = new ContractState() { Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP, (byte)OpCode.NOP }.Concat(script.ToArray()).ToArray() }; var contractC = new ContractState() { Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP, (byte)OpCode.NOP, (byte)OpCode.NOP }.Concat(script.ToArray()).ToArray() }; + contractA.Manifest = ContractManifest.CreateDefault(contractA.ScriptHash); + contractB.Manifest = ContractManifest.CreateDefault(contractB.ScriptHash); + contractC.Manifest = ContractManifest.CreateDefault(contractC.ScriptHash); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractA.ScriptHash.ToArray())); contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractB.ScriptHash.ToArray())); contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractC.ScriptHash.ToArray())); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 85add93fd8..e7edce3a50 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -535,7 +535,7 @@ private static bool Contract_Call(ApplicationEngine engine) contract = _interface; else contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractOrHash.GetByteArray())); - if (contract == null || contract.Manifest == null) return false; + if (contract?.Manifest == null) return false; StackItem method = engine.CurrentContext.EvaluationStack.Pop(); StackItem args = engine.CurrentContext.EvaluationStack.Pop(); From c2722ce8cbd6d6f2970f2f4b12d8d950810eb15e Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 9 Sep 2019 09:41:49 +0200 Subject: [PATCH 19/21] Refactor ReadOnly --- .../Manifest/UT_ContractManifest.cs | 14 +++++++------- neo/SmartContract/InteropService.cs | 3 +-- neo/SmartContract/Manifest/ContractAbi.cs | 17 +++++++++++++++++ neo/SmartContract/Manifest/ContractManifest.cs | 9 --------- .../Manifest/ContractMethodDescriptor.cs | 10 +++++++++- neo/SmartContract/Native/NativeContract.cs | 4 +--- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 96b46bb226..9bafee1c30 100644 --- a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -10,7 +10,7 @@ public class UT_ContractManifest [TestMethod] public void ParseFromJson_Default() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -21,7 +21,7 @@ public void ParseFromJson_Default() [TestMethod] public void ParseFromJson_Features() { - var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToJson().ToString(), json); @@ -33,7 +33,7 @@ public void ParseFromJson_Features() [TestMethod] public void ParseFromJson_Permissions() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""readOnlyMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -52,19 +52,19 @@ public void ParseFromJson_Permissions() [TestMethod] public void ParseFromJson_ReadOnlyMethods() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[""balanceOf""]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":true},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); var check = ContractManifest.CreateDefault(UInt160.Zero); - check.ReadOnlyMethods = WildCardContainer.Create("balanceOf"); + check.Abi.EntryPoint.ReadOnly = true; Assert.AreEqual(manifest.ToString(), check.ToString()); } [TestMethod] public void ParseFromJson_Trust() { - var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""readOnlyMethods"":[]}"; + var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -76,7 +76,7 @@ public void ParseFromJson_Trust() [TestMethod] public void ParseFromJson_Groups() { - var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}"; + var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index d8d428f3ce..757425e741 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -562,8 +562,7 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = - (contract.Manifest.ReadOnlyMethods.IsWildcard || contract.Manifest.ReadOnlyMethods.Contains(methodStr)); + context_new.GetState().ReadOnly = contract.Manifest.Abi.FindMethod(methodStr).ReadOnly; context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); diff --git a/neo/SmartContract/Manifest/ContractAbi.cs b/neo/SmartContract/Manifest/ContractAbi.cs index 1d27ae4f1b..15f23a62f7 100644 --- a/neo/SmartContract/Manifest/ContractAbi.cs +++ b/neo/SmartContract/Manifest/ContractAbi.cs @@ -1,4 +1,5 @@ using Neo.IO.Json; +using System; using System.Linq; namespace Neo.SmartContract.Manifest @@ -53,5 +54,21 @@ public JObject ToJson() json["events"] = new JArray(Events.Select(u => u.ToJson()).ToArray()); return json; } + + /// + /// Find a method + /// + /// Name + /// ContractMethodDescriptor + public ContractMethodDescriptor FindMethod(string name) + { + foreach (var method in Methods) + { + if (method.Name == name) return method; + } + + if (EntryPoint.Name == name) return EntryPoint; + return null; + } } } diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs index 917221ce9c..3c47ccd9e3 100644 --- a/neo/SmartContract/Manifest/ContractManifest.cs +++ b/neo/SmartContract/Manifest/ContractManifest.cs @@ -52,12 +52,6 @@ public class ContractManifest : ISerializable /// public WildCardContainer Trusts { get; set; } - /// - /// The read only methods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe. - /// If a method is marked as read only, the user interface will not give any warnings when it is called by any other contract. - /// - public WildCardContainer ReadOnlyMethods { get; set; } - /// /// Create Default Contract manifest /// @@ -77,7 +71,6 @@ public static ContractManifest CreateDefault(UInt160 hash) }, Features = ContractFeatures.NoProperty, Groups = new ContractGroup[0], - ReadOnlyMethods = WildCardContainer.Create(), Trusts = WildCardContainer.Create() }; } @@ -127,7 +120,6 @@ public JObject ToJson() json["abi"] = Abi.ToJson(); json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray(); json["trusts"] = Trusts.ToJson(); - json["readOnlyMethods"] = ReadOnlyMethods.ToJson(); return json; } @@ -161,7 +153,6 @@ private void DeserializeFromJson(JObject json) Features = ContractFeatures.NoProperty; Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson(u)).ToArray(); Trusts = WildCardContainer.FromJson(json["trusts"], u => UInt160.Parse(u.AsString())); - ReadOnlyMethods = WildCardContainer.FromJson(json["readOnlyMethods"], u => u.AsString()); if (json["features"]["storage"].AsBoolean()) Features |= ContractFeatures.HasStorage; if (json["features"]["payable"].AsBoolean()) Features |= ContractFeatures.Payable; diff --git a/neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs index 476ec546a6..286308049e 100644 --- a/neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -25,7 +25,8 @@ public class ContractMethodDescriptor : ContractEventDescriptor Type = ContractParameterType.Array } }, - ReturnType = ContractParameterType.Any + ReturnType = ContractParameterType.Any, + ReadOnly = false }; /// @@ -34,6 +35,11 @@ public class ContractMethodDescriptor : ContractEventDescriptor /// public ContractParameterType ReturnType { get; set; } + /// + /// If a method is marked as read only, the user interface will not give any warnings when it is called by any other contract. + /// + public bool ReadOnly { get; set; } + /// /// Parse ContractMethodDescription from json /// @@ -46,6 +52,7 @@ public class ContractMethodDescriptor : ContractEventDescriptor Name = json["name"].AsString(), Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(), ReturnType = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["returnType"].AsString()), + ReadOnly = json["readOnly"].AsBoolean(), }; } @@ -53,6 +60,7 @@ public override JObject ToJson() { var json = base.ToJson(); json["returnType"] = ReturnType.ToString(); + json["readOnly"] = ReadOnly; return json; } } diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 2a727355a2..4eda5f5041 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -41,7 +41,6 @@ protected NativeContract() this.Hash = Script.ToScriptHash(); this.Manifest = ContractManifest.CreateDefault(this.Hash); List descriptors = new List(); - List readOnlyMethods = new List(); foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { ContractMethodAttribute attribute = method.GetCustomAttribute(); @@ -51,9 +50,9 @@ protected NativeContract() { Name = name, ReturnType = attribute.ReturnType, + ReadOnly = attribute.ReadOnly, Parameters = attribute.ParameterTypes.Zip(attribute.ParameterNames, (t, n) => new ContractParameterDefinition { Type = t, Name = n }).ToArray() }); - if (attribute.ReadOnly) readOnlyMethods.Add(name); methods.Add(name, new ContractMethodMetadata { Delegate = (Func)method.CreateDelegate(typeof(Func), this), @@ -61,7 +60,6 @@ protected NativeContract() }); } this.Manifest.Abi.Methods = descriptors.ToArray(); - this.Manifest.ReadOnlyMethods = WildCardContainer.Create(readOnlyMethods.ToArray()); contracts.Add(this); } From c8cd01651afab0c4dd41495a57cd2e8c648232a2 Mon Sep 17 00:00:00 2001 From: Shargon Date: Tue, 10 Sep 2019 07:37:08 +0200 Subject: [PATCH 20/21] ReadOnly if is specified --- neo/SmartContract/InteropService.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 757425e741..037d856fbb 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -562,10 +562,16 @@ private static bool Contract_Call(ApplicationEngine engine) } ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().ReadOnly = contract.Manifest.Abi.FindMethod(methodStr).ReadOnly; - context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); + + // Check readOnly flag + var abiMethod = contract.Manifest.Abi.FindMethod(methodStr); + if (abiMethod != null && abiMethod.ReadOnly) + { + context_new.GetState().ReadOnly = true; + } + return true; } From fca084454e356859ff2d0317d5535167a314baba Mon Sep 17 00:00:00 2001 From: Shargon Date: Tue, 10 Sep 2019 07:50:43 +0200 Subject: [PATCH 21/21] UT --- neo.UnitTests/SmartContract/UT_Syscalls.cs | 61 ++++++++++++++++++++++ neo/SmartContract/Manifest/ContractAbi.cs | 3 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/neo.UnitTests/SmartContract/UT_Syscalls.cs b/neo.UnitTests/SmartContract/UT_Syscalls.cs index 3ca4867478..059562ee1e 100644 --- a/neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -10,6 +10,62 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_Syscalls { + [TestMethod] + public void System_Storage_GetContext() + { + var snapshot = TestBlockchain.GetStore().GetSnapshot(); + var contracts = (TestDataCache)snapshot.Contracts; + + // Call System_Storage_GetContext syscall + + var script = new ScriptBuilder(); + script.EmitSysCall(InteropService.System_Storage_GetContext); + + var contract = new ContractState() { Script = script.ToArray() }; + contract.Manifest = ContractManifest.CreateDefault(contract.ScriptHash); + + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contract.ScriptHash.ToArray())); + contracts.Add(contract.ScriptHash, contract); + + // Call Contract + + script = new ScriptBuilder(); + script.EmitSysCall(InteropService.System_Contract_Call, contract.ScriptHash.ToArray(), "", 0); + + // Execute + + var engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, true); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(engine.Execute(), VMState.HALT); + + // Check the results + + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsInstanceOfType(engine.ResultStack.Peek(), typeof(VM.Types.InteropInterface)); + + var context = ((VM.Types.InteropInterface)engine.ResultStack.Pop()).GetInterface(); + Assert.AreEqual(context.ScriptHash, contract.ScriptHash); + + // Change to ReadOnly Abi + + contract.Manifest.Abi.EntryPoint.ReadOnly = true; + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contract.ScriptHash.ToArray())); + contracts.Add(contract.ScriptHash, contract); + + // Execute + + engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, true); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(engine.Execute(), VMState.FAULT); + + // Check the results + + Assert.AreEqual(0, engine.ResultStack.Count); + + // Clean + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contract.ScriptHash.ToArray())); + } + [TestMethod] public void System_Runtime_GetInvocationCounter() { @@ -66,6 +122,11 @@ public void System_Runtime_GetInvocationCounter() 1 /* C */ } ); + + // Clean + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractA.ScriptHash.ToArray())); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractB.ScriptHash.ToArray())); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractC.ScriptHash.ToArray())); } } } diff --git a/neo/SmartContract/Manifest/ContractAbi.cs b/neo/SmartContract/Manifest/ContractAbi.cs index 15f23a62f7..747c599d3f 100644 --- a/neo/SmartContract/Manifest/ContractAbi.cs +++ b/neo/SmartContract/Manifest/ContractAbi.cs @@ -67,8 +67,7 @@ public ContractMethodDescriptor FindMethod(string name) if (method.Name == name) return method; } - if (EntryPoint.Name == name) return EntryPoint; - return null; + return EntryPoint; } } }