Skip to content

Commit

Permalink
RpcClient: Check for NEP17 Transfer (#833)
Browse files Browse the repository at this point in the history
* added issue 775

* changed with output

* changed error to match error of spec

* fixed exception output

* fixed issue 775

* Added `.ConfigureAwait(false)` to `InvokeScriptAsync`

* Removed unused namespaces and fixed formatting

* fixed tests for CreateTransferTxAsync

* Code comment removed for invalid param

* Use Assert in script for nep-17 transfers

Revert "v3.6.1 (#831)"

This reverts commit 7b4145c.

* fixed tests

* Update Directory.Build.props

guess i went back to much

* Add argument

---------

Co-authored-by: Shargon <shargon@gmail.com>
Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 8, 2023
1 parent 4f55458 commit 35993a4
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 26 deletions.
14 changes: 10 additions & 4 deletions src/RpcClient/Nep17API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,18 @@ public async Task<RpcNep17TokenInfo> GetTokenInfoAsync(string contractHash)
/// <param name="to">to account script hash</param>
/// <param name="amount">transfer amount</param>
/// <param name="data">onPayment data</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns></returns>
public async Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, object data = null)
public async Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, object data = null, bool addAssert = true)
{
var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash();
Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } };
byte[] script = scriptHash.MakeScript("transfer", sender, to, amount, data);
if (addAssert) script = script.Concat(new[] { (byte)OpCode.ASSERT }).ToArray();

TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient);
TransactionManagerFactory factory = new(rpcClient);
TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false);

return await manager
.AddSignature(fromKey)
.SignAsync().ConfigureAwait(false);
Expand All @@ -156,17 +159,20 @@ public async Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, KeyPair
/// <param name="to">to account</param>
/// <param name="amount">transfer amount</param>
/// <param name="data">onPayment data</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns></returns>
public async Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount, object data = null)
public async Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount, object data = null, bool addAssert = true)
{
if (m > fromKeys.Length)
throw new ArgumentException($"Need at least {m} KeyPairs for signing!");
var sender = Contract.CreateMultiSigContract(m, pubKeys).ScriptHash;
Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } };
byte[] script = scriptHash.MakeScript("transfer", sender, to, amount, data);
if (addAssert) script = script.Concat(new[] { (byte)OpCode.ASSERT }).ToArray();

TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient);
TransactionManagerFactory factory = new(rpcClient);
TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false);

return await manager
.AddMultiSig(fromKeys, m, pubKeys)
.SignAsync().ConfigureAwait(false);
Expand Down
28 changes: 18 additions & 10 deletions src/RpcClient/WalletAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,26 @@ public Task<BigInteger> GetTokenBalanceAsync(string tokenHash, string account)
/// </summary>
/// <param name="key">wif or private key
/// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005")</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns>The transaction sended</returns>
public Task<Transaction> ClaimGasAsync(string key)
public Task<Transaction> ClaimGasAsync(string key, bool addAssert = true)
{
KeyPair keyPair = Utility.GetKeyPair(key);
return ClaimGasAsync(keyPair);
return ClaimGasAsync(keyPair, addAssert);
}

