diff --git a/.gitignore b/.gitignore index 88426a8e9b..cb49b769de 100644 --- a/.gitignore +++ b/.gitignore @@ -255,3 +255,4 @@ paket-files/ *.sln.iml PublishProfiles +/.vscode diff --git a/neo.UnitTests/Network/RPC/UT_ContractClient.cs b/neo.UnitTests/Network/RPC/UT_ContractClient.cs index fde8a7747d..13a02b5b9d 100644 --- a/neo.UnitTests/Network/RPC/UT_ContractClient.cs +++ b/neo.UnitTests/Network/RPC/UT_ContractClient.cs @@ -24,15 +24,6 @@ public void TestSetup() 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() { @@ -60,7 +51,7 @@ public void TestDeployContract() UT_TransactionManager.MockInvokeScript(rpcClientMock, script, new ContractParameter()); ContractClient contractClient = new ContractClient(rpcClientMock.Object); - var result = contractClient.DeployContract(new byte[1], manifest, keyPair1); + var result = contractClient.CreateDeployContractTx(new byte[1], manifest, keyPair1); Assert.IsNotNull(result); } diff --git a/neo.UnitTests/Network/RPC/UT_Helper.cs b/neo.UnitTests/Network/RPC/UT_Helper.cs new file mode 100644 index 0000000000..cb791fa6ce --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_Helper.cs @@ -0,0 +1,29 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.RPC; +using System; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_Helper + { + [TestMethod] + public void TestToBigInteger() + { + decimal amount = 1.23456789m; + uint decimals = 9; + var result = amount.ToBigInteger(decimals); + Assert.AreEqual(1234567890, result); + + amount = 1.23456789m; + decimals = 18; + result = amount.ToBigInteger(decimals); + Assert.AreEqual(BigInteger.Parse("1234567890000000000"), result); + + amount = 1.23456789m; + decimals = 4; + Assert.ThrowsException(() => result = amount.ToBigInteger(decimals)); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_Nep5API.cs b/neo.UnitTests/Network/RPC/UT_Nep5API.cs index 72e0c29487..48fd711638 100644 --- a/neo.UnitTests/Network/RPC/UT_Nep5API.cs +++ b/neo.UnitTests/Network/RPC/UT_Nep5API.cs @@ -5,6 +5,7 @@ using Neo.SmartContract.Native; using Neo.VM; using Neo.Wallets; +using System.Linq; using System.Numerics; namespace Neo.UnitTests.Network.RPC @@ -76,13 +77,35 @@ public void TestGetTotalSupply() Assert.AreEqual(1_00000000, (int)result); } + [TestMethod] + public void TestGetTokenInfo() + { + UInt160 scriptHash = NativeContract.GAS.Hash; + byte[] testScript = scriptHash.MakeScript("name") + .Concat(scriptHash.MakeScript("symbol")) + .Concat(scriptHash.MakeScript("decimals")) + .Concat(scriptHash.MakeScript("totalSupply")) + .ToArray(); ; + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, + new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Name }, + new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var result = nep5API.GetTokenInfo(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Name, result.Name); + Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); + Assert.AreEqual(8, (int)result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + } + [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)); + var result = nep5API.CreateTransferTx(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000)); Assert.IsNotNull(result); } } diff --git a/neo.UnitTests/Network/RPC/UT_RpcClient.cs b/neo.UnitTests/Network/RPC/UT_RpcClient.cs index eb256cc6d1..a8b212fe37 100644 --- a/neo.UnitTests/Network/RPC/UT_RpcClient.cs +++ b/neo.UnitTests/Network/RPC/UT_RpcClient.cs @@ -109,13 +109,9 @@ public void TestGetBlockHex() { JObject response = CreateResponse(1); response["result"] = "000000002deadfa82cbc4682f5800"; - MockResponse(response.ToString()); - var result = rpc.GetBlockHex("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); - Assert.AreEqual("000000002deadfa82cbc4682f5800", result); - MockResponse(response.ToString()); - result = rpc.GetBlockHex("100"); + var result = rpc.GetBlockHex("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); Assert.AreEqual("000000002deadfa82cbc4682f5800", result); } @@ -134,20 +130,15 @@ public void TestGetBlock() }; JObject json = block.ToJson(); + json["confirmations"] = 20; JObject response = CreateResponse(1); response["result"] = json; - MockResponse(response.ToString()); - var result = rpc.GetBlock("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); - Assert.AreEqual(block.Hash.ToString(), result.Block.Hash.ToString()); - Assert.IsNull(result.Confirmations); - Assert.AreEqual(block.Transactions.Length, result.Block.Transactions.Length); - Assert.AreEqual(block.Transactions[0].Hash.ToString(), result.Block.Transactions[0].Hash.ToString()); - MockResponse(response.ToString()); - result = rpc.GetBlock("100"); + var result = rpc.GetBlock("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); Assert.AreEqual(block.Hash.ToString(), result.Block.Hash.ToString()); - Assert.IsNull(result.Confirmations); + Assert.IsNull(result.NextBlockHash); + Assert.AreEqual(20, result.Confirmations); Assert.AreEqual(block.Transactions.Length, result.Block.Transactions.Length); Assert.AreEqual(block.Transactions[0].Hash.ToString(), result.Block.Transactions[0].Hash.ToString()); @@ -158,6 +149,7 @@ public void TestGetBlock() result = rpc.GetBlock("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); Assert.AreEqual(block.Hash.ToString(), result.Block.Hash.ToString()); Assert.AreEqual(20, result.Confirmations); + Assert.AreEqual("0x773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e", result.NextBlockHash.ToString()); Assert.AreEqual(block.Transactions.Length, result.Block.Transactions.Length); Assert.AreEqual(block.Transactions[0].Hash.ToString(), result.Block.Transactions[0].Hash.ToString()); } @@ -189,13 +181,9 @@ public void TestGetBlockHeaderHex() { JObject response = CreateResponse(1); response["result"] = "0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"; - MockResponse(response.ToString()); - var result = rpc.GetBlockHeaderHex("100"); - Assert.AreEqual("0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2", result); - MockResponse(response.ToString()); - result = rpc.GetBlockHeaderHex("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); + var result = rpc.GetBlockHeaderHex("100"); Assert.AreEqual("0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2", result); } @@ -206,18 +194,15 @@ public void TestGetBlockHeader() TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 _, out UInt160 _, out ulong _, out uint _, out Witness _); JObject json = header.ToJson(); + json["confirmations"] = 20; JObject response = CreateResponse(1); response["result"] = json; MockResponse(response.ToString()); var result = rpc.GetBlockHeader("100"); Assert.AreEqual(header.Hash.ToString(), result.Header.Hash.ToString()); - Assert.IsNull(result.Confirmations); - - MockResponse(response.ToString()); - result = rpc.GetBlockHeader("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); - Assert.AreEqual(header.Hash.ToString(), result.Header.Hash.ToString()); - Assert.IsNull(result.Confirmations); + Assert.IsNull(result.NextBlockHash); + Assert.AreEqual(20, result.Confirmations); json["confirmations"] = 20; json["nextblockhash"] = "4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"; @@ -373,9 +358,18 @@ public void TestGetRawTransaction() Assert.AreEqual(transaction.Hash, result.Transaction.Hash); Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + // make the code compatible with the old version json["blockhash"] = UInt256.Zero.ToString(); json["confirmations"] = 100; json["blocktime"] = 10; + MockResponse(response.ToString()); + + result = rpc.GetRawTransaction("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"); + Assert.AreEqual(transaction.Hash, result.Transaction.Hash); + Assert.AreEqual(100, result.Confirmations); + Assert.AreEqual(null, result.VMState); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + json["vmState"] = VMState.HALT; MockResponse(response.ToString()); @@ -472,8 +466,7 @@ public void TestInvokeFunction() ""type"": ""ByteArray"", ""value"": ""262bec084432"" } - ], - ""tx"":""d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"" + ] }"); JObject response = CreateResponse(1); response["result"] = json; @@ -496,8 +489,7 @@ public void TestInvokeScript() ""type"": ""ByteArray"", ""value"": ""262bec084432"" } - ], - ""tx"":""d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"" + ] }"); JObject response = CreateResponse(1); response["result"] = json; diff --git a/neo.UnitTests/Network/RPC/UT_TransactionManager.cs b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs index ee55f81bb9..e1cce967cd 100644 --- a/neo.UnitTests/Network/RPC/UT_TransactionManager.cs +++ b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs @@ -68,11 +68,10 @@ public static void MockInvokeScript(Mock mockClient, byte[] script, p Stack = parameters, GasConsumed = "100", Script = script.ToHexString(), - State = "", - Tx = "" + State = "" }; - mockClient.Setup(p => p.RpcSend("invokescript", It.Is(j => j.AsString() == script.ToHexString()))) + mockClient.Setup(p => p.RpcSend("invokescript", It.Is(j => j[0].AsString() == script.ToHexString()))) .Returns(result.ToJson()) .Verifiable(); } diff --git a/neo.UnitTests/Network/RPC/UT_WalletAPI.cs b/neo.UnitTests/Network/RPC/UT_WalletAPI.cs new file mode 100644 index 0000000000..bba6a787e1 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_WalletAPI.cs @@ -0,0 +1,116 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.IO.Json; +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.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_WalletAPI + { + Mock rpcClientMock; + KeyPair keyPair1; + string address1; + UInt160 sender; + WalletAPI walletAPI; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + address1 = Neo.Wallets.Helper.ToAddress(sender); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + walletAPI = new WalletAPI(rpcClientMock.Object); + } + + [TestMethod] + public void TestGetUnclaimedGas() + { + byte[] testScript = NativeContract.NEO.Hash.MakeScript("unclaimedGas", sender, 99); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = walletAPI.GetUnclaimedGas(address1); + Assert.AreEqual(1.1m, balance); + } + + [TestMethod] + public void TestGetNeoBalance() + { + byte[] testScript = NativeContract.NEO.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var balance = walletAPI.GetNeoBalance(address1); + Assert.AreEqual(1_00000000u, balance); + } + + [TestMethod] + public void TestGetGasBalance() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = walletAPI.GetGasBalance(address1); + Assert.AreEqual(1.1m, balance); + } + + [TestMethod] + public void TestGetTokenBalance() + { + byte[] testScript = UInt160.Zero.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = walletAPI.GetTokenBalance(UInt160.Zero.ToString(), address1); + Assert.AreEqual(1_10000000, balance); + } + + [TestMethod] + public void TestClaimGas() + { + byte[] balanceScript = NativeContract.NEO.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, balanceScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + byte[] testScript = NativeContract.NEO.Hash.MakeScript("transfer", sender, sender, new BigInteger(1_00000000)); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + rpcClientMock.Setup(p => p.RpcSend("sendrawtransaction", It.IsAny())).Returns(true); + + var tranaction = walletAPI.ClaimGas(keyPair1.Export()); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString()); + } + + [TestMethod] + public void TestTransfer() + { + byte[] decimalsScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, decimalsScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(8) }); + + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, NativeContract.GAS.Factor * 100); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + rpcClientMock.Setup(p => p.RpcSend("sendrawtransaction", It.IsAny())).Returns(true); + + var tranaction = walletAPI.Transfer(NativeContract.GAS.Hash.ToString(), keyPair1.Export(), UInt160.Zero.ToAddress(), 100, 1.1m); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString()); + } + + [TestMethod] + public void TestWaitTransaction() + { + Transaction transaction = TestUtils.GetTransaction(); + rpcClientMock.Setup(p => p.RpcSend("getrawtransaction", It.Is(j => j[0].AsString() == transaction.Hash.ToString()))) + .Returns(new RpcTransaction { Transaction = transaction, VMState = VMState.HALT, BlockHash = UInt256.Zero, BlockTime = 100, Confirmations = 1 }.ToJson()); + + var tx = walletAPI.WaitTransaction(transaction).Result; + Assert.AreEqual(VMState.HALT, tx.VMState); + Assert.AreEqual(UInt256.Zero, tx.BlockHash); + } + } +} diff --git a/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index 7e811d3eda..d14285e67e 100644 --- a/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -104,22 +104,13 @@ public void Check_BalanceOfTransferAndBurn() Assert.ThrowsException(() => NativeContract.GAS.Burn(engine, new UInt160(to), BigInteger.MinusOne)); - // Burn more than expected - - Assert.ThrowsException(() => - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000600000000001))); - // Real burn - - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1)); + NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1)).Should().Be(1); NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(3000599999999999); - keyCount.Should().Be(snapshot.Storages.GetChangeSet().Count()); - - // Burn all - - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000599999999999)); + // Burn more than expected + NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000600000000000)).Should().Be(3000599999999999); (keyCount - 1).Should().Be(snapshot.Storages.GetChangeSet().Count()); diff --git a/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs b/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs index 703fc6241e..243cfa89ef 100644 --- a/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs +++ b/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs @@ -240,6 +240,18 @@ public void TestContract_Update() engine.CurrentContext.EvaluationStack.Push(manifest.ToString()); engine.CurrentContext.EvaluationStack.Push(script); InteropService.Invoke(engine, InteropService.Neo_Contract_Update).Should().BeTrue(); + + // Remove Storage flag with something stored + + state.Manifest.Features = ContractFeatures.NoProperty; + mockSnapshot.SetupGet(p => p.Contracts).Returns(new TestDataCache(state.ScriptHash, state)); + mockSnapshot.SetupGet(p => p.Storages).Returns(new TestDataCache(storageKey, storageItem)); + + engine = new ApplicationEngine(TriggerType.Application, null, mockSnapshot.Object, 0); + engine.LoadScript(state.Script); + engine.CurrentContext.EvaluationStack.Push(manifest.ToString()); + engine.CurrentContext.EvaluationStack.Push(script); + InteropService.Invoke(engine, InteropService.Neo_Contract_Update).Should().BeFalse(); } [TestMethod] diff --git a/neo.UnitTests/UT_Utility.cs b/neo.UnitTests/UT_Utility.cs new file mode 100644 index 0000000000..dd68da017c --- /dev/null +++ b/neo.UnitTests/UT_Utility.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.Wallets; +using System; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_Utility + { + private KeyPair keyPair; + private UInt160 scriptHash; + + [TestInitialize] + public void TestSetup() + { + keyPair = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + scriptHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(); + } + + [TestMethod] + public void TestGetKeyPair() + { + string nul = null; + Assert.ThrowsException(() => Utility.GetKeyPair(nul)); + + string wif = "KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"; + var result = Utility.GetKeyPair(wif); + Assert.AreEqual(keyPair, result); + + string privateKey = keyPair.PrivateKey.ToHexString(); + result = Utility.GetKeyPair(privateKey); + Assert.AreEqual(keyPair, result); + } + + [TestMethod] + public void TestGetScriptHash() + { + string nul = null; + Assert.ThrowsException(() => Utility.GetScriptHash(nul)); + + string addr = scriptHash.ToAddress(); + var result = Utility.GetScriptHash(addr); + Assert.AreEqual(scriptHash, result); + + string hash = scriptHash.ToString(); + result = Utility.GetScriptHash(hash); + Assert.AreEqual(scriptHash, result); + + string publicKey = keyPair.PublicKey.ToString(); + result = Utility.GetScriptHash(publicKey); + Assert.AreEqual(scriptHash, result); + } + } +} diff --git a/neo.UnitTests/VM/UT_Helper.cs b/neo.UnitTests/VM/UT_Helper.cs index 39f1ec104a..41d6270822 100644 --- a/neo.UnitTests/VM/UT_Helper.cs +++ b/neo.UnitTests/VM/UT_Helper.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.VM; using System; using System.Collections.Generic; @@ -9,7 +10,7 @@ using System.Numerics; using System.Text; -namespace Neo.UnitTests.IO +namespace Neo.UnitTests.VMT { [TestClass] public class UT_Helper @@ -84,6 +85,15 @@ public void TestEmitAppCall3() Assert.AreEqual(Encoding.Default.GetString(tempArray), Encoding.Default.GetString(resultArray)); } + [TestMethod] + public void TestMakeScript() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + + Assert.AreEqual("14000000000000000000000000000000000000000051c10962616c616e63654f66142582d1b275e86c8f0e93a9b2facd5fdb760976a168627d5b52", + testScript.ToHexString()); + } + [TestMethod] public void TestToParameter() { diff --git a/neo/Ledger/MemoryPool.cs b/neo/Ledger/MemoryPool.cs index 32d287bc2a..65f4054956 100644 --- a/neo/Ledger/MemoryPool.cs +++ b/neo/Ledger/MemoryPool.cs @@ -17,8 +17,7 @@ namespace Neo.Ledger public class MemoryPool : IReadOnlyCollection { // Allow a reverified transaction to be rebroadcasted if it has been this many block times since last broadcast. - private const int BlocksTillRebroadcastLowPriorityPoolTx = 30; - private const int BlocksTillRebroadcastHighPriorityPoolTx = 10; + private const int BlocksTillRebroadcast = 10; private int RebroadcastMultiplierThreshold => Capacity / 10; private static readonly double MaxMillisecondsToReverifyTx = (double)Blockchain.MillisecondsPerBlock / 3; @@ -431,9 +430,8 @@ internal void InvalidateAllTransactions() _txRwLock.EnterWriteLock(); try { - int blocksTillRebroadcast = Object.ReferenceEquals(unverifiedSortedTxPool, _sortedTransactions) - ? BlocksTillRebroadcastHighPriorityPoolTx : BlocksTillRebroadcastLowPriorityPoolTx; - + int blocksTillRebroadcast = BlocksTillRebroadcast; + // Increases, proportionally, blocksTillRebroadcast if mempool has more items than threshold bigger RebroadcastMultiplierThreshold if (Count > RebroadcastMultiplierThreshold) blocksTillRebroadcast = blocksTillRebroadcast * Count / RebroadcastMultiplierThreshold; @@ -483,7 +481,7 @@ internal void InvalidateAllTransactions() /// /// Note: this must only be called from a single thread (the Blockchain actor) /// - /// Max transactions to reverify, the value passed cam be >=1 + /// Max transactions to reverify, the value passed can be >=1 /// The snapshot to use for verifying. /// true if more unsorted messages exist, otherwise false internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, Snapshot snapshot) diff --git a/neo/Network/RPC/ContractClient.cs b/neo/Network/RPC/ContractClient.cs index a329e07674..45fb31d267 100644 --- a/neo/Network/RPC/ContractClient.cs +++ b/neo/Network/RPC/ContractClient.cs @@ -44,7 +44,7 @@ public RpcInvokeResult TestInvoke(UInt160 scriptHash, string operation, params o /// 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) + public Transaction CreateDeployContractTx(byte[] contractScript, ContractManifest manifest, KeyPair key, long networkFee = 0) { byte[] script; using (ScriptBuilder sb = new ScriptBuilder()) @@ -53,7 +53,8 @@ public Transaction DeployContract(byte[] contractScript, ContractManifest manife script = sb.ToArray(); } - Transaction tx = new TransactionManager(rpcClient, Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash()) + UInt160 sender = Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash(); + Transaction tx = new TransactionManager(rpcClient, sender) .MakeTransaction(script, null, null, networkFee) .AddSignature(key) .Sign() diff --git a/neo/Network/RPC/Helper.cs b/neo/Network/RPC/Helper.cs new file mode 100644 index 0000000000..5c5c82e329 --- /dev/null +++ b/neo/Network/RPC/Helper.cs @@ -0,0 +1,38 @@ +using System; +using System.Numerics; + +namespace Neo.Network.RPC +{ + internal static class Helper + { + /// + /// Convert decimal amount to BigInteger: amount * 10 ^ decimals + /// + /// float value + /// token decimals + /// + public static BigInteger ToBigInteger(this decimal amount, uint decimals) + { + BigInteger factor = BigInteger.Pow(10, (int)decimals); + var (numerator, denominator) = Fraction(amount); + if (factor < denominator) + { + throw new OverflowException("The decimal places is too long."); + } + + BigInteger res = factor * numerator / denominator; + return res; + } + + private static (BigInteger numerator, BigInteger denominator) Fraction(decimal d) + { + int[] bits = decimal.GetBits(d); + BigInteger numerator = (1 - ((bits[3] >> 30) & 2)) * + unchecked(((BigInteger)(uint)bits[2] << 64) | + ((BigInteger)(uint)bits[1] << 32) | + (uint)bits[0]); + BigInteger denominator = BigInteger.Pow(10, (bits[3] >> 16) & 0xff); + return (numerator, denominator); + } + } +} diff --git a/neo/Network/RPC/Models/RpcBlock.cs b/neo/Network/RPC/Models/RpcBlock.cs index f71af51168..1ff485f96e 100644 --- a/neo/Network/RPC/Models/RpcBlock.cs +++ b/neo/Network/RPC/Models/RpcBlock.cs @@ -7,16 +7,16 @@ public class RpcBlock { public Block Block { get; set; } - public int? Confirmations { get; set; } + public int Confirmations { get; set; } public UInt256 NextBlockHash { get; set; } public JObject ToJson() { JObject json = Block.ToJson(); - if (Confirmations != null) + json["confirmations"] = Confirmations; + if (NextBlockHash != null) { - json["confirmations"] = Confirmations; json["nextblockhash"] = NextBlockHash.ToString(); } return json; @@ -26,9 +26,9 @@ public static RpcBlock FromJson(JObject json) { RpcBlock block = new RpcBlock(); block.Block = Block.FromJson(json); - if (json["confirmations"] != null) + block.Confirmations = (int)json["confirmations"].AsNumber(); + if (json["nextblockhash"] != null) { - block.Confirmations = (int)json["confirmations"].AsNumber(); block.NextBlockHash = UInt256.Parse(json["nextblockhash"].AsString()); } return block; diff --git a/neo/Network/RPC/Models/RpcBlockHeader.cs b/neo/Network/RPC/Models/RpcBlockHeader.cs index 2b9293ecaf..5346dffd91 100644 --- a/neo/Network/RPC/Models/RpcBlockHeader.cs +++ b/neo/Network/RPC/Models/RpcBlockHeader.cs @@ -7,16 +7,16 @@ public class RpcBlockHeader { public Header Header { get; set; } - public int? Confirmations { get; set; } + public int Confirmations { get; set; } public UInt256 NextBlockHash { get; set; } public JObject ToJson() { JObject json = Header.ToJson(); - if (Confirmations != null) + json["confirmations"] = Confirmations; + if (NextBlockHash != null) { - json["confirmations"] = Confirmations; json["nextblockhash"] = NextBlockHash.ToString(); } return json; @@ -26,9 +26,9 @@ public static RpcBlockHeader FromJson(JObject json) { RpcBlockHeader block = new RpcBlockHeader(); block.Header = Header.FromJson(json); - if (json["confirmations"] != null) + block.Confirmations = (int)json["confirmations"].AsNumber(); + if (json["nextblockhash"] != null) { - block.Confirmations = (int)json["confirmations"].AsNumber(); block.NextBlockHash = UInt256.Parse(json["nextblockhash"].AsString()); } return block; diff --git a/neo/Network/RPC/Models/RpcInvokeResult.cs b/neo/Network/RPC/Models/RpcInvokeResult.cs index c56307950a..a9c5f04c48 100644 --- a/neo/Network/RPC/Models/RpcInvokeResult.cs +++ b/neo/Network/RPC/Models/RpcInvokeResult.cs @@ -14,8 +14,6 @@ public class RpcInvokeResult public ContractParameter[] Stack { get; set; } - public string Tx { get; set; } - public JObject ToJson() { JObject json = new JObject(); @@ -23,7 +21,6 @@ public JObject ToJson() json["state"] = State; json["gas_consumed"] = GasConsumed; json["stack"] = new JArray(Stack.Select(p => p.ToJson())); - json["tx"] = Tx; return json; } @@ -33,7 +30,6 @@ public static RpcInvokeResult FromJson(JObject json) invokeScriptResult.Script = json["script"].AsString(); invokeScriptResult.State = json["state"].AsString(); invokeScriptResult.GasConsumed = json["gas_consumed"].AsString(); - invokeScriptResult.Tx = json["tx"].AsString(); invokeScriptResult.Stack = ((JArray)json["stack"]).Select(p => ContractParameter.FromJson(p)).ToArray(); return invokeScriptResult; } diff --git a/neo/Network/RPC/Models/RpcNep5TokenInfo.cs b/neo/Network/RPC/Models/RpcNep5TokenInfo.cs new file mode 100644 index 0000000000..0f251a5a37 --- /dev/null +++ b/neo/Network/RPC/Models/RpcNep5TokenInfo.cs @@ -0,0 +1,15 @@ +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcNep5TokenInfo + { + public string Name { get; set; } + + public string Symbol { get; set; } + + public uint Decimals { get; set; } + + public BigInteger TotalSupply { get; set; } + } +} diff --git a/neo/Network/RPC/Models/RpcTransaction.cs b/neo/Network/RPC/Models/RpcTransaction.cs index f96179c358..48b1e19bd4 100644 --- a/neo/Network/RPC/Models/RpcTransaction.cs +++ b/neo/Network/RPC/Models/RpcTransaction.cs @@ -24,7 +24,10 @@ public JObject ToJson() json["blockhash"] = BlockHash.ToString(); json["confirmations"] = Confirmations; json["blocktime"] = BlockTime; - json["vmState"] = VMState; + if (VMState != null) + { + json["vmState"] = VMState; + } } return json; } @@ -38,7 +41,7 @@ public static RpcTransaction FromJson(JObject json) transaction.BlockHash = UInt256.Parse(json["blockhash"].AsString()); transaction.Confirmations = (int)json["confirmations"].AsNumber(); transaction.BlockTime = (uint)json["blocktime"].AsNumber(); - transaction.VMState = json["vmState"].TryGetEnum(); + transaction.VMState = json["vmState"]?.TryGetEnum(); } return transaction; } diff --git a/neo/Network/RPC/Nep5API.cs b/neo/Network/RPC/Nep5API.cs index abfad6c9ed..26ae041ae0 100644 --- a/neo/Network/RPC/Nep5API.cs +++ b/neo/Network/RPC/Nep5API.cs @@ -1,4 +1,5 @@ using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; using Neo.SmartContract; using Neo.VM; using Neo.Wallets; @@ -71,15 +72,39 @@ public BigInteger TotalSupply(UInt160 scriptHash) } /// - /// Get name of NEP5 token + /// Get token information in one rpc call + /// + /// contract script hash + /// + public RpcNep5TokenInfo GetTokenInfo(UInt160 scriptHash) + { + byte[] script = scriptHash.MakeScript("name") + .Concat(scriptHash.MakeScript("symbol")) + .Concat(scriptHash.MakeScript("decimals")) + .Concat(scriptHash.MakeScript("totalSupply")) + .ToArray(); + + var result = rpcClient.InvokeScript(script).Stack; + + return new RpcNep5TokenInfo + { + Name = result[0].ToStackItem().GetString(), + Symbol = result[1].ToStackItem().GetString(), + Decimals = (uint)result[2].ToStackItem().GetBigInteger(), + TotalSupply = result[3].ToStackItem().GetBigInteger() + }; + } + + /// + /// Create NEP5 token transfer transaction /// /// contract script hash /// from KeyPair /// to account script hash /// transfer amount - /// netwotk fee, set to be 0 if you don't need higher priority + /// netwotk fee, set to be 0 will auto calculate the least fee /// - public Transaction Transfer(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, long networkFee = 0) + public Transaction CreateTransferTx(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 } }; diff --git a/neo/Network/RPC/RpcClient.cs b/neo/Network/RPC/RpcClient.cs index cd0578953e..0721efcb33 100644 --- a/neo/Network/RPC/RpcClient.cs +++ b/neo/Network/RPC/RpcClient.cs @@ -1,7 +1,10 @@ +using Neo.IO; using Neo.IO.Json; using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.Network.RPC.Models; using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; @@ -257,9 +260,14 @@ 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(byte[] script) + public RpcInvokeResult InvokeScript(byte[] script, params UInt160[] scriptHashesForVerifying) { - return RpcInvokeResult.FromJson(RpcSend("invokescript", script.ToHexString())); + List parameters = new List + { + script.ToHexString() + }; + parameters.AddRange(scriptHashesForVerifying.Select(p => (JObject)p.ToString())); + return RpcInvokeResult.FromJson(RpcSend("invokescript", parameters.ToArray())); } /// @@ -271,7 +279,7 @@ public RpcPlugin[] ListPlugins() } /// - /// Broadcasts a transaction over the NEO network. + /// Broadcasts a serialized transaction over the NEO network. /// public bool SendRawTransaction(byte[] rawTransaction) { @@ -279,7 +287,15 @@ public bool SendRawTransaction(byte[] rawTransaction) } /// - /// Broadcasts a raw block over the NEO network. + /// Broadcasts a transaction over the NEO network. + /// + public bool SendRawTransaction(Transaction transaction) + { + return SendRawTransaction(transaction.ToArray()); + } + + /// + /// Broadcasts a serialized block over the NEO network. /// public bool SubmitBlock(byte[] block) { diff --git a/neo/Network/RPC/TransactionManager.cs b/neo/Network/RPC/TransactionManager.cs index ba714a52ef..27741a58e3 100644 --- a/neo/Network/RPC/TransactionManager.cs +++ b/neo/Network/RPC/TransactionManager.cs @@ -1,5 +1,6 @@ using Neo.Cryptography.ECC; using Neo.IO; +using Neo.IO.Json; using Neo.Network.P2P.Payloads; using Neo.Network.RPC.Models; using Neo.SmartContract; @@ -7,6 +8,7 @@ using Neo.VM; using Neo.Wallets; using System; +using System.Linq; namespace Neo.Network.RPC { @@ -16,6 +18,8 @@ namespace Neo.Network.RPC public class TransactionManager { private readonly RpcClient rpcClient; + private readonly PolicyAPI policyAPI; + private readonly Nep5API nep5API; private readonly UInt160 sender; /// @@ -36,6 +40,8 @@ public class TransactionManager public TransactionManager(RpcClient rpc, UInt160 sender) { rpcClient = rpc; + policyAPI = new PolicyAPI(rpc); + nep5API = new Nep5API(rpc); this.sender = sender; } @@ -63,7 +69,9 @@ public TransactionManager MakeTransaction(byte[] script, TransactionAttribute[] Witnesses = new Witness[0] }; - RpcInvokeResult result = rpcClient.InvokeScript(script); + // Add witness hashes parameter to pass CheckWitness + UInt160[] hashes = Tx.GetScriptHashesForVerifying(null).ToArray(); + RpcInvokeResult result = rpcClient.InvokeScript(script, hashes); Tx.SystemFee = Math.Max(long.Parse(result.GasConsumed) - ApplicationEngine.GasFree, 0); if (Tx.SystemFee > 0) { @@ -80,7 +88,7 @@ public TransactionManager MakeTransaction(byte[] script, TransactionAttribute[] // 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); + var gasBalance = nep5API.BalanceOf(NativeContract.GAS.Hash, sender); if (gasBalance >= Tx.SystemFee + Tx.NetworkFee) return this; throw new InvalidOperationException($"Insufficient GAS in address: {sender.ToAddress()}"); } @@ -101,7 +109,7 @@ private long EstimateNetworkFee() networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_ECDsaVerify, null); } - networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + networkFee += size * policyAPI.GetFeePerByte(); return networkFee; } @@ -129,7 +137,7 @@ private long CalculateNetworkFee() networkFee += Wallet.CalculateNetWorkFee(witness_script, ref size); } - networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + networkFee += size * policyAPI.GetFeePerByte(); return networkFee; } diff --git a/neo/Network/RPC/WalletAPI.cs b/neo/Network/RPC/WalletAPI.cs new file mode 100644 index 0000000000..e1ab3b777f --- /dev/null +++ b/neo/Network/RPC/WalletAPI.cs @@ -0,0 +1,192 @@ +using Neo.Ledger; +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; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// Wallet Common APIs + /// + public class WalletAPI + { + private readonly RpcClient rpcClient; + private readonly Nep5API nep5API; + + /// + /// WalletAPI Constructor + /// + /// the RPC client to call NEO RPC methods + public WalletAPI(RpcClient rpc) + { + rpcClient = rpc; + nep5API = new Nep5API(rpc); + } + + /// + /// Get unclaimed gas with address, scripthash or public key string + /// + /// address, scripthash or public key string + /// Example: address ("AV556nYUwyJKNv8Xy7hVMLQnkmKPukw6x5"), scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public decimal GetUnclaimedGas(string account) + { + UInt160 accountHash = Utility.GetScriptHash(account); + return GetUnclaimedGas(accountHash); + } + + /// + /// Get unclaimed gas + /// + /// account scripthash + /// + public decimal GetUnclaimedGas(UInt160 account) + { + UInt160 scriptHash = NativeContract.NEO.Hash; + BigInteger balance = nep5API.TestInvoke(scriptHash, "unclaimedGas", account, rpcClient.GetBlockCount() - 1) + .Stack.Single().ToStackItem().GetBigInteger(); + return ((decimal)balance) / (long)NativeContract.GAS.Factor; + } + + /// + /// Get Neo Balance + /// + /// address, scripthash or public key string + /// Example: address ("AV556nYUwyJKNv8Xy7hVMLQnkmKPukw6x5"), scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public uint GetNeoBalance(string account) + { + BigInteger balance = GetTokenBalance(NativeContract.NEO.Hash.ToString(), account); + return (uint)balance; + } + + /// + /// Get Gas Balance + /// + /// address, scripthash or public key string + /// Example: address ("AV556nYUwyJKNv8Xy7hVMLQnkmKPukw6x5"), scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public decimal GetGasBalance(string account) + { + BigInteger balance = GetTokenBalance(NativeContract.GAS.Hash.ToString(), account); + return ((decimal)balance) / (long)NativeContract.GAS.Factor; + } + + /// + /// Get token balance with string parameters + /// + /// token script hash, Example: "0x43cf98eddbe047e198a3e5d57006311442a0ca15"(NEO) + /// address, scripthash or public key string + /// Example: address ("AV556nYUwyJKNv8Xy7hVMLQnkmKPukw6x5"), scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public BigInteger GetTokenBalance(string tokenHash, string account) + { + UInt160 scriptHash = Utility.GetScriptHash(tokenHash); + UInt160 accountHash = Utility.GetScriptHash(account); + return nep5API.BalanceOf(scriptHash, accountHash); + } + + /// + /// The GAS is claimed when doing NEO transfer + /// This function will transfer NEO balance from account to itself + /// + /// wif or private key + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// The transaction sended + public Transaction ClaimGas(string key) + { + KeyPair keyPair = Utility.GetKeyPair(key); + return ClaimGas(keyPair); + } + + /// + /// The GAS is claimed when doing NEO transfer + /// This function will transfer NEO balance from account to itself + /// + /// keyPair + /// The transaction sended + public Transaction ClaimGas(KeyPair keyPair) + { + UInt160 toHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(); + BigInteger balance = nep5API.BalanceOf(NativeContract.NEO.Hash, toHash); + Transaction transaction = nep5API.CreateTransferTx(NativeContract.NEO.Hash, keyPair, toHash, balance); + rpcClient.SendRawTransaction(transaction); + return transaction; + } + + /// + /// Transfer NEP5 token balance, with common data types + /// + /// nep5 token script hash, Example: scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91") + /// wif or private key + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// address or account script hash + /// token amount + /// netwotk fee, set to be 0 will auto calculate the least fee + /// + public Transaction Transfer(string tokenHash, string fromKey, string toAddress, decimal amount, decimal networkFee = 0) + { + UInt160 scriptHash = Utility.GetScriptHash(tokenHash); + var decimals = nep5API.Decimals(scriptHash); + + KeyPair from = Utility.GetKeyPair(fromKey); + UInt160 to = Utility.GetScriptHash(toAddress); + BigInteger amountInteger = amount.ToBigInteger(decimals); + BigInteger networkFeeInteger = networkFee.ToBigInteger(NativeContract.GAS.Decimals); + return Transfer(scriptHash, from, to, amountInteger, (long)networkFeeInteger); + } + + /// + /// Transfer NEP5 token balance + /// + /// contract script hash + /// from KeyPair + /// to account script hash + /// transfer amount + /// netwotk fee, set to be 0 will auto calculate the least fee + /// + public Transaction Transfer(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger, BigInteger networkFeeInteger = default) + { + Transaction transaction = nep5API.CreateTransferTx(scriptHash, from, to, amountInteger, (long)networkFeeInteger); + rpcClient.SendRawTransaction(transaction); + return transaction; + } + + /// + /// Wait until the transaction is observable block chain + /// + /// the transaction to observe + /// TimeoutException throws after "timeout" seconds + /// the Transaction state, including vmState and blockhash + public async Task WaitTransaction(Transaction transaction, int timeout = 60) + { + DateTime deadline = DateTime.UtcNow.AddSeconds(timeout); + RpcTransaction rpcTx = null; + while (rpcTx == null || rpcTx.Confirmations == null) + { + if (deadline < DateTime.UtcNow) + { + throw new TimeoutException(); + } + + try + { + rpcTx = rpcClient.GetRawTransaction(transaction.Hash.ToString()); + if (rpcTx == null || rpcTx.Confirmations == null) + { + await Task.Delay((int)Blockchain.MillisecondsPerBlock / 2); + } + } + catch (Exception) { } + } + return rpcTx; + } + } +} diff --git a/neo/Network/UPnP.cs b/neo/Network/UPnP.cs index 5cac628daa..2d0d1d1cc5 100644 --- a/neo/Network/UPnP.cs +++ b/neo/Network/UPnP.cs @@ -76,7 +76,7 @@ private static string GetServiceUrl(string resp) { try { - XmlDocument desc = new XmlDocument(); + XmlDocument desc = new XmlDocument() { XmlResolver = null }; HttpWebRequest request = WebRequest.CreateHttp(resp); using (WebResponse response = request.GetResponse()) { @@ -155,7 +155,7 @@ private static XmlDocument SOAPRequest(string url, string soap, string function) using (Stream reqs = r.GetRequestStream()) { reqs.Write(b, 0, b.Length); - XmlDocument resp = new XmlDocument(); + XmlDocument resp = new XmlDocument() { XmlResolver = null }; WebResponse wres = r.GetResponse(); using (Stream ress = wres.GetResponseStream()) { diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 90a49a4df2..212d1091b6 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -236,6 +236,7 @@ private static bool Contract_Update(ApplicationEngine engine) contract = engine.Snapshot.Contracts.GetAndChange(contract.ScriptHash); contract.Manifest = ContractManifest.Parse(manifest); if (!contract.Manifest.IsValid(contract.ScriptHash)) return false; + if (!contract.HasStorage && engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).Any()) return false; } return true; diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index 7ef522dab7..11dcfee9a9 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -37,12 +37,25 @@ internal override bool Initialize(ApplicationEngine engine) protected override bool OnPersist(ApplicationEngine engine) { if (!base.OnPersist(engine)) return false; + BigInteger totalNetworkFee = 0; + BigInteger totalSysFee = 0; foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) - Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); + { + BigInteger burned = Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); + if (burned >= tx.NetworkFee) + { + totalNetworkFee += tx.NetworkFee; + totalSysFee += burned - tx.NetworkFee; + } + else + { + totalNetworkFee += burned; + } + } ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); - Mint(engine, primary, engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.NetworkFee)); - BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.SystemFee); + Mint(engine, primary, totalNetworkFee); + BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + totalSysFee; StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(engine.Snapshot.PersistingBlock.Index)); engine.Snapshot.Storages.Add(key, new StorageItem { diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index a95885a665..5a506e50e8 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -87,15 +87,25 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, engine.SendNotification(Hash, new StackItem[] { "Transfer", StackItem.Null, account.ToArray(), amount }); } - internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) + /// + /// Burn token, when the account balance less than amount, it will burn all the balance of the account + /// + /// The application engine + /// The burning account + /// The amount to be burned + /// The actual amount of burned + internal protected virtual BigInteger Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); - if (amount.IsZero) return; + if (amount.IsZero) return amount; StorageKey key = CreateAccountKey(account); StorageItem storage = engine.Snapshot.Storages.GetAndChange(key); TState state = new TState(); state.FromByteArray(storage.Value); - if (state.Balance < amount) throw new InvalidOperationException(); + if (state.Balance < amount) + { + amount = state.Balance; + } OnBalanceChanging(engine, account, state, -amount); if (state.Balance == amount) { @@ -111,6 +121,7 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, totalSupply -= amount; storage.Value = totalSupply.ToByteArray(); engine.SendNotification(Hash, new StackItem[] { "Transfer", account.ToArray(), StackItem.Null, amount }); + return amount; } [ContractMethod(0, ContractParameterType.String, Name = "name", SafeMethod = true)] diff --git a/neo/Utility.cs b/neo/Utility.cs new file mode 100644 index 0000000000..56e048a512 --- /dev/null +++ b/neo/Utility.cs @@ -0,0 +1,61 @@ +using Neo.Cryptography.ECC; +using Neo.SmartContract; +using Neo.Wallets; +using System; + +namespace Neo +{ + public static class Utility + { + /// + /// Parse WIF or private key hex string to KeyPair + /// + /// WIF or private key hex string + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// + public static KeyPair GetKeyPair(string key) + { + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } + if (key.StartsWith("0x")) { key = key.Substring(2); } + + if (key.Length == 52) + { + return new KeyPair(Wallet.GetPrivateKeyFromWIF(key)); + } + else if (key.Length == 64) + { + return new KeyPair(key.HexToBytes()); + } + + throw new FormatException(); + } + + /// + /// Parse address, scripthash or public key string to UInt160 + /// + /// account address, scripthash or public key string + /// Example: address ("AV556nYUwyJKNv8Xy7hVMLQnkmKPukw6x5"), scripthash ("0x6a38cd693b615aea24dd00de12a9f5836844da91"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public static UInt160 GetScriptHash(string account) + { + if (string.IsNullOrEmpty(account)) { throw new ArgumentNullException(nameof(account)); } + if (account.StartsWith("0x")) { account = account.Substring(2); } + + if (account.Length == 34) + { + return Wallets.Helper.ToScriptHash(account); + } + else if (account.Length == 40) + { + return UInt160.Parse(account); + } + else if (account.Length == 66) + { + var pubKey = ECPoint.Parse(account, ECCurve.Secp256r1); + return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash(); + } + + throw new FormatException(); + } + } +}