From 7a1a01328986ff93e8008ec8a9e8abca9ff90f89 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 19:33:58 +0800 Subject: [PATCH 1/4] LoadToken --- .../ApplicationEngine.Contract.cs | 49 ------------ src/neo/SmartContract/ApplicationEngine.cs | 75 +++++++++++++++++-- .../SmartContract/ExecutionContextState.cs | 5 ++ 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.Contract.cs b/src/neo/SmartContract/ApplicationEngine.Contract.cs index 7e9f9e9c1b..65249da2b7 100644 --- a/src/neo/SmartContract/ApplicationEngine.Contract.cs +++ b/src/neo/SmartContract/ApplicationEngine.Contract.cs @@ -1,8 +1,6 @@ using Neo.Cryptography.ECC; using Neo.Network.P2P.Payloads; -using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; -using Neo.VM; using Neo.VM.Types; using System; @@ -35,53 +33,6 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl CallContractInternal(contractHash, method, callFlags, hasReturnValue, args); } - private void CallContractInternal(UInt160 contractHash, string method, CallFlags flags, bool hasReturnValue, StackItem[] args) - { - 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}"); - - if (md.Safe) - { - flags &= ~CallFlags.WriteStates; - } - else - { - ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); - if (currentContract?.CanCall(contract, method) == false) - throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contractHash} From Contract {CurrentScriptHash}"); - } - - CallContractInternal(contract, md, flags, hasReturnValue, args); - } - - private void CallContractInternal(ContractState contract, ContractMethodDescriptor method, CallFlags flags, bool hasReturnValue, StackItem[] args) - { - if (invocationCounter.TryGetValue(contract.Hash, out var counter)) - { - invocationCounter[contract.Hash] = counter + 1; - } - else - { - invocationCounter[contract.Hash] = 1; - } - - ExecutionContextState state = CurrentContext.GetState(); - UInt160 callingScriptHash = state.ScriptHash; - CallFlags callingFlags = state.CallFlags; - - if (args.Length != method.Parameters.Length) throw new InvalidOperationException($"Method {method.Name} Expects {method.Parameters.Length} Arguments But Receives {args.Length} Arguments"); - ExecutionContext context_new = LoadContract(contract, method.Name, flags & callingFlags, hasReturnValue, (ushort)args.Length); - state = context_new.GetState(); - state.CallingScriptHash = callingScriptHash; - - for (int i = args.Length - 1; i >= 0; i--) - context_new.EvaluationStack.Push(args[i]); - if (NativeContract.IsNative(contract.Hash)) - context_new.EvaluationStack.Push(method.Name); - } - protected internal void CallNativeContract(string name) { NativeContract contract = NativeContract.GetContract(name); diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index d04c7e2b9b..5badb21c8b 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -74,20 +74,68 @@ protected override void OnFault(Exception e) base.OnFault(e); } - internal void CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + private ExecutionContext CallContractInternal(UInt160 contractHash, string method, CallFlags flags, bool hasReturnValue, StackItem[] args) { - CallContractInternal(hash, method, CallFlags.All, false, args); + 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}"); + + if (md.Safe) + { + flags &= ~CallFlags.WriteStates; + } + else + { + ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); + if (currentContract?.CanCall(contract, method) == false) + throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contractHash} From Contract {CurrentScriptHash}"); + } + + if (invocationCounter.TryGetValue(contract.Hash, out var counter)) + { + invocationCounter[contract.Hash] = counter + 1; + } + else + { + invocationCounter[contract.Hash] = 1; + } + ExecutionContextState state = CurrentContext.GetState(); + UInt160 callingScriptHash = state.ScriptHash; + CallFlags callingFlags = state.CallFlags; + + if (args.Length != md.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {md.Parameters.Length} Arguments But Receives {args.Length} Arguments"); + ExecutionContext context_new = LoadContract(contract, method, flags & callingFlags, hasReturnValue, (ushort)args.Length); + state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; - StepOut(); + + for (int i = args.Length - 1; i >= 0; i--) + context_new.EvaluationStack.Push(args[i]); + if (NativeContract.IsNative(contract.Hash)) + context_new.EvaluationStack.Push(method); + + return context_new; + } + + internal void CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + { + ExecutionContext context_current = CurrentContext; + ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, false, args); + ExecutionContextState state = context_new.GetState(); + state.CallingScriptHash = callingScriptHash; + while (CurrentContext != context_current) + StepOut(); } internal T CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) { - CallContractInternal(hash, method, CallFlags.All, true, args); - ExecutionContextState state = CurrentContext.GetState(); + ExecutionContext context_current = CurrentContext; + ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, true, args); + ExecutionContextState state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; - StepOut(); + while (CurrentContext != context_current) + StepOut(); return (T)Convert(Pop(), new InteropParameterDescriptor(typeof(T))); } @@ -121,6 +169,7 @@ public ExecutionContext LoadContract(ContractState contract, string method, Call { p.CallFlags = callFlags; p.ScriptHash = contract.Hash; + p.Contract = contract; }); // Call initialization @@ -143,6 +192,20 @@ public ExecutionContext LoadScript(Script script, ushort pcount = 0, int rvcount return context; } + protected override ExecutionContext LoadToken(ushort tokenId) + { + ContractState contract = CurrentContext.GetState().Contract; + if (contract is null || tokenId >= contract.Nef.Tokens.Length) + throw new InvalidOperationException(); + MethodToken token = contract.Nef.Tokens[tokenId]; + if (token.ParametersCount > CurrentContext.EvaluationStack.Count) + throw new InvalidOperationException(); + StackItem[] args = new StackItem[token.ParametersCount]; + for (int i = 0; i < token.ParametersCount; i++) + args[i] = Pop(); + return CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); + } + protected internal StackItem Convert(object value) { if (value is IDisposable disposable) Disposables.Add(disposable); diff --git a/src/neo/SmartContract/ExecutionContextState.cs b/src/neo/SmartContract/ExecutionContextState.cs index 0d0250803c..bbbdefbae9 100644 --- a/src/neo/SmartContract/ExecutionContextState.cs +++ b/src/neo/SmartContract/ExecutionContextState.cs @@ -12,6 +12,11 @@ public class ExecutionContextState /// public UInt160 CallingScriptHash { get; set; } + /// + /// The ContractState of the current context. + /// + public ContractState Contract { get; set; } + /// /// Execution context rights /// From 3c1811ddef7475f2faa37ab8f2c084b562b2633e Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 8 Jan 2021 13:26:08 +0800 Subject: [PATCH 2/4] Check return type in ABI --- src/neo/SmartContract/ApplicationEngine.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 5badb21c8b..d04b3f5e7f 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -106,6 +106,7 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho CallFlags callingFlags = state.CallFlags; if (args.Length != md.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {md.Parameters.Length} Arguments But Receives {args.Length} Arguments"); + if (hasReturnValue ^ (md.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException(); ExecutionContext context_new = LoadContract(contract, method, flags & callingFlags, hasReturnValue, (ushort)args.Length); state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; From f42caf38b0ba695889c79fbd36c23b07124ec06b Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 8 Jan 2021 13:31:46 +0800 Subject: [PATCH 3/4] Fix UT --- tests/neo.UnitTests/SmartContract/UT_InteropService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs index 60ff884f44..d23e289d1a 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs @@ -599,20 +599,20 @@ public void TestContract_Call() engine.LoadScript(new byte[] { 0x01 }); engine.Push(args[1]); engine.Push(args[0]); - engine.CallContract(state.Hash, method, CallFlags.All, false, (ushort)args.Count); + engine.CallContract(state.Hash, method, CallFlags.All, true, (ushort)args.Count); engine.CurrentContext.EvaluationStack.Pop().Should().Be(args[0]); engine.CurrentContext.EvaluationStack.Pop().Should().Be(args[1]); state.Manifest.Permissions[0].Methods = WildcardContainer.Create("a"); engine.Push(args[1]); engine.Push(args[0]); - Assert.ThrowsException(() => engine.CallContract(state.Hash, method, CallFlags.All, false, (ushort)args.Count)); + Assert.ThrowsException(() => engine.CallContract(state.Hash, method, CallFlags.All, true, (ushort)args.Count)); state.Manifest.Permissions[0].Methods = WildcardContainer.CreateWildcard(); engine.Push(args[1]); engine.Push(args[0]); - engine.CallContract(state.Hash, method, CallFlags.All, false, (ushort)args.Count); + engine.CallContract(state.Hash, method, CallFlags.All, true, (ushort)args.Count); engine.Push(args[1]); engine.Push(args[0]); - Assert.ThrowsException(() => engine.CallContract(UInt160.Zero, method, CallFlags.All, false, (ushort)args.Count)); + Assert.ThrowsException(() => engine.CallContract(UInt160.Zero, method, CallFlags.All, true, (ushort)args.Count)); } [TestMethod] From a165d5f67c75b42bc4c5ddc876e639669043c435 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 8 Jan 2021 18:11:47 +0800 Subject: [PATCH 4/4] Add exception message --- src/neo/SmartContract/ApplicationEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index d04b3f5e7f..23d61a876c 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -106,7 +106,7 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho CallFlags callingFlags = state.CallFlags; if (args.Length != md.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {md.Parameters.Length} Arguments But Receives {args.Length} Arguments"); - if (hasReturnValue ^ (md.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException(); + if (hasReturnValue ^ (md.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException("The return value type does not match."); ExecutionContext context_new = LoadContract(contract, method, flags & callingFlags, hasReturnValue, (ushort)args.Length); state = context_new.GetState(); state.CallingScriptHash = callingScriptHash;