/// <summary>
/// The GAS is claimed when doing NEO transfer
/// This function will transfer NEO balance from account to itself
/// </summary>
/// <param name="keyPair">keyPair</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns>The transaction sended</returns>
public async Task<Transaction> ClaimGasAsync(KeyPair keyPair)
public async Task<Transaction> ClaimGasAsync(KeyPair keyPair, bool addAssert = true)
{
UInt160 toHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash();
BigInteger balance = await nep17API.BalanceOfAsync(NativeContract.NEO.Hash, toHash).ConfigureAwait(false);
Transaction transaction = await nep17API.CreateTransferTxAsync(NativeContract.NEO.Hash, keyPair, toHash, balance).ConfigureAwait(false);
Transaction transaction = await nep17API.CreateTransferTxAsync(NativeContract.NEO.Hash, keyPair, toHash, balance, null, addAssert).ConfigureAwait(false);
await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false);
return transaction;
}
Expand All @@ -139,16 +141,18 @@ public async Task<Transaction> ClaimGasAsync(KeyPair keyPair)
/// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005")</param>
/// <param name="toAddress">address or account script hash</param>
/// <param name="amount">token amount</param>
/// <param name="data">onPayment data</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns></returns>
public async Task<Transaction> TransferAsync(string tokenHash, string fromKey, string toAddress, decimal amount, object data = null)
public async Task<Transaction> TransferAsync(string tokenHash, string fromKey, string toAddress, decimal amount, object data = null, bool addAssert = true)
{
UInt160 scriptHash = Utility.GetScriptHash(tokenHash, rpcClient.protocolSettings);
var decimals = await nep17API.DecimalsAsync(scriptHash).ConfigureAwait(false);

KeyPair from = Utility.GetKeyPair(fromKey);
UInt160 to = Utility.GetScriptHash(toAddress, rpcClient.protocolSettings);
BigInteger amountInteger = amount.ToBigInteger(decimals);
return await TransferAsync(scriptHash, from, to, amountInteger, data).ConfigureAwait(false);
return await TransferAsync(scriptHash, from, to, amountInteger, data, addAssert).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -158,10 +162,12 @@ public async Task<Transaction> TransferAsync(string tokenHash, string fromKey, s
/// <param name="from">from KeyPair</param>
/// <param name="to">to account script hash</param>
/// <param name="amountInteger">transfer amount</param>
/// <param name="data">onPayment data</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns></returns>
public async Task<Transaction> TransferAsync(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger, object data = null)
public async Task<Transaction> TransferAsync(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger, object data = null, bool addAssert = true)
{
Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, from, to, amountInteger, data).ConfigureAwait(false);
Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, from, to, amountInteger, data, addAssert).ConfigureAwait(false);
await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false);
return transaction;
}
Expand All @@ -175,10 +181,12 @@ public async Task<Transaction> TransferAsync(UInt160 scriptHash, KeyPair from, U
/// <param name="keys">sign keys</param>
/// <param name="to">to account</param>
/// <param name="amountInteger">transfer amount</param>
/// <param name="data">onPayment data</param>
/// <param name="addAssert">Add assert at the end of the script</param>
/// <returns></returns>
public async Task<Transaction> TransferAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger, object data = null)
public async Task<Transaction> TransferAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger, object data = null, bool addAssert = true)
{
Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, m, pubKeys, keys, to, amountInteger, data).ConfigureAwait(false);
Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, m, pubKeys, keys, to, amountInteger, data, addAssert).ConfigureAwait(false);
await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false);
return transaction;
}
Expand Down
12 changes: 8 additions & 4 deletions tests/Neo.Network.RPC.Tests/UT_Nep17API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,20 @@ public async Task TestGetTokenInfo()
[TestMethod]
public async Task TestTransfer()
{
byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), null);
byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), null)
.Concat(new[] { (byte)OpCode.ASSERT })
.ToArray();
UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter());

var client = rpcClientMock.Object;
var result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000));
var result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), null, true);

testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), string.Empty);
testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), string.Empty)
.Concat(new[] { (byte)OpCode.ASSERT })
.ToArray();
UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter());

result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), string.Empty);
result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), string.Empty, true);
Assert.IsNotNull(result);
}
}
Expand Down
23 changes: 15 additions & 8 deletions tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;

Expand Down Expand Up @@ -89,7 +90,7 @@ public async Task TestClaimGas()
json["hash"] = UInt256.Zero.ToString();
rpcClientMock.Setup(p => p.RpcSendAsync("sendrawtransaction", It.IsAny<JToken>())).ReturnsAsync(json);

var tranaction = await walletAPI.ClaimGasAsync(keyPair1.Export());
var tranaction = await walletAPI.ClaimGasAsync(keyPair1.Export(), false);
Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString());
}

Expand All @@ -99,14 +100,16 @@ public async Task 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, null);
byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, NativeContract.GAS.Factor * 100, null)
.Concat(new[] { (byte)OpCode.ASSERT })
.ToArray();
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.RpcSendAsync("sendrawtransaction", It.IsAny<JToken>())).ReturnsAsync(json);

var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash.ToString(), keyPair1.Export(), UInt160.Zero.ToAddress(client.protocolSettings.AddressVersion), 100);
var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash.ToString(), keyPair1.Export(), UInt160.Zero.ToAddress(client.protocolSettings.AddressVersion), 100, null, true);
Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString());
}

Expand All @@ -121,30 +124,34 @@ public async Task TestTransferfromMultiSigAccount()
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, null);
byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, null)
.Concat(new[] { (byte)OpCode.ASSERT })
.ToArray();
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.RpcSendAsync("sendrawtransaction", It.IsAny<JToken>())).ReturnsAsync(json);

var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100);
var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, null, true);
Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString());

try
{
tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 2, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100);
tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 2, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, null, true);
Assert.Fail();
}
catch (System.Exception e)
{
Assert.AreEqual(e.Message, $"Need at least 2 KeyPairs for signing!");
}

testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty);
testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty)
.Concat(new[] { (byte)OpCode.ASSERT })
.ToArray();
UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) });

tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty);
tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty, true);
Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString());
}

Expand Down

0 comments on commit 35993a4

Please sign in to comment.