diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4b3679871..2d56584c7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -15,7 +15,7 @@ - + diff --git a/src/RpcClient/Models/RpcContractState.cs b/src/RpcClient/Models/RpcContractState.cs index e4e0f711d..797fa1a79 100644 --- a/src/RpcClient/Models/RpcContractState.cs +++ b/src/RpcClient/Models/RpcContractState.cs @@ -1,5 +1,7 @@ using Neo; +using Neo.IO; using Neo.IO.Json; +using Neo.Network.RPC.Models; using Neo.SmartContract; using Neo.SmartContract.Manifest; using System; @@ -22,7 +24,7 @@ public static RpcContractState FromJson(JObject json) Id = (int)json["id"].AsNumber(), UpdateCounter = (ushort)json["updatecounter"].AsNumber(), Hash = UInt160.Parse(json["hash"].AsString()), - Script = Convert.FromBase64String(json["script"].AsString()), + Nef = RpcNefFile.FromJson(json["nef"]), Manifest = ContractManifest.FromJson(json["manifest"]) } }; diff --git a/src/RpcClient/Models/RpcMethodToken.cs b/src/RpcClient/Models/RpcMethodToken.cs new file mode 100644 index 000000000..02bf31ead --- /dev/null +++ b/src/RpcClient/Models/RpcMethodToken.cs @@ -0,0 +1,25 @@ +using Neo.IO.Json; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Models +{ + class RpcMethodToken + { + public static MethodToken FromJson(JObject json) + { + return new MethodToken + { + Hash = UInt160.Parse(json["hash"].AsString()), + Method = json["method"].AsString(), + ParametersCount = (ushort)json["paramcount"].AsNumber(), + HasReturnValue = json["hasreturnvalue"].AsBoolean(), + CallFlags = (CallFlags)Enum.Parse(typeof(CallFlags), json["callflags"].AsString()) + }; + } + } +} diff --git a/src/RpcClient/Models/RpcNefFile.cs b/src/RpcClient/Models/RpcNefFile.cs new file mode 100644 index 000000000..d366d5294 --- /dev/null +++ b/src/RpcClient/Models/RpcNefFile.cs @@ -0,0 +1,25 @@ +using Neo.IO.Json; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Models +{ + class RpcNefFile + { + public static NefFile FromJson(JObject json) + { + return new NefFile + { + Compiler = json["compiler"].AsString(), + Version = json["version"].AsString(), + Tokens = ((JArray)json["tokens"]).Select(p => RpcMethodToken.FromJson(p)).ToArray(), + Script = Convert.FromBase64String(json["script"].AsString()), + CheckSum = (uint)json["checksum"].AsNumber() + }; + } + } +} diff --git a/src/RpcClient/Nep17API.cs b/src/RpcClient/Nep17API.cs index 14312e841..e3d7a8c53 100644 --- a/src/RpcClient/Nep17API.cs +++ b/src/RpcClient/Nep17API.cs @@ -76,14 +76,32 @@ public async Task TotalSupplyAsync(UInt160 scriptHash) /// public async Task GetTokenInfoAsync(UInt160 scriptHash) { + var contractState = await rpcClient.GetContractStateAsync(scriptHash.ToString()).ConfigureAwait(false); byte[] script = Concat( scriptHash.MakeScript("symbol", true), scriptHash.MakeScript("decimals", true), scriptHash.MakeScript("totalSupply", true)); - - var contractState = await rpcClient.GetContractStateAsync(scriptHash.ToString()).ConfigureAwait(false); var name = contractState.Manifest.Name; + var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); + var stack = result.Stack; + return new RpcNep17TokenInfo + { + Name = name, + Symbol = stack[0].GetString(), + Decimals = (byte)stack[1].GetInteger(), + TotalSupply = stack[2].GetInteger() + }; + } + + public async Task GetTokenInfoAsync(string contractName) + { + var contractState = await rpcClient.GetContractStateAsync(contractName).ConfigureAwait(false); + byte[] script = Concat( + contractState.Hash.MakeScript("symbol", true), + contractState.Hash.MakeScript("decimals", true), + contractState.Hash.MakeScript("totalSupply", true)); + var name = contractState.Manifest.Name; var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); var stack = result.Stack; @@ -109,7 +127,6 @@ public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair { var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash(); Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; - byte[] script = scriptHash.MakeScript("transfer", true, sender, to, amount, data); TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient, magic); @@ -136,7 +153,6 @@ public async Task CreateTransferTxAsync(UInt160 scriptHash, int m, throw new ArgumentException($"Need at least {m} KeyPairs for signing!"); var sender = Contract.CreateMultiSigContract(m, pubKeys).ScriptHash; Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; - byte[] script = scriptHash.MakeScript("transfer", true, sender, to, amount, data); TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient, magic); diff --git a/src/RpcClient/RpcClient.cs b/src/RpcClient/RpcClient.cs index ca47d82a5..a02447e4a 100644 --- a/src/RpcClient/RpcClient.cs +++ b/src/RpcClient/RpcClient.cs @@ -203,7 +203,7 @@ public static ContractState ContractStateFromJson(JObject json) Id = (int)json["id"].AsNumber(), UpdateCounter = (ushort)json["updatecounter"].AsNumber(), Hash = UInt160.Parse(json["hash"].AsString()), - Script = Convert.FromBase64String(json["script"].AsString()), + Nef = RpcNefFile.FromJson(json["nef"]), Manifest = ContractManifest.FromJson(json["manifest"]) }; } diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index 4ffadec7a..bcfe69d59 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -313,12 +313,28 @@ "result": "300000000" } }, + { + "Name": "getcommitteeasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcommittee", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + "02ced432397ddc44edba031c0bc3b933f28fdd9677792d7b20e6c036ddaaacf1e2" + ] + } + }, { "Name": "getcontractstateasync", "Request": { "jsonrpc": "2.0", "method": "getcontractstate", - "params": [ "0xa6a6c15dcdc9b997dac448b6926522d22efeedfb" ], + "params": [ "gastoken" ], "id": 1 }, "Response": { @@ -327,8 +343,15 @@ "result": { "id": -2, "updatecounter": 0, - "hash": "0xa6a6c15dcdc9b997dac448b6926522d22efeedfb", - "script": "DAhHYXNUb2tlbkEa93tn", + "hash": "0x149a7f61eb3b4763b9655836ec7e75ddafdd1717", + "nef": { + "magic": 860243278, + "compiler": "ScriptBuilder", + "version": "3.0", + "tokens": [], + "script": "DAhHYXNUb2tlbkEa93tn", + "checksum": 4072842264 + }, "manifest": { "name": "GasToken", "groups": [], @@ -349,7 +372,7 @@ "parameters": [ { "name": "account", - "type": "ByteArray" + "type": "Hash160" } ], "offset": 0, @@ -361,11 +384,132 @@ "parameters": [ { "name": "from", - "type": "ByteArray" + "type": "Hash160" }, { "name": "to", - "type": "ByteArray" + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "offset": 0, + "returntype": "String", + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "offset": 0, + "returntype": "Integer", + "safe": true + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcontractstate", + "params": [ "0x149a7f61eb3b4763b9655836ec7e75ddafdd1717" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -2, + "updatecounter": 0, + "hash": "0x149a7f61eb3b4763b9655836ec7e75ddafdd1717", + "nef": { + "magic": 860243278, + "compiler": "ScriptBuilder", + "version": "3.0", + "tokens": [], + "script": "DAhHYXNUb2tlbkEa93tn", + "checksum": 4072842264 + }, + "manifest": { + "name": "GasToken", + "groups": [], + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "totalSupply", + "parameters": [], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" }, { "name": "amount", @@ -433,7 +577,7 @@ "jsonrpc": "2.0", "id": 1, "method": "getcontractstate", - "params": [ "0x0a46e2e37c9987f570b4af253fb77e7eef0f72b6" ] + "params": [ "neotoken" ] }, "Response": { "jsonrpc": "2.0", @@ -441,8 +585,15 @@ "result": { "id": -1, "updatecounter": 0, - "hash": "0x0a46e2e37c9987f570b4af253fb77e7eef0f72b6", - "script": "DAhOZW9Ub2tlbkEa93tn", + "hash": "0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add", + "nef": { + "magic": 860243278, + "compiler": "ScriptBuilder", + "version": "3.0", + "tokens": [], + "script": "DAhOZW9Ub2tlbkEa93tn", + "checksum": 1698415525 + }, "manifest": { "name": "NeoToken", "groups": [], @@ -482,7 +633,7 @@ "parameters": [ { "name": "account", - "type": "ByteArray" + "type": "Hash160" }, { "name": "end", @@ -522,7 +673,7 @@ "parameters": [ { "name": "account", - "type": "ByteArray" + "type": "Hash160" }, { "name": "voteTo", @@ -559,7 +710,7 @@ "parameters": [ { "name": "account", - "type": "ByteArray" + "type": "Hash160" } ], "offset": 0, @@ -571,11 +722,228 @@ "parameters": [ { "name": "from", - "type": "ByteArray" + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "offset": 0, + "returntype": "String", + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "offset": 0, + "returntype": "Integer", + "safe": true + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" }, { "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "getcontractstate", + "params": [ "0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add" ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -1, + "updatecounter": 0, + "hash": "0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add", + "nef": { + "magic": 860243278, + "compiler": "ScriptBuilder", + "version": "3.0", + "tokens": [], + "script": "DAhOZW9Ub2tlbkEa93tn", + "checksum": 1698415525 + }, + "manifest": { + "name": "NeoToken", + "groups": [], + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "totalSupply", + "parameters": [], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "setGasPerBlock", + "parameters": [ + { + "name": "gasPerBlock", + "type": "Integer" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "getGasPerBlock", + "parameters": [], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "unclaimedGas", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "end", + "type": "Integer" + } + ], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "registerCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "unregisterCandidate", + "parameters": [ + { + "name": "pubkey", "type": "ByteArray" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "voteTo", + "type": "ByteArray" + } + ], + "offset": 0, + "returntype": "Boolean", + "safe": false + }, + { + "name": "getCandidates", + "parameters": [], + "offset": 0, + "returntype": "Array", + "safe": true + }, + { + "name": "getCommittee", + "parameters": [], + "offset": 0, + "returntype": "Array", + "safe": true + }, + { + "name": "getNextBlockValidators", + "parameters": [], + "offset": 0, + "returntype": "Array", + "safe": true + }, + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "offset": 0, + "returntype": "Integer", + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" }, { "name": "amount", diff --git a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs index 4d4582f11..a3d7bb63e 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs @@ -97,18 +97,33 @@ public async Task TestGetTokenInfo() rpcClientMock.Setup(p => p.RpcSendAsync("getcontractstate", It.Is(u => true))) .ReturnsAsync(test.Response.Result) .Verifiable(); - - if (test.Request.Params[0].AsString() == "0xa6a6c15dcdc9b997dac448b6926522d22efeedfb") + var gasToken = "0x149a7f61eb3b4763b9655836ec7e75ddafdd1717"; + Assert.AreEqual(NativeContract.GAS.Hash.ToString(), gasToken); + var neoToken = "0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add"; + Assert.AreEqual(NativeContract.NEO.Hash.ToString(), neoToken); + if (test.Request.Params[0].AsString() == gasToken || test.Request.Params[0].AsString().Equals(NativeContract.GAS.Name, System.StringComparison.OrdinalIgnoreCase)) { - var result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Hash); + var result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Name.ToLower()); + Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); + Assert.AreEqual(8, (int)result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("GasToken", result.Name); + + result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Hash); Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); Assert.AreEqual(8, (int)result.Decimals); Assert.AreEqual(1_00000000, (int)result.TotalSupply); Assert.AreEqual("GasToken", result.Name); } - else if (test.Request.Params[0].AsString() == "0x0a46e2e37c9987f570b4af253fb77e7eef0f72b6") + else if (test.Request.Params[0].AsString() == neoToken || test.Request.Params[0].AsString().Equals(NativeContract.NEO.Name, System.StringComparison.OrdinalIgnoreCase)) { - var result = await nep17API.GetTokenInfoAsync(NativeContract.NEO.Hash); + var result = await nep17API.GetTokenInfoAsync(NativeContract.NEO.Name.ToLower()); + Assert.AreEqual(NativeContract.NEO.Symbol, result.Symbol); + Assert.AreEqual(0, (int)result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("NeoToken", result.Name); + + result = await nep17API.GetTokenInfoAsync(NativeContract.NEO.Hash); Assert.AreEqual(NativeContract.NEO.Symbol, result.Symbol); Assert.AreEqual(0, (int)result.Decimals); Assert.AreEqual(1_00000000, (int)result.TotalSupply); diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs index f4f2f5c12..9add97bec 100644 --- a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs +++ b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs @@ -155,6 +155,17 @@ public async Task TestGetBlockHeader() } } + [TestMethod] + public async Task TestGetCommittee() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetCommitteeAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetCommitteeAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => (JObject)p).ToArray()).ToString()); + } + } + [TestMethod] public async Task TestGetContractState() { diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs index 18b55c603..bb557da6a 100644 --- a/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs +++ b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs @@ -46,6 +46,9 @@ public void TestGetContractState() JObject json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetContractStateAsync).ToLower()).Response.Result; var item = RpcContractState.FromJson(json); Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + + var nef = RpcNefFile.FromJson(json["nef"]); + Assert.AreEqual(json["nef"].ToString(), nef.ToJson().ToString()); } [TestMethod()] @@ -56,6 +59,12 @@ public void TestRpcInvokeResult() Assert.AreEqual(json.ToString(), item.ToJson().ToString()); } + [TestMethod()] + public void TestRpcMethodToken() + { + RpcMethodToken.FromJson(JObject.Parse("{\"hash\": \"0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add\", \"method\":\"test\",\"paramcount\":\"1\",\"hasreturnvalue\":\"true\",\"callflags\":\"All\"}")); + } + [TestMethod()] public void TestRpcNep17Balances() {