diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index a5decc1d0c..9ef6a63c1c 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -14,6 +14,7 @@ namespace Neo public enum Hardfork : byte { HF_Aspidochelone, - HF_Basilisk + HF_Basilisk, + HF_Cockatrice } } diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index d299f8de12..1303770a3f 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -117,7 +117,8 @@ static NeoSystem() /// The storage engine used to create the objects. If this parameter is , a default in-memory storage engine will be used. /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. public NeoSystem(ProtocolSettings settings, string? storageProvider = null, string? storagePath = null) : - this(settings, StoreFactory.GetStoreProvider(storageProvider ?? nameof(MemoryStore)), storagePath) + this(settings, StoreFactory.GetStoreProvider(storageProvider ?? nameof(MemoryStore)) + ?? throw new ArgumentException($"Can't find the storage provider {storageProvider}", nameof(storageProvider)), storagePath) { } diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 6a1a5236bc..86fbd69961 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -96,7 +96,7 @@ public void Add(StorageKey key, StorageItem value) { TrackState.Deleted => TrackState.Changed, TrackState.NotFound => TrackState.Added, - _ => throw new ArgumentException() + _ => throw new ArgumentException($"The element currently has state {trackable.State}") }; } else diff --git a/src/Neo/ProtocolSettings.cs b/src/Neo/ProtocolSettings.cs index defbab9994..1b7367a6a2 100644 --- a/src/Neo/ProtocolSettings.cs +++ b/src/Neo/ProtocolSettings.cs @@ -24,6 +24,8 @@ namespace Neo /// public record ProtocolSettings { + private static readonly IList AllHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToArray(); + /// /// The magic number of the NEO network. /// @@ -115,7 +117,7 @@ public record ProtocolSettings MemoryPoolMaxTransactions = 50_000, MaxTraceableBlocks = 2_102_400, InitialGasDistribution = 52_000_000_00000000, - Hardforks = ImmutableDictionary.Empty + Hardforks = EnsureOmmitedHardforks(new Dictionary()).ToImmutableDictionary() }; public static ProtocolSettings? Custom { get; set; } @@ -159,17 +161,39 @@ public static ProtocolSettings Load(IConfigurationSection section) MaxTraceableBlocks = section.GetValue("MaxTraceableBlocks", Default.MaxTraceableBlocks), InitialGasDistribution = section.GetValue("InitialGasDistribution", Default.InitialGasDistribution), Hardforks = section.GetSection("Hardforks").Exists() - ? section.GetSection("Hardforks").GetChildren().ToImmutableDictionary(p => Enum.Parse(p.Key), p => uint.Parse(p.Value)) + ? EnsureOmmitedHardforks(section.GetSection("Hardforks").GetChildren().ToDictionary(p => Enum.Parse(p.Key, true), p => uint.Parse(p.Value))).ToImmutableDictionary() : Default.Hardforks }; } + /// + /// Explicitly set the height of all old omitted hardforks to 0 for proper IsHardforkEnabled behaviour. + /// + /// HardForks + /// Processed hardfork configuration + private static Dictionary EnsureOmmitedHardforks(Dictionary hardForks) + { + foreach (Hardfork hf in AllHardforks) + { + if (!hardForks.ContainsKey(hf)) + { + hardForks[hf] = 0; + } + else + { + break; + } + } + + return hardForks; + } + private static void CheckingHardfork(ProtocolSettings settings) { var allHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToList(); // Check for continuity in configured hardforks var sortedHardforks = settings.Hardforks.Keys - .OrderBy(h => allHardforks.IndexOf(h)) + .OrderBy(allHardforks.IndexOf) .ToList(); for (int i = 0; i < sortedHardforks.Count - 1; i++) @@ -179,7 +203,7 @@ private static void CheckingHardfork(ProtocolSettings settings) // If they aren't consecutive, return false. if (nextIndex - currentIndex > 1) - throw new Exception("Hardfork configuration is not continuous."); + throw new ArgumentException("Hardfork configuration is not continuous."); } // Check that block numbers are not higher in earlier hardforks than in later ones for (int i = 0; i < sortedHardforks.Count - 1; i++) @@ -187,9 +211,27 @@ private static void CheckingHardfork(ProtocolSettings settings) if (settings.Hardforks[sortedHardforks[i]] > settings.Hardforks[sortedHardforks[i + 1]]) { // This means the block number for the current hardfork is greater than the next one, which should not be allowed. - throw new Exception($"The Hardfork configuration for {sortedHardforks[i]} is greater than for {sortedHardforks[i + 1]}"); + throw new ArgumentException($"The Hardfork configuration for {sortedHardforks[i]} is greater than for {sortedHardforks[i + 1]}"); } } } + + /// + /// Check if the Hardfork is Enabled + /// + /// Hardfork + /// Block index + /// True if enabled + public bool IsHardforkEnabled(Hardfork hardfork, uint index) + { + if (Hardforks.TryGetValue(hardfork, out uint height)) + { + // If the hardfork has a specific height in the configuration, check the block height. + return index >= height; + } + + // If the hardfork isn't specified in the configuration, return false. + return false; + } } } diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index 1331ff2608..63cbde9960 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -49,7 +49,6 @@ public partial class ApplicationEngine : ExecutionEngine /// public static event EventHandler Log; - private static readonly IList AllHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToArray(); private static Dictionary services; private readonly long gas_amount; private Dictionary states; @@ -647,6 +646,25 @@ public T GetState() return (T)state; } + public T GetState(Func factory) + { + if (states is null) + { + T state = factory(); + SetState(state); + return state; + } + else + { + if (!states.TryGetValue(typeof(T), out object state)) + { + state = factory(); + SetState(state); + } + return (T)state; + } + } + public void SetState(T state) { states ??= new Dictionary(); @@ -655,28 +673,11 @@ public void SetState(T state) public bool IsHardforkEnabled(Hardfork hardfork) { - // Return true if there's no specific configuration or PersistingBlock is null - if (PersistingBlock is null || ProtocolSettings.Hardforks.Count == 0) - return true; - - // If the hardfork isn't specified in the configuration, check if it's a new one. - if (!ProtocolSettings.Hardforks.ContainsKey(hardfork)) - { - int currentHardforkIndex = AllHardforks.IndexOf(hardfork); - int lastConfiguredHardforkIndex = AllHardforks.IndexOf(ProtocolSettings.Hardforks.Keys.Last()); + // Return true if PersistingBlock is null and Hardfork is enabled + if (PersistingBlock is null) + return ProtocolSettings.Hardforks.ContainsKey(hardfork); - // If it's a newer hardfork compared to the ones in the configuration, disable it. - if (currentHardforkIndex > lastConfiguredHardforkIndex) - return false; - } - - if (ProtocolSettings.Hardforks.TryGetValue(hardfork, out uint height)) - { - // If the hardfork has a specific height in the configuration, check the block height. - return PersistingBlock.Index >= height; - } - // If no specific conditions are met, return true. - return true; + return ProtocolSettings.IsHardforkEnabled(hardfork, PersistingBlock.Index); } } } diff --git a/src/Neo/SmartContract/Native/ContractEventAttribute.cs b/src/Neo/SmartContract/Native/ContractEventAttribute.cs new file mode 100644 index 0000000000..656ecef725 --- /dev/null +++ b/src/Neo/SmartContract/Native/ContractEventAttribute.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractEventAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Manifest; +using System; +using System.Diagnostics; + +namespace Neo.SmartContract.Native +{ + [DebuggerDisplay("{Descriptor.Name}")] + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = true)] + internal class ContractEventAttribute : Attribute + { + public int Order { get; init; } + public ContractEventDescriptor Descriptor { get; set; } + public Hardfork? ActiveIn { get; init; } = null; + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value) : this(order, name, arg1Name, arg1Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value, arg3Name, arg3Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value + ) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + }, + new ContractParameterDefinition() + { + Name = arg3Name, + Type = arg3Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value, + string arg4Name, ContractParameterType arg4Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value, arg3Name, arg3Value, arg4Name, arg4Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value, + string arg4Name, ContractParameterType arg4Value + ) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + }, + new ContractParameterDefinition() + { + Name = arg3Name, + Type = arg3Value + }, + new ContractParameterDefinition() + { + Name = arg4Name, + Type = arg4Value + } + } + }; + } + } +} diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 0f846a37cc..3347163a6c 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -35,50 +35,10 @@ public sealed class ContractManagement : NativeContract private const byte Prefix_Contract = 8; private const byte Prefix_ContractHash = 12; - internal ContractManagement() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Deploy", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - }, - new ContractEventDescriptor - { - Name = "Update", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - }, - new ContractEventDescriptor - { - Name = "Destroy", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "Deploy", "Hash", ContractParameterType.Hash160)] + [ContractEvent(1, name: "Update", "Hash", ContractParameterType.Hash160)] + [ContractEvent(2, name: "Destroy", "Hash", ContractParameterType.Hash160)] + internal ContractManagement() : base() { } private int GetNextAvailableId(DataCache snapshot) { @@ -88,10 +48,13 @@ private int GetNextAvailableId(DataCache snapshot) return value; } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); - engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); + engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + } return ContractTask.CompletedTask; } @@ -107,17 +70,31 @@ internal override async ContractTask OnPersist(ApplicationEngine engine) { foreach (NativeContract contract in Contracts) { - if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index)) + if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index, out Hardfork? hf)) { - engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(new ContractState + ContractState contractState = contract.GetContractState(engine.ProtocolSettings, engine.PersistingBlock.Index); + StorageItem state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash)); + + if (state is null) + { + // Create the contract state + engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState)); + engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); + } + else { - Id = contract.Id, - Nef = contract.Nef, - Hash = contract.Hash, - Manifest = contract.Manifest - })); - engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); - await contract.Initialize(engine); + // Parse old contract + var oldContract = state.GetInteroperable(); + // Increase the update counter + oldContract.UpdateCounter++; + // Modify nef and manifest + oldContract.Nef = contractState.Nef; + oldContract.Manifest = contractState.Manifest; + } + + await contract.Initialize(engine, hf); + // Emit native contract notification + engine.SendNotification(Hash, state is null ? "Deploy" : "Update", new VM.Types.Array(engine.ReferenceCounter) { contract.Hash.ToArray() }); } } } diff --git a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs index 55940f2eb3..cd8f7de59e 100644 --- a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs @@ -10,9 +10,11 @@ // modifications are permitted. using System; +using System.Diagnostics; namespace Neo.SmartContract.Native { + [DebuggerDisplay("{Name}")] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)] internal class ContractMethodAttribute : Attribute { @@ -20,5 +22,13 @@ internal class ContractMethodAttribute : Attribute public CallFlags RequiredCallFlags { get; init; } public long CpuFee { get; init; } public long StorageFee { get; init; } + public Hardfork? ActiveIn { get; init; } = null; + + public ContractMethodAttribute() { } + + public ContractMethodAttribute(Hardfork activeIn) + { + ActiveIn = activeIn; + } } } diff --git a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs index 83c24191fd..9f83eb20fc 100644 --- a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs @@ -15,6 +15,7 @@ using Neo.SmartContract.Manifest; using Neo.VM.Types; using System; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; @@ -22,6 +23,7 @@ namespace Neo.SmartContract.Native { + [DebuggerDisplay("{Name}")] internal class ContractMethodMetadata { public string Name { get; } @@ -33,6 +35,7 @@ internal class ContractMethodMetadata public long StorageFee { get; } public CallFlags RequiredCallFlags { get; } public ContractMethodDescriptor Descriptor { get; } + public Hardfork? ActiveIn { get; init; } = null; public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribute) { @@ -56,6 +59,7 @@ public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribu this.CpuFee = attribute.CpuFee; this.StorageFee = attribute.StorageFee; this.RequiredCallFlags = attribute.RequiredCallFlags; + this.ActiveIn = attribute.ActiveIn; this.Descriptor = new ContractMethodDescriptor { Name = Name, diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs index 257deaa72f..a7b822e39c 100644 --- a/src/Neo/SmartContract/Native/CryptoLib.cs +++ b/src/Neo/SmartContract/Native/CryptoLib.cs @@ -11,6 +11,7 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; +using Org.BouncyCastle.Crypto.Digests; using System; using System.Collections.Generic; @@ -27,7 +28,7 @@ public sealed partial class CryptoLib : NativeContract [NamedCurve.secp256r1] = ECCurve.Secp256r1 }; - internal CryptoLib() { } + internal CryptoLib() : base() { } /// /// Computes the hash value for the specified byte array using the ripemd160 algorithm. @@ -64,6 +65,21 @@ public static byte[] Murmur32(byte[] data, uint seed) return murmur.ComputeHash(data); } + /// + /// Computes the hash value for the specified byte array using the keccak256 algorithm. + /// + /// The input to compute the hash code for. + /// Computed hash + [ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 15)] + public static byte[] Keccak256(byte[] data) + { + KeccakDigest keccak = new(256); + keccak.BlockUpdate(data, 0, data.Length); + byte[] result = new byte[keccak.GetDigestSize()]; + keccak.DoFinal(result, 0); + return result; + } + /// /// Verifies that a digital signature is appropriate for the provided key and message using the ECDSA algorithm. /// diff --git a/src/Neo/SmartContract/Native/FungibleToken.cs b/src/Neo/SmartContract/Native/FungibleToken.cs index 1a3a2ac532..6499c60c19 100644 --- a/src/Neo/SmartContract/Native/FungibleToken.cs +++ b/src/Neo/SmartContract/Native/FungibleToken.cs @@ -14,7 +14,6 @@ using Neo.SmartContract.Manifest; using Neo.VM.Types; using System; -using System.Collections.Generic; using System.Numerics; using Array = Neo.VM.Types.Array; @@ -57,39 +56,18 @@ public abstract class FungibleToken : NativeContract /// /// Initializes a new instance of the class. /// - protected FungibleToken() + [ContractEvent(0, name: "Transfer", + "from", ContractParameterType.Hash160, + "to", ContractParameterType.Hash160, + "amount", ContractParameterType.Integer)] + protected FungibleToken() : base() { this.Factor = BigInteger.Pow(10, Decimals); + } - Manifest.SupportedStandards = new[] { "NEP-17" }; - - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Transfer", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "from", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "to", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "amount", - Type = ContractParameterType.Integer - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); + protected override void OnManifestCompose(ContractManifest manifest) + { + manifest.SupportedStandards = new[] { "NEP-17" }; } internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, bool callOnPayment) diff --git a/src/Neo/SmartContract/Native/GasToken.cs b/src/Neo/SmartContract/Native/GasToken.cs index 0c5a721edc..ce2f77ad2c 100644 --- a/src/Neo/SmartContract/Native/GasToken.cs +++ b/src/Neo/SmartContract/Native/GasToken.cs @@ -26,10 +26,14 @@ internal GasToken() { } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork) { - UInt160 account = Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators); - return Mint(engine, account, engine.ProtocolSettings.InitialGasDistribution, false); + if (hardfork == ActiveIn) + { + UInt160 account = Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators); + return Mint(engine, account, engine.ProtocolSettings.InitialGasDistribution, false); + } + return ContractTask.CompletedTask; } internal override async ContractTask OnPersist(ApplicationEngine engine) diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index 3bb7568adf..d72f07c022 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -32,9 +32,7 @@ public sealed class LedgerContract : NativeContract private const byte Prefix_Block = 5; private const byte Prefix_Transaction = 11; - internal LedgerContract() - { - } + internal LedgerContract() : base() { } internal override ContractTask OnPersist(ApplicationEngine engine) { diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 0229582310..6d5d105b7d 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -14,6 +14,8 @@ using Neo.VM; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; @@ -24,9 +26,32 @@ namespace Neo.SmartContract.Native /// public abstract class NativeContract { + private class NativeContractsCache + { + public class CacheEntry + { + public Dictionary Methods { get; set; } + public byte[] Script { get; set; } + } + + internal Dictionary NativeContracts { get; set; } = new Dictionary(); + + public CacheEntry GetAllowedMethods(NativeContract native, ApplicationEngine engine) + { + if (NativeContracts.TryGetValue(native.Id, out var value)) return value; + + uint index = engine.PersistingBlock is null ? Ledger.CurrentIndex(engine.Snapshot) : engine.PersistingBlock.Index; + CacheEntry methods = native.GetAllowedMethods(engine.ProtocolSettings, index); + NativeContracts[native.Id] = methods; + return methods; + } + } + private static readonly List contractsList = new(); private static readonly Dictionary contractsDictionary = new(); - private readonly Dictionary methods = new(); + private readonly ImmutableHashSet usedHardforks; + private readonly ReadOnlyCollection methodDescriptors; + private readonly ReadOnlyCollection eventsDescriptors; private static int id_counter = 0; #region Named Native Contracts @@ -93,11 +118,6 @@ public abstract class NativeContract /// public virtual Hardfork? ActiveIn { get; } = null; - /// - /// The nef of the native contract. - /// - public NefFile Nef { get; } - /// /// The hash of the native contract. /// @@ -108,28 +128,58 @@ public abstract class NativeContract /// public int Id { get; } = --id_counter; - /// - /// The manifest of the native contract. - /// - public ContractManifest Manifest { get; } - /// /// Initializes a new instance of the class. /// protected NativeContract() { - List descriptors = new(); + this.Hash = Helper.GetContractHash(UInt160.Zero, 0, Name); + + // Reflection to get the methods + + List listMethods = new(); foreach (MemberInfo member in GetType().GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { ContractMethodAttribute attribute = member.GetCustomAttribute(); if (attribute is null) continue; - descriptors.Add(new ContractMethodMetadata(member, attribute)); + listMethods.Add(new ContractMethodMetadata(member, attribute)); } - descriptors = descriptors.OrderBy(p => p.Name, StringComparer.Ordinal).ThenBy(p => p.Parameters.Length).ToList(); + methodDescriptors = listMethods.OrderBy(p => p.Name, StringComparer.Ordinal).ThenBy(p => p.Parameters.Length).ToList().AsReadOnly(); + + // Reflection to get the events + eventsDescriptors = + GetType().GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Array.Empty(), null)?. + GetCustomAttributes(). + OrderBy(p => p.Order).ToList().AsReadOnly(); + + // Calculate the initializations forks + usedHardforks = + methodDescriptors.Select(u => u.ActiveIn) + .Concat(eventsDescriptors.Select(u => u.ActiveIn)) + .Concat(new Hardfork?[] { ActiveIn }) + .Where(u => u is not null) + .OrderBy(u => (byte)u) + .Cast().ToImmutableHashSet(); + + contractsList.Add(this); + contractsDictionary.Add(Hash, this); + } + + /// + /// The allowed methods and his offsets. + /// + /// The where the HardForks are configured. + /// Block index + /// The . + private NativeContractsCache.CacheEntry GetAllowedMethods(ProtocolSettings settings, uint index) + { + Dictionary methods = new(); + + // Reflection to get the ContractMethods byte[] script; using (ScriptBuilder sb = new()) { - foreach (ContractMethodMetadata method in descriptors) + foreach (ContractMethodMetadata method in methodDescriptors.Where(u => u.ActiveIn is null || settings.IsHardforkEnabled(u.ActiveIn.Value, index))) { method.Descriptor.Offset = sb.Length; sb.EmitPush(0); //version @@ -139,49 +189,99 @@ protected NativeContract() } script = sb.ToArray(); } - this.Nef = new NefFile + + return new NativeContractsCache.CacheEntry() { Methods = methods, Script = script }; + } + + /// + /// The of the native contract. + /// + /// The where the HardForks are configured. + /// Block index + /// The . + internal ContractState GetContractState(ProtocolSettings settings, uint index) + { + // Get allowed methods and nef script + NativeContractsCache.CacheEntry allowedMethods = GetAllowedMethods(settings, index); + + // Compose nef file + NefFile nef = new() { Compiler = "neo-core-v3.0", Source = string.Empty, Tokens = Array.Empty(), - Script = script + Script = allowedMethods.Script }; - this.Nef.CheckSum = NefFile.ComputeChecksum(Nef); - this.Hash = Helper.GetContractHash(UInt160.Zero, 0, Name); - this.Manifest = new ContractManifest + nef.CheckSum = NefFile.ComputeChecksum(nef); + + // Compose manifest + ContractManifest manifest = new() { Name = Name, Groups = Array.Empty(), SupportedStandards = Array.Empty(), Abi = new ContractAbi() { - Events = Array.Empty(), - Methods = descriptors.Select(p => p.Descriptor).ToArray() + Events = eventsDescriptors + .Where(u => u.ActiveIn is null || settings.IsHardforkEnabled(u.ActiveIn.Value, index)) + .Select(p => p.Descriptor).ToArray(), + Methods = allowedMethods.Methods.Values + .Select(p => p.Descriptor).ToArray() }, Permissions = new[] { ContractPermission.DefaultPermission }, Trusts = WildcardContainer.Create(), Extra = null }; - contractsList.Add(this); - contractsDictionary.Add(Hash, this); + + OnManifestCompose(manifest); + + // Return ContractState + return new ContractState + { + Id = Id, + Nef = nef, + Hash = Hash, + Manifest = manifest + }; } + protected virtual void OnManifestCompose(ContractManifest manifest) { } + /// /// It is the initialize block /// /// The where the HardForks are configured. /// Block index + /// Active hardfork /// True if the native contract must be initialized - internal bool IsInitializeBlock(ProtocolSettings settings, uint index) + internal bool IsInitializeBlock(ProtocolSettings settings, uint index, out Hardfork? hardfork) { - if (ActiveIn is null) return index == 0; + // If is not configured, the Genesis is the a initialized block + if (index == 0 && ActiveIn is null) + { + hardfork = null; + return true; + } - if (!settings.Hardforks.TryGetValue(ActiveIn.Value, out var activeIn)) + // If is in the hardfork height, return true + foreach (Hardfork hf in usedHardforks) { - return false; + if (!settings.Hardforks.TryGetValue(hf, out var activeIn)) + { + // If is not set in the configuration is treated as enabled from the genesis + activeIn = 0; + } + + if (activeIn == index) + { + hardfork = hf; + return true; + } } - return activeIn == index; + // Initialized not required + hardfork = null; + return false; } /// @@ -196,7 +296,8 @@ internal bool IsActive(ProtocolSettings settings, uint index) if (!settings.Hardforks.TryGetValue(ActiveIn.Value, out var activeIn)) { - return false; + // If is not set in the configuration is treated as enabled from the genesis + activeIn = 0; } return activeIn <= index; @@ -235,8 +336,14 @@ internal async void Invoke(ApplicationEngine engine, byte version) { if (version != 0) throw new InvalidOperationException($"The native contract of version {version} is not active."); + // Get native contracts invocation cache + NativeContractsCache nativeContracts = engine.GetState(() => new NativeContractsCache()); + NativeContractsCache.CacheEntry currentAllowedMethods = nativeContracts.GetAllowedMethods(this, engine); + // Check if the method is allowed ExecutionContext context = engine.CurrentContext; - ContractMethodMetadata method = methods[context.InstructionPointer]; + ContractMethodMetadata method = currentAllowedMethods.Methods[context.InstructionPointer]; + if (method.ActiveIn is not null && !engine.IsHardforkEnabled(method.ActiveIn.Value)) + throw new InvalidOperationException($"Cannot call this method before hardfork {method.ActiveIn}."); ExecutionContextState state = context.GetState(); if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); @@ -277,7 +384,7 @@ public static bool IsNative(UInt160 hash) return contractsDictionary.ContainsKey(hash); } - internal virtual ContractTask Initialize(ApplicationEngine engine) + internal virtual ContractTask Initialize(ApplicationEngine engine, Hardfork? hardFork) { return ContractTask.CompletedTask; } diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index 2fd26d04a7..ed796a9756 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -15,7 +15,6 @@ using Neo.IO; using Neo.Persistence; using Neo.SmartContract.Iterators; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -55,64 +54,18 @@ public sealed class NeoToken : FungibleToken private const byte CommitteeRewardRatio = 10; private const byte VoterRewardRatio = 80; - internal NeoToken() + [ContractEvent(1, name: "CandidateStateChanged", + "pubkey", ContractParameterType.PublicKey, + "registered", ContractParameterType.Boolean, + "votes", ContractParameterType.Integer)] + [ContractEvent(2, name: "Vote", + "account", ContractParameterType.Hash160, + "from", ContractParameterType.PublicKey, + "to", ContractParameterType.PublicKey, + "amount", ContractParameterType.Integer)] + internal NeoToken() : base() { this.TotalAmount = 100000000 * Factor; - - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "CandidateStateChanged", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "pubkey", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "registered", - Type = ContractParameterType.Boolean - }, - new ContractParameterDefinition() - { - Name = "votes", - Type = ContractParameterType.Integer - } - } - }, - new ContractEventDescriptor - { - Name = "Vote", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "from", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "to", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "amount", - Type = ContractParameterType.Integer - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); } public override BigInteger TotalSupply(DataCache snapshot) @@ -220,14 +173,18 @@ private void CheckCandidate(DataCache snapshot, ECPoint pubkey, CandidateState c /// if the votes should be recounted; otherwise, . public static bool ShouldRefreshCommittee(uint height, int committeeMembersCount) => height % committeeMembersCount == 0; - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork) { - var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); - engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); - engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); - engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); - return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); + if (hardfork == ActiveIn) + { + var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); + engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); + engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); + engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); + engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); + return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); + } + return ContractTask.CompletedTask; } internal override ContractTask OnPersist(ApplicationEngine engine) diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index 42f41f5720..d8415fca21 100644 --- a/src/Neo/SmartContract/Native/OracleContract.cs +++ b/src/Neo/SmartContract/Native/OracleContract.cs @@ -15,7 +15,6 @@ using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -41,58 +40,15 @@ public sealed class OracleContract : NativeContract private const byte Prefix_Request = 7; private const byte Prefix_IdList = 6; - internal OracleContract() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "OracleRequest", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Id", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "RequestContract", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "Url", - Type = ContractParameterType.String - }, - new ContractParameterDefinition() - { - Name = "Filter", - Type = ContractParameterType.String - } - } - }, - new ContractEventDescriptor - { - Name = "OracleResponse", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Id", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "OriginalTx", - Type = ContractParameterType.Hash256 - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "OracleRequest", + "Id", ContractParameterType.Integer, + "RequestContract", ContractParameterType.Hash160, + "Url", ContractParameterType.String, + "Filter", ContractParameterType.String)] + [ContractEvent(1, name: "OracleResponse", + "Id", ContractParameterType.Integer, + "OriginalTx", ContractParameterType.Hash256)] + internal OracleContract() : base() { } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetPrice(ApplicationEngine engine, long price) @@ -178,10 +134,13 @@ private static byte[] GetUrlHash(string url) return Crypto.Hash160(Utility.StrictUTF8.GetBytes(url)); } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); - engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); + engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + } return ContractTask.CompletedTask; } diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index c633cb7309..5b00af92ad 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -64,15 +64,16 @@ public sealed class PolicyContract : NativeContract private const byte Prefix_StoragePrice = 19; private const byte Prefix_AttributeFee = 20; - internal PolicyContract() - { - } + internal PolicyContract() : base() { } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); - engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); + engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); + engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + } return ContractTask.CompletedTask; } diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index 1c2fa0a299..6e989b78cf 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -12,11 +12,9 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; -using System.Collections.Generic; using System.Linq; namespace Neo.SmartContract.Native @@ -26,31 +24,10 @@ namespace Neo.SmartContract.Native /// public sealed class RoleManagement : NativeContract { - internal RoleManagement() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Designation", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Role", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "BlockIndex", - Type = ContractParameterType.Integer - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "Designation", + "Role", ContractParameterType.Integer, + "BlockIndex", ContractParameterType.Integer)] + internal RoleManagement() : base() { } /// /// Gets the list of nodes for the specified role. diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index 3ca836a3ec..f8fa9efcc2 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -27,7 +27,7 @@ public sealed class StdLib : NativeContract { private const int MaxInputLength = 1024; - internal StdLib() { } + internal StdLib() : base() { } [ContractMethod(CpuFee = 1 << 12)] private static byte[] Serialize(ApplicationEngine engine, StackItem item) diff --git a/src/Neo/SmartContract/StorageItem.cs b/src/Neo/SmartContract/StorageItem.cs index 0f13c3c1f0..41486997de 100644 --- a/src/Neo/SmartContract/StorageItem.cs +++ b/src/Neo/SmartContract/StorageItem.cs @@ -160,6 +160,16 @@ public void Set(BigInteger integer) value = null; } + /// + /// Sets the interoperable value of the storage. + /// + /// The value of the . + public void Set(IInteroperable interoperable) + { + cache = interoperable; + value = null; + } + public static implicit operator BigInteger(StorageItem item) { item.cache ??= new BigInteger(item.value.Span); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs new file mode 100644 index 0000000000..ba8f703253 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs @@ -0,0 +1,151 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractEventAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.SmartContract.Native; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_ContractEventAttribute + { + [TestMethod] + public void TestConstructorOneArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "1", "a1", ContractParameterType.String); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("1", arg.Descriptor.Name); + Assert.AreEqual(1, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + + arg = new ContractEventAttribute(1, "1", "a1", ContractParameterType.String); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("1", arg.Descriptor.Name); + Assert.AreEqual(1, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + } + + [TestMethod] + public void TestConstructorTwoArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "2", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("2", arg.Descriptor.Name); + Assert.AreEqual(2, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + + arg = new ContractEventAttribute(1, "2", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("2", arg.Descriptor.Name); + Assert.AreEqual(2, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + } + + [TestMethod] + public void TestConstructorThreeArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "3", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("3", arg.Descriptor.Name); + Assert.AreEqual(3, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + + arg = new ContractEventAttribute(1, "3", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("3", arg.Descriptor.Name); + Assert.AreEqual(3, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + } + + [TestMethod] + public void TestConstructorFourArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "4", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean, + "a4", ContractParameterType.Array); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("4", arg.Descriptor.Name); + Assert.AreEqual(4, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + Assert.AreEqual("a4", arg.Descriptor.Parameters[3].Name); + Assert.AreEqual(ContractParameterType.Array, arg.Descriptor.Parameters[3].Type); + + arg = new ContractEventAttribute(1, "4", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean, + "a4", ContractParameterType.Array); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("4", arg.Descriptor.Name); + Assert.AreEqual(4, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + Assert.AreEqual("a4", arg.Descriptor.Parameters[3].Name); + Assert.AreEqual(ContractParameterType.Array, arg.Descriptor.Parameters[3].Type); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs new file mode 100644 index 0000000000..a2b746d7c8 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractMethodAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Native; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_ContractMethodAttribute + { + [TestMethod] + public void TestConstructorOneArg() + { + var arg = new ContractMethodAttribute(); + + Assert.IsNull(arg.ActiveIn); + + arg = new ContractMethodAttribute(Hardfork.HF_Aspidochelone); + + Assert.AreEqual(Hardfork.HF_Aspidochelone, arg.ActiveIn); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs index 9e36488031..6f150cca12 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs @@ -15,6 +15,7 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; +using Org.BouncyCastle.Utilities.Encoders; namespace Neo.UnitTests.SmartContract.Native { @@ -334,5 +335,97 @@ public void TestBls12381ScalarMul_Compat() BLS12381PointType.G2Proj ); } + + /// + /// Keccak256 cases are verified in https://emn178.github.io/online-tools/keccak_256.html + /// + [TestMethod] + public void TestKeccak256_HelloWorld() + { + // Arrange + byte[] inputData = "Hello, World!"u8.ToArray(); + string expectedHashHex = "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Hello, World!'."); + } + [TestMethod] + public void TestKeccak256_Keccak() + { + // Arrange + byte[] inputData = "Keccak"u8.ToArray(); + string expectedHashHex = "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Keccak'."); + } + + [TestMethod] + public void TestKeccak256_Cryptography() + { + // Arrange + byte[] inputData = "Cryptography"u8.ToArray(); + string expectedHashHex = "53d49d225dd2cfe77d8c5e2112bcc9efe77bea1c7aa5e5ede5798a36e99e2d29"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Cryptography'."); + } + + [TestMethod] + public void TestKeccak256_Testing123() + { + // Arrange + byte[] inputData = "Testing123"u8.ToArray(); + string expectedHashHex = "3f82db7b16b0818a1c6b2c6152e265f682d5ebcf497c9aad776ad38bc39cb6ca"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Testing123'."); + } + + [TestMethod] + public void TestKeccak256_LongString() + { + // Arrange + byte[] inputData = "This is a longer string for Keccak256 testing purposes."u8.ToArray(); + string expectedHashHex = "24115e5c2359f85f6840b42acd2f7ea47bc239583e576d766fa173bf711bdd2f"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for the longer string."); + } + + [TestMethod] + public void TestKeccak256_BlankString() + { + // Arrange + byte[] inputData = ""u8.ToArray(); + string expectedHashHex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for blank string."); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 5f4b5b43e0..7f23519e9f 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract.Native; +using System.IO; namespace Neo.UnitTests.SmartContract.Native { @@ -22,5 +23,25 @@ public void TestGetContract() { Assert.IsTrue(NativeContract.NEO == NativeContract.GetContract(NativeContract.NEO.Hash)); } + + [TestMethod] + public void TestIsInitializeBlock() + { + string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Cockatrice\": 20"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + Assert.IsTrue(NativeContract.CryptoLib.IsInitializeBlock(settings, 0, out var hf)); + Assert.IsNull(hf); + + Assert.IsFalse(NativeContract.CryptoLib.IsInitializeBlock(settings, 1, out hf)); + Assert.IsNull(hf); + + Assert.IsTrue(NativeContract.CryptoLib.IsInitializeBlock(settings, 20, out hf)); + Assert.AreEqual(Hardfork.HF_Cockatrice, hf); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 3798eab327..a0ab12fdc3 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -76,7 +76,7 @@ public void Runtime_GetNotifications_Test() // Wrong length - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Retrive @@ -93,7 +93,7 @@ public void Runtime_GetNotifications_Test() // All test - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification @@ -162,7 +162,7 @@ public void Runtime_GetNotifications_Test() // Script notifications - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification diff --git a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs index 3ab32ad7a5..fc29c95c12 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs @@ -272,7 +272,7 @@ public void Serialize_Map_Test() [TestMethod] public void Deserialize_Map_Test() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("{\"test1\":123,\"test2\":321}"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(Map)); @@ -302,7 +302,7 @@ public void Serialize_Array_Bool_Str_Num() [TestMethod] public void Deserialize_Array_Bool_Str_Num() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("[true,\"test\",123,9.05E+28]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); @@ -333,7 +333,7 @@ public void Serialize_Array_OfArray() [TestMethod] public void Deserialize_Array_OfArray() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("[[true,\"test1\",123],[true,\"test2\",321]]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); diff --git a/tests/Neo.UnitTests/UT_ProtocolSettings.cs b/tests/Neo.UnitTests/UT_ProtocolSettings.cs index 28817bb482..e5d829ef8b 100644 --- a/tests/Neo.UnitTests/UT_ProtocolSettings.cs +++ b/tests/Neo.UnitTests/UT_ProtocolSettings.cs @@ -14,6 +14,7 @@ using Neo.Cryptography.ECC; using Neo.Wallets; using System; +using System.IO; namespace Neo.UnitTests { @@ -48,6 +49,132 @@ public void TestGetMillisecondsPerBlock() TestProtocolSettings.Default.MillisecondsPerBlock.Should().Be(15000); } + [TestMethod] + public void HardForkTestBAndNotA() + { + string json = CreateHKSettings("\"HF_Basilisk\": 4120000"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks[Hardfork.HF_Basilisk].Should().Be(4120000); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 4120000).Should().BeTrue(); + } + + [TestMethod] + public void HardForkTestAAndNotB() + { + string json = CreateHKSettings("\"HF_Aspidochelone\": 0"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks.ContainsKey(Hardfork.HF_Basilisk).Should().BeFalse(); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 4120000).Should().BeFalse(); + } + + [TestMethod] + public void HardForkTestNone() + { + string json = CreateHKSettings(""); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks[Hardfork.HF_Basilisk].Should().Be(0); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeTrue(); + } + + [TestMethod] + public void HardForkTestAMoreThanB() + { + string json = CreateHKSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + Assert.ThrowsException(() => ProtocolSettings.Load(file, false)); + File.Delete(file); + } + + internal static string CreateHKSettings(string hf) + { + return @" +{ + ""ProtocolConfiguration"": { + ""Network"": 860833102, + ""AddressVersion"": 53, + ""MillisecondsPerBlock"": 15000, + ""MaxTransactionsPerBlock"": 512, + ""MemoryPoolMaxTransactions"": 50000, + ""MaxTraceableBlocks"": 2102400, + ""Hardforks"": { + " + hf + @" + }, + ""InitialGasDistribution"": 5200000000000000, + ""ValidatorsCount"": 7, + ""StandbyCommittee"": [ + ""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"", + ""02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"", + ""03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"", + ""02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"", + ""024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"", + ""02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"", + ""02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"", + ""023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe"", + ""03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379"", + ""03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050"", + ""03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0"", + ""02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62"", + ""03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0"", + ""0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654"", + ""020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639"", + ""0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30"", + ""03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde"", + ""02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad"", + ""0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d"", + ""03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc"", + ""02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a"" + ], + ""SeedList"": [ + ""seed1.neo.org:10333"", + ""seed2.neo.org:10333"", + ""seed3.neo.org:10333"", + ""seed4.neo.org:10333"", + ""seed5.neo.org:10333"" + ] + } +} +"; + } + [TestMethod] public void TestGetSeedList() {