From 411f2970d6cb1c521de330300100a07f883f0959 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 15 Jan 2021 15:35:16 +0800 Subject: [PATCH 1/4] Allow method overloads --- .../ApplicationEngine.Contract.cs | 4 ++-- src/neo/SmartContract/ApplicationEngine.cs | 19 +++++++-------- src/neo/SmartContract/DeployedContract.cs | 2 +- src/neo/SmartContract/Helper.cs | 6 +++-- src/neo/SmartContract/Manifest/ContractAbi.cs | 24 ++++++++++++++----- .../Native/ContractManagement.cs | 4 ++-- src/neo/Wallets/Wallet.cs | 8 +++++-- .../Extensions/NativeContractExtensions.cs | 3 ++- .../SmartContract/Native/UT_NativeContract.cs | 4 ++-- 9 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.Contract.cs b/src/neo/SmartContract/ApplicationEngine.Contract.cs index 0d0312849d..e08a17099b 100644 --- a/src/neo/SmartContract/ApplicationEngine.Contract.cs +++ b/src/neo/SmartContract/ApplicationEngine.Contract.cs @@ -30,8 +30,8 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}"); - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method); - if (md is null) throw new InvalidOperationException($"Method {method} Does Not Exist In Contract {contractHash}"); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method, args.Count); + if (md is null) throw new InvalidOperationException($"Method \"{method}\" with {args.Count} parameter(s) doesn't exist in the contract {contractHash}."); bool hasReturnValue = md.ReturnType != ContractParameterType.Void; if (!hasReturnValue) CurrentContext.EvaluationStack.Push(StackItem.Null); diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index f481e27726..5b972f48bb 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -80,8 +80,8 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho { ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}"); - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method); - if (md is null) throw new InvalidOperationException($"Method {method} Does Not Exist In Contract {contractHash}"); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method, args.Length); + if (md is null) throw new InvalidOperationException($"Method \"{method}\" with {args.Length} parameter(s) doesn't exist in the contract {contractHash}."); return CallContractInternal(contract, md, flags, hasReturnValue, args); } @@ -113,7 +113,7 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe if (args.Count != method.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {method.Parameters.Length} Arguments But Receives {args.Count} Arguments"); if (hasReturnValue ^ (method.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException("The return value type does not match."); - ExecutionContext context_new = LoadContract(contract, method.Name, flags & callingFlags, hasReturnValue, (ushort)args.Count); + ExecutionContext context_new = LoadContract(contract, method, flags & callingFlags); state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; @@ -163,15 +163,12 @@ protected override void LoadContext(ExecutionContext context) base.LoadContext(context); } - public ExecutionContext LoadContract(ContractState contract, string method, CallFlags callFlags, bool hasReturnValue, ushort pcount) + public ExecutionContext LoadContract(ContractState contract, ContractMethodDescriptor method, CallFlags callFlags) { - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method); - if (md is null) return null; - ExecutionContext context = LoadScript(contract.Script, - pcount: pcount, - rvcount: hasReturnValue ? 1 : 0, - initialPosition: md.Offset, + pcount: (ushort)method.Parameters.Length, + rvcount: method.ReturnType == ContractParameterType.Void ? 0 : 1, + initialPosition: method.Offset, configureState: p => { p.CallFlags = callFlags; @@ -180,7 +177,7 @@ public ExecutionContext LoadContract(ContractState contract, string method, Call }); // Call initialization - var init = contract.Manifest.Abi.GetMethod("_initialize"); + var init = contract.Manifest.Abi.GetMethod("_initialize", 0); if (init != null) { LoadContext(context.Clone(init.Offset)); diff --git a/src/neo/SmartContract/DeployedContract.cs b/src/neo/SmartContract/DeployedContract.cs index c784811b5e..4a5e3ba2a5 100644 --- a/src/neo/SmartContract/DeployedContract.cs +++ b/src/neo/SmartContract/DeployedContract.cs @@ -14,7 +14,7 @@ public DeployedContract(ContractState contract) Script = null; ScriptHash = contract.Hash; - ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod("verify"); + ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod("verify", -1); if (descriptor is null) throw new NotSupportedException("The smart contract haven't got verify method."); ParameterList = descriptor.Parameters.Select(u => u.Type).ToArray(); diff --git a/src/neo/SmartContract/Helper.cs b/src/neo/SmartContract/Helper.cs index b4d22851da..c6091f0be6 100644 --- a/src/neo/SmartContract/Helper.cs +++ b/src/neo/SmartContract/Helper.cs @@ -2,6 +2,7 @@ using Neo.Cryptography.ECC; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; using System; @@ -187,8 +188,9 @@ internal static bool VerifyWitness(this IVerifiable verifiable, StoreView snapsh { ContractState cs = NativeContract.ContractManagement.GetContract(snapshot, hash); if (cs is null) return false; - if (engine.LoadContract(cs, "verify", callFlags, true, 0) is null) - return false; + ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify", -1); + if (md?.ReturnType != ContractParameterType.Boolean) return false; + engine.LoadContract(cs, md, callFlags); } else { diff --git a/src/neo/SmartContract/Manifest/ContractAbi.cs b/src/neo/SmartContract/Manifest/ContractAbi.cs index a6cd7d0af1..902413bd7b 100644 --- a/src/neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/neo/SmartContract/Manifest/ContractAbi.cs @@ -1,15 +1,16 @@ using Neo.IO.Json; +using System; using System.Collections.Generic; 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) + /// NeoContract ABI /// public class ContractAbi { - private IReadOnlyDictionary methodDictionary; + private IReadOnlyDictionary<(string, int), ContractMethodDescriptor> methodDictionary; /// /// Methods is an array of Method objects which describe the details of each method in the contract. @@ -44,11 +45,22 @@ public static ContractAbi FromJson(JObject json) }; } - public ContractMethodDescriptor GetMethod(string name) + public ContractMethodDescriptor GetMethod(string name, int pcount) { - methodDictionary ??= Methods.ToDictionary(p => p.Name); - methodDictionary.TryGetValue(name, out var method); - return method; + if (pcount < -1 || pcount > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(pcount)); + methodDictionary ??= Methods.ToDictionary(p => (p.Name, p.Parameters.Length)); + if (pcount >= 0) + { + methodDictionary.TryGetValue((name, pcount), out var method); + return method; + } + else + { + foreach (var ((n, _), method) in methodDictionary) + if (n == name) + return method; + return null; + } } public JObject ToJson() diff --git a/src/neo/SmartContract/Native/ContractManagement.cs b/src/neo/SmartContract/Native/ContractManagement.cs index a65d9fd100..d9523dd040 100644 --- a/src/neo/SmartContract/Native/ContractManagement.cs +++ b/src/neo/SmartContract/Native/ContractManagement.cs @@ -154,7 +154,7 @@ private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] ma // Execute _deploy - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy"); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 1); if (md != null) engine.CallFromNativeContract(Hash, hash, md.Name, false); @@ -192,7 +192,7 @@ private void Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest) contract.UpdateCounter++; // Increase update counter if (nefFile != null) { - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy"); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 1); if (md != null) engine.CallFromNativeContract(Hash, contract.Hash, md.Name, true); } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 77015abf60..9f51a7f82b 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -391,14 +391,18 @@ public long CalculateNetworkFee(StoreView snapshot, Transaction tx) { var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); if (contract is null) continue; + var md = contract.Manifest.Abi.GetMethod("verify", 0); + if (md is null) + throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); + if (md.ReturnType != ContractParameterType.Boolean) + throw new ArgumentException("The verify method doesn't return boolean value."); // Empty invocation and verification scripts size += Array.Empty().GetVarSize() * 2; // Check verify cost using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.Clone()); - if (engine.LoadContract(contract, "verify", CallFlags.None, true, 0) is null) - throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); + engine.LoadContract(contract, md, CallFlags.None); if (NativeContract.IsNative(hash)) engine.Push("verify"); if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false."); diff --git a/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs b/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs index a98b08b3b8..f87da6250b 100644 --- a/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -99,6 +99,7 @@ public static StackItem Call(this NativeContract contract, StoreView snapshot, I var engine = ApplicationEngine.Create(TriggerType.Application, container, snapshot, persistingBlock); var contractState = NativeContract.ContractManagement.GetContract(snapshot, contract.Hash); if (contractState == null) throw new InvalidOperationException(); + var md = contract.Manifest.Abi.GetMethod(method, args.Length); var script = new ScriptBuilder(); @@ -106,7 +107,7 @@ public static StackItem Call(this NativeContract contract, StoreView snapshot, I script.EmitPush(args[i]); script.EmitPush(method); - engine.LoadContract(contractState, method, CallFlags.All, contract.Manifest.Abi.GetMethod(method).ReturnType != ContractParameterType.Void, (ushort)args.Length); + engine.LoadContract(contractState, md, CallFlags.All); engine.LoadScript(script.ToArray()); if (engine.Execute() != VMState.HALT) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 601c21ab52..d8704b614a 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -51,7 +51,7 @@ public void VMTypes( public void TestToParameter() { var manifest = new DummyNative().Manifest; - var netTypes = manifest.Abi.GetMethod("netTypes"); + var netTypes = manifest.Abi.GetMethod("netTypes", 17); Assert.AreEqual(netTypes.ReturnType, ContractParameterType.Void); Assert.AreEqual(netTypes.Parameters[0].Type, ContractParameterType.Boolean); @@ -72,7 +72,7 @@ public void TestToParameter() Assert.AreEqual(netTypes.Parameters[15].Type, ContractParameterType.Integer); Assert.AreEqual(netTypes.Parameters[16].Type, ContractParameterType.Any); - var vmTypes = manifest.Abi.GetMethod("vMTypes"); + var vmTypes = manifest.Abi.GetMethod("vMTypes", 8); Assert.AreEqual(vmTypes.ReturnType, ContractParameterType.Void); Assert.AreEqual(vmTypes.Parameters[0].Type, ContractParameterType.Boolean); From 7457d0250c244b99cb6eaf9ce2be8e9ff0b76b93 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 15 Jan 2021 16:41:55 +0800 Subject: [PATCH 2/4] Fix _deploy --- src/neo/SmartContract/Native/ContractManagement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/Native/ContractManagement.cs b/src/neo/SmartContract/Native/ContractManagement.cs index 1af66f55f4..1bbfbac331 100644 --- a/src/neo/SmartContract/Native/ContractManagement.cs +++ b/src/neo/SmartContract/Native/ContractManagement.cs @@ -155,7 +155,7 @@ private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] ma // Execute _deploy - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 1); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); if (md != null) engine.CallFromNativeContract(Hash, hash, md.Name, data, false); @@ -196,7 +196,7 @@ private void Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, S contract.UpdateCounter++; // Increase update counter if (nefFile != null) { - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 1); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); if (md != null) engine.CallFromNativeContract(Hash, contract.Hash, md.Name, data, true); } From 150ae6185025ded5514197b88cd618085235de1f Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 15 Jan 2021 20:39:41 +0800 Subject: [PATCH 3/4] deterministic --- src/neo/SmartContract/Manifest/ContractAbi.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/neo/SmartContract/Manifest/ContractAbi.cs b/src/neo/SmartContract/Manifest/ContractAbi.cs index 902413bd7b..5067cf9552 100644 --- a/src/neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/neo/SmartContract/Manifest/ContractAbi.cs @@ -48,18 +48,15 @@ public static ContractAbi FromJson(JObject json) public ContractMethodDescriptor GetMethod(string name, int pcount) { if (pcount < -1 || pcount > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(pcount)); - methodDictionary ??= Methods.ToDictionary(p => (p.Name, p.Parameters.Length)); if (pcount >= 0) { + methodDictionary ??= Methods.ToDictionary(p => (p.Name, p.Parameters.Length)); methodDictionary.TryGetValue((name, pcount), out var method); return method; } else { - foreach (var ((n, _), method) in methodDictionary) - if (n == name) - return method; - return null; + return Methods.FirstOrDefault(p => p.Name == name); } } From 801e52f388cd528784e1b9dbcf29f6dcec40e473 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sat, 16 Jan 2021 12:31:52 +0100 Subject: [PATCH 4/4] Update src/neo/Wallets/Wallet.cs --- src/neo/Wallets/Wallet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 9f51a7f82b..141b21eec7 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -393,7 +393,7 @@ public long CalculateNetworkFee(StoreView snapshot, Transaction tx) if (contract is null) continue; var md = contract.Manifest.Abi.GetMethod("verify", 0); if (md is null) - throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); + throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method without arguments"); if (md.ReturnType != ContractParameterType.Boolean) throw new ArgumentException("The verify method doesn't return boolean value.");