Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multi-sig-transfer #213

Merged
merged 11 commits into from
Apr 18, 2020
29 changes: 29 additions & 0 deletions src/RpcClient/Nep5API.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -117,5 +119,32 @@ public Transaction CreateTransferTx(UInt160 scriptHash, KeyPair fromKey, UInt160

return tx;
}

/// <summary>
/// Create NEP5 token transfer transaction from multi-sig account
/// </summary>
/// <param name="scriptHash">contract script hash</param>
/// <param name="m">multi-sig min signature count</param>
/// <param name="pubKeys">multi-sig pubKeys</param>
/// <param name="fromKeys">sign keys</param>
/// <param name="to">to account</param>
/// <param name="amount">transfer amount</param>
/// <returns></returns>
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;
}
}
}
17 changes: 11 additions & 6 deletions src/RpcClient/RpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
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<JObject> parameters = new List<JObject> { 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()));
}

/// <summary>
Expand All @@ -327,11 +332,11 @@ public RpcInvokeResult InvokeFunction(string scriptHash, string operation, RpcSt
/// </summary>
public RpcInvokeResult InvokeScript(byte[] script, params UInt160[] scriptHashesForVerifying)
{
List<JObject> parameters = new List<JObject>
List<JObject> parameters = new List<JObject> { 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()));
}

Expand Down
16 changes: 15 additions & 1 deletion src/RpcClient/TransactionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SignItem>();

Expand Down Expand Up @@ -141,6 +140,21 @@ public TransactionManager AddMultiSig(KeyPair key, int m, params ECPoint[] publi
return this;
}

/// <summary>
/// Add Multi-Signature
/// </summary>
/// <param name="keys">The KeyPairs to sign transction</param>
/// <param name="m">The least count of signatures needed for multiple signature contract</param>
/// <param name="publicKeys">The Public Keys construct the multiple signature contract</param>
public TransactionManager AddMultiSig(KeyPair[] keys, int m, params ECPoint[] publicKeys)
{
foreach (var key in keys)
{
AddMultiSig(key, m, publicKeys);
chenquanyu marked this conversation as resolved.
Show resolved Hide resolved
}
return this;
}

private void AddSignItem(Contract contract, KeyPair key)
{
if (!Tx.GetScriptHashesForVerifying(null).Contains(contract.ScriptHash))
Expand Down
2 changes: 1 addition & 1 deletion src/RpcClient/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static UInt160 GetScriptHash(string account)
/// <param name="amount">float value</param>
/// <param name="decimals">token decimals</param>
/// <returns></returns>
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);
Expand Down
20 changes: 19 additions & 1 deletion src/RpcClient/WalletAPI.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Neo.Cryptography.ECC;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC.Models;
Expand Down Expand Up @@ -142,7 +143,7 @@ public Transaction Transfer(string tokenHash, string fromKey, string toAddress,
}

/// <summary>
/// Transfer NEP5 token balance
/// Transfer NEP5 token from single-sig account
/// </summary>
/// <param name="scriptHash">contract script hash</param>
/// <param name="from">from KeyPair</param>
Expand All @@ -156,6 +157,23 @@ public Transaction Transfer(UInt160 scriptHash, KeyPair from, UInt160 to, BigInt
return transaction;
}

/// <summary>
/// Transfer NEP5 token from multi-sig account
/// </summary>
/// <param name="scriptHash">contract script hash</param>
/// <param name="m">multi-sig min signature count</param>
/// <param name="pubKeys">multi-sig pubKeys</param>
/// <param name="keys">sign keys</param>
/// <param name="to">to account</param>
/// <param name="amountInteger">transfer amount</param>
/// <returns></returns>
public Transaction Transfer(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger)
chenquanyu marked this conversation as resolved.
Show resolved Hide resolved
{
Transaction transaction = nep5API.CreateTransferTx(scriptHash, m, pubKeys, keys, to, amountInteger);
rpcClient.SendRawTransaction(transaction);
return transaction;
}

/// <summary>
/// Wait until the transaction is observable block chain
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
24 changes: 24 additions & 0 deletions tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<JObject>())).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()
{
Expand Down