diff --git a/src/RpcClient/Nep5API.cs b/src/RpcClient/Nep5API.cs
index 558c5453e..c6ffe60c9 100644
--- a/src/RpcClient/Nep5API.cs
+++ b/src/RpcClient/Nep5API.cs
@@ -1,8 +1,10 @@
+using Neo.Cryptography.ECC;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC.Models;
using Neo.SmartContract;
using Neo.VM;
using Neo.Wallets;
+using System;
using System.Linq;
using System.Numerics;
using static Neo.Helper;
@@ -117,5 +119,32 @@ public Transaction CreateTransferTx(UInt160 scriptHash, KeyPair fromKey, UInt160
return tx;
}
+
+ ///
+ /// Create NEP5 token transfer transaction from multi-sig account
+ ///
+ /// contract script hash
+ /// multi-sig min signature count
+ /// multi-sig pubKeys
+ /// sign keys
+ /// to account
+ /// transfer amount
+ ///
+ public Transaction CreateTransferTx(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount)
+ {
+ if (m > fromKeys.Length)
+ throw new ArgumentException($"Need at least {m} KeyPairs for signing!");
+ var sender = Contract.CreateMultiSigContract(m, pubKeys).ScriptHash;
+ 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)
+ .AddMultiSig(fromKeys, m, pubKeys)
+ .Sign()
+ .Tx;
+
+ return tx;
+ }
}
}
diff --git a/src/RpcClient/RpcClient.cs b/src/RpcClient/RpcClient.cs
index 1bd381cb2..ea1eebeaf 100644
--- a/src/RpcClient/RpcClient.cs
+++ b/src/RpcClient/RpcClient.cs
@@ -316,9 +316,14 @@ public UInt256 SubmitBlock(byte[] block)
/// Returns the result after calling a smart contract at scripthash with the given operation and parameters.
/// This RPC call does not affect the blockchain in any way.
///
- public RpcInvokeResult InvokeFunction(string scriptHash, string operation, RpcStack[] stacks)
+ public RpcInvokeResult InvokeFunction(string scriptHash, string operation, RpcStack[] stacks, params UInt160[] scriptHashesForVerifying)
{
- return RpcInvokeResult.FromJson(RpcSend("invokefunction", scriptHash, operation, stacks.Select(p => p.ToJson()).ToArray()));
+ List parameters = new List { scriptHash, operation, stacks.Select(p => p.ToJson()).ToArray() };
+ if (scriptHashesForVerifying.Length > 0)
+ {
+ parameters.Add(scriptHashesForVerifying.Select(p => (JObject)p.ToString()).ToArray());
+ }
+ return RpcInvokeResult.FromJson(RpcSend("invokefunction", parameters.ToArray()));
}
///
@@ -327,11 +332,11 @@ public RpcInvokeResult InvokeFunction(string scriptHash, string operation, RpcSt
///
public RpcInvokeResult InvokeScript(byte[] script, params UInt160[] scriptHashesForVerifying)
{
- List parameters = new List
+ List parameters = new List { script.ToHexString() };
+ if (scriptHashesForVerifying.Length > 0)
{
- script.ToHexString()
- };
- parameters.AddRange(scriptHashesForVerifying.Select(p => (JObject)p.ToString()));
+ parameters.Add(scriptHashesForVerifying.Select(p => (JObject)p.ToString()).ToArray());
+ }
return RpcInvokeResult.FromJson(RpcSend("invokescript", parameters.ToArray()));
}
diff --git a/src/RpcClient/TransactionManager.cs b/src/RpcClient/TransactionManager.cs
index 1ce09d6d9..f71482400 100644
--- a/src/RpcClient/TransactionManager.cs
+++ b/src/RpcClient/TransactionManager.cs
@@ -78,7 +78,6 @@ public TransactionManager MakeTransaction(byte[] script, TransactionAttribute[]
UInt160[] hashes = Tx.GetScriptHashesForVerifying(null);
RpcInvokeResult result = rpcClient.InvokeScript(script, hashes);
Tx.SystemFee = Math.Max(long.Parse(result.GasConsumed) - ApplicationEngine.GasFree, 0);
-
context = new ContractParametersContext(Tx);
signStore = new List();
@@ -141,6 +140,21 @@ public TransactionManager AddMultiSig(KeyPair key, int m, params ECPoint[] publi
return this;
}
+ ///
+ /// Add Multi-Signature
+ ///
+ /// The KeyPairs 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[] keys, int m, params ECPoint[] publicKeys)
+ {
+ foreach (var key in keys)
+ {
+ AddMultiSig(key, m, publicKeys);
+ }
+ return this;
+ }
+
private void AddSignItem(Contract contract, KeyPair key)
{
if (!Tx.GetScriptHashesForVerifying(null).Contains(contract.ScriptHash))
diff --git a/src/RpcClient/Utility.cs b/src/RpcClient/Utility.cs
index bf7c95fe2..e6a6aa847 100644
--- a/src/RpcClient/Utility.cs
+++ b/src/RpcClient/Utility.cs
@@ -76,7 +76,7 @@ public static UInt160 GetScriptHash(string account)
/// float value
/// token decimals
///
- internal static BigInteger ToBigInteger(this decimal amount, uint decimals)
+ public static BigInteger ToBigInteger(this decimal amount, uint decimals)
{
BigInteger factor = BigInteger.Pow(10, (int)decimals);
var (numerator, denominator) = Fraction(amount);
diff --git a/src/RpcClient/WalletAPI.cs b/src/RpcClient/WalletAPI.cs
index afb44e6d4..c00b6d188 100644
--- a/src/RpcClient/WalletAPI.cs
+++ b/src/RpcClient/WalletAPI.cs
@@ -1,3 +1,4 @@
+using Neo.Cryptography.ECC;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC.Models;
@@ -142,7 +143,7 @@ public Transaction Transfer(string tokenHash, string fromKey, string toAddress,
}
///
- /// Transfer NEP5 token balance
+ /// Transfer NEP5 token from single-sig account
///
/// contract script hash
/// from KeyPair
@@ -156,6 +157,23 @@ public Transaction Transfer(UInt160 scriptHash, KeyPair from, UInt160 to, BigInt
return transaction;
}
+ ///
+ /// Transfer NEP5 token from multi-sig account
+ ///
+ /// contract script hash
+ /// multi-sig min signature count
+ /// multi-sig pubKeys
+ /// sign keys
+ /// to account
+ /// transfer amount
+ ///
+ public Transaction Transfer(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger)
+ {
+ Transaction transaction = nep5API.CreateTransferTx(scriptHash, m, pubKeys, keys, to, amountInteger);
+ rpcClient.SendRawTransaction(transaction);
+ return transaction;
+ }
+
///
/// Wait until the transaction is observable block chain
///
diff --git a/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs
index 722c1bb04..976dbf0b9 100644
--- a/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs
+++ b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs
@@ -127,9 +127,10 @@ public void TestSign()
byte[] signature = tx.Witnesses[0].InvocationScript.Skip(2).ToArray();
Assert.IsTrue(Crypto.VerifySignature(tx.GetHashData(), signature, keyPair1.PublicKey.EncodePoint(false).Skip(1).ToArray()));
- // verify network fee
+ // verify network fee and system fee
long networkFee = tx.Size * (long)1000 + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null, null);
Assert.AreEqual(networkFee, tx.NetworkFee);
+ Assert.AreEqual(100, tx.SystemFee);
// duplicate sign should not add new witness
txManager.AddSignature(keyPair1).Sign();
diff --git a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs
index 72b83af8a..37a0cc181 100644
--- a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs
+++ b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs
@@ -19,12 +19,14 @@ public class UT_WalletAPI
string address1;
UInt160 sender;
WalletAPI walletAPI;
+ UInt160 multiSender;
[TestInitialize]
public void TestSetup()
{
keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"));
sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash();
+ multiSender = Contract.CreateMultiSigContract(1, keyPair1.PublicKey).ScriptHash;
address1 = Wallets.Helper.ToAddress(sender);
rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]);
walletAPI = new WalletAPI(rpcClientMock.Object);
@@ -104,6 +106,28 @@ public void TestTransfer()
Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString());
}
+ [TestMethod]
+ public void TestTransferfromMultiSigAccount()
+ {
+ byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", multiSender);
+ var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") };
+
+ UT_TransactionManager.MockInvokeScript(rpcClientMock, balanceScript, balanceResult);
+
+ 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", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100);
+ UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) });
+
+ var json = new JObject();
+ json["hash"] = UInt256.Zero.ToString();
+ rpcClientMock.Setup(p => p.RpcSend("sendrawtransaction", It.IsAny())).Returns(json);
+
+ var tranaction = walletAPI.Transfer(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100);
+ Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString());
+ }
+
[TestMethod]
public void TestWaitTransaction()
{