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

private key account tests #12

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
349 changes: 349 additions & 0 deletions Thirdweb.Tests/Thirdweb.PrivateKeyAccount.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
using System.Numerics;
using Nethereum.Hex.HexTypes;
using Nethereum.RPC.Eth.DTOs;

namespace Thirdweb.Tests;

public class PrivateKeyAccountTests : BaseTests
{
public PrivateKeyAccountTests(ITestOutputHelper output)
: base(output) { }

private PrivateKeyAccount GetAccount()
{
var client = new ThirdwebClient(secretKey: _secretKey);
var privateKeyAccount = new PrivateKeyAccount(client: client, privateKeyHex: _testPrivateKey);
return privateKeyAccount;
}

[Fact]
public void Initialization_Success()
{
var account = GetAccount();
Assert.NotNull(account);
}

[Fact]
public void Initialization_NullPrivateKey()
{
var client = new ThirdwebClient(secretKey: _secretKey);
var ex = Assert.Throws<ArgumentNullException>(() => new PrivateKeyAccount(client, null));
Assert.Equal("Private key cannot be null or empty. (Parameter 'privateKeyHex')", ex.Message);
}

[Fact]
public async Task Connect()
{
var account = GetAccount();
await account.Connect();
Assert.True(await account.IsConnected());
}

[Fact]
public async Task GetAddress()
{
var account = GetAccount();
await account.Connect();
var address = await account.GetAddress();
Assert.True(address.Length == 42);
}

[Fact]
public async Task EthSign_Success()
{
var account = GetAccount();
await account.Connect();
var message = "Hello, World!";
var signature = await account.EthSign(message);
Assert.True(signature.Length == 132);
}

[Fact]
public async Task EthSign_NullMessage()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.EthSign(null));
Assert.Equal("Message to sign cannot be null. (Parameter 'message')", ex.Message);
}

[Fact]
public async Task PersonalSign_Success()
{
var account = GetAccount();
await account.Connect();
var message = "Hello, World!";
var signature = await account.PersonalSign(message);
Assert.True(signature.Length == 132);
}

[Fact]
public async Task PersonalSign_EmptyMessage()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.PersonalSign(string.Empty));
Assert.Equal("Message to sign cannot be null. (Parameter 'message')", ex.Message);
}

[Fact]
public async Task PersonalSign_NullyMessage()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.PersonalSign(null as string));
Assert.Equal("Message to sign cannot be null. (Parameter 'message')", ex.Message);
}

[Fact]
public async Task PersonalSignRaw_Success()
{
var account = GetAccount();
await account.Connect();
var message = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var signature = await account.PersonalSign(message);
Assert.True(signature.Length == 132);
}

[Fact]
public async Task PersonalSignRaw_NullMessage()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.PersonalSign(null as byte[]));
Assert.Equal("Message to sign cannot be null. (Parameter 'rawMessage')", ex.Message);
}

[Fact]
public async Task SignTypedDataV4_Success()
{
var account = GetAccount();
await account.Connect();
var json =
"{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":1,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Cow\",\"wallet\":\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbBBbBbbBbBbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}}";
var signature = await account.SignTypedDataV4(json);
Assert.True(signature.Length == 132);
}

[Fact]
public async Task SignTypedDataV4_NullJson()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.SignTypedDataV4(null));
Assert.Equal("Json to sign cannot be null. (Parameter 'json')", ex.Message);
}

[Fact]
public async Task SignTypedDataV4_EmptyJson()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.SignTypedDataV4(string.Empty));
Assert.Equal("Json to sign cannot be null. (Parameter 'json')", ex.Message);
}

[Fact]
public async Task SignTypedDataV4_Typed()
{
var account = GetAccount();
await account.Connect();
var typedData = EIP712.GetTypedDefinition_SmartAccount_AccountMessage("Account", "1", 421614, await account.GetAddress());
var accountMessage = new AccountAbstraction.AccountMessage { Message = System.Text.Encoding.UTF8.GetBytes("Hello, world!") };
var signature = await account.SignTypedDataV4(accountMessage, typedData);
Assert.True(signature.Length == 132);
}

[Fact]
public async Task SignTypedDataV4_Typed_NullData()
{
var account = GetAccount();
await account.Connect();
var typedData = EIP712.GetTypedDefinition_SmartAccount_AccountMessage("Account", "1", 421614, await account.GetAddress());
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.SignTypedDataV4(null as string, typedData));
Assert.Equal("Data to sign cannot be null. (Parameter 'data')", ex.Message);
}

[Fact]
public async Task SignTransaction_Success()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
// Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
GasPrice = new HexBigInteger(10000000000)
};
var signature = await account.SignTransaction(transaction, 421614);
Assert.NotNull(signature);
}

[Fact]
public async Task SignTransaction_NullTransaction()
{
var account = GetAccount();
await account.Connect();
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.SignTransaction(null, 421614));
Assert.Equal("Value cannot be null. (Parameter 'transaction')", ex.Message);
}

