This commit is contained in:
Georgios Konstantopoulos 2020-05-22 21:37:21 +03:00
commit 1a49a62a81
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
11 changed files with 327 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "ethers"
version = "0.1.0"
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
edition = "2018"
[dependencies]
solc = { git = "https://github.com/paritytech/rust_solc "}
ethereum-types = "0.9.2"
url = "2.1.1"
once_cell = "1.4.0"
async-trait = "0.1.31"
reqwest = { version = "0.10.4", features = ["json"] }
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.53"
thiserror = "1.0.19"
[dev-dependencies]
tokio = { version = "0.2.21", features = ["macros"] }

44
README.md Normal file
View File

@ -0,0 +1,44 @@
Provider model
should be usable both for tests and for normal actions
Features
- [ ] Keep your private keys in your client, safe and sound
- [ ] Import and export JSON wallets (Geth, Parity and crowdsale)
- [ ] Import and export BIP 39 mnemonic phrases (12 word backup phrases) and HD Wallets (English, Italian, Japanese, Korean, Simplified Chinese, Traditional Chinese; more coming soon)
- [ ] Meta-classes create JavaScript objects from any contract ABI, including ABIv2 and Human-Readable ABI
- [ ] Connect to Ethereum nodes over JSON-RPC, INFURA, Etherscan, Alchemy, Cloudflare or MetaMask.
- [ ] ENS names are first-class citizens; they can be used anywhere an Ethereum addresses can be used
- [ ] Tiny (~88kb compressed; 284kb uncompressed)
- [ ] Complete functionality for all your Ethereum needs
- [ ] Extensive documentation
- [ ] Large collection of test cases which are maintained and added to
- [ ] Fully TypeScript ready, with definition files and full TypeScript source
- [ ] MIT License (including ALL dependencies); completely open source to do with as you please
- [ ] Compatible with ethers.js and Metamask web3 providers via WASM
- Calls by default are made async -> provide a synchronous API
- Provider
- Signer
- Contract
- Choice of BigNumber library? Probably the ones in ethabi
- Human readable ABI very important
- https://docs-beta.ethers.io/getting-started/
- Supports IN-EVM Testing -> SUPER fast tests which are ALSO typesafe
- build.rs type safe methods
This library is inspired by the APIs of Riemann Ether and Ethers https://github.com/summa-tx/riemann-ether#development
- Listening to events via HTTP polls, while WS push/pulls -> look at rust-web3
- Async std futures 1.0 RPC calls for everything `rpc_impl!` using reqwest and mockito
https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/contracts/src.ts/index.ts#L721
- Wallet
ethers::wallet::new().connect(provider)
::get_default_provider()

15
examples/transfer_eth.rs Normal file
View File

@ -0,0 +1,15 @@
use ethers::providers::{Provider, ProviderTrait};
use ethers::wallet::Signer;
use std::convert::TryFrom;
#[tokio::main]
async fn main() {
let provider =
Provider::try_from("https://mainnet.infura.io/v3/4aebe67796c64b95ab20802677b7bb55")
.unwrap();
let num = provider.get_block_number().await.unwrap();
dbg!(num);
// let signer = Signer::random().connect(&provider);
}

138
src/jsonrpc.rs Normal file
View File

