From af77daf317f95af111f363b0a7bfa31418add6f7 Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 24 May 2019 05:39:11 +0200 Subject: [PATCH 1/8] Feature manifest and permission system (#766) --- neo.UnitTests/UT_ContractManifest.cs | 88 +++++++++ neo.UnitTests/UT_InteropPrices.cs | 19 +- neo/Ledger/ContractState.cs | 21 +- neo/SmartContract/ApplicationEngine.cs | 7 +- neo/SmartContract/ContractParameterType.cs | 1 + neo/SmartContract/InteropService.NEO.cs | 62 ++++-- neo/SmartContract/InteropService.cs | 22 ++- neo/SmartContract/Manifest/ContractAbi.cs | 57 ++++++ .../Manifest/ContractEventDescriptor.cs | 40 ++++ .../Manifest/ContractFeatures.cs} | 4 +- neo/SmartContract/Manifest/ContractGroup.cs | 55 ++++++ .../Manifest/ContractManifest.cs | 180 ++++++++++++++++++ .../Manifest/ContractMethodDescriptor.cs | 59 ++++++ .../Manifest/ContractParameterDefinition.cs | 41 ++++ .../Manifest/ContractPermission.cs | 74 +++++++ .../Manifest/ContractPermissionDescriptor.cs | 56 ++++++ .../Manifest/WildCardContainer.cs | 76 ++++++++ neo/SmartContract/Native/NativeContract.cs | 21 +- neo/SmartContract/Native/PolicyContract.cs | 122 +++++++++++- neo/SmartContract/Native/Tokens/GasToken.cs | 30 ++- neo/SmartContract/Native/Tokens/NeoToken.cs | 76 +++++++- neo/SmartContract/Native/Tokens/Nep5Token.cs | 100 +++++++++- 22 files changed, 1141 insertions(+), 70 deletions(-) create mode 100644 neo.UnitTests/UT_ContractManifest.cs create mode 100644 neo/SmartContract/Manifest/ContractAbi.cs create mode 100644 neo/SmartContract/Manifest/ContractEventDescriptor.cs rename neo/{Ledger/ContractPropertyState.cs => SmartContract/Manifest/ContractFeatures.cs} (61%) create mode 100644 neo/SmartContract/Manifest/ContractGroup.cs create mode 100644 neo/SmartContract/Manifest/ContractManifest.cs create mode 100644 neo/SmartContract/Manifest/ContractMethodDescriptor.cs create mode 100644 neo/SmartContract/Manifest/ContractParameterDefinition.cs create mode 100644 neo/SmartContract/Manifest/ContractPermission.cs create mode 100644 neo/SmartContract/Manifest/ContractPermissionDescriptor.cs create mode 100644 neo/SmartContract/Manifest/WildCardContainer.cs diff --git a/neo.UnitTests/UT_ContractManifest.cs b/neo.UnitTests/UT_ContractManifest.cs new file mode 100644 index 0000000000..451791e9bd --- /dev/null +++ b/neo.UnitTests/UT_ContractManifest.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.SmartContract.Manifest; + +namespace Neo.UnitTests +{ + [TestClass] + 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 manifest = ContractManifest.Parse(json); + + Assert.AreEqual(manifest.ToString(), json); + Assert.AreEqual(manifest.ToString(), ContractManifest.CreateDefault(UInt160.Zero).ToString()); + Assert.IsTrue(manifest.IsValid(UInt160.Zero)); + } + + [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 manifest = ContractManifest.Parse(json); + Assert.AreEqual(manifest.ToJson().ToString(), json); + + var check = ContractManifest.CreateDefault(UInt160.Zero); + check.Features = ContractFeatures.HasStorage | ContractFeatures.Payable; + Assert.AreEqual(manifest.ToString(), check.ToString()); + } + + [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 manifest = ContractManifest.Parse(json); + Assert.AreEqual(manifest.ToString(), json); + + var check = ContractManifest.CreateDefault(UInt160.Zero); + check.Permissions = new[] + { + new ContractPermission() + { + Contract = ContractPermissionDescriptor.Create(UInt160.Zero), + Methods = WildCardContainer.Create("method1", "method2") + } + }; + Assert.AreEqual(manifest.ToString(), check.ToString()); + } + + [TestMethod] + public void ParseFromJson_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"":[],""safeMethods"":[""balanceOf""]}"; + var manifest = ContractManifest.Parse(json); + Assert.AreEqual(manifest.ToString(), json); + + var check = ContractManifest.CreateDefault(UInt160.Zero); + check.SafeMethods = 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 manifest = ContractManifest.Parse(json); + Assert.AreEqual(manifest.ToString(), json); + + var check = ContractManifest.CreateDefault(UInt160.Zero); + check.Trusts = WildCardContainer.Create(UInt160.Parse("0x0000000000000000000000000000000000000001")); + Assert.AreEqual(manifest.ToString(), check.ToString()); + } + + [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 manifest = ContractManifest.Parse(json); + Assert.AreEqual(manifest.ToString(), json); + + var check = ContractManifest.CreateDefault(UInt160.Zero); + check.Groups = new ContractGroup[] { new ContractGroup() { PubKey = ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), Signature = "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141".HexToBytes() } }; + Assert.AreEqual(manifest.ToString(), check.ToString()); + } + } +} diff --git a/neo.UnitTests/UT_InteropPrices.cs b/neo.UnitTests/UT_InteropPrices.cs index c46c00811a..c570e86efc 100644 --- a/neo.UnitTests/UT_InteropPrices.cs +++ b/neo.UnitTests/UT_InteropPrices.cs @@ -1,10 +1,10 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using Neo.VM; -using System.Reflection; using System; +using System.Reflection; namespace Neo.UnitTests { @@ -106,7 +106,7 @@ public void ApplicationEngineVariablePrices() MethodInfo GetPriceForSysCall = typeof(ApplicationEngine).GetMethod("GetPriceForSysCall", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[]{}, null); // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - byte[] SyscallContractCreateHash00 = new byte[]{(byte)ContractPropertyState.NoProperty, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e}; + byte[] SyscallContractCreateHash00 = new byte[]{(byte)ContractFeatures.NoProperty, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e}; using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) { Debugger debugger = new Debugger(ae); @@ -131,19 +131,6 @@ public void ApplicationEngineVariablePrices() GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(500L * 100000000L / 100000); // assuming private ae.ratio = 100000 } - // Neo.Contract.Migrate: 471b6290 (requires push properties on fourth position) - byte[] SyscallContractMigrateHash00 = new byte[]{(byte)ContractPropertyState.NoProperty, 0x00, 0x00, 0x00, 0x68, 0x47, 0x1b, 0x62, 0x90}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) - { - Debugger debugger = new Debugger(ae); - ae.LoadScript(SyscallContractMigrateHash00); - debugger.StepInto(); // push 0 - ContractPropertyState.NoProperty - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(100L * 100000000L / 100000); // assuming private ae.ratio = 100000 - } - // System.Storage.Put: e63f1884 (requires push key and value) byte[] SyscallStoragePutHash = new byte[]{0x53, 0x53, 0x00, 0x68, 0xe6, 0x3f, 0x18, 0x84}; using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) diff --git a/neo/Ledger/ContractState.cs b/neo/Ledger/ContractState.cs index 3501f43359..8f97d4b363 100644 --- a/neo/Ledger/ContractState.cs +++ b/neo/Ledger/ContractState.cs @@ -1,6 +1,7 @@ using Neo.IO; using Neo.IO.Json; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using System.IO; namespace Neo.Ledger @@ -8,10 +9,10 @@ namespace Neo.Ledger public class ContractState : ICloneable, ISerializable { public byte[] Script; - public ContractPropertyState ContractProperties; + public ContractManifest Manifest; - public bool HasStorage => ContractProperties.HasFlag(ContractPropertyState.HasStorage); - public bool Payable => ContractProperties.HasFlag(ContractPropertyState.Payable); + public bool HasStorage => Manifest.Features.HasFlag(ContractFeatures.HasStorage); + public bool Payable => Manifest.Features.HasFlag(ContractFeatures.Payable); private UInt160 _scriptHash; public UInt160 ScriptHash @@ -26,33 +27,33 @@ public UInt160 ScriptHash } } - int ISerializable.Size => Script.GetVarSize() + sizeof(ContractParameterType); + int ISerializable.Size => Script.GetVarSize() + Manifest.ToJson().ToString().GetVarSize(); ContractState ICloneable.Clone() { return new ContractState { Script = Script, - ContractProperties = ContractProperties + Manifest = Manifest.Clone() }; } void ISerializable.Deserialize(BinaryReader reader) { Script = reader.ReadVarBytes(); - ContractProperties = (ContractPropertyState)reader.ReadByte(); + Manifest = reader.ReadSerializable(); } void ICloneable.FromReplica(ContractState replica) { Script = replica.Script; - ContractProperties = replica.ContractProperties; + Manifest = replica.Manifest.Clone(); } void ISerializable.Serialize(BinaryWriter writer) { writer.WriteVarBytes(Script); - writer.Write((byte)ContractProperties); + writer.Write(Manifest); } public JObject ToJson() @@ -60,9 +61,7 @@ public JObject ToJson() JObject json = new JObject(); json["hash"] = ScriptHash.ToString(); json["script"] = Script.ToHexString(); - json["properties"] = new JObject(); - json["properties"]["storage"] = HasStorage; - json["properties"]["payable"] = Payable; + json["manifest"] = Manifest.ToJson(); return json; } } diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 5e4f8c6ce1..ff68377d2d 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -1,6 +1,7 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; using System; @@ -101,13 +102,13 @@ protected virtual long GetPriceForSysCall() return 100 * n; } if (method == InteropService.Neo_Contract_Create || - method == InteropService.Neo_Contract_Migrate) + method == InteropService.Neo_Contract_Update) { long fee = 100L; - ContractPropertyState contract_properties = (ContractPropertyState)(byte)CurrentContext.EvaluationStack.Peek(3).GetBigInteger(); + ContractFeatures contract_properties = (ContractFeatures)(byte)CurrentContext.EvaluationStack.Peek(3).GetBigInteger(); - if (contract_properties.HasFlag(ContractPropertyState.HasStorage)) + if (contract_properties.HasFlag(ContractFeatures.HasStorage)) { fee += 400L; } diff --git a/neo/SmartContract/ContractParameterType.cs b/neo/SmartContract/ContractParameterType.cs index 4e743431cb..67191852fa 100644 --- a/neo/SmartContract/ContractParameterType.cs +++ b/neo/SmartContract/ContractParameterType.cs @@ -16,6 +16,7 @@ public enum ContractParameterType : byte InteropInterface = 0xf0, + Any = 0xfe, Void = 0xff } } diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 849c4a38f4..8e0144c84f 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -4,6 +4,7 @@ using Neo.Network.P2P.Payloads; using Neo.SmartContract.Enumerators; using Neo.SmartContract.Iterators; +using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; @@ -27,7 +28,7 @@ static partial class InteropService public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 100); public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 100); public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create); - public static readonly uint Neo_Contract_Migrate = Register("Neo.Contract.Migrate", Contract_Migrate); + public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update); public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 1); public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 1); public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 1); @@ -56,7 +57,7 @@ private static bool Native_Deploy(ApplicationEngine engine) engine.Snapshot.Contracts.Add(contract.Hash, new ContractState { Script = contract.Script, - ContractProperties = contract.Properties + Manifest = contract.Manifest }); contract.Initialize(engine); } @@ -226,45 +227,57 @@ private static bool Contract_Create(ApplicationEngine engine) if (engine.Trigger != TriggerType.Application) return false; byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; - ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + + var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); + if (manifest.Length > ContractManifest.MaxLength) return false; + UInt160 hash = script.ToScriptHash(); ContractState contract = engine.Snapshot.Contracts.TryGet(hash); - if (contract == null) + if (contract != null) return false; + contract = new ContractState { - contract = new ContractState - { - Script = script, - ContractProperties = contract_properties - }; - engine.Snapshot.Contracts.Add(hash, contract); - } + Script = script, + Manifest = ContractManifest.Parse(manifest) + }; + + if (!contract.Manifest.IsValid(hash)) return false; + + engine.Snapshot.Contracts.Add(hash, contract); engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); return true; } - private static bool Contract_Migrate(ApplicationEngine engine) + private static bool Contract_Update(ApplicationEngine engine) { if (engine.Trigger != TriggerType.Application) return false; + byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); if (script.Length > 1024 * 1024) return false; - ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - UInt160 hash = script.ToScriptHash(); - ContractState contract = engine.Snapshot.Contracts.TryGet(hash); - if (contract == null) + var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString(); + if (manifest.Length > ContractManifest.MaxLength) return false; + + var contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash); + if (contract is null) return false; + + if (script.Length > 0) { + UInt160 hash_new = script.ToScriptHash(); + if (hash_new.Equals(engine.CurrentScriptHash)) return false; + if (engine.Snapshot.Contracts.TryGet(hash_new) != null) return false; contract = new ContractState { Script = script, - ContractProperties = contract_properties + Manifest = contract.Manifest }; - engine.Snapshot.Contracts.Add(hash, contract); + contract.Manifest.Abi.Hash = hash_new; + engine.Snapshot.Contracts.Add(hash_new, contract); if (contract.HasStorage) { foreach (var pair in engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).ToArray()) { engine.Snapshot.Storages.Add(new StorageKey { - ScriptHash = hash, + ScriptHash = hash_new, Key = pair.Key.Key }, new StorageItem { @@ -273,9 +286,16 @@ private static bool Contract_Migrate(ApplicationEngine engine) }); } } + Contract_Destroy(engine); } - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); - return Contract_Destroy(engine); + if (manifest.Length > 0) + { + contract = engine.Snapshot.Contracts.GetAndChange(contract.ScriptHash); + contract.Manifest = ContractManifest.Parse(manifest); + if (!contract.Manifest.IsValid(contract.ScriptHash)) return false; + } + + return true; } private static bool Contract_GetScript(ApplicationEngine engine) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 6b68bf2359..c5de54c7ba 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -5,6 +5,7 @@ using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -472,18 +473,25 @@ private static bool StorageContext_AsReadOnly(ApplicationEngine engine) private static bool Contract_Call(ApplicationEngine engine) { - StackItem item0 = engine.CurrentContext.EvaluationStack.Pop(); + StackItem contractOrHash = engine.CurrentContext.EvaluationStack.Pop(); + ContractState contract; - if (item0 is InteropInterface _interface) + if (contractOrHash is InteropInterface _interface) contract = _interface; else - contract = engine.Snapshot.Contracts.TryGet(new UInt160(item0.GetByteArray())); + contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractOrHash.GetByteArray())); if (contract is null) return false; - StackItem item1 = engine.CurrentContext.EvaluationStack.Pop(); - StackItem item2 = engine.CurrentContext.EvaluationStack.Pop(); + + StackItem method = engine.CurrentContext.EvaluationStack.Pop(); + StackItem args = engine.CurrentContext.EvaluationStack.Pop(); + ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest; + + if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method.GetString())) + return false; + ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.EvaluationStack.Push(item2); - context_new.EvaluationStack.Push(item1); + context_new.EvaluationStack.Push(args); + context_new.EvaluationStack.Push(method); return true; } diff --git a/neo/SmartContract/Manifest/ContractAbi.cs b/neo/SmartContract/Manifest/ContractAbi.cs new file mode 100644 index 0000000000..033306f8e6 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractAbi.cs @@ -0,0 +1,57 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + /// + /// For technical details of ABI, please refer to NEP-3: NeoContract ABI. (https://github.com/neo-project/proposals/blob/master/nep-3.mediawiki) + /// + public class ContractAbi + { + /// + /// Hash is the script hash of the contract. It is encoded as a hexadecimal string in big-endian. + /// + public UInt160 Hash { get; set; } + + /// + /// Entrypoint is a Method object which describe the details of the entrypoint of the contract. + /// + public ContractMethodDescriptor EntryPoint { get; set; } + + /// + /// Methods is an array of Method objects which describe the details of each method in the contract. + /// + public ContractMethodDescriptor[] Methods { get; set; } + + /// + /// Events is an array of Event objects which describe the details of each event in the contract. + /// + public ContractEventDescriptor[] Events { get; set; } + + /// + /// Parse ContractAbi from json + /// + /// Json + /// Return ContractAbi + public static ContractAbi FromJson(JObject json) + { + return new ContractAbi + { + Hash = UInt160.Parse(json["hash"].AsString()), + EntryPoint = ContractMethodDescriptor.FromJson(json["entryPoint"]), + Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson(u)).ToArray(), + Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson(u)).ToArray() + }; + } + + public JObject ToJson() + { + var json = new JObject(); + json["hash"] = Hash.ToString(); + json["entryPoint"] = EntryPoint.ToJson(); + json["methods"] = new JArray(Methods.Select(u => u.ToJson()).ToArray()); + json["events"] = new JArray(Events.Select(u => u.ToJson()).ToArray()); + return json; + } + } +} diff --git a/neo/SmartContract/Manifest/ContractEventDescriptor.cs b/neo/SmartContract/Manifest/ContractEventDescriptor.cs new file mode 100644 index 0000000000..b06b64f341 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -0,0 +1,40 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + public class ContractEventDescriptor + { + /// + /// Name is the name of the method, which can be any valid identifier. + /// + public string Name { get; set; } + + /// + /// Parameters is an array of Parameter objects which describe the details of each parameter in the method. + /// + public ContractParameterDefinition[] Parameters { get; set; } + + /// + /// Parse ContractMethodDescription from json + /// + /// Json + /// Return ContractMethodDescription + public static ContractMethodDescriptor FromJson(JObject json) + { + return new ContractMethodDescriptor + { + Name = json["name"].AsString(), + Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(), + }; + } + + public virtual JObject ToJson() + { + var json = new JObject(); + json["name"] = Name; + json["parameters"] = new JArray(Parameters.Select(u => u.ToJson()).ToArray()); + return json; + } + } +} diff --git a/neo/Ledger/ContractPropertyState.cs b/neo/SmartContract/Manifest/ContractFeatures.cs similarity index 61% rename from neo/Ledger/ContractPropertyState.cs rename to neo/SmartContract/Manifest/ContractFeatures.cs index 8966ae4aa8..dd8336f695 100644 --- a/neo/Ledger/ContractPropertyState.cs +++ b/neo/SmartContract/Manifest/ContractFeatures.cs @@ -1,9 +1,9 @@ using System; -namespace Neo.Ledger +namespace Neo.SmartContract.Manifest { [Flags] - public enum ContractPropertyState : byte + public enum ContractFeatures : byte { NoProperty = 0, diff --git a/neo/SmartContract/Manifest/ContractGroup.cs b/neo/SmartContract/Manifest/ContractGroup.cs new file mode 100644 index 0000000000..fd73787a09 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractGroup.cs @@ -0,0 +1,55 @@ +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO.Json; + +namespace Neo.SmartContract.Manifest +{ + /// + /// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. + /// A group is identified by a public key and must be accompanied by a signature for the contract hash to prove that the contract is indeed included in the group. + /// + public class ContractGroup + { + /// + /// Pubkey represents the public key of the group. + /// + public ECPoint PubKey { get; set; } + + /// + /// Signature is the signature of the contract hash. + /// + public byte[] Signature { get; set; } + + /// + /// Parse ContractManifestGroup from json + /// + /// Json + /// Return ContractManifestGroup + public static ContractGroup FromJson(JObject json) + { + return new ContractGroup + { + PubKey = ECPoint.Parse(json["pubKey"].AsString(), ECCurve.Secp256r1), + Signature = json["signature"].AsString().HexToBytes(), + }; + } + + /// + /// Return true if the signature is valid + /// + /// Contract Hash + /// Return true or false + public bool IsValid(UInt160 hash) + { + return Crypto.Default.VerifySignature(hash.ToArray(), Signature, PubKey.EncodePoint(false)); + } + + public virtual JObject ToJson() + { + var json = new JObject(); + json["pubKey"] = PubKey.ToString(); + json["signature"] = Signature.ToHexString(); + return json; + } + } +} diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs new file mode 100644 index 0000000000..967a10e856 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractManifest.cs @@ -0,0 +1,180 @@ +using Neo.IO; +using Neo.IO.Json; +using System.IO; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + /// + /// When a smart contract is deployed, it must explicitly declare the features and permissions it will use. + /// When it is running, it will be limited by its declared list of features and permissions, and cannot make any behavior beyond the scope of the list. + /// + public class ContractManifest : ISerializable + { + /// + /// Max length for a valid Contract Manifest + /// + public const int MaxLength = 2048; + + /// + /// Serialized size + /// + public int Size => ToJson().ToString().GetVarSize(); + + /// + /// Contract hash + /// + public UInt160 Hash => Abi.Hash; + + /// + /// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. + /// + public ContractGroup[] Groups { get; set; } + + /// + /// The features field describes what features are available for the contract. + /// + public ContractFeatures Features { get; set; } + + /// + /// For technical details of ABI, please refer to NEP-3: NeoContract ABI. (https://github.com/neo-project/proposals/blob/master/nep-3.mediawiki) + /// + public ContractAbi Abi { get; set; } + + /// + /// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called. + /// + public ContractPermission[] Permissions { get; set; } + + /// + /// The trusts field is an array containing a set of contract hashes or group public keys. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that it trusts any contract. + /// If a contract is trusted, the user interface will not give any warnings when called by the contract. + /// + 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 + /// + /// Hash + /// Return default manifest for this contract + public static ContractManifest CreateDefault(UInt160 hash) + { + return new ContractManifest() + { + Permissions = new[] { ContractPermission.DefaultPermission }, + Abi = new ContractAbi() + { + Hash = hash, + EntryPoint = ContractMethodDescriptor.DefaultEntryPoint, + Events = new ContractEventDescriptor[0], + Methods = new ContractMethodDescriptor[0] + }, + Features = ContractFeatures.NoProperty, + Groups = new ContractGroup[0], + SafeMethods = WildCardContainer.Create(), + Trusts = WildCardContainer.Create() + }; + } + + /// + /// Return true if is allowed + /// + /// Manifest + /// Method + /// Return true or false + public bool CanCall(ContractManifest manifest, string method) + { + return Permissions.Any(u => u.IsAllowed(manifest, method)); + } + + /// + /// Parse ContractManifest from json + /// + /// Json + /// Return ContractManifest + public static ContractManifest FromJson(JObject json) + { + var manifest = new ContractManifest(); + manifest.DeserializeFromJson(json); + return manifest; + } + + /// + /// Parse ContractManifest from json + /// + /// Json + /// Return ContractManifest + public static ContractManifest Parse(string json) => FromJson(JObject.Parse(json)); + + /// + public JObject ToJson() + { + var feature = new JObject(); + feature["storage"] = Features.HasFlag(ContractFeatures.HasStorage); + feature["payable"] = Features.HasFlag(ContractFeatures.Payable); + + var json = new JObject(); + json["groups"] = new JArray(Groups.Select(u => u.ToJson()).ToArray()); + json["features"] = feature; + json["abi"] = Abi.ToJson(); + json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray(); + json["trusts"] = Trusts.ToJson(); + json["safeMethods"] = SafeMethods.ToJson(); + + return json; + } + + /// + /// Clone + /// + /// Return a copy of this object + public ContractManifest Clone() => FromJson(ToJson()); + + /// + /// String representation + /// + /// Return json string + public override string ToString() => ToJson().ToString(); + + public void Serialize(BinaryWriter writer) + { + writer.WriteVarString(ToJson().ToString()); + } + + public void Deserialize(BinaryReader reader) + { + DeserializeFromJson(JObject.Parse(reader.ReadVarString(MaxLength))); + } + + private void DeserializeFromJson(JObject json) + { + Abi = ContractAbi.FromJson(json["abi"]); + Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray(); + 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; + } + + /// + /// Return true if is valid + /// + /// Return true or false + public bool IsValid(UInt160 hash) + { + if (!Abi.Hash.Equals(hash)) return false; + return Groups.All(u => u.IsValid(hash)); + } + } +} diff --git a/neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs new file mode 100644 index 0000000000..c631aa0450 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -0,0 +1,59 @@ +using Neo.IO.Json; +using System; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + public class ContractMethodDescriptor : ContractEventDescriptor + { + /// + /// Default entry point + /// + public static readonly ContractMethodDescriptor DefaultEntryPoint = new ContractMethodDescriptor() + { + Name = "Main", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "operation", + Type = ContractParameterType.String + }, + new ContractParameterDefinition() + { + Name = "args", + Type = ContractParameterType.Array + } + }, + ReturnType = ContractParameterType.Any + }; + + /// + /// Returntype indicates the return type of the method. It can be one of the following values: + /// Signature, Boolean, Integer, Hash160, Hash256, ByteArray, PublicKey, String, Array, InteropInterface, Void. + /// + public ContractParameterType ReturnType { get; set; } + + /// + /// Parse ContractMethodDescription from json + /// + /// Json + /// Return ContractMethodDescription + public new static ContractMethodDescriptor FromJson(JObject json) + { + return new ContractMethodDescriptor + { + Name = json["name"].AsString(), + Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(), + ReturnType = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["returnType"].AsString()), + }; + } + + public override JObject ToJson() + { + var json = base.ToJson(); + json["returnType"] = ReturnType.ToString(); + return json; + } + } +} diff --git a/neo/SmartContract/Manifest/ContractParameterDefinition.cs b/neo/SmartContract/Manifest/ContractParameterDefinition.cs new file mode 100644 index 0000000000..af07315a3a --- /dev/null +++ b/neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -0,0 +1,41 @@ +using Neo.IO.Json; +using System; + +namespace Neo.SmartContract.Manifest +{ + public class ContractParameterDefinition + { + /// + /// Name is the name of the parameter, which can be any valid identifier. + /// + public string Name { get; set; } + + /// + /// Type indicates the type of the parameter. It can be one of the following values: + /// Signature, Boolean, Integer, Hash160, Hash256, ByteArray, PublicKey, String, Array, InteropInterface. + /// + public ContractParameterType Type { get; set; } + + /// + /// Parse ContractParameterDefinition from json + /// + /// Json + /// Return ContractParameterDefinition + public static ContractParameterDefinition FromJson(JObject json) + { + return new ContractParameterDefinition + { + Name = json["name"].AsString(), + Type = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["type"].AsString()), + }; + } + + public virtual JObject ToJson() + { + var json = new JObject(); + json["name"] = Name; + json["type"] = Type.ToString(); + return json; + } + } +} diff --git a/neo/SmartContract/Manifest/ContractPermission.cs b/neo/SmartContract/Manifest/ContractPermission.cs new file mode 100644 index 0000000000..b7d1d54bb9 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractPermission.cs @@ -0,0 +1,74 @@ +using Neo.IO.Json; +using System; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + /// + /// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called. + /// + public class ContractPermission + { + /// + /// The contract field indicates the contract to be invoked. It can be a hash of a contract, a public key of a group, or a wildcard *. + /// If it specifies a hash of a contract, then the contract will be invoked; If it specifies a public key of a group, then any contract in this group will be invoked; If it specifies a wildcard*, then any contract will be invoked. + /// + public ContractPermissionDescriptor Contract { get; set; } + + /// + /// The methods field is an array containing a set of methods to be called. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that any method can be called. + /// If a contract invokes a contract or method that is not declared in the manifest at runtime, the invocation will fail. + /// + public WildCardContainer Methods { get; set; } + + public static readonly ContractPermission DefaultPermission = new ContractPermission + { + Contract = ContractPermissionDescriptor.CreateWildcard(), + Methods = WildCardContainer.CreateWildcard() + }; + + /// + /// Parse ContractPermission from json + /// + /// Json + /// Return ContractPermission + public static ContractPermission FromJson(JObject json) + { + return new ContractPermission + { + Contract = ContractPermissionDescriptor.FromJson(json["contract"]), + Methods = WildCardContainer.FromJson(json["methods"], u => u.AsString()), + }; + } + + /// + public JObject ToJson() + { + var json = new JObject(); + json["contract"] = Contract.ToJson(); + json["methods"] = Methods.ToJson(); + return json; + } + + /// + /// Return true if is allowed + /// + /// The manifest of which contract we are calling + /// Method + /// Return true or false + public bool IsAllowed(ContractManifest manifest, string method) + { + if (Contract.IsHash) + { + if (!Contract.Hash.Equals(manifest.Hash)) return false; + } + else if (Contract.IsGroup) + { + if (manifest.Groups.All(p => !p.PubKey.Equals(Contract.Group))) return false; + } + return Methods.IsWildcard || Methods.Contains(method); + } + } +} diff --git a/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs new file mode 100644 index 0000000000..7b64a66935 --- /dev/null +++ b/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -0,0 +1,56 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Json; +using System; + +namespace Neo.SmartContract.Manifest +{ + public class ContractPermissionDescriptor + { + public UInt160 Hash { get; } + public ECPoint Group { get; } + + public bool IsHash => Hash != null; + public bool IsGroup => Group != null; + public bool IsWildcard => Hash is null && Group is null; + + private ContractPermissionDescriptor(UInt160 hash, ECPoint group) + { + this.Hash = hash; + this.Group = group; + } + + public static ContractPermissionDescriptor Create(UInt160 hash) + { + return new ContractPermissionDescriptor(hash, null); + } + + public static ContractPermissionDescriptor Create(ECPoint group) + { + return new ContractPermissionDescriptor(null, group); + } + + public static ContractPermissionDescriptor CreateWildcard() + { + return new ContractPermissionDescriptor(null, null); + } + + public static ContractPermissionDescriptor FromJson(JObject json) + { + string str = json.AsString(); + if (str.Length == 42) + return Create(UInt160.Parse(str)); + if (str.Length == 66) + return Create(ECPoint.Parse(str, ECCurve.Secp256r1)); + if (str == "*") + return CreateWildcard(); + throw new FormatException(); + } + + public JObject ToJson() + { + if (IsHash) return Hash.ToString(); + if (IsGroup) return Group.ToString(); + return "*"; + } + } +} diff --git a/neo/SmartContract/Manifest/WildCardContainer.cs b/neo/SmartContract/Manifest/WildCardContainer.cs new file mode 100644 index 0000000000..aab6ba838e --- /dev/null +++ b/neo/SmartContract/Manifest/WildCardContainer.cs @@ -0,0 +1,76 @@ +using Neo.IO.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.SmartContract.Manifest +{ + public class WildCardContainer : IReadOnlyList + { + private readonly T[] _data; + + public T this[int index] => _data[index]; + + /// + /// Number of items + /// + public int Count => _data?.Length ?? 0; + + /// + /// Is wildcard? + /// + public bool IsWildcard => _data is null; + + /// + /// Constructor + /// + /// Data + private WildCardContainer(T[] data) + { + _data = data; + } + + /// + /// Create a new WildCardContainer + /// + /// Data + /// WildCardContainer + public static WildCardContainer Create(params T[] data) => new WildCardContainer(data); + + /// + /// Create a wildcard + /// + /// WildCardContainer + public static WildCardContainer CreateWildcard() => new WildCardContainer(null); + + public static WildCardContainer FromJson(JObject json, Func elementSelector) + { + switch (json) + { + case JString str: + if (str.Value != "*") throw new FormatException(); + return CreateWildcard(); + case JArray array: + return Create(array.Select(p => elementSelector(p)).ToArray()); + default: + throw new FormatException(); + } + } + + public IEnumerator GetEnumerator() + { + if (_data == null) return ((IReadOnlyList)new T[0]).GetEnumerator(); + + return ((IReadOnlyList)_data).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public JObject ToJson() + { + if (IsWildcard) return "*"; + return _data.Select(p => (JObject)p.ToString()).ToArray(); + } + } +} diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index be8599b846..0cba715ada 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -1,5 +1,6 @@ using Neo.IO; using Neo.Ledger; +using Neo.SmartContract.Manifest; using Neo.SmartContract.Native.Tokens; using Neo.VM; using System; @@ -22,7 +23,7 @@ public abstract class NativeContract public uint ServiceHash { get; } public byte[] Script { get; } public UInt160 Hash { get; } - public virtual ContractPropertyState Properties => ContractPropertyState.NoProperty; + public ContractManifest Manifest { get; } public virtual string[] SupportedStandards { get; } = { "NEP-10" }; protected NativeContract() @@ -33,7 +34,25 @@ protected NativeContract() sb.EmitSysCall(ServiceHash); this.Script = sb.ToArray(); } + this.Hash = Script.ToScriptHash(); + this.Manifest = ContractManifest.CreateDefault(this.Hash); + this.Manifest.Abi.Methods = new ContractMethodDescriptor[] + { + new ContractMethodDescriptor() + { + Name = "onPersist", + ReturnType = ContractParameterType.Boolean, + Parameters = new ContractParameterDefinition[0] + }, + new ContractMethodDescriptor() + { + Name = "supportedStandards", + ReturnType = ContractParameterType.Array, + Parameters = new ContractParameterDefinition[0] + } + }; + contracts.Add(this); } diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index 0a3f5b2971..ed3ae120d2 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -1,6 +1,7 @@ using Neo.IO; using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.VM; using System; using System.Collections.Generic; @@ -11,7 +12,6 @@ namespace Neo.SmartContract.Native public sealed class PolicyContract : NativeContract { public override string ServiceName => "Neo.Native.Policy"; - public override ContractPropertyState Properties => ContractPropertyState.HasStorage; private const byte Prefix_MaxTransactionsPerBlock = 23; private const byte Prefix_MaxLowPriorityTransactionsPerBlock = 34; @@ -19,6 +19,126 @@ public sealed class PolicyContract : NativeContract private const byte Prefix_FeePerByte = 10; private const byte Prefix_BlockedAccounts = 15; + public PolicyContract() : base() + { + Manifest.Features = ContractFeatures.HasStorage; + + var list = new List(Manifest.Abi.Methods) + { + new ContractMethodDescriptor() + { + Name = "getMaxTransactionsPerBlock", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "getMaxLowPriorityTransactionsPerBlock", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "getMaxLowPriorityTransactionSize", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "getFeePerByte", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "getBlockedAccounts", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "setMaxTransactionsPerBlock", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "value", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "setMaxLowPriorityTransactionsPerBlock", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "value", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "setMaxLowPriorityTransactionSize", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "value", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "setFeePerByte", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "value", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + }, + + new ContractMethodDescriptor() + { + Name = "blockAccount", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "account", + Type = ContractParameterType.Hash160 + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "unblockAccount", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "account", + Type = ContractParameterType.Hash160 + } + }, + ReturnType = ContractParameterType.Boolean + } + }; + + Manifest.Abi.Methods = list.ToArray(); + } + private bool CheckValidators(ApplicationEngine engine) { UInt256 prev_hash = engine.Snapshot.PersistingBlock.PrevHash; diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index 695e4febb8..f4c801b1ec 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -1,11 +1,13 @@ -using System; -using System.Linq; -using System.Numerics; -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.VM; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using VMArray = Neo.VM.Types.Array; namespace Neo.SmartContract.Native.Tokens @@ -19,8 +21,26 @@ public sealed class GasToken : Nep5Token private const byte Prefix_SystemFeeAmount = 15; - internal GasToken() + internal GasToken() : base() { + var list = new List(Manifest.Abi.Methods) + { + new ContractMethodDescriptor() + { + Name = "getSysFeeAmount", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "index", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Integer + } + }; + + Manifest.Abi.Methods = list.ToArray(); } protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index d942aaee51..71a560507e 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -2,6 +2,7 @@ using Neo.IO; using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -25,9 +26,82 @@ public sealed class NeoToken : Nep5Token private const byte Prefix_ValidatorsCount = 15; private const byte Prefix_NextValidators = 14; - internal NeoToken() + internal NeoToken() : base() { this.TotalAmount = 100000000 * Factor; + + var list = new List(Manifest.Abi.Methods) + { + new ContractMethodDescriptor() + { + Name = "unclaimedGas", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "account", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "end", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "registerValidator", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "pubkey", + Type = ContractParameterType.ByteArray + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "vote", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "account", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "pubkeys", + Type = ContractParameterType.Array + } + }, + ReturnType = ContractParameterType.Boolean + }, + new ContractMethodDescriptor() + { + Name = "getRegisteredValidators", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Array + }, + new ContractMethodDescriptor() + { + Name = "getValidators", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Array + }, + new ContractMethodDescriptor() + { + Name = "getNextBlockValidators", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Array + } + }; + + Manifest.Abi.Methods = list.ToArray(); } protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index b8403f2dd9..6f1847678b 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -1,7 +1,9 @@ using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.VM; using System; +using System.Collections.Generic; using System.Numerics; using VMArray = Neo.VM.Types.Array; @@ -10,7 +12,6 @@ namespace Neo.SmartContract.Native.Tokens public abstract class Nep5Token : NativeContract where TState : Nep5AccountState, new() { - public override ContractPropertyState Properties => ContractPropertyState.HasStorage; public override string[] SupportedStandards { get; } = { "NEP-5", "NEP-10" }; public abstract string Name { get; } public abstract string Symbol { get; } @@ -20,9 +21,104 @@ public abstract class Nep5Token : NativeContract protected const byte Prefix_TotalSupply = 11; protected const byte Prefix_Account = 20; - protected Nep5Token() + protected Nep5Token() : base() { this.Factor = BigInteger.Pow(10, Decimals); + + var methods = new List(Manifest.Abi.Methods) + { + new ContractMethodDescriptor() + { + Name = "name", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.String + }, + new ContractMethodDescriptor() + { + Name = "symbol", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.String + }, + new ContractMethodDescriptor() + { + Name = "decimals", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "totalSupply", + Parameters = new ContractParameterDefinition[0], + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "balanceOf", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "account", + Type = ContractParameterType.Hash160 + } + }, + ReturnType = ContractParameterType.Integer + }, + new ContractMethodDescriptor() + { + Name = "transfer", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "from", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "to", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "amount", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + } + }; + + Manifest.Abi.Methods = methods.ToArray(); + + var events = new List(Manifest.Abi.Events) + { + new ContractMethodDescriptor() + { + Name = "Transfer", + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = "from", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "to", + Type = ContractParameterType.Hash160 + }, + new ContractParameterDefinition() + { + Name = "amount", + Type = ContractParameterType.Integer + } + }, + ReturnType = ContractParameterType.Boolean + } + }; + + Manifest.Abi.Events = events.ToArray(); } protected StorageKey CreateAccountKey(UInt160 account) From 8e6269b27f54e8a113e9c3d6f3f0f81ff5351dbc Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 24 May 2019 16:40:12 +0800 Subject: [PATCH 2/8] Fix `ConsensusData` (#774) --- neo.UnitTests/UT_Block.cs | 20 +++++----------- neo.UnitTests/UT_MemoryPool.cs | 2 +- neo/Consensus/ConsensusContext.cs | 4 ++-- neo/Ledger/TrimmedBlock.cs | 22 ++++++++++-------- neo/Network/P2P/Payloads/Block.cs | 23 +++++++++++++------ neo/Network/P2P/Payloads/ConsensusData.cs | 14 ++++++++++- .../P2P/Payloads/MerkleBlockPayload.cs | 17 +++++--------- 7 files changed, 57 insertions(+), 45 deletions(-) diff --git a/neo.UnitTests/UT_Block.cs b/neo.UnitTests/UT_Block.cs index 48e6685580..64a60ba9e4 100644 --- a/neo.UnitTests/UT_Block.cs +++ b/neo.UnitTests/UT_Block.cs @@ -24,16 +24,6 @@ public void Transactions_Get() uut.Transactions.Should().BeNull(); } - [TestMethod] - public void Transactions_Set() - { - Transaction[] val = new Transaction[10]; - uut.Transactions = val; - uut.Transactions.Length.Should().Be(10); - } - - - [TestMethod] public void Header_Get() { @@ -79,7 +69,8 @@ public void Size_Get_1_Transaction() Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); - uut.Transactions = new Transaction[1] { + uut.Transactions = new[] + { TestUtils.GetTransaction() }; @@ -97,7 +88,8 @@ public void Size_Get_3_Transaction() Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); - uut.Transactions = new Transaction[3] { + uut.Transactions = new[] + { TestUtils.GetTransaction(), TestUtils.GetTransaction(), TestUtils.GetTransaction() @@ -127,7 +119,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < data.Length; i++) @@ -149,7 +141,7 @@ public void Deserialize() uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/UT_MemoryPool.cs index 1802eacf2b..cfb74a4f18 100644 --- a/neo.UnitTests/UT_MemoryPool.cs +++ b/neo.UnitTests/UT_MemoryPool.cs @@ -280,7 +280,7 @@ public void VerifySortOrderAndThatHighetFeeTransactionsAreReverifiedFirst() verifiedTxs.Length.ShouldBeEquivalentTo(2); verifiedTxs[0].ShouldBeEquivalentTo(maxHighPriorityTransaction); verifiedTxs[1].ShouldBeEquivalentTo(maxLowPriorityTransaction); - var blockWith2Tx = new Block { Transactions = new Transaction[2] { maxHighPriorityTransaction, maxLowPriorityTransaction } }; + var blockWith2Tx = new Block { Transactions = new[] { maxHighPriorityTransaction, maxLowPriorityTransaction } }; // verify and remove the 2 transactions from the verified pool _unit.UpdatePoolForBlockPersisted(blockWith2Tx, Blockchain.Singleton.GetSnapshot()); _unit.InvalidateVerifiedTransactions(); diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index 0699295ed6..5fa53ce567 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -95,11 +95,11 @@ public void Deserialize(BinaryReader reader) Reset(0); if (reader.ReadUInt32() != Block.Version) throw new FormatException(); if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); - Block.ConsensusData = reader.ReadSerializable(); Block.Timestamp = reader.ReadUInt32(); Block.NextConsensus = reader.ReadSerializable(); if (Block.NextConsensus.Equals(UInt160.Zero)) Block.NextConsensus = null; + Block.ConsensusData = reader.ReadSerializable(); ViewNumber = reader.ReadByte(); TransactionHashes = reader.ReadSerializableArray(); if (TransactionHashes.Length == 0) @@ -328,9 +328,9 @@ public void Serialize(BinaryWriter writer) { writer.Write(Block.Version); writer.Write(Block.Index); - writer.Write(Block.ConsensusData); writer.Write(Block.Timestamp); writer.Write(Block.NextConsensus ?? UInt160.Zero); + writer.Write(Block.ConsensusData); writer.Write(ViewNumber); writer.Write(TransactionHashes ?? new UInt256[0]); writer.Write(Transactions?.Values.ToArray() ?? new Transaction[0]); diff --git a/neo/Ledger/TrimmedBlock.cs b/neo/Ledger/TrimmedBlock.cs index 663932638d..614ee89ac1 100644 --- a/neo/Ledger/TrimmedBlock.cs +++ b/neo/Ledger/TrimmedBlock.cs @@ -9,8 +9,8 @@ namespace Neo.Ledger { public class TrimmedBlock : BlockBase, ICloneable { - public ConsensusData ConsensusData; public UInt256[] Hashes; + public ConsensusData ConsensusData; public bool IsBlock => Hashes.Length > 0; @@ -26,7 +26,7 @@ public Block GetBlock(DataCache cache) NextConsensus = NextConsensus, Witness = Witness, ConsensusData = ConsensusData, - Transactions = Hashes.Select(p => cache[p].Transaction).ToArray() + Transactions = Hashes.Skip(1).Select(p => cache[p].Transaction).ToArray() }; } @@ -52,7 +52,9 @@ public Header Header } } - public override int Size => base.Size + ConsensusData.Size + Hashes.GetVarSize(); + public override int Size => base.Size + + Hashes.GetVarSize() //Hashes + + (ConsensusData?.Size ?? 0); //ConsensusData TrimmedBlock ICloneable.Clone() { @@ -65,8 +67,8 @@ TrimmedBlock ICloneable.Clone() Index = Index, NextConsensus = NextConsensus, Witness = Witness, - ConsensusData = ConsensusData, Hashes = Hashes, + ConsensusData = ConsensusData, _header = _header }; } @@ -74,8 +76,9 @@ TrimmedBlock ICloneable.Clone() public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - ConsensusData = reader.ReadSerializable(); - Hashes = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); + Hashes = reader.ReadSerializableArray(Block.MaxContentsPerBlock); + if (Hashes.Length > 0) + ConsensusData = reader.ReadSerializable(); } void ICloneable.FromReplica(TrimmedBlock replica) @@ -87,22 +90,23 @@ void ICloneable.FromReplica(TrimmedBlock replica) Index = replica.Index; NextConsensus = replica.NextConsensus; Witness = replica.Witness; - ConsensusData = replica.ConsensusData; Hashes = replica.Hashes; + ConsensusData = replica.ConsensusData; _header = replica._header; } public override void Serialize(BinaryWriter writer) { base.Serialize(writer); - writer.Write(ConsensusData); writer.Write(Hashes); + if (Hashes.Length > 0) + writer.Write(ConsensusData); } public override JObject ToJson() { JObject json = base.ToJson(); - json["consensus_data"] = ConsensusData.ToJson(); + json["consensus_data"] = ConsensusData?.ToJson(); json["hashes"] = Hashes.Select(p => (JObject)p.ToString()).ToArray(); return json; } diff --git a/neo/Network/P2P/Payloads/Block.cs b/neo/Network/P2P/Payloads/Block.cs index b90ba7805c..58faf54cb1 100644 --- a/neo/Network/P2P/Payloads/Block.cs +++ b/neo/Network/P2P/Payloads/Block.cs @@ -11,7 +11,8 @@ namespace Neo.Network.P2P.Payloads { public class Block : BlockBase, IInventory, IEquatable { - public const int MaxTransactionsPerBlock = ushort.MaxValue; + public const int MaxContentsPerBlock = ushort.MaxValue; + public const int MaxTransactionsPerBlock = MaxContentsPerBlock - 1; public ConsensusData ConsensusData; public Transaction[] Transactions; @@ -39,7 +40,10 @@ public Header Header InventoryType IInventory.InventoryType => InventoryType.Block; - public override int Size => base.Size + ConsensusData.Size + Transactions.GetVarSize(); + public override int Size => base.Size + + IO.Helper.GetVarSize(Transactions.Length + 1) //Count + + ConsensusData.Size //ConsensusData + + Transactions.Sum(p => p.Size); //Transactions public static UInt256 CalculateMerkleRoot(UInt256 consensusDataHash, params UInt256[] transactionHashes) { @@ -51,9 +55,12 @@ public static UInt256 CalculateMerkleRoot(UInt256 consensusDataHash, params UInt public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); + int count = (int)reader.ReadVarInt(MaxContentsPerBlock); + if (count == 0) throw new FormatException(); ConsensusData = reader.ReadSerializable(); - Transactions = reader.ReadSerializableArray(MaxTransactionsPerBlock); - if (Transactions.Length == 0) throw new FormatException(); + Transactions = new Transaction[count - 1]; + for (int i = 0; i < Transactions.Length; i++) + Transactions[i] = reader.ReadSerializable(); if (Transactions.Distinct().Count() != Transactions.Length) throw new FormatException(); if (CalculateMerkleRoot(ConsensusData.Hash, Transactions.Select(p => p.Hash).ToArray()) != MerkleRoot) @@ -85,8 +92,10 @@ public void RebuildMerkleRoot() public override void Serialize(BinaryWriter writer) { base.Serialize(writer); + writer.WriteVarInt(Transactions.Length + 1); writer.Write(ConsensusData); - writer.Write(Transactions); + foreach (Transaction tx in Transactions) + writer.Write(tx); } public override JObject ToJson() @@ -108,8 +117,8 @@ public TrimmedBlock Trim() Index = Index, NextConsensus = NextConsensus, Witness = Witness, - ConsensusData = ConsensusData, - Hashes = Transactions.Select(p => p.Hash).ToArray() + Hashes = new[] { ConsensusData.Hash }.Concat(Transactions.Select(p => p.Hash)).ToArray(), + ConsensusData = ConsensusData }; } } diff --git a/neo/Network/P2P/Payloads/ConsensusData.cs b/neo/Network/P2P/Payloads/ConsensusData.cs index 692527c3d4..a48a8f1304 100644 --- a/neo/Network/P2P/Payloads/ConsensusData.cs +++ b/neo/Network/P2P/Payloads/ConsensusData.cs @@ -11,7 +11,19 @@ public class ConsensusData : ISerializable public uint PrimaryIndex; public ulong Nonce; - public UInt256 Hash => new UInt256(Crypto.Default.Hash256(this.ToArray())); + private UInt256 _hash = null; + public UInt256 Hash + { + get + { + if (_hash == null) + { + _hash = new UInt256(Crypto.Default.Hash256(this.ToArray())); + } + return _hash; + } + } + public int Size => IO.Helper.GetVarSize((int)PrimaryIndex) + sizeof(ulong); void ISerializable.Deserialize(BinaryReader reader) diff --git a/neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs index 08de3c0fb3..11b9fb3a6e 100644 --- a/neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -8,17 +8,15 @@ namespace Neo.Network.P2P.Payloads { public class MerkleBlockPayload : BlockBase { - public ConsensusData ConsensusData; - public int TxCount; + public int ContentCount; public UInt256[] Hashes; public byte[] Flags; - public override int Size => base.Size + ConsensusData.Size + sizeof(int) + Hashes.GetVarSize() + Flags.GetVarSize(); + public override int Size => base.Size + sizeof(int) + Hashes.GetVarSize() + Flags.GetVarSize(); public static MerkleBlockPayload Create(Block block, BitArray flags) { - MerkleTree tree = new MerkleTree(block.Transactions.Select(p => p.Hash).ToArray()); - tree.Trim(flags); + MerkleTree tree = new MerkleTree(new[] { block.ConsensusData.Hash }.Concat(block.Transactions.Select(p => p.Hash)).ToArray()); byte[] buffer = new byte[(flags.Length + 7) / 8]; flags.CopyTo(buffer, 0); return new MerkleBlockPayload @@ -30,8 +28,7 @@ public static MerkleBlockPayload Create(Block block, BitArray flags) Index = block.Index, NextConsensus = block.NextConsensus, Witness = block.Witness, - ConsensusData = block.ConsensusData, - TxCount = block.Transactions.Length, + ContentCount = block.Transactions.Length + 1, Hashes = tree.ToHashArray(), Flags = buffer }; @@ -40,8 +37,7 @@ public static MerkleBlockPayload Create(Block block, BitArray flags) public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - ConsensusData = reader.ReadSerializable(); - TxCount = (int)reader.ReadVarInt(int.MaxValue); + ContentCount = (int)reader.ReadVarInt(int.MaxValue); Hashes = reader.ReadSerializableArray(); Flags = reader.ReadVarBytes(); } @@ -49,8 +45,7 @@ public override void Deserialize(BinaryReader reader) public override void Serialize(BinaryWriter writer) { base.Serialize(writer); - writer.Write(ConsensusData); - writer.WriteVarInt(TxCount); + writer.WriteVarInt(ContentCount); writer.Write(Hashes); writer.WriteVarBytes(Flags); } From 172358d12c842c12087a5b68e6e535826a23ad2f Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 24 May 2019 17:30:23 +0800 Subject: [PATCH 3/8] Modify the pricing model (#771) 1. Redefine the price of all `OpCode`s. 2. Redefine the price of all `SYSCALL`s. 3. Define the price of `NativeContract`s. 4. Storage is charged in bytes. 5. Modify the price of contract deployment. (100 for deployment, and 10 for migration) 6. Remove the 10 free gas for each invocation. 7. Limit the gas for transactions' verification to 0.1 gas. --- .../Nep5NativeContractExtensions.cs | 8 +- neo.UnitTests/UT_InteropPrices.cs | 119 ++--------- neo.UnitTests/UT_NativeContractPrices.cs | 27 +++ neo.UnitTests/UT_OpCodePrices.cs | 18 ++ neo/Network/P2P/Payloads/BlockBase.cs | 2 +- neo/Network/P2P/Payloads/ConsensusPayload.cs | 2 +- neo/Network/P2P/Payloads/Transaction.cs | 3 +- .../ApplicationEngine.OpCodePrices.cs | 185 ++++++++++++++++++ neo/SmartContract/ApplicationEngine.cs | 73 ++----- neo/SmartContract/Helper.cs | 4 +- neo/SmartContract/InteropService.NEO.cs | 65 +++--- neo/SmartContract/InteropService.cs | 99 +++++----- neo/SmartContract/Native/NativeContract.cs | 10 + neo/SmartContract/Native/PolicyContract.cs | 22 +++ neo/SmartContract/Native/Tokens/GasToken.cs | 11 ++ neo/SmartContract/Native/Tokens/NeoToken.cs | 19 ++ neo/SmartContract/Native/Tokens/Nep5Token.cs | 18 ++ 17 files changed, 447 insertions(+), 238 deletions(-) create mode 100644 neo.UnitTests/UT_NativeContractPrices.cs create mode 100644 neo.UnitTests/UT_OpCodePrices.cs create mode 100644 neo/SmartContract/ApplicationEngine.OpCodePrices.cs diff --git a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs index 8d927e1250..f8b7ba1f8d 100644 --- a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs +++ b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs @@ -69,7 +69,7 @@ public static bool Transfer(this NativeContract contract, Persistence.Snapshot s public static string[] SupportedStandards(this NativeContract contract) { - var engine = new ApplicationEngine(TriggerType.Application, null, null, 0); + var engine = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true); engine.LoadScript(contract.Script); @@ -132,7 +132,7 @@ public static BigInteger BalanceOf(this NativeContract contract, Persistence.Sna public static BigInteger Decimals(this NativeContract contract) { - var engine = new ApplicationEngine(TriggerType.Application, null, null, 0); + var engine = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true); engine.LoadScript(contract.Script); @@ -152,7 +152,7 @@ public static BigInteger Decimals(this NativeContract contract) public static string Symbol(this NativeContract contract) { - var engine = new ApplicationEngine(TriggerType.Application, null, null, 0); + var engine = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true); engine.LoadScript(contract.Script); @@ -172,7 +172,7 @@ public static string Symbol(this NativeContract contract) public static string Name(this NativeContract contract) { - var engine = new ApplicationEngine(TriggerType.Application, null, null, 0); + var engine = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true); engine.LoadScript(contract.Script); diff --git a/neo.UnitTests/UT_InteropPrices.cs b/neo.UnitTests/UT_InteropPrices.cs index c570e86efc..6e0b4fcb61 100644 --- a/neo.UnitTests/UT_InteropPrices.cs +++ b/neo.UnitTests/UT_InteropPrices.cs @@ -3,7 +3,6 @@ using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; -using System; using System.Reflection; namespace Neo.UnitTests @@ -11,148 +10,70 @@ namespace Neo.UnitTests [TestClass] public class UT_InteropPrices { - [TestMethod] - public void NeoServiceFixedPrices() - { - InteropService.GetPrice(InteropService.Neo_Header_GetVersion).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Header_GetMerkleRoot).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Header_GetNextConsensus).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Transaction_GetWitnesses).Should().Be(200); - InteropService.GetPrice(InteropService.Neo_Transaction_GetScript).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Witness_GetVerificationScript).Should().Be(100); - InteropService.GetPrice(InteropService.Neo_Account_IsStandard).Should().Be(100); - InteropService.GetPrice(InteropService.Neo_Contract_GetScript).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Contract_IsPayable).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Storage_Find).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Enumerator_Create).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Enumerator_Next).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Enumerator_Value).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Enumerator_Concat).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Iterator_Create).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Iterator_Key).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Iterator_Keys).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Iterator_Values).Should().Be(1); - InteropService.GetPrice(InteropService.Neo_Iterator_Concat).Should().Be(1); - } - - [TestMethod] - public void StandardServiceFixedPrices() - { - InteropService.GetPrice(InteropService.System_Runtime_Platform).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_GetTrigger).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_CheckWitness).Should().Be(200); - InteropService.GetPrice(InteropService.System_Runtime_Notify).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_Log).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_GetTime).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_Serialize).Should().Be(1); - InteropService.GetPrice(InteropService.System_Runtime_Deserialize).Should().Be(1); - InteropService.GetPrice(InteropService.System_Blockchain_GetHeight).Should().Be(1); - InteropService.GetPrice(InteropService.System_Blockchain_GetHeader).Should().Be(100); - InteropService.GetPrice(InteropService.System_Blockchain_GetBlock).Should().Be(200); - InteropService.GetPrice(InteropService.System_Blockchain_GetTransaction).Should().Be(200); - InteropService.GetPrice(InteropService.System_Blockchain_GetTransactionHeight).Should().Be(100); - InteropService.GetPrice(InteropService.System_Blockchain_GetContract).Should().Be(100); - InteropService.GetPrice(InteropService.System_Header_GetIndex).Should().Be(1); - InteropService.GetPrice(InteropService.System_Header_GetHash).Should().Be(1); - InteropService.GetPrice(InteropService.System_Header_GetPrevHash).Should().Be(1); - InteropService.GetPrice(InteropService.System_Header_GetTimestamp).Should().Be(1); - InteropService.GetPrice(InteropService.System_Block_GetTransactionCount).Should().Be(1); - InteropService.GetPrice(InteropService.System_Block_GetTransactions).Should().Be(1); - InteropService.GetPrice(InteropService.System_Block_GetTransaction).Should().Be(1); - InteropService.GetPrice(InteropService.System_Transaction_GetHash).Should().Be(1); - InteropService.GetPrice(InteropService.System_Contract_Destroy).Should().Be(1); - InteropService.GetPrice(InteropService.System_Storage_GetContext).Should().Be(1); - InteropService.GetPrice(InteropService.System_Storage_GetReadOnlyContext).Should().Be(1); - InteropService.GetPrice(InteropService.System_Storage_Get).Should().Be(100); - InteropService.GetPrice(InteropService.System_Storage_Delete).Should().Be(100); - InteropService.GetPrice(InteropService.System_StorageContext_AsReadOnly).Should().Be(1); - } - [TestMethod] public void ApplicationEngineFixedPrices() { - // ApplicationEngine.GetPriceForSysCall is protected, so we will access through reflection - MethodInfo GetPriceForSysCall = typeof(ApplicationEngine).GetMethod("GetPriceForSysCall", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[]{}, null); - // System.Runtime.CheckWitness: f827ec8c (price is 200) - byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[]{0x68, 0xf8, 0x27, 0xec, 0x8c}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(200L); + InteropService.GetPrice(InteropService.System_Runtime_CheckWitness, ae.CurrentContext.EvaluationStack).Should().Be(0_00030000L); } // System.Storage.GetContext: 9bf667ce (price is 1) - byte[] SyscallSystemStorageGetContextHash = new byte[]{0x68, 0x9b, 0xf6, 0x67, 0xce}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallSystemStorageGetContextHash = new byte[] { 0x68, 0x9b, 0xf6, 0x67, 0xce }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(1L); + InteropService.GetPrice(InteropService.System_Storage_GetContext, ae.CurrentContext.EvaluationStack).Should().Be(0_00000400L); } // System.Storage.Get: 925de831 (price is 100) - byte[] SyscallSystemStorageGetHash = new byte[]{0x68, 0x92, 0x5d, 0xe8, 0x31}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallSystemStorageGetHash = new byte[] { 0x68, 0x92, 0x5d, 0xe8, 0x31 }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetHash); - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(100L); + InteropService.GetPrice(InteropService.System_Storage_Get, ae.CurrentContext.EvaluationStack).Should().Be(0_01000000L); } } [TestMethod] public void ApplicationEngineVariablePrices() { - // ApplicationEngine.GetPriceForSysCall is protected, so we will access through reflection - MethodInfo GetPriceForSysCall = typeof(ApplicationEngine).GetMethod("GetPriceForSysCall", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[]{}, null); - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - byte[] SyscallContractCreateHash00 = new byte[]{(byte)ContractFeatures.NoProperty, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallContractCreateHash00 = new byte[] { 0x01, 0x00, 0x02, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(SyscallContractCreateHash00); - debugger.StepInto(); // push 0 - ContractPropertyState.NoProperty - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(100L * 100000000L / 100000); // assuming private ae.ratio = 100000 - } - - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - byte[] SyscallContractCreateHash01 = new byte[]{0x51, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) - { - Debugger debugger = new Debugger(ae); - ae.LoadScript(SyscallContractCreateHash01); - debugger.StepInto(); // push 01 - ContractPropertyState.HasStorage - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - debugger.StepInto(); // push 0 - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(500L * 100000000L / 100000); // assuming private ae.ratio = 100000 + debugger.StepInto(); // PUSHBYTES1 + debugger.StepInto(); // PUSHBYTES2 + InteropService.GetPrice(InteropService.Neo_Contract_Create, ae.CurrentContext.EvaluationStack).Should().Be(0_00300000L); } // System.Storage.Put: e63f1884 (requires push key and value) - byte[] SyscallStoragePutHash = new byte[]{0x53, 0x53, 0x00, 0x68, 0xe6, 0x3f, 0x18, 0x84}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallStoragePutHash = new byte[] { 0x53, 0x53, 0x00, 0x68, 0xe6, 0x3f, 0x18, 0x84 }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(SyscallStoragePutHash); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(1000L); //((1+1-1) / 1024 + 1) * 1000); + InteropService.GetPrice(InteropService.System_Storage_Put, ae.CurrentContext.EvaluationStack).Should().Be(200000L); } // System.Storage.PutEx: 73e19b3a (requires push key and value) - byte[] SyscallStoragePutExHash = new byte[]{0x53, 0x53, 0x00, 0x68, 0x73, 0xe1, 0x9b, 0x3a}; - using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) ) + byte[] SyscallStoragePutExHash = new byte[] { 0x53, 0x53, 0x00, 0x68, 0x73, 0xe1, 0x9b, 0x3a }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(SyscallStoragePutExHash); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(1000L); //((1+1-1) / 1024 + 1) * 1000); + InteropService.GetPrice(InteropService.System_Storage_PutEx, ae.CurrentContext.EvaluationStack).Should().Be(200000L); } } } diff --git a/neo.UnitTests/UT_NativeContractPrices.cs b/neo.UnitTests/UT_NativeContractPrices.cs new file mode 100644 index 0000000000..b7e07a9ec3 --- /dev/null +++ b/neo.UnitTests/UT_NativeContractPrices.cs @@ -0,0 +1,27 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Native; +using System; +using System.Reflection; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_NativeContractPrices + { + [TestMethod] + public void AllNativeContractPriceAreSet() + { + Type t = typeof(NativeContract); + foreach (Type ct in t.Assembly.GetTypes()) + { + if (t.IsAssignableFrom(ct)) + { + MethodInfo method1 = ct.GetMethod("GetPrice", BindingFlags.NonPublic | BindingFlags.Instance); + MethodInfo method2 = ct.GetMethod("GetPriceForMethod", BindingFlags.NonPublic | BindingFlags.Instance); + (method1.DeclaringType == ct || method2.DeclaringType == ct).Should().BeTrue(); + } + } + } + } +} diff --git a/neo.UnitTests/UT_OpCodePrices.cs b/neo.UnitTests/UT_OpCodePrices.cs new file mode 100644 index 0000000000..b53d745d6a --- /dev/null +++ b/neo.UnitTests/UT_OpCodePrices.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.VM; +using System; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_OpCodePrices + { + [TestMethod] + public void AllOpcodePriceAreSet() + { + foreach (OpCode opcode in Enum.GetValues(typeof(OpCode))) + Assert.IsTrue(ApplicationEngine.OpCodePrices.ContainsKey(opcode)); + } + } +} diff --git a/neo/Network/P2P/Payloads/BlockBase.cs b/neo/Network/P2P/Payloads/BlockBase.cs index 5943ead7f9..8937325509 100644 --- a/neo/Network/P2P/Payloads/BlockBase.cs +++ b/neo/Network/P2P/Payloads/BlockBase.cs @@ -105,7 +105,7 @@ public virtual bool Verify(Snapshot snapshot) if (prev_header == null) return false; if (prev_header.Index + 1 != Index) return false; if (prev_header.Timestamp >= Timestamp) return false; - if (!this.VerifyWitnesses(snapshot)) return false; + if (!this.VerifyWitnesses(snapshot, 1_00000000)) return false; return true; } } diff --git a/neo/Network/P2P/Payloads/ConsensusPayload.cs b/neo/Network/P2P/Payloads/ConsensusPayload.cs index 845e6782c0..fbe242ae67 100644 --- a/neo/Network/P2P/Payloads/ConsensusPayload.cs +++ b/neo/Network/P2P/Payloads/ConsensusPayload.cs @@ -118,7 +118,7 @@ public bool Verify(Snapshot snapshot) { if (BlockIndex <= snapshot.Height) return false; - return this.VerifyWitnesses(snapshot); + return this.VerifyWitnesses(snapshot, 0_02000000); } } } diff --git a/neo/Network/P2P/Payloads/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs index 30fb024b00..fec88aafcf 100644 --- a/neo/Network/P2P/Payloads/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -23,6 +23,7 @@ public class Transaction : IEquatable, IInventory /// Maximum number of attributes that can be contained within a transaction /// private const int MaxTransactionAttributes = 16; + private const long VerificationGasLimited = 0_10000000; public byte Version; public uint Nonce; @@ -209,7 +210,7 @@ public virtual bool Verify(Snapshot snapshot, IEnumerable mempool) if (balance < fee) return false; fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Sum(p => p.Gas + p.NetworkFee); if (balance < fee) return false; - return this.VerifyWitnesses(snapshot); + return this.VerifyWitnesses(snapshot, VerificationGasLimited); } } } diff --git a/neo/SmartContract/ApplicationEngine.OpCodePrices.cs b/neo/SmartContract/ApplicationEngine.OpCodePrices.cs new file mode 100644 index 0000000000..05105e2261 --- /dev/null +++ b/neo/SmartContract/ApplicationEngine.OpCodePrices.cs @@ -0,0 +1,185 @@ +using Neo.VM; +using System.Collections.Generic; + +namespace Neo.SmartContract +{ + partial class ApplicationEngine + { + public static readonly IReadOnlyDictionary OpCodePrices = new Dictionary + { + [OpCode.PUSH0] = 30, + [OpCode.PUSHBYTES1] = 120, + [OpCode.PUSHBYTES2] = 120, + [OpCode.PUSHBYTES3] = 120, + [OpCode.PUSHBYTES4] = 120, + [OpCode.PUSHBYTES5] = 120, + [OpCode.PUSHBYTES6] = 120, + [OpCode.PUSHBYTES7] = 120, + [OpCode.PUSHBYTES8] = 120, + [OpCode.PUSHBYTES9] = 120, + [OpCode.PUSHBYTES10] = 120, + [OpCode.PUSHBYTES11] = 120, + [OpCode.PUSHBYTES12] = 120, + [OpCode.PUSHBYTES13] = 120, + [OpCode.PUSHBYTES14] = 120, + [OpCode.PUSHBYTES15] = 120, + [OpCode.PUSHBYTES16] = 120, + [OpCode.PUSHBYTES17] = 120, + [OpCode.PUSHBYTES18] = 120, + [OpCode.PUSHBYTES19] = 120, + [OpCode.PUSHBYTES20] = 120, + [OpCode.PUSHBYTES21] = 120, + [OpCode.PUSHBYTES22] = 120, + [OpCode.PUSHBYTES23] = 120, + [OpCode.PUSHBYTES24] = 120, + [OpCode.PUSHBYTES25] = 120, + [OpCode.PUSHBYTES26] = 120, + [OpCode.PUSHBYTES27] = 120, + [OpCode.PUSHBYTES28] = 120, + [OpCode.PUSHBYTES29] = 120, + [OpCode.PUSHBYTES30] = 120, + [OpCode.PUSHBYTES31] = 120, + [OpCode.PUSHBYTES32] = 120, + [OpCode.PUSHBYTES33] = 120, + [OpCode.PUSHBYTES34] = 120, + [OpCode.PUSHBYTES35] = 120, + [OpCode.PUSHBYTES36] = 120, + [OpCode.PUSHBYTES37] = 120, + [OpCode.PUSHBYTES38] = 120, + [OpCode.PUSHBYTES39] = 120, + [OpCode.PUSHBYTES40] = 120, + [OpCode.PUSHBYTES41] = 120, + [OpCode.PUSHBYTES42] = 120, + [OpCode.PUSHBYTES43] = 120, + [OpCode.PUSHBYTES44] = 120, + [OpCode.PUSHBYTES45] = 120, + [OpCode.PUSHBYTES46] = 120, + [OpCode.PUSHBYTES47] = 120, + [OpCode.PUSHBYTES48] = 120, + [OpCode.PUSHBYTES49] = 120, + [OpCode.PUSHBYTES50] = 120, + [OpCode.PUSHBYTES51] = 120, + [OpCode.PUSHBYTES52] = 120, + [OpCode.PUSHBYTES53] = 120, + [OpCode.PUSHBYTES54] = 120, + [OpCode.PUSHBYTES55] = 120, + [OpCode.PUSHBYTES56] = 120, + [OpCode.PUSHBYTES57] = 120, + [OpCode.PUSHBYTES58] = 120, + [OpCode.PUSHBYTES59] = 120, + [OpCode.PUSHBYTES60] = 120, + [OpCode.PUSHBYTES61] = 120, + [OpCode.PUSHBYTES62] = 120, + [OpCode.PUSHBYTES63] = 120, + [OpCode.PUSHBYTES64] = 120, + [OpCode.PUSHBYTES65] = 120, + [OpCode.PUSHBYTES66] = 120, + [OpCode.PUSHBYTES67] = 120, + [OpCode.PUSHBYTES68] = 120, + [OpCode.PUSHBYTES69] = 120, + [OpCode.PUSHBYTES70] = 120, + [OpCode.PUSHBYTES71] = 120, + [OpCode.PUSHBYTES72] = 120, + [OpCode.PUSHBYTES73] = 120, + [OpCode.PUSHBYTES74] = 120, + [OpCode.PUSHBYTES75] = 120, + [OpCode.PUSHDATA1] = 180, + [OpCode.PUSHDATA2] = 13000, + [OpCode.PUSHDATA4] = 110000, + [OpCode.PUSHM1] = 30, + [OpCode.PUSH1] = 30, + [OpCode.PUSH2] = 30, + [OpCode.PUSH3] = 30, + [OpCode.PUSH4] = 30, + [OpCode.PUSH5] = 30, + [OpCode.PUSH6] = 30, + [OpCode.PUSH7] = 30, + [OpCode.PUSH8] = 30, + [OpCode.PUSH9] = 30, + [OpCode.PUSH10] = 30, + [OpCode.PUSH11] = 30, + [OpCode.PUSH12] = 30, + [OpCode.PUSH13] = 30, + [OpCode.PUSH14] = 30, + [OpCode.PUSH15] = 30, + [OpCode.PUSH16] = 30, + [OpCode.NOP] = 30, + [OpCode.JMP] = 70, + [OpCode.JMPIF] = 70, + [OpCode.JMPIFNOT] = 70, + [OpCode.CALL] = 22000, + [OpCode.RET] = 40, + [OpCode.SYSCALL] = 0, + [OpCode.DUPFROMALTSTACKBOTTOM] = 60, + [OpCode.DUPFROMALTSTACK] = 60, + [OpCode.TOALTSTACK] = 60, + [OpCode.FROMALTSTACK] = 60, + [OpCode.XDROP] = 400, + [OpCode.XSWAP] = 60, + [OpCode.XTUCK] = 400, + [OpCode.DEPTH] = 60, + [OpCode.DROP] = 60, + [OpCode.DUP] = 60, + [OpCode.NIP] = 60, + [OpCode.OVER] = 60, + [OpCode.PICK] = 60, + [OpCode.ROLL] = 400, + [OpCode.ROT] = 60, + [OpCode.SWAP] = 60, + [OpCode.TUCK] = 60, + [OpCode.CAT] = 80000, + [OpCode.SUBSTR] = 80000, + [OpCode.LEFT] = 80000, + [OpCode.RIGHT] = 80000, + [OpCode.SIZE] = 60, + [OpCode.INVERT] = 100, + [OpCode.AND] = 200, + [OpCode.OR] = 200, + [OpCode.XOR] = 200, + [OpCode.EQUAL] = 200, + [OpCode.INC] = 100, + [OpCode.DEC] = 100, + [OpCode.SIGN] = 100, + [OpCode.NEGATE] = 100, + [OpCode.ABS] = 100, + [OpCode.NOT] = 100, + [OpCode.NZ] = 100, + [OpCode.ADD] = 200, + [OpCode.SUB] = 200, + [OpCode.MUL] = 300, + [OpCode.DIV] = 300, + [OpCode.MOD] = 300, + [OpCode.SHL] = 300, + [OpCode.SHR] = 300, + [OpCode.BOOLAND] = 200, + [OpCode.BOOLOR] = 200, + [OpCode.NUMEQUAL] = 200, + [OpCode.NUMNOTEQUAL] = 200, + [OpCode.LT] = 200, + [OpCode.GT] = 200, + [OpCode.LTE] = 200, + [OpCode.GTE] = 200, + [OpCode.MIN] = 200, + [OpCode.MAX] = 200, + [OpCode.WITHIN] = 200, + [OpCode.SHA1] = 300000, + [OpCode.SHA256] = 1000000, + [OpCode.ARRAYSIZE] = 150, + [OpCode.PACK] = 7000, + [OpCode.UNPACK] = 7000, + [OpCode.PICKITEM] = 270000, + [OpCode.SETITEM] = 270000, + [OpCode.NEWARRAY] = 15000, + [OpCode.NEWSTRUCT] = 15000, + [OpCode.NEWMAP] = 200, + [OpCode.APPEND] = 15000, + [OpCode.REVERSE] = 500, + [OpCode.REMOVE] = 500, + [OpCode.HASKEY] = 270000, + [OpCode.KEYS] = 500, + [OpCode.VALUES] = 7000, + [OpCode.THROW] = 30, + [OpCode.THROWIFNOT] = 30 + }; + } +} diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index ff68377d2d..5562ac0ef2 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -1,22 +1,18 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Manifest; -using Neo.SmartContract.Native; using Neo.VM; using System; using System.Collections.Generic; -using VMArray = Neo.VM.Types.Array; namespace Neo.SmartContract { - public class ApplicationEngine : ExecutionEngine + public partial class ApplicationEngine : ExecutionEngine { public static event EventHandler Notify; public static event EventHandler Log; - public static readonly long GasFree = 10 * (long)NativeContract.GAS.Factor; - private const long ratio = 100000; + public const long GasFree = 0; private readonly long gas_amount; private readonly bool testMode; private readonly RandomAccessStack hashes = new RandomAccessStack(); @@ -49,6 +45,12 @@ internal T AddDisposable(T disposable) where T : IDisposable return disposable; } + private bool AddGas(long gas) + { + GasConsumed = checked(GasConsumed + gas); + return testMode || GasConsumed <= gas_amount; + } + private void ApplicationEngine_ContextLoaded(object sender, ExecutionContext e) { hashes.Push(((byte[])e.Script).ToScriptHash()); @@ -67,61 +69,10 @@ public override void Dispose() base.Dispose(); } - protected virtual long GetPrice() - { - Instruction instruction = CurrentContext.CurrentInstruction; - if (instruction.OpCode <= OpCode.NOP) return 0; - switch (instruction.OpCode) - { - case OpCode.SYSCALL: - return GetPriceForSysCall(); - case OpCode.SHA1: - case OpCode.SHA256: - return 10; - default: return 1; - } - } - - protected virtual long GetPriceForSysCall() - { - Instruction instruction = CurrentContext.CurrentInstruction; - uint method = instruction.TokenU32; - long price = InteropService.GetPrice(method); - if (price > 0) return price; - if (method == InteropService.Neo_Crypto_CheckMultiSig) - { - if (CurrentContext.EvaluationStack.Count == 0) return 1; - - var item = CurrentContext.EvaluationStack.Peek(); - - int n; - if (item is VMArray array) n = array.Count; - else n = (int)item.GetBigInteger(); - - if (n < 1) return 1; - return 100 * n; - } - if (method == InteropService.Neo_Contract_Create || - method == InteropService.Neo_Contract_Update) - { - long fee = 100L; - - ContractFeatures contract_properties = (ContractFeatures)(byte)CurrentContext.EvaluationStack.Peek(3).GetBigInteger(); - - if (contract_properties.HasFlag(ContractFeatures.HasStorage)) - { - fee += 400L; - } - return fee * (long)NativeContract.GAS.Factor / ratio; - } - if (method == InteropService.System_Storage_Put || - method == InteropService.System_Storage_PutEx) - return ((CurrentContext.EvaluationStack.Peek(1).GetByteArray().Length + CurrentContext.EvaluationStack.Peek(2).GetByteArray().Length - 1) / 1024 + 1) * 1000; - return 1; - } - protected override bool OnSysCall(uint method) { + if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack))) + return false; return InteropService.Invoke(this, method); } @@ -129,9 +80,7 @@ protected override bool PreExecuteInstruction() { if (CurrentContext.InstructionPointer >= CurrentContext.Script.Length) return true; - GasConsumed = checked(GasConsumed + GetPrice() * ratio); - if (!testMode && GasConsumed > gas_amount) return false; - return true; + return AddGas(OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); } public static ApplicationEngine Run(byte[] script, Snapshot snapshot, diff --git a/neo/SmartContract/Helper.cs b/neo/SmartContract/Helper.cs index 4678ab6fea..efab36ced3 100644 --- a/neo/SmartContract/Helper.cs +++ b/neo/SmartContract/Helper.cs @@ -246,7 +246,7 @@ public static UInt160 ToScriptHash(this byte[] script) return new UInt160(Crypto.Default.Hash160(script)); } - internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snapshot) + internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snapshot, long gas) { UInt160[] hashes; try @@ -270,7 +270,7 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snaps { if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false; } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, 0)) + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) { engine.LoadScript(verification); engine.LoadScript(verifiable.Witnesses[i].InvocationScript); diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 8e0144c84f..685573e25a 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -18,34 +18,51 @@ namespace Neo.SmartContract static partial class InteropService { public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0); - public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 100); - public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig); - public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 1); - public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 1); - public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 1); - public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 200); - public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 1); - public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 100); - public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 100); - public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create); - public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update); - public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 1); - public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 1); - public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 1); - public static readonly uint Neo_Enumerator_Create = Register("Neo.Enumerator.Create", Enumerator_Create, 1); - public static readonly uint Neo_Enumerator_Next = Register("Neo.Enumerator.Next", Enumerator_Next, 1); - public static readonly uint Neo_Enumerator_Value = Register("Neo.Enumerator.Value", Enumerator_Value, 1); - public static readonly uint Neo_Enumerator_Concat = Register("Neo.Enumerator.Concat", Enumerator_Concat, 1); - public static readonly uint Neo_Iterator_Create = Register("Neo.Iterator.Create", Iterator_Create, 1); - public static readonly uint Neo_Iterator_Key = Register("Neo.Iterator.Key", Iterator_Key, 1); - public static readonly uint Neo_Iterator_Keys = Register("Neo.Iterator.Keys", Iterator_Keys, 1); - public static readonly uint Neo_Iterator_Values = Register("Neo.Iterator.Values", Iterator_Values, 1); - public static readonly uint Neo_Iterator_Concat = Register("Neo.Iterator.Concat", Iterator_Concat, 1); + public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000); + public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice); + public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400); + public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 0_00000400); + public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 0_00000400); + public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000); + public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 0_00000400); + public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400); + public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000); + public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice); + public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice); + public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400); + public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400); + public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000); + public static readonly uint Neo_Enumerator_Create = Register("Neo.Enumerator.Create", Enumerator_Create, 0_00000400); + public static readonly uint Neo_Enumerator_Next = Register("Neo.Enumerator.Next", Enumerator_Next, 0_01000000); + public static readonly uint Neo_Enumerator_Value = Register("Neo.Enumerator.Value", Enumerator_Value, 0_00000400); + public static readonly uint Neo_Enumerator_Concat = Register("Neo.Enumerator.Concat", Enumerator_Concat, 0_00000400); + public static readonly uint Neo_Iterator_Create = Register("Neo.Iterator.Create", Iterator_Create, 0_00000400); + public static readonly uint Neo_Iterator_Key = Register("Neo.Iterator.Key", Iterator_Key, 0_00000400); + public static readonly uint Neo_Iterator_Keys = Register("Neo.Iterator.Keys", Iterator_Keys, 0_00000400); + public static readonly uint Neo_Iterator_Values = Register("Neo.Iterator.Values", Iterator_Values, 0_00000400); + public static readonly uint Neo_Iterator_Concat = Register("Neo.Iterator.Concat", Iterator_Concat, 0_00000400); static InteropService() { foreach (NativeContract contract in NativeContract.Contracts) - Register(contract.ServiceName, contract.Invoke); + Register(contract.ServiceName, contract.Invoke, contract.GetPrice); + } + + private static long GetCheckMultiSigPrice(RandomAccessStack stack) + { + if (stack.Count == 0) return 0; + var item = stack.Peek(); + int n; + if (item is VMArray array) n = array.Count; + else n = (int)item.GetBigInteger(); + if (n < 1) return 0; + return GetPrice(Neo_Crypto_CheckSig, stack) * n; + } + + private static long GetDeploymentPrice(RandomAccessStack stack) + { + int size = stack.Peek(0).GetByteLength() + stack.Peek(1).GetByteLength(); + return GasPerByte * size; } private static bool Native_Deploy(ApplicationEngine engine) diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index c5de54c7ba..9019affe33 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -19,48 +19,50 @@ namespace Neo.SmartContract { public static partial class InteropService { + public const long GasPerByte = 100000; public const int MaxStorageKeySize = 64; public const int MaxStorageValueSize = ushort.MaxValue; private static readonly Dictionary> methods = new Dictionary>(); private static readonly Dictionary prices = new Dictionary(); - - public static readonly uint System_ExecutionEngine_GetScriptContainer = Register("System.ExecutionEngine.GetScriptContainer", ExecutionEngine_GetScriptContainer, 1); - public static readonly uint System_ExecutionEngine_GetExecutingScriptHash = Register("System.ExecutionEngine.GetExecutingScriptHash", ExecutionEngine_GetExecutingScriptHash, 1); - public static readonly uint System_ExecutionEngine_GetCallingScriptHash = Register("System.ExecutionEngine.GetCallingScriptHash", ExecutionEngine_GetCallingScriptHash, 1); - public static readonly uint System_ExecutionEngine_GetEntryScriptHash = Register("System.ExecutionEngine.GetEntryScriptHash", ExecutionEngine_GetEntryScriptHash, 1); - public static readonly uint System_Runtime_Platform = Register("System.Runtime.Platform", Runtime_Platform, 1); - public static readonly uint System_Runtime_GetTrigger = Register("System.Runtime.GetTrigger", Runtime_GetTrigger, 1); - public static readonly uint System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", Runtime_CheckWitness, 200); - public static readonly uint System_Runtime_Notify = Register("System.Runtime.Notify", Runtime_Notify, 1); - public static readonly uint System_Runtime_Log = Register("System.Runtime.Log", Runtime_Log, 1); - public static readonly uint System_Runtime_GetTime = Register("System.Runtime.GetTime", Runtime_GetTime, 1); - public static readonly uint System_Runtime_Serialize = Register("System.Runtime.Serialize", Runtime_Serialize, 1); - public static readonly uint System_Runtime_Deserialize = Register("System.Runtime.Deserialize", Runtime_Deserialize, 1); - public static readonly uint System_Crypto_Verify = Register("System.Crypto.Verify", Crypto_Verify, 100); - public static readonly uint System_Blockchain_GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 1); - public static readonly uint System_Blockchain_GetHeader = Register("System.Blockchain.GetHeader", Blockchain_GetHeader, 100); - public static readonly uint System_Blockchain_GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 200); - public static readonly uint System_Blockchain_GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 200); - public static readonly uint System_Blockchain_GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight, 100); - public static readonly uint System_Blockchain_GetContract = Register("System.Blockchain.GetContract", Blockchain_GetContract, 100); - public static readonly uint System_Header_GetIndex = Register("System.Header.GetIndex", Header_GetIndex, 1); - public static readonly uint System_Header_GetHash = Register("System.Header.GetHash", Header_GetHash, 1); - public static readonly uint System_Header_GetPrevHash = Register("System.Header.GetPrevHash", Header_GetPrevHash, 1); - public static readonly uint System_Header_GetTimestamp = Register("System.Header.GetTimestamp", Header_GetTimestamp, 1); - public static readonly uint System_Block_GetTransactionCount = Register("System.Block.GetTransactionCount", Block_GetTransactionCount, 1); - public static readonly uint System_Block_GetTransactions = Register("System.Block.GetTransactions", Block_GetTransactions, 1); - public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 1); - public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 1); - public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 10); - public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 1); - public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 1); - public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 1); - public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 100); - public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put); - public static readonly uint System_Storage_PutEx = Register("System.Storage.PutEx", Storage_PutEx); - public static readonly uint System_Storage_Delete = Register("System.Storage.Delete", Storage_Delete, 100); - public static readonly uint System_StorageContext_AsReadOnly = Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly, 1); + private static readonly Dictionary, long>> priceCalculators = new Dictionary, long>>(); + + public static readonly uint System_ExecutionEngine_GetScriptContainer = Register("System.ExecutionEngine.GetScriptContainer", ExecutionEngine_GetScriptContainer, 0_00000250); + public static readonly uint System_ExecutionEngine_GetExecutingScriptHash = Register("System.ExecutionEngine.GetExecutingScriptHash", ExecutionEngine_GetExecutingScriptHash, 0_00000400); + public static readonly uint System_ExecutionEngine_GetCallingScriptHash = Register("System.ExecutionEngine.GetCallingScriptHash", ExecutionEngine_GetCallingScriptHash, 0_00000400); + public static readonly uint System_ExecutionEngine_GetEntryScriptHash = Register("System.ExecutionEngine.GetEntryScriptHash", ExecutionEngine_GetEntryScriptHash, 0_00000400); + public static readonly uint System_Runtime_Platform = Register("System.Runtime.Platform", Runtime_Platform, 0_00000250); + public static readonly uint System_Runtime_GetTrigger = Register("System.Runtime.GetTrigger", Runtime_GetTrigger, 0_00000250); + public static readonly uint System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", Runtime_CheckWitness, 0_00030000); + public static readonly uint System_Runtime_Notify = Register("System.Runtime.Notify", Runtime_Notify, 0_00000250); + public static readonly uint System_Runtime_Log = Register("System.Runtime.Log", Runtime_Log, 0_00300000); + public static readonly uint System_Runtime_GetTime = Register("System.Runtime.GetTime", Runtime_GetTime, 0_00000250); + public static readonly uint System_Runtime_Serialize = Register("System.Runtime.Serialize", Runtime_Serialize, 0_00100000); + public static readonly uint System_Runtime_Deserialize = Register("System.Runtime.Deserialize", Runtime_Deserialize, 0_00500000); + public static readonly uint System_Crypto_Verify = Register("System.Crypto.Verify", Crypto_Verify, 0_01000000); + public static readonly uint System_Blockchain_GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400); + public static readonly uint System_Blockchain_GetHeader = Register("System.Blockchain.GetHeader", Blockchain_GetHeader, 0_00007000); + public static readonly uint System_Blockchain_GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 0_02500000); + public static readonly uint System_Blockchain_GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 0_01000000); + public static readonly uint System_Blockchain_GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight, 0_01000000); + public static readonly uint System_Blockchain_GetContract = Register("System.Blockchain.GetContract", Blockchain_GetContract, 0_01000000); + public static readonly uint System_Header_GetIndex = Register("System.Header.GetIndex", Header_GetIndex, 0_00000400); + public static readonly uint System_Header_GetHash = Register("System.Header.GetHash", Header_GetHash, 0_00000400); + public static readonly uint System_Header_GetPrevHash = Register("System.Header.GetPrevHash", Header_GetPrevHash, 0_00000400); + public static readonly uint System_Header_GetTimestamp = Register("System.Header.GetTimestamp", Header_GetTimestamp, 0_00000400); + public static readonly uint System_Block_GetTransactionCount = Register("System.Block.GetTransactionCount", Block_GetTransactionCount, 0_00000400); + public static readonly uint System_Block_GetTransactions = Register("System.Block.GetTransactions", Block_GetTransactions, 0_00010000); + public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 0_00000400); + public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 0_00000400); + public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000); + public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000); + public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400); + public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400); + public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000); + public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice); + public static readonly uint System_Storage_PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice); + public static readonly uint System_Storage_Delete = Register("System.Storage.Delete", Storage_Delete, 0_01000000); + public static readonly uint System_StorageContext_AsReadOnly = Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly, 0_00000400); private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) { @@ -70,10 +72,17 @@ private static bool CheckStorageContext(ApplicationEngine engine, StorageContext return true; } - public static long GetPrice(uint hash) + public static long GetPrice(uint hash, RandomAccessStack stack) + { + if (prices.TryGetValue(hash, out long price)) + return price; + else + return priceCalculators[hash](stack); + } + + private static long GetStoragePrice(RandomAccessStack stack) { - prices.TryGetValue(hash, out long price); - return price; + return (stack.Peek(1).GetByteLength() + stack.Peek(2).GetByteLength()) * GasPerByte; } internal static bool Invoke(ApplicationEngine engine, uint method) @@ -83,17 +92,19 @@ internal static bool Invoke(ApplicationEngine engine, uint method) return func(engine); } - private static uint Register(string method, Func handler) + private static uint Register(string method, Func handler, long price) { uint hash = method.ToInteropMethodHash(); methods.Add(hash, handler); + prices.Add(hash, price); return hash; } - private static uint Register(string method, Func handler, long price) + private static uint Register(string method, Func handler, Func, long> priceCalculator) { - uint hash = Register(method, handler); - prices.Add(hash, price); + uint hash = method.ToInteropMethodHash(); + methods.Add(hash, handler); + priceCalculators.Add(hash, priceCalculator); return hash; } diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 0cba715ada..98e4da64c2 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -85,6 +85,16 @@ internal bool Invoke(ApplicationEngine engine) return true; } + internal virtual long GetPrice(RandomAccessStack stack) + { + return GetPriceForMethod(stack.Peek().GetString()); + } + + protected virtual long GetPriceForMethod(string method) + { + return 0; + } + protected virtual StackItem Main(ApplicationEngine engine, string operation, VMArray args) { switch (operation) diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index ed3ae120d2..2e94bfa326 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -146,6 +146,28 @@ private bool CheckValidators(ApplicationEngine engine) return InteropService.CheckWitness(engine, prev_block.NextConsensus); } + protected override long GetPriceForMethod(string method) + { + switch (method) + { + case "getMaxTransactionsPerBlock": + case "getMaxLowPriorityTransactionsPerBlock": + case "getMaxLowPriorityTransactionSize": + case "getFeePerByte": + case "getBlockedAccounts": + return 0_01000000; + case "setMaxTransactionsPerBlock": + case "setMaxLowPriorityTransactionsPerBlock": + case "setMaxLowPriorityTransactionSize": + case "setFeePerByte": + case "blockAccount": + case "unblockAccount": + return 0_03000000; + default: + return base.GetPriceForMethod(method); + } + } + protected override StackItem Main(ApplicationEngine engine, string operation, VM.Types.Array args) { switch (operation) diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index f4c801b1ec..0de853efce 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -43,6 +43,17 @@ internal GasToken() : base() Manifest.Abi.Methods = list.ToArray(); } + protected override long GetPriceForMethod(string method) + { + switch (method) + { + case "getSysFeeAmount": + return 0_01000000; + default: + return base.GetPriceForMethod(method); + } + } + protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) { if (operation == "getSysFeeAmount") diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index 71a560507e..99afdfbc1f 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -104,6 +104,25 @@ internal NeoToken() : base() Manifest.Abi.Methods = list.ToArray(); } + protected override long GetPriceForMethod(string method) + { + switch (method) + { + case "unclaimedGas": + return 0_03000000; + case "registerValidator": + return 0_05000000; + case "vote": + return 5_00000000; + case "getRegisteredValidators": + case "getValidators": + case "getNextBlockValidators": + return 1_00000000; + default: + return base.GetPriceForMethod(method); + } + } + protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) { switch (operation) diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index 6f1847678b..69f797f3d3 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -126,6 +126,24 @@ protected StorageKey CreateAccountKey(UInt160 account) return CreateStorageKey(Prefix_Account, account); } + protected override long GetPriceForMethod(string method) + { + switch (method) + { + case "name": + case "symbol": + case "decimals": + return 0; + case "totalSupply": + case "balanceOf": + return 0_01000000; + case "transfer": + return 0_08000000; + default: + return base.GetPriceForMethod(method); + } + } + protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) { switch (operation) From f26968f251189dafb7b361c0a3e1dcdda408f3dd Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 24 May 2019 16:33:52 +0200 Subject: [PATCH 4/8] Fix visual studio messages (#780) --- neo/IO/Caching/Cache.cs | 2 +- neo/IO/Caching/CloneCache.cs | 2 +- neo/IO/Caching/CloneMetaCache.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neo/IO/Caching/Cache.cs b/neo/IO/Caching/Cache.cs index 277f85c9bb..45556b8ae3 100644 --- a/neo/IO/Caching/Cache.cs +++ b/neo/IO/Caching/Cache.cs @@ -247,7 +247,7 @@ public bool TryGet(TKey key, out TValue item) { RwSyncRootLock.ExitReadLock(); } - item = default(TValue); + item = default; return false; } } diff --git a/neo/IO/Caching/CloneCache.cs b/neo/IO/Caching/CloneCache.cs index 951de4b942..f13865a41b 100644 --- a/neo/IO/Caching/CloneCache.cs +++ b/neo/IO/Caching/CloneCache.cs @@ -7,7 +7,7 @@ internal class CloneCache : DataCache where TKey : IEquatable, ISerializable where TValue : class, ICloneable, ISerializable, new() { - private DataCache innerCache; + private readonly DataCache innerCache; public CloneCache(DataCache innerCache) { diff --git a/neo/IO/Caching/CloneMetaCache.cs b/neo/IO/Caching/CloneMetaCache.cs index f44600a336..6b354e256e 100644 --- a/neo/IO/Caching/CloneMetaCache.cs +++ b/neo/IO/Caching/CloneMetaCache.cs @@ -3,7 +3,7 @@ internal class CloneMetaCache : MetaDataCache where T : class, ICloneable, ISerializable, new() { - private MetaDataCache innerCache; + private readonly MetaDataCache innerCache; public CloneMetaCache(MetaDataCache innerCache) : base(null) From 56866f46f2ab6522bd6a28110dd8ed9f370f5d4d Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Sat, 25 May 2019 19:22:28 +0800 Subject: [PATCH 5/8] Add `ContractMethodAttribute` (#778) --- neo.UnitTests/UT_NativeContractPrices.cs | 27 -- neo/Cryptography/ECC/ECPoint.cs | 13 - .../Native/ContractMethodAttribute.cs | 22 ++ .../Native/ContractMethodMetadata.cs | 13 + neo/SmartContract/Native/NativeContract.cs | 78 +++--- neo/SmartContract/Native/PolicyContract.cs | 236 ++++-------------- neo/SmartContract/Native/Tokens/GasToken.cs | 52 +--- neo/SmartContract/Native/Tokens/NeoToken.cs | 167 ++++--------- neo/SmartContract/Native/Tokens/Nep5Token.cs | 151 ++++------- neo/SmartContract/TriggerType.cs | 9 +- 10 files changed, 238 insertions(+), 530 deletions(-) delete mode 100644 neo.UnitTests/UT_NativeContractPrices.cs create mode 100644 neo/SmartContract/Native/ContractMethodAttribute.cs create mode 100644 neo/SmartContract/Native/ContractMethodMetadata.cs diff --git a/neo.UnitTests/UT_NativeContractPrices.cs b/neo.UnitTests/UT_NativeContractPrices.cs deleted file mode 100644 index b7e07a9ec3..0000000000 --- a/neo.UnitTests/UT_NativeContractPrices.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.SmartContract.Native; -using System; -using System.Reflection; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_NativeContractPrices - { - [TestMethod] - public void AllNativeContractPriceAreSet() - { - Type t = typeof(NativeContract); - foreach (Type ct in t.Assembly.GetTypes()) - { - if (t.IsAssignableFrom(ct)) - { - MethodInfo method1 = ct.GetMethod("GetPrice", BindingFlags.NonPublic | BindingFlags.Instance); - MethodInfo method2 = ct.GetMethod("GetPriceForMethod", BindingFlags.NonPublic | BindingFlags.Instance); - (method1.DeclaringType == ct || method2.DeclaringType == ct).Should().BeTrue(); - } - } - } - } -} diff --git a/neo/Cryptography/ECC/ECPoint.cs b/neo/Cryptography/ECC/ECPoint.cs index 99d2b099e1..6c0746adab 100644 --- a/neo/Cryptography/ECC/ECPoint.cs +++ b/neo/Cryptography/ECC/ECPoint.cs @@ -47,13 +47,6 @@ public static ECPoint DecodePoint(byte[] encoded, ECCurve curve) int expectedLength = (curve.Q.GetBitLength() + 7) / 8; switch (encoded[0]) { - case 0x00: // infinity - { - if (encoded.Length != 1) - throw new FormatException("Incorrect length for infinity encoding"); - p = curve.Infinity; - break; - } case 0x02: // compressed case 0x03: // compressed { @@ -65,8 +58,6 @@ public static ECPoint DecodePoint(byte[] encoded, ECCurve curve) break; } case 0x04: // uncompressed - case 0x06: // hybrid - case 0x07: // hybrid { if (encoded.Length != (2 * expectedLength + 1)) throw new FormatException("Incorrect length for uncompressed/hybrid encoding"); @@ -120,15 +111,11 @@ public static ECPoint DeserializeFrom(BinaryReader reader, ECCurve curve) buffer[0] = reader.ReadByte(); switch (buffer[0]) { - case 0x00: - return curve.Infinity; case 0x02: case 0x03: reader.Read(buffer, 1, expectedLength); return DecodePoint(buffer.Take(1 + expectedLength).ToArray(), curve); case 0x04: - case 0x06: - case 0x07: reader.Read(buffer, 1, expectedLength * 2); return DecodePoint(buffer, curve); default: diff --git a/neo/SmartContract/Native/ContractMethodAttribute.cs b/neo/SmartContract/Native/ContractMethodAttribute.cs new file mode 100644 index 0000000000..145979a6cb --- /dev/null +++ b/neo/SmartContract/Native/ContractMethodAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Neo.SmartContract.Native +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + internal class ContractMethodAttribute : Attribute + { + public string Name { get; set; } + public long Price { get; } + public TriggerType AllowedTriggers { get; set; } = TriggerType.All; + 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 ContractMethodAttribute(long price, ContractParameterType returnType) + { + this.Price = price; + this.ReturnType = returnType; + } + } +} diff --git a/neo/SmartContract/Native/ContractMethodMetadata.cs b/neo/SmartContract/Native/ContractMethodMetadata.cs new file mode 100644 index 0000000000..1c67b553e2 --- /dev/null +++ b/neo/SmartContract/Native/ContractMethodMetadata.cs @@ -0,0 +1,13 @@ +using Neo.VM; +using System; +using VMArray = Neo.VM.Types.Array; + +namespace Neo.SmartContract.Native +{ + internal class ContractMethodMetadata + { + public Func Delegate; + public long Price; + public TriggerType AllowedTriggers; + } +} diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 98e4da64c2..22c54ac127 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -1,4 +1,6 @@ -using Neo.IO; +#pragma warning disable IDE0060 + +using Neo.IO; using Neo.Ledger; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native.Tokens; @@ -6,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using VMArray = Neo.VM.Types.Array; namespace Neo.SmartContract.Native @@ -13,6 +16,7 @@ namespace Neo.SmartContract.Native public abstract class NativeContract { private static readonly List contracts = new List(); + private readonly Dictionary methods = new Dictionary(); public static IReadOnlyCollection Contracts { get; } = contracts; public static NeoToken NEO { get; } = new NeoToken(); @@ -34,25 +38,31 @@ protected NativeContract() sb.EmitSysCall(ServiceHash); this.Script = sb.ToArray(); } - this.Hash = Script.ToScriptHash(); this.Manifest = ContractManifest.CreateDefault(this.Hash); - this.Manifest.Abi.Methods = new ContractMethodDescriptor[] + List descriptors = new List(); + List safeMethods = new List(); + foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { - new ContractMethodDescriptor() + ContractMethodAttribute attribute = method.GetCustomAttribute(); + if (attribute is null) continue; + string name = attribute.Name ?? (method.Name.ToLower()[0] + method.Name.Substring(1)); + descriptors.Add(new ContractMethodDescriptor { - Name = "onPersist", - ReturnType = ContractParameterType.Boolean, - Parameters = new ContractParameterDefinition[0] - }, - new ContractMethodDescriptor() + Name = name, + ReturnType = attribute.ReturnType, + 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 { - Name = "supportedStandards", - ReturnType = ContractParameterType.Array, - Parameters = new ContractParameterDefinition[0] - } - }; - + Delegate = (Func)method.CreateDelegate(typeof(Func), this), + Price = attribute.Price, + AllowedTriggers = attribute.AllowedTriggers + }); + } + this.Manifest.Abi.Methods = descriptors.ToArray(); + this.Manifest.SafeMethods = WildCardContainer.Create(safeMethods.ToArray()); contracts.Add(this); } @@ -80,45 +90,41 @@ internal bool Invoke(ApplicationEngine engine) return false; string operation = engine.CurrentContext.EvaluationStack.Pop().GetString(); VMArray args = (VMArray)engine.CurrentContext.EvaluationStack.Pop(); - StackItem result = Main(engine, operation, args); + if (!methods.TryGetValue(operation, out ContractMethodMetadata method)) + return false; + if (!method.AllowedTriggers.HasFlag(engine.Trigger)) return false; + StackItem result = method.Delegate(engine, args); engine.CurrentContext.EvaluationStack.Push(result); return true; } - internal virtual long GetPrice(RandomAccessStack stack) + internal long GetPrice(RandomAccessStack stack) { - return GetPriceForMethod(stack.Peek().GetString()); + return methods.TryGetValue(stack.Peek().GetString(), out ContractMethodMetadata method) ? method.Price : 0; } - protected virtual long GetPriceForMethod(string method) + internal virtual bool Initialize(ApplicationEngine engine) { - return 0; + if (engine.Trigger != TriggerType.Application) + throw new InvalidOperationException(); + return true; } - protected virtual StackItem Main(ApplicationEngine engine, string operation, VMArray args) + [ContractMethod(0, ContractParameterType.Boolean, AllowedTriggers = TriggerType.System)] + protected StackItem OnPersist(ApplicationEngine engine, VMArray args) { - switch (operation) - { - case "onPersist": - return OnPersist(engine); - case "supportedStandards": - return SupportedStandards.Select(p => (StackItem)p).ToList(); - } - throw new NotSupportedException(); + return OnPersist(engine); } - internal virtual bool Initialize(ApplicationEngine engine) + protected virtual bool OnPersist(ApplicationEngine engine) { - if (engine.Trigger != TriggerType.Application) - throw new InvalidOperationException(); return true; } - protected virtual bool OnPersist(ApplicationEngine engine) + [ContractMethod(0, ContractParameterType.Array, Name = "supportedStandards", SafeMethod = true)] + protected StackItem SupportedStandardsMethod(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.System) - throw new InvalidOperationException(); - return true; + return SupportedStandards.Select(p => (StackItem)p).ToList(); } public ApplicationEngine TestCall(string operation, params object[] args) diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index 2e94bfa326..e5f8dbeefe 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -1,4 +1,7 @@ -using Neo.IO; +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + +using Neo.IO; using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract.Manifest; @@ -6,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; +using VMArray = Neo.VM.Types.Array; namespace Neo.SmartContract.Native { @@ -19,124 +23,9 @@ public sealed class PolicyContract : NativeContract private const byte Prefix_FeePerByte = 10; private const byte Prefix_BlockedAccounts = 15; - public PolicyContract() : base() + public PolicyContract() { Manifest.Features = ContractFeatures.HasStorage; - - var list = new List(Manifest.Abi.Methods) - { - new ContractMethodDescriptor() - { - Name = "getMaxTransactionsPerBlock", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "getMaxLowPriorityTransactionsPerBlock", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "getMaxLowPriorityTransactionSize", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "getFeePerByte", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "getBlockedAccounts", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "setMaxTransactionsPerBlock", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "value", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "setMaxLowPriorityTransactionsPerBlock", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "value", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "setMaxLowPriorityTransactionSize", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "value", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "setFeePerByte", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "value", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Boolean - }, - - new ContractMethodDescriptor() - { - Name = "blockAccount", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "unblockAccount", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - } - }, - ReturnType = ContractParameterType.Boolean - } - }; - - Manifest.Abi.Methods = list.ToArray(); } private bool CheckValidators(ApplicationEngine engine) @@ -146,59 +35,6 @@ private bool CheckValidators(ApplicationEngine engine) return InteropService.CheckWitness(engine, prev_block.NextConsensus); } - protected override long GetPriceForMethod(string method) - { - switch (method) - { - case "getMaxTransactionsPerBlock": - case "getMaxLowPriorityTransactionsPerBlock": - case "getMaxLowPriorityTransactionSize": - case "getFeePerByte": - case "getBlockedAccounts": - return 0_01000000; - case "setMaxTransactionsPerBlock": - case "setMaxLowPriorityTransactionsPerBlock": - case "setMaxLowPriorityTransactionSize": - case "setFeePerByte": - case "blockAccount": - case "unblockAccount": - return 0_03000000; - default: - return base.GetPriceForMethod(method); - } - } - - protected override StackItem Main(ApplicationEngine engine, string operation, VM.Types.Array args) - { - switch (operation) - { - case "getMaxTransactionsPerBlock": - return GetMaxTransactionsPerBlock(engine.Snapshot); - case "getMaxLowPriorityTransactionsPerBlock": - return GetMaxLowPriorityTransactionsPerBlock(engine.Snapshot); - case "getMaxLowPriorityTransactionSize": - return GetMaxLowPriorityTransactionSize(engine.Snapshot); - case "getFeePerByte": - return GetFeePerByte(engine.Snapshot); - case "getBlockedAccounts": - return GetBlockedAccounts(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToArray(); - case "setMaxTransactionsPerBlock": - return SetMaxTransactionsPerBlock(engine, (uint)args[0].GetBigInteger()); - case "setMaxLowPriorityTransactionsPerBlock": - return SetMaxLowPriorityTransactionsPerBlock(engine, (uint)args[0].GetBigInteger()); - case "setMaxLowPriorityTransactionSize": - return SetMaxLowPriorityTransactionSize(engine, (uint)args[0].GetBigInteger()); - case "setFeePerByte": - return SetFeePerByte(engine, (long)args[0].GetBigInteger()); - case "blockAccount": - return BlockAccount(engine, new UInt160(args[0].GetByteArray())); - case "unblockAccount": - return UnblockAccount(engine, new UInt160(args[0].GetByteArray())); - default: - return base.Main(engine, operation, args); - } - } - internal override bool Initialize(ApplicationEngine engine) { if (!base.Initialize(engine)) return false; @@ -225,71 +61,106 @@ internal override bool Initialize(ApplicationEngine engine) return true; } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + private StackItem GetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray args) + { + return GetMaxTransactionsPerBlock(engine.Snapshot); + } + public uint GetMaxTransactionsPerBlock(Snapshot snapshot) { return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxTransactionsPerBlock)].Value, 0); } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + private StackItem GetMaxLowPriorityTransactionsPerBlock(ApplicationEngine engine, VMArray args) + { + return GetMaxLowPriorityTransactionsPerBlock(engine.Snapshot); + } + public uint GetMaxLowPriorityTransactionsPerBlock(Snapshot snapshot) { return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxLowPriorityTransactionsPerBlock)].Value, 0); } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + private StackItem GetMaxLowPriorityTransactionSize(ApplicationEngine engine, VMArray args) + { + return GetMaxLowPriorityTransactionSize(engine.Snapshot); + } + public uint GetMaxLowPriorityTransactionSize(Snapshot snapshot) { return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxLowPriorityTransactionSize)].Value, 0); } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + private StackItem GetFeePerByte(ApplicationEngine engine, VMArray args) + { + return GetFeePerByte(engine.Snapshot); + } + public long GetFeePerByte(Snapshot snapshot) { return BitConverter.ToInt64(snapshot.Storages[CreateStorageKey(Prefix_FeePerByte)].Value, 0); } + [ContractMethod(0_01000000, ContractParameterType.Array, SafeMethod = true)] + private StackItem GetBlockedAccounts(ApplicationEngine engine, VMArray args) + { + return GetBlockedAccounts(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); + } + public UInt160[] GetBlockedAccounts(Snapshot snapshot) { return snapshot.Storages[CreateStorageKey(Prefix_BlockedAccounts)].Value.AsSerializableArray(); } - private bool SetMaxTransactionsPerBlock(ApplicationEngine engine, uint value) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] + private StackItem SetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + uint value = (uint)args[0].GetBigInteger(); StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxTransactionsPerBlock)); storage.Value = BitConverter.GetBytes(value); return true; } - private bool SetMaxLowPriorityTransactionsPerBlock(ApplicationEngine engine, uint value) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] + private StackItem SetMaxLowPriorityTransactionsPerBlock(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + uint value = (uint)args[0].GetBigInteger(); StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxLowPriorityTransactionsPerBlock)); storage.Value = BitConverter.GetBytes(value); return true; } - private bool SetMaxLowPriorityTransactionSize(ApplicationEngine engine, uint value) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] + private StackItem SetMaxLowPriorityTransactionSize(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + uint value = (uint)args[0].GetBigInteger(); StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxLowPriorityTransactionSize)); storage.Value = BitConverter.GetBytes(value); return true; } - private bool SetFeePerByte(ApplicationEngine engine, long value) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] + private StackItem SetFeePerByte(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + long value = (long)args[0].GetBigInteger(); StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_FeePerByte)); storage.Value = BitConverter.GetBytes(value); return true; } - private bool BlockAccount(ApplicationEngine engine, UInt160 account) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, AllowedTriggers = TriggerType.Application)] + private StackItem BlockAccount(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + UInt160 account = new UInt160(args[0].GetByteArray()); StorageKey key = CreateStorageKey(Prefix_BlockedAccounts); StorageItem storage = engine.Snapshot.Storages[key]; HashSet accounts = new HashSet(storage.Value.AsSerializableArray()); @@ -299,10 +170,11 @@ private bool BlockAccount(ApplicationEngine engine, UInt160 account) return true; } - private bool UnblockAccount(ApplicationEngine engine, UInt160 account) + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160 }, ParameterNames = new[] { "account" }, AllowedTriggers = TriggerType.Application)] + private StackItem UnblockAccount(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.Application) return false; if (!CheckValidators(engine)) return false; + UInt160 account = new UInt160(args[0].GetByteArray()); StorageKey key = CreateStorageKey(Prefix_BlockedAccounts); StorageItem storage = engine.Snapshot.Storages[key]; HashSet accounts = new HashSet(storage.Value.AsSerializableArray()); diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index 0de853efce..0bcc303fe7 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -1,11 +1,11 @@ -using Neo.Cryptography.ECC; +#pragma warning disable IDE0051 + +using Neo.Cryptography.ECC; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; using VMArray = Neo.VM.Types.Array; @@ -21,45 +21,8 @@ public sealed class GasToken : Nep5Token private const byte Prefix_SystemFeeAmount = 15; - internal GasToken() : base() + internal GasToken() { - var list = new List(Manifest.Abi.Methods) - { - new ContractMethodDescriptor() - { - Name = "getSysFeeAmount", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "index", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Integer - } - }; - - Manifest.Abi.Methods = list.ToArray(); - } - - protected override long GetPriceForMethod(string method) - { - switch (method) - { - case "getSysFeeAmount": - return 0_01000000; - default: - return base.GetPriceForMethod(method); - } - } - - protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) - { - if (operation == "getSysFeeAmount") - return GetSysFeeAmount(engine.Snapshot, (uint)args[0].GetBigInteger()); - else - return base.Main(engine, operation, args); } protected override bool OnPersist(ApplicationEngine engine) @@ -80,6 +43,13 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, SafeMethod = true)] + private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) + { + uint index = (uint)args[0].GetBigInteger(); + return GetSysFeeAmount(engine.Snapshot, index); + } + public BigInteger GetSysFeeAmount(Snapshot snapshot, uint index) { if (index == 0) return Blockchain.GenesisBlock.Transactions.Sum(p => p.Gas); diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index 99afdfbc1f..5320171059 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -1,8 +1,10 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + using Neo.Cryptography.ECC; using Neo.IO; using Neo.Ledger; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -26,122 +28,9 @@ public sealed class NeoToken : Nep5Token private const byte Prefix_ValidatorsCount = 15; private const byte Prefix_NextValidators = 14; - internal NeoToken() : base() + internal NeoToken() { this.TotalAmount = 100000000 * Factor; - - var list = new List(Manifest.Abi.Methods) - { - new ContractMethodDescriptor() - { - Name = "unclaimedGas", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "end", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "registerValidator", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "pubkey", - Type = ContractParameterType.ByteArray - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "vote", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "pubkeys", - Type = ContractParameterType.Array - } - }, - ReturnType = ContractParameterType.Boolean - }, - new ContractMethodDescriptor() - { - Name = "getRegisteredValidators", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Array - }, - new ContractMethodDescriptor() - { - Name = "getValidators", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Array - }, - new ContractMethodDescriptor() - { - Name = "getNextBlockValidators", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Array - } - }; - - Manifest.Abi.Methods = list.ToArray(); - } - - protected override long GetPriceForMethod(string method) - { - switch (method) - { - case "unclaimedGas": - return 0_03000000; - case "registerValidator": - return 0_05000000; - case "vote": - return 5_00000000; - case "getRegisteredValidators": - case "getValidators": - case "getNextBlockValidators": - return 1_00000000; - default: - return base.GetPriceForMethod(method); - } - } - - protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) - { - switch (operation) - { - case "unclaimedGas": - return UnclaimedGas(engine.Snapshot, new UInt160(args[0].GetByteArray()), (uint)args[1].GetBigInteger()); - case "registerValidator": - return RegisterValidator(engine, args[0].GetByteArray()); - case "vote": - return Vote(engine, new UInt160(args[0].GetByteArray()), ((VMArray)args[1]).Select(p => p.GetByteArray().AsSerializable()).ToArray()); - case "getRegisteredValidators": - return GetRegisteredValidators(engine.Snapshot).Select(p => new Struct(new StackItem[] { p.PublicKey.ToArray(), p.Votes })).ToArray(); - case "getValidators": - return GetValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToArray(); - case "getNextBlockValidators": - return GetNextBlockValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToArray(); - default: - return base.Main(engine, operation, args); - } } public override BigInteger TotalSupply(Snapshot snapshot) @@ -215,7 +104,7 @@ internal override bool Initialize(ApplicationEngine engine) UInt160 account = Contract.CreateMultiSigRedeemScript(Blockchain.StandbyValidators.Length / 2 + 1, Blockchain.StandbyValidators).ToScriptHash(); Mint(engine, account, TotalAmount); foreach (ECPoint pubkey in Blockchain.StandbyValidators) - RegisterValidator(engine, pubkey.EncodePoint(true)); + RegisterValidator(engine.Snapshot, pubkey); return true; } @@ -227,6 +116,14 @@ 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)] + private StackItem UnclaimedGas(ApplicationEngine engine, VMArray args) + { + UInt160 account = new UInt160(args[0].GetByteArray()); + uint end = (uint)args[1].GetBigInteger(); + return UnclaimedGas(engine.Snapshot, account, end); + } + public BigInteger UnclaimedGas(Snapshot snapshot, UInt160 account, uint end) { StorageItem storage = snapshot.Storages.TryGet(CreateAccountKey(account)); @@ -235,21 +132,29 @@ public BigInteger UnclaimedGas(Snapshot snapshot, UInt160 account, uint end) return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end); } - private bool RegisterValidator(ApplicationEngine engine, byte[] pubkey) + [ContractMethod(0_05000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.PublicKey }, ParameterNames = new[] { "pubkey" }, AllowedTriggers = TriggerType.Application)] + private StackItem RegisterValidator(ApplicationEngine engine, VMArray args) + { + ECPoint pubkey = args[0].GetByteArray().AsSerializable(); + return RegisterValidator(engine.Snapshot, pubkey); + } + + private bool RegisterValidator(Snapshot snapshot, ECPoint pubkey) { - if (pubkey.Length != 33 || (pubkey[0] != 0x02 && pubkey[0] != 0x03)) - throw new ArgumentException(); StorageKey key = CreateStorageKey(Prefix_Validator, pubkey); - if (engine.Snapshot.Storages.TryGet(key) != null) return false; - engine.Snapshot.Storages.Add(key, new StorageItem + if (snapshot.Storages.TryGet(key) != null) return false; + snapshot.Storages.Add(key, new StorageItem { Value = new ValidatorState().ToByteArray() }); return true; } - private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint[] pubkeys) + [ContractMethod(5_00000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Array }, ParameterNames = new[] { "account", "pubkeys" }, AllowedTriggers = TriggerType.Application)] + private StackItem Vote(ApplicationEngine engine, VMArray args) { + UInt160 account = new UInt160(args[0].GetByteArray()); + ECPoint[] pubkeys = ((VMArray)args[1]).Select(p => p.GetByteArray().AsSerializable()).ToArray(); if (!InteropService.CheckWitness(engine, account)) return false; StorageKey key_account = CreateAccountKey(account); if (engine.Snapshot.Storages.TryGet(key_account) is null) return false; @@ -288,6 +193,12 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint[] pubkeys) return true; } + [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args) + { + return GetRegisteredValidators(engine.Snapshot).Select(p => new Struct(new StackItem[] { p.PublicKey.ToArray(), p.Votes })).ToArray(); + } + public IEnumerable<(ECPoint PublicKey, BigInteger Votes)> GetRegisteredValidators(Snapshot snapshot) { return snapshot.Storages.Find(new[] { Prefix_Validator }).Select(p => @@ -297,6 +208,12 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint[] pubkeys) )); } + [ContractMethod(1_00000000, ContractParameterType.Array, SafeMethod = true)] + private StackItem GetValidators(ApplicationEngine engine, VMArray args) + { + return GetValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); + } + public ECPoint[] GetValidators(Snapshot snapshot) { StorageItem storage_count = snapshot.Storages.TryGet(CreateStorageKey(Prefix_ValidatorsCount)); @@ -316,6 +233,12 @@ 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)] + private StackItem GetNextBlockValidators(ApplicationEngine engine, VMArray args) + { + return GetNextBlockValidators(engine.Snapshot).Select(p => (StackItem)p.ToArray()).ToList(); + } + public ECPoint[] GetNextBlockValidators(Snapshot snapshot) { StorageItem storage = snapshot.Storages.TryGet(CreateStorageKey(Prefix_NextValidators)); diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index 69f797f3d3..8234bbcac4 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -1,4 +1,6 @@ -using Neo.Ledger; +#pragma warning disable IDE0060 + +using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract.Manifest; using Neo.VM; @@ -21,75 +23,11 @@ public abstract class Nep5Token : NativeContract protected const byte Prefix_TotalSupply = 11; protected const byte Prefix_Account = 20; - protected Nep5Token() : base() + protected Nep5Token() { this.Factor = BigInteger.Pow(10, Decimals); - var methods = new List(Manifest.Abi.Methods) - { - new ContractMethodDescriptor() - { - Name = "name", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.String - }, - new ContractMethodDescriptor() - { - Name = "symbol", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.String - }, - new ContractMethodDescriptor() - { - Name = "decimals", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "totalSupply", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "balanceOf", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - } - }, - ReturnType = ContractParameterType.Integer - }, - new ContractMethodDescriptor() - { - Name = "transfer", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "from", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "to", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "amount", - Type = ContractParameterType.Integer - } - }, - ReturnType = ContractParameterType.Boolean - } - }; - - Manifest.Abi.Methods = methods.ToArray(); + Manifest.Features = ContractFeatures.HasStorage; var events = new List(Manifest.Abi.Events) { @@ -126,45 +64,6 @@ protected StorageKey CreateAccountKey(UInt160 account) return CreateStorageKey(Prefix_Account, account); } - protected override long GetPriceForMethod(string method) - { - switch (method) - { - case "name": - case "symbol": - case "decimals": - return 0; - case "totalSupply": - case "balanceOf": - return 0_01000000; - case "transfer": - return 0_08000000; - default: - return base.GetPriceForMethod(method); - } - } - - protected override StackItem Main(ApplicationEngine engine, string operation, VMArray args) - { - switch (operation) - { - case "name": - return Name; - case "symbol": - return Symbol; - case "decimals": - return (uint)Decimals; - case "totalSupply": - return TotalSupply(engine.Snapshot); - case "balanceOf": - return BalanceOf(engine.Snapshot, new UInt160(args[0].GetByteArray())); - case "transfer": - return Transfer(engine, new UInt160(args[0].GetByteArray()), new UInt160(args[1].GetByteArray()), args[2].GetBigInteger()); - default: - return base.Main(engine, operation, args); - } - } - internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); @@ -214,6 +113,30 @@ 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)] + protected StackItem NameMethod(ApplicationEngine engine, VMArray args) + { + return Name; + } + + [ContractMethod(0, ContractParameterType.String, Name = "symbol", SafeMethod = true)] + protected StackItem SymbolMethod(ApplicationEngine engine, VMArray args) + { + return Symbol; + } + + [ContractMethod(0, ContractParameterType.Integer, Name = "decimals", SafeMethod = true)] + protected StackItem DecimalsMethod(ApplicationEngine engine, VMArray args) + { + return (uint)Decimals; + } + + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + protected StackItem TotalSupply(ApplicationEngine engine, VMArray args) + { + return TotalSupply(engine.Snapshot); + } + public virtual BigInteger TotalSupply(Snapshot snapshot) { StorageItem storage = snapshot.Storages.TryGet(CreateStorageKey(Prefix_TotalSupply)); @@ -221,6 +144,12 @@ 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)] + protected StackItem BalanceOf(ApplicationEngine engine, VMArray args) + { + return BalanceOf(engine.Snapshot, new UInt160(args[0].GetByteArray())); + } + public virtual BigInteger BalanceOf(Snapshot snapshot, UInt160 account) { StorageItem storage = snapshot.Storages.TryGet(CreateAccountKey(account)); @@ -229,9 +158,17 @@ public virtual BigInteger BalanceOf(Snapshot snapshot, UInt160 account) return state.Balance; } + [ContractMethod(0_08000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "from", "to", "amount" }, AllowedTriggers = TriggerType.Application)] + protected StackItem Transfer(ApplicationEngine engine, VMArray args) + { + UInt160 from = new UInt160(args[0].GetByteArray()); + UInt160 to = new UInt160(args[1].GetByteArray()); + BigInteger amount = args[2].GetBigInteger(); + return Transfer(engine, from, to, amount); + } + protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount) { - if (engine.Trigger != TriggerType.Application) throw new InvalidOperationException(); if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (!from.Equals(engine.CallingScriptHash) && !InteropService.CheckWitness(engine, from)) return false; diff --git a/neo/SmartContract/TriggerType.cs b/neo/SmartContract/TriggerType.cs index e270f31683..c7da2c9f1e 100644 --- a/neo/SmartContract/TriggerType.cs +++ b/neo/SmartContract/TriggerType.cs @@ -1,8 +1,11 @@ +using System; + namespace Neo.SmartContract { + [Flags] public enum TriggerType : byte { - System = 0x00, + System = 0x01, /// /// The verification trigger indicates that the contract is being invoked as a verification function. /// The verification function can accept multiple parameters, and should return a boolean value that indicates the validity of the transaction or block. @@ -18,6 +21,8 @@ public enum TriggerType : byte /// public byte[] main(string operation, params object[] args) /// The functions can be invoked by creating an InvocationTransaction. /// - Application = 0x40 + Application = 0x40, + + All = System | Verification | Application } } From 2401a113cb51e52e4a3eb1136ad868fb07993e0e Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Sat, 25 May 2019 19:29:20 +0800 Subject: [PATCH 6/8] Only allow one `Witness` in `Transaction`. (#770) --- .../Nep5NativeContractExtensions.cs | 16 +- neo.UnitTests/TestUtils.cs | 12 +- neo.UnitTests/TestVerifiable.cs | 4 +- neo.UnitTests/UT_Block.cs | 15 +- neo.UnitTests/UT_Consensus.cs | 2 +- neo.UnitTests/UT_Header.cs | 8 +- neo.UnitTests/UT_MemoryPool.cs | 6 +- neo.UnitTests/UT_NeoToken.cs | 2 +- neo.UnitTests/UT_Policy.cs | 24 +-- neo.UnitTests/UT_PoolItem.cs | 11 +- neo.UnitTests/UT_Transaction.cs | 18 +- neo/Consensus/ConsensusContext.cs | 4 +- neo/Cryptography/Helper.cs | 3 +- neo/Ledger/Blockchain.cs | 9 +- neo/Network/P2P/Payloads/BlockBase.cs | 26 +-- neo/Network/P2P/Payloads/ConsensusPayload.cs | 21 +- neo/Network/P2P/Payloads/IVerifiable.cs | 4 +- neo/Network/P2P/Payloads/Transaction.cs | 28 +-- .../P2P/Payloads/TransactionAttribute.cs | 23 +-- .../P2P/Payloads/TransactionAttributeUsage.cs | 1 - .../ContractParametersContext.cs | 170 ++++++---------- neo/SmartContract/Helper.cs | 40 ++-- neo/SmartContract/InteropService.NEO.cs | 28 +-- neo/SmartContract/InteropService.cs | 3 +- neo/SmartContract/WitnessWrapper.cs | 27 --- neo/Wallets/Wallet.cs | 183 ++++++------------ 26 files changed, 257 insertions(+), 431 deletions(-) delete mode 100644 neo/SmartContract/WitnessWrapper.cs diff --git a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs index f8b7ba1f8d..5f80114dc9 100644 --- a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs +++ b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs @@ -15,22 +15,26 @@ public static class Nep5NativeContractExtensions { internal class ManualWitness : IVerifiable { - private readonly UInt160[] _hashForVerify; + private readonly UInt160 _hashForVerify; - public Witness[] Witnesses => throw new NotImplementedException(); + public Witness Witness + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } public int Size => 0; - public ManualWitness(UInt160[] hashForVerify) + public ManualWitness(UInt160 hashForVerify) { - _hashForVerify = hashForVerify ?? new UInt160[0]; + _hashForVerify = hashForVerify; } public void Deserialize(BinaryReader reader) { } public void DeserializeUnsigned(BinaryReader reader) { } - public UInt160[] GetScriptHashesForVerifying(Persistence.Snapshot snapshot) + public UInt160 GetScriptHashForVerification(Persistence.Snapshot snapshot) { return _hashForVerify; } @@ -43,7 +47,7 @@ public void SerializeUnsigned(BinaryWriter writer) { } public static bool Transfer(this NativeContract contract, Persistence.Snapshot snapshot, byte[] from, byte[] to, BigInteger amount, bool signFrom) { var engine = new ApplicationEngine(TriggerType.Application, - new ManualWitness(signFrom ? new[] { new UInt160(from) } : null), snapshot, 0, true); + new ManualWitness(signFrom ? new UInt160(from) : null), snapshot, 0, true); engine.LoadScript(contract.Script); diff --git a/neo.UnitTests/TestUtils.cs b/neo.UnitTests/TestUtils.cs index c5d21fccf2..8660328bf7 100644 --- a/neo.UnitTests/TestUtils.cs +++ b/neo.UnitTests/TestUtils.cs @@ -28,7 +28,11 @@ public static Transaction GetTransaction() Script = new byte[1], Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], - Witnesses = new Witness[0] + Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } }; } @@ -82,7 +86,11 @@ public static Transaction CreateRandomHashTransaction() Script = randomBytes, Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], - Witnesses = new Witness[0] + Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } }; } diff --git a/neo.UnitTests/TestVerifiable.cs b/neo.UnitTests/TestVerifiable.cs index c4641ce02c..78d3d314c0 100644 --- a/neo.UnitTests/TestVerifiable.cs +++ b/neo.UnitTests/TestVerifiable.cs @@ -9,7 +9,7 @@ public class TestVerifiable : IVerifiable { private string testStr = "testStr"; - public Witness[] Witnesses { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public Witness Witness { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int Size => throw new NotImplementedException(); @@ -23,7 +23,7 @@ public void DeserializeUnsigned(BinaryReader reader) throw new NotImplementedException(); } - public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) + public UInt160 GetScriptHashForVerification(Snapshot snapshot) { throw new NotImplementedException(); } diff --git a/neo.UnitTests/UT_Block.cs b/neo.UnitTests/UT_Block.cs index 64a60ba9e4..c0c1163d5e 100644 --- a/neo.UnitTests/UT_Block.cs +++ b/neo.UnitTests/UT_Block.cs @@ -53,9 +53,9 @@ public void Size_Get() Witness scriptVal; Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 1 + 3 + // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 3 // block 9 + 1 - uut.Size.Should().Be(110); + uut.Size.Should().Be(109); } [TestMethod] @@ -95,7 +95,7 @@ public void Size_Get_3_Transaction() TestUtils.GetTransaction() }; - uut.Size.Should().Be(257); + uut.Size.Should().Be(259); } [TestMethod] @@ -119,7 +119,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < data.Length; i++) @@ -141,7 +141,7 @@ public void Deserialize() uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { @@ -258,18 +258,17 @@ public void ToJson() jObj["index"].AsNumber().Should().Be(0); jObj["nextconsensus"].AsString().Should().Be("AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"); - JObject scObj = jObj["script"]; + JObject scObj = jObj["witness"]; scObj["invocation"].AsString().Should().Be(""); scObj["verification"].AsString().Should().Be("51"); jObj["tx"].Should().NotBeNull(); JArray txObj = (JArray)jObj["tx"]; txObj[0]["txid"].AsString().Should().Be("0x7647acf1ef8a841b87f2369398a0e9f0ccde0ed9e835d980657103da6da59580"); - txObj[0]["size"].AsNumber().Should().Be(49); + txObj[0]["size"].AsNumber().Should().Be(50); txObj[0]["version"].AsNumber().Should().Be(0); ((JArray)txObj[0]["attributes"]).Count.Should().Be(0); txObj[0]["net_fee"].AsString().Should().Be("0"); - ((JArray)txObj[0]["witnesses"]).Count.Should().Be(0); } } } diff --git a/neo.UnitTests/UT_Consensus.cs b/neo.UnitTests/UT_Consensus.cs index d410bb5142..4ea314238b 100644 --- a/neo.UnitTests/UT_Consensus.cs +++ b/neo.UnitTests/UT_Consensus.cs @@ -75,7 +75,7 @@ public void ConsensusService_Primary_Sends_PrepareRequest_After_OnStart() // Creating proposed block Header header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); - header.Size.Should().Be(101); + header.Size.Should().Be(100); Console.WriteLine($"header {header} hash {header.Hash} timstamp {timestampVal}"); diff --git a/neo.UnitTests/UT_Header.cs b/neo.UnitTests/UT_Header.cs index ebbf7038a9..edcde41674 100644 --- a/neo.UnitTests/UT_Header.cs +++ b/neo.UnitTests/UT_Header.cs @@ -26,9 +26,9 @@ public void Size_Get() uint timestampVal, indexVal; Witness scriptVal; TestUtils.SetupHeaderWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 1 + 3 + // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 3 // header 1 - uut.Size.Should().Be(101); + uut.Size.Should().Be(100); } [TestMethod] @@ -43,7 +43,7 @@ public void Deserialize() uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 0 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { @@ -122,7 +122,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; + byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 0 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < data.Length; i++) diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/UT_MemoryPool.cs index cfb74a4f18..71a4e84f09 100644 --- a/neo.UnitTests/UT_MemoryPool.cs +++ b/neo.UnitTests/UT_MemoryPool.cs @@ -55,7 +55,11 @@ private Transaction CreateTransactionWithFee(long fee) mock.Object.Sender = UInt160.Zero; mock.Object.NetworkFee = fee; mock.Object.Attributes = new TransactionAttribute[0]; - mock.Object.Witnesses = new Witness[0]; + mock.Object.Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + }; return mock.Object; } diff --git a/neo.UnitTests/UT_NeoToken.cs b/neo.UnitTests/UT_NeoToken.cs index c816226cf1..02f63c086e 100644 --- a/neo.UnitTests/UT_NeoToken.cs +++ b/neo.UnitTests/UT_NeoToken.cs @@ -237,7 +237,7 @@ public void Check_BadScript() internal static (bool State, bool Result) Check_Vote(Snapshot snapshot, byte[] account, byte[][] pubkeys, bool signAccount) { var engine = new ApplicationEngine(TriggerType.Application, - new Nep5NativeContractExtensions.ManualWitness(signAccount ? new[] { new UInt160(account) } : null), snapshot, 0, true); + new Nep5NativeContractExtensions.ManualWitness(signAccount ? new UInt160(account) : UInt160.Zero), snapshot, 0, true); engine.LoadScript(NativeContract.NEO.Script); diff --git a/neo.UnitTests/UT_Policy.cs b/neo.UnitTests/UT_Policy.cs index d169239072..f0893a0a88 100644 --- a/neo.UnitTests/UT_Policy.cs +++ b/neo.UnitTests/UT_Policy.cs @@ -69,7 +69,7 @@ public void Check_SetMaxTransactionsPerBlock() // Without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "setMaxTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -80,7 +80,7 @@ public void Check_SetMaxTransactionsPerBlock() // With signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "setMaxTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); @@ -104,7 +104,7 @@ public void Check_SetMaxLowPriorityTransactionsPerBlock() // Without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "setMaxLowPriorityTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -115,7 +115,7 @@ public void Check_SetMaxLowPriorityTransactionsPerBlock() // With signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "setMaxLowPriorityTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); @@ -139,7 +139,7 @@ public void Check_SetMaxLowPriorityTransactionSize() // Without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "setMaxLowPriorityTransactionSize", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -150,7 +150,7 @@ public void Check_SetMaxLowPriorityTransactionSize() // With signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "setMaxLowPriorityTransactionSize", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); @@ -174,7 +174,7 @@ public void Check_SetFeePerByte() // Without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "setFeePerByte", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -185,7 +185,7 @@ public void Check_SetFeePerByte() // With signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "setFeePerByte", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); @@ -209,7 +209,7 @@ public void Check_Block_UnblockAccount() // Block without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "blockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -220,7 +220,7 @@ public void Check_Block_UnblockAccount() // Block with signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "blockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); @@ -232,7 +232,7 @@ public void Check_Block_UnblockAccount() // Unblock without signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), "unblockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -244,7 +244,7 @@ public void Check_Block_UnblockAccount() // Unblock with signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(new UInt160[] { UInt160.Zero }), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), "unblockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeTrue(); diff --git a/neo.UnitTests/UT_PoolItem.cs b/neo.UnitTests/UT_PoolItem.cs index 9ae4f95d89..9b240dbe0c 100644 --- a/neo.UnitTests/UT_PoolItem.cs +++ b/neo.UnitTests/UT_PoolItem.cs @@ -122,20 +122,17 @@ public static Transaction GenerateTx(long networkFee, int size, byte[] overrideS Sender = UInt160.Zero, NetworkFee = networkFee, Attributes = new TransactionAttribute[0], - Witnesses = new[] + Witness = new Witness { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new byte[0] - } + InvocationScript = new byte[0], + VerificationScript = new byte[0] } }; int diff = size - tx.Size; if (diff < 0) throw new ArgumentException(); if (diff > 0) - tx.Witnesses[0].VerificationScript = new byte[diff]; + tx.Witness.VerificationScript = new byte[diff]; return tx; } } diff --git a/neo.UnitTests/UT_Transaction.cs b/neo.UnitTests/UT_Transaction.cs index 56961f7a32..d68f288270 100644 --- a/neo.UnitTests/UT_Transaction.cs +++ b/neo.UnitTests/UT_Transaction.cs @@ -55,12 +55,16 @@ public void Size_Get() uut.Script = TestUtils.GetByteArray(32, 0x42); uut.Sender = UInt160.Zero; uut.Attributes = new TransactionAttribute[0]; - uut.Witnesses = new Witness[0]; + uut.Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + }; uut.Version.Should().Be(0); uut.Script.Length.Should().Be(32); uut.Script.GetVarSize().Should().Be(33); - uut.Size.Should().Be(80); + uut.Size.Should().Be(81); } [TestMethod] @@ -70,17 +74,19 @@ public void ToJson() uut.Sender = UInt160.Zero; uut.Gas = 4200000000; uut.Attributes = new TransactionAttribute[0]; - uut.Witnesses = new Witness[0]; + uut.Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + }; JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); jObj["txid"].AsString().Should().Be("0x38274692538dfecaae36f8fd518d92bae25607d491c40a8f927cc06bd97ab2c8"); - jObj["size"].AsNumber().Should().Be(80); + jObj["size"].AsNumber().Should().Be(81); jObj["version"].AsNumber().Should().Be(0); ((JArray)jObj["attributes"]).Count.Should().Be(0); jObj["net_fee"].AsString().Should().Be("0"); - ((JArray)jObj["witnesses"]).Count.Should().Be(0); - jObj["script"].AsString().Should().Be("4220202020202020202020202020202020202020202020202020202020202020"); jObj["gas"].AsNumber().Should().Be(42); } diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index 5fa53ce567..3affe821f5 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -85,7 +85,7 @@ public Block CreateBlock() sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage().Signature); j++; } - Block.Witness = sc.GetWitnesses()[0]; + Block.Witness = sc.GetWitness(); Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); return Block; } @@ -202,7 +202,7 @@ private void SignPayload(ConsensusPayload payload) { return; } - payload.Witness = sc.GetWitnesses()[0]; + payload.Witness = sc.GetWitness(); } public ConsensusPayload MakePrepareRequest() diff --git a/neo/Cryptography/Helper.cs b/neo/Cryptography/Helper.cs index 48410049f1..f46aad0121 100644 --- a/neo/Cryptography/Helper.cs +++ b/neo/Cryptography/Helper.cs @@ -117,8 +117,7 @@ public static byte[] Sha256(this byte[] value, int offset, int count) internal static bool Test(this BloomFilter filter, Transaction tx) { if (filter.Check(tx.Hash.ToArray())) return true; - if (tx.Witnesses.Any(p => filter.Check(p.ScriptHash.ToArray()))) - return true; + if (filter.Check(tx.Sender.ToArray())) return true; return false; } diff --git a/neo/Ledger/Blockchain.cs b/neo/Ledger/Blockchain.cs index 9a049508a9..d00bde3c39 100644 --- a/neo/Ledger/Blockchain.cs +++ b/neo/Ledger/Blockchain.cs @@ -154,13 +154,10 @@ private static Transaction DeployNativeContracts() Sender = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(), Gas = 0, Attributes = new TransactionAttribute[0], - Witnesses = new[] + Witness = new Witness { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } - } + InvocationScript = new byte[0], + VerificationScript = new[] { (byte)OpCode.PUSHT } } }; } diff --git a/neo/Network/P2P/Payloads/BlockBase.cs b/neo/Network/P2P/Payloads/BlockBase.cs index 8937325509..769b457e9e 100644 --- a/neo/Network/P2P/Payloads/BlockBase.cs +++ b/neo/Network/P2P/Payloads/BlockBase.cs @@ -17,7 +17,7 @@ public abstract class BlockBase : IVerifiable public uint Timestamp; public uint Index; public UInt160 NextConsensus; - public Witness Witness; + public Witness Witness { get; set; } private UInt256 _hash = null; public UInt256 Hash @@ -32,20 +32,11 @@ public UInt256 Hash } } - Witness[] IVerifiable.Witnesses - { - get - { - return new[] { Witness }; - } - } - - public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + NextConsensus.Size + 1 + Witness.Size; + public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + NextConsensus.Size + Witness.Size; public virtual void Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); - if (reader.ReadByte() != 1) throw new FormatException(); Witness = reader.ReadSerializable(); } @@ -59,19 +50,18 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) NextConsensus = reader.ReadSerializable(); } - UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) + UInt160 IVerifiable.GetScriptHashForVerification(Snapshot snapshot) { - if (PrevHash == UInt256.Zero) - return new[] { Witness.ScriptHash }; + if (PrevHash == UInt256.Zero) return Witness.ScriptHash; Header prev_header = snapshot.GetHeader(PrevHash); if (prev_header == null) throw new InvalidOperationException(); - return new UInt160[] { prev_header.NextConsensus }; + return prev_header.NextConsensus; } public virtual void Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write((byte)1); writer.Write(Witness); + writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -95,7 +85,7 @@ public virtual JObject ToJson() json["time"] = Timestamp; json["index"] = Index; json["nextconsensus"] = NextConsensus.ToAddress(); - json["script"] = Witness.ToJson(); + json["witness"] = Witness.ToJson(); return json; } @@ -105,7 +95,7 @@ public virtual bool Verify(Snapshot snapshot) if (prev_header == null) return false; if (prev_header.Index + 1 != Index) return false; if (prev_header.Timestamp >= Timestamp) return false; - if (!this.VerifyWitnesses(snapshot, 1_00000000)) return false; + if (!this.VerifyWitness(snapshot, 1_00000000)) return false; return true; } } diff --git a/neo/Network/P2P/Payloads/ConsensusPayload.cs b/neo/Network/P2P/Payloads/ConsensusPayload.cs index fbe242ae67..cdbf459418 100644 --- a/neo/Network/P2P/Payloads/ConsensusPayload.cs +++ b/neo/Network/P2P/Payloads/ConsensusPayload.cs @@ -17,7 +17,7 @@ public class ConsensusPayload : IInventory public uint BlockIndex; public ushort ValidatorIndex; public byte[] Data; - public Witness Witness; + public Witness Witness { get; set; } private ConsensusMessage _deserializedMessage = null; public ConsensusMessage ConsensusMessage @@ -53,14 +53,6 @@ public UInt256 Hash InventoryType IInventory.InventoryType => InventoryType.Consensus; - Witness[] IVerifiable.Witnesses - { - get - { - return new[] { Witness }; - } - } - public int Size => sizeof(uint) + //Version PrevHash.Size + //PrevHash @@ -68,7 +60,7 @@ Witness[] IVerifiable.Witnesses sizeof(ushort) + //ValidatorIndex sizeof(uint) + //Timestamp Data.GetVarSize() + //Data - 1 + Witness.Size; //Witness + Witness.Size; //Witness public T GetDeserializedMessage() where T : ConsensusMessage { @@ -78,7 +70,6 @@ public T GetDeserializedMessage() where T : ConsensusMessage void ISerializable.Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); - if (reader.ReadByte() != 1) throw new FormatException(); Witness = reader.ReadSerializable(); } @@ -91,18 +82,18 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) Data = reader.ReadVarBytes(); } - UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) + UInt160 IVerifiable.GetScriptHashForVerification(Snapshot snapshot) { ECPoint[] validators = NativeContract.NEO.GetNextBlockValidators(snapshot); if (validators.Length <= ValidatorIndex) throw new InvalidOperationException(); - return new[] { Contract.CreateSignatureRedeemScript(validators[ValidatorIndex]).ToScriptHash() }; + return Contract.CreateSignatureRedeemScript(validators[ValidatorIndex]).ToScriptHash(); } void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write((byte)1); writer.Write(Witness); + writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -118,7 +109,7 @@ public bool Verify(Snapshot snapshot) { if (BlockIndex <= snapshot.Height) return false; - return this.VerifyWitnesses(snapshot, 0_02000000); + return this.VerifyWitness(snapshot, 0_02000000); } } } diff --git a/neo/Network/P2P/Payloads/IVerifiable.cs b/neo/Network/P2P/Payloads/IVerifiable.cs index 26ce90574c..87a7dcb701 100644 --- a/neo/Network/P2P/Payloads/IVerifiable.cs +++ b/neo/Network/P2P/Payloads/IVerifiable.cs @@ -6,11 +6,11 @@ namespace Neo.Network.P2P.Payloads { public interface IVerifiable : ISerializable { - Witness[] Witnesses { get; } + Witness Witness { get; set; } void DeserializeUnsigned(BinaryReader reader); - UInt160[] GetScriptHashesForVerifying(Snapshot snapshot); + UInt160 GetScriptHashForVerification(Snapshot snapshot); void SerializeUnsigned(BinaryWriter writer); } diff --git a/neo/Network/P2P/Payloads/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs index fec88aafcf..52a6fc867a 100644 --- a/neo/Network/P2P/Payloads/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -33,7 +33,7 @@ public class Transaction : IEquatable, IInventory public long NetworkFee; public uint ValidUntilBlock; public TransactionAttribute[] Attributes; - public Witness[] Witnesses { get; set; } + public Witness Witness { get; set; } /// /// The NetworkFee for the transaction divided by its Size. @@ -65,13 +65,17 @@ public UInt256 Hash sizeof(long) + //NetworkFee sizeof(uint) + //ValidUntilBlock Attributes.GetVarSize() + //Attributes - Witnesses.GetVarSize(); //Witnesses + Witness.Size; //Witnesses public void CalculateFees() { if (Sender is null) Sender = UInt160.Zero; if (Attributes is null) Attributes = new TransactionAttribute[0]; - if (Witnesses is null) Witnesses = new Witness[0]; + if (Witness is null) Witness = new Witness + { + InvocationScript = new byte[65], + VerificationScript = new byte[39] + }; _hash = null; long consumed; using (ApplicationEngine engine = ApplicationEngine.Run(Script, this)) @@ -108,7 +112,7 @@ public void CalculateFees() void ISerializable.Deserialize(BinaryReader reader) { DeserializeUnsigned(reader); - Witnesses = reader.ReadSerializableArray(); + Witness = reader.ReadSerializable(); } public void DeserializeUnsigned(BinaryReader reader) @@ -127,8 +131,6 @@ public void DeserializeUnsigned(BinaryReader reader) if (Gas + NetworkFee < Gas) throw new FormatException(); ValidUntilBlock = reader.ReadUInt32(); Attributes = reader.ReadSerializableArray(MaxTransactionAttributes); - var cosigners = GetScriptHashesForVerifying(null); - if (cosigners.Distinct().Count() != cosigners.Length) throw new FormatException(); } public bool Equals(Transaction other) @@ -148,17 +150,15 @@ public override int GetHashCode() return Hash.GetHashCode(); } - public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) + public UInt160 GetScriptHashForVerification(Snapshot snapshot) { - HashSet hashes = new HashSet { Sender }; - hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Cosigner).Select(p => new UInt160(p.Data))); - return hashes.OrderBy(p => p).ToArray(); + return Sender; } void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(Witnesses); + writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -186,7 +186,7 @@ public JObject ToJson() json["net_fee"] = new BigDecimal(NetworkFee, (byte)NativeContract.GAS.Decimals).ToString(); json["valid_until_block"] = ValidUntilBlock; json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray(); - json["witnesses"] = Witnesses.Select(p => p.ToJson()).ToArray(); + json["witness"] = Witness.ToJson(); return json; } @@ -203,14 +203,14 @@ public virtual bool Verify(Snapshot snapshot, IEnumerable mempool) if (size > MaxTransactionSize) return false; if (size > NativeContract.Policy.GetMaxLowPriorityTransactionSize(snapshot) && NetworkFee / size < NativeContract.Policy.GetFeePerByte(snapshot)) return false; - if (NativeContract.Policy.GetBlockedAccounts(snapshot).Intersect(GetScriptHashesForVerifying(snapshot)).Count() > 0) + if (NativeContract.Policy.GetBlockedAccounts(snapshot).Contains(Sender)) return false; BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, Sender); BigInteger fee = Gas + NetworkFee; if (balance < fee) return false; fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Sum(p => p.Gas + p.NetworkFee); if (balance < fee) return false; - return this.VerifyWitnesses(snapshot, VerificationGasLimited); + return this.VerifyWitness(snapshot, VerificationGasLimited); } } } diff --git a/neo/Network/P2P/Payloads/TransactionAttribute.cs b/neo/Network/P2P/Payloads/TransactionAttribute.cs index a66f8ae5f0..2f384fa046 100644 --- a/neo/Network/P2P/Payloads/TransactionAttribute.cs +++ b/neo/Network/P2P/Payloads/TransactionAttribute.cs @@ -10,35 +10,20 @@ public class TransactionAttribute : ISerializable public TransactionAttributeUsage Usage; public byte[] Data; - public int Size - { - get - { - if (Usage == TransactionAttributeUsage.Cosigner) - return sizeof(TransactionAttributeUsage) + 20; - else - return sizeof(TransactionAttributeUsage) + Data.GetVarSize(); - } - } + public int Size => sizeof(TransactionAttributeUsage) + Data.GetVarSize(); void ISerializable.Deserialize(BinaryReader reader) { Usage = (TransactionAttributeUsage)reader.ReadByte(); - if (Usage == TransactionAttributeUsage.Cosigner) - Data = reader.ReadBytes(20); - else if (Usage == TransactionAttributeUsage.Url) - Data = reader.ReadVarBytes(252); - else + if (!Enum.IsDefined(typeof(TransactionAttributeUsage), Usage)) throw new FormatException(); + Data = reader.ReadVarBytes(252); } void ISerializable.Serialize(BinaryWriter writer) { writer.Write((byte)Usage); - if (Usage == TransactionAttributeUsage.Cosigner) - writer.Write(Data); - else - writer.WriteVarBytes(Data); + writer.WriteVarBytes(Data); } public JObject ToJson() diff --git a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs index d878348c28..89c1a1cec2 100644 --- a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs +++ b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs @@ -2,7 +2,6 @@ { public enum TransactionAttributeUsage : byte { - Cosigner = 0x20, Url = 0x81 } } diff --git a/neo/SmartContract/ContractParametersContext.cs b/neo/SmartContract/ContractParametersContext.cs index a22ea5c673..8af153119d 100644 --- a/neo/SmartContract/ContractParametersContext.cs +++ b/neo/SmartContract/ContractParametersContext.cs @@ -15,88 +15,48 @@ namespace Neo.SmartContract { public class ContractParametersContext { - private class ContextItem - { - public byte[] Script; - public ContractParameter[] Parameters; - public Dictionary Signatures; - - private ContextItem() { } - - public ContextItem(Contract contract) - { - this.Script = contract.Script; - this.Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); - } - - public static ContextItem FromJson(JObject json) - { - return new ContextItem - { - Script = json["script"]?.AsString().HexToBytes(), - Parameters = ((JArray)json["parameters"]).Select(p => ContractParameter.FromJson(p)).ToArray(), - Signatures = json["signatures"]?.Properties.Select(p => new - { - PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), - Signature = p.Value.AsString().HexToBytes() - }).ToDictionary(p => p.PublicKey, p => p.Signature) - }; - } - - public JObject ToJson() - { - JObject json = new JObject(); - if (Script != null) - json["script"] = Script.ToHexString(); - json["parameters"] = new JArray(Parameters.Select(p => p.ToJson())); - if (Signatures != null) - { - json["signatures"] = new JObject(); - foreach (var signature in Signatures) - json["signatures"][signature.Key.ToString()] = signature.Value.ToHexString(); - } - return json; - } - } - public readonly IVerifiable Verifiable; - private readonly Dictionary ContextItems; + private byte[] Script; + private ContractParameter[] Parameters; + private Dictionary Signatures; public bool Completed { get { - if (ContextItems.Count < ScriptHashes.Count) - return false; - return ContextItems.Values.All(p => p != null && p.Parameters.All(q => q.Value != null)); + if (Parameters is null) return false; + return Parameters.All(p => p.Value != null); } } - private UInt160[] _ScriptHashes = null; - public IReadOnlyList ScriptHashes + private UInt160 _ScriptHash = null; + public UInt160 ScriptHash { get { - if (_ScriptHashes == null) + if (_ScriptHash == null) using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - _ScriptHashes = Verifiable.GetScriptHashesForVerifying(snapshot); + _ScriptHash = Verifiable.GetScriptHashForVerification(snapshot); } - return _ScriptHashes; + return _ScriptHash; } } public ContractParametersContext(IVerifiable verifiable) { this.Verifiable = verifiable; - this.ContextItems = new Dictionary(); } public bool Add(Contract contract, int index, object parameter) { - ContextItem item = CreateItem(contract); - if (item == null) return false; - item.Parameters[index].Value = parameter; + if (!ScriptHash.Equals(contract.ScriptHash)) return false; + if (Parameters is null) + { + Script = contract.Script; + Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); + } + Parameters[index].Value = parameter; return true; } @@ -104,12 +64,16 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) { if (contract.Script.IsMultiSigContract()) { - ContextItem item = CreateItem(contract); - if (item == null) return false; - if (item.Parameters.All(p => p.Value != null)) return false; - if (item.Signatures == null) - item.Signatures = new Dictionary(); - else if (item.Signatures.ContainsKey(pubkey)) + if (!ScriptHash.Equals(contract.ScriptHash)) return false; + if (Parameters is null) + { + Script = contract.Script; + Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); + } + if (Parameters.All(p => p.Value != null)) return false; + if (Signatures == null) + Signatures = new Dictionary(); + else if (Signatures.ContainsKey(pubkey)) return false; List points = new List(); { @@ -130,15 +94,15 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) } } if (!points.Contains(pubkey)) return false; - item.Signatures.Add(pubkey, signature); - if (item.Signatures.Count == contract.ParameterList.Length) + Signatures.Add(pubkey, signature); + if (Signatures.Count == contract.ParameterList.Length) { Dictionary dic = points.Select((p, i) => new { PublicKey = p, Index = i }).ToDictionary(p => p.PublicKey, p => p.Index); - byte[][] sigs = item.Signatures.Select(p => new + byte[][] sigs = Signatures.Select(p => new { Signature = p.Value, Index = dic[p.Key] @@ -146,7 +110,7 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) for (int i = 0; i < sigs.Length; i++) if (!Add(contract, i, sigs[i])) throw new InvalidOperationException(); - item.Signatures = null; + Signatures = null; } return true; } @@ -170,17 +134,6 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) } } - private ContextItem CreateItem(Contract contract) - { - if (ContextItems.TryGetValue(contract.ScriptHash, out ContextItem item)) - return item; - if (!ScriptHashes.Contains(contract.ScriptHash)) - return null; - item = new ContextItem(contract); - ContextItems.Add(contract.ScriptHash, item); - return item; - } - public static ContractParametersContext FromJson(JObject json) { IVerifiable verifiable = typeof(ContractParametersContext).GetTypeInfo().Assembly.CreateInstance(json["type"].AsString()) as IVerifiable; @@ -190,47 +143,43 @@ public static ContractParametersContext FromJson(JObject json) { verifiable.DeserializeUnsigned(reader); } - ContractParametersContext context = new ContractParametersContext(verifiable); - foreach (var property in json["items"].Properties) + return new ContractParametersContext(verifiable) { - context.ContextItems.Add(UInt160.Parse(property.Key), ContextItem.FromJson(property.Value)); - } - return context; + Script = json["script"]?.AsString().HexToBytes(), + Parameters = ((JArray)json["parameters"])?.Select(p => ContractParameter.FromJson(p)).ToArray(), + Signatures = json["signatures"]?.Properties.Select(p => new + { + PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), + Signature = p.Value.AsString().HexToBytes() + }).ToDictionary(p => p.PublicKey, p => p.Signature) + }; } - public ContractParameter GetParameter(UInt160 scriptHash, int index) + public ContractParameter GetParameter(int index) { - return GetParameters(scriptHash)?[index]; + return GetParameters()?[index]; } - public IReadOnlyList GetParameters(UInt160 scriptHash) + public IReadOnlyList GetParameters() { - if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) - return null; - return item.Parameters; + return Parameters; } - public Witness[] GetWitnesses() + public Witness GetWitness() { if (!Completed) throw new InvalidOperationException(); - Witness[] witnesses = new Witness[ScriptHashes.Count]; - for (int i = 0; i < ScriptHashes.Count; i++) + using (ScriptBuilder sb = new ScriptBuilder()) { - ContextItem item = ContextItems[ScriptHashes[i]]; - using (ScriptBuilder sb = new ScriptBuilder()) + foreach (ContractParameter parameter in Parameters.Reverse()) { - foreach (ContractParameter parameter in item.Parameters.Reverse()) - { - sb.EmitPush(parameter); - } - witnesses[i] = new Witness - { - InvocationScript = sb.ToArray(), - VerificationScript = item.Script ?? new byte[0] - }; + sb.EmitPush(parameter); } + return new Witness + { + InvocationScript = sb.ToArray(), + VerificationScript = Script ?? new byte[0] + }; } - return witnesses; } public static ContractParametersContext Parse(string value) @@ -249,9 +198,16 @@ public JObject ToJson() writer.Flush(); json["hex"] = ms.ToArray().ToHexString(); } - json["items"] = new JObject(); - foreach (var item in ContextItems) - json["items"][item.Key.ToString()] = item.Value.ToJson(); + if (Script != null) + json["script"] = Script.ToHexString(); + if (Parameters != null) + json["parameters"] = new JArray(Parameters.Select(p => p.ToJson())); + if (Signatures != null) + { + json["signatures"] = new JObject(); + foreach (var signature in Signatures) + json["signatures"][signature.Key.ToString()] = signature.Value.ToHexString(); + } return json; } diff --git a/neo/SmartContract/Helper.cs b/neo/SmartContract/Helper.cs index efab36ced3..ddd16de13e 100644 --- a/neo/SmartContract/Helper.cs +++ b/neo/SmartContract/Helper.cs @@ -246,37 +246,33 @@ public static UInt160 ToScriptHash(this byte[] script) return new UInt160(Crypto.Default.Hash160(script)); } - internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snapshot, long gas) + internal static bool VerifyWitness(this IVerifiable verifiable, Snapshot snapshot, long gas) { - UInt160[] hashes; + UInt160 hash; try { - hashes = verifiable.GetScriptHashesForVerifying(snapshot); + hash = verifiable.GetScriptHashForVerification(snapshot); } catch (InvalidOperationException) { return false; } - if (hashes.Length != verifiable.Witnesses.Length) return false; - for (int i = 0; i < hashes.Length; i++) + byte[] verification = verifiable.Witness.VerificationScript; + if (verification.Length == 0) { - byte[] verification = verifiable.Witnesses[i].VerificationScript; - if (verification.Length == 0) - { - verification = snapshot.Contracts.TryGet(hashes[i])?.Script; - if (verification is null) return false; - } - else - { - if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false; - } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) - { - engine.LoadScript(verification); - engine.LoadScript(verifiable.Witnesses[i].InvocationScript); - if (engine.Execute().HasFlag(VMState.FAULT)) return false; - if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; - } + verification = snapshot.Contracts.TryGet(hash)?.Script; + if (verification is null) return false; + } + else + { + if (hash != verifiable.Witness.ScriptHash) return false; + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) + { + engine.LoadScript(verification); + engine.LoadScript(verifiable.Witness.InvocationScript); + if (engine.Execute().HasFlag(VMState.FAULT)) return false; + if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; } return true; } diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 685573e25a..d8ecab8cb6 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -23,9 +23,8 @@ static partial class InteropService public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400); public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 0_00000400); public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 0_00000400); - public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000); public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 0_00000400); - public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400); + public static readonly uint Neo_Transaction_GetWitnessScript = Register("Neo.Transaction.GetWitnessScript", Transaction_GetWitnessScript, 0_00000400); public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000); public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice); public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice); @@ -192,20 +191,6 @@ private static bool Header_GetNextConsensus(ApplicationEngine engine) return false; } - private static bool Transaction_GetWitnesses(ApplicationEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - Transaction tx = _interface.GetInterface(); - if (tx == null) return false; - if (tx.Witnesses.Length > engine.MaxArraySize) - return false; - engine.CurrentContext.EvaluationStack.Push(WitnessWrapper.Create(tx, engine.Snapshot).Select(p => StackItem.FromInterface(p)).ToArray()); - return true; - } - return false; - } - private static bool Transaction_GetScript(ApplicationEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) @@ -218,13 +203,16 @@ private static bool Transaction_GetScript(ApplicationEngine engine) return false; } - private static bool Witness_GetVerificationScript(ApplicationEngine engine) + private static bool Transaction_GetWitnessScript(ApplicationEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - WitnessWrapper witness = _interface.GetInterface(); - if (witness == null) return false; - engine.CurrentContext.EvaluationStack.Push(witness.VerificationScript); + Transaction tx = _interface.GetInterface(); + if (tx == null) return false; + byte[] script = tx.Witness.VerificationScript; + if (script.Length == 0) + script = engine.Snapshot.Contracts[tx.Sender].Script; + engine.CurrentContext.EvaluationStack.Push(script); return true; } return false; diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index 9019affe33..b110fe7796 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -146,8 +146,7 @@ private static bool Runtime_GetTrigger(ApplicationEngine engine) internal static bool CheckWitness(ApplicationEngine engine, UInt160 hash) { - UInt160[] _hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); - return _hashes_for_verifying.Contains(hash); + return hash.Equals(engine.ScriptContainer.GetScriptHashForVerification(engine.Snapshot)); } private static bool CheckWitness(ApplicationEngine engine, ECPoint pubkey) diff --git a/neo/SmartContract/WitnessWrapper.cs b/neo/SmartContract/WitnessWrapper.cs deleted file mode 100644 index f67690cad5..0000000000 --- a/neo/SmartContract/WitnessWrapper.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using System.Linq; - -namespace Neo.SmartContract -{ - internal class WitnessWrapper - { - public byte[] VerificationScript; - - public static WitnessWrapper[] Create(IVerifiable verifiable, Snapshot snapshot) - { - WitnessWrapper[] wrappers = verifiable.Witnesses.Select(p => new WitnessWrapper - { - VerificationScript = p.VerificationScript - }).ToArray(); - if (wrappers.Any(p => p.VerificationScript.Length == 0)) - { - UInt160[] hashes = verifiable.GetScriptHashesForVerifying(snapshot); - for (int i = 0; i < wrappers.Length; i++) - if (wrappers[i].VerificationScript.Length == 0) - wrappers[i].VerificationScript = snapshot.Contracts[hashes[i]].Script; - } - return wrappers; - } - } -} diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index 212b6a3cb4..a7578651d0 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -72,64 +72,6 @@ public void FillTransaction(Transaction tx, UInt160 sender = null) throw new InvalidOperationException(); } - private List<(UInt160 Account, BigInteger Value)> FindPayingAccounts(List<(UInt160 Account, BigInteger Value)> orderedAccounts, BigInteger amount) - { - var result = new List<(UInt160 Account, BigInteger Value)>(); - BigInteger sum_balance = orderedAccounts.Select(p => p.Value).Sum(); - if (sum_balance == amount) - { - result.AddRange(orderedAccounts); - orderedAccounts.Clear(); - } - else - { - for (int i = 0; i < orderedAccounts.Count; i++) - { - if (orderedAccounts[i].Value < amount) - continue; - if (orderedAccounts[i].Value == amount) - { - result.Add(orderedAccounts[i]); - orderedAccounts.RemoveAt(i); - } - else - { - result.Add((orderedAccounts[i].Account, amount)); - orderedAccounts[i] = (orderedAccounts[i].Account, orderedAccounts[i].Value - amount); - } - break; - } - if (result.Count == 0) - { - int i = orderedAccounts.Count - 1; - while (orderedAccounts[i].Value <= amount) - { - result.Add(orderedAccounts[i]); - amount -= orderedAccounts[i].Value; - orderedAccounts.RemoveAt(i); - i--; - } - for (i = 0; i < orderedAccounts.Count; i++) - { - if (orderedAccounts[i].Value < amount) - continue; - if (orderedAccounts[i].Value == amount) - { - result.Add(orderedAccounts[i]); - orderedAccounts.RemoveAt(i); - } - else - { - result.Add((orderedAccounts[i].Account, amount)); - orderedAccounts[i] = (orderedAccounts[i].Account, orderedAccounts[i].Value - amount); - } - break; - } - } - } - return result; - } - public WalletAccount GetAccount(ECPoint pubkey) { return GetAccount(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); @@ -226,55 +168,68 @@ public virtual WalletAccount Import(string nep2, string passphrase) return account; } - public Transaction MakeTransaction(List attributes, IEnumerable outputs, UInt160 from = null) + public Transaction MakeTransaction(IEnumerable attributes, TransferOutput[] outputs, UInt160 from = null) { - if (attributes == null) attributes = new List(); - var output_groups = outputs.GroupBy(p => p.AssetId); - UInt160[] accounts = from is null ? GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray() : new[] { from }; - HashSet sAttributes = new HashSet(); - byte[] script; - List<(UInt160 Account, BigInteger Value)> balances_gas = null; - using (ScriptBuilder sb = new ScriptBuilder()) + uint nonce = (uint)rand.Next(); + var totalPay = outputs.GroupBy(p => p.AssetId, (k, g) => (k, g.Select(p => p.Value.Value).Sum())).ToArray(); + UInt160[] accounts; + if (from is null) + { + accounts = GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray(); + } + else { - foreach (var group in output_groups) + if (!Contains(from)) return null; + accounts = new[] { from }; + } + TransactionAttribute[] attr = attributes?.ToArray() ?? new TransactionAttribute[0]; + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + { + uint validUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement; + foreach (UInt160 account in accounts) { - BigInteger sum_output = group.Select(p => p.Value.Value).Sum(); - var balances = new List<(UInt160 Account, BigInteger Value)>(); - foreach (UInt160 account in accounts) - using (ScriptBuilder sb2 = new ScriptBuilder()) - { - sb2.EmitAppCall(group.Key, "balanceOf", account); - ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray()); - if (engine.State.HasFlag(VMState.FAULT)) return null; - balances.Add((account, engine.ResultStack.Pop().GetBigInteger())); - } - BigInteger sum_balance = balances.Select(p => p.Value).Sum(); - if (sum_balance < sum_output) return null; - foreach (var output in group) + Transaction tx = MakeTransaction(snapshot, 0, nonce, totalPay, outputs, account, validUntilBlock, attr); + if (tx != null) return tx; + } + } + return null; + } + + private Transaction MakeTransaction(Snapshot snapshot, byte version, uint nonce, (UInt160, BigInteger)[] totalPay, TransferOutput[] outputs, UInt160 sender, uint validUntilBlock, TransactionAttribute[] attributes) + { + BigInteger balance_gas = BigInteger.Zero; + foreach (var (assetId, amount) in totalPay) + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitAppCall(assetId, "balanceOf", sender); + ApplicationEngine engine = ApplicationEngine.Run(sb.ToArray()); + if (engine.State.HasFlag(VMState.FAULT)) return null; + BigInteger balance = engine.ResultStack.Peek().GetBigInteger(); + if (balance < amount) return null; + if (assetId.Equals(NativeContract.GAS.Hash)) { - balances = balances.OrderBy(p => p.Value).ToList(); - var balances_used = FindPayingAccounts(balances, output.Value.Value); - sAttributes.UnionWith(balances_used.Select(p => p.Account)); - foreach (var (account, value) in balances_used) - { - sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); - sb.Emit(OpCode.THROWIFNOT); - } + balance_gas = balance - amount; + if (balance_gas.Sign <= 0) return null; } - if (group.Key.Equals(NativeContract.GAS.Hash)) - balances_gas = balances; + } + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + foreach (var output in outputs) + { + sb.EmitAppCall(output.AssetId, "transfer", sender, output.ScriptHash, output.Value.Value); + sb.Emit(OpCode.THROWIFNOT); } script = sb.ToArray(); } - attributes.AddRange(sAttributes.Select(p => new TransactionAttribute - { - Usage = TransactionAttributeUsage.Cosigner, - Data = p.ToArray() - })); Transaction tx = new Transaction { + Version = version, + Nonce = nonce, Script = script, - Attributes = attributes.ToArray() + Sender = sender, + ValidUntilBlock = validUntilBlock, + Attributes = attributes }; try { @@ -285,39 +240,19 @@ public Transaction MakeTransaction(List attributes, IEnume return null; } BigInteger fee = tx.Gas + tx.NetworkFee; - if (balances_gas is null) - { - using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) - foreach (UInt160 account in accounts) - { - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, account); - if (balance >= fee) - { - tx.Sender = account; - break; - } - } - } - else - { - tx.Sender = balances_gas.FirstOrDefault(p => p.Value >= fee).Account; - } - if (tx.Sender is null) return null; + if (balance_gas == BigInteger.Zero) + balance_gas = NativeContract.GAS.BalanceOf(snapshot, sender); + if (balance_gas < fee) return null; return tx; } public bool Sign(ContractParametersContext context) { - bool fSuccess = false; - foreach (UInt160 scriptHash in context.ScriptHashes) - { - WalletAccount account = GetAccount(scriptHash); - if (account?.HasKey != true) continue; - KeyPair key = account.GetKey(); - byte[] signature = context.Verifiable.Sign(key); - fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); - } - return fSuccess; + WalletAccount account = GetAccount(context.ScriptHash); + if (account?.HasKey != true) return false; + KeyPair key = account.GetKey(); + byte[] signature = context.Verifiable.Sign(key); + return context.AddSignature(account.Contract, key.PublicKey, signature); } public abstract bool VerifyPassword(string password); From e365f00ad5f0362bb53c518057f14d2533eb2c3f Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 30 May 2019 12:40:40 +0200 Subject: [PATCH 7/8] Json smart contract parser (#785) * Json api * Serialize and Deserialize * Number overflow protection * Erik's review * Clean code * Fix max js integer * Prevent non deterministic code * Throw with decimal values * improve * rename * rename * simplify * simplify * Add `MIN_SAFE_INTEGER` * Json changes * readability * Implement JSON according to RFC 7159 * Fix the parser of `object` and `array` * disallow to contain duplicate keys in an object * Fix the parser of `number` * Fix the parser of `string` * Fix `JString.Parse()` * Fix `JObject.ParseObject()` and `JArray.Parse()` * Some json unit test * Fix `JObject.Parse()` * Ensure that is not possible to parse invalid numbers * Two more tests * More unit test * Add generic `OrderedDictionary` * Use ordered dictionary for properties * Fix Ordered dictionary * internal * Null in uppercase --- neo.UnitTests/UT_JsonSerializer.cs | 332 ++++++++++++++++++++++++ neo/IO/Caching/OrderedDictionary.cs | 112 ++++++++ neo/IO/Json/JArray.cs | 40 +-- neo/IO/Json/JBoolean.cs | 47 ++-- neo/IO/Json/JNumber.cs | 68 ++++- neo/IO/Json/JObject.cs | 137 +++++----- neo/IO/Json/JString.cs | 37 +-- neo/SmartContract/InteropService.NEO.cs | 22 ++ neo/SmartContract/JsonSerializer.cs | 105 ++++++++ 9 files changed, 772 insertions(+), 128 deletions(-) create mode 100644 neo.UnitTests/UT_JsonSerializer.cs create mode 100644 neo/IO/Caching/OrderedDictionary.cs create mode 100644 neo/SmartContract/JsonSerializer.cs diff --git a/neo.UnitTests/UT_JsonSerializer.cs b/neo.UnitTests/UT_JsonSerializer.cs new file mode 100644 index 0000000000..ed20b15392 --- /dev/null +++ b/neo.UnitTests/UT_JsonSerializer.cs @@ -0,0 +1,332 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_JsonSerializer + { + [TestMethod] + public void JsonTest_WrongJson() + { + var json = "[ ]XXXXXXX"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "{ }XXXXXXX"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[,,,,]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "false,X"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "false@@@"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"{""length"":99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999}"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = $"{{\"length\":{long.MaxValue}}}"; + Assert.ThrowsException(() => JObject.Parse(json)); + } + + [TestMethod] + public void JsonTest_Array() + { + var json = "[ ]"; + var parsed = JObject.Parse(json); + + Assert.AreEqual("[]", parsed.ToString()); + + json = "[1,\"a==\", -1.3 ,null] "; + parsed = JObject.Parse(json); + + Assert.AreEqual("[1,\"a==\",-1.3,null]", parsed.ToString()); + } + + [TestMethod] + public void JsonTest_Bool() + { + var json = "[ true ,false ]"; + var parsed = JObject.Parse(json); + + Assert.AreEqual("[true,false]", parsed.ToString()); + + json = "[True,FALSE] "; + Assert.ThrowsException(() => JObject.Parse(json)); + } + + [TestMethod] + public void JsonTest_Numbers() + { + var json = "[ 1, -2 , 3.5 ]"; + var parsed = JObject.Parse(json); + + Assert.AreEqual("[1,-2,3.5]", parsed.ToString()); + + json = "[200.500000E+005,200.500000e+5,-1.1234e-100]"; + parsed = JObject.Parse(json); + + Assert.AreEqual("[20050000,20050000,-1.1234E-100]", parsed.ToString()); + + json = "[-]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[1.]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[.123]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[--1.123]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[+1.123]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[1.12.3]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[e--1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[e++1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[E- 1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[3e--1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[2e++1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = "[1E- 1]"; + Assert.ThrowsException(() => JObject.Parse(json)); + } + + [TestMethod] + public void JsonTest_String() + { + var json = @" ["""" , ""\b\f\t\n\r\/\\"" ]"; + var parsed = JObject.Parse(json); + + Assert.AreEqual(@"["""",""\b\f\t\n\r\/\\""]", parsed.ToString()); + + json = @"[""\uD834\uDD1E""]"; + parsed = JObject.Parse(json); + + Assert.AreEqual(json, parsed.ToString()); + + json = @"[""\\x00""]"; + parsed = JObject.Parse(json); + + Assert.AreEqual(json, parsed.ToString()); + + json = @"[""]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"[""\uaaa""]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"[""\uaa""]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"[""\ua""]"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"[""\u""]"; + Assert.ThrowsException(() => JObject.Parse(json)); + } + + [TestMethod] + public void JsonTest_Object() + { + var json = @" {""test"": true}"; + var parsed = JObject.Parse(json); + + Assert.AreEqual(@"{""test"":true}", parsed.ToString()); + + json = @" {""\uAAAA"": true}"; + parsed = JObject.Parse(json); + + Assert.AreEqual(@"{""\uAAAA"":true}", parsed.ToString()); + + json = @"{""a"":}"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"{NULL}"; + Assert.ThrowsException(() => JObject.Parse(json)); + + json = @"[""a"":]"; + Assert.ThrowsException(() => JObject.Parse(json)); + } + + [TestMethod] + public void Deserialize_WrongJson() + { + Assert.ThrowsException(() => JsonSerializer.Deserialize(JObject.Parse("x"))); + } + + [TestMethod] + public void Serialize_WrongJson() + { + Assert.ThrowsException(() => JsonSerializer.Serialize(StackItem.FromInterface(new object()))); + } + + [TestMethod] + public void Serialize_EmptyObject() + { + var entry = new Map(); + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "{}"); + } + + [TestMethod] + public void Serialize_Number() + { + var entry = new VM.Types.Array { 1, 9007199254740992 }; + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "[1,\"9007199254740992\"]"); + } + + [TestMethod] + public void Deserialize_EmptyObject() + { + var items = JsonSerializer.Deserialize(JObject.Parse("{}")); + + Assert.IsInstanceOfType(items, typeof(Map)); + Assert.AreEqual(((Map)items).Count, 0); + } + + [TestMethod] + public void Serialize_EmptyArray() + { + var entry = new VM.Types.Array(); + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "[]"); + } + + [TestMethod] + public void Deserialize_EmptyArray() + { + var items = JsonSerializer.Deserialize(JObject.Parse("[]")); + + Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); + Assert.AreEqual(((VM.Types.Array)items).Count, 0); + } + + [TestMethod] + public void Serialize_Map_Test() + { + var entry = new Map + { + ["test1"] = 1, + ["test3"] = 3, + ["test2"] = 2 + }; + + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "{\"test1\":1,\"test3\":3,\"test2\":2}"); + } + + [TestMethod] + public void Deserialize_Map_Test() + { + var items = JsonSerializer.Deserialize(JObject.Parse("{\"test1\":123,\"test2\":321}")); + + Assert.IsInstanceOfType(items, typeof(Map)); + Assert.AreEqual(((Map)items).Count, 2); + + var map = (Map)items; + + Assert.IsTrue(map.TryGetValue("test1", out var value)); + Assert.AreEqual(value.GetBigInteger(), 123); + + Assert.IsTrue(map.TryGetValue("test2", out value)); + Assert.AreEqual(value.GetBigInteger(), 321); + + CollectionAssert.AreEqual(map.Values.Select(u => u.GetBigInteger()).ToArray(), new BigInteger[] { 123, 321 }); + } + + [TestMethod] + public void Serialize_Array_Bool_Str_Num() + { + var entry = new VM.Types.Array { true, "test", 123 }; + + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "[true,\"test\",123]"); + } + + [TestMethod] + public void Deserialize_Array_Bool_Str_Num() + { + var items = JsonSerializer.Deserialize(JObject.Parse("[true,\"test\",123]")); + + Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); + Assert.AreEqual(((VM.Types.Array)items).Count, 3); + + var array = (VM.Types.Array)items; + + Assert.IsTrue(array[0].GetBoolean()); + Assert.AreEqual(array[1].GetString(), "test"); + Assert.AreEqual(array[2].GetBigInteger(), 123); + } + + [TestMethod] + public void Serialize_Array_OfArray() + { + var entry = new VM.Types.Array + { + new VM.Types.Array { true, "test1", 123 }, + new VM.Types.Array { true, "test2", 321 } + }; + + var json = JsonSerializer.Serialize(entry).ToString(); + + Assert.AreEqual(json, "[[true,\"test1\",123],[true,\"test2\",321]]"); + } + + [TestMethod] + public void Deserialize_Array_OfArray() + { + var items = JsonSerializer.Deserialize(JObject.Parse("[[true,\"test1\",123],[true,\"test2\",321]]")); + + Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); + Assert.AreEqual(((VM.Types.Array)items).Count, 2); + + var array = (VM.Types.Array)items; + + Assert.IsInstanceOfType(array[0], typeof(VM.Types.Array)); + Assert.AreEqual(((VM.Types.Array)array[0]).Count, 3); + + array = (VM.Types.Array)array[0]; + Assert.AreEqual(array.Count, 3); + + Assert.IsTrue(array[0].GetBoolean()); + Assert.AreEqual(array[1].GetString(), "test1"); + Assert.AreEqual(array[2].GetBigInteger(), 123); + + array = (VM.Types.Array)items; + array = (VM.Types.Array)array[1]; + Assert.AreEqual(array.Count, 3); + + Assert.IsTrue(array[0].GetBoolean()); + Assert.AreEqual(array[1].GetString(), "test2"); + Assert.AreEqual(array[2].GetBigInteger(), 321); + } + } +} diff --git a/neo/IO/Caching/OrderedDictionary.cs b/neo/IO/Caching/OrderedDictionary.cs new file mode 100644 index 0000000000..bf12785406 --- /dev/null +++ b/neo/IO/Caching/OrderedDictionary.cs @@ -0,0 +1,112 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Neo.IO.Caching +{ + internal class OrderedDictionary : IDictionary + { + private class TItem + { + public TKey Key; + public TValue Value; + } + + private class InternalCollection : KeyedCollection + { + protected override TKey GetKeyForItem(TItem item) + { + return item.Key; + } + } + + private readonly InternalCollection collection = new InternalCollection(); + + public int Count => collection.Count; + public bool IsReadOnly => false; + public ICollection Keys => collection.Select(p => p.Key).ToArray(); + public ICollection Values => collection.Select(p => p.Value).ToArray(); + + public TValue this[TKey key] + { + get + { + return collection[key].Value; + } + set + { + if (collection.Contains(key)) + collection[key].Value = value; + else + Add(key, value); + } + } + + public void Add(TKey key, TValue value) + { + collection.Add(new TItem + { + Key = key, + Value = value + }); + } + + public bool ContainsKey(TKey key) + { + return collection.Contains(key); + } + + public bool Remove(TKey key) + { + return collection.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (collection.Contains(key)) + { + value = collection[key].Value; + return true; + } + value = default; + return false; + } + + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + collection.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return collection.Contains(item.Key); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + for (int i = 0; i < collection.Count; i++) + array[i + arrayIndex] = new KeyValuePair(collection[i].Key, collection[i].Value); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return collection.Remove(item.Key); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return collection.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return collection.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); + } + } +} diff --git a/neo/IO/Json/JArray.cs b/neo/IO/Json/JArray.cs index 02911d505f..3109b3b61c 100644 --- a/neo/IO/Json/JArray.cs +++ b/neo/IO/Json/JArray.cs @@ -9,7 +9,7 @@ namespace Neo.IO.Json { public class JArray : JObject, IList { - private List items = new List(); + private readonly List items = new List(); public JArray(params JObject[] items) : this((IEnumerable)items) { @@ -55,7 +55,7 @@ public void Add(JObject item) public override string AsString() { - return string.Join(",", items.Select(p => p?.AsString())); + return string.Join(VALUE_SEPARATOR.ToString(), items.Select(p => p?.AsString())); } public void Clear() @@ -95,19 +95,27 @@ public void Insert(int index, JObject item) internal new static JArray Parse(TextReader reader, int max_nest) { - if (max_nest < 0) throw new FormatException(); - SkipSpace(reader); - if (reader.Read() != '[') throw new FormatException(); SkipSpace(reader); + if (reader.Read() != BEGIN_ARRAY) throw new FormatException(); JArray array = new JArray(); - while (reader.Peek() != ']') + SkipSpace(reader); + if (reader.Peek() != END_ARRAY) + { + while (true) + { + JObject obj = JObject.Parse(reader, max_nest - 1); + array.items.Add(obj); + SkipSpace(reader); + char nextchar = (char)reader.Read(); + if (nextchar == VALUE_SEPARATOR) continue; + if (nextchar == END_ARRAY) break; + throw new FormatException(); + } + } + else { - if (reader.Peek() == ',') reader.Read(); - JObject obj = JObject.Parse(reader, max_nest - 1); - array.items.Add(obj); - SkipSpace(reader); + reader.Read(); } - reader.Read(); return array; } @@ -124,22 +132,22 @@ public void RemoveAt(int index) public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.Append('['); + sb.Append(BEGIN_ARRAY); foreach (JObject item in items) { if (item == null) - sb.Append("null"); + sb.Append(LITERAL_NULL); else sb.Append(item); - sb.Append(','); + sb.Append(VALUE_SEPARATOR); } if (items.Count == 0) { - sb.Append(']'); + sb.Append(END_ARRAY); } else { - sb[sb.Length - 1] = ']'; + sb[sb.Length - 1] = END_ARRAY; } return sb.ToString(); } diff --git a/neo/IO/Json/JBoolean.cs b/neo/IO/Json/JBoolean.cs index 6cc39ed231..83a9111422 100644 --- a/neo/IO/Json/JBoolean.cs +++ b/neo/IO/Json/JBoolean.cs @@ -24,37 +24,38 @@ public override double AsNumber() public override string AsString() { - return Value.ToString().ToLower(); + return Value.ToString().ToLowerInvariant(); } internal static JBoolean Parse(TextReader reader) { SkipSpace(reader); - char firstChar = (char)reader.Read(); - if (firstChar == 't') - { - int c2 = reader.Read(); - int c3 = reader.Read(); - int c4 = reader.Read(); - if (c2 == 'r' && c3 == 'u' && c4 == 'e') - { - return new JBoolean(true); - } - } - else if (firstChar == 'f') - { - int c2 = reader.Read(); - int c3 = reader.Read(); - int c4 = reader.Read(); - int c5 = reader.Read(); - if (c2 == 'a' && c3 == 'l' && c4 == 's' && c5 == 'e') - { - return new JBoolean(false); - } - } + char firstChar = (char)reader.Peek(); + if (firstChar == LITERAL_FALSE[0]) + return ParseFalse(reader); + else if (firstChar == LITERAL_TRUE[0]) + return ParseTrue(reader); throw new FormatException(); } + internal static JBoolean ParseFalse(TextReader reader) + { + SkipSpace(reader); + for (int i = 0; i < LITERAL_FALSE.Length; i++) + if ((char)reader.Read() != LITERAL_FALSE[i]) + throw new FormatException(); + return new JBoolean(false); + } + + internal static JBoolean ParseTrue(TextReader reader) + { + SkipSpace(reader); + for (int i = 0; i < LITERAL_TRUE.Length; i++) + if ((char)reader.Read() != LITERAL_TRUE[i]) + throw new FormatException(); + return new JBoolean(true); + } + public override string ToString() { return AsString(); diff --git a/neo/IO/Json/JNumber.cs b/neo/IO/Json/JNumber.cs index fdeb495211..f9fa5e05e5 100644 --- a/neo/IO/Json/JNumber.cs +++ b/neo/IO/Json/JNumber.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Text; @@ -6,6 +7,9 @@ namespace Neo.IO.Json { public class JNumber : JObject { + public static readonly long MAX_SAFE_INTEGER = (long)Math.Pow(2, 53) - 1; + public static readonly long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; + public double Value { get; private set; } public JNumber(double value = 0) @@ -25,29 +29,71 @@ public override double AsNumber() public override string AsString() { - if (double.IsPositiveInfinity(Value)) return "Infinity"; - if (double.IsNegativeInfinity(Value)) return "-Infinity"; - return Value.ToString(); + if (double.IsPositiveInfinity(Value)) throw new FormatException("Positive infinity number"); + if (double.IsNegativeInfinity(Value)) throw new FormatException("Negative infinity number"); + return Value.ToString(CultureInfo.InvariantCulture); } internal static JNumber Parse(TextReader reader) { SkipSpace(reader); StringBuilder sb = new StringBuilder(); - while (true) + char nextchar = (char)reader.Read(); + if (nextchar == '-') + { + sb.Append(nextchar); + nextchar = (char)reader.Read(); + } + if (nextchar < '0' || nextchar > '9') throw new FormatException(); + sb.Append(nextchar); + if (nextchar > '0') + { + while (true) + { + char c = (char)reader.Peek(); + if (c < '0' || c > '9') break; + sb.Append((char)reader.Read()); + } + } + nextchar = (char)reader.Peek(); + if (nextchar == '.') { - char c = (char)reader.Peek(); - if (c >= '0' && c <= '9' || c == '.' || c == '-') + sb.Append((char)reader.Read()); + nextchar = (char)reader.Read(); + if (nextchar < '0' || nextchar > '9') throw new FormatException(); + sb.Append(nextchar); + while (true) { - sb.Append(c); - reader.Read(); + nextchar = (char)reader.Peek(); + if (nextchar < '0' || nextchar > '9') break; + sb.Append((char)reader.Read()); } - else + } + if (nextchar == 'e' || nextchar == 'E') + { + sb.Append((char)reader.Read()); + nextchar = (char)reader.Read(); + if (nextchar == '-' || nextchar == '+') + { + sb.Append(nextchar); + nextchar = (char)reader.Read(); + } + if (nextchar < '0' || nextchar > '9') throw new FormatException(); + sb.Append(nextchar); + while (true) { - break; + nextchar = (char)reader.Peek(); + if (nextchar < '0' || nextchar > '9') break; + sb.Append((char)reader.Read()); } } - return new JNumber(double.Parse(sb.ToString())); + + var value = double.Parse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture); + + if (value > MAX_SAFE_INTEGER || value < MIN_SAFE_INTEGER) + throw new FormatException(); + + return new JNumber(value); } public override string ToString() diff --git a/neo/IO/Json/JObject.cs b/neo/IO/Json/JObject.cs index a477e49902..40aa18f5da 100644 --- a/neo/IO/Json/JObject.cs +++ b/neo/IO/Json/JObject.cs @@ -1,4 +1,5 @@ -using System; +using Neo.IO.Caching; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -7,24 +8,34 @@ namespace Neo.IO.Json { public class JObject { + protected const char BEGIN_ARRAY = '['; + protected const char BEGIN_OBJECT = '{'; + protected const char END_ARRAY = ']'; + protected const char END_OBJECT = '}'; + protected const char NAME_SEPARATOR = ':'; + protected const char VALUE_SEPARATOR = ','; + protected const char QUOTATION_MARK = '"'; + protected const string WS = " \t\n\r"; + protected const string LITERAL_FALSE = "false"; + protected const string LITERAL_NULL = "null"; + protected const string LITERAL_TRUE = "true"; + public static readonly JObject Null = null; - private Dictionary properties = new Dictionary(); + public IDictionary Properties { get; } = new OrderedDictionary(); public JObject this[string name] { get { - properties.TryGetValue(name, out JObject value); + Properties.TryGetValue(name, out JObject value); return value; } set { - properties[name] = value; + Properties[name] = value; } } - public IReadOnlyDictionary Properties => properties; - public virtual bool AsBoolean() { return true; @@ -37,12 +48,12 @@ public virtual double AsNumber() public virtual string AsString() { - return "[object Object]"; + return ToString(); } public bool ContainsProperty(string key) { - return properties.ContainsKey(key); + return Properties.ContainsKey(key); } public static JObject Parse(TextReader reader, int max_nest = 100) @@ -50,71 +61,75 @@ public static JObject Parse(TextReader reader, int max_nest = 100) if (max_nest < 0) throw new FormatException(); SkipSpace(reader); char firstChar = (char)reader.Peek(); - if (firstChar == '\"' || firstChar == '\'') - { - return JString.Parse(reader); - } - if (firstChar == '[') - { + if (firstChar == LITERAL_FALSE[0]) + return JBoolean.ParseFalse(reader); + if (firstChar == LITERAL_NULL[0]) + return ParseNull(reader); + if (firstChar == LITERAL_TRUE[0]) + return JBoolean.ParseTrue(reader); + if (firstChar == BEGIN_OBJECT) + return ParseObject(reader, max_nest); + if (firstChar == BEGIN_ARRAY) return JArray.Parse(reader, max_nest); - } if ((firstChar >= '0' && firstChar <= '9') || firstChar == '-') - { return JNumber.Parse(reader); - } - if (firstChar == 't' || firstChar == 'f') - { - return JBoolean.Parse(reader); - } - if (firstChar == 'n') - { - return ParseNull(reader); - } - if (reader.Read() != '{') throw new FormatException(); - SkipSpace(reader); - JObject obj = new JObject(); - while (reader.Peek() != '}') - { - if (reader.Peek() == ',') reader.Read(); - SkipSpace(reader); - string name = JString.Parse(reader).Value; - SkipSpace(reader); - if (reader.Read() != ':') throw new FormatException(); - JObject value = Parse(reader, max_nest - 1); - obj.properties.Add(name, value); - SkipSpace(reader); - } - reader.Read(); - return obj; + if (firstChar == QUOTATION_MARK) + return JString.Parse(reader); + throw new FormatException(); } public static JObject Parse(string value, int max_nest = 100) { using (StringReader reader = new StringReader(value)) { - return Parse(reader, max_nest); + JObject json = Parse(reader, max_nest); + SkipSpace(reader); + if (reader.Read() != -1) throw new FormatException(); + return json; } } private static JObject ParseNull(TextReader reader) { - char firstChar = (char)reader.Read(); - if (firstChar == 'n') + for (int i = 0; i < LITERAL_NULL.Length; i++) + if ((char)reader.Read() != LITERAL_NULL[i]) + throw new FormatException(); + return null; + } + + private static JObject ParseObject(TextReader reader, int max_nest) + { + SkipSpace(reader); + if (reader.Read() != BEGIN_OBJECT) throw new FormatException(); + JObject obj = new JObject(); + SkipSpace(reader); + if (reader.Peek() != END_OBJECT) { - int c2 = reader.Read(); - int c3 = reader.Read(); - int c4 = reader.Read(); - if (c2 == 'u' && c3 == 'l' && c4 == 'l') + while (true) { - return null; + string name = JString.Parse(reader).Value; + if (obj.Properties.ContainsKey(name)) throw new FormatException(); + SkipSpace(reader); + if (reader.Read() != NAME_SEPARATOR) throw new FormatException(); + JObject value = Parse(reader, max_nest - 1); + obj.Properties.Add(name, value); + SkipSpace(reader); + char nextchar = (char)reader.Read(); + if (nextchar == VALUE_SEPARATOR) continue; + if (nextchar == END_OBJECT) break; + throw new FormatException(); } } - throw new FormatException(); + else + { + reader.Read(); + } + return obj; } protected static void SkipSpace(TextReader reader) { - while (reader.Peek() == ' ' || reader.Peek() == '\t' || reader.Peek() == '\r' || reader.Peek() == '\n') + while (WS.IndexOf((char)reader.Peek()) >= 0) { reader.Read(); } @@ -123,30 +138,28 @@ protected static void SkipSpace(TextReader reader) public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.Append('{'); - foreach (KeyValuePair pair in properties) + sb.Append(BEGIN_OBJECT); + foreach (KeyValuePair pair in Properties) { - sb.Append('"'); - sb.Append(pair.Key); - sb.Append('"'); - sb.Append(':'); + sb.Append((JObject)pair.Key); + sb.Append(NAME_SEPARATOR); if (pair.Value == null) { - sb.Append("null"); + sb.Append(LITERAL_NULL); } else { sb.Append(pair.Value); } - sb.Append(','); + sb.Append(VALUE_SEPARATOR); } - if (properties.Count == 0) + if (Properties.Count == 0) { - sb.Append('}'); + sb.Append(END_OBJECT); } else { - sb[sb.Length - 1] = '}'; + sb[sb.Length - 1] = END_OBJECT; } return sb.ToString(); } diff --git a/neo/IO/Json/JString.cs b/neo/IO/Json/JString.cs index f208ea95c5..065e7b59f9 100644 --- a/neo/IO/Json/JString.cs +++ b/neo/IO/Json/JString.cs @@ -23,7 +23,7 @@ public override bool AsBoolean() public override double AsNumber() { if (string.IsNullOrEmpty(Value)) return 0; - return double.TryParse(Value, out double result) ? result : double.NaN; + return double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double result) ? result : double.NaN; } public override string AsString() @@ -34,40 +34,45 @@ public override string AsString() internal static JString Parse(TextReader reader) { SkipSpace(reader); + if (reader.Read() != QUOTATION_MARK) throw new FormatException(); char[] buffer = new char[4]; - char firstChar = (char)reader.Read(); - if (firstChar != '\"' && firstChar != '\'') throw new FormatException(); StringBuilder sb = new StringBuilder(); while (true) { - char c = (char)reader.Read(); - if (c == 65535) throw new FormatException(); - if (c == firstChar) break; + int c = reader.Read(); + if (c == QUOTATION_MARK) break; if (c == '\\') { c = (char)reader.Read(); switch (c) { + case QUOTATION_MARK: c = QUOTATION_MARK; break; + case '\\': c = '\\'; break; + case '/': c = '/'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; case 'u': - reader.Read(buffer, 0, 4); - c = (char)short.Parse(new string(buffer), NumberStyles.HexNumber); - break; - case 'r': - c = '\r'; - break; - case 'n': - c = '\n'; + reader.Read(buffer, 0, buffer.Length); + c = short.Parse(new string(buffer), NumberStyles.HexNumber); break; + default: throw new FormatException(); } } - sb.Append(c); + else if (c < ' ' || c == -1) + { + throw new FormatException(); + } + sb.Append((char)c); } return new JString(sb.ToString()); } public override string ToString() { - return $"\"{JavaScriptEncoder.Default.Encode(Value)}\""; + return $"{QUOTATION_MARK}{JavaScriptEncoder.Default.Encode(Value)}{QUOTATION_MARK}"; } public override T TryGetEnum(T defaultValue = default, bool ignoreCase = false) diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index d8ecab8cb6..d1e1d50aa3 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -1,4 +1,5 @@ using Neo.Cryptography; +using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -40,6 +41,8 @@ static partial class InteropService public static readonly uint Neo_Iterator_Keys = Register("Neo.Iterator.Keys", Iterator_Keys, 0_00000400); public static readonly uint Neo_Iterator_Values = Register("Neo.Iterator.Values", Iterator_Values, 0_00000400); public static readonly uint Neo_Iterator_Concat = Register("Neo.Iterator.Concat", Iterator_Concat, 0_00000400); + public static readonly uint Neo_Json_Serialize = Register("Neo.Json.Serialize", Json_Serialize, 0_00100000); + public static readonly uint Neo_Json_Deserialize = Register("Neo.Json.Deserialize", Json_Deserialize, 0_00500000); static InteropService() { @@ -462,5 +465,24 @@ private static bool Iterator_Concat(ApplicationEngine engine) engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(result)); return true; } + + private static bool Json_Deserialize(ApplicationEngine engine) + { + var json = engine.CurrentContext.EvaluationStack.Pop().GetString(); + var obj = JObject.Parse(json, 10); + var item = JsonSerializer.Deserialize(obj); + + engine.CurrentContext.EvaluationStack.Push(item); + return true; + } + + private static bool Json_Serialize(ApplicationEngine engine) + { + var item = engine.CurrentContext.EvaluationStack.Pop(); + var json = JsonSerializer.Serialize(item); + + engine.CurrentContext.EvaluationStack.Push(json.ToString()); + return true; + } } } diff --git a/neo/SmartContract/JsonSerializer.cs b/neo/SmartContract/JsonSerializer.cs new file mode 100644 index 0000000000..d635c6c7b8 --- /dev/null +++ b/neo/SmartContract/JsonSerializer.cs @@ -0,0 +1,105 @@ +using Neo.IO.Json; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; +using System.Numerics; +using VMArray = Neo.VM.Types.Array; +using VMBoolean = Neo.VM.Types.Boolean; + +namespace Neo.SmartContract +{ + public static class JsonSerializer + { + /// + /// Convert stack item in json + /// + /// Item + /// Json + public static JObject Serialize(StackItem item) + { + switch (item) + { + case VMArray array: + { + return array.Select(p => Serialize(p)).ToArray(); + } + case ByteArray buffer: + { + return buffer.GetString(); + } + case Integer num: + { + var integer = num.GetBigInteger(); + if (integer > JNumber.MAX_SAFE_INTEGER || integer < JNumber.MIN_SAFE_INTEGER) + return integer.ToString(); + return (double)num.GetBigInteger(); + } + case VMBoolean boolean: + { + return boolean.GetBoolean(); + } + case Map map: + { + var ret = new JObject(); + + foreach (var entry in map) + { + var key = entry.Key.GetString(); + var value = Serialize(entry.Value); + + ret[key] = value; + } + + return ret; + } + default: throw new FormatException(); + } + } + + /// + /// Convert json object to stack item + /// + /// Json + /// Return stack item + public static StackItem Deserialize(JObject json) + { + switch (json) + { + case JArray array: + { + return array.Select(p => Deserialize(p)).ToList(); + } + case JString str: + { + return str.Value; + } + case JNumber num: + { + if ((num.Value % 1) != 0) throw new FormatException("Decimal value is not allowed"); + + return (BigInteger)num.Value; + } + case JBoolean boolean: + { + return new VMBoolean(boolean.Value); + } + case JObject obj: + { + var item = new Map(); + + foreach (var entry in obj.Properties) + { + var key = entry.Key; + var value = Deserialize(entry.Value); + + item.Add(key, value); + } + + return item; + } + default: throw new FormatException(); + } + } + } +} From a8ada562b73911df10db2e9d6a3b2c459bb409fd Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 31 May 2019 15:46:42 +0200 Subject: [PATCH 8/8] Clean unit test and follow some visual studio hints (#792) * Indentantions * Follow some Visual studio hints * Clean code --- neo.UnitTests/UT_Header.cs | 32 ++++++++---------------------- neo.UnitTests/UT_Helper.cs | 6 +++--- neo.UnitTests/UT_InteropPrices.cs | 2 -- neo.UnitTests/UT_MemoryPool.cs | 2 -- neo.UnitTests/UT_P2PMessage.cs | 2 -- neo.UnitTests/UT_Policy.cs | 2 +- neo.UnitTests/UT_PoolItem.cs | 4 ++-- neo.UnitTests/UT_StorageKey.cs | 28 ++++++++++++++++---------- neo.UnitTests/UT_Transaction.cs | 2 +- neo.UnitTests/UT_UIntBenchmarks.cs | 8 -------- neo.UnitTests/UT_Witness.cs | 5 ++--- 11 files changed, 34 insertions(+), 59 deletions(-) diff --git a/neo.UnitTests/UT_Header.cs b/neo.UnitTests/UT_Header.cs index edcde41674..e3df13e14d 100644 --- a/neo.UnitTests/UT_Header.cs +++ b/neo.UnitTests/UT_Header.cs @@ -21,11 +21,7 @@ public void TestSetup() public void Size_Get() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - TestUtils.SetupHeaderWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); + TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 3 // header 1 uut.Size.Should().Be(100); @@ -35,11 +31,7 @@ public void Size_Get() public void Deserialize() { UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - TestUtils.SetupHeaderWithValues(new Header(), val256, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal); + TestUtils.SetupHeaderWithValues(new Header(), val256, out UInt256 merkRoot, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid @@ -65,7 +57,7 @@ private void assertStandardHeaderTestVals(UInt256 val256, UInt256 merkRoot, UInt uut.NextConsensus.Should().Be(val160); uut.Witness.InvocationScript.Length.Should().Be(0); uut.Witness.Size.Should().Be(scriptVal.Size); - uut.Witness.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); + uut.Witness.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); } [TestMethod] @@ -80,18 +72,14 @@ public void Equals_SameHeader() { uut.Equals(uut).Should().BeTrue(); } - + [TestMethod] public void Equals_SameHash() { - Header newHeader = new Header(); + Header newHeader = new Header(); UInt256 prevHash = new UInt256(TestUtils.GetByteArray(32, 0x42)); - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - TestUtils.SetupHeaderWithValues(newHeader, prevHash, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal); - TestUtils.SetupHeaderWithValues(uut, prevHash, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal); + TestUtils.SetupHeaderWithValues(newHeader, prevHash, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(uut, prevHash, out _, out _, out _, out _, out _); uut.Equals(newHeader).Should().BeTrue(); } @@ -106,11 +94,7 @@ public void Equals_SameObject() public void Serialize() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - TestUtils.SetupHeaderWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); + TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); byte[] data; using (MemoryStream stream = new MemoryStream()) diff --git a/neo.UnitTests/UT_Helper.cs b/neo.UnitTests/UT_Helper.cs index c836219b88..d64d9c9173 100644 --- a/neo.UnitTests/UT_Helper.cs +++ b/neo.UnitTests/UT_Helper.cs @@ -15,7 +15,7 @@ public void GetHashData() TestVerifiable verifiable = new TestVerifiable(); byte[] res = verifiable.GetHashData(); res.Length.Should().Be(8); - byte[] requiredData = new byte[] {7, 116, 101, 115, 116, 83, 116, 114}; + byte[] requiredData = new byte[] { 7, 116, 101, 115, 116, 83, 116, 114 }; for (int i = 0; i < requiredData.Length; i++) { res[i].Should().Be(requiredData[i]); @@ -26,14 +26,14 @@ public void GetHashData() public void Sign() { TestVerifiable verifiable = new TestVerifiable(); - byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32,0x42))); + byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32, 0x42))); res.Length.Should().Be(64); } [TestMethod] public void ToScriptHash() { - byte[] testByteArray = TestUtils.GetByteArray(64,0x42); + byte[] testByteArray = TestUtils.GetByteArray(64, 0x42); UInt160 res = testByteArray.ToScriptHash(); res.Should().Be(UInt160.Parse("2d3b96ae1bcc5a585e075e3b81920210dec16302")); } diff --git a/neo.UnitTests/UT_InteropPrices.cs b/neo.UnitTests/UT_InteropPrices.cs index 6e0b4fcb61..27c0a4cdc0 100644 --- a/neo.UnitTests/UT_InteropPrices.cs +++ b/neo.UnitTests/UT_InteropPrices.cs @@ -1,9 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; -using Neo.SmartContract.Manifest; using Neo.VM; -using System.Reflection; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/UT_MemoryPool.cs index 71a4e84f09..a35c85fcc7 100644 --- a/neo.UnitTests/UT_MemoryPool.cs +++ b/neo.UnitTests/UT_MemoryPool.cs @@ -35,8 +35,6 @@ public void TestSetup() _unit.Count.ShouldBeEquivalentTo(0); } - - long LongRandom(long min, long max, Random rand) { // Only returns positive random long values. diff --git a/neo.UnitTests/UT_P2PMessage.cs b/neo.UnitTests/UT_P2PMessage.cs index e8fe25d83b..99af976992 100644 --- a/neo.UnitTests/UT_P2PMessage.cs +++ b/neo.UnitTests/UT_P2PMessage.cs @@ -74,7 +74,6 @@ public void Serialize_Deserialize_WithoutPayload_ByteString() buffer.Count.Should().Be(length); } - [TestMethod] public void Compression() { @@ -111,7 +110,6 @@ public void Compression() payloadCopy.Capabilities.Length.Should().Be(1); ((ServerCapability)payloadCopy.Capabilities[0]).Type.Should().Be(NodeCapabilityType.TcpServer); ((ServerCapability)payloadCopy.Capabilities[0]).Port.Should().Be(25); - } } } \ No newline at end of file diff --git a/neo.UnitTests/UT_Policy.cs b/neo.UnitTests/UT_Policy.cs index f0893a0a88..da93eca368 100644 --- a/neo.UnitTests/UT_Policy.cs +++ b/neo.UnitTests/UT_Policy.cs @@ -229,7 +229,7 @@ public void Check_Block_UnblockAccount() ret.Should().BeOfType(); ((VM.Types.Array)ret).Count.Should().Be(1); ((VM.Types.Array)ret)[0].GetByteArray().ShouldBeEquivalentTo(UInt160.Zero.ToArray()); - + // Unblock without signature ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), diff --git a/neo.UnitTests/UT_PoolItem.cs b/neo.UnitTests/UT_PoolItem.cs index 9b240dbe0c..272dcf4084 100644 --- a/neo.UnitTests/UT_PoolItem.cs +++ b/neo.UnitTests/UT_PoolItem.cs @@ -1,9 +1,9 @@ -using System; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Neo.Ledger; -using FluentAssertions; using Neo.Network.P2P.Payloads; +using System; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_StorageKey.cs b/neo.UnitTests/UT_StorageKey.cs index 1ba8d61d26..965e46ae4e 100644 --- a/neo.UnitTests/UT_StorageKey.cs +++ b/neo.UnitTests/UT_StorageKey.cs @@ -62,9 +62,11 @@ public void Equals_SameHash_SameKey() { UInt160 val = new UInt160(TestUtils.GetByteArray(20, 0x42)); byte[] keyVal = TestUtils.GetByteArray(10, 0x42); - StorageKey newSk = new StorageKey(); - newSk.ScriptHash = val; - newSk.Key = keyVal; + StorageKey newSk = new StorageKey + { + ScriptHash = val, + Key = keyVal + }; uut.ScriptHash = val; uut.Key = keyVal; @@ -76,10 +78,12 @@ public void Equals_DiffHash_SameKey() { UInt160 val = new UInt160(TestUtils.GetByteArray(20, 0x42)); byte[] keyVal = TestUtils.GetByteArray(10, 0x42); - StorageKey newSk = new StorageKey(); - newSk.ScriptHash = val; - newSk.Key = keyVal; - uut.ScriptHash = new UInt160(TestUtils.GetByteArray(20, 0x88)); + StorageKey newSk = new StorageKey + { + ScriptHash = val, + Key = keyVal + }; + uut.ScriptHash = new UInt160(TestUtils.GetByteArray(20, 0x88)); uut.Key = keyVal; uut.Equals(newSk).Should().BeFalse(); @@ -91,11 +95,13 @@ public void Equals_SameHash_DiffKey() { UInt160 val = new UInt160(TestUtils.GetByteArray(20, 0x42)); byte[] keyVal = TestUtils.GetByteArray(10, 0x42); - StorageKey newSk = new StorageKey(); - newSk.ScriptHash = val; - newSk.Key = keyVal; + StorageKey newSk = new StorageKey + { + ScriptHash = val, + Key = keyVal + }; uut.ScriptHash = val; - uut.Key = TestUtils.GetByteArray(10, 0x88); + uut.Key = TestUtils.GetByteArray(10, 0x88); uut.Equals(newSk).Should().BeFalse(); } diff --git a/neo.UnitTests/UT_Transaction.cs b/neo.UnitTests/UT_Transaction.cs index d68f288270..2600867986 100644 --- a/neo.UnitTests/UT_Transaction.cs +++ b/neo.UnitTests/UT_Transaction.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO.Json; using Neo.IO; +using Neo.IO.Json; using Neo.Network.P2P.Payloads; namespace Neo.UnitTests diff --git a/neo.UnitTests/UT_UIntBenchmarks.cs b/neo.UnitTests/UT_UIntBenchmarks.cs index be43120d6f..1dea95f035 100644 --- a/neo.UnitTests/UT_UIntBenchmarks.cs +++ b/neo.UnitTests/UT_UIntBenchmarks.cs @@ -67,14 +67,6 @@ private byte[] RandomBytes(int count) return (elapsed, result); } - /* Could do this also so just pass the method to benchmark, but overhead of delegate call might affect benchmark - public delegate int ComparisonMethod(byte[] b1, byte[] b2); - - public int BechmarkComparisonMethod(ComparisonMethod compareMethod) - { - } - */ - [TestMethod] public void Benchmark_CompareTo_UInt256() { diff --git a/neo.UnitTests/UT_Witness.cs b/neo.UnitTests/UT_Witness.cs index 26dc8b84dd..4ca00b6643 100644 --- a/neo.UnitTests/UT_Witness.cs +++ b/neo.UnitTests/UT_Witness.cs @@ -28,7 +28,7 @@ public void InvocationScript_Set() byte[] dataArray = new byte[] { 0, 32, 32, 20, 32, 32 }; uut.InvocationScript = dataArray; uut.InvocationScript.Length.Should().Be(6); - Assert.AreEqual(uut.InvocationScript.ToHexString(), "002020142020"); + Assert.AreEqual(uut.InvocationScript.ToHexString(), "002020142020"); } private void setupWitnessWithValues(Witness uut, int lenghtInvocation, int lengthVerification, out byte[] invocationScript, out byte[] verificationScript) @@ -68,10 +68,9 @@ public void ToJson() JObject json = uut.ToJson(); Assert.IsTrue(json.ContainsProperty("invocation")); - Assert.IsTrue(json.ContainsProperty("verification")); + Assert.IsTrue(json.ContainsProperty("verification")); Assert.AreEqual(json["invocation"].AsString(), "2020"); Assert.AreEqual(json["verification"].AsString(), "202020"); - } } } \ No newline at end of file