From 805d3702a1fe72275c4314f88b05daa33c8d3e98 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Mon, 16 Dec 2019 18:56:13 -0300 Subject: [PATCH 01/50] Rebase --- neo.sln | 6 + src/neo/IO/Caching/DataCache.cs | 2 +- src/neo/SmartContract/ApplicationEngine.cs | 27 +- src/neo/SmartContract/InteropDescriptor.cs | 18 + .../SmartContract/InteropService.Storage.cs | 239 ++++--- src/neo/SmartContract/InteropService.cs | 639 +++++++++++++++++- src/neo/Wallets/Wallet.cs | 4 +- .../SmartContract/UT_InteropPrices.cs | 233 ++++++- 8 files changed, 1046 insertions(+), 122 deletions(-) diff --git a/neo.sln b/neo.sln index 79e13f0185..768576c8c0 100644 --- a/neo.sln +++ b/neo.sln @@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B5339DF7-5D1 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDE05FA8-8E73-4924-BC63-DD117127EEE1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo-vm", "..\neo-vm\src\neo-vm\neo-vm.csproj", "{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,6 +26,10 @@ Global {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.Build.0 = Release|Any CPU + {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/neo/IO/Caching/DataCache.cs b/src/neo/IO/Caching/DataCache.cs index 1665c8f238..51da1db9a6 100644 --- a/src/neo/IO/Caching/DataCache.cs +++ b/src/neo/IO/Caching/DataCache.cs @@ -260,7 +260,7 @@ public TValue GetOrAdd(TKey key, Func factory) } } - public TValue TryGet(TKey key) + public virtual TValue TryGet(TKey key) { lock (dictionary) { diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 29a8d9a414..61d1c2e8f4 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -1,6 +1,7 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; using System; @@ -19,6 +20,8 @@ public partial class ApplicationEngine : ExecutionEngine private readonly bool testMode; private readonly List notifications = new List(); private readonly List disposables = new List(); + private readonly List updatedKeys = new List(); + private long maxConsumedGas = 0; public TriggerType Trigger { get; } public IVerifiable ScriptContainer { get; } @@ -45,12 +48,34 @@ internal T AddDisposable(T disposable) where T : IDisposable return disposable; } + internal bool TryAddUpdatedKey(byte[] key) + { + bool keyAdded = false; + if (!updatedKeys.Contains(key)) + { + updatedKeys.Add(key); + keyAdded = true; + } + return keyAdded; + } + private bool AddGas(long gas) { + if (gas < 0 && GasConsumed > maxConsumedGas) + maxConsumedGas = GasConsumed; GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; } + /// + /// Recalculate the property GasConsumed to use the gas required to run the whole transaction. + /// + private void RecalculateConsumedGas() + { + if (maxConsumedGas > GasConsumed) + GasConsumed = maxConsumedGas; + } + protected override void LoadContext(ExecutionContext context) { // Set default execution context state @@ -77,7 +102,7 @@ public override void Dispose() protected override bool OnSysCall(uint method) { - if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack))) + if (!AddGas(InteropService.GetPrice(method, this))) return false; return InteropService.Invoke(this, method); } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 6984eba13c..6bb59c3e7c 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -10,6 +10,8 @@ public class InteropDescriptor internal Func Handler { get; } public long Price { get; } public Func PriceCalculator { get; } + public Func StoragePriceCalculator { get; } + public bool IsStateful { get; } public TriggerType AllowedTriggers { get; } public CallFlags RequiredCallFlags { get; } @@ -34,8 +36,24 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } + public long GetPrice() + { + return Price; + } + + public long GetPrice(ApplicationEngine applicationEngine) + { + return StoragePriceCalculator is null ? Price : StoragePriceCalculator(applicationEngine); + } + public long GetPrice(EvaluationStack stack) { +#if DEBUG + if (IsStateful) + { + throw new InvalidOperationException(); + } +#endif return PriceCalculator is null ? Price : PriceCalculator(stack); } diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index ebf826b7f5..edb3ec3fec 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -12,6 +12,8 @@ partial class InteropService public static class Storage { public const long GasPerByte = 100000; + public const long GasPerReusedByte = -GasPerByte; + public const long GasPerReleasedByte = -GasPerByte; public const int MaxKeySize = 64; public const int MaxValueSize = ushort.MaxValue; @@ -32,149 +34,174 @@ private static bool CheckStorageContext(ApplicationEngine engine, StorageContext return true; } - private static long GetStoragePrice(EvaluationStack stack) + private static long GetStoragePrice(ApplicationEngine engine) { - return (stack.Peek(1).GetByteLength() + stack.Peek(2).GetByteLength()) * GasPerByte; - } - - private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) - { - if (key.Length > MaxKeySize) return false; - if (value.Length > MaxValueSize) return false; - if (context.IsReadOnly) return false; - if (!CheckStorageContext(engine, context)) return false; - + var stack = engine.CurrentContext.EvaluationStack; + var key = stack.Peek(1); + var newDataSize = stack.Peek(2).GetByteLength(); StorageKey skey = new StorageKey { - ScriptHash = context.ScriptHash, - Key = key + ScriptHash = engine.CurrentScriptHash, + Key = key.GetSpan().ToArray() }; - if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; + var skeyValue = engine.Snapshot.Storages.TryGet(skey); + + if (skeyValue == null || !engine.TryAddUpdatedKey(skey.Key)) + return (key.GetByteLength() + newDataSize) * GasPerByte; + + var currentOccupiedBytes = skeyValue.Value.Length; - if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) + if (newDataSize <= currentOccupiedBytes) { - // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) - engine.Snapshot.Storages.Delete(skey); + var releasedBytes = currentOccupiedBytes - newDataSize; + return releasedBytes * GasPerReleasedByte; } else { - StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); - item.Value = value; - item.IsConstant = flags.HasFlag(StorageFlags.Constant); + var reusedBytes = currentOccupiedBytes; + return (newDataSize - reusedBytes) * GasPerByte; } - return true; } - private static bool Storage_GetContext(ApplicationEngine engine) + private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) + { + if (key.Length > MaxKeySize) return false; + if (value.Length > MaxValueSize) return false; + if (context.IsReadOnly) return false; + if (!CheckStorageContext(engine, context)) return false; + + StorageKey skey = new StorageKey { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - { - ScriptHash = engine.CurrentScriptHash, - IsReadOnly = false - })); - return true; - } + ScriptHash = context.ScriptHash, + Key = key + }; + + if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; - private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) + if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - { - ScriptHash = engine.CurrentScriptHash, - IsReadOnly = true - })); - return true; + // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) + engine.Snapshot.Storages.Delete(skey); } - - private static bool Storage_AsReadOnly(ApplicationEngine engine) + else { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (!context.IsReadOnly) - context = new StorageContext - { - ScriptHash = context.ScriptHash, - IsReadOnly = true - }; - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); - return true; - } - return false; + StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); + item.Value = value; + item.IsConstant = flags.HasFlag(StorageFlags.Constant); } + return true; + } - private static bool Storage_Get(ApplicationEngine engine) + private static bool Storage_GetContext(ApplicationEngine engine) + { + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (!CheckStorageContext(engine, context)) return false; - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey - { - ScriptHash = context.ScriptHash, - Key = key - }); - engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); - return true; - } - return false; - } + ScriptHash = engine.CurrentScriptHash, + IsReadOnly = false + })); + return true; + } - private static bool Storage_Find(ApplicationEngine engine) + private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) + { + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (!CheckStorageContext(engine, context)) return false; - byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix); - StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator())); - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); - return true; - } - return false; + ScriptHash = engine.CurrentScriptHash, + IsReadOnly = true + })); + return true; + } + + private static bool Storage_AsReadOnly(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (!context.IsReadOnly) + context = new StorageContext + { + ScriptHash = context.ScriptHash, + IsReadOnly = true + }; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); + return true; } + return false; + } - private static bool Storage_Put(ApplicationEngine engine) + private static bool Storage_Get(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - return false; StorageContext context = _interface.GetInterface(); + if (!CheckStorageContext(engine, context)) return false; byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - return PutExInternal(engine, context, key, value, StorageFlags.None); + StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey + { + ScriptHash = context.ScriptHash, + Key = key + }); + engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); + return true; } + return false; + } - private static bool Storage_PutEx(ApplicationEngine engine) + private static bool Storage_Find(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - return false; StorageContext context = _interface.GetInterface(); - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - return PutExInternal(engine, context, key, value, flags); + if (!CheckStorageContext(engine, context)) return false; + byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix); + StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator())); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); + return true; } + return false; + } - private static bool Storage_Delete(ApplicationEngine engine) + private static bool Storage_Put(ApplicationEngine engine) + { + if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + return false; + StorageContext context = _interface.GetInterface(); + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + return PutExInternal(engine, context, key, value, StorageFlags.None); + } + + private static bool Storage_PutEx(ApplicationEngine engine) + { + if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + return false; + StorageContext context = _interface.GetInterface(); + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + return PutExInternal(engine, context, key, value, flags); + } + + private static bool Storage_Delete(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + StorageContext context = _interface.GetInterface(); + if (context.IsReadOnly) return false; + if (!CheckStorageContext(engine, context)) return false; + StorageKey key = new StorageKey { - StorageContext context = _interface.GetInterface(); - if (context.IsReadOnly) return false; - if (!CheckStorageContext(engine, context)) return false; - StorageKey key = new StorageKey - { - ScriptHash = context.ScriptHash, - Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() - }; - if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; - engine.Snapshot.Storages.Delete(key); - return true; - } - return false; + ScriptHash = context.ScriptHash, + Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() + }; + if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; + engine.Snapshot.Storages.Delete(key); + return true; } + return false; } } } +} diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index c87ad277ea..7ceb8193ca 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -15,14 +15,21 @@ static InteropService() t.GetFields()[0].GetValue(null); } + public static long GetPrice(uint hash) + { + return methods[hash].GetPrice(); + } + public static long GetPrice(uint hash, EvaluationStack stack) { return methods[hash].GetPrice(stack); } - public static IEnumerable SupportedMethods() + + public static long GetPrice(uint hash, ApplicationEngine applicationEngine) { - return methods.Values; + var interopDescriptor = methods[hash]; + return interopDescriptor.IsStateful ? interopDescriptor.GetPrice(applicationEngine) : interopDescriptor.GetPrice(applicationEngine.CurrentContext.EvaluationStack); } internal static bool Invoke(ApplicationEngine engine, uint method) @@ -50,5 +57,633 @@ private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) + { + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + methods.Add(descriptor.Hash, descriptor); + return descriptor; + } } + + // public const long GasPerByte = 100000; + // public const long GasPerReusedByte = -GasPerByte; + // public const long GasPerReleasedByte = -GasPerByte; + // public const int MaxStorageKeySize = 64; + // public const int MaxStorageValueSize = ushort.MaxValue; + // public const int MaxNotificationSize = 1024; + + // public static readonly uint System_ExecutionEngine_GetScriptContainer = Register("System.ExecutionEngine.GetScriptContainer", ExecutionEngine_GetScriptContainer, 0_00000250, TriggerType.All); + // public static readonly uint System_ExecutionEngine_GetExecutingScriptHash = Register("System.ExecutionEngine.GetExecutingScriptHash", ExecutionEngine_GetExecutingScriptHash, 0_00000400, TriggerType.All); + // public static readonly uint System_ExecutionEngine_GetCallingScriptHash = Register("System.ExecutionEngine.GetCallingScriptHash", ExecutionEngine_GetCallingScriptHash, 0_00000400, TriggerType.All); + // public static readonly uint System_ExecutionEngine_GetEntryScriptHash = Register("System.ExecutionEngine.GetEntryScriptHash", ExecutionEngine_GetEntryScriptHash, 0_00000400, TriggerType.All); + // public static readonly uint System_Runtime_Platform = Register("System.Runtime.Platform", Runtime_Platform, 0_00000250, TriggerType.All); + // public static readonly uint System_Runtime_GetTrigger = Register("System.Runtime.GetTrigger", Runtime_GetTrigger, 0_00000250, TriggerType.All); + // public static readonly uint System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", Runtime_CheckWitness, 0_00030000, TriggerType.All); + // public static readonly uint System_Runtime_Notify = Register("System.Runtime.Notify", Runtime_Notify, 0_01000000, TriggerType.All); + // public static readonly uint System_Runtime_Log = Register("System.Runtime.Log", Runtime_Log, 0_01000000, TriggerType.All); + // public static readonly uint System_Runtime_GetTime = Register("System.Runtime.GetTime", Runtime_GetTime, 0_00000250, TriggerType.Application); + // public static readonly uint System_Runtime_Serialize = Register("System.Runtime.Serialize", Runtime_Serialize, 0_00100000, TriggerType.All); + // public static readonly uint System_Runtime_Deserialize = Register("System.Runtime.Deserialize", Runtime_Deserialize, 0_00500000, TriggerType.All); + // public static readonly uint System_Runtime_GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", Runtime_GetInvocationCounter, 0_00000400, TriggerType.All); + // public static readonly uint System_Runtime_GetNotifications = Register("System.Runtime.GetNotifications", Runtime_GetNotifications, 0_00010000, TriggerType.All); + // public static readonly uint System_Blockchain_GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application); + // public static readonly uint System_Blockchain_GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 0_02500000, TriggerType.Application); + // public static readonly uint System_Blockchain_GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 0_01000000, TriggerType.Application); + // public static readonly uint System_Blockchain_GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight, 0_01000000, TriggerType.Application); + // public static readonly uint System_Blockchain_GetTransactionFromBlock = Register("System.Blockchain.GetTransactionFromBlock", Blockchain_GetTransactionFromBlock, 0_01000000, TriggerType.Application); + // public static readonly uint System_Blockchain_GetContract = Register("System.Blockchain.GetContract", Blockchain_GetContract, 0_01000000, TriggerType.Application); + // public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application); + // public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application); + // public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application); + // public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application); + // public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application); + // public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application); + // public static readonly uint System_Storage_PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice, TriggerType.Application); + // public static readonly uint System_Storage_Delete = Register("System.Storage.Delete", Storage_Delete, GetDeleteStoragePrice, TriggerType.Application); + // public static readonly uint System_StorageContext_AsReadOnly = Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly, 0_00000400, TriggerType.Application); + + // private static bool CheckItemForNotification(StackItem state) + // { + // int size = 0; + // List items_checked = new List(); + // Queue items_unchecked = new Queue(); + // while (true) + // { + // switch (state) + // { + // case Struct array: + // foreach (StackItem item in array) + // items_unchecked.Enqueue(item); + // break; + // case Array array: + // if (items_checked.All(p => !ReferenceEquals(p, array))) + // { + // items_checked.Add(array); + // foreach (StackItem item in array) + // items_unchecked.Enqueue(item); + // } + // break; + // case PrimitiveType primitive: + // size += primitive.GetByteLength(); + // break; + // case Null _: + // break; + // case InteropInterface _: + // return false; + // case Map map: + // if (items_checked.All(p => !ReferenceEquals(p, map))) + // { + // items_checked.Add(map); + // foreach (var pair in map) + // { + // size += pair.Key.GetByteLength(); + // items_unchecked.Enqueue(pair.Value); + // } + // } + // break; + // } + // if (size > MaxNotificationSize) return false; + // if (items_unchecked.Count == 0) return true; + // state = items_unchecked.Dequeue(); + // } + // } + + // private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) + // { + // foreach (Type t in typeof(InteropService).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + // t.GetFields()[0].GetValue(null); + // } + + // public static long GetPrice(uint hash) + // { + // return methods[hash].GetPrice(); + // } + + // public static long GetPrice(uint hash, EvaluationStack stack) + // { + // return methods[hash].GetPrice(stack); + // } + + // public static IEnumerable SupportedMethods() + // { + // return methods.Values; + // } + // public static long GetPrice(uint hash, ApplicationEngine applicationEngine) + // { + // var interopDescriptor = methods[hash]; + // return interopDescriptor.IsStateful ? interopDescriptor.GetPrice(applicationEngine) : interopDescriptor.GetPrice(applicationEngine.CurrentContext.EvaluationStack); + // } + + // public static Dictionary SupportedMethods() + // { + // return methods.ToDictionary(p => p.Key, p => p.Value.Method); + // } + + // private static long GetDeleteStoragePrice(ApplicationEngine engine) + // { + // var stack = engine.CurrentContext.EvaluationStack; + // var key = stack.Peek(1); + // StorageKey skey = new StorageKey + // { + // ScriptHash = engine.CurrentScriptHash, + // Key = key.GetSpan().ToArray() + // }; + + // var skeyValue = engine.Snapshot.Storages.TryGet(skey); + + // if (skey == null || !engine.TryAddUpdatedKey(key.GetSpan().ToArray())) + // return 0_01000000; + + // return skeyValue.Value.Length * GasPerReleasedByte; + // } + + // private static long GetStoragePrice(ApplicationEngine engine) + // { + // var stack = engine.CurrentContext.EvaluationStack; + // var key = stack.Peek(1); + // var newDataSize = stack.Peek(2).GetByteLength(); + // StorageKey skey = new StorageKey + // { + // ScriptHash = engine.CurrentScriptHash, + // Key = key.GetSpan().ToArray() + // }; + + // var skeyValue = engine.Snapshot.Storages.TryGet(skey); + + // if (skeyValue == null || !engine.TryAddUpdatedKey(skey.Key)) + // return (key.GetByteLength() + newDataSize) * GasPerByte; + + // var currentOccupiedBytes = skeyValue.Value.Length; + + // if (newDataSize <= currentOccupiedBytes) + // { + // var releasedBytes = currentOccupiedBytes - newDataSize; + // return releasedBytes * GasPerReleasedByte; + // } + // else + // { + // var reusedBytes = currentOccupiedBytes; + // return (newDataSize - reusedBytes) * GasPerByte; + // } + // } + + // private static uint Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) + // { + // InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + // methods.Add(descriptor.Hash, descriptor); + // return descriptor.Hash; + // } + + // private static bool ExecutionEngine_GetScriptContainer(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push( + // engine.ScriptContainer is IInteroperable value ? value.ToStackItem(engine.ReferenceCounter) : + // StackItem.FromInterface(engine.ScriptContainer)); + // return true; + // } + + // private static bool ExecutionEngine_GetExecutingScriptHash(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(engine.CurrentScriptHash.ToArray()); + // return true; + // } + + // private static bool ExecutionEngine_GetCallingScriptHash(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(engine.CallingScriptHash?.ToArray() ?? StackItem.Null); + // return true; + // } + + // private static bool ExecutionEngine_GetEntryScriptHash(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(engine.EntryScriptHash.ToArray()); + // return true; + // } + + // private static bool Runtime_Platform(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(Encoding.ASCII.GetBytes("NEO")); + // return true; + // } + + // private static bool Runtime_GetTrigger(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push((int)engine.Trigger); + // return true; + // } + + // internal static bool CheckWitness(ApplicationEngine engine, UInt160 hash) + // { + // if (engine.ScriptContainer is Transaction tx) + // { + // Cosigner usage = tx.Cosigners.FirstOrDefault(p => p.Account.Equals(hash)); + // if (usage is null) return false; + // if (usage.Scopes == WitnessScope.Global) return true; + // if (usage.Scopes.HasFlag(WitnessScope.CalledByEntry)) + // { + // if (engine.CallingScriptHash == engine.EntryScriptHash) + // return true; + // } + // if (usage.Scopes.HasFlag(WitnessScope.CustomContracts)) + // { + // if (usage.AllowedContracts.Contains(engine.CurrentScriptHash)) + // return true; + // } + // if (usage.Scopes.HasFlag(WitnessScope.CustomGroups)) + // { + // var contract = engine.Snapshot.Contracts[engine.CallingScriptHash]; + // // check if current group is the required one + // if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(usage.AllowedGroups).Any()) + // return true; + // } + // return false; + // } + + // // only for non-Transaction types (Block, etc) + + // var hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); + // return hashes_for_verifying.Contains(hash); + // } + + // private static bool CheckWitness(ApplicationEngine engine, ECPoint pubkey) + // { + // return CheckWitness(engine, Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); + // } + + // private static bool Runtime_CheckWitness(ApplicationEngine engine) + // { + // ReadOnlySpan hashOrPubkey = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // bool result; + // if (hashOrPubkey.Length == 20) + // result = CheckWitness(engine, new UInt160(hashOrPubkey)); + // else if (hashOrPubkey.Length == 33) + // result = CheckWitness(engine, ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)); + // else + // return false; + // engine.CurrentContext.EvaluationStack.Push(result); + // return true; + // } + + // private static bool Runtime_Notify(ApplicationEngine engine) + // { + // StackItem state = engine.CurrentContext.EvaluationStack.Pop(); + // if (!CheckItemForNotification(state)) return false; + // engine.SendNotification(engine.CurrentScriptHash, state); + // return true; + // } + + // private static bool Runtime_Log(ApplicationEngine engine) + // { + // ReadOnlySpan state = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // if (state.Length > MaxNotificationSize) return false; + // string message = Encoding.UTF8.GetString(state); + // engine.SendLog(engine.CurrentScriptHash, message); + // return true; + // } + + // private static bool Runtime_GetTime(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(engine.Snapshot.PersistingBlock.Timestamp); + // return true; + // } + + // private static bool Runtime_Serialize(ApplicationEngine engine) + // { + // byte[] serialized; + // try + // { + // serialized = StackItemSerializer.Serialize(engine.CurrentContext.EvaluationStack.Pop(), engine.MaxItemSize); + // } + // catch + // { + // return false; + // } + // engine.CurrentContext.EvaluationStack.Push(serialized); + // return true; + // } + + // private static bool Runtime_GetNotifications(ApplicationEngine engine) + // { + // StackItem item = engine.CurrentContext.EvaluationStack.Pop(); + + // IEnumerable notifications = engine.Notifications; + // if (!item.IsNull) // must filter by scriptHash + // { + // var hash = new UInt160(item.GetSpan()); + // notifications = notifications.Where(p => p.ScriptHash == hash); + // } + + // if (notifications.Count() > engine.MaxStackSize) return false; + // engine.Push(new Array(engine.ReferenceCounter, notifications.Select(u => new Array(engine.ReferenceCounter, new[] { u.ScriptHash.ToArray(), u.State })))); + // return true; + // } + + // private static bool Runtime_GetInvocationCounter(ApplicationEngine engine) + // { + // if (!engine.InvocationCounter.TryGetValue(engine.CurrentScriptHash, out var counter)) + // { + // return false; + // } + + // engine.CurrentContext.EvaluationStack.Push(counter); + // return true; + // } + + // private static bool Runtime_Deserialize(ApplicationEngine engine) + // { + // StackItem item; + // try + // { + // item = StackItemSerializer.Deserialize(engine.CurrentContext.EvaluationStack.Pop().GetSpan(), engine.MaxItemSize, engine.ReferenceCounter); + // } + // catch (FormatException) + // { + // return false; + // } + // catch (IOException) + // { + // return false; + // } + // engine.CurrentContext.EvaluationStack.Push(item); + // return true; + // } + + // private static bool Blockchain_GetHeight(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(engine.Snapshot.Height); + // return true; + // } + + // private static bool Blockchain_GetBlock(ApplicationEngine engine) + // { + // ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // UInt256 hash; + // if (data.Length <= 5) + // hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); + // else if (data.Length == 32) + // hash = new UInt256(data); + // else + // return false; + + // Block block = hash != null ? engine.Snapshot.GetBlock(hash) : null; + // if (block == null) + // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + // else + // engine.CurrentContext.EvaluationStack.Push(block.ToStackItem(engine.ReferenceCounter)); + // return true; + // } + + // private static bool Blockchain_GetTransaction(ApplicationEngine engine) + // { + // ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // Transaction tx = engine.Snapshot.GetTransaction(new UInt256(hash)); + // if (tx == null) + // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + // else + // engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); + // return true; + // } + + // private static bool Blockchain_GetTransactionHeight(ApplicationEngine engine) + // { + // ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // var tx = engine.Snapshot.Transactions.TryGet(new UInt256(hash)); + // engine.CurrentContext.EvaluationStack.Push(tx != null ? new BigInteger(tx.BlockIndex) : BigInteger.MinusOne); + // return true; + // } + + // private static bool Blockchain_GetTransactionFromBlock(ApplicationEngine engine) + // { + // ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); + // UInt256 hash; + // if (data.Length <= 5) + // hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); + // else if (data.Length == 32) + // hash = new UInt256(data); + // else + // return false; + + // TrimmedBlock block = hash != null ? engine.Snapshot.Blocks.TryGet(hash) : null; + // if (block == null) + // { + // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + // } + // else + // { + // int index = (int)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + // if (index < 0 || index >= block.Hashes.Length - 1) return false; + + // Transaction tx = engine.Snapshot.GetTransaction(block.Hashes[index + 1]); + // if (tx == null) + // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + // else + // engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); + // } + // return true; + // } + + // private static bool Blockchain_GetContract(ApplicationEngine engine) + // { + // UInt160 hash = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetSpan()); + // ContractState contract = engine.Snapshot.Contracts.TryGet(hash); + // if (contract == null) + // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + // else + // engine.CurrentContext.EvaluationStack.Push(contract.ToStackItem(engine.ReferenceCounter)); + // return true; + // } + + // private static bool Storage_GetContext(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + // { + // ScriptHash = engine.CurrentScriptHash, + // IsReadOnly = false + // })); + // return true; + // } + + // private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) + // { + // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + // { + // ScriptHash = engine.CurrentScriptHash, + // IsReadOnly = true + // })); + // return true; + // } + + // private static bool Storage_Get(ApplicationEngine engine) + // { + // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + // { + // StorageContext context = _interface.GetInterface(); + // if (!CheckStorageContext(engine, context)) return false; + // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + // StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey + // { + // ScriptHash = context.ScriptHash, + // Key = key + // }); + // engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); + // return true; + // } + // return false; + // } + + // private static bool StorageContext_AsReadOnly(ApplicationEngine engine) + // { + // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + // { + // StorageContext context = _interface.GetInterface(); + // if (!context.IsReadOnly) + // context = new StorageContext + // { + // ScriptHash = context.ScriptHash, + // IsReadOnly = true + // }; + // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); + // return true; + // } + // return false; + // } + + // private static bool Contract_Call(ApplicationEngine engine) + // { + // StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop(); + + // ContractState contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractHash.GetSpan())); + // if (contract is null) return false; + + // 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; + + // if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter)) + // { + // engine.InvocationCounter[contract.ScriptHash] = counter + 1; + // } + // else + // { + // engine.InvocationCounter[contract.ScriptHash] = 1; + // } + + // ExecutionContext context_new = engine.LoadScript(contract.Script, 1); + // context_new.EvaluationStack.Push(args); + // context_new.EvaluationStack.Push(method); + // return true; + // } + + // private static bool Contract_Destroy(ApplicationEngine engine) + // { + // UInt160 hash = engine.CurrentScriptHash; + // ContractState contract = engine.Snapshot.Contracts.TryGet(hash); + // if (contract == null) return true; + // engine.Snapshot.Contracts.Delete(hash); + // if (contract.HasStorage) + // foreach (var pair in engine.Snapshot.Storages.Find(hash.ToArray())) + // engine.Snapshot.Storages.Delete(pair.Key); + // return true; + // } + + // private static bool PutEx(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) + // { + // if (key.Length > MaxStorageKeySize) return false; + // if (value.Length > MaxStorageValueSize) return false; + // if (context.IsReadOnly) return false; + // if (!CheckStorageContext(engine, context)) return false; + + // StorageKey skey = new StorageKey + // { + // ScriptHash = context.ScriptHash, + // Key = key + // }; + + // var skeyValue = engine.Snapshot.Storages.TryGet(skey); + // if (skeyValue?.IsConstant == true) return false; + + // if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) + // { + // // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) + // engine.Snapshot.Storages.Delete(skey); + // } + // else + // { + // StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); + // item.Value = value; + // item.IsConstant = flags.HasFlag(StorageFlags.Constant); + // } + + // return true; + // } + + // private static bool Storage_Put(ApplicationEngine engine) + // { + // if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + // return false; + // StorageContext context = _interface.GetInterface(); + // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + // byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + // return PutEx(engine, context, key, value, StorageFlags.None); + // } + + // private static bool Storage_PutEx(ApplicationEngine engine) + // { + // if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + // return false; + // StorageContext context = _interface.GetInterface(); + // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + // byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + // StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + // return PutEx(engine, context, key, value, flags); + // } + + // private static bool Storage_Delete(ApplicationEngine engine) + // { + // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + // { + // StorageContext context = _interface.GetInterface(); + // if (context.IsReadOnly) return false; + // if (!CheckStorageContext(engine, context)) return false; + // StorageKey key = new StorageKey + // { + // ScriptHash = context.ScriptHash, + // Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() + // }; + + // var value = engine.Snapshot.Storages.TryGet(key); + // if (value?.IsConstant == true) return false; + // engine.Snapshot.Storages.Delete(key); + // return true; + // } + // return false; + //>>>>>>> Storage payback + + // internal static bool Invoke(ApplicationEngine engine, uint method) + // { + // if (!methods.TryGetValue(method, out InteropDescriptor descriptor)) + // return false; + // if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger)) + // return false; + // return descriptor.Handler(engine); + // } + + // private static InteropDescriptor Register(string method, Func handler, long price, TriggerType allowedTriggers) + // { + // InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers); + // methods.Add(descriptor.Hash, descriptor); + // return descriptor; + // } + + // private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) + // { + // InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + // methods.Add(descriptor.Hash, descriptor); + // return descriptor; + // } + } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 1752372bec..015e2a72b9 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -354,7 +354,7 @@ public static long CalculateNetWorkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { @@ -366,7 +366,7 @@ public static long CalculateNetWorkFee(byte[] witness_script, ref int size) networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; using (ScriptBuilder sb = new ScriptBuilder()) networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null) * n; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify) * n; } else { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 19eb7a5d38..63b97fb135 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -1,6 +1,10 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using Neo.VM; namespace Neo.UnitTests.SmartContract @@ -8,6 +12,12 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_InteropPrices { + [TestInitialize] + public void Initialize() + { + TestBlockchain.InitializeMockNeoSystem(); + } + [TestMethod] public void ApplicationEngineFixedPrices() { @@ -50,29 +60,232 @@ public void ApplicationEngineVariablePrices() InteropService.GetPrice(InteropService.Contract.Create, ae.CurrentContext.EvaluationStack).Should().Be(0_00300000L); } - // System.Storage.Put: e63f1884 (requires push key and value) - byte[] SyscallStoragePutHash = new byte[] { (byte)OpCode.PUSH3, (byte)OpCode.PUSH3, (byte)OpCode.PUSH0, (byte)OpCode.SYSCALL, 0xe6, 0x3f, 0x18, 0x84 }; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) + var mockedStoreView = new Mock(); + + var manifest = ContractManifest.CreateDefault(UInt160.Zero); + manifest.Features = ContractFeatures.HasStorage; + + var scriptBuilder = new ScriptBuilder(); + var key = new byte[] { (byte)OpCode.PUSH3 }; + var value = new byte[] { (byte)OpCode.PUSH3 }; + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + byte[] script = scriptBuilder.ToArray(); + + ContractState contractState = new ContractState + { + Script = script, + Manifest = manifest + }; + + StorageKey skey = new StorageKey + { + ScriptHash = script.ToScriptHash(), + Key = key + }; + + StorageItem sItem = null; + + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + byte[] scriptPut = script; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { Debugger debugger = new Debugger(ae); - ae.LoadScript(SyscallStoragePutHash); + ae.LoadScript(scriptPut); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack).Should().Be(200000L); + InteropService.GetPrice(InteropService.Storage.Put, ae).Should().Be(200000L); } - // System.Storage.PutEx: 73e19b3a (requires push key and value) - byte[] SyscallStoragePutExHash = new byte[] { (byte)OpCode.PUSH3, (byte)OpCode.PUSH3, (byte)OpCode.PUSH0, (byte)OpCode.SYSCALL, 0x73, 0xe1, 0x9b, 0x3a }; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) + scriptBuilder = new ScriptBuilder(); + key = new byte[] { (byte)OpCode.PUSH3 }; + value = new byte[] { (byte)OpCode.PUSH3 }; + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.PutEx); + script = scriptBuilder.ToArray(); + + byte[] scriptPutEx = script; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { Debugger debugger = new Debugger(ae); - ae.LoadScript(SyscallStoragePutExHash); + ae.LoadScript(scriptPut); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.PutEx, ae.CurrentContext.EvaluationStack).Should().Be(200000L); + InteropService.GetPrice(InteropService.Storage.PutEx, ae).Should().Be(200000L); + } + } + + [TestMethod] + public void ApplicationEngineRegularPut() + { + var scriptBuilder = new ScriptBuilder(); + + var key = new byte[] { (byte)OpCode.PUSH1 }; + var value = new byte[] { (byte)OpCode.PUSH1 }; + + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + + var mockedStoreView = new Mock(); + + byte[] script = scriptBuilder.ToArray(); + + var manifest = ContractManifest.CreateDefault(UInt160.Zero); + manifest.Features = ContractFeatures.HasStorage; + + ContractState contractState = new ContractState + { + Script = script, + Manifest = manifest + }; + + StorageKey skey = new StorageKey + { + ScriptHash = script.ToScriptHash(), + Key = key + }; + + StorageItem sItem = null; + + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + { + Debugger debugger = new Debugger(ae); + ae.LoadScript(script); + debugger.StepInto(); + debugger.StepInto(); + debugger.StepInto(); + var setupPrice = ae.GasConsumed; + var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); + debugger.StepInto(); + var expectedCost = defaultDataPrice + setupPrice; + debugger.StepInto(); + ae.GasConsumed.Should().Be(expectedCost); + } + } + + [TestMethod] + public void ApplicationEngineReusedStorage_FullReuse() + { + var scriptBuilder = new ScriptBuilder(); + + var key = new byte[] { (byte)OpCode.PUSH1 }; + var value = new byte[] { (byte)OpCode.PUSH1 }; + + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + + var mockedStoreView = new Mock(); + + byte[] script = scriptBuilder.ToArray(); + + var manifest = ContractManifest.CreateDefault(UInt160.Zero); + manifest.Features = ContractFeatures.HasStorage; + + ContractState contractState = new ContractState + { + Script = script, + Manifest = manifest + }; + + StorageKey skey = new StorageKey + { + ScriptHash = script.ToScriptHash(), + Key = key + }; + + StorageItem sItem = new StorageItem + { + Value = value + }; + + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + using (ApplicationEngine applicationEngine = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + { + Debugger debugger = new Debugger(applicationEngine); + applicationEngine.LoadScript(script); + debugger.StepInto(); + debugger.StepInto(); + debugger.StepInto(); + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine); + reusedDataPrice.Should().Be(0); + } + } + + [TestMethod] + public void ApplicationEngineReusedStorage_PartialReuse() + { + var scriptBuilder = new ScriptBuilder(); + + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; + + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + + var mockedStoreView = new Mock(); + + byte[] script = scriptBuilder.ToArray(); + + var manifest = ContractManifest.CreateDefault(UInt160.Zero); + manifest.Features = ContractFeatures.HasStorage; + + ContractState contractState = new ContractState + { + Script = script, + Manifest = manifest + }; + + StorageKey skey = new StorageKey + { + ScriptHash = script.ToScriptHash(), + Key = key + }; + + StorageItem sItem = new StorageItem + { + Value = oldValue + }; + + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + { + Debugger debugger = new Debugger(ae); + ae.LoadScript(script); + debugger.StepInto(); + debugger.StepInto(); + debugger.StepInto(); + var setupPrice = ae.GasConsumed; + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); + debugger.StepInto(); + var expectedCost = reusedDataPrice + setupPrice; + debugger.StepInto(); + ae.GasConsumed.Should().Be(expectedCost); } } } + } From 21ced64da8b25ec88b650b1486cf2679f9ceda36 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 17 Dec 2019 04:43:48 -0300 Subject: [PATCH 02/50] Refactor, more tests --- src/neo/SmartContract/ApplicationEngine.cs | 16 +- .../SmartContract/InteropService.Storage.cs | 244 +++---- src/neo/SmartContract/InteropService.cs | 622 ------------------ src/neo/Wallets/Wallet.cs | 1 + .../SmartContract/UT_InteropPrices.cs | 293 ++++++--- tests/neo.UnitTests/TestUtils.cs | 27 + 6 files changed, 356 insertions(+), 847 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 61d1c2e8f4..059cf66c67 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -20,8 +20,7 @@ public partial class ApplicationEngine : ExecutionEngine private readonly bool testMode; private readonly List notifications = new List(); private readonly List disposables = new List(); - private readonly List updatedKeys = new List(); - private long maxConsumedGas = 0; + private readonly List updatedKeys = new List(); public TriggerType Trigger { get; } public IVerifiable ScriptContainer { get; } @@ -48,7 +47,7 @@ internal T AddDisposable(T disposable) where T : IDisposable return disposable; } - internal bool TryAddUpdatedKey(byte[] key) + internal bool TryAddUpdatedKey(StorageKey key) { bool keyAdded = false; if (!updatedKeys.Contains(key)) @@ -61,21 +60,10 @@ internal bool TryAddUpdatedKey(byte[] key) private bool AddGas(long gas) { - if (gas < 0 && GasConsumed > maxConsumedGas) - maxConsumedGas = GasConsumed; GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; } - /// - /// Recalculate the property GasConsumed to use the gas required to run the whole transaction. - /// - private void RecalculateConsumedGas() - { - if (maxConsumedGas > GasConsumed) - GasConsumed = maxConsumedGas; - } - protected override void LoadContext(ExecutionContext context) { // Set default execution context state diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index edb3ec3fec..1430f19b0f 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -34,11 +34,31 @@ private static bool CheckStorageContext(ApplicationEngine engine, StorageContext return true; } + private static long GetDeletePrice(ApplicationEngine engine) + { + var stack = engine.CurrentContext.EvaluationStack; + var key = stack.Peek(1); + + StorageKey skey = new StorageKey + { + ScriptHash = engine.CurrentScriptHash, + Key = key.GetSpan().ToArray() + }; + + var skeyValue = engine.Snapshot.Storages.TryGet(skey); + + if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || !engine.TryAddUpdatedKey(skey)) + return 0_01000000; + + return (skeyValue.Value.Length + key.GetByteLength()) * GasPerReleasedByte; + } + private static long GetStoragePrice(ApplicationEngine engine) { var stack = engine.CurrentContext.EvaluationStack; var key = stack.Peek(1); - var newDataSize = stack.Peek(2).GetByteLength(); + var value = stack.Peek(2); + var newDataSize = value.IsNull ? 0 : value.GetByteLength(); StorageKey skey = new StorageKey { ScriptHash = engine.CurrentScriptHash, @@ -47,7 +67,7 @@ private static long GetStoragePrice(ApplicationEngine engine) var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || !engine.TryAddUpdatedKey(skey.Key)) + if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || !engine.TryAddUpdatedKey(skey)) return (key.GetByteLength() + newDataSize) * GasPerByte; var currentOccupiedBytes = skeyValue.Value.Length; @@ -64,144 +84,144 @@ private static long GetStoragePrice(ApplicationEngine engine) } } - private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) - { - if (key.Length > MaxKeySize) return false; - if (value.Length > MaxValueSize) return false; - if (context.IsReadOnly) return false; - if (!CheckStorageContext(engine, context)) return false; - - StorageKey skey = new StorageKey + private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) { - ScriptHash = context.ScriptHash, - Key = key - }; + if (key.Length > MaxKeySize) return false; + if (value.Length > MaxValueSize) return false; + if (context.IsReadOnly) return false; + if (!CheckStorageContext(engine, context)) return false; + + StorageKey skey = new StorageKey + { + ScriptHash = context.ScriptHash, + Key = key + }; - if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; + if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; - if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) - { - // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) - engine.Snapshot.Storages.Delete(skey); + if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) + { + // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) + engine.Snapshot.Storages.Delete(skey); + } + else + { + StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); + item.Value = value; + item.IsConstant = flags.HasFlag(StorageFlags.Constant); + } + return true; } - else + + private static bool Storage_GetContext(ApplicationEngine engine) { - StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); - item.Value = value; - item.IsConstant = flags.HasFlag(StorageFlags.Constant); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + { + ScriptHash = engine.CurrentScriptHash, + IsReadOnly = false + })); + return true; } - return true; - } - private static bool Storage_GetContext(ApplicationEngine engine) - { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) { - ScriptHash = engine.CurrentScriptHash, - IsReadOnly = false - })); - return true; - } + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + { + ScriptHash = engine.CurrentScriptHash, + IsReadOnly = true + })); + return true; + } - private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) - { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + private static bool Storage_AsReadOnly(ApplicationEngine engine) { - ScriptHash = engine.CurrentScriptHash, - IsReadOnly = true - })); - return true; - } + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (!context.IsReadOnly) + context = new StorageContext + { + ScriptHash = context.ScriptHash, + IsReadOnly = true + }; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); + return true; + } + return false; + } - private static bool Storage_AsReadOnly(ApplicationEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + private static bool Storage_Get(ApplicationEngine engine) { - StorageContext context = _interface.GetInterface(); - if (!context.IsReadOnly) - context = new StorageContext + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (!CheckStorageContext(engine, context)) return false; + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey { ScriptHash = context.ScriptHash, - IsReadOnly = true - }; - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); - return true; + Key = key + }); + engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); + return true; + } + return false; } - return false; - } - private static bool Storage_Get(ApplicationEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + private static bool Storage_Find(ApplicationEngine engine) { - StorageContext context = _interface.GetInterface(); - if (!CheckStorageContext(engine, context)) return false; - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - ScriptHash = context.ScriptHash, - Key = key - }); - engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); - return true; + StorageContext context = _interface.GetInterface(); + if (!CheckStorageContext(engine, context)) return false; + byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix); + StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator())); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); + return true; + } + return false; } - return false; - } - private static bool Storage_Find(ApplicationEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + private static bool Storage_Put(ApplicationEngine engine) { + if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + return false; StorageContext context = _interface.GetInterface(); - if (!CheckStorageContext(engine, context)) return false; - byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix); - StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator())); - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); - return true; + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + return PutExInternal(engine, context, key, value, StorageFlags.None); } - return false; - } - private static bool Storage_Put(ApplicationEngine engine) - { - if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - return false; - StorageContext context = _interface.GetInterface(); - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - return PutExInternal(engine, context, key, value, StorageFlags.None); - } - - private static bool Storage_PutEx(ApplicationEngine engine) - { - if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - return false; - StorageContext context = _interface.GetInterface(); - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - return PutExInternal(engine, context, key, value, flags); - } - - private static bool Storage_Delete(ApplicationEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + private static bool Storage_PutEx(ApplicationEngine engine) { + if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) + return false; StorageContext context = _interface.GetInterface(); - if (context.IsReadOnly) return false; - if (!CheckStorageContext(engine, context)) return false; - StorageKey key = new StorageKey + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); + StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + return PutExInternal(engine, context, key, value, flags); + } + + private static bool Storage_Delete(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - ScriptHash = context.ScriptHash, - Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() - }; - if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; - engine.Snapshot.Storages.Delete(key); - return true; + StorageContext context = _interface.GetInterface(); + if (context.IsReadOnly) return false; + if (!CheckStorageContext(engine, context)) return false; + StorageKey key = new StorageKey + { + ScriptHash = context.ScriptHash, + Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() + }; + if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; + engine.Snapshot.Storages.Delete(key); + return true; + } + return false; } - return false; } } } -} diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 7ceb8193ca..c725be59d7 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -25,7 +25,6 @@ public static long GetPrice(uint hash, EvaluationStack stack) return methods[hash].GetPrice(stack); } - public static long GetPrice(uint hash, ApplicationEngine applicationEngine) { var interopDescriptor = methods[hash]; @@ -65,625 +64,4 @@ private static InteropDescriptor Register(string method, Func items_checked = new List(); - // Queue items_unchecked = new Queue(); - // while (true) - // { - // switch (state) - // { - // case Struct array: - // foreach (StackItem item in array) - // items_unchecked.Enqueue(item); - // break; - // case Array array: - // if (items_checked.All(p => !ReferenceEquals(p, array))) - // { - // items_checked.Add(array); - // foreach (StackItem item in array) - // items_unchecked.Enqueue(item); - // } - // break; - // case PrimitiveType primitive: - // size += primitive.GetByteLength(); - // break; - // case Null _: - // break; - // case InteropInterface _: - // return false; - // case Map map: - // if (items_checked.All(p => !ReferenceEquals(p, map))) - // { - // items_checked.Add(map); - // foreach (var pair in map) - // { - // size += pair.Key.GetByteLength(); - // items_unchecked.Enqueue(pair.Value); - // } - // } - // break; - // } - // if (size > MaxNotificationSize) return false; - // if (items_unchecked.Count == 0) return true; - // state = items_unchecked.Dequeue(); - // } - // } - - // private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) - // { - // foreach (Type t in typeof(InteropService).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) - // t.GetFields()[0].GetValue(null); - // } - - // public static long GetPrice(uint hash) - // { - // return methods[hash].GetPrice(); - // } - - // public static long GetPrice(uint hash, EvaluationStack stack) - // { - // return methods[hash].GetPrice(stack); - // } - - // public static IEnumerable SupportedMethods() - // { - // return methods.Values; - // } - // public static long GetPrice(uint hash, ApplicationEngine applicationEngine) - // { - // var interopDescriptor = methods[hash]; - // return interopDescriptor.IsStateful ? interopDescriptor.GetPrice(applicationEngine) : interopDescriptor.GetPrice(applicationEngine.CurrentContext.EvaluationStack); - // } - - // public static Dictionary SupportedMethods() - // { - // return methods.ToDictionary(p => p.Key, p => p.Value.Method); - // } - - // private static long GetDeleteStoragePrice(ApplicationEngine engine) - // { - // var stack = engine.CurrentContext.EvaluationStack; - // var key = stack.Peek(1); - // StorageKey skey = new StorageKey - // { - // ScriptHash = engine.CurrentScriptHash, - // Key = key.GetSpan().ToArray() - // }; - - // var skeyValue = engine.Snapshot.Storages.TryGet(skey); - - // if (skey == null || !engine.TryAddUpdatedKey(key.GetSpan().ToArray())) - // return 0_01000000; - - // return skeyValue.Value.Length * GasPerReleasedByte; - // } - - // private static long GetStoragePrice(ApplicationEngine engine) - // { - // var stack = engine.CurrentContext.EvaluationStack; - // var key = stack.Peek(1); - // var newDataSize = stack.Peek(2).GetByteLength(); - // StorageKey skey = new StorageKey - // { - // ScriptHash = engine.CurrentScriptHash, - // Key = key.GetSpan().ToArray() - // }; - - // var skeyValue = engine.Snapshot.Storages.TryGet(skey); - - // if (skeyValue == null || !engine.TryAddUpdatedKey(skey.Key)) - // return (key.GetByteLength() + newDataSize) * GasPerByte; - - // var currentOccupiedBytes = skeyValue.Value.Length; - - // if (newDataSize <= currentOccupiedBytes) - // { - // var releasedBytes = currentOccupiedBytes - newDataSize; - // return releasedBytes * GasPerReleasedByte; - // } - // else - // { - // var reusedBytes = currentOccupiedBytes; - // return (newDataSize - reusedBytes) * GasPerByte; - // } - // } - - // private static uint Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) - // { - // InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); - // methods.Add(descriptor.Hash, descriptor); - // return descriptor.Hash; - // } - - // private static bool ExecutionEngine_GetScriptContainer(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push( - // engine.ScriptContainer is IInteroperable value ? value.ToStackItem(engine.ReferenceCounter) : - // StackItem.FromInterface(engine.ScriptContainer)); - // return true; - // } - - // private static bool ExecutionEngine_GetExecutingScriptHash(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(engine.CurrentScriptHash.ToArray()); - // return true; - // } - - // private static bool ExecutionEngine_GetCallingScriptHash(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(engine.CallingScriptHash?.ToArray() ?? StackItem.Null); - // return true; - // } - - // private static bool ExecutionEngine_GetEntryScriptHash(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(engine.EntryScriptHash.ToArray()); - // return true; - // } - - // private static bool Runtime_Platform(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(Encoding.ASCII.GetBytes("NEO")); - // return true; - // } - - // private static bool Runtime_GetTrigger(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push((int)engine.Trigger); - // return true; - // } - - // internal static bool CheckWitness(ApplicationEngine engine, UInt160 hash) - // { - // if (engine.ScriptContainer is Transaction tx) - // { - // Cosigner usage = tx.Cosigners.FirstOrDefault(p => p.Account.Equals(hash)); - // if (usage is null) return false; - // if (usage.Scopes == WitnessScope.Global) return true; - // if (usage.Scopes.HasFlag(WitnessScope.CalledByEntry)) - // { - // if (engine.CallingScriptHash == engine.EntryScriptHash) - // return true; - // } - // if (usage.Scopes.HasFlag(WitnessScope.CustomContracts)) - // { - // if (usage.AllowedContracts.Contains(engine.CurrentScriptHash)) - // return true; - // } - // if (usage.Scopes.HasFlag(WitnessScope.CustomGroups)) - // { - // var contract = engine.Snapshot.Contracts[engine.CallingScriptHash]; - // // check if current group is the required one - // if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(usage.AllowedGroups).Any()) - // return true; - // } - // return false; - // } - - // // only for non-Transaction types (Block, etc) - - // var hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); - // return hashes_for_verifying.Contains(hash); - // } - - // private static bool CheckWitness(ApplicationEngine engine, ECPoint pubkey) - // { - // return CheckWitness(engine, Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); - // } - - // private static bool Runtime_CheckWitness(ApplicationEngine engine) - // { - // ReadOnlySpan hashOrPubkey = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // bool result; - // if (hashOrPubkey.Length == 20) - // result = CheckWitness(engine, new UInt160(hashOrPubkey)); - // else if (hashOrPubkey.Length == 33) - // result = CheckWitness(engine, ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)); - // else - // return false; - // engine.CurrentContext.EvaluationStack.Push(result); - // return true; - // } - - // private static bool Runtime_Notify(ApplicationEngine engine) - // { - // StackItem state = engine.CurrentContext.EvaluationStack.Pop(); - // if (!CheckItemForNotification(state)) return false; - // engine.SendNotification(engine.CurrentScriptHash, state); - // return true; - // } - - // private static bool Runtime_Log(ApplicationEngine engine) - // { - // ReadOnlySpan state = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // if (state.Length > MaxNotificationSize) return false; - // string message = Encoding.UTF8.GetString(state); - // engine.SendLog(engine.CurrentScriptHash, message); - // return true; - // } - - // private static bool Runtime_GetTime(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(engine.Snapshot.PersistingBlock.Timestamp); - // return true; - // } - - // private static bool Runtime_Serialize(ApplicationEngine engine) - // { - // byte[] serialized; - // try - // { - // serialized = StackItemSerializer.Serialize(engine.CurrentContext.EvaluationStack.Pop(), engine.MaxItemSize); - // } - // catch - // { - // return false; - // } - // engine.CurrentContext.EvaluationStack.Push(serialized); - // return true; - // } - - // private static bool Runtime_GetNotifications(ApplicationEngine engine) - // { - // StackItem item = engine.CurrentContext.EvaluationStack.Pop(); - - // IEnumerable notifications = engine.Notifications; - // if (!item.IsNull) // must filter by scriptHash - // { - // var hash = new UInt160(item.GetSpan()); - // notifications = notifications.Where(p => p.ScriptHash == hash); - // } - - // if (notifications.Count() > engine.MaxStackSize) return false; - // engine.Push(new Array(engine.ReferenceCounter, notifications.Select(u => new Array(engine.ReferenceCounter, new[] { u.ScriptHash.ToArray(), u.State })))); - // return true; - // } - - // private static bool Runtime_GetInvocationCounter(ApplicationEngine engine) - // { - // if (!engine.InvocationCounter.TryGetValue(engine.CurrentScriptHash, out var counter)) - // { - // return false; - // } - - // engine.CurrentContext.EvaluationStack.Push(counter); - // return true; - // } - - // private static bool Runtime_Deserialize(ApplicationEngine engine) - // { - // StackItem item; - // try - // { - // item = StackItemSerializer.Deserialize(engine.CurrentContext.EvaluationStack.Pop().GetSpan(), engine.MaxItemSize, engine.ReferenceCounter); - // } - // catch (FormatException) - // { - // return false; - // } - // catch (IOException) - // { - // return false; - // } - // engine.CurrentContext.EvaluationStack.Push(item); - // return true; - // } - - // private static bool Blockchain_GetHeight(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(engine.Snapshot.Height); - // return true; - // } - - // private static bool Blockchain_GetBlock(ApplicationEngine engine) - // { - // ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // UInt256 hash; - // if (data.Length <= 5) - // hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); - // else if (data.Length == 32) - // hash = new UInt256(data); - // else - // return false; - - // Block block = hash != null ? engine.Snapshot.GetBlock(hash) : null; - // if (block == null) - // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - // else - // engine.CurrentContext.EvaluationStack.Push(block.ToStackItem(engine.ReferenceCounter)); - // return true; - // } - - // private static bool Blockchain_GetTransaction(ApplicationEngine engine) - // { - // ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // Transaction tx = engine.Snapshot.GetTransaction(new UInt256(hash)); - // if (tx == null) - // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - // else - // engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); - // return true; - // } - - // private static bool Blockchain_GetTransactionHeight(ApplicationEngine engine) - // { - // ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // var tx = engine.Snapshot.Transactions.TryGet(new UInt256(hash)); - // engine.CurrentContext.EvaluationStack.Push(tx != null ? new BigInteger(tx.BlockIndex) : BigInteger.MinusOne); - // return true; - // } - - // private static bool Blockchain_GetTransactionFromBlock(ApplicationEngine engine) - // { - // ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - // UInt256 hash; - // if (data.Length <= 5) - // hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); - // else if (data.Length == 32) - // hash = new UInt256(data); - // else - // return false; - - // TrimmedBlock block = hash != null ? engine.Snapshot.Blocks.TryGet(hash) : null; - // if (block == null) - // { - // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - // } - // else - // { - // int index = (int)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - // if (index < 0 || index >= block.Hashes.Length - 1) return false; - - // Transaction tx = engine.Snapshot.GetTransaction(block.Hashes[index + 1]); - // if (tx == null) - // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - // else - // engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); - // } - // return true; - // } - - // private static bool Blockchain_GetContract(ApplicationEngine engine) - // { - // UInt160 hash = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetSpan()); - // ContractState contract = engine.Snapshot.Contracts.TryGet(hash); - // if (contract == null) - // engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - // else - // engine.CurrentContext.EvaluationStack.Push(contract.ToStackItem(engine.ReferenceCounter)); - // return true; - // } - - // private static bool Storage_GetContext(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - // { - // ScriptHash = engine.CurrentScriptHash, - // IsReadOnly = false - // })); - // return true; - // } - - // private static bool Storage_GetReadOnlyContext(ApplicationEngine engine) - // { - // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - // { - // ScriptHash = engine.CurrentScriptHash, - // IsReadOnly = true - // })); - // return true; - // } - - // private static bool Storage_Get(ApplicationEngine engine) - // { - // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - // { - // StorageContext context = _interface.GetInterface(); - // if (!CheckStorageContext(engine, context)) return false; - // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - // StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey - // { - // ScriptHash = context.ScriptHash, - // Key = key - // }); - // engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null); - // return true; - // } - // return false; - // } - - // private static bool StorageContext_AsReadOnly(ApplicationEngine engine) - // { - // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - // { - // StorageContext context = _interface.GetInterface(); - // if (!context.IsReadOnly) - // context = new StorageContext - // { - // ScriptHash = context.ScriptHash, - // IsReadOnly = true - // }; - // engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); - // return true; - // } - // return false; - // } - - // private static bool Contract_Call(ApplicationEngine engine) - // { - // StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop(); - - // ContractState contract = engine.Snapshot.Contracts.TryGet(new UInt160(contractHash.GetSpan())); - // if (contract is null) return false; - - // 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; - - // if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter)) - // { - // engine.InvocationCounter[contract.ScriptHash] = counter + 1; - // } - // else - // { - // engine.InvocationCounter[contract.ScriptHash] = 1; - // } - - // ExecutionContext context_new = engine.LoadScript(contract.Script, 1); - // context_new.EvaluationStack.Push(args); - // context_new.EvaluationStack.Push(method); - // return true; - // } - - // private static bool Contract_Destroy(ApplicationEngine engine) - // { - // UInt160 hash = engine.CurrentScriptHash; - // ContractState contract = engine.Snapshot.Contracts.TryGet(hash); - // if (contract == null) return true; - // engine.Snapshot.Contracts.Delete(hash); - // if (contract.HasStorage) - // foreach (var pair in engine.Snapshot.Storages.Find(hash.ToArray())) - // engine.Snapshot.Storages.Delete(pair.Key); - // return true; - // } - - // private static bool PutEx(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) - // { - // if (key.Length > MaxStorageKeySize) return false; - // if (value.Length > MaxStorageValueSize) return false; - // if (context.IsReadOnly) return false; - // if (!CheckStorageContext(engine, context)) return false; - - // StorageKey skey = new StorageKey - // { - // ScriptHash = context.ScriptHash, - // Key = key - // }; - - // var skeyValue = engine.Snapshot.Storages.TryGet(skey); - // if (skeyValue?.IsConstant == true) return false; - - // if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) - // { - // // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) - // engine.Snapshot.Storages.Delete(skey); - // } - // else - // { - // StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem()); - // item.Value = value; - // item.IsConstant = flags.HasFlag(StorageFlags.Constant); - // } - - // return true; - // } - - // private static bool Storage_Put(ApplicationEngine engine) - // { - // if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - // return false; - // StorageContext context = _interface.GetInterface(); - // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - // byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - // return PutEx(engine, context, key, value, StorageFlags.None); - // } - - // private static bool Storage_PutEx(ApplicationEngine engine) - // { - // if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface)) - // return false; - // StorageContext context = _interface.GetInterface(); - // byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - // byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray(); - // StorageFlags flags = (StorageFlags)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - // return PutEx(engine, context, key, value, flags); - // } - - // private static bool Storage_Delete(ApplicationEngine engine) - // { - // if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - // { - // StorageContext context = _interface.GetInterface(); - // if (context.IsReadOnly) return false; - // if (!CheckStorageContext(engine, context)) return false; - // StorageKey key = new StorageKey - // { - // ScriptHash = context.ScriptHash, - // Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray() - // }; - - // var value = engine.Snapshot.Storages.TryGet(key); - // if (value?.IsConstant == true) return false; - // engine.Snapshot.Storages.Delete(key); - // return true; - // } - // return false; - //>>>>>>> Storage payback - - // internal static bool Invoke(ApplicationEngine engine, uint method) - // { - // if (!methods.TryGetValue(method, out InteropDescriptor descriptor)) - // return false; - // if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger)) - // return false; - // return descriptor.Handler(engine); - // } - - // private static InteropDescriptor Register(string method, Func handler, long price, TriggerType allowedTriggers) - // { - // InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers); - // methods.Add(descriptor.Hash, descriptor); - // return descriptor; - // } - - // private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers) - // { - // InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); - // methods.Add(descriptor.Hash, descriptor); - // return descriptor; - // } - } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 015e2a72b9..419b946985 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -353,6 +353,7 @@ public static long CalculateNetWorkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { + //TODO Document this value size += 67 + witness_script.GetVarSize(); networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify); } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 63b97fb135..2bc266c510 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -6,6 +6,8 @@ using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; +using Neo.VM.Types; +using System.Linq; namespace Neo.UnitTests.SmartContract { @@ -65,27 +67,13 @@ public void ApplicationEngineVariablePrices() var manifest = ContractManifest.CreateDefault(UInt160.Zero); manifest.Features = ContractFeatures.HasStorage; - var scriptBuilder = new ScriptBuilder(); var key = new byte[] { (byte)OpCode.PUSH3 }; var value = new byte[] { (byte)OpCode.PUSH3 }; - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); - byte[] script = scriptBuilder.ToArray(); - ContractState contractState = new ContractState - { - Script = script, - Manifest = manifest - }; - - StorageKey skey = new StorageKey - { - ScriptHash = script.ToScriptHash(), - Key = key - }; + byte[] script = CreateDummyPutScript(key, value); + ContractState contractState = TestUtils.GetContract(script); + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); StorageItem sItem = null; mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); @@ -102,14 +90,9 @@ public void ApplicationEngineVariablePrices() InteropService.GetPrice(InteropService.Storage.Put, ae).Should().Be(200000L); } - scriptBuilder = new ScriptBuilder(); key = new byte[] { (byte)OpCode.PUSH3 }; value = new byte[] { (byte)OpCode.PUSH3 }; - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.PutEx); - script = scriptBuilder.ToArray(); + script = CreateDummyPutExScript(key, value); byte[] scriptPutEx = script; using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) @@ -123,40 +106,24 @@ public void ApplicationEngineVariablePrices() } } + /// + /// Put without previous content (should charge per byte used) + /// [TestMethod] public void ApplicationEngineRegularPut() { - var scriptBuilder = new ScriptBuilder(); - var key = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1 }; - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); - - var mockedStoreView = new Mock(); - - byte[] script = scriptBuilder.ToArray(); + byte[] script = CreateDummyPutScript(key, value); - var manifest = ContractManifest.CreateDefault(UInt160.Zero); - manifest.Features = ContractFeatures.HasStorage; - - ContractState contractState = new ContractState - { - Script = script, - Manifest = manifest - }; + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = new StorageKey - { - ScriptHash = script.ToScriptHash(), - Key = key - }; - - StorageItem sItem = null; + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(null); + var mockedStoreView = new Mock(); mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); @@ -170,49 +137,30 @@ public void ApplicationEngineRegularPut() var setupPrice = ae.GasConsumed; var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); - debugger.StepInto(); var expectedCost = defaultDataPrice + setupPrice; - debugger.StepInto(); + debugger.Execute(); ae.GasConsumed.Should().Be(expectedCost); } } + /// + /// Reuses the same amount of storage. Should cost 0. + /// [TestMethod] public void ApplicationEngineReusedStorage_FullReuse() { - var scriptBuilder = new ScriptBuilder(); - var key = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1 }; - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); - var mockedStoreView = new Mock(); - byte[] script = scriptBuilder.ToArray(); - - var manifest = ContractManifest.CreateDefault(UInt160.Zero); - manifest.Features = ContractFeatures.HasStorage; - - ContractState contractState = new ContractState - { - Script = script, - Manifest = manifest - }; + byte[] script = CreateDummyPutScript(key, value); - StorageKey skey = new StorageKey - { - ScriptHash = script.ToScriptHash(), - Key = key - }; + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageItem sItem = new StorageItem - { - Value = value - }; + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(value); mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); @@ -224,49 +172,106 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); debugger.StepInto(); + var setupPrice = applicationEngine.GasConsumed; var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine); reusedDataPrice.Should().Be(0); + debugger.Execute(); + var expectedCost = reusedDataPrice + setupPrice; + applicationEngine.GasConsumed.Should().Be(expectedCost); } } + /// + /// Reuses one byte and allocates a new one + /// It should only pay for the second byte. + /// [TestMethod] public void ApplicationEngineReusedStorage_PartialReuse() { - var scriptBuilder = new ScriptBuilder(); - var key = new byte[] { (byte)OpCode.PUSH1 }; var oldValue = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); + byte[] script = CreateDummyPutScript(key, value); - var mockedStoreView = new Mock(); + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; - byte[] script = scriptBuilder.ToArray(); + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var manifest = ContractManifest.CreateDefault(UInt160.Zero); - manifest.Features = ContractFeatures.HasStorage; + var mockedStoreView = new Mock(); + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); - ContractState contractState = new ContractState + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { - Script = script, - Manifest = manifest - }; + Debugger debugger = new Debugger(ae); + ae.LoadScript(script); + debugger.StepInto(); + debugger.StepInto(); + debugger.StepInto(); + var setupPrice = ae.GasConsumed; + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); + debugger.StepInto(); + var expectedCost = reusedDataPrice + setupPrice; + debugger.StepInto(); + ae.GasConsumed.Should().Be(expectedCost); + } + } - StorageKey skey = new StorageKey - { - ScriptHash = script.ToScriptHash(), - Key = key - }; + /// + /// Use put for the same key twice. + /// Pays for 1 extra byte for the first Put and 3 extra bytes for the second put (key + value). + /// + [TestMethod] + public void ApplicationEngineReusedStorage_PartialReuseTwice() + { + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; - StorageItem sItem = new StorageItem + byte[] script = CreateDummyPutTwiceScript(key, value); + + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; + + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); + + var mockedStoreView = new Mock(); + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { - Value = oldValue - }; + Debugger debugger = new Debugger(ae); + ae.LoadScript(script); + ae.Execute(); + //1 reused + 1 from key + 2 from value (no discount on second use) + ae.GasConsumed.Should().BeGreaterThan(4 * InteropService.Storage.GasPerByte); + } + } + /// + /// Releases 1 byte from the storage receiving Gas credit using implicit delete + /// + [TestMethod] + public void ApplicationEngineReleaseStorage_ImplicitDelete() + { + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + + byte[] script = CreateDummyImplicitDeleteScript(key); + + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; + + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); + + var mockedStoreView = new Mock(); mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); @@ -279,13 +284,103 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); var setupPrice = ae.GasConsumed; var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); - reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); var expectedCost = reusedDataPrice + setupPrice; + ae.GasConsumed.Should().Be(expectedCost); + } + } + + /// + /// Releases 1 byte from the storage receiving Gas credit using explicit delete + /// + [TestMethod] + public void ApplicationEngineReleaseStorage_ExplicitDelete() + { + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + + byte[] script = CreateDummyExplicitDeleteScript(key); + + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; + + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); + + var mockedStoreView = new Mock(); + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + { + Debugger debugger = new Debugger(ae); + ae.LoadScript(script); debugger.StepInto(); + debugger.StepInto(); + var setupPrice = ae.GasConsumed; + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Delete, ae); + reusedDataPrice.Should().Be((skey.Key.Length + sItem.Value.Length) * InteropService.Storage.GasPerReleasedByte); + debugger.StepInto(); + var expectedCost = reusedDataPrice + setupPrice; ae.GasConsumed.Should().Be(expectedCost); } } + + + private byte[] CreateDummyExplicitDeleteScript(byte[] key) + { + var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Delete); + return scriptBuilder.ToArray(); + } + + private byte[] CreateDummyImplicitDeleteScript(byte[] key) + { + var emptyArray = new byte[0]; + var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitPush(emptyArray); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + return scriptBuilder.ToArray(); + } + + private byte[] CreateDummyPutTwiceScript(byte[] key, byte[] value) + { + var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + return scriptBuilder.ToArray(); + } + + private byte[] CreateDummyPutScript(byte[] key, byte[] value) + { + var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + return scriptBuilder.ToArray(); + } + + private byte[] CreateDummyPutExScript(byte[] key, byte[] value) + { + var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.PutEx); + return scriptBuilder.ToArray(); + } } } diff --git a/tests/neo.UnitTests/TestUtils.cs b/tests/neo.UnitTests/TestUtils.cs index a68eba49a0..ae787434de 100644 --- a/tests/neo.UnitTests/TestUtils.cs +++ b/tests/neo.UnitTests/TestUtils.cs @@ -4,6 +4,7 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Manifest; +using Neo.SmartContract; using Neo.VM; using Neo.Wallets.NEP6; using System; @@ -64,6 +65,32 @@ internal static ContractState GetContract() }; } + internal static ContractState GetContract(byte[] script) + { + return new ContractState + { + Script = script, + Manifest = ContractManifest.CreateDefault(script.ToScriptHash()) + }; + } + + internal static StorageItem GetStorageItem(byte[] value) + { + return new StorageItem + { + Value = value + }; + } + + internal static StorageKey GetStorageKey(UInt160 scriptHash, byte[] keyValue) + { + return new StorageKey + { + ScriptHash = scriptHash, + Key = keyValue + }; + } + public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal) { setupBlockBaseWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); From a376c48196cd71eccdbef93e9d24649e46b7f841 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 17 Dec 2019 04:51:45 -0300 Subject: [PATCH 03/50] Discarding changes --- neo.sln | 2 -- src/neo/SmartContract/InteropService.cs | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/neo.sln b/neo.sln index 768576c8c0..6cd50689da 100644 --- a/neo.sln +++ b/neo.sln @@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B5339DF7-5D1 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDE05FA8-8E73-4924-BC63-DD117127EEE1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo-vm", "..\neo-vm\src\neo-vm\neo-vm.csproj", "{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index c725be59d7..529a94ed65 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -15,6 +15,11 @@ static InteropService() t.GetFields()[0].GetValue(null); } + public static IEnumerable SupportedMethods() + { + return methods.Values; + } + public static long GetPrice(uint hash) { return methods[hash].GetPrice(); From 0239d041e71d5dc0987df6b778c9c0421babf46f Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 18 Dec 2019 21:11:25 -0300 Subject: [PATCH 04/50] Fixes, tests and refactor --- src/neo/IO/Caching/DataCache.cs | 2 +- src/neo/Persistence/SnapshotView.cs | 5 +- src/neo/Persistence/StoreView.cs | 14 +- src/neo/SmartContract/ApplicationEngine.cs | 11 ++ src/neo/SmartContract/InteropDescriptor.cs | 13 +- .../SmartContract/InteropService.Storage.cs | 8 +- src/neo/SmartContract/InteropService.cs | 3 +- .../SmartContract/Native/Tokens/Nep5Token.cs | 4 +- src/neo/Wallets/NEP6/NEP6Wallet.cs | 8 ++ src/neo/Wallets/Wallet.cs | 76 +++++----- .../SmartContract/UT_InteropPrices.cs | 136 +++++++++++++++++- 11 files changed, 223 insertions(+), 57 deletions(-) diff --git a/src/neo/IO/Caching/DataCache.cs b/src/neo/IO/Caching/DataCache.cs index 51da1db9a6..8d96533522 100644 --- a/src/neo/IO/Caching/DataCache.cs +++ b/src/neo/IO/Caching/DataCache.cs @@ -17,7 +17,7 @@ public class Trackable private readonly Dictionary dictionary = new Dictionary(); - public TValue this[TKey key] + public virtual TValue this[TKey key] { get { diff --git a/src/neo/Persistence/SnapshotView.cs b/src/neo/Persistence/SnapshotView.cs index 3b20025cd0..53d8087647 100644 --- a/src/neo/Persistence/SnapshotView.cs +++ b/src/neo/Persistence/SnapshotView.cs @@ -8,7 +8,7 @@ namespace Neo.Persistence /// /// Provide a for accessing snapshots. /// - public class SnapshotView : StoreView, IDisposable + public class SnapshotView : StoreView { private readonly ISnapshot snapshot; @@ -38,8 +38,9 @@ public override void Commit() snapshot.Commit(); } - public void Dispose() + public override void Dispose() { + base.Dispose(); snapshot.Dispose(); } } diff --git a/src/neo/Persistence/StoreView.cs b/src/neo/Persistence/StoreView.cs index 4c2a1eed78..448321fa03 100644 --- a/src/neo/Persistence/StoreView.cs +++ b/src/neo/Persistence/StoreView.cs @@ -2,13 +2,14 @@ using Neo.IO.Caching; using Neo.Ledger; using Neo.Network.P2P.Payloads; +using System; namespace Neo.Persistence { /// /// It provides a set of properties and methods for reading formatted data from the underlying storage. Such as and . /// - public abstract class StoreView + public abstract class StoreView : IDisposable { public Block PersistingBlock { get; internal set; } public abstract DataCache Blocks { get; } @@ -19,12 +20,12 @@ public abstract class StoreView public abstract MetaDataCache BlockHashIndex { get; } public abstract MetaDataCache HeaderHashIndex { get; } - public uint Height => BlockHashIndex.Get().Index; + public virtual uint Height => BlockHashIndex.Get().Index; public uint HeaderHeight => HeaderHashIndex.Get().Index; - public UInt256 CurrentBlockHash => BlockHashIndex.Get().Hash; + public virtual UInt256 CurrentBlockHash => BlockHashIndex.Get().Hash; public UInt256 CurrentHeaderHash => HeaderHashIndex.Get().Hash; - public StoreView Clone() + public virtual StoreView Clone() { return new ClonedView(this); } @@ -53,6 +54,11 @@ public bool ContainsTransaction(UInt256 hash) return state != null; } + public virtual void Dispose() + { + + } + public Block GetBlock(UInt256 hash) { TrimmedBlock state = Blocks.TryGet(hash); diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 059cf66c67..ad54917e6d 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -26,6 +26,7 @@ public partial class ApplicationEngine : ExecutionEngine public IVerifiable ScriptContainer { get; } public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; + public long GasCredit { get; private set; } = 0; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; @@ -47,6 +48,11 @@ internal T AddDisposable(T disposable) where T : IDisposable return disposable; } + internal bool HasUpdatedKey(StorageKey key) + { + return updatedKeys.Contains(key); + } + internal bool TryAddUpdatedKey(StorageKey key) { bool keyAdded = false; @@ -60,6 +66,11 @@ internal bool TryAddUpdatedKey(StorageKey key) private bool AddGas(long gas) { + if (gas < 0) + { + GasCredit = checked(GasCredit + gas); + } + GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 6bb59c3e7c..a7855429d0 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -43,7 +43,18 @@ public long GetPrice() public long GetPrice(ApplicationEngine applicationEngine) { - return StoragePriceCalculator is null ? Price : StoragePriceCalculator(applicationEngine); + long price = Price; + + if (!IsStateful && PriceCalculator != null) + { + price = PriceCalculator(applicationEngine.CurrentContext.EvaluationStack); + } + else if (IsStateful && StoragePriceCalculator != null) + { + price = StoragePriceCalculator(applicationEngine); + } + + return price; } public long GetPrice(EvaluationStack stack) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 1430f19b0f..854243e062 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -47,7 +47,7 @@ private static long GetDeletePrice(ApplicationEngine engine) var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || !engine.TryAddUpdatedKey(skey)) + if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) return 0_01000000; return (skeyValue.Value.Length + key.GetByteLength()) * GasPerReleasedByte; @@ -67,7 +67,7 @@ private static long GetStoragePrice(ApplicationEngine engine) var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || !engine.TryAddUpdatedKey(skey)) + if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) return (key.GetByteLength() + newDataSize) * GasPerByte; var currentOccupiedBytes = skeyValue.Value.Length; @@ -110,6 +110,9 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte item.Value = value; item.IsConstant = flags.HasFlag(StorageFlags.Constant); } + + engine.TryAddUpdatedKey(skey); + return true; } @@ -218,6 +221,7 @@ private static bool Storage_Delete(ApplicationEngine engine) }; if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; engine.Snapshot.Storages.Delete(key); + engine.TryAddUpdatedKey(key); return true; } return false; diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 529a94ed65..670775416a 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -32,8 +32,7 @@ public static long GetPrice(uint hash, EvaluationStack stack) public static long GetPrice(uint hash, ApplicationEngine applicationEngine) { - var interopDescriptor = methods[hash]; - return interopDescriptor.IsStateful ? interopDescriptor.GetPrice(applicationEngine) : interopDescriptor.GetPrice(applicationEngine.CurrentContext.EvaluationStack); + return methods[hash].GetPrice(applicationEngine); } internal static bool Invoke(ApplicationEngine engine, uint method) diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index bb92fbbd17..7536c80355 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -23,7 +23,7 @@ public abstract class Nep5Token : NativeContract public BigInteger Factor { get; } protected const byte Prefix_TotalSupply = 11; - protected const byte Prefix_Account = 20; + public const byte Prefix_Account = 20; protected Nep5Token() { @@ -61,7 +61,7 @@ protected Nep5Token() Manifest.Abi.Events = events.ToArray(); } - protected StorageKey CreateAccountKey(UInt160 account) + internal StorageKey CreateAccountKey(UInt160 account) { return CreateStorageKey(Prefix_Account, account); } diff --git a/src/neo/Wallets/NEP6/NEP6Wallet.cs b/src/neo/Wallets/NEP6/NEP6Wallet.cs index daf1b9158d..a67b653fb1 100644 --- a/src/neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/neo/Wallets/NEP6/NEP6Wallet.cs @@ -23,6 +23,14 @@ public class NEP6Wallet : Wallet public override string Name => name; public override Version Version => version; + public NEP6Wallet() + { + this.version = Version.Parse("3.0"); + this.Scrypt = ScryptParameters.Default; + this.accounts = new Dictionary(); + this.extra = JObject.Null; + } + public NEP6Wallet(string path, string name = null) { this.path = path; diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 419b946985..306838140a 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -31,7 +31,7 @@ public abstract class Wallet public abstract WalletAccount GetAccount(UInt160 scriptHash); public abstract IEnumerable GetAccounts(); - public WalletAccount CreateAccount() + public virtual WalletAccount CreateAccount() { byte[] privateKey = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) @@ -209,8 +209,10 @@ public virtual WalletAccount Import(string nep2, string passphrase, int N = 1638 return account; } - public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null) + public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null, StoreView snapshot = null) { + snapshot ??= Blockchain.Singleton.GetSnapshot(); + UInt160[] accounts; if (from is null) { @@ -222,47 +224,46 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null throw new ArgumentException($"The address {from.ToString()} was not found in the wallet"); accounts = new[] { from }; } - using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) + + HashSet cosignerList = new HashSet(); + byte[] script; + List<(UInt160 Account, BigInteger Value)> balances_gas = null; + using (ScriptBuilder sb = new ScriptBuilder()) { - HashSet cosignerList = new HashSet(); - byte[] script; - List<(UInt160 Account, BigInteger Value)> balances_gas = null; - using (ScriptBuilder sb = new ScriptBuilder()) + foreach (var (assetId, group, sum) in outputs.GroupBy(p => p.AssetId, (k, g) => (k, g, g.Select(p => p.Value.Value).Sum()))) { - foreach (var (assetId, group, sum) in outputs.GroupBy(p => p.AssetId, (k, g) => (k, g, g.Select(p => p.Value.Value).Sum()))) - { - var balances = new List<(UInt160 Account, BigInteger Value)>(); - foreach (UInt160 account in accounts) - using (ScriptBuilder sb2 = new ScriptBuilder()) - { - sb2.EmitAppCall(assetId, "balanceOf", account); - using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, testMode: true)) - { - if (engine.State.HasFlag(VMState.FAULT)) - throw new InvalidOperationException($"Execution for {assetId.ToString()}.balanceOf('{account.ToString()}' fault"); - BigInteger value = engine.ResultStack.Pop().GetBigInteger(); - if (value.Sign > 0) balances.Add((account, value)); - } - } - BigInteger sum_balance = balances.Select(p => p.Value).Sum(); - if (sum_balance < sum) - throw new InvalidOperationException($"It does not have enough balance, expected: {sum.ToString()} found: {sum_balance.ToString()}"); - foreach (TransferOutput output in group) + var balances = new List<(UInt160 Account, BigInteger Value)>(); + foreach (UInt160 account in accounts) + using (ScriptBuilder sb2 = new ScriptBuilder()) { - balances = balances.OrderBy(p => p.Value).ToList(); - var balances_used = FindPayingAccounts(balances, output.Value.Value); - cosignerList.UnionWith(balances_used.Select(p => p.Account)); - foreach (var (account, value) in balances_used) + sb2.EmitAppCall(assetId, "balanceOf", account); + using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, testMode: true)) { - sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); - sb.Emit(OpCode.THROWIFNOT); + if (engine.State.HasFlag(VMState.FAULT)) + throw new InvalidOperationException($"Execution for {assetId.ToString()}.balanceOf('{account.ToString()}' fault"); + BigInteger value = engine.ResultStack.Pop().GetBigInteger(); + if (value.Sign > 0) balances.Add((account, value)); } } - if (assetId.Equals(NativeContract.GAS.Hash)) - balances_gas = balances; + BigInteger sum_balance = balances.Select(p => p.Value).Sum(); + if (sum_balance < sum) + throw new InvalidOperationException($"It does not have enough balance, expected: {sum.ToString()} found: {sum_balance.ToString()}"); + foreach (TransferOutput output in group) + { + balances = balances.OrderBy(p => p.Value).ToList(); + var balances_used = FindPayingAccounts(balances, output.Value.Value); + cosignerList.UnionWith(balances_used.Select(p => p.Account)); + foreach (var (account, value) in balances_used) + { + sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + } } - script = sb.ToArray(); + if (assetId.Equals(NativeContract.GAS.Hash)) + balances_gas = balances; } + script = sb.ToArray(); + if (balances_gas is null) balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); @@ -278,7 +279,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null } } - public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null) + public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null, StoreView snapshot = null) { UInt160[] accounts; if (sender is null) @@ -291,7 +292,8 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Transac throw new ArgumentException($"The address {sender.ToString()} was not found in the wallet"); accounts = new[] { sender }; } - using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) + + using (snapshot ??= Blockchain.Singleton.GetSnapshot()) { var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); return MakeTransaction(snapshot, script, attributes ?? new TransactionAttribute[0], cosigners ?? new Cosigner[0], balances_gas); diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 2bc266c510..e75548a1fc 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -5,8 +5,12 @@ using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; using Neo.VM; using Neo.VM.Types; +using Neo.Wallets; +using Neo.Wallets.NEP6; using System.Linq; namespace Neo.UnitTests.SmartContract @@ -232,7 +236,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() var oldValue = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; - byte[] script = CreateDummyPutTwiceScript(key, value); + byte[] script = CreatePutTwiceScript(key, value); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -263,7 +267,7 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() var key = new byte[] { (byte)OpCode.PUSH1 }; var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - byte[] script = CreateDummyImplicitDeleteScript(key); + byte[] script = CreateImplicitDeleteScript(key); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -291,6 +295,8 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() } } + + /// /// Releases 1 byte from the storage receiving Gas credit using explicit delete /// @@ -300,7 +306,7 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() var key = new byte[] { (byte)OpCode.PUSH1 }; var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - byte[] script = CreateDummyExplicitDeleteScript(key); + byte[] script = CreateExplicitDeleteScript(key); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -327,8 +333,126 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() } } + [TestMethod] + public void TestCalculateMinimumRequiredToRun() + { + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + + byte[] script = CreateExplicitDeleteScript(key); + + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; + + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); + + var mockedStoreView = new Mock(); + mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); + mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + + long finalConsumedGas = 0; + long gasCredit = 0; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + { + ae.LoadScript(script); + ae.Execute(); + finalConsumedGas = ae.GasConsumed; + gasCredit = ae.GasCredit; + } + + //Negative Gas caused by released space. + finalConsumedGas.Should().BeLessOrEqualTo(0); + + //If you send GasConsumed, it should fail due to lack of GAS. + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, finalConsumedGas, testMode: false)) + { + ae.LoadScript(script); + ae.Execute(); + ae.State.Should().Be(VMState.FAULT); + } + + //To work properly, you have to send ConsumedGas + GasCredit. + //GasCredit is a negative value. + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, finalConsumedGas - gasCredit, testMode: false)) + { + ae.LoadScript(script); + ae.Execute(); + ae.State.Should().Be(VMState.HALT); + } + } + + [TestMethod] + public void TestPaybackExceedingSysFee() + { + var mock = new Mock() + { + CallBase = true + }; + + var mockedStoreView = new Mock() + { + CallBase = true, + }; + + var dummyBlock = Blockchain.Singleton.GetBlock(0); + var trimmedBlock = new TrimmedBlock(); + var testSnapshot = Blockchain.Singleton.GetSnapshot(); + + mockedStoreView.Setup(s => s.Storages).Returns(testSnapshot.Storages); + mockedStoreView.Setup(s => s.Contracts).Returns(testSnapshot.Contracts); + mockedStoreView.Setup(s => s.Height).Returns(0); + mockedStoreView.Setup(s => s.CurrentBlockHash).Returns(dummyBlock.Hash); + mockedStoreView.Setup(s => s.Blocks[dummyBlock.Hash]).Returns(trimmedBlock); + mockedStoreView.Setup(s => s.Clone()).Returns(mockedStoreView.Object); + + var wallet = mock.Object; + + using (wallet.Unlock("")) + { + var startingGas = 1000000; + var account = wallet.CreateAccount(); + var account2 = wallet.CreateAccount(); + var userKey = NativeContract.GAS.CreateAccountKey(account.ScriptHash); + var nep5Balance = new Nep5AccountState() + { + Balance = startingGas * NativeContract.GAS.Factor + }; + + var userBalance = TestUtils.GetStorageItem(nep5Balance.ToByteArray()); + mockedStoreView.Object.Storages.Add(userKey, userBalance); + + var balance = NativeContract.GAS.BalanceOf(mockedStoreView.Object, account.ScriptHash); + balance.Should().Be(startingGas * NativeContract.GAS.Factor); + + var transferOutput = new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account2.ScriptHash, + Value = new BigDecimal(1, 0) + }; + + //Regular transfer transaction + var transaction = wallet.MakeTransaction(new TransferOutput[] { transferOutput }, account.ScriptHash, mockedStoreView.Object); + transaction.SystemFee.Should().BeGreaterThan(0); + + //Calling a script that releases storage + var key = new byte[] { (byte)OpCode.PUSH1 }; + var oldValue = new byte[] { (byte)OpCode.PUSH1 }; + byte[] script = CreateExplicitDeleteScript(key); + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(oldValue); + mockedStoreView.Object.Storages.Add(skey, sItem); + mockedStoreView.Object.Contracts.Add(contractState.ScriptHash, contractState); + var storageReleaseTransaction = wallet.MakeTransaction(script, account.ScriptHash, snapshot: mockedStoreView.Object); + transaction.SystemFee.Should().Be(0); + } + + } - private byte[] CreateDummyExplicitDeleteScript(byte[] key) + private byte[] CreateExplicitDeleteScript(byte[] key) { var scriptBuilder = new ScriptBuilder(); scriptBuilder.EmitPush(key); @@ -337,7 +461,7 @@ private byte[] CreateDummyExplicitDeleteScript(byte[] key) return scriptBuilder.ToArray(); } - private byte[] CreateDummyImplicitDeleteScript(byte[] key) + private byte[] CreateImplicitDeleteScript(byte[] key) { var emptyArray = new byte[0]; var scriptBuilder = new ScriptBuilder(); @@ -348,7 +472,7 @@ private byte[] CreateDummyImplicitDeleteScript(byte[] key) return scriptBuilder.ToArray(); } - private byte[] CreateDummyPutTwiceScript(byte[] key, byte[] value) + private byte[] CreatePutTwiceScript(byte[] key, byte[] value) { var scriptBuilder = new ScriptBuilder(); scriptBuilder.EmitPush(value); From 92b256a66f9e9a60632f28bebe65f04a3f0e24cc Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 03:02:31 -0300 Subject: [PATCH 05/50] Calculating final fee during onPersist --- src/neo/Ledger/Blockchain.cs | 1 + src/neo/Network/P2P/Payloads/Transaction.cs | 6 ++ src/neo/SmartContract/ApplicationEngine.cs | 6 ++ .../SmartContract/Native/Tokens/GasToken.cs | 25 +++++- src/neo/Wallets/Wallet.cs | 11 +-- .../SmartContract/UT_InteropPrices.cs | 79 ++++++++++++------- 6 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 74d06806e4..099cda0dce 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -518,6 +518,7 @@ private void Persist(Block block) state.VMState = engine.Execute(); if (state.VMState == VMState.HALT) { + tx.SysFeeCredit = engine.GasCredit; engine.Snapshot.Commit(); } ApplicationExecuted application_executed = new ApplicationExecuted(engine); diff --git a/src/neo/Network/P2P/Payloads/Transaction.cs b/src/neo/Network/P2P/Payloads/Transaction.cs index 2b6523a6aa..5d58122891 100644 --- a/src/neo/Network/P2P/Payloads/Transaction.cs +++ b/src/neo/Network/P2P/Payloads/Transaction.cs @@ -67,6 +67,12 @@ public Cosigner[] Cosigners /// public long FeePerByte => NetworkFee / Size; + /// + /// Credit for releasing storage. + /// Calculated after running the TX. + /// + public long SysFeeCredit { get; set; } + private UInt256 _hash = null; public UInt256 Hash { diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index ad54917e6d..b9c17d670a 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -21,12 +21,14 @@ public partial class ApplicationEngine : ExecutionEngine private readonly List notifications = new List(); private readonly List disposables = new List(); private readonly List updatedKeys = new List(); + private long maxConsumedGas = 0; public TriggerType Trigger { get; } public IVerifiable ScriptContainer { get; } public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; public long GasCredit { get; private set; } = 0; + public long MinimumGasRequired { get { return Math.Max(GasConsumed, maxConsumedGas); } } public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; @@ -69,6 +71,10 @@ private bool AddGas(long gas) if (gas < 0) { GasCredit = checked(GasCredit + gas); + if (GasConsumed > maxConsumedGas) + { + maxConsumedGas = GasConsumed; + } } GasConsumed = checked(GasConsumed + gas); diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index daa57b6747..18310ebec4 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -35,15 +35,36 @@ internal override bool Initialize(ApplicationEngine engine) return true; } + /// + /// The paid final paid amount is calculated based based on the sysfee and sysfeeCredit + /// + /// + /// + private long CalculateSystemFeeWithPayback(Transaction tx) + { + long factor = (long)GAS.Factor; + long finalFee = Math.Max(tx.SystemFee + tx.SysFeeCredit, 0); + long remainder = finalFee % factor; + if (remainder > 0) + finalFee += factor - remainder; + + return finalFee; + } + protected override bool OnPersist(ApplicationEngine engine) { if (!base.OnPersist(engine)) return false; + long userPaidFees = 0; foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) - Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); + { + var sysFee = CalculateSystemFeeWithPayback(tx); + Burn(engine, tx.Sender, userPaidFees + tx.NetworkFee); + userPaidFees += userPaidFees; + } ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); Mint(engine, primary, engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.NetworkFee)); - BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.SystemFee); + BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + userPaidFees; StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(engine.Snapshot.PersistingBlock.Index)); engine.Snapshot.Storages.Add(key, new StorageItem { diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 306838140a..9b118662c3 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -320,15 +320,12 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Transacti { if (engine.State.HasFlag(VMState.FAULT)) throw new InvalidOperationException($"Failed execution for '{script.ToHexString()}'"); - tx.SystemFee = Math.Max(engine.GasConsumed - ApplicationEngine.GasFree, 0); + tx.SystemFee = Math.Max(engine.MinimumGasRequired - ApplicationEngine.GasFree, 0); if (tx.SystemFee > 0) { - long d = (long)NativeContract.GAS.Factor; - long remainder = tx.SystemFee % d; - if (remainder > 0) - tx.SystemFee += d - remainder; - else if (remainder < 0) - tx.SystemFee -= remainder; + long gasUnit = (long)NativeContract.GAS.Factor; + long remainder = tx.SystemFee % gasUnit; + tx.SystemFee += gasUnit - remainder; } } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index e75548a1fc..d4fcb4f58a 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -11,7 +11,9 @@ using Neo.VM.Types; using Neo.Wallets; using Neo.Wallets.NEP6; +using System; using System.Linq; +using System.Numerics; namespace Neo.UnitTests.SmartContract { @@ -74,7 +76,7 @@ public void ApplicationEngineVariablePrices() var key = new byte[] { (byte)OpCode.PUSH3 }; var value = new byte[] { (byte)OpCode.PUSH3 }; - byte[] script = CreateDummyPutScript(key, value); + byte[] script = CreatePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); @@ -96,7 +98,7 @@ public void ApplicationEngineVariablePrices() key = new byte[] { (byte)OpCode.PUSH3 }; value = new byte[] { (byte)OpCode.PUSH3 }; - script = CreateDummyPutExScript(key, value); + script = CreatePutExScript(key, value); byte[] scriptPutEx = script; using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) @@ -119,7 +121,7 @@ public void ApplicationEngineRegularPut() var key = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1 }; - byte[] script = CreateDummyPutScript(key, value); + byte[] script = CreatePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -158,7 +160,7 @@ public void ApplicationEngineReusedStorage_FullReuse() var mockedStoreView = new Mock(); - byte[] script = CreateDummyPutScript(key, value); + byte[] script = CreatePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -196,7 +198,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() var oldValue = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; - byte[] script = CreateDummyPutScript(key, value); + byte[] script = CreatePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -236,7 +238,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() var oldValue = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1, (byte)OpCode.PUSH1 }; - byte[] script = CreatePutTwiceScript(key, value); + byte[] script = CreateMultiplePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; @@ -434,20 +436,40 @@ public void TestPaybackExceedingSysFee() //Regular transfer transaction var transaction = wallet.MakeTransaction(new TransferOutput[] { transferOutput }, account.ScriptHash, mockedStoreView.Object); - transaction.SystemFee.Should().BeGreaterThan(0); + //Minimum fee + transaction.SystemFee.Should().Be(1 * (long)NativeContract.GAS.Factor); + + //fill the storage with data that will be released and builds the delete script + var random = new Random(); + var scriptBuilder = new ScriptBuilder(); + for (BigInteger i = 1; i < 10; i++) + { + var key = i.ToByteArray(); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Delete); + } + + var script = scriptBuilder.ToArray(); + for (BigInteger i = 1; i < 10; i++) + { + var key = i.ToByteArray(); + var value = new byte[2048]; + random.NextBytes(value); + StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageItem sItem = TestUtils.GetStorageItem(value); + mockedStoreView.Object.Storages.Add(skey, sItem); + } - //Calling a script that releases storage - var key = new byte[] { (byte)OpCode.PUSH1 }; - var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - byte[] script = CreateExplicitDeleteScript(key); ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); - StorageItem sItem = TestUtils.GetStorageItem(oldValue); - mockedStoreView.Object.Storages.Add(skey, sItem); - mockedStoreView.Object.Contracts.Add(contractState.ScriptHash, contractState); - var storageReleaseTransaction = wallet.MakeTransaction(script, account.ScriptHash, snapshot: mockedStoreView.Object); - transaction.SystemFee.Should().Be(0); + mockedStoreView.Object.Contracts.Add(script.ToScriptHash(), contractState); + + var storageReleaseTransaction = wallet.MakeTransaction(script.ToArray(), account.ScriptHash, snapshot: mockedStoreView.Object); + storageReleaseTransaction.SystemFee.Should().NotBe(0); + + //Calling expensive script with large payback + } } @@ -472,21 +494,22 @@ private byte[] CreateImplicitDeleteScript(byte[] key) return scriptBuilder.ToArray(); } - private byte[] CreatePutTwiceScript(byte[] key, byte[] value) + private byte[] CreateMultiplePutScript(byte[] key, byte[] value, int times = 2) { var scriptBuilder = new ScriptBuilder(); - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); + + for (int i = 0; i < times; i++) + { + scriptBuilder.EmitPush(value); + scriptBuilder.EmitPush(key); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage.Put); + } + return scriptBuilder.ToArray(); } - private byte[] CreateDummyPutScript(byte[] key, byte[] value) + private byte[] CreatePutScript(byte[] key, byte[] value) { var scriptBuilder = new ScriptBuilder(); scriptBuilder.EmitPush(value); @@ -496,7 +519,7 @@ private byte[] CreateDummyPutScript(byte[] key, byte[] value) return scriptBuilder.ToArray(); } - private byte[] CreateDummyPutExScript(byte[] key, byte[] value) + private byte[] CreatePutExScript(byte[] key, byte[] value) { var scriptBuilder = new ScriptBuilder(); scriptBuilder.EmitPush(value); From 26c50fa7ec277d137a736a083ddab4252f8e64c0 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 03:08:51 -0300 Subject: [PATCH 06/50] Rebase --- src/neo/SmartContract/InteropDescriptor.cs | 8 ++++++++ src/neo/SmartContract/InteropService.cs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index a7855429d0..cb00e9f648 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -21,6 +21,13 @@ internal InteropDescriptor(string method, Func handler, this.Price = price; } + internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) + : this(method, handler, allowedTriggers, requiredCallFlags) + { + this.StoragePriceCalculator = priceCalculator; + this.IsStateful = true; + } + internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) : this(method, handler, allowedTriggers, requiredCallFlags) { @@ -36,6 +43,7 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } + public long GetPrice() { return Price; diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 670775416a..444f6d3407 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -61,9 +61,9 @@ 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 flags) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, flags); methods.Add(descriptor.Hash, descriptor); return descriptor; } From 1fb19e424c8c6d451947e1d31444b1f5b32726dc Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 03:28:55 -0300 Subject: [PATCH 07/50] dotnet format + missing change --- src/neo/SmartContract/InteropService.Storage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 854243e062..3e2a39509d 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -24,7 +24,7 @@ public static class Storage 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); + public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, GetDeletePrice, TriggerType.Application, CallFlags.AllowModifyStates); private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) { From daa4e2bca6a6b867c7edaa1627c9a2a174ac5e1d Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 04:22:56 -0300 Subject: [PATCH 08/50] Minor improvements --- .../SmartContract/Native/Tokens/GasToken.cs | 16 +++++++--------- .../SmartContract/UT_InteropPrices.cs | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index 18310ebec4..ce58e2f61b 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -42,29 +42,27 @@ internal override bool Initialize(ApplicationEngine engine) /// private long CalculateSystemFeeWithPayback(Transaction tx) { - long factor = (long)GAS.Factor; + long gasUnit = (long)GAS.Factor; long finalFee = Math.Max(tx.SystemFee + tx.SysFeeCredit, 0); - long remainder = finalFee % factor; - if (remainder > 0) - finalFee += factor - remainder; - + long remainder = finalFee % gasUnit; + finalFee += gasUnit - remainder; return finalFee; } protected override bool OnPersist(ApplicationEngine engine) { if (!base.OnPersist(engine)) return false; - long userPaidFees = 0; + long sysFeeWithStorageDiscount = 0; foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) { var sysFee = CalculateSystemFeeWithPayback(tx); - Burn(engine, tx.Sender, userPaidFees + tx.NetworkFee); - userPaidFees += userPaidFees; + Burn(engine, tx.Sender, sysFeeWithStorageDiscount + tx.NetworkFee); + sysFeeWithStorageDiscount += sysFeeWithStorageDiscount; } ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); Mint(engine, primary, engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.NetworkFee)); - BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + userPaidFees; + BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + sysFeeWithStorageDiscount; StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(engine.Snapshot.PersistingBlock.Index)); engine.Snapshot.Storages.Add(key, new StorageItem { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index d4fcb4f58a..db42587dde 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -355,12 +355,14 @@ public void TestCalculateMinimumRequiredToRun() long finalConsumedGas = 0; long gasCredit = 0; + long minimumRequiredToRun = 0; using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { ae.LoadScript(script); ae.Execute(); finalConsumedGas = ae.GasConsumed; gasCredit = ae.GasCredit; + minimumRequiredToRun = ae.MinimumGasRequired; } //Negative Gas caused by released space. @@ -381,6 +383,22 @@ public void TestCalculateMinimumRequiredToRun() ae.LoadScript(script); ae.Execute(); ae.State.Should().Be(VMState.HALT); + + } + + //The application engine already calculates the value you need to send to get this tx approved. + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun, testMode: false)) + { + ae.LoadScript(script); + ae.Execute(); + ae.State.Should().Be(VMState.HALT); + } + + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun - 1, testMode: false)) + { + ae.LoadScript(script); + ae.Execute(); + ae.State.Should().Be(VMState.FAULT); } } From 42c6dd35e6c36c317baa7220e582d13dbb3ee804 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 04:33:19 -0300 Subject: [PATCH 09/50] fix --- src/neo/SmartContract/Native/Tokens/GasToken.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index ce58e2f61b..cb2fa6e3ed 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -56,8 +56,8 @@ protected override bool OnPersist(ApplicationEngine engine) foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) { var sysFee = CalculateSystemFeeWithPayback(tx); - Burn(engine, tx.Sender, sysFeeWithStorageDiscount + tx.NetworkFee); - sysFeeWithStorageDiscount += sysFeeWithStorageDiscount; + Burn(engine, tx.Sender, sysFee + tx.NetworkFee); + sysFeeWithStorageDiscount += sysFee; } ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); From c2aa68395141e8141d5b1cd28d2d5d3e72687140 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Dec 2019 04:43:16 -0300 Subject: [PATCH 10/50] Removing empty lines --- src/neo/SmartContract/InteropService.Storage.cs | 13 ------------- src/neo/Wallets/Wallet.cs | 2 -- .../neo.UnitTests/SmartContract/UT_InteropPrices.cs | 7 ++----- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 3e2a39509d..1687d097c4 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -38,18 +38,14 @@ private static long GetDeletePrice(ApplicationEngine engine) { var stack = engine.CurrentContext.EvaluationStack; var key = stack.Peek(1); - StorageKey skey = new StorageKey { ScriptHash = engine.CurrentScriptHash, Key = key.GetSpan().ToArray() }; - var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) return 0_01000000; - return (skeyValue.Value.Length + key.GetByteLength()) * GasPerReleasedByte; } @@ -64,14 +60,10 @@ private static long GetStoragePrice(ApplicationEngine engine) ScriptHash = engine.CurrentScriptHash, Key = key.GetSpan().ToArray() }; - var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) return (key.GetByteLength() + newDataSize) * GasPerByte; - var currentOccupiedBytes = skeyValue.Value.Length; - if (newDataSize <= currentOccupiedBytes) { var releasedBytes = currentOccupiedBytes - newDataSize; @@ -90,15 +82,12 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte if (value.Length > MaxValueSize) return false; if (context.IsReadOnly) return false; if (!CheckStorageContext(engine, context)) return false; - StorageKey skey = new StorageKey { ScriptHash = context.ScriptHash, Key = key }; - if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; - if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) { // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) @@ -110,9 +99,7 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte item.Value = value; item.IsConstant = flags.HasFlag(StorageFlags.Constant); } - engine.TryAddUpdatedKey(skey); - return true; } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 9b118662c3..e4b18490b0 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -212,7 +212,6 @@ public virtual WalletAccount Import(string nep2, string passphrase, int N = 1638 public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null, StoreView snapshot = null) { snapshot ??= Blockchain.Singleton.GetSnapshot(); - UInt160[] accounts; if (from is null) { @@ -263,7 +262,6 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null balances_gas = balances; } script = sb.ToArray(); - if (balances_gas is null) balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index db42587dde..729ef1d5cf 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -383,7 +383,7 @@ public void TestCalculateMinimumRequiredToRun() ae.LoadScript(script); ae.Execute(); ae.State.Should().Be(VMState.HALT); - + } //The application engine already calculates the value you need to send to get this tx approved. @@ -457,7 +457,6 @@ public void TestPaybackExceedingSysFee() //Minimum fee transaction.SystemFee.Should().Be(1 * (long)NativeContract.GAS.Factor); - //fill the storage with data that will be released and builds the delete script var random = new Random(); var scriptBuilder = new ScriptBuilder(); for (BigInteger i = 1; i < 10; i++) @@ -468,6 +467,7 @@ public void TestPaybackExceedingSysFee() scriptBuilder.EmitSysCall(InteropService.Storage.Delete); } + //fill the storage with data that will be released and builds the delete script var script = scriptBuilder.ToArray(); for (BigInteger i = 1; i < 10; i++) { @@ -485,9 +485,6 @@ public void TestPaybackExceedingSysFee() var storageReleaseTransaction = wallet.MakeTransaction(script.ToArray(), account.ScriptHash, snapshot: mockedStoreView.Object); storageReleaseTransaction.SystemFee.Should().NotBe(0); - - //Calling expensive script with large payback - } } From cb33f4764aca99329e2f00e88d6417b67708013c Mon Sep 17 00:00:00 2001 From: Charis Date: Thu, 6 Feb 2020 15:56:09 +0800 Subject: [PATCH 11/50] add comment --- src/neo/SmartContract/ApplicationEngine.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index b9c17d670a..6b8743e7b3 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -27,6 +27,10 @@ public partial class ApplicationEngine : ExecutionEngine public IVerifiable ScriptContainer { get; } public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; + + /* + GasCredit is a negative number, which shows how many gas should be paybacked in systemfee + */ public long GasCredit { get; private set; } = 0; public long MinimumGasRequired { get { return Math.Max(GasConsumed, maxConsumedGas); } } public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; @@ -71,6 +75,7 @@ private bool AddGas(long gas) if (gas < 0) { GasCredit = checked(GasCredit + gas); + //if gas is negative,GasConsumed will be reduced,so keep the max GasConsumed if (GasConsumed > maxConsumedGas) { maxConsumedGas = GasConsumed; From 6238206ac8e0713aab027f2a0544b411818a114c Mon Sep 17 00:00:00 2001 From: Charis Date: Thu, 6 Feb 2020 16:35:44 +0800 Subject: [PATCH 12/50] remove checkStorageContext --- .../SmartContract/InteropService.Storage.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 6ef99d5ae4..bc578af0f6 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -26,21 +26,16 @@ public static class Storage 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, GetDeletePrice, TriggerType.Application, CallFlags.AllowModifyStates); - private static bool CheckStorageContext(ApplicationEngine engine, StorageContext context) - { - ContractState contract = engine.Snapshot.Contracts.TryGet(context.ScriptHash); - if (contract == null) return false; - if (!contract.HasStorage) return false; - return true; - } - private static long GetDeletePrice(ApplicationEngine engine) { var stack = engine.CurrentContext.EvaluationStack; var key = stack.Peek(1); + if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) + return 0_01000000; + StorageContext context = _interface.GetInterface(); StorageKey skey = new StorageKey { - ScriptHash = engine.CurrentScriptHash, + Id = context.Id, Key = key.GetSpan().ToArray() }; var skeyValue = engine.Snapshot.Storages.TryGet(skey); @@ -55,9 +50,12 @@ private static long GetStoragePrice(ApplicationEngine engine) var key = stack.Peek(1); var value = stack.Peek(2); var newDataSize = value.IsNull ? 0 : value.GetByteLength(); + if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) + return (key.GetByteLength() + newDataSize) * GasPerByte; + StorageContext context = _interface.GetInterface(); StorageKey skey = new StorageKey { - ScriptHash = engine.CurrentScriptHash, + Id = context.Id, Key = key.GetSpan().ToArray() }; var skeyValue = engine.Snapshot.Storages.TryGet(skey); From a600683974adc9172d0d9564629027e42e4fc474 Mon Sep 17 00:00:00 2001 From: Charis Date: Thu, 6 Feb 2020 17:37:40 +0800 Subject: [PATCH 13/50] update ut --- .../SmartContract/UT_InteropPrices.cs | 35 +++++++++---------- tests/neo.UnitTests/TestUtils.cs | 5 +-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 729ef1d5cf..e0a8357880 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -68,20 +68,17 @@ public void ApplicationEngineVariablePrices() InteropService.GetPrice(InteropService.Contract.Create, ae.CurrentContext.EvaluationStack).Should().Be(0_00300000L); } - var mockedStoreView = new Mock(); - - var manifest = ContractManifest.CreateDefault(UInt160.Zero); - manifest.Features = ContractFeatures.HasStorage; - var key = new byte[] { (byte)OpCode.PUSH3 }; var value = new byte[] { (byte)OpCode.PUSH3 }; - byte[] script = CreatePutScript(key, value); + ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); - StorageItem sItem = null; + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); + StorageItem sItem = TestUtils.GetStorageItem(null); + var mockedStoreView = new Mock(); mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); @@ -104,7 +101,7 @@ public void ApplicationEngineVariablePrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { Debugger debugger = new Debugger(ae); - ae.LoadScript(scriptPut); + ae.LoadScript(scriptPutEx); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 @@ -126,7 +123,7 @@ public void ApplicationEngineRegularPut() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(null); var mockedStoreView = new Mock(); @@ -165,7 +162,7 @@ public void ApplicationEngineReusedStorage_FullReuse() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(value); mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); @@ -203,7 +200,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); var mockedStoreView = new Mock(); @@ -243,7 +240,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); var mockedStoreView = new Mock(); @@ -274,7 +271,7 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); var mockedStoreView = new Mock(); @@ -313,7 +310,7 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); var mockedStoreView = new Mock(); @@ -346,7 +343,7 @@ public void TestCalculateMinimumRequiredToRun() ContractState contractState = TestUtils.GetContract(script); contractState.Manifest.Features = ContractFeatures.HasStorage; - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); var mockedStoreView = new Mock(); @@ -469,17 +466,17 @@ public void TestPaybackExceedingSysFee() //fill the storage with data that will be released and builds the delete script var script = scriptBuilder.ToArray(); + ContractState contractState = TestUtils.GetContract(script); for (BigInteger i = 1; i < 10; i++) { var key = i.ToByteArray(); var value = new byte[2048]; random.NextBytes(value); - StorageKey skey = TestUtils.GetStorageKey(script.ToScriptHash(), key); + StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(value); mockedStoreView.Object.Storages.Add(skey, sItem); } - - ContractState contractState = TestUtils.GetContract(script); + contractState.Manifest.Features = ContractFeatures.HasStorage; mockedStoreView.Object.Contracts.Add(script.ToScriptHash(), contractState); diff --git a/tests/neo.UnitTests/TestUtils.cs b/tests/neo.UnitTests/TestUtils.cs index ae787434de..dfbd973a26 100644 --- a/tests/neo.UnitTests/TestUtils.cs +++ b/tests/neo.UnitTests/TestUtils.cs @@ -69,6 +69,7 @@ internal static ContractState GetContract(byte[] script) { return new ContractState { + Id = 1, Script = script, Manifest = ContractManifest.CreateDefault(script.ToScriptHash()) }; @@ -82,11 +83,11 @@ internal static StorageItem GetStorageItem(byte[] value) }; } - internal static StorageKey GetStorageKey(UInt160 scriptHash, byte[] keyValue) + internal static StorageKey GetStorageKey(int id, byte[] keyValue) { return new StorageKey { - ScriptHash = scriptHash, + Id = id, Key = keyValue }; } From b5b0fb4541166819bf453368b005eab6a1445f26 Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 13:45:42 +0800 Subject: [PATCH 14/50] revert modification --- src/neo/IO/Caching/DataCache.cs | 4 +- src/neo/Persistence/SnapshotView.cs | 5 +- src/neo/Persistence/StoreView.cs | 13 +-- .../SmartContract/InteropService.Storage.cs | 1 - .../SmartContract/Native/Tokens/Nep5Token.cs | 4 +- src/neo/Wallets/NEP6/NEP6Wallet.cs | 8 -- src/neo/Wallets/Wallet.cs | 87 ++++++++++--------- 7 files changed, 55 insertions(+), 67 deletions(-) diff --git a/src/neo/IO/Caching/DataCache.cs b/src/neo/IO/Caching/DataCache.cs index 6e97323a30..4529978a19 100644 --- a/src/neo/IO/Caching/DataCache.cs +++ b/src/neo/IO/Caching/DataCache.cs @@ -17,7 +17,7 @@ public class Trackable private readonly Dictionary dictionary = new Dictionary(); - public virtual TValue this[TKey key] + public TValue this[TKey key] { get { @@ -285,7 +285,7 @@ public TValue GetOrAdd(TKey key, Func factory) } } - public virtual TValue TryGet(TKey key) + public TValue TryGet(TKey key) { lock (dictionary) { diff --git a/src/neo/Persistence/SnapshotView.cs b/src/neo/Persistence/SnapshotView.cs index 7b47dd5a6a..d634200134 100644 --- a/src/neo/Persistence/SnapshotView.cs +++ b/src/neo/Persistence/SnapshotView.cs @@ -8,7 +8,7 @@ namespace Neo.Persistence /// /// Provide a for accessing snapshots. /// - public class SnapshotView : StoreView + public class SnapshotView : StoreView, IDisposable { private readonly ISnapshot snapshot; @@ -40,9 +40,8 @@ public override void Commit() snapshot.Commit(); } - public override void Dispose() + public void Dispose() { - base.Dispose(); snapshot.Dispose(); } } diff --git a/src/neo/Persistence/StoreView.cs b/src/neo/Persistence/StoreView.cs index 20a354b68c..ed9f7054b8 100644 --- a/src/neo/Persistence/StoreView.cs +++ b/src/neo/Persistence/StoreView.cs @@ -9,7 +9,7 @@ namespace Neo.Persistence /// /// It provides a set of properties and methods for reading formatted data from the underlying storage. Such as and . /// - public abstract class StoreView : IDisposable + public abstract class StoreView { public Block PersistingBlock { get; internal set; } public abstract DataCache Blocks { get; } @@ -21,12 +21,12 @@ public abstract class StoreView : IDisposable public abstract MetaDataCache HeaderHashIndex { get; } public abstract MetaDataCache ContractId { get; } - public virtual uint Height => BlockHashIndex.Get().Index; + public uint Height => BlockHashIndex.Get().Index; public uint HeaderHeight => HeaderHashIndex.Get().Index; - public virtual UInt256 CurrentBlockHash => BlockHashIndex.Get().Hash; + public UInt256 CurrentBlockHash => BlockHashIndex.Get().Hash; public UInt256 CurrentHeaderHash => HeaderHashIndex.Get().Hash; - public virtual StoreView Clone() + public StoreView Clone() { return new ClonedView(this); } @@ -56,11 +56,6 @@ public bool ContainsTransaction(UInt256 hash) return state != null; } - public virtual void Dispose() - { - - } - public Block GetBlock(UInt256 hash) { TrimmedBlock state = Blocks.TryGet(hash); diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index bc578af0f6..91f411b133 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -12,7 +12,6 @@ partial class InteropService public static class Storage { public const long GasPerByte = 100000; - public const long GasPerReusedByte = -GasPerByte; public const long GasPerReleasedByte = -GasPerByte; public const int MaxKeySize = 64; public const int MaxValueSize = ushort.MaxValue; diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index 13a69c9673..3e331de487 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -23,7 +23,7 @@ public abstract class Nep5Token : NativeContract public BigInteger Factor { get; } protected const byte Prefix_TotalSupply = 11; - public const byte Prefix_Account = 20; + protected const byte Prefix_Account = 20; protected Nep5Token() { @@ -61,7 +61,7 @@ protected Nep5Token() Manifest.Abi.Events = events.ToArray(); } - internal StorageKey CreateAccountKey(UInt160 account) + protected StorageKey CreateAccountKey(UInt160 account) { return CreateStorageKey(Prefix_Account, account); } diff --git a/src/neo/Wallets/NEP6/NEP6Wallet.cs b/src/neo/Wallets/NEP6/NEP6Wallet.cs index a67b653fb1..daf1b9158d 100644 --- a/src/neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/neo/Wallets/NEP6/NEP6Wallet.cs @@ -23,14 +23,6 @@ public class NEP6Wallet : Wallet public override string Name => name; public override Version Version => version; - public NEP6Wallet() - { - this.version = Version.Parse("3.0"); - this.Scrypt = ScryptParameters.Default; - this.accounts = new Dictionary(); - this.extra = JObject.Null; - } - public NEP6Wallet(string path, string name = null) { this.path = path; diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index e937cc82fd..39c7102bfa 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -31,7 +31,7 @@ public abstract class Wallet public abstract WalletAccount GetAccount(UInt160 scriptHash); public abstract IEnumerable GetAccounts(); - public virtual WalletAccount CreateAccount() + public WalletAccount CreateAccount() { byte[] privateKey = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) @@ -209,9 +209,8 @@ public virtual WalletAccount Import(string nep2, string passphrase, int N = 1638 return account; } - public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null, StoreView snapshot = null) + public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null) { - snapshot ??= Blockchain.Singleton.GetSnapshot(); UInt160[] accounts; if (from is null) { @@ -223,45 +222,47 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null throw new ArgumentException($"The address {from.ToString()} was not found in the wallet"); accounts = new[] { from }; } - - HashSet cosignerList = new HashSet(); - byte[] script; - List<(UInt160 Account, BigInteger Value)> balances_gas = null; - using (ScriptBuilder sb = new ScriptBuilder()) - { - foreach (var (assetId, group, sum) in outputs.GroupBy(p => p.AssetId, (k, g) => (k, g, g.Select(p => p.Value.Value).Sum()))) + using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) + { + HashSet cosignerList = new HashSet(); + byte[] script; + List<(UInt160 Account, BigInteger Value)> balances_gas = null; + using (ScriptBuilder sb = new ScriptBuilder()) { - var balances = new List<(UInt160 Account, BigInteger Value)>(); - foreach (UInt160 account in accounts) - using (ScriptBuilder sb2 = new ScriptBuilder()) - { - sb2.EmitAppCall(assetId, "balanceOf", account); - using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, testMode: true)) + foreach (var (assetId, group, sum) in outputs.GroupBy(p => p.AssetId, (k, g) => (k, g, g.Select(p => p.Value.Value).Sum()))) + { + var balances = new List<(UInt160 Account, BigInteger Value)>(); + foreach (UInt160 account in accounts) + using (ScriptBuilder sb2 = new ScriptBuilder()) { - if (engine.State.HasFlag(VMState.FAULT)) - throw new InvalidOperationException($"Execution for {assetId.ToString()}.balanceOf('{account.ToString()}' fault"); - BigInteger value = engine.ResultStack.Pop().GetBigInteger(); - if (value.Sign > 0) balances.Add((account, value)); + sb2.EmitAppCall(assetId, "balanceOf", account); + using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, testMode: true)) + { + if (engine.State.HasFlag(VMState.FAULT)) + throw new InvalidOperationException($"Execution for {assetId.ToString()}.balanceOf('{account.ToString()}' fault"); + BigInteger value = engine.ResultStack.Pop().GetBigInteger(); + if (value.Sign > 0) balances.Add((account, value)); + } } - } - BigInteger sum_balance = balances.Select(p => p.Value).Sum(); - if (sum_balance < sum) - throw new InvalidOperationException($"It does not have enough balance, expected: {sum.ToString()} found: {sum_balance.ToString()}"); - foreach (TransferOutput output in group) - { - balances = balances.OrderBy(p => p.Value).ToList(); - var balances_used = FindPayingAccounts(balances, output.Value.Value); - cosignerList.UnionWith(balances_used.Select(p => p.Account)); - foreach (var (account, value) in balances_used) + BigInteger sum_balance = balances.Select(p => p.Value).Sum(); + if (sum_balance < sum) + throw new InvalidOperationException($"It does not have enough balance, expected: {sum.ToString()} found: {sum_balance.ToString()}"); + foreach (TransferOutput output in group) { - sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); - sb.Emit(OpCode.THROWIFNOT); + balances = balances.OrderBy(p => p.Value).ToList(); + var balances_used = FindPayingAccounts(balances, output.Value.Value); + cosignerList.UnionWith(balances_used.Select(p => p.Account)); + foreach (var (account, value) in balances_used) + { + sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + } } + if (assetId.Equals(NativeContract.GAS.Hash)) + balances_gas = balances; } - if (assetId.Equals(NativeContract.GAS.Hash)) - balances_gas = balances; + script = sb.ToArray(); } - script = sb.ToArray(); if (balances_gas is null) balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); @@ -277,7 +278,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null } } - public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null, StoreView snapshot = null) + public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null) { UInt160[] accounts; if (sender is null) @@ -291,7 +292,7 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Transac accounts = new[] { sender }; } - using (snapshot ??= Blockchain.Singleton.GetSnapshot()) + using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) { var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); return MakeTransaction(snapshot, script, attributes ?? new TransactionAttribute[0], cosigners ?? new Cosigner[0], balances_gas); @@ -318,12 +319,15 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Transacti { if (engine.State.HasFlag(VMState.FAULT)) throw new InvalidOperationException($"Failed execution for '{script.ToHexString()}'"); - tx.SystemFee = Math.Max(engine.MinimumGasRequired - ApplicationEngine.GasFree, 0); + tx.SystemFee = Math.Max(engine.GasConsumed - ApplicationEngine.GasFree, 0); if (tx.SystemFee > 0) { - long gasUnit = (long)NativeContract.GAS.Factor; - long remainder = tx.SystemFee % gasUnit; - tx.SystemFee += gasUnit - remainder; + long d = (long)NativeContract.GAS.Factor; + long remainder = tx.SystemFee % d; + if (remainder > 0) + tx.SystemFee += d - remainder; + else if (remainder < 0) + tx.SystemFee -= remainder; } } @@ -350,7 +354,6 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { - //TODO Document this value size += 67 + witness_script.GetVarSize(); networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify); } From 96aca150299b7176841dce5b06f58511fa1900db Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 14:19:10 +0800 Subject: [PATCH 15/50] revert --- src/neo/Persistence/StoreView.cs | 1 - src/neo/SmartContract/InteropDescriptor.cs | 2 +- src/neo/SmartContract/InteropService.Storage.cs | 2 ++ src/neo/SmartContract/InteropService.cs | 12 ++++++------ src/neo/Wallets/Wallet.cs | 9 ++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/neo/Persistence/StoreView.cs b/src/neo/Persistence/StoreView.cs index ed9f7054b8..c0126b0c95 100644 --- a/src/neo/Persistence/StoreView.cs +++ b/src/neo/Persistence/StoreView.cs @@ -2,7 +2,6 @@ using Neo.IO.Caching; using Neo.Ledger; using Neo.Network.P2P.Payloads; -using System; namespace Neo.Persistence { diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index cb00e9f648..095c2d522f 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -11,7 +11,7 @@ public class InteropDescriptor public long Price { get; } public Func PriceCalculator { get; } public Func StoragePriceCalculator { get; } - public bool IsStateful { get; } + public bool IsStateful { get; } = false; public TriggerType AllowedTriggers { get; } public CallFlags RequiredCallFlags { get; } diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 91f411b133..a6b0c1aa35 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -84,7 +84,9 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte Id = context.Id, Key = key }; + if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false; + if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant)) { // If put 'value' is empty (and non-const), we remove it (implicit `Storage.Delete`) diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 444f6d3407..a00a8b02a1 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -15,14 +15,14 @@ static InteropService() t.GetFields()[0].GetValue(null); } - public static IEnumerable SupportedMethods() + public static long GetPrice(uint hash) { - return methods.Values; + return methods[hash].GetPrice(); } - public static long GetPrice(uint hash) + public static long GetPrice(uint hash, ApplicationEngine applicationEngine) { - return methods[hash].GetPrice(); + return methods[hash].GetPrice(applicationEngine); } public static long GetPrice(uint hash, EvaluationStack stack) @@ -30,9 +30,9 @@ public static long GetPrice(uint hash, EvaluationStack stack) return methods[hash].GetPrice(stack); } - public static long GetPrice(uint hash, ApplicationEngine applicationEngine) + public static IEnumerable SupportedMethods() { - return methods[hash].GetPrice(applicationEngine); + return methods.Values; } internal static bool Invoke(ApplicationEngine engine, uint method) diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 39c7102bfa..9409ec7eb9 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -223,7 +223,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null accounts = new[] { from }; } using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) - { + { HashSet cosignerList = new HashSet(); byte[] script; List<(UInt160 Account, BigInteger Value)> balances_gas = null; @@ -291,7 +291,6 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Transac throw new ArgumentException($"The address {sender.ToString()} was not found in the wallet"); accounts = new[] { sender }; } - using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) { var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); @@ -322,9 +321,9 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Transacti tx.SystemFee = Math.Max(engine.GasConsumed - ApplicationEngine.GasFree, 0); if (tx.SystemFee > 0) { - long d = (long)NativeContract.GAS.Factor; - long remainder = tx.SystemFee % d; - if (remainder > 0) + long d = (long)NativeContract.GAS.Factor; + long remainder = tx.SystemFee % d; + if (remainder > 0) tx.SystemFee += d - remainder; else if (remainder < 0) tx.SystemFee -= remainder; From 5074592affaad23e28d7bec6457a55764716a83f Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 15:56:34 +0800 Subject: [PATCH 16/50] add recycle reward gas --- src/neo/Ledger/Blockchain.cs | 27 ++++++++++++++- src/neo/Network/P2P/Payloads/Transaction.cs | 4 +-- src/neo/SmartContract/ApplicationEngine.cs | 19 ++++------- .../SmartContract/Native/Tokens/GasToken.cs | 33 +++++++------------ .../SmartContract/UT_InteropPrices.cs | 2 +- 5 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index f329e167ab..c87c6efba3 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Threading; using System.Threading.Tasks; @@ -96,6 +97,7 @@ static Blockchain() onPersistNativeContractScript = sb.ToArray(); } + } public Blockchain(NeoSystem system, IStore store) @@ -493,6 +495,7 @@ private void Persist(Block block) using (SnapshotView snapshot = GetSnapshot()) { List all_application_executed = new List(); + List recycleRewardGasTx = new List(); snapshot.PersistingBlock = block; if (block.Index > 0) { @@ -522,7 +525,11 @@ private void Persist(Block block) state.VMState = engine.Execute(); if (state.VMState == VMState.HALT) { - tx.SysFeeCredit = engine.GasCredit; + if (engine.RecyclingRewardGas > 0) + { + tx.RecycleRewardGas = engine.RecyclingRewardGas; + recycleRewardGasTx.Add(tx); + } engine.Snapshot.Commit(); } ApplicationExecuted application_executed = new ApplicationExecuted(engine); @@ -530,6 +537,24 @@ private void Persist(Block block) all_application_executed.Add(application_executed); } } + foreach(Transaction tx in recycleRewardGasTx) + { + Script onRecycleRewardGasScript = null; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); + sb.Emit(OpCode.THROWIFNOT); + onRecycleRewardGasScript = sb.ToArray(); + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) + { + engine.LoadScript(onRecycleRewardGasScript); + if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); + ApplicationExecuted application_executed = new ApplicationExecuted(engine); + Context.System.EventStream.Publish(application_executed); + all_application_executed.Add(application_executed); + } + } snapshot.BlockHashIndex.GetAndChange().Set(block); if (block.Index == header_index.Count) { diff --git a/src/neo/Network/P2P/Payloads/Transaction.cs b/src/neo/Network/P2P/Payloads/Transaction.cs index 5d58122891..480e805499 100644 --- a/src/neo/Network/P2P/Payloads/Transaction.cs +++ b/src/neo/Network/P2P/Payloads/Transaction.cs @@ -68,10 +68,10 @@ public Cosigner[] Cosigners public long FeePerByte => NetworkFee / Size; /// - /// Credit for releasing storage. + /// reward gas for releasing storage. /// Calculated after running the TX. /// - public long SysFeeCredit { get; set; } + public long RecycleRewardGas; private UInt256 _hash = null; public UInt256 Hash diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 6e1341767e..4b89632fdc 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -22,7 +22,6 @@ public partial class ApplicationEngine : ExecutionEngine private readonly List notifications = new List(); private readonly List disposables = new List(); private readonly List updatedKeys = new List(); - private long maxConsumedGas = 0; public TriggerType Trigger { get; } public IVerifiable ScriptContainer { get; } @@ -30,10 +29,9 @@ public partial class ApplicationEngine : ExecutionEngine public long GasConsumed { get; private set; } = 0; /* - GasCredit is a negative number, which shows how many gas should be paybacked in systemfee + RecyclingRewardGas shows how many gas should be recycled as a reward in systemfee */ - public long GasCredit { get; private set; } = 0; - public long MinimumGasRequired { get { return Math.Max(GasConsumed, maxConsumedGas); } } + public long RecyclingRewardGas { get; private set; } = 0; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; @@ -75,15 +73,12 @@ private bool AddGas(long gas) { if (gas < 0) { - GasCredit = checked(GasCredit + gas); - //if gas is negative,GasConsumed will be reduced,so keep the max GasConsumed - if (GasConsumed > maxConsumedGas) - { - maxConsumedGas = GasConsumed; - } + RecyclingRewardGas = checked(RecyclingRewardGas + (-gas)); + } + else + { + GasConsumed = checked(GasConsumed + gas); } - - GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; } diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index 4e1f932b3b..c7c63eaa4c 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -36,34 +36,15 @@ internal override bool Initialize(ApplicationEngine engine) return true; } - /// - /// The paid final paid amount is calculated based based on the sysfee and sysfeeCredit - /// - /// - /// - private long CalculateSystemFeeWithPayback(Transaction tx) - { - long gasUnit = (long)GAS.Factor; - long finalFee = Math.Max(tx.SystemFee + tx.SysFeeCredit, 0); - long remainder = finalFee % gasUnit; - finalFee += gasUnit - remainder; - return finalFee; - } - protected override bool OnPersist(ApplicationEngine engine) { if (!base.OnPersist(engine)) return false; - long sysFeeWithStorageDiscount = 0; foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) - { - var sysFee = CalculateSystemFeeWithPayback(tx); - Burn(engine, tx.Sender, sysFee + tx.NetworkFee); - sysFeeWithStorageDiscount += sysFee; - } + Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); Mint(engine, primary, engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.NetworkFee)); - BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + sysFeeWithStorageDiscount; + BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.SystemFee); StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(engine.Snapshot.PersistingBlock.Index)); engine.Snapshot.Storages.Add(key, new StorageItem { @@ -73,6 +54,16 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } + [ContractMethod(0, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "amount" })] + private StackItem OnRecycleRewardGas(ApplicationEngine engine, VMArray args) + { + if (engine.Trigger != TriggerType.System) return false; + UInt160 acount = new UInt160(args[0].GetSpan()); + BigInteger amount = args[1].GetBigInteger(); + Mint(engine, acount, amount); + return true; + } + [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, SafeMethod = true)] private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index e0a8357880..278effe1fe 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -358,7 +358,7 @@ public void TestCalculateMinimumRequiredToRun() ae.LoadScript(script); ae.Execute(); finalConsumedGas = ae.GasConsumed; - gasCredit = ae.GasCredit; + gasCredit = ae.RecyclingRewardGas; minimumRequiredToRun = ae.MinimumGasRequired; } From 764c67dc84b300348a278175bc115420568c572f Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 16:05:09 +0800 Subject: [PATCH 17/50] update --- src/neo/Ledger/Blockchain.cs | 8 +++----- src/neo/SmartContract/InteropDescriptor.cs | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index c87c6efba3..5518ac7ae9 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -14,7 +14,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using System.Threading; using System.Threading.Tasks; @@ -97,7 +96,6 @@ static Blockchain() onPersistNativeContractScript = sb.ToArray(); } - } public Blockchain(NeoSystem system, IStore store) @@ -495,7 +493,7 @@ private void Persist(Block block) using (SnapshotView snapshot = GetSnapshot()) { List all_application_executed = new List(); - List recycleRewardGasTx = new List(); + List recycleRewardGasTxs = new List(); snapshot.PersistingBlock = block; if (block.Index > 0) { @@ -528,7 +526,7 @@ private void Persist(Block block) if (engine.RecyclingRewardGas > 0) { tx.RecycleRewardGas = engine.RecyclingRewardGas; - recycleRewardGasTx.Add(tx); + recycleRewardGasTxs.Add(tx); } engine.Snapshot.Commit(); } @@ -537,7 +535,7 @@ private void Persist(Block block) all_application_executed.Add(application_executed); } } - foreach(Transaction tx in recycleRewardGasTx) + foreach(Transaction tx in recycleRewardGasTxs) { Script onRecycleRewardGasScript = null; using (ScriptBuilder sb = new ScriptBuilder()) diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 095c2d522f..0e6e7780ff 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -43,7 +43,6 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } - public long GetPrice() { return Price; From 54efe6a86747b8d6e548ebad22551cff33a34c88 Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 17:01:11 +0800 Subject: [PATCH 18/50] fix ut --- neo.sln | 4 - src/neo/IO/Caching/DataCache.cs | 2 +- .../SmartContract/Native/Tokens/Nep5Token.cs | 2 +- .../SmartContract/UT_InteropPrices.cs | 133 +++--------------- 4 files changed, 18 insertions(+), 123 deletions(-) diff --git a/neo.sln b/neo.sln index 6cd50689da..79e13f0185 100644 --- a/neo.sln +++ b/neo.sln @@ -24,10 +24,6 @@ Global {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.Build.0 = Release|Any CPU - {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/neo/IO/Caching/DataCache.cs b/src/neo/IO/Caching/DataCache.cs index 4529978a19..553633b35e 100644 --- a/src/neo/IO/Caching/DataCache.cs +++ b/src/neo/IO/Caching/DataCache.cs @@ -285,7 +285,7 @@ public TValue GetOrAdd(TKey key, Func factory) } } - public TValue TryGet(TKey key) + public virtual TValue TryGet(TKey key) { lock (dictionary) { diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index 3e331de487..53ecc843a3 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -61,7 +61,7 @@ protected Nep5Token() Manifest.Abi.Events = events.ToArray(); } - protected StorageKey CreateAccountKey(UInt160 account) + public StorageKey CreateAccountKey(UInt160 account) { return CreateStorageKey(Prefix_Account, account); } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 278effe1fe..09ba4a38f8 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -289,8 +289,10 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); - var expectedCost = reusedDataPrice + setupPrice; + var expectedCost = setupPrice; + var expectedReward = -reusedDataPrice; ae.GasConsumed.Should().Be(expectedCost); + ae.RecyclingRewardGas.Should().Be(expectedReward); } } @@ -327,8 +329,10 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Delete, ae); reusedDataPrice.Should().Be((skey.Key.Length + sItem.Value.Length) * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); - var expectedCost = reusedDataPrice + setupPrice; + var expectedCost = setupPrice; + var expectedReward = -reusedDataPrice; ae.GasConsumed.Should().Be(expectedCost); + ae.RecyclingRewardGas.Should().Be(expectedReward); } } @@ -350,40 +354,20 @@ public void TestCalculateMinimumRequiredToRun() mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); - long finalConsumedGas = 0; - long gasCredit = 0; + long ConsumedGas = 0; + long rewardGas = 0; long minimumRequiredToRun = 0; using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) { ae.LoadScript(script); ae.Execute(); - finalConsumedGas = ae.GasConsumed; - gasCredit = ae.RecyclingRewardGas; - minimumRequiredToRun = ae.MinimumGasRequired; - } - - //Negative Gas caused by released space. - finalConsumedGas.Should().BeLessOrEqualTo(0); - - //If you send GasConsumed, it should fail due to lack of GAS. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, finalConsumedGas, testMode: false)) - { - ae.LoadScript(script); - ae.Execute(); - ae.State.Should().Be(VMState.FAULT); - } - - //To work properly, you have to send ConsumedGas + GasCredit. - //GasCredit is a negative value. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, finalConsumedGas - gasCredit, testMode: false)) - { - ae.LoadScript(script); - ae.Execute(); - ae.State.Should().Be(VMState.HALT); - + ConsumedGas = ae.GasConsumed; + rewardGas = ae.RecyclingRewardGas; } + rewardGas.Should().Be(2 * InteropService.Storage.GasPerByte); - //The application engine already calculates the value you need to send to get this tx approved. + minimumRequiredToRun = ConsumedGas; + //If you send GasConsumed, it should HALT because GasConsumed is enough to cover the cost. using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun, testMode: false)) { ae.LoadScript(script); @@ -391,7 +375,9 @@ public void TestCalculateMinimumRequiredToRun() ae.State.Should().Be(VMState.HALT); } - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun - 1, testMode: false)) + minimumRequiredToRun = ConsumedGas - 1; + //If you send GasConsumed-1, it should FAULT because it isn't enough to cover the cost. + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun, testMode: false)) { ae.LoadScript(script); ae.Execute(); @@ -399,93 +385,6 @@ public void TestCalculateMinimumRequiredToRun() } } - [TestMethod] - public void TestPaybackExceedingSysFee() - { - var mock = new Mock() - { - CallBase = true - }; - - var mockedStoreView = new Mock() - { - CallBase = true, - }; - - var dummyBlock = Blockchain.Singleton.GetBlock(0); - var trimmedBlock = new TrimmedBlock(); - var testSnapshot = Blockchain.Singleton.GetSnapshot(); - - mockedStoreView.Setup(s => s.Storages).Returns(testSnapshot.Storages); - mockedStoreView.Setup(s => s.Contracts).Returns(testSnapshot.Contracts); - mockedStoreView.Setup(s => s.Height).Returns(0); - mockedStoreView.Setup(s => s.CurrentBlockHash).Returns(dummyBlock.Hash); - mockedStoreView.Setup(s => s.Blocks[dummyBlock.Hash]).Returns(trimmedBlock); - mockedStoreView.Setup(s => s.Clone()).Returns(mockedStoreView.Object); - - var wallet = mock.Object; - - using (wallet.Unlock("")) - { - var startingGas = 1000000; - var account = wallet.CreateAccount(); - var account2 = wallet.CreateAccount(); - var userKey = NativeContract.GAS.CreateAccountKey(account.ScriptHash); - var nep5Balance = new Nep5AccountState() - { - Balance = startingGas * NativeContract.GAS.Factor - }; - - var userBalance = TestUtils.GetStorageItem(nep5Balance.ToByteArray()); - mockedStoreView.Object.Storages.Add(userKey, userBalance); - - var balance = NativeContract.GAS.BalanceOf(mockedStoreView.Object, account.ScriptHash); - balance.Should().Be(startingGas * NativeContract.GAS.Factor); - - var transferOutput = new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account2.ScriptHash, - Value = new BigDecimal(1, 0) - }; - - //Regular transfer transaction - var transaction = wallet.MakeTransaction(new TransferOutput[] { transferOutput }, account.ScriptHash, mockedStoreView.Object); - //Minimum fee - transaction.SystemFee.Should().Be(1 * (long)NativeContract.GAS.Factor); - - var random = new Random(); - var scriptBuilder = new ScriptBuilder(); - for (BigInteger i = 1; i < 10; i++) - { - var key = i.ToByteArray(); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Delete); - } - - //fill the storage with data that will be released and builds the delete script - var script = scriptBuilder.ToArray(); - ContractState contractState = TestUtils.GetContract(script); - for (BigInteger i = 1; i < 10; i++) - { - var key = i.ToByteArray(); - var value = new byte[2048]; - random.NextBytes(value); - StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(value); - mockedStoreView.Object.Storages.Add(skey, sItem); - } - - contractState.Manifest.Features = ContractFeatures.HasStorage; - mockedStoreView.Object.Contracts.Add(script.ToScriptHash(), contractState); - - var storageReleaseTransaction = wallet.MakeTransaction(script.ToArray(), account.ScriptHash, snapshot: mockedStoreView.Object); - storageReleaseTransaction.SystemFee.Should().NotBe(0); - } - - } - private byte[] CreateExplicitDeleteScript(byte[] key) { var scriptBuilder = new ScriptBuilder(); From 3174c0d3f045cf05f9f8a879405442168a295a99 Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 17:07:17 +0800 Subject: [PATCH 19/50] fix --- src/neo/SmartContract/Native/Tokens/Nep5Token.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index 53ecc843a3..3e331de487 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -61,7 +61,7 @@ protected Nep5Token() Manifest.Abi.Events = events.ToArray(); } - public StorageKey CreateAccountKey(UInt160 account) + protected StorageKey CreateAccountKey(UInt160 account) { return CreateStorageKey(Prefix_Account, account); } From 51643d71555d9c2530f48c9c7f42f11a3aa699b0 Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 7 Feb 2020 23:57:14 +0800 Subject: [PATCH 20/50] add OnRecycleRewardGas test --- .../Native/Tokens/UT_GasToken.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index 614322aa20..3e5e002d61 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -5,6 +5,7 @@ using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; using Neo.UnitTests.Extensions; using Neo.VM; using Neo.VM.Types; @@ -179,5 +180,54 @@ public void TestGetSysFeeAmount2() NativeContract.GAS.GetSysFeeAmount(snapshot, 1).Should().Be(sys_fee); } + + [TestMethod] + public void TestOnRecycleRewardGas() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = Blockchain.Singleton.GetSnapshot(); + + using (var unlock = wallet.Unlock("123")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + snapshot.Commit(); + + NativeContract.GAS.BalanceOf(snapshot, acc.ScriptHash).Should().Be(10000 * NativeContract.GAS.Factor); + + var tx = TestUtils.GetTransaction(); + tx.Sender = acc.ScriptHash; + tx.RecycleRewardGas = (long)(1000 * NativeContract.GAS.Factor); + + Script onRecycleRewardGasScript = null; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); + sb.Emit(OpCode.THROWIFNOT); + onRecycleRewardGasScript = sb.ToArray(); + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) + { + engine.LoadScript(onRecycleRewardGasScript); + engine.Execute().Should().Be(VMState.HALT); + } + NativeContract.GAS.BalanceOf(snapshot, acc.ScriptHash).Should().Be(11000 * NativeContract.GAS.Factor); + } + } } } From 7131999ac84ede403a47e040bda8a3db1e444494 Mon Sep 17 00:00:00 2001 From: Charis Date: Mon, 10 Feb 2020 10:53:32 +0800 Subject: [PATCH 21/50] optimise --- src/neo/Ledger/Blockchain.cs | 42 ++++++++++--------- src/neo/SmartContract/ApplicationEngine.cs | 5 --- src/neo/SmartContract/InteropDescriptor.cs | 2 - .../Native/Tokens/UT_GasToken.cs | 1 - .../SmartContract/UT_InteropPrices.cs | 10 ----- 5 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 5518ac7ae9..8675265b30 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -286,7 +286,6 @@ private void OnFillMemoryPool(IEnumerable transactions) MemPool.TryAdd(tx.Hash, tx); } // Transactions originally in the pool will automatically be reverified based on their priority. - Sender.Tell(new FillCompleted()); } @@ -535,24 +534,7 @@ private void Persist(Block block) all_application_executed.Add(application_executed); } } - foreach(Transaction tx in recycleRewardGasTxs) - { - Script onRecycleRewardGasScript = null; - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); - sb.Emit(OpCode.THROWIFNOT); - onRecycleRewardGasScript = sb.ToArray(); - } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) - { - engine.LoadScript(onRecycleRewardGasScript); - if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); - ApplicationExecuted application_executed = new ApplicationExecuted(engine); - Context.System.EventStream.Publish(application_executed); - all_application_executed.Add(application_executed); - } - } + RecycleRewardGas(snapshot, all_application_executed, recycleRewardGasTxs); snapshot.BlockHashIndex.GetAndChange().Set(block); if (block.Index == header_index.Count) { @@ -586,6 +568,28 @@ private void Persist(Block block) OnPersistCompleted(block); } + private static void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List recycleRewardGasTxs) + { + foreach (Transaction tx in recycleRewardGasTxs) + { + Script onRecycleRewardGasScript = null; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); + sb.Emit(OpCode.THROWIFNOT); + onRecycleRewardGasScript = sb.ToArray(); + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) + { + engine.LoadScript(onRecycleRewardGasScript); + if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); + ApplicationExecuted application_executed = new ApplicationExecuted(engine); + Context.System.EventStream.Publish(application_executed); + all_application_executed.Add(application_executed); + } + } + } + protected override void PostStop() { base.PostStop(); diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 4b89632fdc..d977d79e71 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -1,7 +1,6 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; using System; @@ -72,13 +71,9 @@ internal bool TryAddUpdatedKey(StorageKey key) private bool AddGas(long gas) { if (gas < 0) - { RecyclingRewardGas = checked(RecyclingRewardGas + (-gas)); - } else - { GasConsumed = checked(GasConsumed + gas); - } return testMode || GasConsumed <= gas_amount; } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 0e6e7780ff..1da56a6743 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -51,7 +51,6 @@ public long GetPrice() public long GetPrice(ApplicationEngine applicationEngine) { long price = Price; - if (!IsStateful && PriceCalculator != null) { price = PriceCalculator(applicationEngine.CurrentContext.EvaluationStack); @@ -60,7 +59,6 @@ public long GetPrice(ApplicationEngine applicationEngine) { price = StoragePriceCalculator(applicationEngine); } - return price; } diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index 3e5e002d61..a2f9a7eb1d 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -192,7 +192,6 @@ public void TestOnRecycleRewardGas() var acc = wallet.CreateAccount(); // Fake balance - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 09ba4a38f8..dbbcbbebd0 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -5,15 +5,8 @@ using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; -using Neo.SmartContract.Native; -using Neo.SmartContract.Native.Tokens; using Neo.VM; -using Neo.VM.Types; using Neo.Wallets; -using Neo.Wallets.NEP6; -using System; -using System.Linq; -using System.Numerics; namespace Neo.UnitTests.SmartContract { @@ -296,8 +289,6 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() } } - - /// /// Releases 1 byte from the storage receiving Gas credit using explicit delete /// @@ -440,5 +431,4 @@ private byte[] CreatePutExScript(byte[] key, byte[] value) return scriptBuilder.ToArray(); } } - } From b5dac3d80427c1399ef379a4fd0198e051d314f0 Mon Sep 17 00:00:00 2001 From: Charis Date: Mon, 10 Feb 2020 11:08:28 +0800 Subject: [PATCH 22/50] update --- src/neo/SmartContract/ApplicationEngine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index d977d79e71..817617183b 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -27,9 +27,9 @@ public partial class ApplicationEngine : ExecutionEngine public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; - /* - RecyclingRewardGas shows how many gas should be recycled as a reward in systemfee - */ + /// + /// RecyclingRewardGas shows how many gas should be recycled as a reward in systemfee + /// public long RecyclingRewardGas { get; private set; } = 0; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; From 1da5ebff0e2eae0be345ab962b40e1a6c19eda6b Mon Sep 17 00:00:00 2001 From: Charis Date: Tue, 11 Feb 2020 18:40:19 +0800 Subject: [PATCH 23/50] optimise --- src/neo/Ledger/Blockchain.cs | 14 ++++++++------ src/neo/Network/P2P/Payloads/Transaction.cs | 6 ------ .../SmartContract/Native/Tokens/UT_GasToken.cs | 4 ++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 8675265b30..3c4de41d74 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -492,7 +492,7 @@ private void Persist(Block block) using (SnapshotView snapshot = GetSnapshot()) { List all_application_executed = new List(); - List recycleRewardGasTxs = new List(); + List> recycleRewardGasTxs = new List>(); snapshot.PersistingBlock = block; if (block.Index > 0) { @@ -524,8 +524,8 @@ private void Persist(Block block) { if (engine.RecyclingRewardGas > 0) { - tx.RecycleRewardGas = engine.RecyclingRewardGas; - recycleRewardGasTxs.Add(tx); + + recycleRewardGasTxs.Add(new Tuple(tx, engine.RecyclingRewardGas)); } engine.Snapshot.Commit(); } @@ -568,14 +568,16 @@ private void Persist(Block block) OnPersistCompleted(block); } - private static void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List recycleRewardGasTxs) + private static void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List> recycleRewardGasTxs) { - foreach (Transaction tx in recycleRewardGasTxs) + foreach (var tuple in recycleRewardGasTxs) { + var tx = tuple.Item1; + var recycleRewardGas = tuple.Item2; Script onRecycleRewardGasScript = null; using (ScriptBuilder sb = new ScriptBuilder()) { - sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); + sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, recycleRewardGas); sb.Emit(OpCode.THROWIFNOT); onRecycleRewardGasScript = sb.ToArray(); } diff --git a/src/neo/Network/P2P/Payloads/Transaction.cs b/src/neo/Network/P2P/Payloads/Transaction.cs index 480e805499..2b6523a6aa 100644 --- a/src/neo/Network/P2P/Payloads/Transaction.cs +++ b/src/neo/Network/P2P/Payloads/Transaction.cs @@ -67,12 +67,6 @@ public Cosigner[] Cosigners /// public long FeePerByte => NetworkFee / Size; - /// - /// reward gas for releasing storage. - /// Calculated after running the TX. - /// - public long RecycleRewardGas; - private UInt256 _hash = null; public UInt256 Hash { diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index a2f9a7eb1d..fa3857d02e 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -211,12 +211,12 @@ public void TestOnRecycleRewardGas() var tx = TestUtils.GetTransaction(); tx.Sender = acc.ScriptHash; - tx.RecycleRewardGas = (long)(1000 * NativeContract.GAS.Factor); + var recycleRewardGas = (long)(1000 * NativeContract.GAS.Factor); Script onRecycleRewardGasScript = null; using (ScriptBuilder sb = new ScriptBuilder()) { - sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, tx.RecycleRewardGas); + sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, recycleRewardGas); sb.Emit(OpCode.THROWIFNOT); onRecycleRewardGasScript = sb.ToArray(); } From dfc3d7a98316e1f7dcaa3d11cc5a79e62f4e8bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Naz=C3=A1rio=20Coelho?= Date: Tue, 11 Feb 2020 09:42:22 -0300 Subject: [PATCH 24/50] Removing extra space --- src/neo/Ledger/Blockchain.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 3c4de41d74..aec6780251 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -523,10 +523,7 @@ private void Persist(Block block) if (state.VMState == VMState.HALT) { if (engine.RecyclingRewardGas > 0) - { - recycleRewardGasTxs.Add(new Tuple(tx, engine.RecyclingRewardGas)); - } engine.Snapshot.Commit(); } ApplicationExecuted application_executed = new ApplicationExecuted(engine); From a856d445ee828170253a148b39afaa32fad2c63a Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 12 Feb 2020 10:05:19 +0800 Subject: [PATCH 25/50] update comment --- 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 817617183b..fd9888e80b 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -28,7 +28,7 @@ public partial class ApplicationEngine : ExecutionEngine public long GasConsumed { get; private set; } = 0; /// - /// RecyclingRewardGas shows how many gas should be recycled as a reward in systemfee + /// RecyclingRewardGas shows how many gas should be recycled as a reward /// public long RecyclingRewardGas { get; private set; } = 0; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; From ddac1f5a9bd3f53b9454cd287becad9904c2fa8b Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 12 Feb 2020 10:23:10 +0800 Subject: [PATCH 26/50] update static --- src/neo/Ledger/Blockchain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index aec6780251..f33774ba70 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -565,7 +565,7 @@ private void Persist(Block block) OnPersistCompleted(block); } - private static void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List> recycleRewardGasTxs) + private void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List> recycleRewardGasTxs) { foreach (var tuple in recycleRewardGasTxs) { From 5c61cbda64c3a1729f5daf64519a54240a807558 Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 12 Feb 2020 10:24:41 +0800 Subject: [PATCH 27/50] format --- src/neo/Ledger/Blockchain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index f33774ba70..1a67a9b4ad 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -492,7 +492,7 @@ private void Persist(Block block) using (SnapshotView snapshot = GetSnapshot()) { List all_application_executed = new List(); - List> recycleRewardGasTxs = new List>(); + List> recycleRewardGasTxs = new List>(); snapshot.PersistingBlock = block; if (block.Index > 0) { From cbb8531f3cdce59f7f7244df996d9665633105c8 Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 14 Feb 2020 14:51:48 +0800 Subject: [PATCH 28/50] revert virtual --- src/neo/IO/Caching/DataCache.cs | 2 +- .../SmartContract/UT_InteropPrices.cs | 73 ++++++++++--------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/neo/IO/Caching/DataCache.cs b/src/neo/IO/Caching/DataCache.cs index 553633b35e..4529978a19 100644 --- a/src/neo/IO/Caching/DataCache.cs +++ b/src/neo/IO/Caching/DataCache.cs @@ -285,7 +285,7 @@ public TValue GetOrAdd(TKey key, Func factory) } } - public virtual TValue TryGet(TKey key) + public TValue TryGet(TKey key) { lock (dictionary) { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index dbbcbbebd0..0a6cc739d0 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -71,12 +71,12 @@ public void ApplicationEngineVariablePrices() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(null); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); byte[] scriptPut = script; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(scriptPut); @@ -89,9 +89,9 @@ public void ApplicationEngineVariablePrices() key = new byte[] { (byte)OpCode.PUSH3 }; value = new byte[] { (byte)OpCode.PUSH3 }; script = CreatePutExScript(key, value); - + snapshot.Contracts.Add(script.ToScriptHash(), contractState); byte[] scriptPutEx = script; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(scriptPutEx); @@ -119,11 +119,11 @@ public void ApplicationEngineRegularPut() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(null); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(script); @@ -158,10 +158,11 @@ public void ApplicationEngineReusedStorage_FullReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(value); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine applicationEngine = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine applicationEngine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(applicationEngine); applicationEngine.LoadScript(script); @@ -196,11 +197,11 @@ public void ApplicationEngineReusedStorage_PartialReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(script); @@ -236,11 +237,11 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(script); @@ -267,11 +268,11 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(script); @@ -306,11 +307,11 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { Debugger debugger = new Debugger(ae); ae.LoadScript(script); @@ -341,25 +342,27 @@ public void TestCalculateMinimumRequiredToRun() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var mockedStoreView = new Mock(); - mockedStoreView.Setup(p => p.Storages.TryGet(skey)).Returns(sItem); - mockedStoreView.Setup(p => p.Contracts.TryGet(script.ToScriptHash())).Returns(contractState); + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.Storages.Add(skey, sItem); + snapshot.Contracts.Add(script.ToScriptHash(), contractState); long ConsumedGas = 0; long rewardGas = 0; long minimumRequiredToRun = 0; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, 0, testMode: true)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { ae.LoadScript(script); ae.Execute(); + ae.State.Should().Be(VMState.HALT); ConsumedGas = ae.GasConsumed; rewardGas = ae.RecyclingRewardGas; } rewardGas.Should().Be(2 * InteropService.Storage.GasPerByte); minimumRequiredToRun = ConsumedGas; + snapshot.Storages.Add(skey, sItem); //If you send GasConsumed, it should HALT because GasConsumed is enough to cover the cost. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun, testMode: false)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, minimumRequiredToRun, testMode: false)) { ae.LoadScript(script); ae.Execute(); @@ -368,7 +371,7 @@ public void TestCalculateMinimumRequiredToRun() minimumRequiredToRun = ConsumedGas - 1; //If you send GasConsumed-1, it should FAULT because it isn't enough to cover the cost. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, mockedStoreView.Object, minimumRequiredToRun, testMode: false)) + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, minimumRequiredToRun, testMode: false)) { ae.LoadScript(script); ae.Execute(); From c661c4127e5b1a410c31473d27480599c090224e Mon Sep 17 00:00:00 2001 From: Charis Date: Fri, 14 Feb 2020 15:09:41 +0800 Subject: [PATCH 29/50] format --- tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 0a6cc739d0..fd8d1ee916 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -90,6 +90,7 @@ public void ApplicationEngineVariablePrices() value = new byte[] { (byte)OpCode.PUSH3 }; script = CreatePutExScript(key, value); snapshot.Contracts.Add(script.ToScriptHash(), contractState); + byte[] scriptPutEx = script; using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) { From 1bf381568bf3f1261345860018e65164b0172801 Mon Sep 17 00:00:00 2001 From: Charis Date: Tue, 18 Feb 2020 11:28:11 +0800 Subject: [PATCH 30/50] update --- src/neo/SmartContract/ApplicationEngine.cs | 2 +- src/neo/SmartContract/InteropDescriptor.cs | 11 ----------- src/neo/SmartContract/InteropService.cs | 5 ----- src/neo/SmartContract/Native/Tokens/GasToken.cs | 2 +- tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs | 8 ++++---- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index fd9888e80b..4d27d9f7a7 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -71,7 +71,7 @@ internal bool TryAddUpdatedKey(StorageKey key) private bool AddGas(long gas) { if (gas < 0) - RecyclingRewardGas = checked(RecyclingRewardGas + (-gas)); + RecyclingRewardGas = checked(RecyclingRewardGas - gas); else GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 1da56a6743..4c8c11758e 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -62,17 +62,6 @@ public long GetPrice(ApplicationEngine applicationEngine) return price; } - public long GetPrice(EvaluationStack stack) - { -#if DEBUG - if (IsStateful) - { - throw new InvalidOperationException(); - } -#endif - return PriceCalculator is null ? Price : PriceCalculator(stack); - } - public static implicit operator uint(InteropDescriptor descriptor) { return descriptor.Hash; diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index a00a8b02a1..6b5f07e417 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -25,11 +25,6 @@ public static long GetPrice(uint hash, ApplicationEngine applicationEngine) return methods[hash].GetPrice(applicationEngine); } - public static long GetPrice(uint hash, EvaluationStack stack) - { - return methods[hash].GetPrice(stack); - } - public static IEnumerable SupportedMethods() { return methods.Values; diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index c7c63eaa4c..f9e7e04030 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -57,7 +57,7 @@ protected override bool OnPersist(ApplicationEngine engine) [ContractMethod(0, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "amount" })] private StackItem OnRecycleRewardGas(ApplicationEngine engine, VMArray args) { - if (engine.Trigger != TriggerType.System) return false; + if (engine.Trigger != TriggerType.System || args.Count != 2) return false; UInt160 acount = new UInt160(args[0].GetSpan()); BigInteger amount = args[1].GetBigInteger(); Mint(engine, acount, amount); diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index fd8d1ee916..643b0252c0 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -27,7 +27,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); - InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae.CurrentContext.EvaluationStack).Should().Be(0_00030000L); + InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae).Should().Be(0_00030000L); } // System.Storage.GetContext: 9bf667ce (price is 1) @@ -35,7 +35,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); - InteropService.GetPrice(InteropService.Storage.GetContext, ae.CurrentContext.EvaluationStack).Should().Be(0_00000400L); + InteropService.GetPrice(InteropService.Storage.GetContext, ae).Should().Be(0_00000400L); } // System.Storage.Get: 925de831 (price is 100) @@ -43,7 +43,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetHash); - InteropService.GetPrice(InteropService.Storage.Get, ae.CurrentContext.EvaluationStack).Should().Be(0_01000000L); + InteropService.GetPrice(InteropService.Storage.Get, ae).Should().Be(0_01000000L); } } @@ -58,7 +58,7 @@ public void ApplicationEngineVariablePrices() ae.LoadScript(SyscallContractCreateHash00); debugger.StepInto(); // PUSHDATA1 debugger.StepInto(); // PUSHDATA1 - InteropService.GetPrice(InteropService.Contract.Create, ae.CurrentContext.EvaluationStack).Should().Be(0_00300000L); + InteropService.GetPrice(InteropService.Contract.Create, ae).Should().Be(0_00300000L); } var key = new byte[] { (byte)OpCode.PUSH3 }; From 5be0d71c3daa4ea5b74e1314551e9a64b77f2607 Mon Sep 17 00:00:00 2001 From: Charis Date: Tue, 18 Feb 2020 11:39:22 +0800 Subject: [PATCH 31/50] remove ThrowIfNot --- src/neo/Ledger/Blockchain.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 1a67a9b4ad..b791e64fd2 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -575,7 +575,6 @@ private void RecycleRewardGas(SnapshotView snapshot, List a using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, recycleRewardGas); - sb.Emit(OpCode.THROWIFNOT); onRecycleRewardGasScript = sb.ToArray(); } using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) From 3d21376cc6ffcd5b85b3cf83c976d6e6d0ca6e11 Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 19 Feb 2020 12:04:17 +0800 Subject: [PATCH 32/50] update TryGet and engine notify --- src/neo/SmartContract/ApplicationEngine.cs | 5 +++- src/neo/SmartContract/InteropDescriptor.cs | 25 ++++++++++++++----- .../SmartContract/InteropService.Crypto.cs | 2 +- src/neo/SmartContract/InteropService.cs | 12 ++++++--- .../SmartContract/Native/Tokens/GasToken.cs | 2 +- .../SmartContract/Native/Tokens/Nep5Token.cs | 4 +-- src/neo/Wallets/Wallet.cs | 4 +-- .../SmartContract/UT_InteropPrices.cs | 22 ++++++++-------- 8 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 4d27d9f7a7..9fbbb1d2ba 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -103,7 +103,10 @@ public override void Dispose() protected override bool OnSysCall(uint method) { - if (!AddGas(InteropService.GetPrice(method, this))) + var ret = InteropService.TryGetPrice(method, this, out long price); + if (!ret) + return false; + if (!AddGas(price)) return false; return InteropService.Invoke(this, method); } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 4c8c11758e..4404bf4b73 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -8,7 +8,7 @@ public class InteropDescriptor public string Method { get; } public uint Hash { get; } internal Func Handler { get; } - public long Price { get; } + public long? Price { get; } = null; public Func PriceCalculator { get; } public Func StoragePriceCalculator { get; } public bool IsStateful { get; } = false; @@ -43,23 +43,36 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } - public long GetPrice() + public bool TryGetPrice(out long price) { - return Price; + if (Price.HasValue) + { + price = (long)Price; + return true; + } + price = 0; + return false; } - public long GetPrice(ApplicationEngine applicationEngine) + public bool TryGetPrice(ApplicationEngine applicationEngine, out long price) { - long price = Price; if (!IsStateful && PriceCalculator != null) { price = PriceCalculator(applicationEngine.CurrentContext.EvaluationStack); + return true; } else if (IsStateful && StoragePriceCalculator != null) { price = StoragePriceCalculator(applicationEngine); + return true; + } + else if (Price.HasValue) + { + price = (long)Price; + return true; } - return price; + price = 0; + return false; } public static implicit operator uint(InteropDescriptor descriptor) diff --git a/src/neo/SmartContract/InteropService.Crypto.cs b/src/neo/SmartContract/InteropService.Crypto.cs index 448d312ea4..59251eba80 100644 --- a/src/neo/SmartContract/InteropService.Crypto.cs +++ b/src/neo/SmartContract/InteropService.Crypto.cs @@ -24,7 +24,7 @@ private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack) if (item is Array array) n = array.Count; else n = (int)item.GetBigInteger(); if (n < 1) return 0; - return ECDsaVerify.Price * n; + return (ECDsaVerify.Price.HasValue? (long)ECDsaVerify.Price : 0) * n; } private static bool Crypto_ECDsaVerify(ApplicationEngine engine) diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 6b5f07e417..57cf7e4d14 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -15,14 +15,18 @@ static InteropService() t.GetFields()[0].GetValue(null); } - public static long GetPrice(uint hash) + public static bool TryGetPrice(uint hash, out long value) { - return methods[hash].GetPrice(); + var ret = methods[hash].TryGetPrice(out long price); + value = price; + return ret; } - public static long GetPrice(uint hash, ApplicationEngine applicationEngine) + public static bool TryGetPrice(uint hash, ApplicationEngine applicationEngine, out long value) { - return methods[hash].GetPrice(applicationEngine); + var ret = methods[hash].TryGetPrice(applicationEngine,out long price); + value = price; + return ret; } public static IEnumerable SupportedMethods() diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index f9e7e04030..c4c918625c 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -60,7 +60,7 @@ private StackItem OnRecycleRewardGas(ApplicationEngine engine, VMArray args) if (engine.Trigger != TriggerType.System || args.Count != 2) return false; UInt160 acount = new UInt160(args[0].GetSpan()); BigInteger amount = args[1].GetBigInteger(); - Mint(engine, acount, amount); + Mint(engine, acount, amount, "RecycleRewardGas"); return true; } diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index 3e331de487..d1a33e9d5c 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -66,7 +66,7 @@ protected StorageKey CreateAccountKey(UInt160 account) return CreateStorageKey(Prefix_Account, account); } - internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount) + internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, string actionName = "Transfer") { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; @@ -86,7 +86,7 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger totalSupply = new BigInteger(storage.Value); totalSupply += amount; storage.Value = totalSupply.ToByteArrayStandard(); - engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount })); + engine.SendNotification(Hash, new Array(new StackItem[] { actionName, StackItem.Null, account.ToArray(), amount })); } internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 9409ec7eb9..b3bfdda328 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -354,7 +354,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price)? price : 0); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { @@ -366,7 +366,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; using (ScriptBuilder sb = new ScriptBuilder()) networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify) * n; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price)? price : 0) * n; } else { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 643b0252c0..61fbff3bef 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -27,7 +27,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); - InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae).Should().Be(0_00030000L); + (InteropService.TryGetPrice(InteropService.Runtime.CheckWitness, ae, out long price) ? price : 0).Should().Be(0_00030000L); } // System.Storage.GetContext: 9bf667ce (price is 1) @@ -35,7 +35,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); - InteropService.GetPrice(InteropService.Storage.GetContext, ae).Should().Be(0_00000400L); + (InteropService.TryGetPrice(InteropService.Storage.GetContext, ae, out long price) ? price : 0).Should().Be(0_00000400L); } // System.Storage.Get: 925de831 (price is 100) @@ -43,7 +43,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetHash); - InteropService.GetPrice(InteropService.Storage.Get, ae).Should().Be(0_01000000L); + (InteropService.TryGetPrice(InteropService.Storage.Get, ae, out long price) ? price : 0).Should().Be(0_01000000L); } } @@ -58,7 +58,7 @@ public void ApplicationEngineVariablePrices() ae.LoadScript(SyscallContractCreateHash00); debugger.StepInto(); // PUSHDATA1 debugger.StepInto(); // PUSHDATA1 - InteropService.GetPrice(InteropService.Contract.Create, ae).Should().Be(0_00300000L); + (InteropService.TryGetPrice(InteropService.Contract.Create, ae, out long price) ? price : 0).Should().Be(0_00300000L); } var key = new byte[] { (byte)OpCode.PUSH3 }; @@ -83,7 +83,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.Put, ae).Should().Be(200000L); + (InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0) .Should().Be(200000L); } key = new byte[] { (byte)OpCode.PUSH3 }; @@ -99,7 +99,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.PutEx, ae).Should().Be(200000L); + (InteropService.TryGetPrice(InteropService.Storage.PutEx, ae, out long price)? price : 0).Should().Be(200000L); } } @@ -132,7 +132,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var defaultDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); var expectedCost = defaultDataPrice + setupPrice; debugger.Execute(); @@ -171,7 +171,7 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = applicationEngine.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine); + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, applicationEngine, out long price)? price : 0; reusedDataPrice.Should().Be(0); debugger.Execute(); var expectedCost = reusedDataPrice + setupPrice; @@ -210,7 +210,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.StepInto(); var expectedCost = reusedDataPrice + setupPrice; @@ -281,7 +281,7 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); var expectedCost = setupPrice; @@ -319,7 +319,7 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Delete, ae); + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Delete, ae, out long price)? price : 0; reusedDataPrice.Should().Be((skey.Key.Length + sItem.Value.Length) * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); var expectedCost = setupPrice; From 938af9548229084425d843728f841aa0344d90b9 Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 19 Feb 2020 12:14:33 +0800 Subject: [PATCH 33/50] format --- src/neo/SmartContract/InteropService.Crypto.cs | 2 +- src/neo/SmartContract/InteropService.cs | 2 +- src/neo/Wallets/Wallet.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Crypto.cs b/src/neo/SmartContract/InteropService.Crypto.cs index 59251eba80..65c2609011 100644 --- a/src/neo/SmartContract/InteropService.Crypto.cs +++ b/src/neo/SmartContract/InteropService.Crypto.cs @@ -24,7 +24,7 @@ private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack) if (item is Array array) n = array.Count; else n = (int)item.GetBigInteger(); if (n < 1) return 0; - return (ECDsaVerify.Price.HasValue? (long)ECDsaVerify.Price : 0) * n; + return (ECDsaVerify.Price.HasValue ? (long)ECDsaVerify.Price : 0) * n; } private static bool Crypto_ECDsaVerify(ApplicationEngine engine) diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 57cf7e4d14..71a467aa6b 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -24,7 +24,7 @@ public static bool TryGetPrice(uint hash, out long value) public static bool TryGetPrice(uint hash, ApplicationEngine applicationEngine, out long value) { - var ret = methods[hash].TryGetPrice(applicationEngine,out long price); + var ret = methods[hash].TryGetPrice(applicationEngine, out long price); value = price; return ret; } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index b3bfdda328..caee2f1321 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -354,7 +354,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price)? price : 0); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price) ? price : 0); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { @@ -366,7 +366,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; using (ScriptBuilder sb = new ScriptBuilder()) networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price)? price : 0) * n; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price) ? price : 0) * n; } else { From c39bb894f32824179c4b13dadc1bf74f1ef16215 Mon Sep 17 00:00:00 2001 From: Charis Date: Wed, 19 Feb 2020 12:22:13 +0800 Subject: [PATCH 34/50] format --- .../SmartContract/UT_InteropPrices.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 61fbff3bef..4a66c98ff6 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -83,7 +83,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - (InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0) .Should().Be(200000L); + (InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0).Should().Be(200000L); } key = new byte[] { (byte)OpCode.PUSH3 }; @@ -99,7 +99,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - (InteropService.TryGetPrice(InteropService.Storage.PutEx, ae, out long price)? price : 0).Should().Be(200000L); + (InteropService.TryGetPrice(InteropService.Storage.PutEx, ae, out long price) ? price : 0).Should().Be(200000L); } } @@ -132,7 +132,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var defaultDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; + var defaultDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); var expectedCost = defaultDataPrice + setupPrice; debugger.Execute(); @@ -171,7 +171,7 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = applicationEngine.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, applicationEngine, out long price)? price : 0; + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, applicationEngine, out long price) ? price : 0; reusedDataPrice.Should().Be(0); debugger.Execute(); var expectedCost = reusedDataPrice + setupPrice; @@ -210,7 +210,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.StepInto(); var expectedCost = reusedDataPrice + setupPrice; @@ -281,7 +281,7 @@ public void ApplicationEngineReleaseStorage_ImplicitDelete() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price)? price : 0; + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); var expectedCost = setupPrice; @@ -319,7 +319,7 @@ public void ApplicationEngineReleaseStorage_ExplicitDelete() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Delete, ae, out long price)? price : 0; + var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Delete, ae, out long price) ? price : 0; reusedDataPrice.Should().Be((skey.Key.Length + sItem.Value.Length) * InteropService.Storage.GasPerReleasedByte); debugger.StepInto(); var expectedCost = setupPrice; From b8a6824b8d5bcfa897e1e9e7b763e884be939f1a Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 19 Feb 2020 09:34:27 +0100 Subject: [PATCH 35/50] Update ApplicationEngine.cs --- src/neo/SmartContract/ApplicationEngine.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 9fbbb1d2ba..2b13816356 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -103,8 +103,7 @@ public override void Dispose() protected override bool OnSysCall(uint method) { - var ret = InteropService.TryGetPrice(method, this, out long price); - if (!ret) + if (!InteropService.TryGetPrice(method, this, out long price)) return false; if (!AddGas(price)) return false; From ca9ab6896fcb58b44df72e2139d96004b4299674 Mon Sep 17 00:00:00 2001 From: Charis Date: Thu, 20 Feb 2020 10:26:33 +0800 Subject: [PATCH 36/50] update mint --- src/neo/SmartContract/Native/Tokens/Nep5Token.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index d1a33e9d5c..eac1277c84 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -66,7 +66,7 @@ protected StorageKey CreateAccountKey(UInt160 account) return CreateStorageKey(Prefix_Account, account); } - internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, string actionName = "Transfer") + internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, string reason = null) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; @@ -86,7 +86,10 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger totalSupply = new BigInteger(storage.Value); totalSupply += amount; storage.Value = totalSupply.ToByteArrayStandard(); - engine.SendNotification(Hash, new Array(new StackItem[] { actionName, StackItem.Null, account.ToArray(), amount })); + if(reason != null) + engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount, reason})); + else + engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount})); } internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) From d23334601c6c653fe24742bbd1a3c60b80a19c03 Mon Sep 17 00:00:00 2001 From: Charis Date: Thu, 20 Feb 2020 10:31:43 +0800 Subject: [PATCH 37/50] format --- src/neo/SmartContract/Native/Tokens/Nep5Token.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index eac1277c84..f087615413 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -86,10 +86,10 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger totalSupply = new BigInteger(storage.Value); totalSupply += amount; storage.Value = totalSupply.ToByteArrayStandard(); - if(reason != null) - engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount, reason})); + if (reason != null) + engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount, reason })); else - engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount})); + engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount })); } internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) From e7c5d23c840030b0fc0ee024fbe71ba9e801cec0 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 20 Feb 2020 12:05:05 +0100 Subject: [PATCH 38/50] Fix typo --- src/neo/SmartContract/Native/Tokens/GasToken.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index c4c918625c..55d7255a84 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -58,9 +58,9 @@ protected override bool OnPersist(ApplicationEngine engine) private StackItem OnRecycleRewardGas(ApplicationEngine engine, VMArray args) { if (engine.Trigger != TriggerType.System || args.Count != 2) return false; - UInt160 acount = new UInt160(args[0].GetSpan()); + UInt160 account = new UInt160(args[0].GetSpan()); BigInteger amount = args[1].GetBigInteger(); - Mint(engine, acount, amount, "RecycleRewardGas"); + Mint(engine, account, amount, "RecycleRewardGas"); return true; } From e567bd3c5a5b45353def6bd4b16c18eeaf11d5c1 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 20 Feb 2020 12:18:22 +0100 Subject: [PATCH 39/50] Simplify --- src/neo/SmartContract/InteropDescriptor.cs | 18 +++++------------- src/neo/SmartContract/InteropService.cs | 4 ++-- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index 4404bf4b73..e43f742c74 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -9,9 +9,7 @@ public class InteropDescriptor public uint Hash { get; } internal Func Handler { get; } public long? Price { get; } = null; - public Func PriceCalculator { get; } - public Func StoragePriceCalculator { get; } - public bool IsStateful { get; } = false; + public Func PriceCalculator { get; } public TriggerType AllowedTriggers { get; } public CallFlags RequiredCallFlags { get; } @@ -24,14 +22,13 @@ internal InteropDescriptor(string method, Func handler, internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) : this(method, handler, allowedTriggers, requiredCallFlags) { - this.StoragePriceCalculator = priceCalculator; - this.IsStateful = true; + this.PriceCalculator = priceCalculator; } internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) : this(method, handler, allowedTriggers, requiredCallFlags) { - this.PriceCalculator = priceCalculator; + this.PriceCalculator = (engine) => priceCalculator(engine.CurrentContext.EvaluationStack); } private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers, CallFlags requiredCallFlags) @@ -56,14 +53,9 @@ public bool TryGetPrice(out long price) public bool TryGetPrice(ApplicationEngine applicationEngine, out long price) { - if (!IsStateful && PriceCalculator != null) - { - price = PriceCalculator(applicationEngine.CurrentContext.EvaluationStack); - return true; - } - else if (IsStateful && StoragePriceCalculator != null) + if (PriceCalculator != null) { - price = StoragePriceCalculator(applicationEngine); + price = PriceCalculator(applicationEngine); return true; } else if (Price.HasValue) diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 71a467aa6b..48a3501707 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -60,9 +60,9 @@ private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags flags) + private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, flags); + InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requiredCallFlags); methods.Add(descriptor.Hash, descriptor); return descriptor; } From 7134701c337c33d0f96799c600b375844969310b Mon Sep 17 00:00:00 2001 From: Luchuan Date: Tue, 3 Mar 2020 17:48:30 +0800 Subject: [PATCH 40/50] reset, charge only for increment of data, add put basic fee --- src/neo/Ledger/Blockchain.cs | 28 +-- src/neo/SmartContract/ApplicationEngine.cs | 30 +-- src/neo/SmartContract/InteropDescriptor.cs | 28 +-- .../SmartContract/InteropService.Crypto.cs | 2 +- .../SmartContract/InteropService.Storage.cs | 30 +-- src/neo/SmartContract/InteropService.cs | 13 +- .../SmartContract/Native/Tokens/GasToken.cs | 10 - .../SmartContract/Native/Tokens/Nep5Token.cs | 7 +- src/neo/Wallets/Wallet.cs | 4 +- .../Native/Tokens/UT_GasToken.cs | 49 ---- .../SmartContract/UT_InteropPrices.cs | 216 ++---------------- 11 files changed, 36 insertions(+), 381 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index b791e64fd2..9090a5b339 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -286,6 +286,7 @@ private void OnFillMemoryPool(IEnumerable transactions) MemPool.TryAdd(tx.Hash, tx); } // Transactions originally in the pool will automatically be reverified based on their priority. + Sender.Tell(new FillCompleted()); } @@ -492,7 +493,6 @@ private void Persist(Block block) using (SnapshotView snapshot = GetSnapshot()) { List all_application_executed = new List(); - List> recycleRewardGasTxs = new List>(); snapshot.PersistingBlock = block; if (block.Index > 0) { @@ -522,8 +522,6 @@ private void Persist(Block block) state.VMState = engine.Execute(); if (state.VMState == VMState.HALT) { - if (engine.RecyclingRewardGas > 0) - recycleRewardGasTxs.Add(new Tuple(tx, engine.RecyclingRewardGas)); engine.Snapshot.Commit(); } ApplicationExecuted application_executed = new ApplicationExecuted(engine); @@ -531,7 +529,6 @@ private void Persist(Block block) all_application_executed.Add(application_executed); } } - RecycleRewardGas(snapshot, all_application_executed, recycleRewardGasTxs); snapshot.BlockHashIndex.GetAndChange().Set(block); if (block.Index == header_index.Count) { @@ -565,29 +562,6 @@ private void Persist(Block block) OnPersistCompleted(block); } - private void RecycleRewardGas(SnapshotView snapshot, List all_application_executed, List> recycleRewardGasTxs) - { - foreach (var tuple in recycleRewardGasTxs) - { - var tx = tuple.Item1; - var recycleRewardGas = tuple.Item2; - Script onRecycleRewardGasScript = null; - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, recycleRewardGas); - onRecycleRewardGasScript = sb.ToArray(); - } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) - { - engine.LoadScript(onRecycleRewardGasScript); - if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); - ApplicationExecuted application_executed = new ApplicationExecuted(engine); - Context.System.EventStream.Publish(application_executed); - all_application_executed.Add(application_executed); - } - } - } - protected override void PostStop() { base.PostStop(); diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 2b13816356..550932cb3a 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -20,17 +20,12 @@ public partial class ApplicationEngine : ExecutionEngine private readonly bool testMode; private readonly List notifications = new List(); private readonly List disposables = new List(); - private readonly List updatedKeys = new List(); public TriggerType Trigger { get; } public IVerifiable ScriptContainer { get; } public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; - /// - /// RecyclingRewardGas shows how many gas should be recycled as a reward - /// - public long RecyclingRewardGas { get; private set; } = 0; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; @@ -52,28 +47,9 @@ internal T AddDisposable(T disposable) where T : IDisposable return disposable; } - internal bool HasUpdatedKey(StorageKey key) - { - return updatedKeys.Contains(key); - } - - internal bool TryAddUpdatedKey(StorageKey key) - { - bool keyAdded = false; - if (!updatedKeys.Contains(key)) - { - updatedKeys.Add(key); - keyAdded = true; - } - return keyAdded; - } - private bool AddGas(long gas) { - if (gas < 0) - RecyclingRewardGas = checked(RecyclingRewardGas - gas); - else - GasConsumed = checked(GasConsumed + gas); + GasConsumed = checked(GasConsumed + gas); return testMode || GasConsumed <= gas_amount; } @@ -103,9 +79,7 @@ public override void Dispose() protected override bool OnSysCall(uint method) { - if (!InteropService.TryGetPrice(method, this, out long price)) - return false; - if (!AddGas(price)) + if (!AddGas(InteropService.GetPrice(method, this))) return false; return InteropService.Invoke(this, method); } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index e43f742c74..ff5bb645a0 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -8,7 +8,7 @@ public class InteropDescriptor public string Method { get; } public uint Hash { get; } internal Func Handler { get; } - public long? Price { get; } = null; + public long Price { get; } public Func PriceCalculator { get; } public TriggerType AllowedTriggers { get; } public CallFlags RequiredCallFlags { get; } @@ -40,31 +40,9 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } - public bool TryGetPrice(out long price) + public long GetPrice(ApplicationEngine applicationEngine) { - if (Price.HasValue) - { - price = (long)Price; - return true; - } - price = 0; - return false; - } - - public bool TryGetPrice(ApplicationEngine applicationEngine, out long price) - { - if (PriceCalculator != null) - { - price = PriceCalculator(applicationEngine); - return true; - } - else if (Price.HasValue) - { - price = (long)Price; - return true; - } - price = 0; - return false; + return PriceCalculator is null ? Price : PriceCalculator(applicationEngine); } public static implicit operator uint(InteropDescriptor descriptor) diff --git a/src/neo/SmartContract/InteropService.Crypto.cs b/src/neo/SmartContract/InteropService.Crypto.cs index 65c2609011..448d312ea4 100644 --- a/src/neo/SmartContract/InteropService.Crypto.cs +++ b/src/neo/SmartContract/InteropService.Crypto.cs @@ -24,7 +24,7 @@ private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack) if (item is Array array) n = array.Count; else n = (int)item.GetBigInteger(); if (n < 1) return 0; - return (ECDsaVerify.Price.HasValue ? (long)ECDsaVerify.Price : 0) * n; + return ECDsaVerify.Price * n; } private static bool Crypto_ECDsaVerify(ApplicationEngine engine) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index a6b0c1aa35..aed3dcdbd5 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -12,7 +12,6 @@ partial class InteropService public static class Storage { public const long GasPerByte = 100000; - public const long GasPerReleasedByte = -GasPerByte; public const int MaxKeySize = 64; public const int MaxValueSize = ushort.MaxValue; @@ -23,25 +22,7 @@ public static class Storage 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, GetDeletePrice, TriggerType.Application, CallFlags.AllowModifyStates); - - private static long GetDeletePrice(ApplicationEngine engine) - { - var stack = engine.CurrentContext.EvaluationStack; - var key = stack.Peek(1); - if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) - return 0_01000000; - StorageContext context = _interface.GetInterface(); - StorageKey skey = new StorageKey - { - Id = context.Id, - Key = key.GetSpan().ToArray() - }; - var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) - return 0_01000000; - return (skeyValue.Value.Length + key.GetByteLength()) * GasPerReleasedByte; - } + public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates); private static long GetStoragePrice(ApplicationEngine engine) { @@ -51,6 +32,7 @@ private static long GetStoragePrice(ApplicationEngine engine) var newDataSize = value.IsNull ? 0 : value.GetByteLength(); if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) return (key.GetByteLength() + newDataSize) * GasPerByte; + StorageContext context = _interface.GetInterface(); StorageKey skey = new StorageKey { @@ -58,13 +40,13 @@ private static long GetStoragePrice(ApplicationEngine engine) Key = key.GetSpan().ToArray() }; var skeyValue = engine.Snapshot.Storages.TryGet(skey); - if (skeyValue == null || skeyValue.Value == null || skeyValue.Value.Length == 0 || engine.HasUpdatedKey(skey)) + if (skeyValue is null || skeyValue.Value is null || skeyValue.Value.Length == 0) return (key.GetByteLength() + newDataSize) * GasPerByte; + var currentOccupiedBytes = skeyValue.Value.Length; if (newDataSize <= currentOccupiedBytes) { - var releasedBytes = currentOccupiedBytes - newDataSize; - return releasedBytes * GasPerReleasedByte; + return 1 * GasPerByte; } else { @@ -98,7 +80,6 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte item.Value = value; item.IsConstant = flags.HasFlag(StorageFlags.Constant); } - engine.TryAddUpdatedKey(skey); return true; } @@ -210,7 +191,6 @@ private static bool Storage_Delete(ApplicationEngine engine) }; if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false; engine.Snapshot.Storages.Delete(key); - engine.TryAddUpdatedKey(key); return true; } return false; diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 48a3501707..3822626df8 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -15,18 +15,9 @@ static InteropService() t.GetFields()[0].GetValue(null); } - public static bool TryGetPrice(uint hash, out long value) + public static long GetPrice(uint hash, ApplicationEngine applicationEngine) { - var ret = methods[hash].TryGetPrice(out long price); - value = price; - return ret; - } - - public static bool TryGetPrice(uint hash, ApplicationEngine applicationEngine, out long value) - { - var ret = methods[hash].TryGetPrice(applicationEngine, out long price); - value = price; - return ret; + return methods[hash].GetPrice(applicationEngine); } public static IEnumerable SupportedMethods() diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index 55d7255a84..7e89bd02c2 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -54,16 +54,6 @@ protected override bool OnPersist(ApplicationEngine engine) return true; } - [ContractMethod(0, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Hash160, ContractParameterType.Integer }, ParameterNames = new[] { "account", "amount" })] - private StackItem OnRecycleRewardGas(ApplicationEngine engine, VMArray args) - { - if (engine.Trigger != TriggerType.System || args.Count != 2) return false; - UInt160 account = new UInt160(args[0].GetSpan()); - BigInteger amount = args[1].GetBigInteger(); - Mint(engine, account, amount, "RecycleRewardGas"); - return true; - } - [ContractMethod(0_01000000, ContractParameterType.Integer, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "index" }, SafeMethod = true)] private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) { diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs index f087615413..3e331de487 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -66,7 +66,7 @@ protected StorageKey CreateAccountKey(UInt160 account) return CreateStorageKey(Prefix_Account, account); } - internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, string reason = null) + internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; @@ -86,10 +86,7 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger totalSupply = new BigInteger(storage.Value); totalSupply += amount; storage.Value = totalSupply.ToByteArrayStandard(); - if (reason != null) - engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount, reason })); - else - engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount })); + engine.SendNotification(Hash, new Array(new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount })); } internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index cb7f47b972..3fe4c61652 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -354,7 +354,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price) ? price : 0); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { @@ -366,7 +366,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; using (ScriptBuilder sb = new ScriptBuilder()) networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + (InteropService.TryGetPrice(InteropService.Crypto.ECDsaVerify, out long price) ? price : 0) * n; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null) * n; } else { diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index fa3857d02e..614322aa20 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -5,7 +5,6 @@ using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; -using Neo.SmartContract.Native.Tokens; using Neo.UnitTests.Extensions; using Neo.VM; using Neo.VM.Types; @@ -180,53 +179,5 @@ public void TestGetSysFeeAmount2() NativeContract.GAS.GetSysFeeAmount(snapshot, 1).Should().Be(sys_fee); } - - [TestMethod] - public void TestOnRecycleRewardGas() - { - var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); - - using (var unlock = wallet.Unlock("123")) - { - var acc = wallet.CreateAccount(); - - // Fake balance - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - - var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem - { - Value = new Nep5AccountState().ToByteArray() - }); - - entry.Value = new Nep5AccountState() - { - Balance = 10000 * NativeContract.GAS.Factor - } - .ToByteArray(); - - snapshot.Commit(); - - NativeContract.GAS.BalanceOf(snapshot, acc.ScriptHash).Should().Be(10000 * NativeContract.GAS.Factor); - - var tx = TestUtils.GetTransaction(); - tx.Sender = acc.ScriptHash; - var recycleRewardGas = (long)(1000 * NativeContract.GAS.Factor); - - Script onRecycleRewardGasScript = null; - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitAppCall(NativeContract.GAS.Hash, "onRecycleRewardGas", tx.Sender, recycleRewardGas); - sb.Emit(OpCode.THROWIFNOT); - onRecycleRewardGasScript = sb.ToArray(); - } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) - { - engine.LoadScript(onRecycleRewardGasScript); - engine.Execute().Should().Be(VMState.HALT); - } - NativeContract.GAS.BalanceOf(snapshot, acc.ScriptHash).Should().Be(11000 * NativeContract.GAS.Factor); - } - } } } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 4a66c98ff6..dcefae7d6d 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -1,12 +1,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using Neo.Ledger; -using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; -using Neo.Wallets; namespace Neo.UnitTests.SmartContract { @@ -27,7 +24,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); - (InteropService.TryGetPrice(InteropService.Runtime.CheckWitness, ae, out long price) ? price : 0).Should().Be(0_00030000L); + InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae).Should().Be(0_00030000L); } // System.Storage.GetContext: 9bf667ce (price is 1) @@ -35,7 +32,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); - (InteropService.TryGetPrice(InteropService.Storage.GetContext, ae, out long price) ? price : 0).Should().Be(0_00000400L); + InteropService.GetPrice(InteropService.Storage.GetContext, ae).Should().Be(0_00000400L); } // System.Storage.Get: 925de831 (price is 100) @@ -43,7 +40,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetHash); - (InteropService.TryGetPrice(InteropService.Storage.Get, ae, out long price) ? price : 0).Should().Be(0_01000000L); + InteropService.GetPrice(InteropService.Storage.Get, ae).Should().Be(0_01000000L); } } @@ -58,48 +55,31 @@ public void ApplicationEngineVariablePrices() ae.LoadScript(SyscallContractCreateHash00); debugger.StepInto(); // PUSHDATA1 debugger.StepInto(); // PUSHDATA1 - (InteropService.TryGetPrice(InteropService.Contract.Create, ae, out long price) ? price : 0).Should().Be(0_00300000L); + InteropService.GetPrice(InteropService.Contract.Create, ae).Should().Be(0_00300000L); } - var key = new byte[] { (byte)OpCode.PUSH3 }; - var value = new byte[] { (byte)OpCode.PUSH3 }; - byte[] script = CreatePutScript(key, value); - - ContractState contractState = TestUtils.GetContract(script); - contractState.Manifest.Features = ContractFeatures.HasStorage; - - StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(null); - - var snapshot = Blockchain.Singleton.GetSnapshot(); - snapshot.Storages.Add(skey, sItem); - snapshot.Contracts.Add(script.ToScriptHash(), contractState); - - byte[] scriptPut = script; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) + // System.Storage.Put: e63f1884 (requires push key and value) + byte[] SyscallStoragePutHash = new byte[] { (byte)OpCode.PUSH3, (byte)OpCode.PUSH3, (byte)OpCode.PUSH0, (byte)OpCode.SYSCALL, 0xe6, 0x3f, 0x18, 0x84 }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) { Debugger debugger = new Debugger(ae); - ae.LoadScript(scriptPut); + ae.LoadScript(SyscallStoragePutHash); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - (InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0).Should().Be(200000L); + InteropService.GetPrice(InteropService.Storage.Put, ae).Should().Be(200000L); } - key = new byte[] { (byte)OpCode.PUSH3 }; - value = new byte[] { (byte)OpCode.PUSH3 }; - script = CreatePutExScript(key, value); - snapshot.Contracts.Add(script.ToScriptHash(), contractState); - - byte[] scriptPutEx = script; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) + // System.Storage.PutEx: 73e19b3a (requires push key and value) + byte[] SyscallStoragePutExHash = new byte[] { (byte)OpCode.PUSH3, (byte)OpCode.PUSH3, (byte)OpCode.PUSH0, (byte)OpCode.SYSCALL, 0x73, 0xe1, 0x9b, 0x3a }; + using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0, testMode: true)) { Debugger debugger = new Debugger(ae); - ae.LoadScript(scriptPutEx); + ae.LoadScript(SyscallStoragePutExHash); debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - (InteropService.TryGetPrice(InteropService.Storage.PutEx, ae, out long price) ? price : 0).Should().Be(200000L); + InteropService.GetPrice(InteropService.Storage.PutEx, ae).Should().Be(200000L); } } @@ -132,7 +112,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var defaultDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; + var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); var expectedCost = defaultDataPrice + setupPrice; debugger.Execute(); @@ -149,8 +129,6 @@ public void ApplicationEngineReusedStorage_FullReuse() var key = new byte[] { (byte)OpCode.PUSH1 }; var value = new byte[] { (byte)OpCode.PUSH1 }; - var mockedStoreView = new Mock(); - byte[] script = CreatePutScript(key, value); ContractState contractState = TestUtils.GetContract(script); @@ -171,8 +149,8 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = applicationEngine.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, applicationEngine, out long price) ? price : 0; - reusedDataPrice.Should().Be(0); + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine); + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.Execute(); var expectedCost = reusedDataPrice + setupPrice; applicationEngine.GasConsumed.Should().Be(expectedCost); @@ -210,7 +188,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.StepInto(); var expectedCost = reusedDataPrice + setupPrice; @@ -252,154 +230,6 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() } } - /// - /// Releases 1 byte from the storage receiving Gas credit using implicit delete - /// - [TestMethod] - public void ApplicationEngineReleaseStorage_ImplicitDelete() - { - var key = new byte[] { (byte)OpCode.PUSH1 }; - var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - - byte[] script = CreateImplicitDeleteScript(key); - - ContractState contractState = TestUtils.GetContract(script); - contractState.Manifest.Features = ContractFeatures.HasStorage; - - StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(oldValue); - - var snapshot = Blockchain.Singleton.GetSnapshot(); - snapshot.Storages.Add(skey, sItem); - snapshot.Contracts.Add(script.ToScriptHash(), contractState); - - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) - { - Debugger debugger = new Debugger(ae); - ae.LoadScript(script); - debugger.StepInto(); - debugger.StepInto(); - debugger.StepInto(); - var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Put, ae, out long price) ? price : 0; - reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerReleasedByte); - debugger.StepInto(); - var expectedCost = setupPrice; - var expectedReward = -reusedDataPrice; - ae.GasConsumed.Should().Be(expectedCost); - ae.RecyclingRewardGas.Should().Be(expectedReward); - } - } - - /// - /// Releases 1 byte from the storage receiving Gas credit using explicit delete - /// - [TestMethod] - public void ApplicationEngineReleaseStorage_ExplicitDelete() - { - var key = new byte[] { (byte)OpCode.PUSH1 }; - var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - - byte[] script = CreateExplicitDeleteScript(key); - - ContractState contractState = TestUtils.GetContract(script); - contractState.Manifest.Features = ContractFeatures.HasStorage; - - StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(oldValue); - - var snapshot = Blockchain.Singleton.GetSnapshot(); - snapshot.Storages.Add(skey, sItem); - snapshot.Contracts.Add(script.ToScriptHash(), contractState); - - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) - { - Debugger debugger = new Debugger(ae); - ae.LoadScript(script); - debugger.StepInto(); - debugger.StepInto(); - var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.TryGetPrice(InteropService.Storage.Delete, ae, out long price) ? price : 0; - reusedDataPrice.Should().Be((skey.Key.Length + sItem.Value.Length) * InteropService.Storage.GasPerReleasedByte); - debugger.StepInto(); - var expectedCost = setupPrice; - var expectedReward = -reusedDataPrice; - ae.GasConsumed.Should().Be(expectedCost); - ae.RecyclingRewardGas.Should().Be(expectedReward); - } - } - - [TestMethod] - public void TestCalculateMinimumRequiredToRun() - { - var key = new byte[] { (byte)OpCode.PUSH1 }; - var oldValue = new byte[] { (byte)OpCode.PUSH1 }; - - byte[] script = CreateExplicitDeleteScript(key); - - ContractState contractState = TestUtils.GetContract(script); - contractState.Manifest.Features = ContractFeatures.HasStorage; - - StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(oldValue); - - var snapshot = Blockchain.Singleton.GetSnapshot(); - snapshot.Storages.Add(skey, sItem); - snapshot.Contracts.Add(script.ToScriptHash(), contractState); - - long ConsumedGas = 0; - long rewardGas = 0; - long minimumRequiredToRun = 0; - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, testMode: true)) - { - ae.LoadScript(script); - ae.Execute(); - ae.State.Should().Be(VMState.HALT); - ConsumedGas = ae.GasConsumed; - rewardGas = ae.RecyclingRewardGas; - } - rewardGas.Should().Be(2 * InteropService.Storage.GasPerByte); - - minimumRequiredToRun = ConsumedGas; - snapshot.Storages.Add(skey, sItem); - //If you send GasConsumed, it should HALT because GasConsumed is enough to cover the cost. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, minimumRequiredToRun, testMode: false)) - { - ae.LoadScript(script); - ae.Execute(); - ae.State.Should().Be(VMState.HALT); - } - - minimumRequiredToRun = ConsumedGas - 1; - //If you send GasConsumed-1, it should FAULT because it isn't enough to cover the cost. - using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, snapshot, minimumRequiredToRun, testMode: false)) - { - ae.LoadScript(script); - ae.Execute(); - ae.State.Should().Be(VMState.FAULT); - } - } - - private byte[] CreateExplicitDeleteScript(byte[] key) - { - var scriptBuilder = new ScriptBuilder(); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Delete); - return scriptBuilder.ToArray(); - } - - private byte[] CreateImplicitDeleteScript(byte[] key) - { - var emptyArray = new byte[0]; - var scriptBuilder = new ScriptBuilder(); - scriptBuilder.EmitPush(emptyArray); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.Put); - return scriptBuilder.ToArray(); - } - private byte[] CreateMultiplePutScript(byte[] key, byte[] value, int times = 2) { var scriptBuilder = new ScriptBuilder(); @@ -424,15 +254,5 @@ private byte[] CreatePutScript(byte[] key, byte[] value) scriptBuilder.EmitSysCall(InteropService.Storage.Put); return scriptBuilder.ToArray(); } - - private byte[] CreatePutExScript(byte[] key, byte[] value) - { - var scriptBuilder = new ScriptBuilder(); - scriptBuilder.EmitPush(value); - scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); - scriptBuilder.EmitSysCall(InteropService.Storage.PutEx); - return scriptBuilder.ToArray(); - } } } From 1cd0e25177b5fa1cfbd50440b6ce3a344cc8a7c4 Mon Sep 17 00:00:00 2001 From: Luchuan Date: Tue, 3 Mar 2020 18:24:08 +0800 Subject: [PATCH 41/50] fix ut --- .../SmartContract/UT_InteropPrices.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index dcefae7d6d..48add4e871 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -199,7 +199,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() /// /// Use put for the same key twice. - /// Pays for 1 extra byte for the first Put and 3 extra bytes for the second put (key + value). + /// Pays for 1 extra byte for the first Put and 1 byte for the second basic fee (as value2.length == value1.length). /// [TestMethod] public void ApplicationEngineReusedStorage_PartialReuseTwice() @@ -224,9 +224,20 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() { Debugger debugger = new Debugger(ae); ae.LoadScript(script); - ae.Execute(); - //1 reused + 1 from key + 2 from value (no discount on second use) - ae.GasConsumed.Should().BeGreaterThan(4 * InteropService.Storage.GasPerByte); + debugger.StepInto(); //push key + debugger.StepInto(); //push value + debugger.StepInto(); //syscall Storage.GetContext + var setupPrice = ae.GasConsumed; + var incrementDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + incrementDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); + debugger.StepInto(); // syscall Storage.Put + + debugger.StepInto(); //push key + debugger.StepInto(); //push value + debugger.StepInto(); //syscall Storage.GetContext + setupPrice = ae.GasConsumed; + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); // = basic fee } } @@ -238,7 +249,7 @@ private byte[] CreateMultiplePutScript(byte[] key, byte[] value, int times = 2) { scriptBuilder.EmitPush(value); scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); + scriptBuilder.EmitSysCall(InteropService.Storage. ); scriptBuilder.EmitSysCall(InteropService.Storage.Put); } From 7b0ad735dc1f12036c199226be926e6297f70ca9 Mon Sep 17 00:00:00 2001 From: Luchuan Date: Tue, 3 Mar 2020 18:25:46 +0800 Subject: [PATCH 42/50] fix ut --- tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 48add4e871..b42a8a7091 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -234,10 +234,10 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() debugger.StepInto(); //push key debugger.StepInto(); //push value - debugger.StepInto(); //syscall Storage.GetContext + debugger.StepInto(); setupPrice = ae.GasConsumed; var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); - reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); // = basic fee + reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); // = PUT basic fee } } @@ -249,7 +249,7 @@ private byte[] CreateMultiplePutScript(byte[] key, byte[] value, int times = 2) { scriptBuilder.EmitPush(value); scriptBuilder.EmitPush(key); - scriptBuilder.EmitSysCall(InteropService.Storage. ); + scriptBuilder.EmitSysCall(InteropService.Storage.GetContext); scriptBuilder.EmitSysCall(InteropService.Storage.Put); } From 0fe0c2e80cd907a2f0dad1c40de28acdd3db45ff Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 4 Mar 2020 22:00:47 +0100 Subject: [PATCH 43/50] Clean --- src/neo/SmartContract/InteropService.Storage.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index aed3dcdbd5..8f3a9aaa17 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -50,8 +50,7 @@ private static long GetStoragePrice(ApplicationEngine engine) } else { - var reusedBytes = currentOccupiedBytes; - return (newDataSize - reusedBytes) * GasPerByte; + return (newDataSize - currentOccupiedBytes) * GasPerByte; } } From 0e0b63ce3b799a849cb434311a3bcfab1fbdfa15 Mon Sep 17 00:00:00 2001 From: Luchuan Date: Thu, 5 Mar 2020 10:27:34 +0800 Subject: [PATCH 44/50] add throw in GetStoragePrice if non-interface --- src/neo/SmartContract/InteropService.Storage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index aed3dcdbd5..de5eb0c18a 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -31,7 +31,7 @@ private static long GetStoragePrice(ApplicationEngine engine) var value = stack.Peek(2); var newDataSize = value.IsNull ? 0 : value.GetByteLength(); if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) - return (key.GetByteLength() + newDataSize) * GasPerByte; + throw new InvalidOperationException(); StorageContext context = _interface.GetInterface(); StorageKey skey = new StorageKey From a4893dfe581ca8a9010e120425691089d39cd51c Mon Sep 17 00:00:00 2001 From: Luchuan Date: Thu, 5 Mar 2020 11:24:37 +0800 Subject: [PATCH 45/50] fix ut --- tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index b42a8a7091..d4fbdd8bba 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -4,6 +4,7 @@ using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; +using System; namespace Neo.UnitTests.SmartContract { @@ -67,7 +68,8 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.Put, ae).Should().Be(200000L); + Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae); + act.Should().Throw(); } // System.Storage.PutEx: 73e19b3a (requires push key and value) @@ -79,7 +81,8 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - InteropService.GetPrice(InteropService.Storage.PutEx, ae).Should().Be(200000L); + Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae); + act.Should().Throw(); } } From ffad20a554868d54252b28a3781efcbb754d3a2e Mon Sep 17 00:00:00 2001 From: Luchuan Date: Wed, 11 Mar 2020 18:51:23 +0800 Subject: [PATCH 46/50] fix GetPrice parameters --- src/neo/SmartContract/ApplicationEngine.cs | 2 +- src/neo/SmartContract/InteropDescriptor.cs | 15 +++++-------- .../SmartContract/InteropService.Contract.cs | 3 ++- .../SmartContract/InteropService.Crypto.cs | 3 ++- .../SmartContract/InteropService.Storage.cs | 8 +++---- src/neo/SmartContract/InteropService.cs | 14 ++++-------- .../SmartContract/Native/NativeContract.cs | 3 ++- src/neo/Wallets/Wallet.cs | 4 ++-- .../SmartContract/UT_InteropPrices.cs | 22 +++++++++---------- 9 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 550932cb3a..500e75a6b5 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -79,7 +79,7 @@ public override void Dispose() protected override bool OnSysCall(uint method) { - if (!AddGas(InteropService.GetPrice(method, this))) + if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack, Snapshot))) return false; return InteropService.Invoke(this, method); } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index ff5bb645a0..ebc94c2f3e 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -1,3 +1,4 @@ +using Neo.Persistence; using Neo.VM; using System; @@ -9,7 +10,7 @@ public class InteropDescriptor public uint Hash { get; } internal Func Handler { get; } public long Price { get; } - public Func PriceCalculator { get; } + public Func PriceCalculator { get; } public TriggerType AllowedTriggers { get; } public CallFlags RequiredCallFlags { get; } @@ -19,18 +20,12 @@ internal InteropDescriptor(string method, Func handler, this.Price = price; } - internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) + internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) : this(method, handler, allowedTriggers, requiredCallFlags) { this.PriceCalculator = priceCalculator; } - internal InteropDescriptor(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) - : this(method, handler, allowedTriggers, requiredCallFlags) - { - this.PriceCalculator = (engine) => priceCalculator(engine.CurrentContext.EvaluationStack); - } - private InteropDescriptor(string method, Func handler, TriggerType allowedTriggers, CallFlags requiredCallFlags) { this.Method = method; @@ -40,9 +35,9 @@ private InteropDescriptor(string method, Func handler, this.RequiredCallFlags = requiredCallFlags; } - public long GetPrice(ApplicationEngine applicationEngine) + public long GetPrice(EvaluationStack stack, StoreView snapshot) { - return PriceCalculator is null ? Price : PriceCalculator(applicationEngine); + return PriceCalculator is null ? Price : PriceCalculator(stack, snapshot); } public static implicit operator uint(InteropDescriptor descriptor) diff --git a/src/neo/SmartContract/InteropService.Contract.cs b/src/neo/SmartContract/InteropService.Contract.cs index 14823db8c8..b6cd2862b7 100644 --- a/src/neo/SmartContract/InteropService.Contract.cs +++ b/src/neo/SmartContract/InteropService.Contract.cs @@ -1,5 +1,6 @@ using Neo.IO; using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; @@ -19,7 +20,7 @@ public static class Contract 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) + private static long GetDeploymentPrice(EvaluationStack stack, StoreView snapshot) { int size = stack.Peek(0).GetByteLength() + stack.Peek(1).GetByteLength(); return Storage.GasPerByte * size; diff --git a/src/neo/SmartContract/InteropService.Crypto.cs b/src/neo/SmartContract/InteropService.Crypto.cs index 448d312ea4..9678f321ce 100644 --- a/src/neo/SmartContract/InteropService.Crypto.cs +++ b/src/neo/SmartContract/InteropService.Crypto.cs @@ -1,6 +1,7 @@ using Neo.Cryptography; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.VM; using Neo.VM.Types; using System; @@ -16,7 +17,7 @@ public static class Crypto 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) + private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack, StoreView snapshot) { if (stack.Count < 2) return 0; var item = stack.Peek(1); diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 977b3ce71d..fba65cd35a 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -1,4 +1,5 @@ using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract.Iterators; using Neo.VM; using Neo.VM.Types; @@ -24,13 +25,12 @@ public static class Storage 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 long GetStoragePrice(ApplicationEngine engine) + private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot) { - var stack = engine.CurrentContext.EvaluationStack; var key = stack.Peek(1); var value = stack.Peek(2); var newDataSize = value.IsNull ? 0 : value.GetByteLength(); - if (!(engine.CurrentContext.EvaluationStack.Peek() is InteropInterface _interface)) + if (!(stack.Peek() is InteropInterface _interface)) throw new InvalidOperationException(); StorageContext context = _interface.GetInterface(); @@ -39,7 +39,7 @@ private static long GetStoragePrice(ApplicationEngine engine) Id = context.Id, Key = key.GetSpan().ToArray() }; - var skeyValue = engine.Snapshot.Storages.TryGet(skey); + var skeyValue = snapshot.Storages.TryGet(skey); if (skeyValue is null || skeyValue.Value is null || skeyValue.Value.Length == 0) return (key.GetByteLength() + newDataSize) * GasPerByte; diff --git a/src/neo/SmartContract/InteropService.cs b/src/neo/SmartContract/InteropService.cs index 3822626df8..f2a673c82f 100644 --- a/src/neo/SmartContract/InteropService.cs +++ b/src/neo/SmartContract/InteropService.cs @@ -1,3 +1,4 @@ +using Neo.Persistence; using Neo.VM; using System; using System.Collections.Generic; @@ -15,9 +16,9 @@ static InteropService() t.GetFields()[0].GetValue(null); } - public static long GetPrice(uint hash, ApplicationEngine applicationEngine) + public static long GetPrice(uint hash, EvaluationStack stack, StoreView snapshot) { - return methods[hash].GetPrice(applicationEngine); + return methods[hash].GetPrice(stack, snapshot); } public static IEnumerable SupportedMethods() @@ -44,14 +45,7 @@ private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) - { - InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requiredCallFlags); - methods.Add(descriptor.Hash, descriptor); - return descriptor; - } - - private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) + private static InteropDescriptor Register(string method, Func handler, Func priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags) { InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requiredCallFlags); methods.Add(descriptor.Hash, descriptor); diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index b7c7f85251..b28f55be14 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -2,6 +2,7 @@ using Neo.IO; using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native.Tokens; using Neo.VM; @@ -101,7 +102,7 @@ internal bool Invoke(ApplicationEngine engine) return true; } - internal long GetPrice(EvaluationStack stack) + internal long GetPrice(EvaluationStack stack, StoreView snapshot) { return methods.TryGetValue(stack.Peek().GetString(), out ContractMethodMetadata method) ? method.Price : 0; } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 3fe4c61652..c0bf012c2a 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -354,7 +354,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null, null); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { @@ -366,7 +366,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size) networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; using (ScriptBuilder sb = new ScriptBuilder()) networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null) * n; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null, null) * n; } else { diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index d4fbdd8bba..e1d183bb19 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -25,7 +25,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); - InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae).Should().Be(0_00030000L); + InteropService.GetPrice(InteropService.Runtime.CheckWitness, ae.CurrentContext.EvaluationStack, ae.Snapshot).Should().Be(0_00030000L); } // System.Storage.GetContext: 9bf667ce (price is 1) @@ -33,7 +33,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); - InteropService.GetPrice(InteropService.Storage.GetContext, ae).Should().Be(0_00000400L); + InteropService.GetPrice(InteropService.Storage.GetContext, ae.CurrentContext.EvaluationStack, ae.Snapshot).Should().Be(0_00000400L); } // System.Storage.Get: 925de831 (price is 100) @@ -41,7 +41,7 @@ public void ApplicationEngineFixedPrices() using (ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0)) { ae.LoadScript(SyscallSystemStorageGetHash); - InteropService.GetPrice(InteropService.Storage.Get, ae).Should().Be(0_01000000L); + InteropService.GetPrice(InteropService.Storage.Get, ae.CurrentContext.EvaluationStack, ae.Snapshot).Should().Be(0_01000000L); } } @@ -56,7 +56,7 @@ public void ApplicationEngineVariablePrices() ae.LoadScript(SyscallContractCreateHash00); debugger.StepInto(); // PUSHDATA1 debugger.StepInto(); // PUSHDATA1 - InteropService.GetPrice(InteropService.Contract.Create, ae).Should().Be(0_00300000L); + InteropService.GetPrice(InteropService.Contract.Create, ae.CurrentContext.EvaluationStack, ae.Snapshot).Should().Be(0_00300000L); } // System.Storage.Put: e63f1884 (requires push key and value) @@ -68,7 +68,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae); + Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); act.Should().Throw(); } @@ -81,7 +81,7 @@ public void ApplicationEngineVariablePrices() debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 03 (length 1) debugger.StepInto(); // push 00 - Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae); + Action act = () => InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); act.Should().Throw(); } } @@ -115,7 +115,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); var expectedCost = defaultDataPrice + setupPrice; debugger.Execute(); @@ -152,7 +152,7 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = applicationEngine.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine); + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, applicationEngine.CurrentContext.EvaluationStack, applicationEngine.Snapshot); reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.Execute(); var expectedCost = reusedDataPrice + setupPrice; @@ -191,7 +191,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); debugger.StepInto(); var setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.StepInto(); var expectedCost = reusedDataPrice + setupPrice; @@ -231,7 +231,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() debugger.StepInto(); //push value debugger.StepInto(); //syscall Storage.GetContext var setupPrice = ae.GasConsumed; - var incrementDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var incrementDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); incrementDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); debugger.StepInto(); // syscall Storage.Put @@ -239,7 +239,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() debugger.StepInto(); //push value debugger.StepInto(); setupPrice = ae.GasConsumed; - var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae); + var reusedDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); reusedDataPrice.Should().Be(1 * InteropService.Storage.GasPerByte); // = PUT basic fee } } From 2b1939e147825cf916f47386288105c7e685073e Mon Sep 17 00:00:00 2001 From: erikzhang Date: Thu, 12 Mar 2020 15:40:18 +0800 Subject: [PATCH 47/50] Update InteropService.Storage.cs --- src/neo/SmartContract/InteropService.Storage.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index fba65cd35a..98aee6d9f9 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -40,18 +40,13 @@ private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot) Key = key.GetSpan().ToArray() }; var skeyValue = snapshot.Storages.TryGet(skey); - if (skeyValue is null || skeyValue.Value is null || skeyValue.Value.Length == 0) - return (key.GetByteLength() + newDataSize) * GasPerByte; - - var currentOccupiedBytes = skeyValue.Value.Length; - if (newDataSize <= currentOccupiedBytes) - { - return 1 * GasPerByte; - } + if (skeyValue is null) + newDataSize += key.GetByteLength(); + else if (newDataSize <= skeyValue.Value.Length) + newDataSize = 1; else - { - return (newDataSize - currentOccupiedBytes) * GasPerByte; - } + newDataSize -= skeyValue.Value.Length; + return newDataSize * GasPerByte; } private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags) From d683efdb615bd5b2cafbfd358b221968de2300de Mon Sep 17 00:00:00 2001 From: Luchuan Date: Thu, 12 Mar 2020 16:11:07 +0800 Subject: [PATCH 48/50] add check: skeyValue.Value is null --- src/neo/SmartContract/InteropService.Storage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 98aee6d9f9..609ebca028 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -40,7 +40,7 @@ private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot) Key = key.GetSpan().ToArray() }; var skeyValue = snapshot.Storages.TryGet(skey); - if (skeyValue is null) + if (skeyValue is null || skeyValue.Value is null) newDataSize += key.GetByteLength(); else if (newDataSize <= skeyValue.Value.Length) newDataSize = 1; From 073849fab15ff07d337a56ad9b095e5dcecd53cc Mon Sep 17 00:00:00 2001 From: Luchuan Date: Thu, 12 Mar 2020 16:30:20 +0800 Subject: [PATCH 49/50] reset, fix ut --- src/neo/SmartContract/InteropService.Storage.cs | 2 +- tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 609ebca028..98aee6d9f9 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -40,7 +40,7 @@ private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot) Key = key.GetSpan().ToArray() }; var skeyValue = snapshot.Storages.TryGet(skey); - if (skeyValue is null || skeyValue.Value is null) + if (skeyValue is null) newDataSize += key.GetByteLength(); else if (newDataSize <= skeyValue.Value.Length) newDataSize = 1; diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index e1d183bb19..e34c80eb96 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -101,7 +101,7 @@ public void ApplicationEngineRegularPut() contractState.Manifest.Features = ContractFeatures.HasStorage; StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); - StorageItem sItem = TestUtils.GetStorageItem(null); + StorageItem sItem = TestUtils.GetStorageItem(new byte[0] { }); var snapshot = Blockchain.Singleton.GetSnapshot(); snapshot.Storages.Add(skey, sItem); @@ -116,7 +116,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); var setupPrice = ae.GasConsumed; var defaultDataPrice = InteropService.GetPrice(InteropService.Storage.Put, ae.CurrentContext.EvaluationStack, ae.Snapshot); - defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * (key.Length + value.Length)); + defaultDataPrice.Should().Be(InteropService.Storage.GasPerByte * value.Length); var expectedCost = defaultDataPrice + setupPrice; debugger.Execute(); ae.GasConsumed.Should().Be(expectedCost); From 52421319f30795e685abe7204a62a342bc45d613 Mon Sep 17 00:00:00 2001 From: Luchuan Date: Thu, 12 Mar 2020 18:01:06 +0800 Subject: [PATCH 50/50] adjust Storage.Delete price = 1 * GasPerByte --- src/neo/SmartContract/InteropService.Storage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/InteropService.Storage.cs b/src/neo/SmartContract/InteropService.Storage.cs index 98aee6d9f9..a9fc9e9303 100644 --- a/src/neo/SmartContract/InteropService.Storage.cs +++ b/src/neo/SmartContract/InteropService.Storage.cs @@ -23,7 +23,7 @@ public static class Storage 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); + public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, 1 * GasPerByte, TriggerType.Application, CallFlags.AllowModifyStates); private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot) {