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)