diff --git a/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index a96c3ff4c5..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"":[],""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"",""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"":[],""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"",""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"":[],""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"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[]}"; 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"",""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.SafeMethods = 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""],""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"",""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"":[],""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"",""readOnly"":false},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[]}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); diff --git a/neo.UnitTests/SmartContract/UT_Syscalls.cs b/neo.UnitTests/SmartContract/UT_Syscalls.cs index 65066b9079..059562ee1e 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; @@ -9,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() { @@ -27,6 +84,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())); @@ -61,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/ExecutionContextState.cs b/neo/SmartContract/ExecutionContextState.cs index d26641f7dd..121d293389 100644 --- a/neo/SmartContract/ExecutionContextState.cs +++ b/neo/SmartContract/ExecutionContextState.cs @@ -1,4 +1,4 @@ -namespace Neo.SmartContract +namespace Neo.SmartContract { public class ExecutionContextState { @@ -6,5 +6,10 @@ public class ExecutionContextState /// Script hash /// public UInt160 ScriptHash { get; set; } + + /// + /// Is read only + /// + public bool ReadOnly { get; set; } = false; } } diff --git a/neo/SmartContract/InteropDescriptor.cs b/neo/SmartContract/InteropDescriptor.cs index 6db599ea64..4e24f7102b 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 7f0d857fe9..b00fdabe02 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); diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 7aa7fd7284..037d856fbb 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); @@ -93,19 +93,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; } @@ -540,13 +542,14 @@ 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?.Manifest == null) return false; 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)) @@ -561,6 +564,14 @@ private static bool Contract_Call(ApplicationEngine engine) ExecutionContext context_new = engine.LoadScript(contract.Script, 1); 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; } diff --git a/neo/SmartContract/Manifest/ContractAbi.cs b/neo/SmartContract/Manifest/ContractAbi.cs index 1d27ae4f1b..747c599d3f 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,20 @@ 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; + } + + return EntryPoint; + } } } diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs index ebbcd61f2c..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 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. - /// - public WildCardContainer SafeMethods { get; set; } - /// /// Create Default Contract manifest /// @@ -77,7 +71,6 @@ public static ContractManifest CreateDefault(UInt160 hash) }, Features = ContractFeatures.NoProperty, Groups = new ContractGroup[0], - SafeMethods = 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["safeMethods"] = SafeMethods.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())); - SafeMethods = WildCardContainer.FromJson(json["safeMethods"], 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/ContractMethodAttribute.cs b/neo/SmartContract/Native/ContractMethodAttribute.cs index 42b414eed6..b304e2fcee 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 546743fc49..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 safeMethods = 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.SafeMethod) safeMethods.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.SafeMethods = WildCardContainer.Create(safeMethods.ToArray()); contracts.Add(this); } @@ -120,7 +118,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 3ffe8199e5..7eea824fa6 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 7ef522dab7..c0913282da 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 a95885a665..a17bbc8d2b 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()));