diff --git a/src/neo/SmartContract/Manifest/ContractManifest.cs b/src/neo/SmartContract/Manifest/ContractManifest.cs index ff57c1e3c4..b88260272d 100644 --- a/src/neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/neo/SmartContract/Manifest/ContractManifest.cs @@ -34,6 +34,11 @@ public int Size /// public UInt160 Hash => Abi.Hash; + /// + /// Contract name + /// + public string Name { get; set; } + /// /// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. /// @@ -110,6 +115,7 @@ public JObject ToJson() { return new JObject { + ["name"] = Name, ["groups"] = Groups.Select(u => u.ToJson()).ToArray(), ["supportedstandards"] = SupportedStandards.Select(u => new JString(u)).ToArray(), ["abi"] = Abi.ToJson(), @@ -128,6 +134,7 @@ public ContractManifest Clone() { return new ContractManifest { + Name = Name, Groups = Groups.Select(p => p.Clone()).ToArray(), SupportedStandards = SupportedStandards[..], Abi = Abi.Clone(), @@ -156,6 +163,7 @@ public void Deserialize(BinaryReader reader) private void DeserializeFromJson(JObject json) { + Name = json["name"].AsString(); Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray(); SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.AsString()).ToArray(); Abi = ContractAbi.FromJson(json["abi"]); diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index ae670d74a2..e08b8a6954 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -62,6 +62,7 @@ protected NativeContract() } this.Manifest = new ContractManifest { + Name = Name, Groups = System.Array.Empty(), SupportedStandards = new string[0], Abi = new ContractAbi() diff --git a/src/neo/SmartContract/Native/Tokens/GasToken.cs b/src/neo/SmartContract/Native/Tokens/GasToken.cs index c3a6ed08f4..5bec05f471 100644 --- a/src/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/src/neo/SmartContract/Native/Tokens/GasToken.cs @@ -4,7 +4,7 @@ namespace Neo.SmartContract.Native.Tokens { - public sealed class GasToken : Nep5Token + public sealed class GasToken : Nep17Token { public override int Id => -2; public override string Name => "GAS"; diff --git a/src/neo/SmartContract/Native/Tokens/NeoToken.cs b/src/neo/SmartContract/Native/Tokens/NeoToken.cs index 956f78d481..1204d39be6 100644 --- a/src/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/src/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -15,7 +15,7 @@ namespace Neo.SmartContract.Native.Tokens { - public sealed class NeoToken : Nep5Token + public sealed class NeoToken : Nep17Token { public override int Id => -1; public override string Name => "NEO"; diff --git a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs b/src/neo/SmartContract/Native/Tokens/Nep17Token.cs similarity index 86% rename from src/neo/SmartContract/Native/Tokens/Nep5Token.cs rename to src/neo/SmartContract/Native/Tokens/Nep17Token.cs index a2337b1c25..c216908844 100644 --- a/src/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/src/neo/SmartContract/Native/Tokens/Nep17Token.cs @@ -10,7 +10,7 @@ namespace Neo.SmartContract.Native.Tokens { - public abstract class Nep5Token : NativeContract + public abstract class Nep17Token : NativeContract where TState : AccountState, new() { [ContractMethod(0, CallFlags.None)] @@ -22,11 +22,11 @@ public abstract class Nep5Token : NativeContract protected const byte Prefix_TotalSupply = 11; protected const byte Prefix_Account = 20; - protected Nep5Token() + protected Nep17Token() { this.Factor = BigInteger.Pow(10, Decimals); - Manifest.SupportedStandards = new[] { "NEP-5" }; + Manifest.SupportedStandards = new[] { "NEP-17" }; var events = new List(Manifest.Abi.Events) { @@ -67,7 +67,7 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, state.Balance += amount; storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero)); storage.Add(amount); - engine.SendNotification(Hash, "Transfer", new Array { StackItem.Null, account.ToArray(), amount }); + PostTransfer(engine, null, account, amount); } internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) @@ -85,7 +85,7 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, state.Balance -= amount; storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_TotalSupply)); storage.Add(-amount); - engine.SendNotification(Hash, "Transfer", new Array { account.ToArray(), StackItem.Null, amount }); + PostTransfer(engine, account, null, amount); } [ContractMethod(0_01000000, CallFlags.AllowStates)] @@ -104,7 +104,7 @@ public virtual BigInteger BalanceOf(StoreView snapshot, UInt160 account) return storage.GetInteroperable().Balance; } - [ContractMethod(0_08000000, CallFlags.AllowModifyStates)] + [ContractMethod(0_09000000, CallFlags.AllowModifyStates)] protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); @@ -143,12 +143,28 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 state_to.Balance += amount; } } - engine.SendNotification(Hash, "Transfer", new Array { from.ToArray(), to.ToArray(), amount }); + PostTransfer(engine, from, to, amount); return true; } protected virtual void OnBalanceChanging(ApplicationEngine engine, UInt160 account, TState state, BigInteger amount) { } + + private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount) + { + // Send notification + + engine.SendNotification(Hash, "Transfer", + new Array { from?.ToArray() ?? StackItem.Null, to?.ToArray() ?? StackItem.Null, amount }); + + // Check if it's a wallet or smart contract + + if (to is null || engine.Snapshot.Contracts.TryGet(to) is null) return; + + // Call onPayment method if exists (NEP-17) + + engine.CallFromNativeContract(() => { }, to, "onPayment", from.ToArray(), amount); + } } } diff --git a/src/neo/Wallets/AssetDescriptor.cs b/src/neo/Wallets/AssetDescriptor.cs index 9f6a4d4c18..35c80ea82c 100644 --- a/src/neo/Wallets/AssetDescriptor.cs +++ b/src/neo/Wallets/AssetDescriptor.cs @@ -1,3 +1,5 @@ +using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract; using Neo.VM; using System; @@ -12,17 +14,20 @@ public class AssetDescriptor public AssetDescriptor(UInt160 asset_id) { + using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot(); + var contract = snapshot.Contracts.TryGet(asset_id); + if (contract is null) throw new ArgumentException(); + byte[] script; using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitAppCall(asset_id, "decimals"); - sb.EmitAppCall(asset_id, "name"); script = sb.ToArray(); } - using ApplicationEngine engine = ApplicationEngine.Run(script, gas: 3_000_000); + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_02000000); if (engine.State.HasFlag(VMState.FAULT)) throw new ArgumentException(); this.AssetId = asset_id; - this.AssetName = engine.ResultStack.Pop().GetString(); + this.AssetName = contract.Manifest.Name; this.Decimals = (byte)engine.ResultStack.Pop().GetInteger(); } diff --git a/tests/neo.UnitTests/Ledger/UT_ContractState.cs b/tests/neo.UnitTests/Ledger/UT_ContractState.cs index 8fe1930adf..19bcda402e 100644 --- a/tests/neo.UnitTests/Ledger/UT_ContractState.cs +++ b/tests/neo.UnitTests/Ledger/UT_ContractState.cs @@ -72,7 +72,7 @@ public void TestDeserialize() public void TestGetSize() { ISerializable newContract = contract; - newContract.Size.Should().Be(218); + newContract.Size.Should().Be(240); } [TestMethod] diff --git a/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 44c621870f..c4b15df53d 100644 --- a/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -11,7 +11,7 @@ public class UT_ContractManifest [TestMethod] public void ParseFromJson_Default() { - var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}"; + var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -22,7 +22,7 @@ public void ParseFromJson_Default() [TestMethod] public void ParseFromJson_Permissions() { - var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safemethods"":[],""extra"":null}"; + var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safemethods"":[],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -41,7 +41,7 @@ public void ParseFromJson_Permissions() [TestMethod] public void ParseFromJson_SafeMethods() { - var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[""balanceOf""],""extra"":null}"; + var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[""balanceOf""],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -53,7 +53,7 @@ public void ParseFromJson_SafeMethods() [TestMethod] public void ParseFromJson_Trust() { - var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safemethods"":[],""extra"":null}"; + var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safemethods"":[],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -65,7 +65,7 @@ public void ParseFromJson_Trust() [TestMethod] public void ParseFromJson_Groups() { - var json = @"{""groups"":[{""pubkey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==""}],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}"; + var json = @"{""name"":""testManifest"",""groups"":[{""pubkey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==""}],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToString(), json); @@ -77,7 +77,7 @@ public void ParseFromJson_Groups() [TestMethod] public void ParseFromJson_Extra() { - var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}"; + var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(json, json); Assert.AreEqual("value", manifest.Extra["key"].AsString(), false); @@ -110,7 +110,7 @@ public void TestGetHash() public void TestGetSize() { var temp = TestUtils.CreateDefaultManifest(UInt160.Zero); - Assert.AreEqual(212, temp.Size); + Assert.AreEqual(234, temp.Size); } [TestMethod] diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep5Token.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep17Token.cs similarity index 84% rename from tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep5Token.cs rename to tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep17Token.cs index 57b2a25491..0728f5d50a 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep5Token.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep17Token.cs @@ -9,7 +9,7 @@ namespace Neo.UnitTests.SmartContract.Native.Tokens { [TestClass] - public class UT_Nep5Token : TestKit + public class UT_Nep17Token : TestKit { [TestInitialize] public void TestSetup() @@ -18,7 +18,13 @@ public void TestSetup() } protected const byte Prefix_TotalSupply = 11; - private static readonly TestNep5Token test = new TestNep5Token(); + private static readonly TestNep17Token test = new TestNep17Token(); + + [TestMethod] + public void TestName() + { + Assert.AreEqual(test.Name, test.Manifest.Name); + } [TestMethod] public void TestTotalSupply() @@ -71,11 +77,11 @@ public StorageKey CreateStorageKey(byte prefix, byte[] key = null) } } - public class TestNep5Token : Nep5Token + public class TestNep17Token : Nep17Token { public override int Id => 0x10000005; - public override string Name => "testNep5Token"; + public override string Name => "testNep17Token"; public override string Symbol => throw new NotImplementedException(); diff --git a/tests/neo.UnitTests/TestUtils.cs b/tests/neo.UnitTests/TestUtils.cs index 2a4cb10cc3..e1d78738d3 100644 --- a/tests/neo.UnitTests/TestUtils.cs +++ b/tests/neo.UnitTests/TestUtils.cs @@ -22,6 +22,7 @@ public static ContractManifest CreateDefaultManifest(UInt160 hash) { return new ContractManifest() { + Name = "testManifest", Groups = new ContractGroup[0], SupportedStandards = Array.Empty(), Abi = new ContractAbi() diff --git a/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs b/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs index 937a0d89f7..bf9fbbae25 100644 --- a/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs +++ b/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs @@ -1,6 +1,5 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Persistence; using Neo.SmartContract.Native; using System;