The Arcadia Group security audit
EthSmartContractIO
is a .NET library aimed at simplifying the interaction with Ethereum smart contracts. It allows developers to execute actions on the Ethereum network by wrapping the complexity of Ethereum RPC calls into a more manageable, high-level API.
Navigation of EthSmartContractIO
Navigation of EthSmartContractIO.AccountProvider
Navigation of EthSmartContractIO.SecretsProvider
To use EthSmartContractIO
, you will need to add it as a dependency to your project.
You can do this by adding it as a NuGet package:
.NET CLI
dotnet add package EthSmartContractIO
Package Manager
Install-Package EthSmartContractIO
RpcRequest
is the main object that is used to communicate with the Ethereum network.
It represents a request to either read data from a smart contract or write data to a smart contract.
A Read request is created by calling the constructor RpcRequest(string rpcUrl, string to, string data)
.
This type of request doesn't require a transaction to be mined and therefore doesn't require gas settings or a value.
var readRequest = new RpcRequest(
rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID",
to: "0xYourContractAddress",
data: "0xYourData"
);
A Write request is created by calling the constructor RpcRequest(string rpcUrl, string to, WriteRpcRequest writeRequest, string? data = null)
.
A Write request can call contract write methods, and send money to other account
This type of request creates a transaction that needs to be mined, which requires gas settings and potentially a value.
Call contract example:
var writeRequest = new RpcRequest(
rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID",
to: "0xYourContractAddress",
writeRequest: new WriteRpcRequest(
chainId: 1,
value: new HexBigInteger(1000000000000000),
gasSettings: new GasSettings(maxGasLimit: 31000, maxGweiGasPrice: 20),
accountProvider: new YourIAccountProvider() // The class that implements the interface 'IAccountProvider'
),
data: "0xYourData" // Optional parameter, set if need to call contract method
);
Send money example:
var writeRequest = new RpcRequest(
rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID",
to: "0xOtherAccountAddress",
writeRequest: new WriteRpcRequest(
chainId: 1,
value: new HexBigInteger("0xDE0B6B3A7640000"),
gasSettings: new GasSettings(maxGasLimit: 31000, maxGweiGasPrice: 20),
accountProvider: new YourIAccountProvider() // The class that implements the interface 'IAccountProvider'
)
);
GasSettings
is a simple class that holds the GasPrice
and GasLimit
settings for a write request.
Gas is the mechanism that Ethereum uses to allocate resources among nodes.
var gasSettings = new GasSettings(maxGasLimit: 31000, maxGweiGasPrice: 20);
IAccountProvider
is an interface that should be implemented by the class responsible for providing an Ethereum account.
The Ethereum account holds a private key that will be used to sign the Ethereum transaction.
The Account
property is expected to return an instance of the Account
object from the Nethereum.Web3.Accounts
namespace.
Here is an example of how to implement this interface:
using Nethereum.Web3.Accounts;
using EthSmartContractIO.Providers;
public class MyAccountProvider : IAccountProvider
{
public Account Account => new Account("0x4e3c79ee2f53da4e456cb13887f4a7d59488677e9e48b6fb6701832df828f7e9");
}
In this example, we're providing an Ethereum account with a specific private key. Please note that hardcoding private keys is not a good practice, this is just an illustrative example. Always store private keys in a secure manner.
ContractIO
is the main class that interfaces with your Ethereum node.
You can use it to execute actions (read or write) on the Ethereum network.
// Initialize the ContractIO object
var contractIO = new ContractIO();
// Execute the action
var result = contractIO.ExecuteAction(myRpcRequest);
// Handle the result
Console.WriteLine(result);
In the example above, ContractIO
uses the strategy pattern to decide which class (ContractReader
for read operations and ContractWriter
for write operations) to use to execute the action.
Here's a simple example of how to read data from a smart contract using this library:
// Create a read request
var readRequest = new RpcRequest(
rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID",
to: "0xYourContractAddress",
data: "0xYourData"
);
// Initialize the ContractIO object
var contractIO = new ContractIO();
// Execute the action
var result = contractIO.ExecuteAction(readRequest);
// Handle the result
Console.WriteLine(result);
And here's how to write data to a smart contract:
// Initialize your account provider
var myAccountProvider = new MyAccountProvider();
// Create a write request
var writeRequest = new RpcRequest(
rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID",
to: "0xYourContractAddress",
writeRequest: new WriteRpcRequest(
chainId: 1,
value: new HexBigInteger(10000000000000000),
gasSettings: new GasSettings(30000, 6),
accountProvider: myAccountProvider
),
data: "0xYourData"
);
// Initialize the ContractIO object
var contractIO = new ContractIO();
// Execute the action
var result = contractIO.ExecuteAction(writeRequest);
// Handle the result
Console.WriteLine(result);
With EthSmartContractIO
, you're not limited to the default implementation of certain interfaces like IGasPricer
, ITransactionSigner
, and ITransactionSender
.
You can provide your own custom implementations and inject them into the ContractIO
class using the IServiceProvider
property.
Here are the interfaces you can replace:
This interface is responsible for providing the current gas price. If you have a custom strategy for gas price, you can implement this interface and provide your own method for getting the current gas price.
public interface IGasPricer
{
HexBigInteger GetCurrentWeiGasPrice();
}
This interface is responsible for signing transactions. If you have a custom transaction signing method, you can implement this interface and provide your own transaction signing logic.
public interface ITransactionSigner
{
string SignTransaction(TransactionInput transaction);
}
This interface is responsible for sending transactions to the Ethereum network. If you have a custom method for sending transactions, you can implement this interface and provide your own transaction sending logic.
public interface ITransactionSender
{
string SendTransaction(string signedTransaction);
}
To use your custom implementations, you need to provide an IServiceProvider
instance to the ContractIO
class that contains your implementations.
You can use the ServiceProviderBuilder
class from the EthSmartContractIO.Builders
namespace to create an IServiceProvider
object.
Here is a sample usage of ServiceProviderBuilder
:
// Add your custom implementations
var serviceProvider = new ServiceProviderBuilder()
.AddGasPricer(new MyCustomGasPricer())
.AddTransactionSigner(new MyCustomTransactionSigner())
.AddTransactionSender(new MyCustomTransactionSender())
.Build();
// Create a ContractRpc instance and inject your custom implementations
var contractIO = new ContractIO(serviceProvider)
In the example above, MyCustomGasPricer
, MyCustomTransactionSigner
, and MyCustomTransactionSender
are your custom implementations of the IGasPricer
, ITransactionSigner
, and ITransactionSender
interfaces, respectively.
By using this approach, you can easily customize the behavior of the library to suit your specific needs.
Testing is a critical part of software development and ensures the reliability and accuracy of your code. EthSmartContractIO's architecture allows it to be tested in a couple of ways:
The ExecuteAction
method in the ContractIO
class is marked as virtual
.
This allows the method to be overridden in a subclass, which is useful for testing scenarios.
You can use mocking libraries such as Moq
to mock the method's behavior.
Here's an example using Moq
:
var contractIOMock = new Mock<ContractIO> { CallBase = true };
contractIOMock
.Setup(x => x.ExecuteAction(It.IsAny<RpcRequest>()))
.Returns("YourMockedResult");
// Now when you call ExecuteAction, it will return "YourMockedResult"
var result = contractIOMock.Object.ExecuteAction(someRpcRequest);
Assert.Equal("YourMockedResult", result);
In this example, regardless of what RpcRequest
you pass to ExecuteAction
, it will always return the string "YourMockedResult"
.
Alternatively, you can mock the IWeb3
object and pass it to ContractIO
using the ServiceProvider
.
The ServiceProviderBuilder
class provides an AddWeb3
method for this purpose.
Here's an example of how to mock IWeb3
using Moq
and inject it using ServiceProviderBuilder
:
var web3Mock = new Mock<IWeb3>();
// Setup your web3Mock...
var serviceProvider = new ServiceProviderBuilder()
.AddWeb3(web3Mock.Object)
.Build();
var contractIO = new ContractIO(serviceProvider)
In this example, ContractIO
will use your mocked IWeb3
object, allowing you to control its behavior for testing.
By leveraging these strategies, you can create comprehensive unit tests for your code that interacts with the EthSmartContractIO library. This ensures the correct behavior of your Ethereum interactions.
When testing read requests, you might want to simulate the HTTP calls made by the application.
For this purpose, you can use Flurl.Http.Testing
, a library that provides testing utilities for HTTP calls made using Flurl
.
This library provides an HttpTest
class that can be used to set up expectations for HTTP calls and provide predefined responses.
Here's an example of how to use HttpTest for testing read requests:
using Flurl.Http.Testing;
// Setup expectations and response
string rpcUrl = "http://your_rpc_url_here";
string response = "{ your_json_rpc_response_here }";
// Start an HttpTest
using var httpTest = new HttpTest();
httpTest
.ForCallsTo(RpcUrl)
.RespondWithJson(response);
// Now when ContractIO executes a read request, it will receive your predefined response
var contractIO = new ContractIO();
var result = contractIO.ExecuteAction(readRequest);
// You can assert the expected output based on your predefined response
// This will vary based on the structure of your JSON RPC response
The EthSmartContractIO.AccountProvider
is a NuGet package that provides various account providers for Ethereum based smart contract I/O operations.
The package includes two account providers: MnemonicAccountProvider
and PrivateKeyAccountProvider
.
To use EthSmartContractIO.AccountProvider
, you will need to add it as a dependency to your project.
You can do this by adding it as a NuGet package:
.NET CLI
dotnet add package EthSmartContractIO.AccountProvider
Package Manager
Install-Package EthSmartContractIO.AccountProvider
The package provides different classes for account management.
These classes implement the IAccountProvider
interface, providing flexibility and support for different Ethereum account types.
MnemonicAccountProvider
is a class that generates an Ethereum account using a mnemonic (a list of words) that represents a wallet's private key.
This class provides two constructors:
-
The first constructor accepts the mnemonic words, account ID, chain ID and optionally, a password for the seed. It generates an account corresponding to the account ID from the given mnemonic.
-
The second constructor uses an instance of
ISecretsProvider
to obtain the mnemonic words. This can be useful when you have a secure way to store and retrieve the mnemonic words and want to keep them separate from your main application code.
using Nethereum.HdWallet;
using Nethereum.Hex.HexTypes;
using Nethereum.Web3.Accounts;
using EthSmartContractIO.Providers;
namespace EthSmartContractIO.AccountProvider;
public class MnemonicAccountProvider : IAccountProvider
{
public Account Account { get; private set; }
public MnemonicAccountProvider(string mnemonicWords, uint accountId, uint chainId, string seedPassword = "")
{
var wallet = new Wallet(words: mnemonicWords, seedPassword: seedPassword);
Account = wallet.GetAccount((int)accountId, new HexBigInteger(chainId));
}
public MnemonicAccountProvider(ISecretsProvider secretsProvider, uint accountId, uint chainId, string seedPassword = "")
: this(secretsProvider.Secret, accountId, chainId, seedPassword)
{ }
}
The PrivateKeyAccountProvider
is a class that generates an Ethereum account using a private key.
This class provides two constructors:
-
The first constructor accepts a private key and a chain ID. It generates an account from the given private key for the specified chain.
-
The second constructor uses an instance of
ISecretsProvider
to obtain the private key. This can be useful when you have a secure way to store and retrieve the private key and want to keep it separate from your main application code.
Here is the code for PrivateKeyAccountProvider:
using Nethereum.Hex.HexTypes;
using Nethereum.Web3.Accounts;
using EthSmartContractIO.Providers;
namespace EthSmartContractIO.AccountProvider;
public class PrivateKeyAccountProvider : IAccountProvider
{
public Account Account { get; private set; }
public PrivateKeyAccountProvider(string privateKey, uint chainId)
{
Account = new Account(privateKey, new HexBigInteger(chainId));
}
public PrivateKeyAccountProvider(ISecretsProvider secretsProvider, uint chainId)
: this(secretsProvider.Secret, chainId)
{ }
}
EthSmartContractIO.SecretsProvider
is a NuGet package that provides a simple way to manage secrets in your Ethereum-based applications.
It currently includes one secrets provider: EnvironmentSecretProvider
.
To use EthSmartContractIO.SecretsProvider
, you will need to add it as a dependency to your project.
You can do this by adding it as a NuGet package:
.NET CLI
dotnet add package EthSmartContractIO.SecretsProvider
Package Manager
Install-Package EthSmartContractIO.SecretsProvider
The package provides a class for secret management.
This class implements the ISecretsProvider
interface, providing flexibility for different secret management mechanisms.
EnvironmentSecretProvider
is a class that retrieves a secret (such as a private key or a mnemonic) from AWS Secrets Manager.
Here is the code of EnvironmentSecretProvider
:
using SecretsManager;
using EnvironmentManager;
using EthSmartContractIO.Providers;
namespace EthSmartContractIO.SecretsProvider;
public class EnvironmentSecretProvider : ISecretsProvider
{
private static string SecretId =>
EnvManager.GetEnvironmentValue<string>("SECRET_ID", raiseException: true);
private static string SecretKey =>
EnvManager.GetEnvironmentValue<string>("SECRET_KEY", raiseException: true);
private readonly SecretManager secretManager;
public EnvironmentSecretProvider(SecretManager? secretManager = null)
{
this.secretManager = secretManager ?? new SecretManager();
}
public virtual string Secret => secretManager.GetSecretValue(SecretId, SecretKey);
}
The EnvironmentSecretProvider
uses the EnvManager
to retrieve the environment variables SECRET_ID
and SECRET_KEY
.
These are used as identifiers to fetch the actual secret from the AWS Secrets Manager via SecretManager
.
We welcome contributions from the community. Please submit pull requests for bug fixes, improvements and new features.
Happy coding!