From 13084541491245de6f4d96db875ea71a55bdb202 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 19 Dec 2019 06:51:51 +0100 Subject: [PATCH] CallEx implementation (#1364) --- src/neo/SmartContract/ApplicationEngine.cs | 7 +++ src/neo/SmartContract/CallFlags.cs | 17 ++++++ .../SmartContract/ExecutionContextState.cs | 5 ++ src/neo/SmartContract/Helper.cs | 4 +- src/neo/SmartContract/InteropDescriptor.cs | 12 +++-- .../SmartContract/InteropService.Binary.cs | 4 +- .../InteropService.Blockchain.cs | 12 ++--- .../SmartContract/InteropService.Contract.cs | 48 +++++++++++++---- .../SmartContract/InteropService.Crypto.cs | 4 +- .../InteropService.Enumerator.cs | 8 +-- .../SmartContract/InteropService.Iterator.cs | 10 ++-- src/neo/SmartContract/InteropService.Json.cs | 4 +- .../SmartContract/InteropService.Native.cs | 4 +- .../SmartContract/InteropService.Runtime.cs | 24 ++++----- .../SmartContract/InteropService.Storage.cs | 16 +++--- src/neo/SmartContract/InteropService.cs | 11 ++-- .../Native/ContractMethodMetadata.cs | 1 + .../SmartContract/Native/NativeContract.cs | 6 ++- .../SmartContract/UT_ApplicationEngine.cs | 2 +- .../SmartContract/UT_InteropDescriptor.cs | 3 +- .../SmartContract/UT_InteropService.cs | 53 +++++++++++++++++++ 21 files changed, 188 insertions(+), 67 deletions(-) create mode 100644 src/neo/SmartContract/CallFlags.cs diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 4439012f9c..29a8d9a414 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -60,6 +60,13 @@ protected override void LoadContext(ExecutionContext context) base.LoadContext(context); } + public ExecutionContext LoadScript(Script script, CallFlags callFlags, int rvcount = -1) + { + ExecutionContext context = LoadScript(script, rvcount); + context.GetState().CallFlags = callFlags; + return context; + } + public override void Dispose() { foreach (IDisposable disposable in disposables) diff --git a/src/neo/SmartContract/CallFlags.cs b/src/neo/SmartContract/CallFlags.cs new file mode 100644 index 0000000000..99af3e0599 --- /dev/null +++ b/src/neo/SmartContract/CallFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Neo.SmartContract +{ + [Flags] + public enum CallFlags : byte + { + None = 0, + + AllowModifyStates = 0b00000001, + AllowCall = 0b00000010, + AllowNotify = 0b00000100, + + ReadOnly = AllowCall | AllowNotify, + All = AllowModifyStates | AllowCall | AllowNotify + } +} diff --git a/src/neo/SmartContract/ExecutionContextState.cs b/src/neo/SmartContract/ExecutionContextState.cs index 929048fee1..7c5aca4231 100644 --- a/src/neo/SmartContract/ExecutionContextState.cs +++ b/src/neo/SmartContract/ExecutionContextState.cs @@ -11,5 +11,10 @@ internal class ExecutionContextState /// Calling script hash /// public UInt160 CallingScriptHash { get; set; } + + /// + /// Execution context rights + /// + public CallFlags CallFlags { get; set; } = CallFlags.All; } } diff --git a/src/neo/SmartContract/Helper.cs b/src/neo/SmartContract/Helper.cs index 1e6b29e7bb..faac766baf 100644 --- a/src/neo/SmartContract/Helper.cs +++ b/src/neo/SmartContract/Helper.cs @@ -122,8 +122,8 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap } using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) { - engine.LoadScript(verification); - engine.LoadScript(verifiable.Witnesses[i].InvocationScript); + engine.LoadScript(verification, CallFlags.ReadOnly); + engine.LoadScript(verifiable.Witnesses[i].InvocationScript, CallFlags.None); if (engine.Execute() == VMState.FAULT) return false; if (!engine.ResultStack.TryPop(out StackItem result) || !result.ToBoolean()) return false; } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 5cdcf6024a..6984eba13c 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -11,25 +11,27 @@ public class InteropDescriptor public long Price { get; } public Func PriceCalculator { get; } public TriggerType AllowedTriggers { get; } + public CallFlags RequiredCallFlags { get; } - internal InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + internal InteropDescriptor(string method, Func handler, long price, TriggerType allowedTriggers, CallFlags requiredCallFlags) + : this(method, handler, allowedTriggers, requiredCallFlags) { this.Price = price; } - internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) - : this(method, handler, allowedTriggers) + internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) + : this(method, handler, allowedTriggers, requiredCallFlags) { this.PriceCalculator = priceCalculator; } - private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers) + private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers, CallFlags requiredCallFlags) { this.Method = method; this.Hash = method.ToInteropMethodHash(); this.Handler = handler; this.AllowedTriggers = allowedTriggers; + this.RequiredCallFlags = requiredCallFlags; } public long GetPrice(EvaluationStack stack) diff --git a/src/neo/SmartContract/InteropService.Binary.cs b/src/neo/SmartContract/InteropService.Binary.cs index 450e4744c5..f2e2d22a7b 100644 --- a/src/neo/SmartContract/InteropService.Binary.cs +++ b/src/neo/SmartContract/InteropService.Binary.cs @@ -9,8 +9,8 @@ partial class InteropService { public static class Binary { - public static readonly InteropDescriptor Serialize = Register("System.Binary.Serialize", Binary_Serialize, 0_00100000, TriggerType.All); - public static readonly InteropDescriptor Deserialize = Register("System.Binary.Deserialize", Binary_Deserialize, 0_00500000, TriggerType.All); + public static readonly InteropDescriptor Serialize = Register("System.Binary.Serialize", Binary_Serialize, 0_00100000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Deserialize = Register("System.Binary.Deserialize", Binary_Deserialize, 0_00500000, TriggerType.All, CallFlags.None); private static bool Binary_Serialize(ApplicationEngine engine) { diff --git a/src/neo/SmartContract/InteropService.Blockchain.cs b/src/neo/SmartContract/InteropService.Blockchain.cs index d5cdad0769..b31aba4a32 100644 --- a/src/neo/SmartContract/InteropService.Blockchain.cs +++ b/src/neo/SmartContract/InteropService.Blockchain.cs @@ -11,12 +11,12 @@ partial class InteropService { public static class Blockchain { - public static readonly InteropDescriptor GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application); - public static readonly InteropDescriptor GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 0_02500000, TriggerType.Application); - public static readonly InteropDescriptor GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor GetTransactionFromBlock = Register("System.Blockchain.GetTransactionFromBlock", Blockchain_GetTransactionFromBlock, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor GetContract = Register("System.Blockchain.GetContract", Blockchain_GetContract, 0_01000000, TriggerType.Application); + public static readonly InteropDescriptor GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 0_02500000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 0_01000000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight, 0_01000000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetTransactionFromBlock = Register("System.Blockchain.GetTransactionFromBlock", Blockchain_GetTransactionFromBlock, 0_01000000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetContract = Register("System.Blockchain.GetContract", Blockchain_GetContract, 0_01000000, TriggerType.Application, CallFlags.None); private static bool Blockchain_GetHeight(ApplicationEngine engine) { diff --git a/src/neo/SmartContract/InteropService.Contract.cs b/src/neo/SmartContract/InteropService.Contract.cs index 0e50ea8f6d..e6df9d8aa8 100644 --- a/src/neo/SmartContract/InteropService.Contract.cs +++ b/src/neo/SmartContract/InteropService.Contract.cs @@ -3,6 +3,7 @@ using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; +using System; using System.Linq; namespace Neo.SmartContract @@ -11,11 +12,12 @@ partial class InteropService { public static class Contract { - public static readonly InteropDescriptor Create = Register("System.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application); - public static readonly InteropDescriptor Update = Register("System.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application); - public static readonly InteropDescriptor Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application); - public static readonly InteropDescriptor IsStandard = Register("System.Contract.IsStandard", Contract_IsStandard, 0_00030000, TriggerType.All); + public static readonly InteropDescriptor Create = Register("System.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application, CallFlags.AllowModifyStates); + public static readonly InteropDescriptor Update = Register("System.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application, CallFlags.AllowModifyStates); + public static readonly InteropDescriptor Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates); + public static readonly InteropDescriptor Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall); + public static readonly InteropDescriptor CallEx = Register("System.Contract.CallEx", Contract_CallEx, 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall); + public static readonly InteropDescriptor IsStandard = Register("System.Contract.IsStandard", Contract_IsStandard, 0_00030000, TriggerType.All, CallFlags.None); private static long GetDeploymentPrice(EvaluationStack stack) { @@ -112,12 +114,34 @@ private static bool Contract_Destroy(ApplicationEngine engine) private static bool Contract_Call(ApplicationEngine engine) { StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop(); + StackItem method = engine.CurrentContext.EvaluationStack.Pop(); + StackItem args = engine.CurrentContext.EvaluationStack.Pop(); - ContractState contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractHash.GetSpan())); - if (contract is null) return false; + return Contract_CallEx(engine, new UInt160(contractHash.GetSpan()), method, args, CallFlags.All); + } + private static bool Contract_CallEx(ApplicationEngine engine) + { + StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop(); StackItem method = engine.CurrentContext.EvaluationStack.Pop(); StackItem args = engine.CurrentContext.EvaluationStack.Pop(); + + if (!engine.CurrentContext.EvaluationStack.TryPop(out var flagItem)) + { + return false; + } + + CallFlags flags = (CallFlags)(int)flagItem.ToBigInteger(); + if (!Enum.IsDefined(typeof(CallFlags), flags)) return false; + + return Contract_CallEx(engine, new UInt160(contractHash.GetSpan()), method, args, flags); + } + + private static bool Contract_CallEx(ApplicationEngine engine, UInt160 contractHash, StackItem method, StackItem args, CallFlags flags) + { + ContractState contract = engine.Snapshot.Contracts.TryGet(contractHash); + if (contract is null) return false; + ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest; if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method.GetString())) @@ -132,9 +156,15 @@ private static bool Contract_Call(ApplicationEngine engine) engine.InvocationCounter[contract.ScriptHash] = 1; } - UInt160 callingScriptHash = engine.CurrentScriptHash; + ExecutionContextState state = engine.CurrentContext.GetState(); + UInt160 callingScriptHash = state.ScriptHash; + CallFlags callingFlags = state.CallFlags; + ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - context_new.GetState().CallingScriptHash = callingScriptHash; + state = context_new.GetState(); + state.CallingScriptHash = callingScriptHash; + state.CallFlags = flags & callingFlags; + context_new.EvaluationStack.Push(args); context_new.EvaluationStack.Push(method); return true; diff --git a/src/neo/SmartContract/InteropService.Crypto.cs b/src/neo/SmartContract/InteropService.Crypto.cs index f1c2f491e0..448d312ea4 100644 --- a/src/neo/SmartContract/InteropService.Crypto.cs +++ b/src/neo/SmartContract/InteropService.Crypto.cs @@ -13,8 +13,8 @@ partial class InteropService { public static class Crypto { - public static readonly InteropDescriptor ECDsaVerify = Register("Neo.Crypto.ECDsaVerify", Crypto_ECDsaVerify, 0_01000000, TriggerType.All); - public static readonly InteropDescriptor ECDsaCheckMultiSig = Register("Neo.Crypto.ECDsaCheckMultiSig", Crypto_ECDsaCheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All); + public static readonly InteropDescriptor ECDsaVerify = Register("Neo.Crypto.ECDsaVerify", Crypto_ECDsaVerify, 0_01000000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor ECDsaCheckMultiSig = Register("Neo.Crypto.ECDsaCheckMultiSig", Crypto_ECDsaCheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None); private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack) { diff --git a/src/neo/SmartContract/InteropService.Enumerator.cs b/src/neo/SmartContract/InteropService.Enumerator.cs index 143e040ba4..444dda80a2 100644 --- a/src/neo/SmartContract/InteropService.Enumerator.cs +++ b/src/neo/SmartContract/InteropService.Enumerator.cs @@ -9,10 +9,10 @@ partial class InteropService { public static class Enumerator { - public static readonly InteropDescriptor Create = Register("System.Enumerator.Create", Enumerator_Create, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Next = Register("System.Enumerator.Next", Enumerator_Next, 0_01000000, TriggerType.All); - public static readonly InteropDescriptor Value = Register("System.Enumerator.Value", Enumerator_Value, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Concat = Register("System.Enumerator.Concat", Enumerator_Concat, 0_00000400, TriggerType.All); + public static readonly InteropDescriptor Create = Register("System.Enumerator.Create", Enumerator_Create, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Next = Register("System.Enumerator.Next", Enumerator_Next, 0_01000000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Value = Register("System.Enumerator.Value", Enumerator_Value, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Concat = Register("System.Enumerator.Concat", Enumerator_Concat, 0_00000400, TriggerType.All, CallFlags.None); private static bool Enumerator_Create(ApplicationEngine engine) { diff --git a/src/neo/SmartContract/InteropService.Iterator.cs b/src/neo/SmartContract/InteropService.Iterator.cs index 0a99e2848b..590b298eb0 100644 --- a/src/neo/SmartContract/InteropService.Iterator.cs +++ b/src/neo/SmartContract/InteropService.Iterator.cs @@ -9,11 +9,11 @@ partial class InteropService { public static class Iterator { - public static readonly InteropDescriptor Create = Register("System.Iterator.Create", Iterator_Create, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Key = Register("System.Iterator.Key", Iterator_Key, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Keys = Register("System.Iterator.Keys", Iterator_Keys, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Values = Register("System.Iterator.Values", Iterator_Values, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Concat = Register("System.Iterator.Concat", Iterator_Concat, 0_00000400, TriggerType.All); + public static readonly InteropDescriptor Create = Register("System.Iterator.Create", Iterator_Create, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Key = Register("System.Iterator.Key", Iterator_Key, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Keys = Register("System.Iterator.Keys", Iterator_Keys, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Values = Register("System.Iterator.Values", Iterator_Values, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Concat = Register("System.Iterator.Concat", Iterator_Concat, 0_00000400, TriggerType.All, CallFlags.None); private static bool Iterator_Create(ApplicationEngine engine) { diff --git a/src/neo/SmartContract/InteropService.Json.cs b/src/neo/SmartContract/InteropService.Json.cs index 45e12db545..5ab72b5e1d 100644 --- a/src/neo/SmartContract/InteropService.Json.cs +++ b/src/neo/SmartContract/InteropService.Json.cs @@ -7,8 +7,8 @@ partial class InteropService { public static class Json { - public static readonly InteropDescriptor Serialize = Register("System.Json.Serialize", Json_Serialize, 0_00100000, TriggerType.All); - public static readonly InteropDescriptor Deserialize = Register("System.Json.Deserialize", Json_Deserialize, 0_00500000, TriggerType.All); + public static readonly InteropDescriptor Serialize = Register("System.Json.Serialize", Json_Serialize, 0_00100000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Deserialize = Register("System.Json.Deserialize", Json_Deserialize, 0_00500000, TriggerType.All, CallFlags.None); private static bool Json_Serialize(ApplicationEngine engine) { diff --git a/src/neo/SmartContract/InteropService.Native.cs b/src/neo/SmartContract/InteropService.Native.cs index 855a355ed5..f664c4020a 100644 --- a/src/neo/SmartContract/InteropService.Native.cs +++ b/src/neo/SmartContract/InteropService.Native.cs @@ -7,12 +7,12 @@ partial class InteropService { internal static class Native { - public static readonly InteropDescriptor Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application); + public static readonly InteropDescriptor Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application, CallFlags.AllowModifyStates); static Native() { foreach (NativeContract contract in NativeContract.Contracts) - Register(contract.ServiceName, contract.Invoke, contract.GetPrice, TriggerType.System | TriggerType.Application); + Register(contract.ServiceName, contract.Invoke, contract.GetPrice, TriggerType.System | TriggerType.Application, CallFlags.None); } private static bool Native_Deploy(ApplicationEngine engine) diff --git a/src/neo/SmartContract/InteropService.Runtime.cs b/src/neo/SmartContract/InteropService.Runtime.cs index 6a70ae844c..b1c0dec22e 100644 --- a/src/neo/SmartContract/InteropService.Runtime.cs +++ b/src/neo/SmartContract/InteropService.Runtime.cs @@ -17,18 +17,18 @@ public static class Runtime { public const int MaxNotificationSize = 1024; - public static readonly InteropDescriptor Platform = Register("System.Runtime.Platform", Runtime_Platform, 0_00000250, TriggerType.All); - public static readonly InteropDescriptor GetTrigger = Register("System.Runtime.GetTrigger", Runtime_GetTrigger, 0_00000250, TriggerType.All); - public static readonly InteropDescriptor GetTime = Register("System.Runtime.GetTime", Runtime_GetTime, 0_00000250, TriggerType.Application); - public static readonly InteropDescriptor GetScriptContainer = Register("System.Runtime.GetScriptContainer", Runtime_GetScriptContainer, 0_00000250, TriggerType.All); - public static readonly InteropDescriptor GetExecutingScriptHash = Register("System.Runtime.GetExecutingScriptHash", Runtime_GetExecutingScriptHash, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor GetCallingScriptHash = Register("System.Runtime.GetCallingScriptHash", Runtime_GetCallingScriptHash, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", Runtime_GetEntryScriptHash, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor CheckWitness = Register("System.Runtime.CheckWitness", Runtime_CheckWitness, 0_00030000, TriggerType.All); - public static readonly InteropDescriptor GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", Runtime_GetInvocationCounter, 0_00000400, TriggerType.All); - public static readonly InteropDescriptor Log = Register("System.Runtime.Log", Runtime_Log, 0_01000000, TriggerType.All); - public static readonly InteropDescriptor Notify = Register("System.Runtime.Notify", Runtime_Notify, 0_01000000, TriggerType.All); - public static readonly InteropDescriptor GetNotifications = Register("System.Runtime.GetNotifications", Runtime_GetNotifications, 0_00010000, TriggerType.All); + public static readonly InteropDescriptor Platform = Register("System.Runtime.Platform", Runtime_Platform, 0_00000250, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetTrigger = Register("System.Runtime.GetTrigger", Runtime_GetTrigger, 0_00000250, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetTime = Register("System.Runtime.GetTime", Runtime_GetTime, 0_00000250, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetScriptContainer = Register("System.Runtime.GetScriptContainer", Runtime_GetScriptContainer, 0_00000250, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetExecutingScriptHash = Register("System.Runtime.GetExecutingScriptHash", Runtime_GetExecutingScriptHash, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetCallingScriptHash = Register("System.Runtime.GetCallingScriptHash", Runtime_GetCallingScriptHash, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", Runtime_GetEntryScriptHash, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor CheckWitness = Register("System.Runtime.CheckWitness", Runtime_CheckWitness, 0_00030000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", Runtime_GetInvocationCounter, 0_00000400, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor Log = Register("System.Runtime.Log", Runtime_Log, 0_01000000, TriggerType.All, CallFlags.AllowNotify); + public static readonly InteropDescriptor Notify = Register("System.Runtime.Notify", Runtime_Notify, 0_01000000, TriggerType.All, CallFlags.AllowNotify); + public static readonly InteropDescriptor GetNotifications = Register("System.Runtime.GetNotifications", Runtime_GetNotifications, 0_00010000, TriggerType.All, CallFlags.None); private static bool CheckItemForNotification(StackItem state) { diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index ac977c69ca..ebf826b7f5 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -15,14 +15,14 @@ public static class Storage public const int MaxKeySize = 64; public const int MaxValueSize = ushort.MaxValue; - public static readonly InteropDescriptor GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application); - public static readonly InteropDescriptor GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application); - public static readonly InteropDescriptor AsReadOnly = Register("System.Storage.AsReadOnly", Storage_AsReadOnly, 0_00000400, TriggerType.Application); - public static readonly InteropDescriptor Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor Find = Register("System.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application); - public static readonly InteropDescriptor Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application); - public static readonly InteropDescriptor PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice, TriggerType.Application); - public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, 0_01000000, TriggerType.Application); + public static readonly InteropDescriptor GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor AsReadOnly = Register("System.Storage.AsReadOnly", Storage_AsReadOnly, 0_00000400, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor Find = Register("System.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application, CallFlags.None); + public static readonly InteropDescriptor Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application, CallFlags.AllowModifyStates); + public static readonly InteropDescriptor PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice, TriggerType.Application, CallFlags.AllowModifyStates); + public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates); private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) { diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 16a0525510..c87ad277ea 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -31,19 +31,22 @@ internal static bool Invoke(ApplicationEngine engine, uint method) return false; if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger)) return false; + ExecutionContextState state = engine.CurrentContext.GetState(); + if (!state.CallFlags.HasFlag(descriptor.RequiredCallFlags)) + return false; return descriptor.Handler(engine); } - private static InteropDescriptor Register(string method, Func handler, long price, TriggerType allowedTriggers) + private static InteropDescriptor Register(string method, Func handler, long price, TriggerType allowedTriggers, CallFlags requiredCallFlags) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers, requiredCallFlags); methods.Add(descriptor.Hash, descriptor); return descriptor; } - private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) + private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requiredCallFlags); methods.Add(descriptor.Hash, descriptor); return descriptor; } diff --git a/src/neo/SmartContract/Native/ContractMethodMetadata.cs b/src/neo/SmartContract/Native/ContractMethodMetadata.cs index 87dfba4a40..228252f093 100644 --- a/src/neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/neo/SmartContract/Native/ContractMethodMetadata.cs @@ -8,5 +8,6 @@ internal class ContractMethodMetadata { public Func Delegate; public long Price; + public CallFlags RequiredCallFlags; } } diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index cb2863fda2..17f053ddf8 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -58,7 +58,8 @@ protected NativeContract() methods.Add(name, new ContractMethodMetadata { Delegate = (Func)method.CreateDelegate(typeof(Func), this), - Price = attribute.Price + Price = attribute.Price, + RequiredCallFlags = attribute.SafeMethod ? CallFlags.None : CallFlags.AllowModifyStates }); } this.Manifest.Abi.Methods = descriptors.ToArray(); @@ -92,6 +93,9 @@ internal bool Invoke(ApplicationEngine engine) Array args = (Array)engine.CurrentContext.EvaluationStack.Pop(); if (!methods.TryGetValue(operation, out ContractMethodMetadata method)) return false; + ExecutionContextState state = engine.CurrentContext.GetState(); + if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) + return false; StackItem result = method.Delegate(engine, args); engine.CurrentContext.EvaluationStack.Push(result); return true; diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 3790aaf094..71e8dfe759 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -119,7 +119,7 @@ public void TestCreateDummyBlock() [TestMethod] public void TestOnSysCall() { - InteropDescriptor descriptor = new InteropDescriptor("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application); + InteropDescriptor descriptor = new InteropDescriptor("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application, CallFlags.None); TestApplicationEngine engine = new TestApplicationEngine(TriggerType.Application, null, null, 0); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; engine.LoadScript(SyscallSystemRuntimeCheckWitnessHash); diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropDescriptor.cs b/tests/neo.UnitTests/SmartContract/UT_InteropDescriptor.cs index fcccab5dc7..aec2ac3dd7 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropDescriptor.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropDescriptor.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; -using System; namespace Neo.UnitTests.SmartContract { @@ -14,7 +13,7 @@ public void TestGetMethod() string method = @"System.ExecutionEngine.GetScriptContainer"; long price = 0_00000250; TriggerType allowedTriggers = TriggerType.All; - InteropDescriptor descriptor = new InteropDescriptor(method, TestHandler, price, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, TestHandler, price, allowedTriggers, CallFlags.None); descriptor.Method.Should().Be(method); descriptor.Price.Should().Be(price); } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs index e3427aea5b..597da60adc 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs @@ -786,6 +786,59 @@ public void TestContract_Call() InteropService.Invoke(engine, InteropService.Contract.Call).Should().BeFalse(); } + [TestMethod] + public void TestContract_CallEx() + { + var snapshot = Blockchain.Singleton.GetSnapshot(); + + var state = TestUtils.GetContract(); + state.Manifest.Features = ContractFeatures.HasStorage; + snapshot.Contracts.Add(state.ScriptHash, state); + + byte[] method = Encoding.UTF8.GetBytes("method"); + byte[] args = new byte[0]; + + foreach (var flags in new CallFlags[] { CallFlags.None, CallFlags.AllowCall, CallFlags.AllowModifyStates, CallFlags.All }) + { + var engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0); + engine.LoadScript(new byte[] { 0x01 }); + + engine.CurrentContext.EvaluationStack.Push((int)CallFlags.All); + engine.CurrentContext.EvaluationStack.Push(args); + engine.CurrentContext.EvaluationStack.Push(method); + engine.CurrentContext.EvaluationStack.Push(state.ScriptHash.ToArray()); + InteropService.Invoke(engine, InteropService.Contract.CallEx).Should().BeTrue(); + engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToHexString().Should().Be(method.ToHexString()); + engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToHexString().Should().Be(args.ToHexString()); + + // Contract doesn't exists + + engine.CurrentContext.EvaluationStack.Push((int)CallFlags.All); + engine.CurrentContext.EvaluationStack.Push(args); + engine.CurrentContext.EvaluationStack.Push(method); + engine.CurrentContext.EvaluationStack.Push(UInt160.Zero.ToArray()); + InteropService.Invoke(engine, InteropService.Contract.CallEx).Should().BeFalse(); + + // Call with rights + + engine.CurrentContext.EvaluationStack.Push((int)flags); + engine.CurrentContext.EvaluationStack.Push(args); + engine.CurrentContext.EvaluationStack.Push(method); + engine.CurrentContext.EvaluationStack.Push(state.ScriptHash.ToArray()); + InteropService.Invoke(engine, InteropService.Contract.CallEx).Should().BeTrue(); + engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToHexString().Should().Be(method.ToHexString()); + engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToHexString().Should().Be(args.ToHexString()); + + // Check rights + + engine.CurrentContext.EvaluationStack.Push((int)CallFlags.All); + engine.CurrentContext.EvaluationStack.Push(args); + engine.CurrentContext.EvaluationStack.Push(method); + engine.CurrentContext.EvaluationStack.Push(state.ScriptHash.ToArray()); + InteropService.Invoke(engine, InteropService.Contract.CallEx).Should().Be(flags.HasFlag(CallFlags.AllowCall)); + } + } + [TestMethod] public void TestContract_Destroy() {