[Fact]
public async Task SignTransaction_NoNonce()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x"
};
var ex = await Assert.ThrowsAsync<ArgumentNullException>(() => account.SignTransaction(transaction, 421614));
Assert.Equal("Transaction nonce has not been set (Parameter 'transaction')", ex.Message);
}

[Fact]
public async Task SignTransaction_WrongFrom()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = Constants.ADDRESS_ZERO,
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999)
};
var ex = await Assert.ThrowsAsync<Exception>(() => account.SignTransaction(transaction, 421614));
Assert.Equal("Transaction 'From' address does not match the wallet address", ex.Message);
}

[Fact]
public async Task SignTransaction_NoGasPrice()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999)
};
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => account.SignTransaction(transaction, 421614));
Assert.Equal("Transaction gas price must be set for legacy transactions", ex.Message);
}

[Fact]
public async Task SignTransaction_1559_Success()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
Type = new HexBigInteger(2),
MaxFeePerGas = new HexBigInteger(10000000000),
MaxPriorityFeePerGas = new HexBigInteger(10000000000)
};
var signature = await account.SignTransaction(transaction, 421614);
Assert.NotNull(signature);
}

[Fact]
public async Task SignTransaction_1559_NoMaxFeePerGas()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
Type = new HexBigInteger(2),
MaxPriorityFeePerGas = new HexBigInteger(10000000000)
};
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => account.SignTransaction(transaction, 421614));
Assert.Equal("Transaction MaxPriorityFeePerGas and MaxFeePerGas must be set for EIP-1559 transactions", ex.Message);
}

[Fact]
public async Task SignTransaction_1559_NoMaxPriorityFeePerGas()
{
var account = GetAccount();
await account.Connect();
var transaction = new TransactionInput
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
Type = new HexBigInteger(2),
MaxFeePerGas = new HexBigInteger(10000000000)
};
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => account.SignTransaction(transaction, 421614));
Assert.Equal("Transaction MaxPriorityFeePerGas and MaxFeePerGas must be set for EIP-1559 transactions", ex.Message);
}

[Fact]
public async Task IsConnected_True()
{
var account = GetAccount();
await account.Connect();
Assert.True(await account.IsConnected());
}

[Fact]
public async Task IsConnected_False()
{
var account = GetAccount();
Assert.False(await account.IsConnected());
}

[Fact]
public async Task Disconnect()
{
var account = GetAccount();
await account.Connect();
await account.Disconnect();
Assert.False(await account.IsConnected());
}

[Fact]
public async Task Disconnect_NotConnected()
{
var account = GetAccount();
await account.Disconnect();
Assert.False(await account.IsConnected());
}

[Fact]
public async Task Disconnect_Connected()
{
var account = GetAccount();
await account.Connect();
await account.Disconnect();
Assert.False(await account.IsConnected());
}
}
12 changes: 10 additions & 2 deletions Thirdweb/Thirdweb.Wallets/PrivateKeyAccount/PrivateKeyAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public Task<string> PersonalSign(byte[] rawMessage)

public Task<string> PersonalSign(string message)
{
if (message == null)
if (string.IsNullOrEmpty(message))
{
throw new ArgumentNullException(nameof(message), "Message to sign cannot be null.");
}
Expand All @@ -80,7 +80,7 @@ public Task<string> PersonalSign(string message)

public Task<string> SignTypedDataV4(string json)
{
if (json == null)
if (string.IsNullOrEmpty(json))
{
throw new ArgumentNullException(nameof(json), "Json to sign cannot be null.");
}
Expand Down Expand Up @@ -127,6 +127,10 @@ public async Task<string> SignTransaction(TransactionInput transaction, BigInteg
string signedTransaction;
if (transaction.Type != null && transaction.Type.Value == TransactionType.EIP1559.AsByte())
{
if (transaction.MaxPriorityFeePerGas == null || transaction.MaxFeePerGas == null)
{
throw new InvalidOperationException("Transaction MaxPriorityFeePerGas and MaxFeePerGas must be set for EIP-1559 transactions");
}
var maxPriorityFeePerGas = transaction.MaxPriorityFeePerGas.Value;
var maxFeePerGas = transaction.MaxFeePerGas.Value;
var transaction1559 = new Transaction1559(
Expand All @@ -147,6 +151,10 @@ public async Task<string> SignTransaction(TransactionInput transaction, BigInteg
}
else
{
if (transaction.GasPrice == null)
{
throw new InvalidOperationException("Transaction gas price must be set for legacy transactions");
}
var gasPrice = transaction.GasPrice;
var legacySigner = new LegacyTransactionSigner();
signedTransaction = legacySigner.SignTransaction(_ecKey.GetPrivateKey(), chainId, transaction.To, value.Value, nonce, gasPrice.Value, gasLimit.Value, transaction.Data);
Expand Down
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore:
- "Thirdweb/Thirdweb.Wallets/EmbeddedAccount"
Loading