From 9c054321cedfaf118764886994a97dbe370665a1 Mon Sep 17 00:00:00 2001 From: Krain Chen Date: Fri, 20 Sep 2019 20:54:42 +0800 Subject: [PATCH] C# SDK Add Transaction Manager and Smart Contract APIs (#1026) * Add NEO SDK based on RPC client * add rpc interface methods for neo3 * update unit test * add unit test * Update TransactionHelper.cs Changed for neo 3.0, not final yet. * implement sdk rpc client methods * backup files * change class name * remove uncompleted modules for pull request * change json deserialize method with Neo JObject * modified JSON implementation, added FromJson() * more RPC change * PR correction * RPC module fix, remove newton.json * fix * fix getblock issue * PR correction * PR Correction * PR Correction: rename RPC models * PR Correction * resolve conflicts * Clean code * Clean code * Clean code * Clean code * Update RpcValidateAddressResult.cs * Clean code * PR correction * Move test file to the right place * Added SDK Transaction module. * Add SDK SmartContract module * height = count - 1 * Add sign function for TxManager * Add Deploy Contract * Add unit tests * Add Network Fee calculate for TxManager, add unit tests * adjust cosigners change * PR Correction * Remove empty line * Rename TxManager to TransactionManager * PR correction * PR correction * change namespace * Reorder methods * Remove TransactionContext * fix unit test * Remove `virtual` * Remove virtuals * Remove ScriptHash from KeyPair * Add comments * Add comments * Adjust to Neo_Contract_Create parameter * use default mainfest * fix unit test * Fix typo * use manifest as parameter * add cosigner for nep5 transfer * code clean * Update neo.UnitTests/Network/RPC/UT_RpcClient.cs Co-Authored-By: Shargon * move MakeScript to VM.Helper * Add unit test for InteropInterface * PR Correction * Add unit test --- .../Network/RPC/UT_ContractClient.cs | 68 ++++++ neo.UnitTests/Network/RPC/UT_Nep5API.cs | 89 +++++++ neo.UnitTests/Network/RPC/UT_PolicyAPI.cs | 69 ++++++ neo.UnitTests/Network/RPC/UT_RpcClient.cs | 24 +- .../Network/RPC/UT_TransactionManager.cs | 194 +++++++++++++++ neo.UnitTests/VM/UT_Helper.cs | 60 +++++ neo/Network/RPC/ContractClient.cs | 65 ++++++ neo/Network/RPC/Models/RpcInvokeResult.cs | 11 +- neo/Network/RPC/Nep5API.cs | 97 ++++++++ neo/Network/RPC/PolicyAPI.cs | 57 +++++ neo/Network/RPC/RpcClient.cs | 43 ++-- neo/Network/RPC/TransactionManager.cs | 220 ++++++++++++++++++ neo/SmartContract/Contract.cs | 14 ++ .../ContractParametersContext.cs | 27 +++ neo/VM/Helper.cs | 81 +++++++ neo/Wallets/Wallet.cs | 51 ++-- 16 files changed, 1112 insertions(+), 58 deletions(-) create mode 100644 neo.UnitTests/Network/RPC/UT_ContractClient.cs create mode 100644 neo.UnitTests/Network/RPC/UT_Nep5API.cs create mode 100644 neo.UnitTests/Network/RPC/UT_PolicyAPI.cs create mode 100644 neo.UnitTests/Network/RPC/UT_TransactionManager.cs create mode 100644 neo/Network/RPC/ContractClient.cs create mode 100644 neo/Network/RPC/Nep5API.cs create mode 100644 neo/Network/RPC/PolicyAPI.cs create mode 100644 neo/Network/RPC/TransactionManager.cs diff --git a/neo.UnitTests/Network/RPC/UT_ContractClient.cs b/neo.UnitTests/Network/RPC/UT_ContractClient.cs new file mode 100644 index 0000000000..fde8a7747d --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_ContractClient.cs @@ -0,0 +1,68 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_ContractClient + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + } + + [TestMethod] + public void TestMakeScript() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + + Assert.AreEqual("14000000000000000000000000000000000000000051c10962616c616e63654f66142582d1b275e86c8f0e93a9b2facd5fdb760976a168627d5b52", + testScript.ToHexString()); + } + + [TestMethod] + public void TestInvoke() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.ByteArray, Value = "00e057eb481b".HexToBytes() }); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = contractClient.TestInvoke(NativeContract.GAS.Hash, "balanceOf", UInt160.Zero); + + Assert.AreEqual(30000000000000L, (long)result.Stack[0].ToStackItem().GetBigInteger()); + } + + [TestMethod] + public void TestDeployContract() + { + byte[] script; + var manifest = ContractManifest.CreateDefault(new byte[1].ToScriptHash()); + manifest.Features = ContractFeatures.HasStorage | ContractFeatures.Payable; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.Neo_Contract_Create, new byte[1], manifest.ToString()); + script = sb.ToArray(); + } + + UT_TransactionManager.MockInvokeScript(rpcClientMock, script, new ContractParameter()); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = contractClient.DeployContract(new byte[1], manifest, keyPair1); + + Assert.IsNotNull(result); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_Nep5API.cs b/neo.UnitTests/Network/RPC/UT_Nep5API.cs new file mode 100644 index 0000000000..72e0c29487 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_Nep5API.cs @@ -0,0 +1,89 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_Nep5API + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + Nep5API nep5API; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + nep5API = new Nep5API(rpcClientMock.Object); + } + + [TestMethod] + public void TestBalanceOf() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(10000) }); + + var balance = nep5API.BalanceOf(NativeContract.GAS.Hash, UInt160.Zero); + Assert.AreEqual(10000, (int)balance); + } + + [TestMethod] + public void TestGetName() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("name"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Name }); + + var result = nep5API.Name(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Name, result); + } + + [TestMethod] + public void TestGetSymbol() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("symbol"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }); + + var result = nep5API.Symbol(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Symbol, result); + } + + [TestMethod] + public void TestGetDecimals() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }); + + var result = nep5API.Decimals(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Decimals, (byte)result); + } + + [TestMethod] + public void TestGetTotalSupply() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("totalSupply"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var result = nep5API.TotalSupply(NativeContract.GAS.Hash); + Assert.AreEqual(1_00000000, (int)result); + } + + [TestMethod] + public void TestTransfer() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000)); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); + + var result = nep5API.Transfer(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000)); + Assert.IsNotNull(result); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs b/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs new file mode 100644 index 0000000000..6b6c449111 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs @@ -0,0 +1,69 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_PolicyAPI + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + PolicyAPI policyAPI; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + policyAPI = new PolicyAPI(rpcClientMock.Object); + } + + [TestMethod] + public void TestGetMaxTransactionsPerBlock() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getMaxTransactionsPerBlock"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(512) }); + + var result = policyAPI.GetMaxTransactionsPerBlock(); + Assert.AreEqual(512u, result); + } + + [TestMethod] + public void TestGetMaxBlockSize() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getMaxBlockSize"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1024u * 256u) }); + + var result = policyAPI.GetMaxBlockSize(); + Assert.AreEqual(1024u * 256u, result); + } + + [TestMethod] + public void TestGetFeePerByte() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1000) }); + + var result = policyAPI.GetFeePerByte(); + Assert.AreEqual(1000L, result); + } + + [TestMethod] + public void TestGetBlockedAccounts() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getBlockedAccounts"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Array, Value = new[] { new ContractParameter { Type = ContractParameterType.Hash160, Value = UInt160.Zero } } }); + + var result = policyAPI.GetBlockedAccounts(); + Assert.AreEqual(UInt160.Zero, result[0]); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_RpcClient.cs b/neo.UnitTests/Network/RPC/UT_RpcClient.cs index 76cf7556bf..0875a5a793 100644 --- a/neo.UnitTests/Network/RPC/UT_RpcClient.cs +++ b/neo.UnitTests/Network/RPC/UT_RpcClient.cs @@ -119,7 +119,7 @@ public void TestGetBlock() { // create block var block = new Block(); - TestUtils.SetupBlockWithValues(block, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, 0); + TestUtils.SetupBlockWithValues(block, UInt256.Zero, out _, out UInt160 _, out ulong _, out uint _, out Witness _, out Transaction[] _, 0); block.Transactions = new[] { @@ -158,7 +158,7 @@ public void TestGetBlockCount() MockResponse(response.ToString()); var result = rpc.GetBlockCount(); - Assert.AreEqual(100, result); + Assert.AreEqual(100u, result); } [TestMethod] @@ -187,7 +187,7 @@ public void TestGetBlockHeaderHex() public void TestGetBlockHeader() { Header header = new Header(); - TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); + TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 _, out UInt160 _, out ulong _, out uint _, out Witness _); JObject json = header.ToJson(); JObject response = CreateResponse(1); @@ -231,12 +231,16 @@ public void TestGetConnectionCount() [TestMethod] public void TestGetContractState() { - var sb = new ScriptBuilder(); - sb.EmitSysCall(InteropService.System_Runtime_GetInvocationCounter); + byte[] script; + using (var sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.System_Runtime_GetInvocationCounter); + script = sb.ToArray(); + } ContractState state = new ContractState { - Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP }.Concat(sb.ToArray()).ToArray(), + Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP }.Concat(script).ToArray(), Manifest = ContractManifest.CreateDefault(UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")) }; @@ -316,7 +320,7 @@ public void TestGetRawMempoolBoth() MockResponse(response.ToString()); var result = rpc.GetRawMempoolBoth(); - Assert.AreEqual((uint)65535, result.Height); + Assert.AreEqual(65535u, result.Height); Assert.AreEqual("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e", result.Verified[0]); Assert.AreEqual("0xf86f6f2c08fbf766ebe59dc84bc3b8829f1053f0a01deb26bf7960d99fa86cd6", result.UnVerified[1]); } @@ -476,7 +480,7 @@ public void TestInvokeScript() response["result"] = json; MockResponse(response.ToString()); - var result = rpc.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191"); + var result = rpc.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191".HexToBytes()); Assert.AreEqual(json.ToString(), result.ToJson().ToString()); } @@ -507,7 +511,7 @@ public void TestSendRawTransaction() response["result"] = json; MockResponse(response.ToString()); - var result = rpc.SendRawTransaction("80000001195876cb34364dc38b730077156c6bc3a7fc570044a66fbfeeea56f71327e8ab0000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c65eaf440000000f9a23e06f74cf86b8827a9108ec2e0f89ad956c9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50092e14b5e00000030aab52ad93f6ce17ca07fa88fc191828c58cb71014140915467ecd359684b2dc358024ca750609591aa731a0b309c7fb3cab5cd0836ad3992aa0a24da431f43b68883ea5651d548feb6bd3c8e16376e6e426f91f84c58232103322f35c7819267e721335948d385fae5be66e7ba8c748ac15467dcca0693692dac"); + var result = rpc.SendRawTransaction("80000001195876cb34364dc38b730077156c6bc3a7fc570044a66fbfeeea56f71327e8ab0000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c65eaf440000000f9a23e06f74cf86b8827a9108ec2e0f89ad956c9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50092e14b5e00000030aab52ad93f6ce17ca07fa88fc191828c58cb71014140915467ecd359684b2dc358024ca750609591aa731a0b309c7fb3cab5cd0836ad3992aa0a24da431f43b68883ea5651d548feb6bd3c8e16376e6e426f91f84c58232103322f35c7819267e721335948d385fae5be66e7ba8c748ac15467dcca0693692dac".HexToBytes()); Assert.AreEqual(json.ToString(), ((JObject)result).ToString()); } @@ -519,7 +523,7 @@ public void TestSubmitBlock() response["result"] = json; MockResponse(response.ToString()); - var result = rpc.SubmitBlock("03febccf81ac85e3d795bc5cbd4e84e907812aa3"); + var result = rpc.SubmitBlock("03febccf81ac85e3d795bc5cbd4e84e907812aa3".HexToBytes()); Assert.AreEqual(json.ToString(), ((JObject)result).ToString()); } diff --git a/neo.UnitTests/Network/RPC/UT_TransactionManager.cs b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs new file mode 100644 index 0000000000..ee55f81bb9 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs @@ -0,0 +1,194 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_TransactionManager + { + TransactionManager txManager; + Mock rpcClientMock; + KeyPair keyPair1; + KeyPair keyPair2; + UInt160 sender; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + keyPair2 = new KeyPair(Wallet.GetPrivateKeyFromWIF("L2LGkrwiNmUAnWYb1XGd5mv7v2eDf6P4F3gHyXSrNJJR4ArmBp7Q")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = MockRpcClient(sender, new byte[1]); + } + + public static Mock MockRpcClient(UInt160 sender, byte[] script) + { + var mockRpc = new Mock(MockBehavior.Strict, "http://seed1.neo.org:10331"); + + // MockHeight + mockRpc.Setup(p => p.RpcSend("getblockcount")).Returns(100).Verifiable(); + + // MockGasBalance + byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", sender); + var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") }; + + MockInvokeScript(mockRpc, balanceScript, balanceResult); + + // MockFeePerByte + byte[] policyScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + var policyResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("1000") }; + + MockInvokeScript(mockRpc, policyScript, policyResult); + + // MockGasConsumed + var result = new ContractParameter(); + MockInvokeScript(mockRpc, script, result); + + return mockRpc; + } + + public static void MockInvokeScript(Mock mockClient, byte[] script, params ContractParameter[] parameters) + { + var result = new RpcInvokeResult() + { + Stack = parameters, + GasConsumed = "100", + Script = script.ToHexString(), + State = "", + Tx = "" + }; + + mockClient.Setup(p => p.RpcSend("invokescript", It.Is(j => j.AsString() == script.ToHexString()))) + .Returns(result.ToJson()) + .Verifiable(); + } + + [TestMethod] + public void TestMakeTransaction() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + TransactionAttribute[] attributes = new TransactionAttribute[1] + { + new TransactionAttribute + { + Usage = TransactionAttributeUsage.Url, + Data = "53616d706c6555726c".HexToBytes() // "SampleUrl" + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, attributes, null, 60000); + + var tx = txManager.Tx; + Assert.AreEqual("53616d706c6555726c", tx.Attributes[0].Data.ToHexString()); + Assert.AreEqual(0, tx.SystemFee % (long)NativeContract.GAS.Factor); + Assert.AreEqual(60000, tx.NetworkFee); + } + + [TestMethod] + public void TestSign() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + TransactionAttribute[] attributes = new TransactionAttribute[1] + { + new TransactionAttribute + { + Usage = TransactionAttributeUsage.Url, + Data = "53616d706c6555726c".HexToBytes() // "SampleUrl" + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, attributes) + .AddSignature(keyPair1) + .Sign(); + + // get signature from Witnesses + var tx = txManager.Tx; + byte[] signature = tx.Witnesses[0].InvocationScript.Skip(1).ToArray(); + + Assert.IsTrue(Crypto.Default.VerifySignature(tx.GetHashData(), signature, keyPair1.PublicKey.EncodePoint(false).Skip(1).ToArray())); + + // duplicate sign should not add new witness + txManager.AddSignature(keyPair1).Sign(); + Assert.AreEqual(1, txManager.Tx.Witnesses.Length); + + // throw exception when the KeyPair is wrong + Assert.ThrowsException(() => txManager.AddSignature(keyPair2)); + } + + [TestMethod] + public void TestSignMulti() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + var multiContract = Contract.CreateMultiSigContract(2, keyPair1.PublicKey, keyPair2.PublicKey); + + // Cosigner needs multi signature + Cosigner[] cosigners = new Cosigner[1] + { + new Cosigner + { + Account = multiContract.ScriptHash, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, null, cosigners, 0_10000000) + .AddMultiSig(keyPair1, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .AddMultiSig(keyPair2, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .AddSignature(keyPair1) + .Sign(); + + var store = TestBlockchain.GetStore(); + var snapshot = store.GetSnapshot(); + + var tx = txManager.Tx; + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + } + + [TestMethod] + public void TestAddWitness() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + // Cosigner as contract scripthash + Cosigner[] cosigners = new Cosigner[1] + { + new Cosigner + { + Account = UInt160.Zero, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, null, cosigners, 0_10000000); + txManager.AddWitness(UInt160.Zero); + txManager.AddSignature(keyPair1); + txManager.Sign(); + + var tx = txManager.Tx; + Assert.AreEqual(2, tx.Witnesses.Length); + Assert.AreEqual(0, tx.Witnesses[0].VerificationScript.Length); + Assert.AreEqual(0, tx.Witnesses[0].InvocationScript.Length); + } + } +} diff --git a/neo.UnitTests/VM/UT_Helper.cs b/neo.UnitTests/VM/UT_Helper.cs index e9ccc539bb..cf5ad0bff5 100644 --- a/neo.UnitTests/VM/UT_Helper.cs +++ b/neo.UnitTests/VM/UT_Helper.cs @@ -1,7 +1,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; using Neo.SmartContract; using Neo.VM; using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using System.Text; namespace Neo.UnitTests.IO @@ -78,5 +82,61 @@ public void TestEmitAppCall3() byte[] resultArray = sb.ToArray(); Assert.AreEqual(Encoding.Default.GetString(tempArray), Encoding.Default.GetString(resultArray)); } + + [TestMethod] + public void TestToParameter() + { + StackItem byteItem = "00e057eb481b".HexToBytes(); + Assert.AreEqual(30000000000000L, (long)new BigInteger(byteItem.ToParameter().Value as byte[])); + + StackItem boolItem = false; + Assert.AreEqual(false, (bool)boolItem.ToParameter().Value); + + StackItem intItem = new BigInteger(1000); + Assert.AreEqual(1000, (BigInteger)intItem.ToParameter().Value); + + StackItem interopItem = new VM.Types.InteropInterface("test"); + Assert.AreEqual(null, interopItem.ToParameter().Value); + + StackItem arrayItem = new VM.Types.Array(new[] { byteItem, boolItem, intItem, interopItem }); + Assert.AreEqual(1000, (BigInteger)(arrayItem.ToParameter().Value as List)[2].Value); + + StackItem mapItem = new VM.Types.Map(new Dictionary(new[] { new KeyValuePair(byteItem, intItem) })); + Assert.AreEqual(1000, (BigInteger)(mapItem.ToParameter().Value as List>)[0].Value.Value); + } + + [TestMethod] + public void TestToStackItem() + { + ContractParameter byteParameter = new ContractParameter { Type = ContractParameterType.ByteArray, Value = "00e057eb481b".HexToBytes() }; + Assert.AreEqual(30000000000000L, (long)byteParameter.ToStackItem().GetBigInteger()); + + ContractParameter boolParameter = new ContractParameter { Type = ContractParameterType.Boolean, Value = false }; + Assert.AreEqual(false, boolParameter.ToStackItem().GetBoolean()); + + ContractParameter intParameter = new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1000) }; + Assert.AreEqual(1000, intParameter.ToStackItem().GetBigInteger()); + + ContractParameter h160Parameter = new ContractParameter { Type = ContractParameterType.Hash160, Value = UInt160.Zero }; + Assert.AreEqual(0, h160Parameter.ToStackItem().GetBigInteger()); + + ContractParameter h256Parameter = new ContractParameter { Type = ContractParameterType.Hash256, Value = UInt256.Zero }; + Assert.AreEqual(0, h256Parameter.ToStackItem().GetBigInteger()); + + ContractParameter pkParameter = new ContractParameter { Type = ContractParameterType.PublicKey, Value = ECPoint.Parse("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575", ECCurve.Secp256r1) }; + Assert.AreEqual("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575", pkParameter.ToStackItem().GetByteArray().ToHexString()); + + ContractParameter strParameter = new ContractParameter { Type = ContractParameterType.String, Value = "test😂👍" }; + Assert.AreEqual("test😂👍", Encoding.UTF8.GetString(strParameter.ToStackItem().GetByteArray())); + + ContractParameter interopParameter = new ContractParameter { Type = ContractParameterType.InteropInterface }; + Assert.AreEqual(null, interopParameter.ToStackItem()); + + ContractParameter arrayParameter = new ContractParameter { Type = ContractParameterType.Array, Value = new[] { byteParameter, boolParameter, intParameter, h160Parameter, h256Parameter, pkParameter, strParameter, interopParameter }.ToList() }; + Assert.AreEqual(1000, ((VM.Types.Array)arrayParameter.ToStackItem())[2].GetBigInteger()); + + ContractParameter mapParameter = new ContractParameter { Type = ContractParameterType.Map, Value = new[] { new KeyValuePair(byteParameter, pkParameter) } }; + Assert.AreEqual(30000000000000L, (long)((VM.Types.Map)mapParameter.ToStackItem()).Keys.First().GetBigInteger()); + } } } diff --git a/neo/Network/RPC/ContractClient.cs b/neo/Network/RPC/ContractClient.cs new file mode 100644 index 0000000000..a329e07674 --- /dev/null +++ b/neo/Network/RPC/ContractClient.cs @@ -0,0 +1,65 @@ +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.VM; +using Neo.Wallets; + +namespace Neo.Network.RPC +{ + /// + /// Contract related operations through RPC API + /// + public class ContractClient + { + protected readonly RpcClient rpcClient; + + /// + /// ContractClient Constructor + /// + /// the RPC client to call NEO RPC methods + public ContractClient(RpcClient rpc) + { + rpcClient = rpc; + } + + /// + /// Use RPC method to test invoke operation. + /// + /// contract script hash + /// contract operation + /// operation arguments + /// + public RpcInvokeResult TestInvoke(UInt160 scriptHash, string operation, params object[] args) + { + byte[] script = scriptHash.MakeScript(operation, args); + return rpcClient.InvokeScript(script); + } + + /// + /// Deploy Contract, return signed transaction + /// + /// contract script + /// contract manifest + /// sender KeyPair + /// transaction NetworkFee, set to be 0 if you don't need higher priority + /// + public Transaction DeployContract(byte[] contractScript, ContractManifest manifest, KeyPair key, long networkFee = 0) + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.Neo_Contract_Create, contractScript, manifest.ToString()); + script = sb.ToArray(); + } + + Transaction tx = new TransactionManager(rpcClient, Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash()) + .MakeTransaction(script, null, null, networkFee) + .AddSignature(key) + .Sign() + .Tx; + + return tx; + } + } +} diff --git a/neo/Network/RPC/Models/RpcInvokeResult.cs b/neo/Network/RPC/Models/RpcInvokeResult.cs index 73f2c6a2af..c56307950a 100644 --- a/neo/Network/RPC/Models/RpcInvokeResult.cs +++ b/neo/Network/RPC/Models/RpcInvokeResult.cs @@ -1,24 +1,19 @@ using Neo.IO.Json; -using Newtonsoft.Json; +using Neo.SmartContract; using System.Linq; namespace Neo.Network.RPC.Models { public class RpcInvokeResult { - [JsonProperty(PropertyName = "script")] public string Script { get; set; } - [JsonProperty(PropertyName = "state")] public string State { get; set; } - [JsonProperty(PropertyName = "gas_consumed")] public string GasConsumed { get; set; } - [JsonProperty(PropertyName = "stack")] - public RpcStack[] Stack { get; set; } + public ContractParameter[] Stack { get; set; } - [JsonProperty(PropertyName = "tx")] public string Tx { get; set; } public JObject ToJson() @@ -39,7 +34,7 @@ public static RpcInvokeResult FromJson(JObject json) invokeScriptResult.State = json["state"].AsString(); invokeScriptResult.GasConsumed = json["gas_consumed"].AsString(); invokeScriptResult.Tx = json["tx"].AsString(); - invokeScriptResult.Stack = ((JArray)json["stack"]).Select(p => RpcStack.FromJson(p)).ToArray(); + invokeScriptResult.Stack = ((JArray)json["stack"]).Select(p => ContractParameter.FromJson(p)).ToArray(); return invokeScriptResult; } } diff --git a/neo/Network/RPC/Nep5API.cs b/neo/Network/RPC/Nep5API.cs new file mode 100644 index 0000000000..abfad6c9ed --- /dev/null +++ b/neo/Network/RPC/Nep5API.cs @@ -0,0 +1,97 @@ +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.VM; +using Neo.Wallets; +using System.Linq; +using System.Numerics; + +namespace Neo.Network.RPC +{ + /// + /// Call NEP5 methods with RPC API + /// + public class Nep5API : ContractClient + { + /// + /// Nep5API Constructor + /// + /// the RPC client to call NEO RPC methods + public Nep5API(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get balance of NEP5 token + /// + /// contract script hash + /// account script hash + /// + public BigInteger BalanceOf(UInt160 scriptHash, UInt160 account) + { + BigInteger balance = TestInvoke(scriptHash, "balanceOf", account).Stack.Single().ToStackItem().GetBigInteger(); + return balance; + } + + /// + /// Get name of NEP5 token + /// + /// contract script hash + /// + public string Name(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "name").Stack.Single().ToStackItem().GetString(); + } + + /// + /// Get symbol of NEP5 token + /// + /// contract script hash + /// + public string Symbol(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "symbol").Stack.Single().ToStackItem().GetString(); + } + + /// + /// Get decimals of NEP5 token + /// + /// contract script hash + /// + public uint Decimals(UInt160 scriptHash) + { + return (uint)TestInvoke(scriptHash, "decimals").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get total supply of NEP5 token + /// + /// contract script hash + /// + public BigInteger TotalSupply(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "totalSupply").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get name of NEP5 token + /// + /// contract script hash + /// from KeyPair + /// to account script hash + /// transfer amount + /// netwotk fee, set to be 0 if you don't need higher priority + /// + public Transaction Transfer(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, long networkFee = 0) + { + var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash(); + Cosigner[] cosigners = new[] { new Cosigner { Scopes = WitnessScope.CalledByEntry, Account = sender } }; + + byte[] script = scriptHash.MakeScript("transfer", sender, to, amount); + Transaction tx = new TransactionManager(rpcClient, sender) + .MakeTransaction(script, null, cosigners, networkFee) + .AddSignature(fromKey) + .Sign() + .Tx; + + return tx; + } + } +} diff --git a/neo/Network/RPC/PolicyAPI.cs b/neo/Network/RPC/PolicyAPI.cs new file mode 100644 index 0000000000..969c1c22f1 --- /dev/null +++ b/neo/Network/RPC/PolicyAPI.cs @@ -0,0 +1,57 @@ +using Neo.SmartContract.Native; +using Neo.VM; +using System.Linq; + +namespace Neo.Network.RPC +{ + /// + /// Get Policy info by RPC API + /// + public class PolicyAPI : ContractClient + { + readonly UInt160 scriptHash = NativeContract.Policy.Hash; + + /// + /// PolicyAPI Constructor + /// + /// the RPC client to call NEO RPC methods + public PolicyAPI(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get Max Transactions Count Per Block + /// + /// + public uint GetMaxTransactionsPerBlock() + { + return (uint)TestInvoke(scriptHash, "getMaxTransactionsPerBlock").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Max Block Size + /// + /// + public uint GetMaxBlockSize() + { + return (uint)TestInvoke(scriptHash, "getMaxBlockSize").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Network Fee Per Byte + /// + /// + public long GetFeePerByte() + { + return (long)TestInvoke(scriptHash, "getFeePerByte").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Ploicy Blocked Accounts + /// + /// + public UInt160[] GetBlockedAccounts() + { + var result = (VM.Types.Array)TestInvoke(scriptHash, "getBlockedAccounts").Stack.Single().ToStackItem(); + return result.Select(p => new UInt160(p.GetByteArray())).ToArray(); + } + } +} diff --git a/neo/Network/RPC/RpcClient.cs b/neo/Network/RPC/RpcClient.cs index 69352b8cfd..cd0578953e 100644 --- a/neo/Network/RPC/RpcClient.cs +++ b/neo/Network/RPC/RpcClient.cs @@ -9,6 +9,9 @@ namespace Neo.Network.RPC { + /// + /// The RPC client to call NEO RPC methods + /// public class RpcClient : IDisposable { private readonly HttpClient httpClient; @@ -31,17 +34,19 @@ public void Dispose() public async Task SendAsync(RpcRequest request) { var requestJson = request.ToJson().ToString(); - var result = await httpClient.PostAsync(httpClient.BaseAddress, new StringContent(requestJson, Encoding.UTF8)); - var content = await result.Content.ReadAsStringAsync(); - var response = RpcResponse.FromJson(JObject.Parse(content)); - response.RawResponse = content; - - if (response.Error != null) + using (var result = await httpClient.PostAsync(httpClient.BaseAddress, new StringContent(requestJson, Encoding.UTF8))) { - throw new RpcException(response.Error.Code, response.Error.Message); - } + var content = await result.Content.ReadAsStringAsync(); + var response = RpcResponse.FromJson(JObject.Parse(content)); + response.RawResponse = content; - return response; + if (response.Error != null) + { + throw new RpcException(response.Error.Code, response.Error.Message); + } + + return response; + } } public RpcResponse Send(RpcRequest request) @@ -56,7 +61,7 @@ public RpcResponse Send(RpcRequest request) } } - private JObject RpcSend(string method, params JObject[] paraArgs) + public virtual JObject RpcSend(string method, params JObject[] paraArgs) { var request = new RpcRequest { @@ -104,9 +109,9 @@ public RpcBlock GetBlock(string hashOrIndex) /// /// Gets the number of blocks in the main chain. /// - public int GetBlockCount() + public uint GetBlockCount() { - return (int)RpcSend("getblockcount").AsNumber(); + return (uint)RpcSend("getblockcount").AsNumber(); } /// @@ -187,7 +192,7 @@ public string[] GetRawMempool() /// public RpcRawMemPool GetRawMempoolBoth() { - return RpcRawMemPool.FromJson(RpcSend("getrawmempool")); + return RpcRawMemPool.FromJson(RpcSend("getrawmempool", true)); } /// @@ -252,9 +257,9 @@ public RpcInvokeResult InvokeFunction(string address, string function, RpcStack[ /// Returns the result after passing a script through the VM. /// This RPC call does not affect the blockchain in any way. /// - public RpcInvokeResult InvokeScript(string script) + public RpcInvokeResult InvokeScript(byte[] script) { - return RpcInvokeResult.FromJson(RpcSend("invokescript", script)); + return RpcInvokeResult.FromJson(RpcSend("invokescript", script.ToHexString())); } /// @@ -268,17 +273,17 @@ public RpcPlugin[] ListPlugins() /// /// Broadcasts a transaction over the NEO network. /// - public bool SendRawTransaction(string rawTransaction) + public bool SendRawTransaction(byte[] rawTransaction) { - return RpcSend("sendrawtransaction", rawTransaction).AsBoolean(); + return RpcSend("sendrawtransaction", rawTransaction.ToHexString()).AsBoolean(); } /// /// Broadcasts a raw block over the NEO network. /// - public bool SubmitBlock(string block) + public bool SubmitBlock(byte[] block) { - return RpcSend("submitblock", block).AsBoolean(); + return RpcSend("submitblock", block.ToHexString()).AsBoolean(); } /// diff --git a/neo/Network/RPC/TransactionManager.cs b/neo/Network/RPC/TransactionManager.cs new file mode 100644 index 0000000000..7b4db697ab --- /dev/null +++ b/neo/Network/RPC/TransactionManager.cs @@ -0,0 +1,220 @@ +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; + +namespace Neo.Network.RPC +{ + /// + /// This class helps to create transaction with RPC API. + /// + public class TransactionManager + { + private static readonly Random rand = new Random(); + private readonly RpcClient rpcClient; + private readonly UInt160 sender; + + /// + /// The Transaction context to manage the witnesses + /// + private ContractParametersContext context; + + /// + /// The Transaction managed by this class + /// + public Transaction Tx { get; private set; } + + /// + /// TransactionManager Constructor + /// + /// the RPC client to call NEO RPC API + /// the account script hash of sender + public TransactionManager(RpcClient rpc, UInt160 sender) + { + rpcClient = rpc; + this.sender = sender; + } + + /// + /// Create an unsigned Transaction object with given parameters. + /// + /// Transaction Script + /// Transaction Attributes + /// Transaction Cosigners + /// Transaction NetworkFee, will set to estimate value(with only basic signatures) when networkFee is 0 + /// + public TransactionManager MakeTransaction(byte[] script, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null, long networkFee = 0) + { + uint height = rpcClient.GetBlockCount() - 1; + Tx = new Transaction + { + Version = 0, + Nonce = (uint)rand.Next(), + Script = script, + Sender = sender, + ValidUntilBlock = height + Transaction.MaxValidUntilBlockIncrement, + Attributes = attributes ?? new TransactionAttribute[0], + Cosigners = cosigners ?? new Cosigner[0], + Witnesses = new Witness[0] + }; + + RpcInvokeResult result = rpcClient.InvokeScript(script); + Tx.SystemFee = Math.Max(long.Parse(result.GasConsumed) - ApplicationEngine.GasFree, 0); + if (Tx.SystemFee > 0) + { + long d = (long)NativeContract.GAS.Factor; + long remainder = Tx.SystemFee % d; + if (remainder > 0) + Tx.SystemFee += d - remainder; + else if (remainder < 0) + Tx.SystemFee -= remainder; + } + + context = new ContractParametersContext(Tx); + + // set networkfee to estimate value when networkFee is 0 + Tx.NetworkFee = networkFee == 0 ? EstimateNetworkFee() : networkFee; + + var gasBalance = new Nep5API(rpcClient).BalanceOf(NativeContract.GAS.Hash, sender); + if (gasBalance >= Tx.SystemFee + Tx.NetworkFee) return this; + throw new InvalidOperationException($"Insufficient GAS in address: {sender.ToAddress()}"); + } + + /// + /// Estimate NetworkFee, assuming the witnesses are basic Signature Contract + /// + private long EstimateNetworkFee() + { + long networkFee = 0; + UInt160[] hashes = Tx.GetScriptHashesForVerifying(null); + int size = Transaction.HeaderSize + Tx.Attributes.GetVarSize() + Tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + + // assume the hashes are single Signature + foreach (var hash in hashes) + { + size += 166; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); + } + + networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + return networkFee; + } + + /// + /// Calculate NetworkFee with context items + /// + private long CalculateNetworkFee() + { + long networkFee = 0; + UInt160[] hashes = Tx.GetScriptHashesForVerifying(null); + int size = Transaction.HeaderSize + Tx.Attributes.GetVarSize() + Tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + foreach (UInt160 hash in hashes) + { + byte[] witness_script = context.GetScript(hash); + if (witness_script is null || witness_script.Length == 0) + { + try + { + witness_script = rpcClient.GetContractState(hash.ToString())?.Script; + } + catch { } + } + + if (witness_script is null) continue; + + networkFee += Wallet.CalculateNetWorkFee(witness_script, ref size); + } + networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + return networkFee; + } + + /// + /// Add Signature + /// + /// The KeyPair to sign transction + /// + public TransactionManager AddSignature(KeyPair key) + { + var contract = Contract.CreateSignatureContract(key.PublicKey); + + byte[] signature = Tx.Sign(key); + if (!context.AddSignature(contract, key.PublicKey, signature)) + { + throw new Exception("AddSignature failed!"); + } + + return this; + } + + /// + /// Add Multi-Signature + /// + /// The KeyPair to sign transction + /// The least count of signatures needed for multiple signature contract + /// The Public Keys construct the multiple signature contract + public TransactionManager AddMultiSig(KeyPair key, int m, params ECPoint[] publicKeys) + { + Contract contract = Contract.CreateMultiSigContract(m, publicKeys); + + byte[] signature = Tx.Sign(key); + if (!context.AddSignature(contract, key.PublicKey, signature)) + { + throw new Exception("AddMultiSig failed!"); + } + + return this; + } + + /// + /// Add Witness with contract + /// + /// The witness verification contract + /// The witness invocation parameters + public TransactionManager AddWitness(Contract contract, params object[] parameters) + { + if (!context.Add(contract, parameters)) + { + throw new Exception("AddWitness failed!"); + }; + return this; + } + + /// + /// Add Witness with scriptHash + /// + /// The witness verification contract hash + /// The witness invocation parameters + public TransactionManager AddWitness(UInt160 scriptHash, params object[] parameters) + { + var contract = Contract.Create(scriptHash); + return AddWitness(contract, parameters); + } + + /// + /// Verify Witness count and add witnesses + /// + public TransactionManager Sign() + { + // Verify witness count + if (!context.Completed) + { + throw new Exception($"Please add signature or witness first!"); + } + + // Calculate NetworkFee + long leastNetworkFee = CalculateNetworkFee(); + if (Tx.NetworkFee < leastNetworkFee) + { + throw new InvalidOperationException("Insufficient NetworkFee"); + } + + Tx.Witnesses = context.GetWitnesses(); + return this; + } + } +} diff --git a/neo/SmartContract/Contract.cs b/neo/SmartContract/Contract.cs index 19fcceb7ca..58e32768d7 100644 --- a/neo/SmartContract/Contract.cs +++ b/neo/SmartContract/Contract.cs @@ -46,6 +46,20 @@ public static Contract Create(ContractParameterType[] parameterList, byte[] rede }; } + /// + /// Construct special Contract with empty Script, will get the Script with scriptHash from blockchain when doing the Verify + /// verification = snapshot.Contracts.TryGet(hashes[i])?.Script; + /// + public static Contract Create(UInt160 scriptHash, params ContractParameterType[] parameterList) + { + return new Contract + { + Script = new byte[0], + _scriptHash = scriptHash, + ParameterList = parameterList + }; + } + public static Contract CreateMultiSigContract(int m, params ECPoint[] publicKeys) { return new Contract diff --git a/neo/SmartContract/ContractParametersContext.cs b/neo/SmartContract/ContractParametersContext.cs index 8a91c936b8..0388df342c 100644 --- a/neo/SmartContract/ContractParametersContext.cs +++ b/neo/SmartContract/ContractParametersContext.cs @@ -86,10 +86,19 @@ public IReadOnlyList ScriptHashes get { if (_ScriptHashes == null) + { + // snapshot is not necessary for Transaction + if (Verifiable is Transaction) + { + _ScriptHashes = Verifiable.GetScriptHashesForVerifying(null); + return _ScriptHashes; + } + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { _ScriptHashes = Verifiable.GetScriptHashesForVerifying(snapshot); } + } return _ScriptHashes; } } @@ -108,6 +117,17 @@ public bool Add(Contract contract, int index, object parameter) return true; } + public bool Add(Contract contract, params object[] parameters) + { + ContextItem item = CreateItem(contract); + if (item == null) return false; + for (int index = 0; index < parameters.Length; index++) + { + item.Parameters[index].Value = parameters[index]; + } + return true; + } + public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) { if (contract.Script.IsMultiSigContract(out _, out _)) @@ -220,6 +240,13 @@ public IReadOnlyList GetParameters(UInt160 scriptHash) return item.Parameters; } + public byte[] GetScript(UInt160 scriptHash) + { + if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) + return null; + return item.Script; + } + public Witness[] GetWitnesses() { if (!Completed) throw new InvalidOperationException(); diff --git a/neo/VM/Helper.cs b/neo/VM/Helper.cs index e996463e3f..d63bebb89a 100644 --- a/neo/VM/Helper.cs +++ b/neo/VM/Helper.cs @@ -162,6 +162,25 @@ public static ScriptBuilder EmitSysCall(this ScriptBuilder sb, uint method, para return sb.EmitSysCall(method); } + /// + /// Generate scripts to call a specific method from a specific contract. + /// + /// contract script hash + /// contract operation + /// operation arguments + /// + public static byte[] MakeScript(this UInt160 scriptHash, string operation, params object[] args) + { + using (ScriptBuilder sb = new ScriptBuilder()) + { + if (args.Length > 0) + sb.EmitAppCall(scriptHash, operation, args); + else + sb.EmitAppCall(scriptHash, operation); + return sb.ToArray(); + } + } + public static ContractParameter ToParameter(this StackItem item) { return ToParameter(item, null); @@ -228,5 +247,67 @@ private static ContractParameter ToParameter(StackItem item, List> context) + { + StackItem stackItem = null; + switch (parameter.Type) + { + case ContractParameterType.Array: + if (context is null) + context = new List>(); + else + stackItem = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter))?.Item1; + if (stackItem is null) + { + stackItem = ((IList)parameter.Value).Select(p => ToStackItem(p, context)).ToList(); + context.Add(new Tuple(stackItem, parameter)); + } + break; + case ContractParameterType.Map: + if (context is null) + context = new List>(); + else + stackItem = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter))?.Item1; + if (stackItem is null) + { + stackItem = new Map(((IList>)parameter.Value).ToDictionary(p => ToStackItem(p.Key, context), p => ToStackItem(p.Value, context))); + context.Add(new Tuple(stackItem, parameter)); + } + break; + case ContractParameterType.Boolean: + stackItem = (bool)parameter.Value; + break; + case ContractParameterType.ByteArray: + case ContractParameterType.Signature: + stackItem = (byte[])parameter.Value; + break; + case ContractParameterType.Integer: + stackItem = (BigInteger)parameter.Value; + break; + case ContractParameterType.Hash160: + stackItem = ((UInt160)parameter.Value).ToArray(); + break; + case ContractParameterType.Hash256: + stackItem = ((UInt256)parameter.Value).ToArray(); + break; + case ContractParameterType.PublicKey: + stackItem = ((ECPoint)parameter.Value).EncodePoint(true); + break; + case ContractParameterType.String: + stackItem = (string)parameter.Value; + break; + case ContractParameterType.InteropInterface: + break; + default: + throw new ArgumentException($"ContractParameterType({parameter.Type}) is not supported to StackItem."); + } + return stackItem; + } } } diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index eb816c7a0a..5149c2b2fe 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -338,27 +338,7 @@ private Transaction MakeTransaction(Snapshot snapshot, byte[] script, Transactio { byte[] witness_script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script; if (witness_script is null) continue; - if (witness_script.IsSignatureContract()) - { - size += 66 + witness_script.GetVarSize(); - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); - } - else if (witness_script.IsMultiSigContract(out int m, out int n)) - { - int size_inv = 65 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m; - using (ScriptBuilder sb = new ScriptBuilder()) - tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n; - using (ScriptBuilder sb = new ScriptBuilder()) - tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n; - } - else - { - //We can support more contract types in the future. - } + tx.NetworkFee += CalculateNetWorkFee(witness_script, ref size); } tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); if (value >= tx.SystemFee + tx.NetworkFee) return tx; @@ -366,6 +346,35 @@ private Transaction MakeTransaction(Snapshot snapshot, byte[] script, Transactio throw new InvalidOperationException("Insufficient GAS"); } + public static long CalculateNetWorkFee(byte[] witness_script, ref int size) + { + long networkFee = 0; + + if (witness_script.IsSignatureContract()) + { + size += 66 + witness_script.GetVarSize(); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); + } + else if (witness_script.IsMultiSigContract(out int m, out int n)) + { + int size_inv = 65 * m; + size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m; + using (ScriptBuilder sb = new ScriptBuilder()) + networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n; + using (ScriptBuilder sb = new ScriptBuilder()) + networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; + networkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n; + } + else + { + //We can support more contract types in the future. + } + + return networkFee; + } + public bool Sign(ContractParametersContext context) { bool fSuccess = false;