Skip to content

Commit

Permalink
private key account tests (#12)
Browse files Browse the repository at this point in the history
* private key account tests

* Update Thirdweb.PrivateKeyAccount.Tests.cs

* Update dotnet-ci.yml

* ignore ews
  • Loading branch information
0xFirekeeper authored Apr 8, 2024
1 parent 2ff1a05 commit a195fcc
Show file tree
Hide file tree
Showing 3 changed files with 361 additions and 2 deletions.
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"

0 comments on commit a195fcc

Please sign in to comment.