feat: wasm support (#390)

* feat: partial wasm32 support

* make compile for wasm32

* feat: att async-trait attr

* make compatible with wasm

* add type alias for archs

* rustfmt

* add wasm ci

* make compile with wasm-pack test

* make compile with wasm-pack test

* make compile with wasm-pack test

* make compile with wasm-pack test

* ci: disable wasmpack

* feat: use wasm timer delay

* feat: add wasm provider

* rustfmt

* misc refactor

* add wasm example

* make example a directory

* untrack error log

* move profile to root

* fix unused imports

* ci: enable wasm-pack test

* style: unify websocket implementations

* fix: typos

* fix: make policy compatible with wasm target

* chore: do not include ethers-wasm example as top level workspace member

* chore: modify wasm32 dependencies

* chore: make note about getrandom

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Matthias Seitz 2021-08-23 11:56:44 +02:00 committed by GitHub
parent 8587b3e9b3
commit ea566663d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 5427 additions and 104 deletions

View File

@ -115,3 +115,47 @@ jobs:
run: cargo fmt --all -- --check
- name: cargo clippy
run: cargo clippy -- -D warnings
wasm:
name: WASM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install node
uses: actions/setup-node@v1
with:
node-version: 10
- name: Install rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
profile: minimal
override: true
- name: Check
uses: actions-rs/cargo@v1
with:
command: check
args: --target wasm32-unknown-unknown
- name: Launch Ganache
run: |
cd examples/ethers-wasm
npm install
npm run ganache &
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Wasm-pack test firefox
run: |
cd examples/ethers-wasm
wasm-pack test --headless --firefox
- name: Wasm-pack test chrome
run: |
cd examples/ethers-wasm
wasm-pack test --headless --chrome

318
Cargo.lock generated
View File

@ -111,6 +111,17 @@ dependencies = [
"syn",
]
[[package]]
name = "async_io_stream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "541b3487bf601cf3a63dfba621d6d0252611f120aaf27b198f018c0e1714f0df"
dependencies = [
"futures",
"pharos",
"rustc_version 0.3.3",
]
[[package]]
name = "auto_impl"
version = "0.4.1"
@ -144,6 +155,12 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base-x"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base58"
version = "0.1.0"
@ -355,7 +372,7 @@ checksum = "c297bd3135f558552f99a0daa180876984ea2c4ffa7470314540dff8c654109a"
dependencies = [
"camino",
"cargo-platform",
"semver",
"semver 1.0.4",
"serde",
"serde_json",
]
@ -400,7 +417,7 @@ dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
"time 0.1.43",
"winapi",
]
@ -509,6 +526,12 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22"
[[package]]
name = "const_fn"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@ -676,6 +699,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "ecdsa"
version = "0.12.4"
@ -851,7 +880,9 @@ dependencies = [
"Inflector",
"anyhow",
"cargo_metadata",
"cfg-if 1.0.0",
"ethers-core",
"getrandom 0.2.3",
"hex",
"once_cell",
"proc-macro2",
@ -916,6 +947,7 @@ dependencies = [
"ethers-signers",
"futures-util",
"hex",
"instant",
"rand 0.8.4",
"reqwest",
"serde",
@ -942,6 +974,7 @@ dependencies = [
"futures-timer",
"futures-util",
"hex",
"parking_lot",
"pin-project",
"reqwest",
"serde",
@ -954,6 +987,11 @@ dependencies = [
"tracing",
"tracing-futures",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-timer",
"web-sys",
"ws_stream_wasm",
]
[[package]]
@ -1434,6 +1472,19 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"time 0.2.27",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "ipnet"
version = "2.3.1"
@ -1498,6 +1549,15 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "lock_api"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
@ -1753,6 +1813,31 @@ dependencies = [
"syn",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "password-hash"
version = "0.2.2"
@ -1783,6 +1868,25 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pharos"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235c4b2ebc9552f5eba94ec982acb6c12f224980878e5b74a7d61806bb9c3591"
dependencies = [
"futures",
"rustc_version 0.4.0",
]
[[package]]
name = "pin-project"
version = "1.0.8"
@ -2175,7 +2279,7 @@ dependencies = [
"log",
"rusoto_credential",
"rusoto_signature",
"rustc_version",
"rustc_version 0.4.0",
"serde",
"serde_json",
"tokio",
@ -2234,7 +2338,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"rusoto_credential",
"rustc_version",
"rustc_version 0.4.0",
"serde",
"sha2 0.9.5",
"tokio",
@ -2252,13 +2356,31 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver 0.11.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
"semver 1.0.4",
]
[[package]]
@ -2311,6 +2433,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scrypt"
version = "0.7.0"
@ -2358,6 +2486,24 @@ dependencies = [
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser 0.7.0",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser 0.10.2",
]
[[package]]
name = "semver"
version = "1.0.4"
@ -2367,6 +2513,27 @@ dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "send_wrapper"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7"
[[package]]
name = "serde"
version = "1.0.127"
@ -2433,6 +2600,12 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.8.2"
@ -2554,12 +2727,70 @@ dependencies = [
"der",
]
[[package]]
name = "standback"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
dependencies = [
"version_check",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version 0.2.3",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde",
"serde_derive",
"serde_json",
"sha1",
"syn",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "subtle"
version = "2.4.1"
@ -2648,6 +2879,44 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
dependencies = [
"const_fn",
"libc",
"standback",
"stdweb",
"time-macros",
"version_check",
"winapi",
]
[[package]]
name = "time-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
dependencies = [
"proc-macro-hack",
"time-macros-impl",
]
[[package]]
name = "time-macros-impl"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"standback",
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@ -2891,6 +3160,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
version = "0.9.1"
@ -3066,6 +3341,21 @@ version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29"
[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.53"
@ -3126,6 +3416,24 @@ dependencies = [
"winapi",
]
[[package]]
name = "ws_stream_wasm"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47ca1ab42f5afed7fc332b22b6e932ca5414b209465412c8cdf0ad23bc0de645"
dependencies = [
"async_io_stream",
"futures",
"js-sys",
"pharos",
"rustc_version 0.4.0",
"send_wrapper",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "wyz"
version = "0.2.0"

View File

@ -21,6 +21,9 @@ members = [
"ethers-middleware",
]
exclude = [
"examples/ethers-wasm",
]
[package.metadata.docs.rs]
all-features = true

View File

@ -28,8 +28,9 @@ ethers = { version = "0.4.0", path = ".." }
ethers-providers = { version = "0.4.6", path = "../ethers-providers", default-features = false, features = ["ws"] }
ethers-signers = { version = "0.4.6", path = "../ethers-signers" }
ethers-middleware = { version = "0.4.8", path = "../ethers-middleware" }
tokio = { version = "1.5", default-features = false, features = ["macros"] }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.5", default-features = false, features = ["macros"] }
[features]
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]

View File

@ -24,6 +24,11 @@ hex = { version = "0.4.2", default-features = false, features = ["std"] }
reqwest = { version = "0.11.3", features = ["blocking"] }
once_cell = { version = "1.8.0", default-features = false }
cargo_metadata = "0.14.0"
cfg-if = "1.0.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# NOTE: this enables wasm compatibility for getrandom indirectly
getrandom = { version = "0.2", features = ["js"] }
[package.metadata.docs.rs]
all-features = true

View File

@ -3,6 +3,7 @@ use super::util;
use ethers_core::types::Address;
use anyhow::{anyhow, Context, Error, Result};
use cfg_if::cfg_if;
use std::{
borrow::Cow,
env, fs,
@ -77,8 +78,21 @@ impl Source {
P: AsRef<Path>,
S: AsRef<str>,
{
let base = Url::from_directory_path(root)
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let root = root.as_ref();
let root = if root.starts_with("/") {
format!("file:://{}", root.display())
} else {
format!("{}", root.display())
};
let base = Url::parse(&root)
.map_err(|_| anyhow!("root path '{}' is not absolute"))?;
} else {
let base = Url::from_directory_path(root)
.map_err(|_| anyhow!("root path '{}' is not absolute"))?;
}
}
let url = base.join(source.as_ref())?;
match url.scheme() {
@ -134,14 +148,26 @@ impl Source {
/// Retrieves the source JSON of the artifact this will either read the JSON
/// from the file system or retrieve a contract ABI from the network
/// dependending on the source type.
/// depending on the source type.
pub fn get(&self) -> Result<String> {
match self {
Source::Local(path) => get_local_contract(path),
Source::Http(url) => get_http_contract(url),
Source::Etherscan(address) => get_etherscan_contract(*address),
Source::Npm(package) => get_npm_contract(package),
Source::String(abi) => Ok(abi.clone()),
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
match self {
Source::Local(path) => get_local_contract(path),
Source::Http(_) => panic!("Http abi location are not supported for wasm"),
Source::Etherscan(_) => panic!("Etherscan abi location are not supported for wasm"),
Source::Npm(_) => panic!("npm abi location are not supported for wasm"),
Source::String(abi) => Ok(abi.clone()),
}
} else {
match self {
Source::Local(path) => get_local_contract(path),
Source::Http(url) => get_http_contract(url),
Source::Etherscan(address) => get_etherscan_contract(*address),
Source::Npm(package) => get_npm_contract(package),
Source::String(abi) => Ok(abi.clone()),
}
}
}
}
}
@ -179,6 +205,7 @@ fn get_local_contract(path: &Path) -> Result<String> {
}
/// Retrieves a Truffle artifact or ABI from an HTTP URL.
#[cfg(not(target_arch = "wasm32"))]
fn get_http_contract(url: &Url) -> Result<String> {
let json = util::http_get(url.as_str())
.with_context(|| format!("failed to retrieve JSON from {}", url))?;
@ -187,6 +214,7 @@ fn get_http_contract(url: &Url) -> Result<String> {
/// Retrieves a contract ABI from the Etherscan HTTP API and wraps it in an
/// artifact JSON for compatibility with the code generation facilities.
#[cfg(not(target_arch = "wasm32"))]
fn get_etherscan_contract(address: Address) -> Result<String> {
// NOTE: We do not retrieve the bytecode since deploying contracts with the
// same bytecode is unreliable as the libraries have already linked and
@ -206,6 +234,7 @@ fn get_etherscan_contract(address: Address) -> Result<String> {
}
/// Retrieves a Truffle artifact or ABI from an npm package through `unpkg.io`.
#[cfg(not(target_arch = "wasm32"))]
fn get_npm_contract(package: &str) -> Result<String> {
let unpkg_url = format!("https://unpkg.io/{}", package);
let json = util::http_get(&unpkg_url)

View File

@ -103,6 +103,7 @@ where
Ok(address_str[2..].parse()?)
}
#[cfg(not(target_arch = "wasm32"))]
/// Perform an HTTP GET request and return the contents of the response.
pub fn http_get(url: &str) -> Result<String> {
Ok(reqwest::blocking::get(url)?.text()?)

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod derive;
use ethers_core::{
abi::Abi,

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers::{
contract::ContractFactory,
types::{Filter, ValueOrArray, H256},

View File

@ -31,6 +31,7 @@ glob = { version = "0.3.0", default-features = false }
bytes = { version = "1.0.1", features = ["serde"] }
hex = { version = "0.4.3", default-features = false, features = ["std"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# async
tokio = { version = "1.5", default-features = false, optional = true}
futures-util = { version = "0.3.16", optional = true }

View File

@ -291,6 +291,7 @@ fn base_fee_surged(base_fee_per_gas: U256) -> U256 {
///
/// Does not guarantee that the given port is unused after the function exists, just that it was
/// unused before the function started (i.e., it does not reserve a port).
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn unused_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0")
.expect("Failed to create TCP listener to find unused port");

View File

@ -32,12 +32,17 @@ reqwest = { version = "0.11.4", default-features = false, features = ["json", "r
url = { version = "2.2.2", default-features = false }
serde_json = { version = "1.0.64", default-features = false }
instant = {version = "0.1.10", features = ["now"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.5" }
[dev-dependencies]
ethers = { version = "0.4.0", path = ".." }
hex = { version = "0.4.3", default-features = false, features = ["std"] }
rand = { version = "0.8.4", default-features = false }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] }
[features]

View File

@ -9,12 +9,18 @@ use async_trait::async_trait;
use ethers_core::types::{BlockId, TransactionRequest, TxHash, U256};
use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
use futures_util::lock::Mutex;
use instant::Instant;
use std::pin::Pin;
use std::sync::Arc;
use std::{pin::Pin, time::Instant};
use thiserror::Error;
#[cfg(not(target_arch = "wasm32"))]
use tokio::spawn;
use tracing_futures::Instrument;
#[cfg(target_arch = "wasm32")]
type WatcherFuture<'a> = Pin<Box<dyn futures_util::stream::Stream<Item = ()> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
type WatcherFuture<'a> = Pin<Box<dyn futures_util::stream::Stream<Item = ()> + Send + 'a>>;
/// Trait for fetching updated gas prices after a transaction has been first
/// broadcast
@ -69,7 +75,8 @@ pub struct GasEscalatorMiddleware<M, E> {
frequency: Frequency,
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, E> Middleware for GasEscalatorMiddleware<M, E>
where
M: Middleware,
@ -118,11 +125,14 @@ where
/// Initializes the middleware with the provided gas escalator and the chosen
/// escalation frequency (per block or per second)
#[allow(clippy::let_and_return)]
#[cfg(not(target_arch = "wasm32"))]
pub fn new(inner: M, escalator: E, frequency: Frequency) -> Self
where
E: Clone + 'static,
M: Clone + 'static,
{
use tracing_futures::Instrument;
let this = Self {
inner: Arc::new(inner),
escalator,
@ -146,18 +156,17 @@ where
/// Re-broadcasts pending transactions with a gas price escalator
pub async fn escalate(&self) -> Result<(), GasEscalatorError<M>> {
// the escalation frequency is either on a per-block basis, or on a duratoin basis
let mut watcher: Pin<Box<dyn futures_util::stream::Stream<Item = ()> + Send>> =
match self.frequency {
Frequency::PerBlock => Box::pin(
self.inner
.watch_blocks()
.await
.map_err(GasEscalatorError::MiddlewareError)?
.map(|_| ()),
),
Frequency::Duration(ms) => Box::pin(interval(std::time::Duration::from_millis(ms))),
};
// the escalation frequency is either on a per-block basis, or on a duration basis
let mut watcher: WatcherFuture = match self.frequency {
Frequency::PerBlock => Box::pin(
self.inner
.watch_blocks()
.await
.map_err(GasEscalatorError::MiddlewareError)?
.map(|_| ()),
),
Frequency::Duration(ms) => Box::pin(interval(std::time::Duration::from_millis(ms))),
};
while watcher.next().await.is_some() {
let now = Instant::now();

View File

@ -93,7 +93,8 @@ impl EthGasStation {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for EthGasStation {
async fn fetch(&self) -> Result<U256, GasOracleError> {
let res = self.query().await?;

View File

@ -64,7 +64,8 @@ impl Etherchain {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Etherchain {
async fn fetch(&self) -> Result<U256, GasOracleError> {
let res = self.query().await?;

View File

@ -92,7 +92,8 @@ impl Etherscan {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Etherscan {
async fn fetch(&self) -> Result<U256, GasOracleError> {
if matches!(self.gas_category, GasCategory::Fastest) {

View File

@ -67,7 +67,8 @@ impl GasNow {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for GasNow {
async fn fetch(&self) -> Result<U256, GasOracleError> {
let res = self.query().await?;

View File

@ -39,7 +39,8 @@ impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, G> Middleware for GasOracleMiddleware<M, G>
where
M: Middleware,

View File

@ -65,7 +65,8 @@ pub enum GasOracleError {
/// # Ok(())
/// # }
/// ```
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait GasOracle: Send + Sync + std::fmt::Debug {
/// Makes an asynchronous HTTP query to the underlying `GasOracle`
///

View File

@ -69,7 +69,8 @@ impl<M: Middleware> FromErr<M::Error> for NonceManagerError<M> {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M> Middleware for NonceManagerMiddleware<M>
where
M: Middleware,

View File

@ -6,7 +6,8 @@ use std::fmt::Debug;
use thiserror::Error;
/// Basic trait to ensure that transactions about to be sent follow certain rules.
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Policy: Sync + Send + Debug {
type Error: Sync + Send + Debug;
@ -20,7 +21,8 @@ pub trait Policy: Sync + Send + Debug {
#[derive(Debug, Clone, Copy)]
pub struct AllowEverything;
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Policy for AllowEverything {
type Error = ();
@ -33,7 +35,8 @@ impl Policy for AllowEverything {
#[derive(Debug, Clone, Copy)]
pub struct RejectEverything;
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Policy for RejectEverything {
type Error = ();
@ -77,7 +80,8 @@ pub enum PolicyMiddlewareError<M: Middleware, P: Policy> {
MiddlewareError(M::Error),
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, P> Middleware for PolicyMiddleware<M, P>
where
M: Middleware,

View File

@ -152,7 +152,8 @@ where
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, S> Middleware for SignerMiddleware<M, S>
where
M: Middleware,
@ -246,7 +247,7 @@ where
}
}
#[cfg(all(test, not(feature = "celo")))]
#[cfg(all(test, not(feature = "celo"), not(target_arch = "wasm32")))]
mod tests {
use super::*;
use ethers::{providers::Provider, signers::LocalWallet};

View File

@ -39,7 +39,8 @@ impl<M: Middleware> FromErr<M::Error> for TransformerMiddlewareError<M> {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M, T> Middleware for TransformerMiddleware<M, T>
where
M: Middleware,

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_core::types::*;
use ethers_middleware::{
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_core::{types::*, utils::Ganache};
use ethers_middleware::gas_oracle::{
EthGasStation, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleMiddleware,

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn nonce_manager() {

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_providers::{Http, Middleware, Provider};
use ethers_core::types::TransactionRequest;

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "celo"))]
mod tests {
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Ganache};

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_contract::{BaseContract, ContractFactory};
use ethers_core::{
types::*,

View File

@ -36,14 +36,27 @@ pin-project = { version = "1.0.7", default-features = false }
tracing = { version = "0.1.26", default-features = false }
tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] }
bytes = { version = "1.0.1", default-features = false, optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# tokio
tokio-util = { version = "0.6.7", default-features = false, features = ["io"], optional = true }
tokio = { version = "1.5", default-features = false, optional = true }
tokio-tungstenite = { version = "0.15.0", default-features = false, features = ["connect"], optional = true }
tokio-util = { version = "0.6.7", default-features = false, features = ["io"], optional = true }
bytes = { version = "1.0.1", default-features = false, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
ws_stream_wasm = "0.7"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["console"] }
wasm-timer = "0.2"
# this is currently necessary for `wasm-timer::Delay` to work
parking_lot = { version = "0.11", features = ["wasm-bindgen"] }
[dev-dependencies]
ethers = { version = "0.4.0", path = ".." }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.5", default-features = false, features = ["rt", "macros"] }
tempfile = "3.2.0"

View File

@ -90,10 +90,14 @@ use std::{error::Error, fmt::Debug, future::Future, pin::Pin, str::FromStr};
pub use provider::{FilterKind, Provider, ProviderError};
// Helper type alias
#[cfg(target_arch = "wasm32")]
pub(crate) type PinBoxFut<'a, T> = Pin<Box<dyn Future<Output = Result<T, ProviderError>> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) type PinBoxFut<'a, T> =
Pin<Box<dyn Future<Output = Result<T, ProviderError>> + Send + 'a>>;
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
/// Trait which must be implemented by data transports to be used with the Ethereum
/// JSON-RPC provider.
@ -125,8 +129,6 @@ where
}
}
#[async_trait]
#[auto_impl(&, Box, Arc)]
/// A middleware allows customizing requests send and received from an ethereum node.
///
/// Writing a middleware is as simple as:
@ -182,6 +184,9 @@ where
/// }
/// }
/// ```
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
pub trait Middleware: Sync + Send + Debug {
type Error: Sync + Send + Error + FromErr<<Self::Inner as Middleware>::Error>;
type Provider: JsonRpcClient;
@ -759,7 +764,8 @@ where
}
#[cfg(feature = "celo")]
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CeloMiddleware: Middleware {
async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
&self,

View File

@ -5,7 +5,6 @@ use crate::{
};
use ethers_core::types::{Transaction, TransactionReceipt, TxHash, U64};
use futures_core::stream::Stream;
use futures_timer::Delay;
use futures_util::stream::StreamExt;
use pin_project::pin_project;
use std::{
@ -17,6 +16,11 @@ use std::{
time::Duration,
};
#[cfg(not(target_arch = "wasm32"))]
use futures_timer::Delay;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Delay;
/// A pending transaction is a transaction which has been submitted but is not yet mined.
/// `await`'ing on a pending transaction will resolve to a transaction receipt
/// once the transaction has enough `confirmations`. The default number of confirmations
@ -258,7 +262,7 @@ impl<'a, P> Deref for PendingTransaction<'a, P> {
// We box the TransactionReceipts to keep the enum small.
enum PendingTxState<'a> {
/// Initial delay to ensure the GettingTx loop doesn't immediately fail
InitialDelay(Pin<Box<futures_timer::Delay>>),
InitialDelay(Pin<Box<Delay>>),
/// Waiting for interval to elapse before calling API again
PausedGettingTx,

View File

@ -2,9 +2,12 @@ use crate::{
ens,
pubsub::{PubsubClient, SubscriptionStream},
stream::{FilterWatcher, DEFAULT_POLL_INTERVAL},
FeeHistory, FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction,
FeeHistory, FromErr, JsonRpcClient, MockProvider, PendingTransaction,
};
#[cfg(not(target_arch = "wasm32"))]
use crate::Http as HttpProvider;
use ethers_core::{
abi::{self, Detokenize, ParamType},
types::{
@ -23,6 +26,7 @@ use async_trait::async_trait;
use hex::FromHex;
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
#[cfg(not(target_arch = "wasm32"))]
use url::{ParseError, Url};
use std::{convert::TryFrom, fmt::Debug, time::Duration};
@ -167,7 +171,8 @@ impl<P: JsonRpcClient> Provider<P> {
}
#[cfg(feature = "celo")]
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<P: JsonRpcClient> CeloMiddleware for Provider<P> {
async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
&self,
@ -179,7 +184,8 @@ impl<P: JsonRpcClient> CeloMiddleware for Provider<P> {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<P: JsonRpcClient> Middleware for Provider<P> {
type Error = ProviderError;
type Provider = P;
@ -870,14 +876,23 @@ impl<P: JsonRpcClient> Provider<P> {
#[cfg(feature = "ws")]
impl Provider<crate::Ws> {
/// Direct connection to a websocket endpoint
#[cfg(not(target_arch = "wasm32"))]
pub async fn connect(
url: impl tokio_tungstenite::tungstenite::client::IntoClientRequest + Unpin,
) -> Result<Self, ProviderError> {
let ws = crate::Ws::connect(url).await?;
Ok(Self::new(ws))
}
/// Direct connection to a websocket endpoint
#[cfg(target_arch = "wasm32")]
pub async fn connect(url: &str) -> Result<Self, ProviderError> {
let ws = crate::Ws::connect(url).await?;
Ok(Self::new(ws))
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "ipc")]
impl Provider<crate::Ipc> {
/// Direct connection to an IPC socket.
@ -926,6 +941,7 @@ fn decode_bytes<T: Detokenize>(param: ParamType, bytes: Bytes) -> T {
T::from_tokens(tokens).expect("could not parse tokens as address")
}
#[cfg(not(target_arch = "wasm32"))]
impl TryFrom<&str> for Provider<HttpProvider> {
type Error = ParseError;
@ -934,6 +950,7 @@ impl TryFrom<&str> for Provider<HttpProvider> {
}
}
#[cfg(not(target_arch = "wasm32"))]
impl TryFrom<String> for Provider<HttpProvider> {
type Error = ParseError;
@ -943,8 +960,13 @@ impl TryFrom<String> for Provider<HttpProvider> {
}
#[cfg(test)]
mod ens_tests {
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::Http;
use ethers_core::types::{TransactionRequest, H256};
use ethers_core::utils::Geth;
use futures_util::StreamExt;
const INFURA: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
@ -989,15 +1011,6 @@ mod ens_tests {
.await
.unwrap_err();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Http;
use ethers_core::types::{TransactionRequest, H256};
use ethers_core::utils::Geth;
use futures_util::StreamExt;
#[tokio::test]
#[cfg_attr(feature = "celo", ignore)]

View File

@ -4,7 +4,6 @@ use ethers_core::types::{Transaction, TxHash, U256};
use futures_core::stream::Stream;
use futures_core::Future;
use futures_timer::Delay;
use futures_util::stream::FuturesUnordered;
use futures_util::{stream, FutureExt, StreamExt};
use pin_project::pin_project;
@ -18,6 +17,11 @@ use std::{
vec::IntoIter,
};
#[cfg(not(target_arch = "wasm32"))]
use futures_timer::Delay;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Delay;
// https://github.com/tomusdrw/rust-web3/blob/befcb2fb8f3ca0a43e3081f68886fa327e64c8e6/src/api/eth_filter.rs#L20
pub fn interval(duration: Duration) -> impl Stream<Item = ()> + Send + Unpin {
stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
@ -254,6 +258,7 @@ where
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::{Http, Ws};

View File

@ -58,7 +58,8 @@ impl From<ClientError> for ProviderError {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl JsonRpcClient for Provider {
type Error = ClientError;

View File

@ -23,7 +23,8 @@ impl Default for MockProvider {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl JsonRpcClient for MockProvider {
type Error = MockError;
@ -103,6 +104,7 @@ impl From<MockError> for ProviderError {
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::Middleware;

View File

@ -1,17 +1,34 @@
mod common;
mod http;
pub use http::Provider as Http;
macro_rules! if_wasm {
($($item:item)*) => {$(
#[cfg(target_arch = "wasm32")]
$item
)*}
}
macro_rules! if_not_wasm {
($($item:item)*) => {$(
#[cfg(not(target_arch = "wasm32"))]
$item
)*}
}
if_not_wasm! {
mod http;
pub use http::Provider as Http;
#[cfg(feature = "ipc")]
mod ipc;
#[cfg(feature = "ipc")]
pub use ipc::Ipc;
}
#[cfg(feature = "ws")]
mod ws;
#[cfg(feature = "ws")]
pub use ws::Ws;
#[cfg(feature = "ipc")]
mod ipc;
#[cfg(feature = "ipc")]
pub use ipc::Ipc;
mod mock;
pub use mock::{MockError, MockProvider};

View File

@ -22,14 +22,46 @@ use std::{
},
};
use thiserror::Error;
use tokio_tungstenite::{
connect_async,
tungstenite::{
self,
protocol::{CloseFrame, Message},
},
};
use tracing::{error, warn};
if_wasm! {
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use ws_stream_wasm::*;
type Message = WsMessage;
type WsError = ws_stream_wasm::WsErr;
type WsStreamItem = Message;
macro_rules! error {
( $( $t:tt )* ) => {
web_sys::console::error_1(&format!( $( $t )* ).into());
}
}
macro_rules! warn {
( $( $t:tt )* ) => {
web_sys::console::warn_1(&format!( $( $t )* ).into());
}
}
macro_rules! debug {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
}
if_not_wasm! {
use tokio_tungstenite::{
connect_async,
tungstenite::{
self,
protocol::CloseFrame,
},
};
type Message = tungstenite::protocol::Message;
type WsError = tungstenite::Error;
type WsStreamItem = Result<Message, WsError>;
use tracing::{debug, error, warn};
}
type Pending = oneshot::Sender<Result<serde_json::Value, JsonRpcError>>;
type Subscription = mpsc::UnboundedSender<serde_json::Value>;
@ -84,11 +116,7 @@ impl Ws {
/// The websocket connection must be initiated separately.
pub fn new<S: 'static>(ws: S) -> Self
where
S: Send
+ Sync
+ Stream<Item = Result<Message, tungstenite::Error>>
+ Sink<Message, Error = tungstenite::Error>
+ Unpin,
S: Send + Sync + Stream<Item = WsStreamItem> + Sink<Message, Error = WsError> + Unpin,
{
let (sink, stream) = mpsc::unbounded();
@ -107,6 +135,17 @@ impl Ws {
}
/// Initializes a new WebSocket Client
#[cfg(target_arch = "wasm32")]
pub async fn connect(url: &str) -> Result<Self, ClientError> {
let (_, wsio) = WsMeta::connect(url, None)
.await
.expect_throw("Could not create websocket");
Ok(Self::new(wsio))
}
/// Initializes a new WebSocket Client
#[cfg(not(target_arch = "wasm32"))]
pub async fn connect(
url: impl tungstenite::client::IntoClientRequest + Unpin,
) -> Result<Self, ClientError> {
@ -121,7 +160,8 @@ impl Ws {
}
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl JsonRpcClient for Ws {
type Error = ClientError;
@ -181,11 +221,7 @@ struct WsServer<S> {
impl<S> WsServer<S>
where
S: Send
+ Sync
+ Stream<Item = Result<Message, tungstenite::Error>>
+ Sink<Message, Error = tungstenite::Error>
+ Unpin,
S: Send + Sync + Stream<Item = WsStreamItem> + Sink<Message, Error = WsError> + Unpin,
{
/// Instantiates the Websocket Server
fn new(ws: S, requests: mpsc::UnboundedReceiver<Instruction>) -> Self {
@ -215,12 +251,12 @@ where
let f = async move {
loop {
if self.is_done() {
tracing::info!("work complete");
debug!("work complete");
break;
}
match self.tick().await {
Err(ClientError::UnexpectedClose) => {
tracing::error!("{}", ClientError::UnexpectedClose);
error!("{}", ClientError::UnexpectedClose);
break;
}
Err(e) => {
@ -231,6 +267,10 @@ where
}
};
#[cfg(target_arch = "wasm32")]
spawn_local(f);
#[cfg(not(target_arch = "wasm32"))]
tokio::spawn(f);
}
@ -284,6 +324,7 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn handle_ping(&mut self, inner: Vec<u8>) -> Result<(), ClientError> {
self.ws.send(Message::Pong(inner)).await?;
Ok(())
@ -315,6 +356,15 @@ where
Ok(())
}
#[cfg(target_arch = "wasm32")]
async fn handle(&mut self, resp: Message) -> Result<(), ClientError> {
match resp {
Message::Text(inner) => self.handle_text(inner).await,
Message::Binary(buf) => Err(ClientError::UnexpectedBinary(buf)),
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn handle(&mut self, resp: Message) -> Result<(), ClientError> {
match resp {
Message::Text(inner) => self.handle_text(inner).await,
@ -328,6 +378,28 @@ where
/// Processes 1 instruction or 1 incoming websocket message
#[allow(clippy::single_match)]
#[cfg(target_arch = "wasm32")]
async fn tick(&mut self) -> Result<(), ClientError> {
futures_util::select! {
// Handle requests
instruction = self.instructions.select_next_some() => {
self.service(instruction).await?;
},
// Handle ws messages
resp = self.ws.next() => match resp {
Some(resp) => self.handle(resp).await?,
None => {
return Err(ClientError::UnexpectedClose);
},
}
};
Ok(())
}
/// Processes 1 instruction or 1 incoming websocket message
#[allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
async fn tick(&mut self) -> Result<(), ClientError> {
futures_util::select! {
// Handle requests
@ -371,7 +443,7 @@ pub enum ClientError {
/// Thrown if there's an error over the WS connection
#[error(transparent)]
TungsteniteError(#[from] tungstenite::Error),
TungsteniteError(#[from] WsError),
#[error("{0}")]
ChannelError(String),
@ -381,8 +453,14 @@ pub enum ClientError {
/// Remote server sent a Close message
#[error("Websocket closed with info: {0:?}")]
#[cfg(not(target_arch = "wasm32"))]
WsClosed(CloseFrame<'static>),
/// Remote server sent a Close message
#[error("Websocket closed with info")]
#[cfg(target_arch = "wasm32")]
WsClosed,
/// Something caused the websocket to close
#[error("WebSocket connection closed unexpectedly")]
UnexpectedClose,
@ -396,6 +474,7 @@ impl From<ClientError> for ProviderError {
#[cfg(test)]
#[cfg(not(feature = "celo"))]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use ethers_core::types::{Block, TxHash, U256};

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers::providers::{Http, Middleware, Provider};
use std::{convert::TryFrom, time::Duration};

View File

@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers::{
providers::{Http, Middleware, Provider},
types::TransactionRequest,

View File

@ -19,7 +19,6 @@ thiserror = { version = "1.0.24", default-features = false }
coins-bip32 = "0.3.0"
coins-bip39 = "0.3.0"
coins-ledger = { version = "0.3.0", default-features = false, optional = true }
eth-keystore = { version = "0.3.0" }
hex = { version = "0.4.3", default-features = false, features = ["std"] }
async-trait = { version = "0.1.50", default-features = false }
elliptic-curve = { version = "0.10.5", default-features = false }
@ -36,14 +35,18 @@ tracing = { version = "0.1.26", optional = true }
tracing-futures = { version = "0.2.5", optional = true }
spki = { version = "0.4.0", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
eth-keystore = { version = "0.3.0" }
[dev-dependencies]
ethers = { version = "0.4.0", path = ".." }
tracing-subscriber = "0.2.20"
yubihsm = { version = "0.39.0", features = ["secp256k1", "usb", "mockhsm"] }
tempfile = "3.2.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
yubihsm = { version = "0.39.0", features = ["secp256k1", "usb", "mockhsm"] }
tokio = { version = "1.5", default-features = false, features = ["macros"] }
tracing-subscriber = "0.2.20"
tempfile = "3.2.0"
[features]
celo = ["ethers-core/celo"]

View File

@ -7,7 +7,8 @@ use async_trait::async_trait;
use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature};
use types::LedgerError;
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Signer for LedgerEthereum {
type Error = LedgerError;

View File

@ -81,7 +81,8 @@ pub fn to_eip155_v<T: Into<u8>>(recovery_id: T, chain_id: u64) -> u64 {
/// Trait for signing transactions and messages
///
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Signer: std::fmt::Debug + Send + Sync {
type Error: Error + Send + Sync;
/// Signs the hash of the provided message after prefixing it

View File

@ -189,6 +189,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;

View File

@ -64,7 +64,8 @@ pub struct Wallet<D: DigestSigner<Sha256Proxy, RecoverableSignature>> {
pub(crate) chain_id: u64,
}
#[async_trait]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer for Wallet<D> {
type Error = std::convert::Infallible;

View File

@ -4,14 +4,18 @@ use super::Wallet;
use crate::wallet::mnemonic::MnemonicBuilderError;
use coins_bip32::Bip32Error;
use coins_bip39::MnemonicError;
#[cfg(not(target_arch = "wasm32"))]
use elliptic_curve::rand_core;
#[cfg(not(target_arch = "wasm32"))]
use eth_keystore::KeystoreError;
use ethers_core::{
k256::ecdsa::{self, SigningKey},
rand::{CryptoRng, Rng},
utils::secret_key_to_address,
};
use std::{path::Path, str::FromStr};
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use std::str::FromStr;
use thiserror::Error;
#[derive(Error, Debug)]
@ -24,6 +28,7 @@ pub enum WalletError {
#[error(transparent)]
Bip39Error(#[from] MnemonicError),
/// Underlying eth keystore error
#[cfg(not(target_arch = "wasm32"))]
#[error(transparent)]
EthKeystoreError(#[from] KeystoreError),
/// Error propagated from k256's ECDSA module
@ -54,6 +59,7 @@ impl Clone for Wallet<SigningKey> {
impl Wallet<SigningKey> {
/// Creates a new random encrypted JSON with the provided password and stores it in the
/// provided directory
#[cfg(not(target_arch = "wasm32"))]
pub fn new_keystore<P, R, S>(dir: P, rng: &mut R, password: S) -> Result<Self, WalletError>
where
P: AsRef<Path>,
@ -71,6 +77,7 @@ impl Wallet<SigningKey> {
}
/// Decrypts an encrypted JSON from the provided path to construct a Wallet instance
#[cfg(not(target_arch = "wasm32"))]
pub fn decrypt_keystore<P, S>(keypath: P, password: S) -> Result<Self, WalletError>
where
P: AsRef<Path>,
@ -145,6 +152,7 @@ impl FromStr for Wallet<SigningKey> {
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::Signer;

View File

@ -64,6 +64,7 @@ impl From<YubiSigner<Secp256k1>> for Wallet<YubiSigner<Secp256k1>> {
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use crate::Signer;

8
examples/ethers-wasm/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
node_modules/
yarn-error.log

View File

@ -0,0 +1,52 @@
[package]
name = "ethers-wasm"
version = "0.1.0"
authors = ["Matthias Seitz <matthias.seitz@outlook.de>"]
edition = "2018"
license = "MIT OR Apache-2.0"
readme = "README.md"
documentation = "https://docs.rs/ethers"
repository = "https://github.com/gakonst/ethers-rs"
homepage = "https://docs.rs/ethers"
description = """
How to use ethers in the browser with WASM.
"""
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
ethers = { path = "../..", features = ["abigen", "legacy", "ws"] }
serde_derive = "1.0.126"
wasm-bindgen-futures = "0.4.24"
serde_json = "1.0.64"
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true }
serde = { version = "1.0.126", features = ["derive"] }
hex = "0.4.3"
web-sys = "0.3.51"
[dev-dependencies]
wasm-bindgen-test = "0.3.24"
# profile for the wasm example
[profile.release.package.ethers-wasm]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@ -0,0 +1,15 @@
## Example usage of ethers-rs from WASM
Install wasm-pack with
yarn install
Start a local ganache instance
yarn ganache
Then you can build the example locally with:
yarn serve
and then visiting http://localhost:8080 in a browser should run the example!

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>ethers WASM example</title>
</head>
<body>
<script src="index.js"></script>
<h1>ethers WASM</h1>
</body>
</html>

View File

@ -0,0 +1,8 @@
const ethers = import('./pkg');
ethers
.then(m => {
m.setup();
m.deploy().catch(console.error);
})
.catch(console.error);

View File

@ -0,0 +1,18 @@
{
"name": "ethers-wasm",
"license": "MIT OR Apache-2.0",
"scripts": {
"build": "webpack",
"serve": "webpack-dev-server",
"ganache": "ganache-cli --blockTime 5 --seed ethers-wasm-seed"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "1.0.1",
"html-webpack-plugin": "^3.2.0",
"text-encoding": "^0.7.0",
"webpack": "^4.29.4",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.0",
"ganache-cli": "^6.12.2"
}
}

View File

@ -0,0 +1,102 @@
pub mod utils;
use crate::utils::SIMPLECONTRACT_BIN;
use ethers::{
contract::abigen,
prelude::{ContractFactory, LocalWallet, Provider, SignerMiddleware},
providers::{Middleware, Ws},
};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use web_sys::console;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
abigen!(
SimpleContract,
"./../contract_abi.json",
event_derives(serde::Deserialize, serde::Serialize)
);
/// key[0] of ganache with custom seed `ethers-wasm-seed`
pub const KEY: &str = "817169e55f14ede54f4fd6a4f2ab4209db14aeeb1b9972b3b28f1560af0a061a";
#[wasm_bindgen]
pub fn setup() {
utils::set_panic_hook();
}
#[wasm_bindgen]
pub async fn deploy() {
console::log_2(
&"ABI: ".into(),
&JsValue::from_serde(&*SIMPLECONTRACT_ABI).unwrap(),
);
let wallet: LocalWallet = KEY.parse().unwrap();
log!("Wallet: {:?}", wallet);
let endpoint = "ws://127.0.0.1:8545";
let provider = Provider::new(Ws::connect(endpoint).await.unwrap());
let client = Arc::new(SignerMiddleware::new(provider, wallet));
log!("provider connected to `{}`", endpoint);
let version = client.client_version().await;
log!("version {:?}", version);
let account = client.get_accounts().await.unwrap()[0];
log!("account {:?}", account);
let bytecode = hex::decode(SIMPLECONTRACT_BIN).unwrap();
let factory = ContractFactory::new(SIMPLECONTRACT_ABI.clone(), bytecode.into(), client.clone());
let init = "hello WASM!";
let contract = factory
.deploy(init.to_string())
.unwrap()
.send()
.await
.unwrap();
let addr = contract.address();
log!("deployed contract with address {}", addr);
let contract = SimpleContract::new(addr, client.clone());
let value = contract.get_value().call().await.unwrap();
assert_eq!(init, &value);
let _receipt = contract
.set_value("bye WASM!".to_owned())
.send()
.await
.unwrap()
.await
.unwrap();
log!("set value");
// 10. get all events
let logs = contract
.value_changed_filter()
.from_block(0u64)
.query()
.await
.unwrap();
let value = contract.get_value().call().await.unwrap();
log!(
"Value: {}. Logs: {:?}",
value,
JsValue::from_serde(&logs).unwrap()
);
}

View File

@ -0,0 +1,12 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
pub const SIMPLECONTRACT_BIN: &str = "608060405234801561001057600080fd5b5060405161073b38038061073b8339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b8382019150602082018581111561006957600080fd5b825186600182028301116401000000008211171561008657600080fd5b8083526020830192505050908051906020019080838360005b838110156100ba57808201518184015260208101905061009f565b50505050905090810190601f1680156100e75780820380516001836020036101000a031916815260200191505b506040525050503373ffffffffffffffffffffffffffffffffffffffff167fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb6000836040518080602001806020018381038352858181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156101b65780601f1061018b576101008083540402835291602001916101b6565b820191906000526020600020905b81548152906001019060200180831161019957829003601f168201915b5050838103825284818151815260200191508051906020019080838360005b838110156101f05780820151818401526020810190506101d5565b50505050905090810190601f16801561021d5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a28060009080519060200190610242929190610249565b50506102e6565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028a57805160ff19168380011785556102b8565b828001600101855582156102b8579182015b828111156102b757825182559160200191906001019061029c565b5b5090506102c591906102c9565b5090565b5b808211156102e25760008160009055506001016102ca565b5090565b610446806102f56000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100be575b600080fd5b610043610179565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610083578082015181840152602081019050610068565b50505050905090810190601f1680156100b05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610177600480360360208110156100d457600080fd5b81019080803590602001906401000000008111156100f157600080fd5b82018360208201111561010357600080fd5b8035906020019184600183028401116401000000008311171561012557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061021b565b005b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102115780601f106101e657610100808354040283529160200191610211565b820191906000526020600020905b8154815290600101906020018083116101f457829003601f168201915b5050505050905090565b3373ffffffffffffffffffffffffffffffffffffffff167fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb6000836040518080602001806020018381038352858181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156102e35780601f106102b8576101008083540402835291602001916102e3565b820191906000526020600020905b8154815290600101906020018083116102c657829003601f168201915b5050838103825284818151815260200191508051906020019080838360005b8381101561031d578082015181840152602081019050610302565b50505050905090810190601f16801561034a5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2806000908051906020019061036f929190610373565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106103b457805160ff19168380011785556103e2565b828001600101855582156103e2579182015b828111156103e15782518255916020019190600101906103c6565b5b5090506103ef91906103f3565b5090565b5b8082111561040c5760008160009055506001016103f4565b509056fea26469706673582212202d397d3d0e6cf9afdeed7d5192e3abff386699395df0409eb5c3ec494832c57a64736f6c63430007000033";

View File

@ -0,0 +1,71 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
use ethers::{
contract::abigen,
prelude::{ContractFactory, LocalWallet, Provider, SignerMiddleware},
providers::Ws,
};
use std::sync::Arc;
wasm_bindgen_test_configure!(run_in_browser);
// Generate the type-safe contract bindings by providing the ABI
// definition in human readable format
abigen!(
SimpleContract,
"../contract_abi.json",
event_derives(serde::Deserialize, serde::Serialize)
);
#[wasm_bindgen_test]
async fn connect_and_deploy() {
console_log!("starting");
// a private key of a launched ganache `yarn ganache`
let wallet: LocalWallet = ethers_wasm::KEY.parse().unwrap();
let provider = Provider::new(Ws::connect("ws://localhost:8545").await.unwrap());
let client = Arc::new(SignerMiddleware::new(provider, wallet));
let bytecode = hex::decode(ethers_wasm::utils::SIMPLECONTRACT_BIN).unwrap();
let factory = ContractFactory::new(SIMPLECONTRACT_ABI.clone(), bytecode.into(), client.clone());
let contract = factory
.deploy("initial value".to_string())
.unwrap()
.send()
.await
.unwrap();
let addr = contract.address();
console_log!("deployed to {}", addr);
let contract = SimpleContract::new(addr, client.clone());
let _receipt = contract
.set_value("hi".to_owned())
.send()
.await
.unwrap()
.await
.unwrap();
// get all events
let logs = contract
.value_changed_filter()
.from_block(0u64)
.query()
.await
.unwrap();
let value = contract.get_value().call().await.unwrap();
console_log!(
"Value: {}. Logs: {:?}",
value,
JsValue::from_serde(&logs).unwrap()
);
}

View File

@ -0,0 +1,28 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
}
),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// Have this example work in Edge which doesn't ship `TextEncoder` or
// `TextDecoder` at this time.
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development'
};

File diff suppressed because it is too large Load Diff