diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs new file mode 100644 index 0000000000..d6be3d92c1 --- /dev/null +++ b/src/neo/SmartContract/Native/NameService.cs @@ -0,0 +1,285 @@ +#pragma warning disable IDE0051 + +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Numerics; +using System.Text.RegularExpressions; + +namespace Neo.SmartContract.Native +{ + public sealed class NameService : NonfungibleToken + { + public override int Id => -6; + public override string Symbol => "NNS"; + + 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}$", 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() + { + } + + 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.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); + 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))); + } + } + + 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(); + } + + [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)]; + } + + [ContractMethod(0_01000000, CallFlags.ReadStates)] + 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 (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; + } + + [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.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; + } + + [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 (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) + { + 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..]); + byte[] hash_domain = GetKey(Utility.StrictUTF8.GetBytes(domain)); + NameState state = engine.Snapshot.Storages[CreateStorageKey(Prefix_Token).Add(hash_domain)].GetInteroperable(); + 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); + } + + [ContractMethod(0_01000000, CallFlags.ReadStates)] + public string GetRecord(StoreView snapshot, 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)); + 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); + } + + public IEnumerable<(RecordType Type, string Data)> GetRecords(StoreView snapshot, string name) + { + 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)); + 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 (!CheckAdmin(engine, state)) 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) + { + 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; + public UInt160 Admin; + + 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(); + 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; + } + } + + private class StringList : List, IInteroperable + { + void IInteroperable.FromStackItem(StackItem stackItem) + { + foreach (StackItem item in (VM.Types.Array)stackItem) + Add(item.GetString()); + } + + StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + { + return new VM.Types.Array(referenceCounter, this.Select(p => (ByteString)p)); + } + } + } +} 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; } 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)] diff --git a/src/neo/SmartContract/Native/RecordType.cs b/src/neo/SmartContract/Native/RecordType.cs new file mode 100644 index 0000000000..9ecf067c61 --- /dev/null +++ b/src/neo/SmartContract/Native/RecordType.cs @@ -0,0 +1,15 @@ +namespace Neo.SmartContract.Native +{ + public enum RecordType : byte + { + #region [RFC 1035](https://tools.ietf.org/html/rfc1035) + A = 1, + CNAME = 5, + TXT = 16, + #endregion + + #region [RFC 3596](https://tools.ietf.org/html/rfc3596) + AAAA = 28, + #endregion + } +} 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..919d8edce9 --- /dev/null +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -0,0 +1,284 @@ +using Akka.TestKit.Xunit2; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +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; +using System.Numerics; +using System.Text; + +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(); + var persistingBlock = new Block() { Index = 1000 }; + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // no match + var result = Check_AddRoot(snapshot, from, "te_st", persistingBlock); + Assert.IsFalse(result); + + // unsigned + result = Check_AddRoot(snapshot, UInt160.Zero, "test", persistingBlock); + Assert.IsFalse(result); + + // add root + 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", persistingBlock); + Assert.IsFalse(result); + } + + [TestMethod] + public void TestPrice() + { + var snapshot = _snapshot.Clone(); + var persistingBlock = new Block() { Index = 1000 }; + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // unsigned + var result = Check_SetPrice(snapshot, UInt160.Zero, 1, persistingBlock); + Assert.IsFalse(result); + + // under value + result = Check_SetPrice(snapshot, from, 0, persistingBlock); + Assert.IsFalse(result); + + // overvalue + result = Check_SetPrice(snapshot, from, 10000_00000001, persistingBlock); + Assert.IsFalse(result); + + // good + result = Check_SetPrice(snapshot, from, 55, persistingBlock); + Assert.IsTrue(result); + Assert.AreEqual(55, NativeContract.NameService.GetPrice(snapshot)); + } + + [TestMethod] + public void TestRegister() + { + var snapshot = _snapshot.Clone(); + var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // add root + var result = Check_AddRoot(snapshot, from, "com", persistingBlock); + Assert.IsTrue(result); + + // no-roots + 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, persistingBlock); + Assert.IsFalse(result); + + // regex + 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, 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, 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")); + } + + [TestMethod] + public void TestSetRecord() + { + var snapshot = _snapshot.Clone(); + var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; + var from = NativeContract.NEO.GetCommitteeAddress(snapshot); + + // add root + 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"), persistingBlock); + Assert.IsTrue(result); + 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, persistingBlock); + Assert.IsFalse(result); + + // set admin + 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, 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, Block persistingBlock) + { + 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 }, + 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, Block persistingBlock) + { + 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 }, + 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, Block persistingBlock) + { + 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 } + }); + 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_SetAdmin(StoreView snapshot, string name, UInt160 admin, UInt160 signedBy, Block persistingBlock) + { + 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 }, + 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, Block persistingBlock) + { + 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 }, + 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, Block persistingBlock) + { + 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); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + + internal static bool Check_AddRoot(StoreView snapshot, UInt160 signedBy, string root, Block persistingBlock) + { + 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); + + if (engine.Execute() == VMState.FAULT) + { + return false; + } + + return true; + } + } +}