From 4ffcb260b6c5462fb867b82bc65df7cb7a8a62a8 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 19:45:06 +0800 Subject: [PATCH 01/20] Neo Name Service --- src/neo/SmartContract/Native/NameService.cs | 24 +++++++++++++++++++ .../SmartContract/Native/NativeContract.cs | 1 + 2 files changed, 25 insertions(+) create mode 100644 src/neo/SmartContract/Native/NameService.cs diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs new file mode 100644 index 0000000000..082e6272b2 --- /dev/null +++ b/src/neo/SmartContract/Native/NameService.cs @@ -0,0 +1,24 @@ +using Neo.Cryptography; + +namespace Neo.SmartContract.Native +{ + public sealed class NameService : NonfungibleToken + { + public override int Id => -6; + public override string Symbol => "NNS"; + + internal NameService() + { + } + + protected override byte[] GetKey(byte[] tokenId) + { + return Crypto.Hash160(tokenId); + } + + public class NameState : NFTState + { + public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); + } + } +} diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index 115a48a956..9220522ef1 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -25,6 +25,7 @@ public abstract class NativeContract public static PolicyContract Policy { get; } = new PolicyContract(); public static RoleManagement RoleManagement { get; } = new RoleManagement(); public static OracleContract Oracle { get; } = new OracleContract(); + public static NameService NameService { get; } = new NameService(); public string Name => GetType().Name; public NefFile Nef { get; } From 81a009af72a21e2c7cd58416555b4877187a4c04 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 20:33:57 +0800 Subject: [PATCH 02/20] Add RecordType --- src/neo/SmartContract/Native/RecordType.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/neo/SmartContract/Native/RecordType.cs diff --git a/src/neo/SmartContract/Native/RecordType.cs b/src/neo/SmartContract/Native/RecordType.cs new file mode 100644 index 0000000000..316d6c30d6 --- /dev/null +++ b/src/neo/SmartContract/Native/RecordType.cs @@ -0,0 +1,24 @@ +namespace Neo.SmartContract.Native +{ + public enum RecordType : byte + { + #region [RFC 1035](https://tools.ietf.org/html/rfc1035) + A = 1, + CNAME = 5, + MX = 15, + TXT = 16, + #endregion + + #region [RFC 3596](https://tools.ietf.org/html/rfc3596) + AAAA = 28, + #endregion + + #region [RFC 2782](https://tools.ietf.org/html/rfc2782) + SRV = 33, + #endregion + + #region [RFC 6672](https://tools.ietf.org/html/rfc6672) + DNAME = 39 + #endregion + } +} From 9aec758542c447de98f5cafa4964b41817106460 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 21:16:04 +0800 Subject: [PATCH 03/20] Allow the committee to add root names --- src/neo/SmartContract/Native/NameService.cs | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 082e6272b2..82ff266721 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -1,4 +1,14 @@ +#pragma warning disable IDE0051 + using Neo.Cryptography; +using Neo.Ledger; +using Neo.Persistence; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; namespace Neo.SmartContract.Native { @@ -7,6 +17,10 @@ public sealed class NameService : NonfungibleToken public override int Id => -6; public override string Symbol => "NNS"; + private const byte Prefix_Roots = 5; + + private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$"); + internal NameService() { } @@ -16,9 +30,39 @@ protected override byte[] GetKey(byte[] tokenId) return Crypto.Hash160(tokenId); } + [ContractMethod(0_03000000, CallFlags.WriteStates)] + private void AddRoot(ApplicationEngine engine, string root) + { + if (!rootRegex.IsMatch(root)) throw new ArgumentException(null, nameof(root)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + StringList roots = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Roots), () => new StorageItem(new StringList())).GetInteroperable(); + int index = roots.BinarySearch(root); + if (index >= 0) throw new InvalidOperationException("The name already exists."); + roots.Insert(~index, root); + } + + public IEnumerable GetRoots(StoreView snapshot) + { + return snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable() ?? Enumerable.Empty(); + } + public class NameState : NFTState { public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); } + + private class StringList : List, IInteroperable + { + public void FromStackItem(StackItem stackItem) + { + foreach (StackItem item in (VM.Types.Array)stackItem) + Add(item.GetString()); + } + + public StackItem ToStackItem(ReferenceCounter referenceCounter) + { + return new VM.Types.Array(referenceCounter, this.Select(p => (ByteString)p)); + } + } } } From 2a1c383ea294b6ba043a1d18ef4676c730541bf9 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 21:38:29 +0800 Subject: [PATCH 04/20] Allow the committee to modify the price of domains --- src/neo/SmartContract/Native/NameService.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 82ff266721..be5641d8cf 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Text.RegularExpressions; namespace Neo.SmartContract.Native @@ -18,6 +19,7 @@ public sealed class NameService : NonfungibleToken public override string Symbol => "NNS"; private const byte Prefix_Roots = 5; + private const byte Prefix_DomainPrice = 11; private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$"); @@ -25,6 +27,11 @@ internal NameService() { } + internal override void Initialize(ApplicationEngine engine) + { + engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_DomainPrice), new StorageItem(10_00000000)); + } + protected override byte[] GetKey(byte[] tokenId) { return Crypto.Hash160(tokenId); @@ -46,6 +53,20 @@ public IEnumerable GetRoots(StoreView snapshot) return snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable() ?? Enumerable.Empty(); } + [ContractMethod(0_03000000, CallFlags.WriteStates)] + private void SetPrice(ApplicationEngine engine, long price) + { + if (price <= 0 || price > 10000_00000000) throw new ArgumentOutOfRangeException(nameof(price)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_DomainPrice)).Set(price); + } + + [ContractMethod(0_01000000, CallFlags.ReadStates)] + public long GetPrice(StoreView snapshot) + { + return (long)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_DomainPrice)]; + } + public class NameState : NFTState { public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); From 22ee86f0355860612ff8523a7f4bf63d4339473d Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 23:31:01 +0800 Subject: [PATCH 05/20] Expiration/Renew --- src/neo/SmartContract/Native/NameService.cs | 82 ++++++++++++++++++- .../SmartContract/Native/NonfungibleToken.cs | 16 ++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index be5641d8cf..3e5fc3442b 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -1,6 +1,7 @@ #pragma warning disable IDE0051 using Neo.Cryptography; +using Neo.IO.Json; using Neo.Ledger; using Neo.Persistence; using Neo.VM; @@ -18,10 +19,13 @@ public sealed class NameService : NonfungibleToken public override int Id => -6; public override string Symbol => "NNS"; - private const byte Prefix_Roots = 5; - private const byte Prefix_DomainPrice = 11; + private const byte Prefix_Roots = 10; + private const byte Prefix_DomainPrice = 22; + private const byte Prefix_Expiration = 20; + private const uint OneYear = 365 * 24 * 3600; private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$"); + private static readonly Regex nameRegex = new Regex("^(?=.{3,255}$)([a-z0-9]{1,62}\\.)+[a-z][a-z0-9]{0,15}$"); internal NameService() { @@ -32,6 +36,18 @@ internal override void Initialize(ApplicationEngine engine) engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_DomainPrice), new StorageItem(10_00000000)); } + internal override void OnPersist(ApplicationEngine engine) + { + uint now = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + 1; + byte[] start = CreateStorageKey(Prefix_Expiration).AddBigEndian(0).ToArray(); + byte[] end = CreateStorageKey(Prefix_Expiration).AddBigEndian(now).ToArray(); + foreach (var (key, _) in engine.Snapshot.Storages.FindRange(start, end)) + { + engine.Snapshot.Storages.Delete(key); + Burn(engine, CreateStorageKey(Prefix_Token).Add(key.Key.AsSpan(5))); + } + } + protected override byte[] GetKey(byte[] tokenId) { return Crypto.Hash160(tokenId); @@ -67,9 +83,71 @@ public long GetPrice(StoreView snapshot) return (long)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_DomainPrice)]; } + [ContractMethod(0_01000000, CallFlags.WriteStates)] + private bool Register(ApplicationEngine engine, string name, UInt160 owner) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string[] names = name.Split('.'); + if (names.Length != 2) throw new ArgumentException(null, nameof(name)); + if (!engine.CheckWitnessInternal(owner)) throw new InvalidOperationException(); + byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); + if (engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash)) is not null) return false; + StringList roots = engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable(); + if (roots is null || roots.BinarySearch(names[1]) < 0) throw new InvalidOperationException(); + engine.AddGas(GetPrice(engine.Snapshot)); + NameState state = new NameState + { + Owner = owner, + Name = name, + Description = "", + Expiration = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + OneYear + }; + Mint(engine, state); + engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); + return true; + } + + [ContractMethod(0, CallFlags.WriteStates)] + private uint Renew(ApplicationEngine engine, string name) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string[] names = name.Split('.'); + if (names.Length != 2) throw new ArgumentException(null, nameof(name)); + engine.AddGas(GetPrice(engine.Snapshot)); + byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); + NameState state = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Token).Add(hash)).GetInteroperable(); + engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash)); + state.Expiration += OneYear; + engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); + return state.Expiration; + } + public class NameState : NFTState { + public uint Expiration; + public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["expiration"] = Expiration; + return json; + } + + public override void FromStackItem(StackItem stackItem) + { + base.FromStackItem(stackItem); + Struct @struct = (Struct)stackItem; + Expiration = (uint)@struct[3].GetInteger(); + } + + public override StackItem ToStackItem(ReferenceCounter referenceCounter) + { + Struct @struct = (Struct)base.ToStackItem(referenceCounter); + @struct.Add(Expiration); + return @struct; + } } private class StringList : List, IInteroperable diff --git a/src/neo/SmartContract/Native/NonfungibleToken.cs b/src/neo/SmartContract/Native/NonfungibleToken.cs index 4ad9799269..b22ff83a54 100644 --- a/src/neo/SmartContract/Native/NonfungibleToken.cs +++ b/src/neo/SmartContract/Native/NonfungibleToken.cs @@ -24,7 +24,7 @@ public abstract class NonfungibleToken : NativeContract private const byte Prefix_TotalSupply = 11; private const byte Prefix_Account = 7; - private const byte Prefix_Token = 5; + protected const byte Prefix_Token = 5; protected NonfungibleToken() { @@ -75,17 +75,21 @@ protected void Mint(ApplicationEngine engine, TokenState token) protected void Burn(ApplicationEngine engine, byte[] tokenId) { - StorageKey key_token = CreateStorageKey(Prefix_Token).Add(GetKey(tokenId)); - TokenState token = engine.Snapshot.Storages.TryGet(key_token)?.GetInteroperable(); + Burn(engine, CreateStorageKey(Prefix_Token).Add(GetKey(tokenId))); + } + + private protected void Burn(ApplicationEngine engine, StorageKey key) + { + TokenState token = engine.Snapshot.Storages.TryGet(key)?.GetInteroperable(); if (token is null) throw new InvalidOperationException(); - engine.Snapshot.Storages.Delete(key_token); + engine.Snapshot.Storages.Delete(key); StorageKey key_account = CreateStorageKey(Prefix_Account).Add(token.Owner); NFTAccountState account = engine.Snapshot.Storages.GetAndChange(key_account).GetInteroperable(); - account.Remove(tokenId); + account.Remove(token.Id); if (account.Balance.IsZero) engine.Snapshot.Storages.Delete(key_account); engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_TotalSupply)).Add(-1); - PostTransfer(engine, token.Owner, null, tokenId); + PostTransfer(engine, token.Owner, null, token.Id); } [ContractMethod(0_01000000, CallFlags.ReadStates)] From 066e17f1713bf372614e3f48d0f8b415d8783e85 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 5 Jan 2021 23:41:57 +0800 Subject: [PATCH 06/20] SetAdmin --- src/neo/SmartContract/Native/NameService.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 3e5fc3442b..bc61b17ae6 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -1,6 +1,7 @@ #pragma warning disable IDE0051 using Neo.Cryptography; +using Neo.IO; using Neo.IO.Json; using Neo.Ledger; using Neo.Persistence; @@ -122,9 +123,22 @@ private uint Renew(ApplicationEngine engine, string name) return state.Expiration; } + [ContractMethod(0_03000000, CallFlags.WriteStates)] + private void SetAdmin(ApplicationEngine engine, string name, UInt160 admin) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string[] names = name.Split('.'); + if (names.Length != 2) throw new ArgumentException(null, nameof(name)); + if (!engine.CheckWitnessInternal(admin)) throw new InvalidOperationException(); + NameState state = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Token).Add(GetKey(Utility.StrictUTF8.GetBytes(name)))).GetInteroperable(); + if (!engine.CheckWitnessInternal(state.Owner)) throw new InvalidOperationException(); + state.Admin = admin; + } + public class NameState : NFTState { public uint Expiration; + public UInt160 Admin; public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); @@ -140,12 +154,14 @@ public override void FromStackItem(StackItem stackItem) base.FromStackItem(stackItem); Struct @struct = (Struct)stackItem; Expiration = (uint)@struct[3].GetInteger(); + Admin = @struct[4].IsNull ? null : new UInt160(@struct[4].GetSpan()); } public override StackItem ToStackItem(ReferenceCounter referenceCounter) { Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add(Expiration); + @struct.Add(Admin?.ToArray() ?? StackItem.Null); return @struct; } } From f0cb8513da53850d32218054b0518e6837e203b5 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Wed, 6 Jan 2021 01:45:55 +0800 Subject: [PATCH 07/20] Set, query, resolve --- src/neo/SmartContract/Native/NameService.cs | 68 ++++++++++++++++++++- src/neo/SmartContract/Native/RecordType.cs | 9 --- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index bc61b17ae6..1d39bce3d8 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Numerics; using System.Text.RegularExpressions; @@ -23,6 +25,7 @@ public sealed class NameService : NonfungibleToken private const byte Prefix_Roots = 10; private const byte Prefix_DomainPrice = 22; private const byte Prefix_Expiration = 20; + private const byte Prefix_Record = 12; private const uint OneYear = 365 * 24 * 3600; private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$"); @@ -135,6 +138,67 @@ private void SetAdmin(ApplicationEngine engine, string name, UInt160 admin) state.Admin = admin; } + [ContractMethod(0_30000000, CallFlags.WriteStates)] + private void SetRecord(ApplicationEngine engine, string name, RecordType type, string data) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + switch (type) + { + case RecordType.A: + if (!IPAddress.TryParse(data, out IPAddress address)) throw new FormatException(); + if (address.AddressFamily != AddressFamily.InterNetwork) throw new FormatException(); + break; + case RecordType.CNAME: + if (!nameRegex.IsMatch(data)) throw new FormatException(); + break; + case RecordType.TXT: + if (Utility.StrictUTF8.GetByteCount(data) > 255) throw new FormatException(); + break; + case RecordType.AAAA: + if (!IPAddress.TryParse(data, out address)) throw new FormatException(); + if (address.AddressFamily != AddressFamily.InterNetworkV6) throw new FormatException(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + string domain = string.Join('.', name.Split('.')[^2..]); + NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(GetKey(Utility.StrictUTF8.GetBytes(domain)))].GetInteroperable(); + if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); + item.Value = Utility.StrictUTF8.GetBytes(data); + } + + [ContractMethod(0_01000000, CallFlags.ReadStates)] + public string GetRecord(StoreView snapshot, string name, RecordType type) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + StorageItem item = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); + if (item is null) return null; + return Utility.StrictUTF8.GetString(item.Value); + } + + public IEnumerable<(RecordType Type, string Data)> GetRecords(StoreView snapshot, string name) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + foreach (var (key, value) in snapshot.Storages.Find(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).ToArray())) + yield return ((RecordType)key.Key[^1], Utility.StrictUTF8.GetString(value.Value)); + } + + [ContractMethod(0_03000000, CallFlags.ReadStates)] + public string Resolve(StoreView snapshot, string name, RecordType type) + { + return Resolve(snapshot, name, type, 2); + } + + private string Resolve(StoreView snapshot, string name, RecordType type, int redirect) + { + if (redirect < 0) throw new InvalidOperationException(); + var dictionary = GetRecords(snapshot, name).ToDictionary(p => p.Type, p => p.Data); + if (dictionary.TryGetValue(type, out string data)) return data; + if (!dictionary.TryGetValue(RecordType.CNAME, out data)) return null; + return Resolve(snapshot, data, type, redirect - 1); + } + public class NameState : NFTState { public uint Expiration; @@ -168,13 +232,13 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) private class StringList : List, IInteroperable { - public void FromStackItem(StackItem stackItem) + void IInteroperable.FromStackItem(StackItem stackItem) { foreach (StackItem item in (VM.Types.Array)stackItem) Add(item.GetString()); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, this.Select(p => (ByteString)p)); } diff --git a/src/neo/SmartContract/Native/RecordType.cs b/src/neo/SmartContract/Native/RecordType.cs index 316d6c30d6..9ecf067c61 100644 --- a/src/neo/SmartContract/Native/RecordType.cs +++ b/src/neo/SmartContract/Native/RecordType.cs @@ -5,20 +5,11 @@ public enum RecordType : byte #region [RFC 1035](https://tools.ietf.org/html/rfc1035) A = 1, CNAME = 5, - MX = 15, TXT = 16, #endregion #region [RFC 3596](https://tools.ietf.org/html/rfc3596) AAAA = 28, #endregion - - #region [RFC 2782](https://tools.ietf.org/html/rfc2782) - SRV = 33, - #endregion - - #region [RFC 6672](https://tools.ietf.org/html/rfc6672) - DNAME = 39 - #endregion } } From cf2d3dba883adda18d10fa7bb869a1e6e0759e70 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Wed, 6 Jan 2021 02:37:16 +0800 Subject: [PATCH 08/20] Delete record --- src/neo/SmartContract/Native/NameService.cs | 26 +++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 1d39bce3d8..7ce60b2b95 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -48,6 +48,8 @@ internal override void OnPersist(ApplicationEngine engine) foreach (var (key, _) in engine.Snapshot.Storages.FindRange(start, end)) { engine.Snapshot.Storages.Delete(key); + foreach (var (key2, _) in engine.Snapshot.Storages.Find(CreateStorageKey(Prefix_Record).Add(key.Key.AsSpan(5)).ToArray())) + engine.Snapshot.Storages.Delete(key2); Burn(engine, CreateStorageKey(Prefix_Token).Add(key.Key.AsSpan(5))); } } @@ -162,9 +164,10 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s throw new ArgumentOutOfRangeException(nameof(type)); } string domain = string.Join('.', name.Split('.')[^2..]); - NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(GetKey(Utility.StrictUTF8.GetBytes(domain)))].GetInteroperable(); + byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); + NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); - StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); + StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); item.Value = Utility.StrictUTF8.GetBytes(data); } @@ -172,7 +175,9 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s public string GetRecord(StoreView snapshot, string name, RecordType type) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); - StorageItem item = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); + string domain = string.Join('.', name.Split('.')[^2..]); + byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); + StorageItem item = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); if (item is null) return null; return Utility.StrictUTF8.GetString(item.Value); } @@ -180,10 +185,23 @@ public string GetRecord(StoreView snapshot, string name, RecordType type) public IEnumerable<(RecordType Type, string Data)> GetRecords(StoreView snapshot, string name) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); - foreach (var (key, value) in snapshot.Storages.Find(CreateStorageKey(Prefix_Record).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).ToArray())) + string domain = string.Join('.', name.Split('.')[^2..]); + byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); + foreach (var (key, value) in snapshot.Storages.Find(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).ToArray())) yield return ((RecordType)key.Key[^1], Utility.StrictUTF8.GetString(value.Value)); } + [ContractMethod(0_01000000, CallFlags.WriteStates)] + private void DeleteRecord(ApplicationEngine engine, string name, RecordType type) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string domain = string.Join('.', name.Split('.')[^2..]); + byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); + NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); + if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); + } + [ContractMethod(0_03000000, CallFlags.ReadStates)] public string Resolve(StoreView snapshot, string name, RecordType type) { From c8abd3c2a94e4908c099788f006f5bddb5f9b767 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 10:58:19 +0100 Subject: [PATCH 09/20] Add some test --- src/neo/SmartContract/Native/NameService.cs | 4 +- .../SmartContract/Native/UT_NameService.cs | 163 ++++++++++++++++++ 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 7ce60b2b95..7485dd546e 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -28,8 +28,8 @@ public sealed class NameService : NonfungibleToken private const byte Prefix_Record = 12; private const uint OneYear = 365 * 24 * 3600; - private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$"); - private static readonly Regex nameRegex = new Regex("^(?=.{3,255}$)([a-z0-9]{1,62}\\.)+[a-z][a-z0-9]{0,15}$"); + private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); + private static readonly Regex nameRegex = new Regex("^(?=.{3,255}$)([a-z0-9]{1,62}\\.)+[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); internal NameService() { diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs new file mode 100644 index 0000000000..db65c6d44b --- /dev/null +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -0,0 +1,163 @@ +using Akka.TestKit.Xunit2; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests.Extensions; +using Neo.VM; +using System.Linq; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_NameService : TestKit + { + protected StoreView _snapshot; + + [TestInitialize] + public void TestSetup() + { + TestBlockchain.InitializeMockNeoSystem(); + _snapshot = Blockchain.Singleton.GetSnapshot(); + } + + [TestMethod] + public void TestInfo() + { + Assert.AreEqual("NameService", NativeContract.NameService.Name); + Assert.AreEqual("NNS", NativeContract.NameService.Symbol); + } + + [TestMethod] + public void TestRoots() + { + var snapshot = _snapshot.Clone(); + snapshot.PersistingBlock = new Block() { Index = 1000 }; + + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // no match + var result = Check_AddRoot(snapshot, from, "te_st"); + Assert.IsFalse(result); + + // unsigned + result = Check_AddRoot(snapshot, UInt160.Zero, "test"); + Assert.IsFalse(result); + + // add root + result = Check_AddRoot(snapshot, from, "test"); + Assert.IsTrue(result); + CollectionAssert.AreEqual(new string[] { "test" }, NativeContract.NameService.GetRoots(snapshot).ToArray()); + + // add root twice + result = Check_AddRoot(snapshot, from, "test"); + Assert.IsFalse(result); + } + + [TestMethod] + public void TestPrice() + { + var snapshot = _snapshot.Clone(); + snapshot.PersistingBlock = new Block() { Index = 1000 }; + + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // unsigned + var result = Check_SetPrice(snapshot, UInt160.Zero, 1); + Assert.IsFalse(result); + + // under value + result = Check_SetPrice(snapshot, from, 0); + Assert.IsFalse(result); + + // overvalue + result = Check_SetPrice(snapshot, from, 10000_00000001); + Assert.IsFalse(result); + + // good + result = Check_SetPrice(snapshot, from, 55); + Assert.IsTrue(result); + Assert.AreEqual(55, NativeContract.NameService.GetPrice(snapshot)); + } + + [TestMethod] + public void TestRegisterRenew() + { + var snapshot = _snapshot.Clone(); + snapshot.PersistingBlock = new Block() { Index = 1000 }; + + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // no-roots + var result = Check_Register(snapshot, "neo.org", UInt160.Zero); + Assert.IsFalse(result); + + // more than 2 dots + result = Check_Register(snapshot, "doc.neo.org", UInt160.Zero); + Assert.IsFalse(result); + + // regex + result = Check_Register(snapshot, "neo.org\n", UInt160.Zero); + Assert.IsFalse(result); + } + + internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "register", false, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name }, + new ContractParameter(ContractParameterType.Hash160) { Value = owner } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + var result = engine.ResultStack.Pop(); + Assert.IsInstanceOfType(result, typeof(VM.Types.Boolean)); + + return result.GetBoolean(); + } + + internal static bool Check_SetPrice(StoreView snapshot, UInt160 signedBy, long price) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setPrice", false, new ContractParameter[] { new ContractParameter(ContractParameterType.Integer) { Value = price } }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + + internal static bool Check_AddRoot(StoreView snapshot, UInt160 signedBy, string root) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "addRoot", false, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = root } }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + } +} From 020fc3fae4df1181cb22786d4552c7b7f4885101 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 11:16:12 +0100 Subject: [PATCH 10/20] Add IsAvailable --- src/neo/SmartContract/Native/NameService.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 7485dd546e..27337ef72c 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -89,6 +89,19 @@ public long GetPrice(StoreView snapshot) return (long)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_DomainPrice)]; } + [ContractMethod(0_01000000, CallFlags.ReadStates)] + private bool IsAvailable(ApplicationEngine engine, string name) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string[] names = name.Split('.'); + if (names.Length != 2) throw new ArgumentException(null, nameof(name)); + byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); + if (engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash)) is not null) return false; + StringList roots = engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable(); + if (roots is null || roots.BinarySearch(names[1]) < 0) throw new InvalidOperationException(); + return true; + } + [ContractMethod(0_01000000, CallFlags.WriteStates)] private bool Register(ApplicationEngine engine, string name, UInt160 owner) { From 63ae5427039cce2e5fdf2666f033cf55b95205b2 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 11:31:22 +0100 Subject: [PATCH 11/20] Add isAvailable --- .../SmartContract/Native/UT_NameService.cs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index db65c6d44b..6128579916 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -86,15 +86,19 @@ public void TestPrice() } [TestMethod] - public void TestRegisterRenew() + public void TestRegister() { var snapshot = _snapshot.Clone(); snapshot.PersistingBlock = new Block() { Index = 1000 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + // add root + var result = Check_AddRoot(snapshot, from, "com"); + Assert.IsTrue(result); + // no-roots - var result = Check_Register(snapshot, "neo.org", UInt160.Zero); + result = Check_Register(snapshot, "neo.org", UInt160.Zero); Assert.IsFalse(result); // more than 2 dots @@ -104,6 +108,35 @@ public void TestRegisterRenew() // regex result = Check_Register(snapshot, "neo.org\n", UInt160.Zero); Assert.IsFalse(result); + + // good register + result = Check_IsAvailable(snapshot, "neo.com"); + Assert.IsTrue(result); + result = Check_Register(snapshot, "neo.com", UInt160.Zero); + Assert.IsTrue(result); + result = Check_IsAvailable(snapshot, "neo.com"); + Assert.IsFalse(result); + } + + internal static bool Check_IsAvailable(StoreView snapshot, string name) + { + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "isAvailable", true, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + var result = engine.ResultStack.Pop(); + Assert.IsInstanceOfType(result, typeof(VM.Types.Boolean)); + + return result.GetBoolean(); } internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) @@ -111,7 +144,7 @@ internal static bool Check_Register(StoreView snapshot, string name, UInt160 own var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot); var script = new ScriptBuilder(); - script.EmitDynamicCall(NativeContract.NameService.Hash, "register", false, new ContractParameter[] { + script.EmitDynamicCall(NativeContract.NameService.Hash, "register", true, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, new ContractParameter(ContractParameterType.Hash160) { Value = owner } }); From b028495396a31330e5841db87d89a3f9b20d137a Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 11:43:19 +0100 Subject: [PATCH 12/20] Add ExpireOn --- src/neo/SmartContract/Native/NameService.cs | 18 +++++++++-- .../SmartContract/Native/UT_NameService.cs | 30 +++---------------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 27337ef72c..5661b1caaa 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -90,14 +90,14 @@ public long GetPrice(StoreView snapshot) } [ContractMethod(0_01000000, CallFlags.ReadStates)] - private bool IsAvailable(ApplicationEngine engine, string name) + public bool IsAvailable(StoreView snapshot, string name) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); string[] names = name.Split('.'); if (names.Length != 2) throw new ArgumentException(null, nameof(name)); byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); - if (engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash)) is not null) return false; - StringList roots = engine.Snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable(); + if (snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash)) is not null) return false; + StringList roots = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Roots))?.GetInteroperable(); if (roots is null || roots.BinarySearch(names[1]) < 0) throw new InvalidOperationException(); return true; } @@ -126,6 +126,18 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) return true; } + [ContractMethod(0, CallFlags.ReadStates)] + public uint ExpireOn(StoreView snapshot, string name) + { + if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); + string[] names = name.Split('.'); + if (names.Length != 2) throw new ArgumentException(null, nameof(name)); + byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); + NameState state = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash))?.GetInteroperable(); + if (state == null) throw new InvalidOperationException(); + return state.Expiration; + } + [ContractMethod(0, CallFlags.WriteStates)] private uint Renew(ApplicationEngine engine, string name) { diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 6128579916..82ae492a17 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -89,7 +89,7 @@ public void TestPrice() public void TestRegister() { var snapshot = _snapshot.Clone(); - snapshot.PersistingBlock = new Block() { Index = 1000 }; + snapshot.PersistingBlock = new Block() { Index = 1000, Timestamp = 0 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); @@ -110,33 +110,11 @@ public void TestRegister() Assert.IsFalse(result); // good register - result = Check_IsAvailable(snapshot, "neo.com"); - Assert.IsTrue(result); + Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); result = Check_Register(snapshot, "neo.com", UInt160.Zero); Assert.IsTrue(result); - result = Check_IsAvailable(snapshot, "neo.com"); - Assert.IsFalse(result); - } - - internal static bool Check_IsAvailable(StoreView snapshot, string name) - { - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - - var script = new ScriptBuilder(); - script.EmitDynamicCall(NativeContract.NameService.Hash, "isAvailable", true, new ContractParameter[] { - new ContractParameter(ContractParameterType.String) { Value = name } - }); - engine.LoadScript(script.ToArray(), 0, -1, 0); - - if (engine.Execute() == VMState.FAULT) - { - return false; - } - - var result = engine.ResultStack.Pop(); - Assert.IsInstanceOfType(result, typeof(VM.Types.Boolean)); - - return result.GetBoolean(); + Assert.AreEqual(31536000u, NativeContract.NameService.ExpireOn(snapshot, "neo.com")); + Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); } internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) From af5484a24e6e02db017e3f966b15e4be914cf6ab Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 13:28:02 +0100 Subject: [PATCH 13/20] Remove expireOn --- src/neo/SmartContract/Native/NameService.cs | 12 ------------ .../SmartContract/Native/UT_NameService.cs | 6 +++--- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 5661b1caaa..1422de432d 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -126,18 +126,6 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) return true; } - [ContractMethod(0, CallFlags.ReadStates)] - public uint ExpireOn(StoreView snapshot, string name) - { - if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); - string[] names = name.Split('.'); - if (names.Length != 2) throw new ArgumentException(null, nameof(name)); - byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); - NameState state = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Token).Add(hash))?.GetInteroperable(); - if (state == null) throw new InvalidOperationException(); - return state.Expiration; - } - [ContractMethod(0, CallFlags.WriteStates)] private uint Renew(ApplicationEngine engine, string name) { diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 82ae492a17..6d61e7ede0 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -1,7 +1,6 @@ using Akka.TestKit.Xunit2; -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Cryptography.ECC; +using Neo.Cryptography; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -11,6 +10,7 @@ using Neo.UnitTests.Extensions; using Neo.VM; using System.Linq; +using System.Text; namespace Neo.UnitTests.SmartContract.Native { @@ -113,7 +113,7 @@ public void TestRegister() Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); result = Check_Register(snapshot, "neo.com", UInt160.Zero); Assert.IsTrue(result); - Assert.AreEqual(31536000u, NativeContract.NameService.ExpireOn(snapshot, "neo.com")); + Assert.AreEqual(31536000u, (uint)NativeContract.NameService.Properties(snapshot, Encoding.UTF8.GetBytes("neo.com"))["expiration"].AsNumber()); Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); } From 7e1c8e797e2729577badea996f025f3b046fd862 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 13:48:51 +0100 Subject: [PATCH 14/20] Change owner or admin --- src/neo/SmartContract/Native/NameService.cs | 6 +++-- .../SmartContract/Native/UT_NameService.cs | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 1422de432d..b38be2aa9d 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -117,6 +117,7 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) NameState state = new NameState { Owner = owner, + Admin = owner, Name = name, Description = "", Expiration = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + OneYear @@ -135,6 +136,7 @@ private uint Renew(ApplicationEngine engine, string name) engine.AddGas(GetPrice(engine.Snapshot)); byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); NameState state = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Token).Add(hash)).GetInteroperable(); + if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash)); state.Expiration += OneYear; engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); @@ -179,7 +181,7 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); item.Value = Utility.StrictUTF8.GetBytes(data); } @@ -211,7 +213,7 @@ private void DeleteRecord(ApplicationEngine engine, string name, RecordType type string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); } diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 6d61e7ede0..ec743196e6 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -10,6 +10,7 @@ using Neo.UnitTests.Extensions; using Neo.VM; using System.Linq; +using System.Numerics; using System.Text; namespace Neo.UnitTests.SmartContract.Native @@ -115,6 +116,32 @@ public void TestRegister() Assert.IsTrue(result); Assert.AreEqual(31536000u, (uint)NativeContract.NameService.Properties(snapshot, Encoding.UTF8.GetBytes("neo.com"))["expiration"].AsNumber()); Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); + + var resultInt = Check_Renew(snapshot, "neo.com", UInt160.Zero); + Assert.AreEqual(31536000u * 2, (uint)resultInt); + Assert.AreEqual(31536000u * 2, (uint)NativeContract.NameService.Properties(snapshot, Encoding.UTF8.GetBytes("neo.com"))["expiration"].AsNumber()); + Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); + } + + internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 signedBy) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "renew", true, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return -1; + } + + var result = engine.ResultStack.Pop(); + Assert.IsInstanceOfType(result, typeof(VM.Types.Integer)); + + return result.GetInteger(); } internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) From cb2fa6f6b5d1a6d5b66d490e16e16fb2200a03b5 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 13:51:49 +0100 Subject: [PATCH 15/20] Remove renew check --- src/neo/SmartContract/Native/NameService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index b38be2aa9d..d131b938cc 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -136,7 +136,6 @@ private uint Renew(ApplicationEngine engine, string name) engine.AddGas(GetPrice(engine.Snapshot)); byte[] hash = GetKey(Utility.StrictUTF8.GetBytes(name)); NameState state = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Token).Add(hash)).GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash)); state.Expiration += OneYear; engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); From 51b5f1da51bac008354b19e4236cf0f9397786aa Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 14:11:18 +0100 Subject: [PATCH 16/20] Add more ut --- .../SmartContract/Native/UT_NameService.cs | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index ec743196e6..afb0d1d237 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -107,7 +107,7 @@ public void TestRegister() Assert.IsFalse(result); // regex - result = Check_Register(snapshot, "neo.org\n", UInt160.Zero); + result = Check_Register(snapshot, "neo.com\n", UInt160.Zero); Assert.IsFalse(result); // good register @@ -123,6 +123,72 @@ public void TestRegister() Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); } + [TestMethod] + public void TestSetRecord() + { + var snapshot = _snapshot.Clone(); + snapshot.PersistingBlock = new Block() { Index = 1000, Timestamp = 0 }; + + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // add root + var result = Check_AddRoot(snapshot, from, "com"); + Assert.IsTrue(result); + + // good register + Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); + result = Check_Register(snapshot, "neo.com", UInt160.Zero); + Assert.IsTrue(result); + result = Check_SetRecord(snapshot, "neo.com", RecordType.A, "8.8.8.8", UInt160.Zero); + Assert.IsTrue(result); + Assert.AreEqual("8.8.8.8", NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); + CollectionAssert.AreEqual(new string[] { $"{RecordType.A}=8.8.8.8" }, NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); + + // delete register + result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero); + Assert.AreEqual(null, NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); + CollectionAssert.AreEqual(System.Array.Empty(), NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); + } + + internal static bool Check_DeleteRecord(StoreView snapshot, string name, RecordType type, UInt160 signedBy) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "deleteRecord", false, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name }, + new ContractParameter(ContractParameterType.Integer) { Value = (int)type } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + + internal static bool Check_SetRecord(StoreView snapshot, string name, RecordType type, string data, UInt160 signedBy) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setRecord", false, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name }, + new ContractParameter(ContractParameterType.Integer) { Value = (int)type }, + new ContractParameter(ContractParameterType.String) { Value = data } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 signedBy) { var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); From 04d5da3683db8f159e3ba5f88ec82d3800e6c12a Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 14:22:54 +0100 Subject: [PATCH 17/20] Reverted OR --- src/neo/SmartContract/Native/NameService.cs | 4 +-- .../SmartContract/Native/UT_NameService.cs | 32 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index d131b938cc..730f79523d 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -180,7 +180,7 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); item.Value = Utility.StrictUTF8.GetBytes(data); } @@ -212,7 +212,7 @@ private void DeleteRecord(ApplicationEngine engine, string name, RecordType type string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) || !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); } diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index afb0d1d237..30526b38ea 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -137,15 +137,24 @@ public void TestSetRecord() // good register Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); - result = Check_Register(snapshot, "neo.com", UInt160.Zero); + result = Check_Register(snapshot, "neo.com", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); Assert.IsTrue(result); - result = Check_SetRecord(snapshot, "neo.com", RecordType.A, "8.8.8.8", UInt160.Zero); + result = Check_SetRecord(snapshot, "neo.com", RecordType.A, "8.8.8.8", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); Assert.IsTrue(result); Assert.AreEqual("8.8.8.8", NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); CollectionAssert.AreEqual(new string[] { $"{RecordType.A}=8.8.8.8" }, NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); + // wrong signed delete register + result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero); + Assert.IsFalse(result); + + // set admin + result = Check_SetAdmin(snapshot, "neo.com", UInt160.Zero, UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); + Assert.IsTrue(result); + // delete register result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero); + Assert.IsTrue(result); Assert.AreEqual(null, NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); CollectionAssert.AreEqual(System.Array.Empty(), NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); } @@ -210,6 +219,25 @@ internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 return result.GetInteger(); } + internal static bool Check_SetAdmin(StoreView snapshot, string name, UInt160 admin, UInt160 signedBy) + { + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(admin, signedBy), snapshot); + + var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setAdmin", false, new ContractParameter[] { + new ContractParameter(ContractParameterType.String) { Value = name }, + new ContractParameter(ContractParameterType.Hash160) { Value = admin } + }); + engine.LoadScript(script.ToArray(), 0, -1, 0); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) { var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot); From 820f5b4d7c849b0f7507972bebc48f33f2d95636 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 7 Jan 2021 14:30:06 +0100 Subject: [PATCH 18/20] More --- tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 30526b38ea..18526b914b 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -107,8 +107,8 @@ public void TestRegister() Assert.IsFalse(result); // regex - result = Check_Register(snapshot, "neo.com\n", UInt160.Zero); - Assert.IsFalse(result); + Assert.IsFalse(Check_Register(snapshot, "\nneo.com", UInt160.Zero)); + Assert.IsFalse(Check_Register(snapshot, "neo.com\n", UInt160.Zero)); // good register Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); From ec416014e9cdd6fe73ab4c5b4c39e3f98372d0ba Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Thu, 7 Jan 2021 22:35:24 +0800 Subject: [PATCH 19/20] CheckAdmin --- src/neo/SmartContract/Native/NameService.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 730f79523d..a8f39c8f09 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -117,7 +117,6 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) NameState state = new NameState { Owner = owner, - Admin = owner, Name = name, Description = "", Expiration = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + OneYear @@ -148,12 +147,19 @@ private void SetAdmin(ApplicationEngine engine, string name, UInt160 admin) if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); string[] names = name.Split('.'); if (names.Length != 2) throw new ArgumentException(null, nameof(name)); - if (!engine.CheckWitnessInternal(admin)) throw new InvalidOperationException(); + if (admin != null && !engine.CheckWitnessInternal(admin)) throw new InvalidOperationException(); NameState state = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Token).Add(GetKey(Utility.StrictUTF8.GetBytes(name)))).GetInteroperable(); if (!engine.CheckWitnessInternal(state.Owner)) throw new InvalidOperationException(); state.Admin = admin; } + private static bool CheckAdmin(ApplicationEngine engine, NameState state) + { + if (engine.CheckWitnessInternal(state.Owner)) return true; + if (state.Admin is null) return false; + return engine.CheckWitnessInternal(state.Admin); + } + [ContractMethod(0_30000000, CallFlags.WriteStates)] private void SetRecord(ApplicationEngine engine, string name, RecordType type, string data) { @@ -180,7 +186,7 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!CheckAdmin(engine, state)) throw new InvalidOperationException(); StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type), () => new StorageItem()); item.Value = Utility.StrictUTF8.GetBytes(data); } @@ -212,7 +218,7 @@ private void DeleteRecord(ApplicationEngine engine, string name, RecordType type string domain = string.Join('.', name.Split('.')[^2..]); byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); - if (!engine.CheckWitnessInternal(state.Owner) && !engine.CheckWitnessInternal(state.Admin)) throw new InvalidOperationException(); + if (!CheckAdmin(engine, state)) throw new InvalidOperationException(); engine.Snapshot.Storages.Delete(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); } From a6385441a3871f96cf27eca93ea3ff55098a514a Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 10 Jan 2021 11:14:29 +0100 Subject: [PATCH 20/20] Fix merge --- src/neo/SmartContract/Native/NameService.cs | 4 +- .../SmartContract/Native/UT_NameService.cs | 89 ++++++++----------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index a8f39c8f09..d6be3d92c1 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -42,7 +42,7 @@ internal override void Initialize(ApplicationEngine engine) internal override void OnPersist(ApplicationEngine engine) { - uint now = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + 1; + uint now = (uint)(engine.PersistingBlock.Timestamp / 1000) + 1; byte[] start = CreateStorageKey(Prefix_Expiration).AddBigEndian(0).ToArray(); byte[] end = CreateStorageKey(Prefix_Expiration).AddBigEndian(now).ToArray(); foreach (var (key, _) in engine.Snapshot.Storages.FindRange(start, end)) @@ -119,7 +119,7 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) Owner = owner, Name = name, Description = "", - Expiration = (uint)(engine.Snapshot.PersistingBlock.Timestamp / 1000) + OneYear + Expiration = (uint)(engine.PersistingBlock.Timestamp / 1000) + OneYear }; Mint(engine, state); engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 18526b914b..919d8edce9 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -38,25 +38,24 @@ public void TestInfo() public void TestRoots() { var snapshot = _snapshot.Clone(); - snapshot.PersistingBlock = new Block() { Index = 1000 }; - + var persistingBlock = new Block() { Index = 1000 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // no match - var result = Check_AddRoot(snapshot, from, "te_st"); + var result = Check_AddRoot(snapshot, from, "te_st", persistingBlock); Assert.IsFalse(result); // unsigned - result = Check_AddRoot(snapshot, UInt160.Zero, "test"); + result = Check_AddRoot(snapshot, UInt160.Zero, "test", persistingBlock); Assert.IsFalse(result); // add root - result = Check_AddRoot(snapshot, from, "test"); + result = Check_AddRoot(snapshot, from, "test", persistingBlock); Assert.IsTrue(result); CollectionAssert.AreEqual(new string[] { "test" }, NativeContract.NameService.GetRoots(snapshot).ToArray()); // add root twice - result = Check_AddRoot(snapshot, from, "test"); + result = Check_AddRoot(snapshot, from, "test", persistingBlock); Assert.IsFalse(result); } @@ -64,24 +63,23 @@ public void TestRoots() public void TestPrice() { var snapshot = _snapshot.Clone(); - snapshot.PersistingBlock = new Block() { Index = 1000 }; - + var persistingBlock = new Block() { Index = 1000 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // unsigned - var result = Check_SetPrice(snapshot, UInt160.Zero, 1); + var result = Check_SetPrice(snapshot, UInt160.Zero, 1, persistingBlock); Assert.IsFalse(result); // under value - result = Check_SetPrice(snapshot, from, 0); + result = Check_SetPrice(snapshot, from, 0, persistingBlock); Assert.IsFalse(result); // overvalue - result = Check_SetPrice(snapshot, from, 10000_00000001); + result = Check_SetPrice(snapshot, from, 10000_00000001, persistingBlock); Assert.IsFalse(result); // good - result = Check_SetPrice(snapshot, from, 55); + result = Check_SetPrice(snapshot, from, 55, persistingBlock); Assert.IsTrue(result); Assert.AreEqual(55, NativeContract.NameService.GetPrice(snapshot)); } @@ -90,34 +88,33 @@ public void TestPrice() public void TestRegister() { var snapshot = _snapshot.Clone(); - snapshot.PersistingBlock = new Block() { Index = 1000, Timestamp = 0 }; - + var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // add root - var result = Check_AddRoot(snapshot, from, "com"); + var result = Check_AddRoot(snapshot, from, "com", persistingBlock); Assert.IsTrue(result); // no-roots - result = Check_Register(snapshot, "neo.org", UInt160.Zero); + result = Check_Register(snapshot, "neo.org", UInt160.Zero, persistingBlock); Assert.IsFalse(result); // more than 2 dots - result = Check_Register(snapshot, "doc.neo.org", UInt160.Zero); + result = Check_Register(snapshot, "doc.neo.org", UInt160.Zero, persistingBlock); Assert.IsFalse(result); // regex - Assert.IsFalse(Check_Register(snapshot, "\nneo.com", UInt160.Zero)); - Assert.IsFalse(Check_Register(snapshot, "neo.com\n", UInt160.Zero)); + Assert.IsFalse(Check_Register(snapshot, "\nneo.com", UInt160.Zero, persistingBlock)); + Assert.IsFalse(Check_Register(snapshot, "neo.com\n", UInt160.Zero, persistingBlock)); // good register Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); - result = Check_Register(snapshot, "neo.com", UInt160.Zero); + result = Check_Register(snapshot, "neo.com", UInt160.Zero, persistingBlock); Assert.IsTrue(result); Assert.AreEqual(31536000u, (uint)NativeContract.NameService.Properties(snapshot, Encoding.UTF8.GetBytes("neo.com"))["expiration"].AsNumber()); Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); - var resultInt = Check_Renew(snapshot, "neo.com", UInt160.Zero); + var resultInt = Check_Renew(snapshot, "neo.com", UInt160.Zero, persistingBlock); Assert.AreEqual(31536000u * 2, (uint)resultInt); Assert.AreEqual(31536000u * 2, (uint)NativeContract.NameService.Properties(snapshot, Encoding.UTF8.GetBytes("neo.com"))["expiration"].AsNumber()); Assert.IsFalse(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); @@ -127,42 +124,40 @@ public void TestRegister() public void TestSetRecord() { var snapshot = _snapshot.Clone(); - snapshot.PersistingBlock = new Block() { Index = 1000, Timestamp = 0 }; - + var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // add root - var result = Check_AddRoot(snapshot, from, "com"); + var result = Check_AddRoot(snapshot, from, "com", persistingBlock); Assert.IsTrue(result); // good register Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); - result = Check_Register(snapshot, "neo.com", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); + result = Check_Register(snapshot, "neo.com", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), persistingBlock); Assert.IsTrue(result); - result = Check_SetRecord(snapshot, "neo.com", RecordType.A, "8.8.8.8", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); + result = Check_SetRecord(snapshot, "neo.com", RecordType.A, "8.8.8.8", UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), persistingBlock); Assert.IsTrue(result); Assert.AreEqual("8.8.8.8", NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); CollectionAssert.AreEqual(new string[] { $"{RecordType.A}=8.8.8.8" }, NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); // wrong signed delete register - result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero); + result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero, persistingBlock); Assert.IsFalse(result); // set admin - result = Check_SetAdmin(snapshot, "neo.com", UInt160.Zero, UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")); + result = Check_SetAdmin(snapshot, "neo.com", UInt160.Zero, UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), persistingBlock); Assert.IsTrue(result); // delete register - result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero); + result = Check_DeleteRecord(snapshot, "neo.com", RecordType.A, UInt160.Zero, persistingBlock); Assert.IsTrue(result); Assert.AreEqual(null, NativeContract.NameService.GetRecord(snapshot, "neo.com", RecordType.A)); CollectionAssert.AreEqual(System.Array.Empty(), NativeContract.NameService.GetRecords(snapshot, "neo.com").Select(u => u.Type.ToString() + "=" + u.Data).ToArray()); } - internal static bool Check_DeleteRecord(StoreView snapshot, string name, RecordType type, UInt160 signedBy) + internal static bool Check_DeleteRecord(StoreView snapshot, string name, RecordType type, UInt160 signedBy, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "deleteRecord", false, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -178,10 +173,9 @@ internal static bool Check_DeleteRecord(StoreView snapshot, string name, RecordT return true; } - internal static bool Check_SetRecord(StoreView snapshot, string name, RecordType type, string data, UInt160 signedBy) + internal static bool Check_SetRecord(StoreView snapshot, string name, RecordType type, string data, UInt160 signedBy, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setRecord", false, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -198,10 +192,9 @@ internal static bool Check_SetRecord(StoreView snapshot, string name, RecordType return true; } - internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 signedBy) + internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 signedBy, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "renew", true, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name } @@ -219,10 +212,9 @@ internal static BigInteger Check_Renew(StoreView snapshot, string name, UInt160 return result.GetInteger(); } - internal static bool Check_SetAdmin(StoreView snapshot, string name, UInt160 admin, UInt160 signedBy) + internal static bool Check_SetAdmin(StoreView snapshot, string name, UInt160 admin, UInt160 signedBy, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(admin, signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(admin, signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setAdmin", false, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -238,10 +230,9 @@ internal static bool Check_SetAdmin(StoreView snapshot, string name, UInt160 adm return true; } - internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner) + internal static bool Check_Register(StoreView snapshot, string name, UInt160 owner, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "register", true, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -260,10 +251,9 @@ internal static bool Check_Register(StoreView snapshot, string name, UInt160 own return result.GetBoolean(); } - internal static bool Check_SetPrice(StoreView snapshot, UInt160 signedBy, long price) + internal static bool Check_SetPrice(StoreView snapshot, UInt160 signedBy, long price, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setPrice", false, new ContractParameter[] { new ContractParameter(ContractParameterType.Integer) { Value = price } }); engine.LoadScript(script.ToArray(), 0, -1, 0); @@ -276,10 +266,9 @@ internal static bool Check_SetPrice(StoreView snapshot, UInt160 signedBy, long p return true; } - internal static bool Check_AddRoot(StoreView snapshot, UInt160 signedBy, string root) + internal static bool Check_AddRoot(StoreView snapshot, UInt160 signedBy, string root, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot); - + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "addRoot", false, new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = root } }); engine.LoadScript(script.ToArray(), 0, -1, 0);