@ -0,0 +1,138 @@
//! Minimal JSON-RPC 2.0 Client
//! The request/response code is taken from [here](https://github.com/althea-net/guac_rs/blob/master/web3/src/jsonrpc)
use reqwest::{Client, Error as ReqwestError};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use thiserror::Error;
use url::Url;
#[derive(Debug)]
/// JSON-RPC 2.0 Client
pub struct HttpClient {
id: AtomicU64,
client: Client,
url: Url,
}
impl HttpClient {
/// Initializes a new HTTP Client
pub fn new(url: impl Into<Url>) -> Self {
Self {
id: AtomicU64::new(0),
client: Client::new(),
url: url.into(),
}
}
/// Sends a POST request with the provided method and the params serialized as JSON
pub async fn request<T: Serialize, R: for<'a> Deserialize<'a>>(
&self,
method: &str,
params: Option<T>,
) -> Result<R, ClientError> {
let next_id = self.id.load(Ordering::SeqCst) + 1;
self.id.store(next_id, Ordering::SeqCst);
let payload = Request::new(next_id, method, params);
let res = self
.client
.post(self.url.as_ref())
.json(&payload)
.send()
.await?;
let res = res.json::<Response<R>>().await?;
Ok(res.data.into_result()?)
}
}
#[derive(Error, Debug)]
pub enum ClientError {
#[error(transparent)]
ReqwestError(#[from] ReqwestError),
#[error(transparent)]
JsonRpcError(#[from] JsonRpcError),
}
#[derive(Serialize, Deserialize, Debug, Clone, Error)]
/// A JSON-RPC 2.0 error
pub struct JsonRpcError {
/// The error code
pub code: i64,
/// The error message
pub message: String,
/// Additional data
pub data: Option<Value>,
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"(code: {}, message: {}, data: {:?})",
self.code, self.message, self.data
)
}
}
#[derive(Serialize, Deserialize, Debug)]
/// A JSON-RPC request
struct Request<'a, T> {
id: u64,
jsonrpc: &'a str,
method: &'a str,
params: Option<T>,
}
impl<'a, T> Request<'a, T> {
/// Creates a new JSON RPC request
fn new(id: u64, method: &'a str, params: Option<T>) -> Self {
Self {
id,
jsonrpc: "2.0",
method,
params,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Response<T> {
id: u64,
jsonrpc: String,
#[serde(flatten)]
data: ResponseData<T>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
enum ResponseData<R> {
Error { error: JsonRpcError },
Success { result: R },
}
impl<R> ResponseData<R> {
/// Consume response and return value
fn into_result(self) -> Result<R, JsonRpcError> {
match self {
ResponseData::Success { result } => Ok(result),
ResponseData::Error { error } => Err(error),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn response() {
let response: Response<u64> =
serde_json::from_str(r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#).unwrap();
assert_eq!(response.id, 1);
assert_eq!(response.data.into_result().unwrap(), 19);
}
}

16
src/lib.rs Normal file
View File

@ -0,0 +1,16 @@
//! ethers-rs
//!
//! ethers-rs is a port of [ethers-js](github.com/ethers-io/ethers.js) in Rust.
mod network;
pub mod providers;
pub mod wallet;
pub mod primitives;
mod jsonrpc;
/// Re-export solc
pub use solc;

18
src/network.rs Normal file
View File

@ -0,0 +1,18 @@
/// Parameters for instantiating a network
use ethereum_types::Address;
trait Network {
const NAME: &'static str;
const CHAIN_ID: u32;
const ENS: Option<Address>;
}
#[derive(Clone, Debug)]
pub struct Mainnet;
impl Network for Mainnet {
const NAME: &'static str = "mainnet";
const CHAIN_ID: u32 = 1;
// TODO: Replace with ENS address
const ENS: Option<Address> = None;
}

2
src/primitives.rs Normal file
View File

@ -0,0 +1,2 @@
/// A signature;
pub struct Signature([u8; 65]);

50
src/providers.rs Normal file
View File

@ -0,0 +1,50 @@
use crate::jsonrpc::{ClientError, HttpClient};
use async_trait::async_trait;
use ethereum_types::U256;
use std::convert::TryFrom;
use url::{ParseError, Url};
/// An Ethereum JSON-RPC compatible backend
pub struct Provider(HttpClient);
impl From<HttpClient> for Provider {
fn from(src: HttpClient) -> Self {
Self(src)
}
}
impl TryFrom<&str> for Provider {
type Error = ParseError;
fn try_from(src: &str) -> Result<Self, Self::Error> {
Ok(Provider(HttpClient::new(Url::parse(src)?)))
}
}
#[async_trait]
impl ProviderTrait for Provider {
type Error = ClientError;
async fn get_block_number(&self) -> Result<U256, Self::Error> {
self.0.request("eth_blockNumber", None::<()>).await
}
}
#[async_trait]
pub trait ProviderTrait {
type Error;
async fn get_block_number(&self) -> Result<U256, Self::Error>;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn get_balance() {
let provider = Provider::try_from("http://localhost:8545").unwrap();
let num = provider.get_block_number().await.unwrap();
assert_eq!(num, U256::from(0));
}
}

0
src/transaction.rs Normal file
View File

23
src/wallet.rs Normal file
View File

@ -0,0 +1,23 @@
use crate::{primitives::Signature, providers::Provider};
pub struct Signer<'a> {
provider: Option<&'a Provider>,
}
impl<'a> Signer<'a> {
pub fn random() -> Self {
Signer { provider: None }
}
pub fn connect(mut self, provider: &'a Provider) -> Self {
self.provider = Some(provider);
self
}
}
trait SignerC {
/// Connects to a provider
fn connect<'a>(self, provider: &'a Provider) -> Self;
fn sign_message(message: &[u8]) -> Signature;
}