Merge branch 'master' into franfran/ast-fix
merge master (rebase was swallowing code)
This commit is contained in:
commit
e5d2368f96
|
@ -1 +1 @@
|
|||
msrv = "1.62"
|
||||
msrv = "1.64"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock linguist-generated=true
|
||||
flake.lock linguist-generated=true
|
|
@ -232,32 +232,7 @@ jobs:
|
|||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build all examples
|
||||
run: |
|
||||
export PATH=$HOME/bin:$PATH
|
||||
examples=$(cargo metadata --format-version 1 | \
|
||||
jq -c '.packages[]
|
||||
| select(.name == "ethers")
|
||||
| .targets[]
|
||||
| select(.kind[] | contains("example"))
|
||||
| with_entries(select([.key]
|
||||
| inside(["name", "required-features"])))'
|
||||
)
|
||||
for example in $examples; do
|
||||
name="$(echo "$example" | jq -r '.name')"
|
||||
args=(
|
||||
-p ethers
|
||||
--example "$name"
|
||||
)
|
||||
features="$(echo "$example" | jq -r 'try(."required-features" | join(","))')"
|
||||
if [[ ! -z "$features" ]]; then
|
||||
args+=(--features "$features")
|
||||
fi
|
||||
|
||||
echo "building $name"
|
||||
cargo build "${args[@]}"
|
||||
done
|
||||
- name: Run all examples
|
||||
- name: Build and run all examples
|
||||
run: |
|
||||
export PATH=$HOME/bin:$PATH
|
||||
chmod +x ./scripts/examples.sh
|
||||
|
|
|
@ -2,3 +2,8 @@
|
|||
.vscode
|
||||
/.envrc
|
||||
.idea
|
||||
.nlsp-settings
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
.direnv
|
||||
.pre-commit-config.yaml
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- Fix typo in `RwClient` docs for `write_client` method.
|
||||
- Add support for Geth `debug_traceCall` [#1949](https://github.com/gakonst/ethers-rs/pull/1949)
|
||||
- Graceful handling of WebSocket transport errors [#1889](https://github.com/gakonst/ethers-rs/issues/1889) [#1815](https://github.com/gakonst/ethers-rs/issues/1815)
|
||||
- `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers.
|
||||
- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.
|
||||
|
@ -103,6 +105,7 @@
|
|||
- [#842](https://github.com/gakonst/ethers-rs/issues/842) Add support for I256 types in `parse_units` and `format_units`.
|
||||
Added `twos_complement` function for I256.
|
||||
- [#1934](https://github.com/gakonst/ethers-rs/pull/1934) Allow 16 calls in multicall.
|
||||
- [#1941](https://github.com/gakonst/ethers-rs/pull/1941) Add `add_calls` and `call_array` for `Multicall`.
|
||||
|
||||
## ethers-contract-abigen
|
||||
|
||||
|
@ -272,6 +275,7 @@
|
|||
|
||||
- `eth-keystore-rs` crate updated. Allow an optional name for the to-be-generated
|
||||
keystore file [#910](https://github.com/gakonst/ethers-rs/pull/910)
|
||||
- [1983](https://github.com/gakonst/ethers-rs/pull/1983) Added a `from_bytes` function for the `Wallet` type.
|
||||
|
||||
### 0.6.0
|
||||
|
||||
|
@ -340,6 +344,8 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- Added `openssl` and `rustls` feature flags
|
||||
[#1961](https://github.com/gakonst/ethers-rs/pull/1961)
|
||||
- Relax Clone requirements when Arc<Middleware> is used
|
||||
[#1183](https://github.com/gakonst/ethers-rs/pull/1183)
|
||||
- Ensure a consistent chain ID between a Signer and Provider in SignerMiddleware
|
||||
|
|
|
@ -121,6 +121,7 @@ cargo check --all-features
|
|||
cargo +nightly fmt --all
|
||||
cargo build --all-features
|
||||
cargo test --all-features
|
||||
cargo +nightly clippy --all-features
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
|
|
@ -1420,6 +1420,7 @@ dependencies = [
|
|||
"hex",
|
||||
"hex-literal",
|
||||
"k256",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"open-fastrlp",
|
||||
"proc-macro2",
|
||||
|
@ -1626,6 +1627,111 @@ dependencies = [
|
|||
"wee_alloc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-anvil"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-big-numbers"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-contracts"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-events"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-middleware"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-providers"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-queries"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-subscriptions"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-transactions"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-wallets"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ethers",
|
||||
"eyre",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.8"
|
||||
|
@ -2577,6 +2683,27 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.3"
|
||||
|
@ -3750,9 +3877,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153"
|
||||
checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
|
@ -3764,11 +3891,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5"
|
||||
checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
67
Cargo.toml
67
Cargo.toml
|
@ -2,7 +2,7 @@
|
|||
name = "ethers"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
rust-version = "1.64" # must also be changed in **/Cargo.toml and .clippy.toml
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -21,7 +21,8 @@ members = [
|
|||
"ethers-middleware",
|
||||
"ethers-etherscan",
|
||||
"ethers-solc",
|
||||
"examples/ethers-wasm",
|
||||
# Example crates
|
||||
"examples/*",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
|
@ -63,12 +64,14 @@ eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
|
|||
ws = ["ethers-providers/ws"]
|
||||
ipc = ["ethers-providers/ipc"]
|
||||
rustls = [
|
||||
"ethers-middleware/rustls",
|
||||
"ethers-providers/rustls",
|
||||
"ethers-etherscan/rustls",
|
||||
"ethers-contract/rustls",
|
||||
"ethers-solc/rustls",
|
||||
]
|
||||
openssl = [
|
||||
"ethers-middleware/openssl",
|
||||
"ethers-providers/openssl",
|
||||
"ethers-etherscan/openssl",
|
||||
"ethers-contract/openssl",
|
||||
|
@ -84,6 +87,7 @@ abigen = ["ethers-contract/abigen"]
|
|||
### abigen without reqwest
|
||||
abigen-offline = ["ethers-contract/abigen-offline"]
|
||||
## solc
|
||||
ethers-solc = ["dep:ethers-solc", "ethers-etherscan/ethers-solc"]
|
||||
solc-full = ["ethers-solc", "ethers-solc/full"]
|
||||
solc-tests = ["ethers-solc", "ethers-solc/tests"]
|
||||
solc-sha2-asm = ["ethers-solc", "ethers-solc/asm"]
|
||||
|
@ -125,62 +129,3 @@ bytes = "1.3.0"
|
|||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
|
||||
[[example]]
|
||||
name = "abigen"
|
||||
path = "examples/abigen.rs"
|
||||
required-features = ["ethers-solc"]
|
||||
|
||||
[[example]]
|
||||
name = "contract_human_readable"
|
||||
path = "examples/contract_human_readable.rs"
|
||||
required-features = ["ethers-solc"]
|
||||
|
||||
[[example]]
|
||||
name = "contract_with_abi"
|
||||
path = "examples/contract_with_abi.rs"
|
||||
required-features = ["ethers-solc"]
|
||||
|
||||
[[example]]
|
||||
name = "contract_with_abi_and_bytecode"
|
||||
path = "examples/contract_with_abi_and_bytecode.rs"
|
||||
required-features = ["ethers-solc"]
|
||||
|
||||
[[example]]
|
||||
name = "ipc"
|
||||
path = "examples/ipc.rs"
|
||||
required-features = ["ipc"]
|
||||
|
||||
[[example]]
|
||||
name = "ledger"
|
||||
path = "examples/ledger.rs"
|
||||
required-features = ["ledger"]
|
||||
|
||||
[[example]]
|
||||
name = "moonbeam_with_abi"
|
||||
path = "examples/moonbeam_with_abi.rs"
|
||||
required-features = ["legacy", "ethers-solc"]
|
||||
|
||||
[[example]]
|
||||
name = "trezor"
|
||||
path = "examples/trezor.rs"
|
||||
required-features = ["trezor"]
|
||||
|
||||
[[example]]
|
||||
name = "yubi"
|
||||
path = "examples/yubi.rs"
|
||||
required-features = ["yubi"]
|
||||
|
||||
[[example]]
|
||||
name = "paginated_logs"
|
||||
path = "examples/paginated_logs.rs"
|
||||
required-features = ["rustls"]
|
||||
|
||||
[[example]]
|
||||
name = "subscribe_contract_events"
|
||||
path = "examples/subscribe_contract_events.rs"
|
||||
required-features = ["rustls", "ws"]
|
||||
|
||||
[[example]]
|
||||
name = "subscribe_contract_events_with_meta"
|
||||
path = "examples/subscribe_contract_events_with_meta.rs"
|
||||
required-features = ["rustls", "ws"]
|
||||
|
|
|
@ -15,7 +15,12 @@ Extensive documentation and examples are available [here](https://docs.rs/ethers
|
|||
|
||||
Alternatively, you may clone the repository and run `cd ethers/ && cargo doc --open`
|
||||
|
||||
You can also run any of the examples by executing: `cargo run -p ethers --example <name>`
|
||||
Examples are organized into individual crates under the `/examples` folder.
|
||||
You can run any of the examples by executing:
|
||||
```bash
|
||||
# cargo run -p <example-crate-name> --example <name>
|
||||
cargo run -p examples-big-numbers --example math_operations
|
||||
```
|
||||
|
||||
## Add ethers-rs to your repository
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ repository = "https://github.com/gakonst/ethers-rs"
|
|||
keywords = ["ethereum", "web3", "celo", "ethers"]
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.16.0"
|
||||
once_cell = "1.17.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ ethers-derive-eip712 = { version = "^1.0.0", path = "../ethers-core/ethers-deriv
|
|||
serde = { version = "1.0.124", default-features = false }
|
||||
serde_json = { version = "1.0.64", default-features = false }
|
||||
thiserror = { version = "1.0", default-features = false }
|
||||
once_cell = { version = "1.16.0" }
|
||||
once_cell = { version = "1.17.0" }
|
||||
pin-project = { version = "1.0.11", default-features = false }
|
||||
futures-util = { version = "^0.3" }
|
||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
[package]
|
||||
name = "ethers-contract-abigen"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = [
|
||||
"Nicholas Rodrigues Lordello <nlordell@gmail.com>",
|
||||
"Georgios Konstantopoulos <me@gakonst.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Code generation for type-safe bindings to Ethereum smart contracts"
|
||||
homepage = "https://docs.rs/ethers"
|
||||
|
|
|
@ -31,6 +31,12 @@ impl std::ops::Deref for MultiAbigen {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for MultiAbigen {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.abigens
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Abigen>> for MultiAbigen {
|
||||
fn from(abigens: Vec<Abigen>) -> Self {
|
||||
Self { abigens }
|
||||
|
@ -372,6 +378,16 @@ impl MultiBindings {
|
|||
self.expansion.contracts.is_empty()
|
||||
}
|
||||
|
||||
/// Specify whether or not to format the code using a locally installed copy
|
||||
/// of `rustfmt`.
|
||||
///
|
||||
/// Note that in case `rustfmt` does not exist or produces an error, the
|
||||
/// unformatted code will be used.
|
||||
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
||||
self.rustfmt = rustfmt;
|
||||
self
|
||||
}
|
||||
|
||||
fn into_inner(self, single_file: bool) -> MultiBindingsInner {
|
||||
self.expansion.into_bindings(single_file, self.rustfmt)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ pub enum Source {
|
|||
/// An ABI to be retrieved over HTTP(S).
|
||||
Http(Url),
|
||||
|
||||
/// An address of a mainnet contract that has been verified on Bscscan.com.
|
||||
Bscscan(Address),
|
||||
|
||||
/// An address of a mainnet contract that has been verified on Etherscan.io.
|
||||
Etherscan(Address),
|
||||
|
||||
|
@ -54,7 +57,12 @@ impl Source {
|
|||
///
|
||||
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a address or URL of a
|
||||
/// verified contract on Etherscan.
|
||||
///
|
||||
/// - `bscscan:0xXX..XX` or `https://bscscan.io/address/0xXX..XX`: a address or URL of a
|
||||
/// verified contract on Bscscan.
|
||||
/// - `polygonscan:0xXX..XX` or `https://polygonscan.io/address/0xXX..XX`: a address or URL of a
|
||||
/// verified contract on Polygonscan.
|
||||
/// - `snowtrace:0xXX..XX` or `https://snowtrace.io/address/0xXX..XX`: a address or URL of a
|
||||
/// verified contract on Snowtrace.
|
||||
/// - `npm:@org/package@1.0.0/path/to/contract.json` an npmjs package with an optional version
|
||||
/// and path (defaulting to the latest version and `index.js`). The contract ABI will be
|
||||
/// retrieved through `unpkg.io`.
|
||||
|
@ -99,6 +107,12 @@ impl Source {
|
|||
match url.scheme() {
|
||||
"file" => Ok(Source::local(source)),
|
||||
"http" | "https" => match url.host_str() {
|
||||
Some("bscscan.com") => Source::etherscan(
|
||||
url.path()
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("HTTP URL does not have a path"))?,
|
||||
),
|
||||
Some("etherscan.io") => Source::etherscan(
|
||||
url.path()
|
||||
.rsplit('/')
|
||||
|
@ -119,6 +133,7 @@ impl Source {
|
|||
),
|
||||
_ => Ok(Source::Http(url)),
|
||||
},
|
||||
"bscscan" => Source::bscscan(url.path()),
|
||||
"etherscan" => Source::etherscan(url.path()),
|
||||
"polygonscan" => Source::polygonscan(url.path()),
|
||||
"snowtrace" => Source::snowtrace(url.path()),
|
||||
|
@ -140,6 +155,16 @@ impl Source {
|
|||
Ok(Source::Http(Url::parse(url.as_ref())?))
|
||||
}
|
||||
|
||||
/// Creates an Bscscan source from an address string.
|
||||
pub fn bscscan<S>(address: S) -> Result<Self>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let address =
|
||||
util::parse_address(address).context("failed to parse address for Bscscan source")?;
|
||||
Ok(Source::Bscscan(address))
|
||||
}
|
||||
|
||||
/// Creates an Etherscan source from an address string.
|
||||
pub fn etherscan<S>(address: S) -> Result<Self>
|
||||
where
|
||||
|
@ -187,6 +212,7 @@ impl Source {
|
|||
match self {
|
||||
Source::Local(path) => get_local_contract(path),
|
||||
Source::Http(_) => panic!("Http abi location are not supported for wasm"),
|
||||
Source::Bscscan(_) => panic!("Bscscan abi location are not supported for wasm"),
|
||||
Source::Etherscan(_) => panic!("Etherscan abi location are not supported for wasm"),
|
||||
Source::Polygonscan(_) => panic!("Polygonscan abi location are not supported for wasm"),
|
||||
Source::Snowtrace(_) => panic!("Snowtrace abi location are not supported for wasm"),
|
||||
|
@ -197,6 +223,7 @@ impl Source {
|
|||
match self {
|
||||
Source::Local(path) => get_local_contract(path),
|
||||
Source::Http(url) => get_http_contract(url),
|
||||
Source::Bscscan(address) => get_etherscan_contract(*address, "bscscan.com"),
|
||||
Source::Etherscan(address) => get_etherscan_contract(*address, "etherscan.io"),
|
||||
Source::Polygonscan(address) => get_etherscan_contract(*address, "polygonscan.com"),
|
||||
Source::Snowtrace(address) => get_etherscan_contract(*address, "snowtrace.io"),
|
||||
|
@ -261,6 +288,7 @@ fn get_etherscan_contract(address: Address, domain: &str) -> Result<String> {
|
|||
// probably don't reference anything when deploying on other networks.
|
||||
let api_key = {
|
||||
let key_res = match domain {
|
||||
"bscscan.com" => env::var("BSCSCAN_API_KEY").ok(),
|
||||
"etherscan.io" => env::var("ETHERSCAN_API_KEY").ok(),
|
||||
"polygonscan.com" => env::var("POLYGONSCAN_API_KEY").ok(),
|
||||
"snowtrace.io" => env::var("SNOWTRACE_API_KEY").ok(),
|
||||
|
@ -311,6 +339,10 @@ mod tests {
|
|||
"https://my.domain.eth/path/to/Contract.json",
|
||||
Source::http("https://my.domain.eth/path/to/Contract.json").unwrap(),
|
||||
),
|
||||
(
|
||||
"bscscan:0x0001020304050607080910111213141516171819",
|
||||
Source::bscscan("0x0001020304050607080910111213141516171819").unwrap(),
|
||||
),
|
||||
(
|
||||
"etherscan:0x0001020304050607080910111213141516171819",
|
||||
Source::etherscan("0x0001020304050607080910111213141516171819").unwrap(),
|
||||
|
@ -323,6 +355,10 @@ mod tests {
|
|||
"snowtrace:0x0001020304050607080910111213141516171819",
|
||||
Source::snowtrace("0x0001020304050607080910111213141516171819").unwrap(),
|
||||
),
|
||||
(
|
||||
"https://bscscan.io/address/0x0001020304050607080910111213141516171819",
|
||||
Source::bscscan("0x0001020304050607080910111213141516171819").unwrap(),
|
||||
),
|
||||
(
|
||||
"https://etherscan.io/address/0x0001020304050607080910111213141516171819",
|
||||
Source::etherscan("0x0001020304050607080910111213141516171819").unwrap(),
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
[package]
|
||||
name = "ethers-contract-derive"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = [
|
||||
"Nicholas Rodrigues Lordello <nlordell@gmail.com>",
|
||||
"Georgios Konstantopoulos <me@gakonst.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Proc macro for type-safe bindings generation to Ethereum and Celo smart contracts"
|
||||
homepage = "https://docs.rs/ethers"
|
||||
|
@ -17,7 +18,7 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
ethers-core = { version = "^1.0.0", path = "../../ethers-core" }
|
||||
ethers-contract-abigen = { version = "^1.0.0", path = "../ethers-contract-abigen" }
|
||||
ethers-contract-abigen = { version = "^1.0.0", path = "../ethers-contract-abigen", default-features = false }
|
||||
|
||||
serde_json = "1.0.53"
|
||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||
|
|
|
@ -437,6 +437,27 @@ impl<M: Middleware> Multicall<M> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Appends multiple `call`s to the list of calls of the Multicall instance.
|
||||
///
|
||||
/// Version specific details:
|
||||
/// - 1: `allow_failure` is ignored.
|
||||
/// - >=2: `allow_failure` specifies whether or not this call is allowed to revert in the
|
||||
/// multicall.
|
||||
/// - 3: Transaction values are used when broadcasting transactions with [`send`], otherwise
|
||||
/// they are always ignored.
|
||||
///
|
||||
/// [`send`]: #method.send
|
||||
pub fn add_calls<D: Detokenize>(
|
||||
&mut self,
|
||||
allow_failure: bool,
|
||||
calls: impl IntoIterator<Item = ContractCall<M, D>>,
|
||||
) -> &mut Self {
|
||||
for call in calls {
|
||||
self.add_call(call, allow_failure);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends a `call` to the list of calls of the Multicall instance for querying the block hash
|
||||
/// of a given block number.
|
||||
///
|
||||
|
@ -615,6 +636,45 @@ impl<M: Middleware> Multicall<M> {
|
|||
Ok(data)
|
||||
}
|
||||
|
||||
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract, assuming
|
||||
/// that every call returns same data type.
|
||||
///
|
||||
/// Note: this method _does not_ send a transaction from your account.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
||||
/// the tokens back to the expected return type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The return type must be annotated while calling this method:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// # use ethers_core::types::{U256, Address};
|
||||
/// # use ethers_providers::{Provider, Http};
|
||||
/// # use ethers_contract::Multicall;
|
||||
/// # use std::convert::TryFrom;
|
||||
/// #
|
||||
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
|
||||
/// #
|
||||
/// # let multicall = Multicall::new(client, None).await?;
|
||||
/// // If the all Solidity function calls `returns (uint256)`:
|
||||
/// let result: Vec<U256> = multicall.call_array().await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn call_array<D: Detokenize>(&self) -> Result<Vec<D>, M> {
|
||||
let tokens = self.call_raw().await?;
|
||||
let res: std::result::Result<Vec<D>, ContractError<M>> = tokens
|
||||
.into_iter()
|
||||
.map(|token| D::from_tokens(vec![token]).map_err(ContractError::DetokenizationError))
|
||||
.collect();
|
||||
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract and
|
||||
/// without detokenization.
|
||||
///
|
||||
|
|
|
@ -15,7 +15,7 @@ mod eth_tests {
|
|||
use ethers_derive_eip712::*;
|
||||
use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt};
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
|
||||
|
||||
#[tokio::test]
|
||||
async fn deploy_and_call_contract() {
|
||||
|
@ -517,10 +517,26 @@ mod eth_tests {
|
|||
.add_get_eth_balance(addrs[5], false)
|
||||
.add_get_eth_balance(addrs[6], false);
|
||||
|
||||
let valid_balances = [
|
||||
U256::from(10_000_000_000_000_000_000_000u128),
|
||||
U256::from(10_000_000_000_000_000_000_000u128),
|
||||
U256::from(10_000_000_000_000_000_000_000u128),
|
||||
];
|
||||
|
||||
let balances: (U256, U256, U256) = multicall.call().await.unwrap();
|
||||
assert_eq!(balances.0, U256::from(10_000_000_000_000_000_000_000u128));
|
||||
assert_eq!(balances.1, U256::from(10_000_000_000_000_000_000_000u128));
|
||||
assert_eq!(balances.2, U256::from(10_000_000_000_000_000_000_000u128));
|
||||
assert_eq!(balances.0, valid_balances[0]);
|
||||
assert_eq!(balances.1, valid_balances[1]);
|
||||
assert_eq!(balances.2, valid_balances[2]);
|
||||
|
||||
// call_array
|
||||
multicall
|
||||
.clear_calls()
|
||||
.add_get_eth_balance(addrs[4], false)
|
||||
.add_get_eth_balance(addrs[5], false)
|
||||
.add_get_eth_balance(addrs[6], false);
|
||||
|
||||
let balances: Vec<U256> = multicall.call_array().await.unwrap();
|
||||
assert_eq!(balances, Vec::from_iter(valid_balances.iter().copied()));
|
||||
|
||||
// clear multicall so we can test `call_raw` w/ >16 calls
|
||||
multicall.clear_calls();
|
||||
|
@ -536,10 +552,11 @@ mod eth_tests {
|
|||
.unwrap();
|
||||
|
||||
// build up a list of calls greater than the 16 max restriction
|
||||
for i in 0..=16 {
|
||||
let call = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
||||
multicall.add_call(call, false);
|
||||
}
|
||||
multicall.add_calls(
|
||||
false,
|
||||
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
|
||||
.take(17), // .collect(),
|
||||
);
|
||||
|
||||
// must use `call_raw` as `.calls` > 16
|
||||
let tokens = multicall.call_raw().await.unwrap();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
[package]
|
||||
name = "ethers-core"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Core structures for the ethers-rs crate"
|
||||
homepage = "https://docs.rs/ethers"
|
||||
repository = "https://github.com/gakonst/ethers-rs"
|
||||
|
@ -31,7 +31,7 @@ serde_json = { version = "1.0.64", default-features = false, features = ["arbitr
|
|||
thiserror = { version = "1.0", default-features = false }
|
||||
bytes = { version = "1.3.0", features = ["serde"] }
|
||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||
once_cell = { version = "1.16.0", optional = true }
|
||||
once_cell = { version = "1.17.0", optional = true }
|
||||
unicode-xid = "0.2.4"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
|
||||
|
@ -40,14 +40,15 @@ cargo_metadata = { version = "0.15.2", optional = true }
|
|||
|
||||
# eip712 feature enabled dependencies
|
||||
convert_case = { version = "0.6.0", optional = true }
|
||||
syn = { version = "1.0.105", optional = true }
|
||||
proc-macro2 = { version = "1.0.47", optional = true }
|
||||
syn = { version = "1.0.107", optional = true }
|
||||
proc-macro2 = { version = "1.0.49", optional = true }
|
||||
num_enum = "0.5.7"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { version = "3.3.0", default-features = false }
|
||||
serde_json = { version = "1.0.64", default-features = false }
|
||||
bincode = { version = "1.3.3", default-features = false }
|
||||
once_cell = { version = "1.16.0" }
|
||||
once_cell = { version = "1.17.0" }
|
||||
hex-literal = "0.3.4"
|
||||
rand = "0.8.5"
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
[package]
|
||||
name = "ethers-derive-eip712"
|
||||
version = "1.0.2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
description = "Custom derive macro for EIP-712 typed data"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
|
@ -11,7 +12,10 @@ proc-macro = true
|
|||
[dependencies]
|
||||
quote = "1.0.9"
|
||||
syn = "1.0.77"
|
||||
ethers-core = { version = "^1.0.0", path = "../", default-features = false, features = ["eip712", "macros"] }
|
||||
ethers-core = { version = "^1.0.0", path = "../", default-features = false, features = [
|
||||
"eip712",
|
||||
"macros",
|
||||
] }
|
||||
hex = "0.4.3"
|
||||
serde_json = "1.0.68"
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ pub fn determine_ethers_crates() -> (&'static str, &'static str, &'static str) {
|
|||
let needs_lock_file_cleanup = !std::path::Path::new(&lock_file).exists();
|
||||
|
||||
let res = MetadataCommand::new()
|
||||
.manifest_path(&format!("{manifest_dir}/Cargo.toml"))
|
||||
.manifest_path(format!("{manifest_dir}/Cargo.toml"))
|
||||
.exec()
|
||||
.ok()
|
||||
.and_then(|metadata| {
|
||||
|
|
|
@ -1,138 +1,160 @@
|
|||
use super::U256;
|
||||
use super::{U128, U256, U512, U64};
|
||||
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use strum::EnumVariantNames;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[error("Failed to parse chain: {0}")]
|
||||
pub struct ParseChainError(String);
|
||||
use strum::{AsRefStr, EnumString, EnumVariantNames};
|
||||
|
||||
// When adding a new chain:
|
||||
// 1. add new variant to the Chain enum;
|
||||
// 2. update Display/FromStr impl;
|
||||
// 3. add etherscan_keys if supported.
|
||||
// 2. add extra information in the last `impl` block (explorer URLs, block time) when applicable;
|
||||
// 3. (optional) add aliases: `#[strum(serialize = "main", serialize = "alias", ...)]`;
|
||||
// "main" must be present and will be used in `Display`, `Serialize` and `FromStr`,
|
||||
// while the aliases will be added only to `FromStr`.
|
||||
|
||||
/// Enum for all known chains.
|
||||
#[repr(u64)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Deserialize, EnumVariantNames)]
|
||||
/// An Ethereum EIP-155 chain.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
AsRefStr, // also for fmt::Display and serde::Serialize
|
||||
EnumVariantNames, // Self::VARIANTS
|
||||
EnumString, // FromStr, TryFrom<&str>
|
||||
TryFromPrimitive, // TryFrom<u64>
|
||||
Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[repr(u64)]
|
||||
pub enum Chain {
|
||||
#[default]
|
||||
Mainnet = 1,
|
||||
Morden = 2,
|
||||
Ropsten = 3,
|
||||
Rinkeby = 4,
|
||||
Goerli = 5,
|
||||
Optimism = 10,
|
||||
Cronos = 25,
|
||||
Rsk = 30,
|
||||
Kovan = 42,
|
||||
#[strum(serialize = "bsc")]
|
||||
BinanceSmartChain = 56,
|
||||
Sepolia = 11155111,
|
||||
|
||||
Optimism = 10,
|
||||
OptimismKovan = 69,
|
||||
Sokol = 77,
|
||||
#[strum(serialize = "bsc-testnet")]
|
||||
BinanceSmartChainTestnet = 97,
|
||||
Poa = 99,
|
||||
#[strum(serialize = "gnosis")]
|
||||
XDai = 100,
|
||||
Polygon = 137,
|
||||
Fantom = 250,
|
||||
CronosTestnet = 338,
|
||||
OptimismGoerli = 420,
|
||||
MoonbeamDev = 1281,
|
||||
Moonbeam = 1284,
|
||||
Moonriver = 1285,
|
||||
Moonbase = 1287,
|
||||
Dev = 1337,
|
||||
FantomTestnet = 4002,
|
||||
EvmosTestnet = 9000,
|
||||
Evmos = 9001,
|
||||
Chiado = 10200,
|
||||
Oasis = 26863,
|
||||
AnvilHardhat = 31337,
|
||||
|
||||
Arbitrum = 42161,
|
||||
EmeraldTestnet = 42261,
|
||||
Emerald = 42262,
|
||||
AvalancheFuji = 43113,
|
||||
Avalanche = 43114,
|
||||
PolygonMumbai = 80001,
|
||||
ArbitrumTestnet = 421611,
|
||||
ArbitrumGoerli = 421613,
|
||||
Sepolia = 11155111,
|
||||
Aurora = 1313161554,
|
||||
AuroraTestnet = 1313161555,
|
||||
|
||||
Cronos = 25,
|
||||
CronosTestnet = 338,
|
||||
|
||||
Rsk = 30,
|
||||
|
||||
#[strum(serialize = "bsc")]
|
||||
BinanceSmartChain = 56,
|
||||
#[strum(serialize = "bsc-testnet")]
|
||||
BinanceSmartChainTestnet = 97,
|
||||
|
||||
Poa = 99,
|
||||
Sokol = 77,
|
||||
|
||||
#[strum(serialize = "gnosis", serialize = "xdai", serialize = "gnosis-chain")]
|
||||
XDai = 100,
|
||||
|
||||
Polygon = 137,
|
||||
#[strum(serialize = "mumbai", serialize = "polygon-mumbai")]
|
||||
PolygonMumbai = 80001,
|
||||
|
||||
Fantom = 250,
|
||||
FantomTestnet = 4002,
|
||||
|
||||
Moonbeam = 1284,
|
||||
MoonbeamDev = 1281,
|
||||
|
||||
Moonriver = 1285,
|
||||
|
||||
Moonbase = 1287,
|
||||
|
||||
#[strum(serialize = "dev")]
|
||||
Dev = 1337,
|
||||
#[strum(serialize = "anvil-hardhat", serialize = "anvil", serialize = "hardhat")]
|
||||
AnvilHardhat = 31337,
|
||||
|
||||
Evmos = 9001,
|
||||
EvmosTestnet = 9000,
|
||||
|
||||
Chiado = 10200,
|
||||
|
||||
Oasis = 26863,
|
||||
|
||||
Emerald = 42262,
|
||||
EmeraldTestnet = 42261,
|
||||
|
||||
Avalanche = 43114,
|
||||
#[strum(serialize = "fuji", serialize = "avalanche-fuji")]
|
||||
AvalancheFuji = 43113,
|
||||
|
||||
Celo = 42220,
|
||||
CeloAlfajores = 44787,
|
||||
CeloBaklava = 62320,
|
||||
|
||||
Aurora = 1313161554,
|
||||
AuroraTestnet = 1313161555,
|
||||
}
|
||||
|
||||
// === impl Chain ===
|
||||
|
||||
impl fmt::Display for Chain {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let chain = match self {
|
||||
Chain::Mainnet => "mainnet",
|
||||
Chain::Morden => "morden",
|
||||
Chain::Ropsten => "ropsten",
|
||||
Chain::Rinkeby => "rinkeby",
|
||||
Chain::Goerli => "goerli",
|
||||
Chain::Kovan => "kovan",
|
||||
Chain::XDai => "gnosis",
|
||||
Chain::Chiado => "chiado",
|
||||
Chain::Polygon => "polygon",
|
||||
Chain::PolygonMumbai => "mumbai",
|
||||
Chain::Avalanche => "avalanche",
|
||||
Chain::AvalancheFuji => "fuji",
|
||||
Chain::Sepolia => "sepolia",
|
||||
Chain::Moonbeam => "moonbeam",
|
||||
Chain::Moonbase => "moonbase",
|
||||
Chain::MoonbeamDev => "moonbeam-dev",
|
||||
Chain::Moonriver => "moonriver",
|
||||
Chain::Optimism => "optimism",
|
||||
Chain::OptimismGoerli => "optimism-goerli",
|
||||
Chain::OptimismKovan => "optimism-kovan",
|
||||
Chain::Fantom => "fantom",
|
||||
Chain::Dev => "dev",
|
||||
Chain::FantomTestnet => "fantom-testnet",
|
||||
Chain::BinanceSmartChain => "bsc",
|
||||
Chain::BinanceSmartChainTestnet => "bsc-testnet",
|
||||
Chain::Arbitrum => "arbitrum",
|
||||
Chain::ArbitrumTestnet => "arbitrum-testnet",
|
||||
Chain::ArbitrumGoerli => "arbitrum-goerli",
|
||||
Chain::Cronos => "cronos",
|
||||
Chain::CronosTestnet => "cronos-testnet",
|
||||
Chain::Poa => "poa",
|
||||
Chain::Sokol => "sokol",
|
||||
Chain::Rsk => "rsk",
|
||||
Chain::Oasis => "oasis",
|
||||
Chain::Emerald => "emerald",
|
||||
Chain::EmeraldTestnet => "emerald-testnet",
|
||||
Chain::AnvilHardhat => "anvil-hardhat",
|
||||
Chain::Evmos => "evmos",
|
||||
Chain::EvmosTestnet => "evmos-testnet",
|
||||
Chain::Aurora => "aurora",
|
||||
Chain::AuroraTestnet => "aurora-testnet",
|
||||
Chain::Celo => "celo",
|
||||
Chain::CeloAlfajores => "celo-alfajores",
|
||||
Chain::CeloBaklava => "celo-baklava",
|
||||
};
|
||||
|
||||
f.pad(chain)
|
||||
// This must be implemented manually so we avoid a conflict with `TryFromPrimitive` where it treats
|
||||
// the `#[default]` attribute as its own `#[num_enum(default)]`
|
||||
impl Default for Chain {
|
||||
fn default() -> Self {
|
||||
Self::Mainnet
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Chain> for u32 {
|
||||
fn from(chain: Chain) -> Self {
|
||||
chain as u32
|
||||
}
|
||||
macro_rules! impl_into_numeric {
|
||||
($($ty:ty)+) => {$(
|
||||
impl From<Chain> for $ty {
|
||||
fn from(chain: Chain) -> Self {
|
||||
u64::from(chain).into()
|
||||
}
|
||||
}
|
||||
)+};
|
||||
}
|
||||
|
||||
macro_rules! impl_try_from_numeric {
|
||||
($($native:ty)+ ; $($primitive:ty)*) => {
|
||||
$(
|
||||
impl TryFrom<$native> for Chain {
|
||||
type Error = TryFromPrimitiveError<Self>;
|
||||
|
||||
fn try_from(value: $native) -> Result<Self, Self::Error> {
|
||||
(value as u64).try_into()
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
||||
$(
|
||||
impl TryFrom<$primitive> for Chain {
|
||||
type Error = TryFromPrimitiveError<Self>;
|
||||
|
||||
fn try_from(value: $primitive) -> Result<Self, Self::Error> {
|
||||
if value.bits() > 64 {
|
||||
// `TryFromPrimitiveError` only has a `number` field which has the same type
|
||||
// as the `#[repr(_)]` attribute on the enum.
|
||||
return Err(TryFromPrimitiveError { number: value.low_u64() })
|
||||
}
|
||||
value.low_u64().try_into()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl From<Chain> for u64 {
|
||||
|
@ -141,134 +163,21 @@ impl From<Chain> for u64 {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Chain> for U256 {
|
||||
fn from(chain: Chain) -> Self {
|
||||
u64::from(chain).into()
|
||||
impl_into_numeric!(u128 U64 U128 U256 U512);
|
||||
|
||||
impl TryFrom<U64> for Chain {
|
||||
type Error = TryFromPrimitiveError<Self>;
|
||||
|
||||
fn try_from(value: U64) -> Result<Self, Self::Error> {
|
||||
value.low_u64().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Chain {
|
||||
type Error = ParseChainError;
|
||||
impl_try_from_numeric!(u8 u16 u32 usize; U128 U256 U512);
|
||||
|
||||
fn try_from(chain: u32) -> Result<Chain, Self::Error> {
|
||||
(chain as u64).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for Chain {
|
||||
type Error = ParseChainError;
|
||||
|
||||
fn try_from(chain: u64) -> Result<Chain, Self::Error> {
|
||||
Ok(match chain {
|
||||
1 => Chain::Mainnet,
|
||||
2 => Chain::Morden,
|
||||
3 => Chain::Ropsten,
|
||||
4 => Chain::Rinkeby,
|
||||
5 => Chain::Goerli,
|
||||
42 => Chain::Kovan,
|
||||
100 => Chain::XDai,
|
||||
10200 => Chain::Chiado,
|
||||
137 => Chain::Polygon,
|
||||
1337 => Chain::Dev,
|
||||
31337 => Chain::AnvilHardhat,
|
||||
250 => Chain::Fantom,
|
||||
4002 => Chain::FantomTestnet,
|
||||
80001 => Chain::PolygonMumbai,
|
||||
43114 => Chain::Avalanche,
|
||||
43113 => Chain::AvalancheFuji,
|
||||
11155111 => Chain::Sepolia,
|
||||
1284 => Chain::Moonbeam,
|
||||
1287 => Chain::Moonbase,
|
||||
1281 => Chain::MoonbeamDev,
|
||||
1285 => Chain::Moonriver,
|
||||
10 => Chain::Optimism,
|
||||
420 => Chain::OptimismGoerli,
|
||||
69 => Chain::OptimismKovan,
|
||||
56 => Chain::BinanceSmartChain,
|
||||
97 => Chain::BinanceSmartChainTestnet,
|
||||
42161 => Chain::Arbitrum,
|
||||
421611 => Chain::ArbitrumTestnet,
|
||||
421613 => Chain::ArbitrumGoerli,
|
||||
25 => Chain::Cronos,
|
||||
338 => Chain::CronosTestnet,
|
||||
99 => Chain::Poa,
|
||||
77 => Chain::Sokol,
|
||||
30 => Chain::Rsk,
|
||||
26863 => Chain::Oasis,
|
||||
42262 => Chain::Emerald,
|
||||
42261 => Chain::EmeraldTestnet,
|
||||
9001 => Chain::Evmos,
|
||||
9000 => Chain::EvmosTestnet,
|
||||
1313161554 => Chain::Aurora,
|
||||
1313161555 => Chain::AuroraTestnet,
|
||||
42220 => Chain::Celo,
|
||||
44787 => Chain::CeloAlfajores,
|
||||
62320 => Chain::CeloBaklava,
|
||||
_ => return Err(ParseChainError(chain.to_string())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<U256> for Chain {
|
||||
type Error = ParseChainError;
|
||||
|
||||
fn try_from(chain: U256) -> Result<Chain, Self::Error> {
|
||||
if chain.bits() > 64 {
|
||||
return Err(ParseChainError(chain.to_string()))
|
||||
}
|
||||
chain.as_u64().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Chain {
|
||||
type Err = ParseChainError;
|
||||
|
||||
fn from_str(chain: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match chain {
|
||||
"mainnet" => Chain::Mainnet,
|
||||
"morden" => Chain::Morden,
|
||||
"ropsten" => Chain::Ropsten,
|
||||
"rinkeby" => Chain::Rinkeby,
|
||||
"goerli" => Chain::Goerli,
|
||||
"kovan" => Chain::Kovan,
|
||||
"xdai" | "gnosis" | "gnosis-chain" => Chain::XDai,
|
||||
"chiado" => Chain::Chiado,
|
||||
"polygon" => Chain::Polygon,
|
||||
"mumbai" | "polygon-mumbai" => Chain::PolygonMumbai,
|
||||
"avalanche" => Chain::Avalanche,
|
||||
"fuji" | "avalanche-fuji" => Chain::AvalancheFuji,
|
||||
"sepolia" => Chain::Sepolia,
|
||||
"moonbeam" => Chain::Moonbeam,
|
||||
"moonbase" => Chain::Moonbase,
|
||||
"moonbeam-dev" => Chain::MoonbeamDev,
|
||||
"moonriver" => Chain::Moonriver,
|
||||
"optimism" => Chain::Optimism,
|
||||
"optimism-goerli" => Chain::OptimismGoerli,
|
||||
"optimism-kovan" => Chain::OptimismKovan,
|
||||
"fantom" => Chain::Fantom,
|
||||
"fantom-testnet" => Chain::FantomTestnet,
|
||||
"dev" => Chain::Dev,
|
||||
"anvil" | "hardhat" | "anvil-hardhat" => Chain::AnvilHardhat,
|
||||
"bsc" => Chain::BinanceSmartChain,
|
||||
"bsc-testnet" => Chain::BinanceSmartChainTestnet,
|
||||
"arbitrum" => Chain::Arbitrum,
|
||||
"arbitrum-testnet" => Chain::ArbitrumTestnet,
|
||||
"arbitrum-goerli" => Chain::ArbitrumGoerli,
|
||||
"cronos" => Chain::Cronos,
|
||||
"cronos-testnet" => Chain::CronosTestnet,
|
||||
"poa" => Chain::Poa,
|
||||
"sokol" => Chain::Sokol,
|
||||
"rsk" => Chain::Rsk,
|
||||
"oasis" => Chain::Oasis,
|
||||
"emerald" => Chain::Emerald,
|
||||
"emerald-testnet" => Chain::EmeraldTestnet,
|
||||
"aurora" => Chain::Aurora,
|
||||
"aurora-testnet" => Chain::AuroraTestnet,
|
||||
"celo" => Chain::Celo,
|
||||
"celo-alfajores" => Chain::CeloAlfajores,
|
||||
"celo-baklava" => Chain::CeloBaklava,
|
||||
_ => return Err(ParseChainError(chain.to_owned())),
|
||||
})
|
||||
impl fmt::Display for Chain {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,163 +186,135 @@ impl Serialize for Chain {
|
|||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_str(self.to_string().as_ref())
|
||||
s.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: all utility functions *should* be explicitly exhaustive (not use `_` matcher) so we don't
|
||||
// forget to update them when adding a new `Chain` variant.
|
||||
impl Chain {
|
||||
/// The blocktime varies from chain to chain.
|
||||
/// Returns the chain's average blocktime, if applicable.
|
||||
///
|
||||
/// It can be beneficial to know the average blocktime to adjust the polling of an HTTP provider
|
||||
/// for example.
|
||||
///
|
||||
/// **Note:** this will not return the accurate average depending on the time but is rather a
|
||||
/// sensible default derived from blocktime charts like <https://etherscan.com/chart/blocktime>
|
||||
/// <https://polygonscan.com/chart/blocktime>
|
||||
pub fn average_blocktime_hint(&self) -> Option<Duration> {
|
||||
/// **Note:** this is not an accurate average, but is rather a sensible default derived from
|
||||
/// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
|
||||
/// or [Polygonscan's](https://polygonscan.com/chart/blocktime).
|
||||
pub const fn average_blocktime_hint(&self) -> Option<Duration> {
|
||||
use Chain::*;
|
||||
|
||||
let ms = match self {
|
||||
Chain::Arbitrum | Chain::ArbitrumTestnet | Chain::ArbitrumGoerli => 1_300,
|
||||
Chain::Mainnet | Chain::Optimism => 13_000,
|
||||
Chain::Polygon | Chain::PolygonMumbai => 2_100,
|
||||
Chain::Moonbeam | Chain::Moonriver => 12_500,
|
||||
Chain::BinanceSmartChain | Chain::BinanceSmartChainTestnet => 3_000,
|
||||
Chain::Avalanche | Chain::AvalancheFuji => 2_000,
|
||||
Chain::Fantom | Chain::FantomTestnet => 1_200,
|
||||
Chain::Cronos | Chain::CronosTestnet => 5_700,
|
||||
Chain::Evmos | Chain::EvmosTestnet => 1_900,
|
||||
Chain::Aurora | Chain::AuroraTestnet => 1_100,
|
||||
Chain::Oasis => 5_500,
|
||||
Chain::Emerald => 6_000,
|
||||
Chain::Dev | Chain::AnvilHardhat => 200,
|
||||
Chain::Celo | Chain::CeloAlfajores | Chain::CeloBaklava => 5_000,
|
||||
Arbitrum | ArbitrumTestnet | ArbitrumGoerli => 1_300,
|
||||
Mainnet | Optimism => 13_000,
|
||||
Polygon | PolygonMumbai => 2_100,
|
||||
Moonbeam | Moonriver => 12_500,
|
||||
BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
|
||||
Avalanche | AvalancheFuji => 2_000,
|
||||
Fantom | FantomTestnet => 1_200,
|
||||
Cronos | CronosTestnet => 5_700,
|
||||
Evmos | EvmosTestnet => 1_900,
|
||||
Aurora | AuroraTestnet => 1_100,
|
||||
Oasis => 5_500,
|
||||
Emerald => 6_000,
|
||||
Dev | AnvilHardhat => 200,
|
||||
Celo | CeloAlfajores | CeloBaklava => 5_000,
|
||||
// Explictly handle all network to make it easier not to forget this match when new
|
||||
// networks are added.
|
||||
Chain::Morden |
|
||||
Chain::Ropsten |
|
||||
Chain::Rinkeby |
|
||||
Chain::Goerli |
|
||||
Chain::Kovan |
|
||||
Chain::XDai |
|
||||
Chain::Chiado |
|
||||
Chain::Sepolia |
|
||||
Chain::Moonbase |
|
||||
Chain::MoonbeamDev |
|
||||
Chain::OptimismGoerli |
|
||||
Chain::OptimismKovan |
|
||||
Chain::Poa |
|
||||
Chain::Sokol |
|
||||
Chain::Rsk |
|
||||
Chain::EmeraldTestnet => return None,
|
||||
Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
|
||||
MoonbeamDev | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet => {
|
||||
return None
|
||||
}
|
||||
};
|
||||
|
||||
Some(Duration::from_millis(ms))
|
||||
}
|
||||
|
||||
/// Returns the corresponding etherscan URLs
|
||||
/// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
|
||||
///
|
||||
/// Returns `(API URL, BASE_URL)`, like `("https://api(-chain).etherscan.io/api", "https://etherscan.io")`
|
||||
pub fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
|
||||
pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
|
||||
use Chain::*;
|
||||
|
||||
let urls = match self {
|
||||
Chain::Mainnet => ("https://api.etherscan.io/api", "https://etherscan.io"),
|
||||
Chain::Ropsten => {
|
||||
("https://api-ropsten.etherscan.io/api", "https://ropsten.etherscan.io")
|
||||
}
|
||||
Chain::Kovan => ("https://api-kovan.etherscan.io/api", "https://kovan.etherscan.io"),
|
||||
Chain::Rinkeby => {
|
||||
("https://api-rinkeby.etherscan.io/api", "https://rinkeby.etherscan.io")
|
||||
}
|
||||
Chain::Goerli => ("https://api-goerli.etherscan.io/api", "https://goerli.etherscan.io"),
|
||||
Chain::Sepolia => {
|
||||
("https://api-sepolia.etherscan.io/api", "https://sepolia.etherscan.io")
|
||||
}
|
||||
Chain::Polygon => ("https://api.polygonscan.com/api", "https://polygonscan.com"),
|
||||
Chain::PolygonMumbai => {
|
||||
Mainnet => ("https://api.etherscan.io/api", "https://etherscan.io"),
|
||||
Ropsten => ("https://api-ropsten.etherscan.io/api", "https://ropsten.etherscan.io"),
|
||||
Kovan => ("https://api-kovan.etherscan.io/api", "https://kovan.etherscan.io"),
|
||||
Rinkeby => ("https://api-rinkeby.etherscan.io/api", "https://rinkeby.etherscan.io"),
|
||||
Goerli => ("https://api-goerli.etherscan.io/api", "https://goerli.etherscan.io"),
|
||||
Sepolia => ("https://api-sepolia.etherscan.io/api", "https://sepolia.etherscan.io"),
|
||||
Polygon => ("https://api.polygonscan.com/api", "https://polygonscan.com"),
|
||||
PolygonMumbai => {
|
||||
("https://api-testnet.polygonscan.com/api", "https://mumbai.polygonscan.com")
|
||||
}
|
||||
Chain::Avalanche => ("https://api.snowtrace.io/api", "https://snowtrace.io"),
|
||||
Chain::AvalancheFuji => {
|
||||
Avalanche => ("https://api.snowtrace.io/api", "https://snowtrace.io"),
|
||||
AvalancheFuji => {
|
||||
("https://api-testnet.snowtrace.io/api", "https://testnet.snowtrace.io")
|
||||
}
|
||||
Chain::Optimism => {
|
||||
Optimism => {
|
||||
("https://api-optimistic.etherscan.io/api", "https://optimistic.etherscan.io")
|
||||
}
|
||||
Chain::OptimismGoerli => (
|
||||
OptimismGoerli => (
|
||||
"https://api-goerli-optimistic.etherscan.io/api",
|
||||
"https://goerli-optimism.etherscan.io",
|
||||
),
|
||||
Chain::OptimismKovan => (
|
||||
OptimismKovan => (
|
||||
"https://api-kovan-optimistic.etherscan.io/api",
|
||||
"https://kovan-optimistic.etherscan.io",
|
||||
),
|
||||
Chain::Fantom => ("https://api.ftmscan.com/api", "https://ftmscan.com"),
|
||||
Chain::FantomTestnet => {
|
||||
("https://api-testnet.ftmscan.com/api", "https://testnet.ftmscan.com")
|
||||
}
|
||||
Chain::BinanceSmartChain => ("https://api.bscscan.com/api", "https://bscscan.com"),
|
||||
Chain::BinanceSmartChainTestnet => {
|
||||
Fantom => ("https://api.ftmscan.com/api", "https://ftmscan.com"),
|
||||
FantomTestnet => ("https://api-testnet.ftmscan.com/api", "https://testnet.ftmscan.com"),
|
||||
BinanceSmartChain => ("https://api.bscscan.com/api", "https://bscscan.com"),
|
||||
BinanceSmartChainTestnet => {
|
||||
("https://api-testnet.bscscan.com/api", "https://testnet.bscscan.com")
|
||||
}
|
||||
Chain::Arbitrum => ("https://api.arbiscan.io/api", "https://arbiscan.io"),
|
||||
Chain::ArbitrumTestnet => {
|
||||
Arbitrum => ("https://api.arbiscan.io/api", "https://arbiscan.io"),
|
||||
ArbitrumTestnet => {
|
||||
("https://api-testnet.arbiscan.io/api", "https://testnet.arbiscan.io")
|
||||
}
|
||||
Chain::ArbitrumGoerli => (
|
||||
ArbitrumGoerli => (
|
||||
"https://goerli-rollup-explorer.arbitrum.io/api",
|
||||
"https://goerli-rollup-explorer.arbitrum.io",
|
||||
),
|
||||
Chain::Cronos => ("https://api.cronoscan.com/api", "https://cronoscan.com"),
|
||||
Chain::CronosTestnet => {
|
||||
Cronos => ("https://api.cronoscan.com/api", "https://cronoscan.com"),
|
||||
CronosTestnet => {
|
||||
("https://api-testnet.cronoscan.com/api", "https://testnet.cronoscan.com")
|
||||
}
|
||||
Chain::Moonbeam => {
|
||||
("https://api-moonbeam.moonscan.io/api", "https://moonbeam.moonscan.io/")
|
||||
}
|
||||
Chain::Moonbase => {
|
||||
("https://api-moonbase.moonscan.io/api", "https://moonbase.moonscan.io/")
|
||||
}
|
||||
Chain::Moonriver => {
|
||||
("https://api-moonriver.moonscan.io/api", "https://moonriver.moonscan.io")
|
||||
}
|
||||
Moonbeam => ("https://api-moonbeam.moonscan.io/api", "https://moonbeam.moonscan.io/"),
|
||||
Moonbase => ("https://api-moonbase.moonscan.io/api", "https://moonbase.moonscan.io/"),
|
||||
Moonriver => ("https://api-moonriver.moonscan.io/api", "https://moonriver.moonscan.io"),
|
||||
// blockscout API is etherscan compatible
|
||||
Chain::XDai => {
|
||||
XDai => {
|
||||
("https://blockscout.com/xdai/mainnet/api", "https://blockscout.com/xdai/mainnet")
|
||||
}
|
||||
Chain::Chiado => {
|
||||
Chiado => {
|
||||
("https://blockscout.chiadochain.net/api", "https://blockscout.chiadochain.net")
|
||||
}
|
||||
Chain::Sokol => {
|
||||
("https://blockscout.com/poa/sokol/api", "https://blockscout.com/poa/sokol")
|
||||
}
|
||||
Chain::Poa => {
|
||||
("https://blockscout.com/poa/core/api", "https://blockscout.com/poa/core")
|
||||
}
|
||||
Chain::Rsk => {
|
||||
("https://blockscout.com/rsk/mainnet/api", "https://blockscout.com/rsk/mainnet")
|
||||
}
|
||||
Chain::Oasis => ("https://scan.oasischain.io/api", "https://scan.oasischain.io/"),
|
||||
Chain::Emerald => {
|
||||
Sokol => ("https://blockscout.com/poa/sokol/api", "https://blockscout.com/poa/sokol"),
|
||||
Poa => ("https://blockscout.com/poa/core/api", "https://blockscout.com/poa/core"),
|
||||
Rsk => ("https://blockscout.com/rsk/mainnet/api", "https://blockscout.com/rsk/mainnet"),
|
||||
Oasis => ("https://scan.oasischain.io/api", "https://scan.oasischain.io/"),
|
||||
Emerald => {
|
||||
("https://explorer.emerald.oasis.dev/api", "https://explorer.emerald.oasis.dev/")
|
||||
}
|
||||
Chain::EmeraldTestnet => (
|
||||
EmeraldTestnet => (
|
||||
"https://testnet.explorer.emerald.oasis.dev/api",
|
||||
"https://testnet.explorer.emerald.oasis.dev/",
|
||||
),
|
||||
Chain::Aurora => ("https://api.aurorascan.dev/api", "https://aurorascan.dev"),
|
||||
Chain::AuroraTestnet => {
|
||||
Aurora => ("https://api.aurorascan.dev/api", "https://aurorascan.dev"),
|
||||
AuroraTestnet => {
|
||||
("https://testnet.aurorascan.dev/api", "https://testnet.aurorascan.dev")
|
||||
}
|
||||
Chain::Evmos => ("https://evm.evmos.org/api", "https://evm.evmos.org/"),
|
||||
Chain::EvmosTestnet => ("https://evm.evmos.dev/api", "https://evm.evmos.dev/"),
|
||||
Chain::Celo => {
|
||||
("https://explorer.celo.org/mainnet", "https://explorer.celo.org/mainnet/api")
|
||||
}
|
||||
Chain::CeloAlfajores => {
|
||||
Evmos => ("https://evm.evmos.org/api", "https://evm.evmos.org/"),
|
||||
EvmosTestnet => ("https://evm.evmos.dev/api", "https://evm.evmos.dev/"),
|
||||
Celo => ("https://explorer.celo.org/mainnet", "https://explorer.celo.org/mainnet/api"),
|
||||
CeloAlfajores => {
|
||||
("https://explorer.celo.org/alfajores", "https://explorer.celo.org/alfajores/api")
|
||||
}
|
||||
Chain::CeloBaklava => {
|
||||
CeloBaklava => {
|
||||
("https://explorer.celo.org/baklava", "https://explorer.celo.org/baklava/api")
|
||||
}
|
||||
Chain::AnvilHardhat | Chain::Dev | Chain::Morden | Chain::MoonbeamDev => {
|
||||
AnvilHardhat | Dev | Morden | MoonbeamDev => {
|
||||
// this is explicitly exhaustive so we don't forget to add new urls when adding a
|
||||
// new chain
|
||||
return None
|
||||
|
@ -443,34 +324,49 @@ impl Chain {
|
|||
Some(urls)
|
||||
}
|
||||
|
||||
/// Helper function for checking if a chainid corresponds to a legacy chainid
|
||||
/// without eip1559
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
// TODO: Add other chains which do not support EIP1559.
|
||||
matches!(
|
||||
self,
|
||||
Chain::Optimism |
|
||||
Chain::OptimismGoerli |
|
||||
Chain::OptimismKovan |
|
||||
Chain::Fantom |
|
||||
Chain::FantomTestnet |
|
||||
Chain::BinanceSmartChain |
|
||||
Chain::BinanceSmartChainTestnet |
|
||||
Chain::Arbitrum |
|
||||
Chain::ArbitrumTestnet |
|
||||
Chain::ArbitrumGoerli |
|
||||
Chain::Rsk |
|
||||
Chain::Oasis |
|
||||
Chain::Emerald |
|
||||
Chain::EmeraldTestnet |
|
||||
Chain::Celo |
|
||||
Chain::CeloAlfajores |
|
||||
Chain::CeloBaklava,
|
||||
)
|
||||
/// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
|
||||
pub const fn is_legacy(&self) -> bool {
|
||||
use Chain::*;
|
||||
|
||||
match self {
|
||||
// Known legacy chains / non EIP-1559 compliant
|
||||
Optimism |
|
||||
OptimismGoerli |
|
||||
OptimismKovan |
|
||||
Fantom |
|
||||
FantomTestnet |
|
||||
BinanceSmartChain |
|
||||
BinanceSmartChainTestnet |
|
||||
Arbitrum |
|
||||
ArbitrumTestnet |
|
||||
ArbitrumGoerli |
|
||||
Rsk |
|
||||
Oasis |
|
||||
Emerald |
|
||||
EmeraldTestnet |
|
||||
Celo |
|
||||
CeloAlfajores |
|
||||
CeloBaklava => true,
|
||||
|
||||
// Known EIP-1559 chains
|
||||
Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
|
||||
false
|
||||
}
|
||||
|
||||
// Unknown / not applicable, default to false for backwards compatibility
|
||||
Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
|
||||
Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
|
||||
EvmosTestnet | Chiado | Aurora | AuroraTestnet => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_chain() {
|
||||
assert_eq!(serde_json::to_string(&Chain::Mainnet).unwrap(), "\"mainnet\"");
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_chain() {
|
||||
assert_eq!(serde_json::to_string(&Chain::default()).unwrap(), "\"mainnet\"");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::types::{Bytes, H256, U256};
|
||||
use crate::{
|
||||
types::{Bytes, H256, U256},
|
||||
utils::from_int_or_hex,
|
||||
};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
|
@ -6,7 +9,8 @@ use std::collections::BTreeMap;
|
|||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GethTrace {
|
||||
pub failed: bool,
|
||||
pub gas: u64,
|
||||
#[serde(deserialize_with = "from_int_or_hex")]
|
||||
pub gas: U256,
|
||||
#[serde(serialize_with = "serialize_bytes", rename = "returnValue")]
|
||||
pub return_value: Bytes,
|
||||
#[serde(rename = "structLogs")]
|
||||
|
@ -35,6 +39,16 @@ pub struct StructLog {
|
|||
pub storage: Option<BTreeMap<H256, H256>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
/// Available built-in tracers
|
||||
///
|
||||
/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
|
||||
pub enum GethDebugTracerType {
|
||||
/// callTracer (native)
|
||||
#[serde(rename = "callTracer")]
|
||||
CallTracer,
|
||||
}
|
||||
|
||||
/// Bindings for additional `debug_traceTransaction` options
|
||||
///
|
||||
/// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
|
||||
|
@ -50,11 +64,22 @@ pub struct GethDebugTracingOptions {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enable_return_data: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tracer: Option<String>,
|
||||
pub tracer: Option<GethDebugTracerType>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<String>,
|
||||
}
|
||||
|
||||
/// Bindings for additional `debug_traceCall` options
|
||||
///
|
||||
/// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracecall>
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GethDebugTracingCallOptions {
|
||||
#[serde(flatten)]
|
||||
pub tracing_options: GethDebugTracingOptions,
|
||||
// TODO: Add stateoverrides and blockoverrides options
|
||||
}
|
||||
|
||||
fn serialize_bytes<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
|
|
@ -208,7 +208,7 @@ impl Eip1559TransactionRequest {
|
|||
*offset += 1;
|
||||
tx.gas = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
tx.to = decode_to(rlp, offset)?;
|
||||
tx.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
|
||||
tx.value = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
|
|
|
@ -75,7 +75,7 @@ fn decode_signature(
|
|||
fn decode_to(
|
||||
rlp: &rlp::Rlp,
|
||||
offset: &mut usize,
|
||||
) -> Result<Option<super::NameOrAddress>, rlp::DecoderError> {
|
||||
) -> Result<Option<super::Address>, rlp::DecoderError> {
|
||||
let to = {
|
||||
let to = rlp.at(*offset)?;
|
||||
if to.is_empty() {
|
||||
|
|
|
@ -233,7 +233,7 @@ impl TransactionRequest {
|
|||
*offset += 1;
|
||||
}
|
||||
|
||||
txn.to = decode_to(rlp, offset)?;
|
||||
txn.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
|
||||
txn.value = Some(rlp.at(*offset)?.as_val()?);
|
||||
*offset += 1;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Transaction types
|
||||
use super::{
|
||||
decode_signature, eip2718::TypedTransaction, eip2930::AccessList, normalize_v, rlp_opt,
|
||||
rlp_opt_list,
|
||||
decode_signature, decode_to, eip2718::TypedTransaction, eip2930::AccessList, normalize_v,
|
||||
rlp_opt, rlp_opt_list,
|
||||
};
|
||||
use crate::{
|
||||
types::{
|
||||
|
@ -248,8 +248,7 @@ impl Transaction {
|
|||
*offset += 1;
|
||||
self.gas = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
self.to = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.to = decode_to(rlp, offset)?;
|
||||
self.value = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
|
@ -279,8 +278,7 @@ impl Transaction {
|
|||
#[cfg(feature = "celo")]
|
||||
self.decode_celo_metadata(rlp, offset)?;
|
||||
|
||||
self.to = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.to = decode_to(rlp, offset)?;
|
||||
self.value = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
|
@ -310,8 +308,7 @@ impl Transaction {
|
|||
#[cfg(feature = "celo")]
|
||||
self.decode_celo_metadata(rlp, offset)?;
|
||||
|
||||
self.to = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.to = decode_to(rlp, offset)?;
|
||||
self.value = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
|
@ -473,7 +470,7 @@ impl PartialOrd<Self> for TransactionReceipt {
|
|||
#[cfg(test)]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
use rlp::Encodable;
|
||||
use rlp::{Encodable, Rlp};
|
||||
|
||||
use crate::types::transaction::eip2930::AccessListItem;
|
||||
|
||||
|
@ -1093,4 +1090,24 @@ mod tests {
|
|||
let r = rlp::Rlp::new(b.as_slice());
|
||||
Transaction::decode(&r).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rlp_decoding_create_roundtrip() {
|
||||
let tx = Transaction {
|
||||
block_hash: None,
|
||||
block_number: None,
|
||||
from: Address::from_str("c26ad91f4e7a0cad84c4b9315f420ca9217e315d").unwrap(),
|
||||
gas: U256::from_str_radix("0x10e2b", 16).unwrap(),
|
||||
gas_price: Some(U256::from_str_radix("0x12ec276caf", 16).unwrap()),
|
||||
hash: H256::from_str("929ff27a5c7833953df23103c4eb55ebdfb698678139d751c51932163877fada").unwrap(),
|
||||
input: Bytes::from(
|
||||
hex::decode("a9059cbb000000000000000000000000fdae129ecc2c27d166a3131098bc05d143fa258e0000000000000000000000000000000000000000000000000000000002faf080").unwrap()
|
||||
),
|
||||
nonce: U256::zero(),
|
||||
transaction_index: None,
|
||||
value: U256::zero(),
|
||||
..Default::default()
|
||||
};
|
||||
Transaction::decode(&Rlp::new(&tx.rlp())).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@ impl Default for PrivateNetOptions {
|
|||
/// ```
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Geth {
|
||||
program: Option<PathBuf>,
|
||||
port: Option<u16>,
|
||||
authrpc_port: Option<u16>,
|
||||
ipc_path: Option<PathBuf>,
|
||||
|
@ -180,6 +181,32 @@ impl Geth {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a Geth builder which will execute `geth` at the given path.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ethers_core::utils::Geth;
|
||||
/// # fn a() {
|
||||
/// let geth = Geth::at("../go-ethereum/build/bin/geth").spawn();
|
||||
///
|
||||
/// println!("Geth running at `{}`", geth.endpoint());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn at(path: impl Into<PathBuf>) -> Self {
|
||||
Self::new().path(path)
|
||||
}
|
||||
|
||||
/// Sets the `path` to the `geth` executable
|
||||
///
|
||||
/// By default, it's expected that `geth` is in `$PATH`, see also
|
||||
/// [`std::process::Command::new()`]
|
||||
#[must_use]
|
||||
pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
|
||||
self.program = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the port which will be used when the `geth-cli` instance is launched.
|
||||
#[must_use]
|
||||
pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
|
||||
|
@ -274,9 +301,10 @@ impl Geth {
|
|||
/// Consumes the builder and spawns `geth` with stdout redirected
|
||||
/// to /dev/null.
|
||||
pub fn spawn(self) -> GethInstance {
|
||||
let mut cmd = Command::new(GETH);
|
||||
let mut cmd =
|
||||
if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) };
|
||||
// geth uses stderr for its logs
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
let port = if let Some(port) = self.port { port } else { unused_port() };
|
||||
let authrpc_port = if let Some(port) = self.authrpc_port { port } else { unused_port() };
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ pub use geth::{Geth, GethInstance};
|
|||
|
||||
/// Utilities for working with a `genesis.json` and other chain config structs.
|
||||
mod genesis;
|
||||
pub use genesis::{ChainConfig, Genesis};
|
||||
pub use genesis::{ChainConfig, CliqueConfig, EthashConfig, Genesis, GenesisAccount};
|
||||
|
||||
/// Utilities for launching an anvil instance
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "ethers-etherscan"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = [
|
||||
"Matthias Seitz <matthias.seitz@outlook.de>",
|
||||
"Georgios Konstantopoulos <me@gakonst.com>",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
readme = "../README.md"
|
||||
documentation = "https://docs.rs/ethers"
|
||||
repository = "https://github.com/gakonst/ethers-rs"
|
||||
|
@ -26,7 +26,7 @@ serde_json = { version = "1.0.64", default-features = false }
|
|||
serde-aux = { version = "4.1.2", default-features = false }
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1.37"
|
||||
semver = "1.0.14"
|
||||
semver = "1.0.16"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# NOTE: this enables wasm compatibility for getrandom indirectly
|
||||
|
@ -35,7 +35,7 @@ getrandom = { version = "0.2", features = ["js"] }
|
|||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
|
||||
serial_test = "0.9.0"
|
||||
serial_test = "0.10.0"
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
|
||||
ethers-solc = { version = "^1.0.0", path = "../ethers-solc", default-features = false }
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
source_tree::{SourceTree, SourceTreeEntry},
|
||||
utils::{deserialize_address_opt, deserialize_stringified_source_code},
|
||||
utils::{deserialize_address_opt, deserialize_source_code},
|
||||
Client, EtherscanError, Response, Result,
|
||||
};
|
||||
use ethers_core::{
|
||||
|
@ -107,7 +107,7 @@ impl SourceCodeMetadata {
|
|||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Metadata {
|
||||
/// Includes metadata for compiler settings and language.
|
||||
#[serde(deserialize_with = "deserialize_stringified_source_code")]
|
||||
#[serde(deserialize_with = "deserialize_source_code")]
|
||||
pub source_code: SourceCodeMetadata,
|
||||
|
||||
/// The ABI of the contract.
|
||||
|
@ -148,7 +148,11 @@ pub struct Metadata {
|
|||
pub proxy: u64,
|
||||
|
||||
/// If this contract is a proxy, the address of its implementation.
|
||||
#[serde(deserialize_with = "deserialize_address_opt")]
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "deserialize_address_opt"
|
||||
)]
|
||||
pub implementation: Option<Address>,
|
||||
|
||||
/// The swarm source of the contract.
|
||||
|
|
|
@ -36,20 +36,35 @@ pub fn deserialize_address_opt<'de, D: Deserializer<'de>>(
|
|||
|
||||
/// Deserializes as JSON:
|
||||
///
|
||||
/// `{ "SourceCode": "{{ .. }}", ..}`
|
||||
/// Object: `{ "SourceCode": { language: "Solidity", .. }, ..}`
|
||||
///
|
||||
/// or
|
||||
///
|
||||
/// `{ "SourceCode": "..", .. }`
|
||||
pub fn deserialize_stringified_source_code<'de, D: Deserializer<'de>>(
|
||||
/// Stringified JSON: `{ "SourceCode": "{{\r\n \"language\": \"Solidity\", ..}}", ..}`
|
||||
///
|
||||
/// or
|
||||
///
|
||||
/// Normal source code: `{ "SourceCode": "// SPDX-License-Identifier: ...", .. }`
|
||||
pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<SourceCodeMetadata, D::Error> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s.starts_with("{{") && s.ends_with("}}") {
|
||||
let s = &s[1..s.len() - 1];
|
||||
serde_json::from_str(s).map_err(serde::de::Error::custom)
|
||||
} else {
|
||||
Ok(SourceCodeMetadata::SourceCode(s))
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum SourceCode {
|
||||
String(String), // this must come first
|
||||
Obj(SourceCodeMetadata),
|
||||
}
|
||||
let s = SourceCode::deserialize(deserializer)?;
|
||||
match s {
|
||||
SourceCode::String(s) => {
|
||||
if s.starts_with("{{") && s.ends_with("}}") {
|
||||
let s = &s[1..s.len() - 1];
|
||||
serde_json::from_str(s).map_err(serde::de::Error::custom)
|
||||
} else {
|
||||
Ok(SourceCodeMetadata::SourceCode(s))
|
||||
}
|
||||
}
|
||||
SourceCode::Obj(obj) => Ok(obj),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,17 +123,29 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_stringified_source_code() {
|
||||
fn can_deserialize_source_code() {
|
||||
#[derive(Deserialize)]
|
||||
struct Test {
|
||||
#[serde(deserialize_with = "deserialize_stringified_source_code")]
|
||||
#[serde(deserialize_with = "deserialize_source_code")]
|
||||
source_code: SourceCodeMetadata,
|
||||
}
|
||||
|
||||
let src = "source code text";
|
||||
|
||||
// Normal JSON
|
||||
let json = r#"{
|
||||
"source_code": "{{ \"language\": \"Solidity\", \"sources\": {\"Contract\": { \"content\": \"source code text\" } } }}"
|
||||
"source_code": { "language": "Solidity", "sources": { "Contract": { "content": "source code text" } } }
|
||||
}"#;
|
||||
let de: Test = serde_json::from_str(json).unwrap();
|
||||
assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
|
||||
assert_eq!(de.source_code.sources().len(), 1);
|
||||
assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
|
||||
#[cfg(feature = "ethers-solc")]
|
||||
assert!(matches!(de.source_code.settings().unwrap(), None));
|
||||
|
||||
// Stringified JSON
|
||||
let json = r#"{
|
||||
"source_code": "{{ \"language\": \"Solidity\", \"sources\": { \"Contract\": { \"content\": \"source code text\" } } }}"
|
||||
}"#;
|
||||
let de: Test = serde_json::from_str(json).unwrap();
|
||||
assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "ethers-middleware"
|
||||
version = "1.0.2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
@ -47,12 +47,16 @@ ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-f
|
|||
"ws",
|
||||
"rustls",
|
||||
] }
|
||||
once_cell = "1.16.0"
|
||||
once_cell = "1.17.0"
|
||||
ethers-solc = { version = "^1.0.0", path = "../ethers-solc" }
|
||||
serial_test = "0.9.0"
|
||||
serial_test = "0.10.0"
|
||||
reqwest = { version = "0.11.13", default-features = false, features = ["json", "rustls"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
tokio = { version = "1.18", default-features = false, features = ["rt", "macros", "time"] }
|
||||
|
||||
[features]
|
||||
default = ["rustls"]
|
||||
celo = ["ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo", "ethers-contract/celo"]
|
||||
openssl = ["reqwest/native-tls"]
|
||||
rustls = ["reqwest/rustls-tls"]
|
||||
|
|
|
@ -23,13 +23,13 @@ use ethers_providers::{Middleware, Provider, Http};
|
|||
use std::sync::Arc;
|
||||
use std::convert::TryFrom;
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use ethers_middleware::{*,gas_oracle::*};
|
||||
use ethers_middleware::{gas_oracle::{GasOracle, GasNow}, MiddlewareBuilder};
|
||||
|
||||
fn builder_example() {
|
||||
let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||
let signer = key.parse::<LocalWallet>().unwrap();
|
||||
let address = signer.address();
|
||||
let gas_oracle = EthGasStation::new(None);
|
||||
let gas_oracle = GasNow::new();
|
||||
|
||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||
.unwrap()
|
||||
|
@ -58,7 +58,7 @@ fn builder_example_wrap_into() {
|
|||
.unwrap()
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||
.wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
|
||||
.wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
|
||||
}
|
||||
```
|
||||
|
@ -72,7 +72,7 @@ use ethers_providers::{Provider, Http};
|
|||
use ethers_signers::{LocalWallet, Signer};
|
||||
use ethers_middleware::{
|
||||
gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
|
||||
gas_oracle::{GasOracleMiddleware, EthGasStation, GasCategory},
|
||||
gas_oracle::{GasOracleMiddleware, GasCategory, GasNow},
|
||||
signer::SignerMiddleware,
|
||||
nonce_manager::NonceManagerMiddleware,
|
||||
};
|
||||
|
@ -91,8 +91,8 @@ let signer = LocalWallet::new(&mut rand::thread_rng());
|
|||
let address = signer.address();
|
||||
let provider = SignerMiddleware::new(provider, signer);
|
||||
|
||||
// Use EthGasStation as the gas oracle
|
||||
let gas_oracle = EthGasStation::new(None);
|
||||
// Use GasNow as the gas oracle
|
||||
let gas_oracle = GasNow::new();
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
|
||||
// Manage nonces locally
|
||||
|
|
|
@ -16,14 +16,14 @@ use ethers_signers::Signer;
|
|||
/// use std::sync::Arc;
|
||||
/// use std::convert::TryFrom;
|
||||
/// use ethers_signers::{LocalWallet, Signer};
|
||||
/// use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
|
||||
/// use ethers_middleware::{*, gas_escalator::*, gas_oracle::*};
|
||||
///
|
||||
/// fn builder_example() {
|
||||
/// let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||
/// let signer = key.parse::<LocalWallet>().unwrap();
|
||||
/// let address = signer.address();
|
||||
/// let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||
/// let gas_oracle = EthGasStation::new(None);
|
||||
/// let gas_oracle = GasNow::new();
|
||||
///
|
||||
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||
/// .unwrap()
|
||||
|
@ -43,7 +43,7 @@ use ethers_signers::Signer;
|
|||
/// .unwrap()
|
||||
/// .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
/// .wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||
/// .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||
/// .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
|
||||
/// .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
@ -46,7 +46,7 @@ pub enum Frequency {
|
|||
/// use ethers_providers::{Provider, Http};
|
||||
/// use ethers_middleware::{
|
||||
/// gas_escalator::{GeometricGasPrice, Frequency, GasEscalatorMiddleware},
|
||||
/// gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
|
||||
/// gas_oracle::{GasNow, GasCategory, GasOracleMiddleware},
|
||||
/// };
|
||||
/// use std::{convert::TryFrom, time::Duration, sync::Arc};
|
||||
///
|
||||
|
@ -60,7 +60,7 @@ pub enum Frequency {
|
|||
/// };
|
||||
///
|
||||
/// // ... proceed to wrap it in other middleware
|
||||
/// let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
|
||||
/// let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
|
||||
/// let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
/// ```
|
||||
pub struct GasEscalatorMiddleware<M, E> {
|
||||
|
|
|
@ -1,13 +1,128 @@
|
|||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
|
||||
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use reqwest::{header::AUTHORIZATION, Client};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, convert::TryInto};
|
||||
use std::collections::HashMap;
|
||||
use url::Url;
|
||||
|
||||
const BLOCKNATIVE_GAS_PRICE_ENDPOINT: &str = "https://api.blocknative.com/gasprices/blockprices";
|
||||
const URL: &str = "https://api.blocknative.com/gasprices/blockprices";
|
||||
|
||||
/// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
|
||||
/// that implements the `GasOracle` trait.
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct BlockNative {
|
||||
client: Client,
|
||||
url: Url,
|
||||
api_key: Option<String>,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Response {
|
||||
pub system: String,
|
||||
pub network: String,
|
||||
pub unit: String,
|
||||
pub max_price: u64,
|
||||
pub block_prices: Vec<BlockPrice>,
|
||||
pub estimated_base_fees: Option<Vec<HashMap<String, Vec<BaseFeeEstimate>>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockPrice {
|
||||
pub block_number: u64,
|
||||
pub estimated_transaction_count: u64,
|
||||
pub base_fee_per_gas: f64,
|
||||
pub estimated_prices: Vec<GasEstimate>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GasEstimate {
|
||||
pub confidence: u64,
|
||||
pub price: u64,
|
||||
pub max_priority_fee_per_gas: f64,
|
||||
pub max_fee_per_gas: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BaseFeeEstimate {
|
||||
pub confidence: u64,
|
||||
pub base_fee: f64,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn estimate_from_category(&self, gas_category: &GasCategory) -> Result<GasEstimate> {
|
||||
let confidence = gas_category_to_confidence(gas_category);
|
||||
let price = self
|
||||
.block_prices
|
||||
.first()
|
||||
.ok_or(GasOracleError::InvalidResponse)?
|
||||
.estimated_prices
|
||||
.iter()
|
||||
.find(|p| p.confidence == confidence)
|
||||
.ok_or(GasOracleError::GasCategoryNotSupported)?;
|
||||
Ok(*price)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BlockNative {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GasOracle for BlockNative {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
|
||||
Ok(U256::from(estimate.price) * GWEI_TO_WEI_U256)
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
|
||||
let max = from_gwei_f64(estimate.max_fee_per_gas);
|
||||
let prio = from_gwei_f64(estimate.max_priority_fee_per_gas);
|
||||
Ok((max, prio))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockNative {
|
||||
/// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
|
||||
pub fn new(api_key: Option<String>) -> Self {
|
||||
Self::with_client(Client::new(), api_key)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client, api_key: Option<String>) -> Self {
|
||||
let url = Url::parse(URL).unwrap();
|
||||
Self { client, api_key, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<Response, GasOracleError> {
|
||||
let mut request = self.client.get(self.url.clone());
|
||||
if let Some(api_key) = self.api_key.as_ref() {
|
||||
request = request.header(AUTHORIZATION, api_key);
|
||||
}
|
||||
let response = request.send().await?.error_for_status()?.json().await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
|
||||
match gas_category {
|
||||
GasCategory::SafeLow => 80,
|
||||
|
@ -16,124 +131,3 @@ fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
|
|||
GasCategory::Fastest => 99,
|
||||
}
|
||||
}
|
||||
|
||||
/// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
|
||||
/// that implements the `GasOracle` trait
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlockNative {
|
||||
client: Client,
|
||||
url: Url,
|
||||
api_key: String,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockNativeGasResponse {
|
||||
system: Option<String>,
|
||||
network: Option<String>,
|
||||
unit: Option<String>,
|
||||
max_price: Option<u64>,
|
||||
block_prices: Vec<BlockPrice>,
|
||||
estimated_base_fees: Vec<HashMap<String, Vec<BaseFeeEstimate>>>,
|
||||
}
|
||||
|
||||
impl BlockNativeGasResponse {
|
||||
pub fn get_estimation_for(
|
||||
&self,
|
||||
gas_category: &GasCategory,
|
||||
) -> Result<EstimatedPrice, GasOracleError> {
|
||||
let confidence = gas_category_to_confidence(gas_category);
|
||||
Ok(self
|
||||
.block_prices
|
||||
.first()
|
||||
.ok_or(GasOracleError::InvalidResponse)?
|
||||
.estimated_prices
|
||||
.iter()
|
||||
.find(|p| p.confidence == confidence)
|
||||
.ok_or(GasOracleError::GasCategoryNotSupported)?
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockPrice {
|
||||
block_number: u64,
|
||||
estimated_transaction_count: u64,
|
||||
base_fee_per_gas: f64,
|
||||
estimated_prices: Vec<EstimatedPrice>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EstimatedPrice {
|
||||
confidence: u64,
|
||||
price: u64,
|
||||
max_priority_fee_per_gas: f64,
|
||||
max_fee_per_gas: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BaseFeeEstimate {
|
||||
confidence: u64,
|
||||
base_fee: f64,
|
||||
}
|
||||
|
||||
impl BlockNative {
|
||||
/// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self::with_client(Client::new(), api_key)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client, api_key: String) -> Self {
|
||||
Self {
|
||||
client,
|
||||
api_key,
|
||||
url: BLOCKNATIVE_GAS_PRICE_ENDPOINT.try_into().unwrap(),
|
||||
gas_category: GasCategory::Standard,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
#[must_use]
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform request to Blocknative, decode response
|
||||
pub async fn request(&self) -> Result<BlockNativeGasResponse, GasOracleError> {
|
||||
self.client
|
||||
.get(self.url.as_ref())
|
||||
.header(AUTHORIZATION, &self.api_key)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await
|
||||
.map_err(GasOracleError::HttpClientError)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GasOracle for BlockNative {
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
|
||||
Ok(U256::from(prices.price * 100_u64) * U256::from(GWEI_TO_WEI) / U256::from(100))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
|
||||
let base_fee = U256::from((prices.max_fee_per_gas * 100.0) as u64) *
|
||||
U256::from(GWEI_TO_WEI) /
|
||||
U256::from(100);
|
||||
let prio_fee = U256::from((prices.max_priority_fee_per_gas * 100.0) as u64) *
|
||||
U256::from(GWEI_TO_WEI) /
|
||||
U256::from(100);
|
||||
Ok((base_fee, prio_fee))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::gas_oracle::{GasOracle, GasOracleError};
|
||||
use super::{GasOracle, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use futures_locks::RwLock;
|
||||
|
@ -19,6 +19,24 @@ pub struct Cache<T: GasOracle> {
|
|||
#[derive(Default, Debug)]
|
||||
struct Cached<T: Clone>(RwLock<Option<(Instant, T)>>);
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: GasOracle> GasOracle for Cache<T> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
self.fee.get(self.validity, || self.inner.fetch()).await
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GasOracle> Cache<T> {
|
||||
pub fn new(validity: Duration, inner: T) -> Self {
|
||||
Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Cached<T> {
|
||||
async fn get<F, E, Fut>(&self, validity: Duration, fetch: F) -> Result<T, E>
|
||||
where
|
||||
|
@ -50,21 +68,3 @@ impl<T: Clone> Cached<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GasOracle> Cache<T> {
|
||||
pub fn new(validity: Duration, inner: T) -> Self {
|
||||
Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: GasOracle> GasOracle for Cache<T> {
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
self.fee.get(self.validity, || self.inner.fetch()).await
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +1,78 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use ethers_core::types::U256;
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use url::Url;
|
||||
|
||||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
|
||||
const URL: &str = "https://ethgasstation.info/api/ethgasAPI.json";
|
||||
|
||||
const ETH_GAS_STATION_URL_PREFIX: &str = "https://ethgasstation.info/api/ethgasAPI.json";
|
||||
|
||||
/// A client over HTTP for the [EthGasStation](https://ethgasstation.info/api/ethgasAPI.json) gas tracker API
|
||||
/// that implements the `GasOracle` trait
|
||||
/// A client over HTTP for the [EthGasStation](https://ethgasstation.info) gas tracker API
|
||||
/// that implements the `GasOracle` trait.
|
||||
#[derive(Clone, Debug)]
|
||||
#[deprecated = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
|
||||
#[must_use]
|
||||
pub struct EthGasStation {
|
||||
client: Client,
|
||||
url: Url,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Eth Gas Station's response for the current recommended fast, standard and
|
||||
/// safe low gas prices on the Ethereum network, along with the current block
|
||||
/// and wait times for each "speed".
|
||||
pub struct EthGasStationResponse {
|
||||
/// Recommended safe(expected to be mined in < 30 minutes) gas price in
|
||||
/// x10 Gwei (divide by 10 to convert it to gwei)
|
||||
pub safe_low: f64,
|
||||
/// Recommended average(expected to be mined in < 5 minutes) gas price in
|
||||
/// x10 Gwei (divide by 10 to convert it to gwei)
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Response {
|
||||
/// Recommended safe (expected to be mined in < 30 minutes).
|
||||
///
|
||||
/// In gwei * 10 (divide by 10 to convert it to gwei).
|
||||
pub safe_low: u64,
|
||||
/// Recommended average (expected to be mined in < 5 minutes).
|
||||
///
|
||||
/// In gwei * 10 (divide by 10 to convert it to gwei).
|
||||
pub average: u64,
|
||||
/// Recommended fast(expected to be mined in < 2 minutes) gas price in
|
||||
/// x10 Gwei (divide by 10 to convert it to gwei)
|
||||
/// Recommended fast (expected to be mined in < 2 minutes).
|
||||
///
|
||||
/// In gwei * 10 (divide by 10 to convert it to gwei).
|
||||
pub fast: u64,
|
||||
/// Recommended fastest(expected to be mined in < 30 seconds) gas price
|
||||
/// in x10 Gwei(divide by 10 to convert it to gwei)
|
||||
/// Recommended fastest (expected to be mined in < 30 seconds).
|
||||
///
|
||||
/// In gwei * 10 (divide by 10 to convert it to gwei).
|
||||
pub fastest: u64,
|
||||
|
||||
// post eip-1559 fields
|
||||
/// Average time (in seconds) to mine a single block.
|
||||
#[serde(rename = "block_time")] // inconsistent json response naming...
|
||||
/// Average time(in seconds) to mine one single block
|
||||
pub block_time: f64,
|
||||
/// The latest block number
|
||||
/// The latest block number.
|
||||
pub block_num: u64,
|
||||
/// Smallest value of (gasUsed / gaslimit) from last 10 blocks
|
||||
/// Smallest value of `gasUsed / gaslimit` from the last 10 blocks.
|
||||
pub speed: f64,
|
||||
/// Waiting time(in minutes) for the `safe_low` gas price
|
||||
/// Waiting time (in minutes) for the `safe_low` gas price.
|
||||
pub safe_low_wait: f64,
|
||||
/// Waiting time(in minutes) for the `average` gas price
|
||||
/// Waiting time (in minutes) for the `average` gas price.
|
||||
pub avg_wait: f64,
|
||||
/// Waiting time(in minutes) for the `fast` gas price
|
||||
/// Waiting time (in minutes) for the `fast` gas price.
|
||||
pub fast_wait: f64,
|
||||
/// Waiting time(in minutes) for the `fastest` gas price
|
||||
/// Waiting time (in minutes) for the `fastest` gas price.
|
||||
pub fastest_wait: f64,
|
||||
// What is this?
|
||||
pub gas_price_range: HashMap<u64, f64>,
|
||||
}
|
||||
|
||||
impl EthGasStation {
|
||||
/// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle
|
||||
pub fn new(api_key: Option<&str>) -> Self {
|
||||
Self::with_client(Client::new(), api_key)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client, api_key: Option<&str>) -> Self {
|
||||
let mut url = Url::parse(ETH_GAS_STATION_URL_PREFIX).expect("invalid url");
|
||||
if let Some(key) = api_key {
|
||||
url.query_pairs_mut().append_pair("api-key", key);
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
|
||||
match gas_category {
|
||||
GasCategory::SafeLow => self.safe_low,
|
||||
GasCategory::Standard => self.average,
|
||||
GasCategory::Fast => self.fast,
|
||||
GasCategory::Fastest => self.fastest,
|
||||
}
|
||||
EthGasStation { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
#[must_use]
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn query(&self) -> Result<EthGasStationResponse, GasOracleError> {
|
||||
Ok(self.client.get(self.url.as_ref()).send().await?.json::<EthGasStationResponse>().await?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,19 +85,43 @@ impl Default for EthGasStation {
|
|||
#[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> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let res = self.query().await?;
|
||||
let gas_price = match self.gas_category {
|
||||
GasCategory::SafeLow => U256::from((res.safe_low.ceil() as u64 * GWEI_TO_WEI) / 10),
|
||||
GasCategory::Standard => U256::from((res.average * GWEI_TO_WEI) / 10),
|
||||
GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10),
|
||||
GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10),
|
||||
};
|
||||
|
||||
Ok(gas_price)
|
||||
let gas_price = res.gas_from_category(self.gas_category);
|
||||
// gas_price is in `gwei * 10`
|
||||
Ok(U256::from(gas_price) * GWEI_TO_WEI_U256 / U256::from(10_u64))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
impl EthGasStation {
|
||||
/// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle.
|
||||
pub fn new(api_key: Option<&str>) -> Self {
|
||||
Self::with_client(Client::new(), api_key)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client, api_key: Option<&str>) -> Self {
|
||||
let mut url = Url::parse(URL).unwrap();
|
||||
if let Some(key) = api_key {
|
||||
url.query_pairs_mut().append_pair("api-key", key);
|
||||
}
|
||||
EthGasStation { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<Response> {
|
||||
let response =
|
||||
self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,42 @@
|
|||
use ethers_core::types::U256;
|
||||
|
||||
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
|
||||
|
||||
const ETHERCHAIN_URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
|
||||
const URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
|
||||
|
||||
/// A client over HTTP for the [Etherchain](https://www.etherchain.org/api/gasPriceOracle) gas tracker API
|
||||
/// that implements the `GasOracle` trait
|
||||
/// that implements the `GasOracle` trait.
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct Etherchain {
|
||||
client: Client,
|
||||
url: Url,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EtherchainResponse {
|
||||
pub safe_low: f32,
|
||||
pub standard: f32,
|
||||
pub fast: f32,
|
||||
pub fastest: f32,
|
||||
pub current_base_fee: f32,
|
||||
pub recommended_base_fee: f32,
|
||||
pub struct Response {
|
||||
pub safe_low: f64,
|
||||
pub standard: f64,
|
||||
pub fast: f64,
|
||||
pub fastest: f64,
|
||||
pub current_base_fee: f64,
|
||||
pub recommended_base_fee: f64,
|
||||
}
|
||||
|
||||
impl Etherchain {
|
||||
/// Creates a new [Etherchain](https://etherchain.org/tools/gasPriceOracle) gas price oracle.
|
||||
pub fn new() -> Self {
|
||||
Self::with_client(Client::new())
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client) -> Self {
|
||||
let url = Url::parse(ETHERCHAIN_URL).expect("invalid url");
|
||||
|
||||
Etherchain { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
#[must_use]
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn query(&self) -> Result<EtherchainResponse, GasOracleError> {
|
||||
Ok(self.client.get(self.url.as_ref()).send().await?.json::<EtherchainResponse>().await?)
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn gas_from_category(&self, gas_category: GasCategory) -> f64 {
|
||||
match gas_category {
|
||||
GasCategory::SafeLow => self.safe_low,
|
||||
GasCategory::Standard => self.standard,
|
||||
GasCategory::Fast => self.fast,
|
||||
GasCategory::Fastest => self.fastest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,19 +49,39 @@ impl Default for Etherchain {
|
|||
#[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> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let res = self.query().await?;
|
||||
let gas_price = match self.gas_category {
|
||||
GasCategory::SafeLow => U256::from((res.safe_low as u64) * GWEI_TO_WEI),
|
||||
GasCategory::Standard => U256::from((res.standard as u64) * GWEI_TO_WEI),
|
||||
GasCategory::Fast => U256::from((res.fast as u64) * GWEI_TO_WEI),
|
||||
GasCategory::Fastest => U256::from((res.fastest as u64) * GWEI_TO_WEI),
|
||||
};
|
||||
|
||||
Ok(gas_price)
|
||||
let gas_price = res.gas_from_category(self.gas_category);
|
||||
Ok(from_gwei_f64(gas_price))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
impl Etherchain {
|
||||
/// Creates a new [Etherchain](https://etherchain.org/tools/gasPriceOracle) gas price oracle.
|
||||
pub fn new() -> Self {
|
||||
Self::with_client(Client::new())
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client) -> Self {
|
||||
let url = Url::parse(URL).unwrap();
|
||||
Etherchain { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<Response> {
|
||||
let response =
|
||||
self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,57 @@
|
|||
use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use ethers_core::types::U256;
|
||||
use ethers_etherscan::Client;
|
||||
|
||||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A client over HTTP for the [Etherscan](https://api.etherscan.io/api?module=gastracker&action=gasoracle) gas tracker API
|
||||
/// that implements the `GasOracle` trait
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct Etherscan {
|
||||
client: Client,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
impl Deref for Etherscan {
|
||||
type Target = Client;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Etherscan {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.client
|
||||
}
|
||||
}
|
||||
|
||||
#[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> {
|
||||
// handle unsupported gas categories before making the request
|
||||
match self.gas_category {
|
||||
GasCategory::SafeLow | GasCategory::Standard | GasCategory::Fast => {}
|
||||
GasCategory::Fastest => return Err(GasOracleError::GasCategoryNotSupported),
|
||||
}
|
||||
|
||||
let result = self.query().await?;
|
||||
let gas_price = match self.gas_category {
|
||||
GasCategory::SafeLow => result.safe_gas_price,
|
||||
GasCategory::Standard => result.propose_gas_price,
|
||||
GasCategory::Fast => result.fast_gas_price,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(U256::from(gas_price) * GWEI_TO_WEI_U256)
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
impl Etherscan {
|
||||
/// Creates a new [Etherscan](https://etherscan.io/gastracker) gas price oracle.
|
||||
pub fn new(client: Client) -> Self {
|
||||
|
@ -20,32 +59,13 @@ impl Etherscan {
|
|||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
#[must_use]
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
return Err(GasOracleError::GasCategoryNotSupported)
|
||||
}
|
||||
|
||||
let result = self.client.gas_oracle().await?;
|
||||
|
||||
match self.gas_category {
|
||||
GasCategory::SafeLow => Ok(U256::from(result.safe_gas_price * GWEI_TO_WEI)),
|
||||
GasCategory::Standard => Ok(U256::from(result.propose_gas_price * GWEI_TO_WEI)),
|
||||
GasCategory::Fast => Ok(U256::from(result.fast_gas_price * GWEI_TO_WEI)),
|
||||
_ => Err(GasOracleError::GasCategoryNotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<ethers_etherscan::gas::GasOracle> {
|
||||
Ok(self.client.gas_oracle().await?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +1,54 @@
|
|||
use ethers_core::types::U256;
|
||||
|
||||
use super::{GasCategory, GasOracle, GasOracleError, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
|
||||
const URL: &str = "https://beaconcha.in/api/v1/execution/gasnow";
|
||||
|
||||
const GAS_NOW_URL: &str = "https://etherchain.org/api/gasnow";
|
||||
|
||||
/// A client over HTTP for the [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas tracker API
|
||||
/// that implements the `GasOracle` trait
|
||||
/// A client over HTTP for the [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas tracker API
|
||||
/// that implements the `GasOracle` trait.
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct GasNow {
|
||||
client: Client,
|
||||
url: Url,
|
||||
gas_category: GasCategory,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GasNowResponseWrapper {
|
||||
data: GasNowResponse,
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
pub struct Response {
|
||||
pub code: u64,
|
||||
pub data: ResponseData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct GasNowResponse {
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
pub struct ResponseData {
|
||||
pub rapid: u64,
|
||||
pub fast: u64,
|
||||
pub standard: u64,
|
||||
pub slow: u64,
|
||||
pub timestamp: u64,
|
||||
#[serde(rename = "priceUSD")]
|
||||
pub price_usd: f64,
|
||||
}
|
||||
|
||||
impl GasNow {
|
||||
/// Creates a new [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas price oracle.
|
||||
pub fn new() -> Self {
|
||||
Self::with_client(Client::new())
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
|
||||
self.data.gas_from_category(gas_category)
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client) -> Self {
|
||||
let url = Url::parse(GAS_NOW_URL).expect("invalid url");
|
||||
|
||||
Self { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn query(&self) -> Result<GasNowResponse, GasOracleError> {
|
||||
let res = self
|
||||
.client
|
||||
.get(self.url.as_ref())
|
||||
.send()
|
||||
.await?
|
||||
.json::<GasNowResponseWrapper>()
|
||||
.await?;
|
||||
Ok(res.data)
|
||||
impl ResponseData {
|
||||
fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
|
||||
match gas_category {
|
||||
GasCategory::SafeLow => self.slow,
|
||||
GasCategory::Standard => self.standard,
|
||||
GasCategory::Fast => self.fast,
|
||||
GasCategory::Fastest => self.rapid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,19 +61,39 @@ impl Default for GasNow {
|
|||
#[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> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let res = self.query().await?;
|
||||
let gas_price = match self.gas_category {
|
||||
GasCategory::SafeLow => U256::from(res.slow),
|
||||
GasCategory::Standard => U256::from(res.standard),
|
||||
GasCategory::Fast => U256::from(res.fast),
|
||||
GasCategory::Fastest => U256::from(res.rapid),
|
||||
};
|
||||
|
||||
Ok(gas_price)
|
||||
let gas_price = res.gas_from_category(self.gas_category);
|
||||
Ok(U256::from(gas_price))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
impl GasNow {
|
||||
/// Creates a new [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas price oracle.
|
||||
pub fn new() -> Self {
|
||||
Self::with_client(Client::new())
|
||||
}
|
||||
|
||||
/// Same as [`Self::new`] but with a custom [`Client`].
|
||||
pub fn with_client(client: Client) -> Self {
|
||||
let url = Url::parse(URL).unwrap();
|
||||
Self { client, url, gas_category: GasCategory::Standard }
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<Response> {
|
||||
let response =
|
||||
self.client.get(self.url.as_ref()).send().await?.error_for_status()?.json().await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::gas_oracle::{GasOracle, GasOracleError};
|
||||
use super::{GasOracle, GasOracleError, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use futures_util::future::join_all;
|
||||
|
@ -28,13 +28,10 @@ impl Median {
|
|||
self.oracles.push((weight, Box::new(oracle)));
|
||||
}
|
||||
|
||||
pub async fn query_all<'a, Fn, Fut, O>(
|
||||
&'a self,
|
||||
mut f: Fn,
|
||||
) -> Result<Vec<(f32, O)>, GasOracleError>
|
||||
pub async fn query_all<'a, Fn, Fut, O>(&'a self, mut f: Fn) -> Result<Vec<(f32, O)>>
|
||||
where
|
||||
Fn: FnMut(&'a dyn GasOracle) -> Fut,
|
||||
Fut: Future<Output = Result<O, GasOracleError>>,
|
||||
Fut: Future<Output = Result<O>>,
|
||||
{
|
||||
// Process the oracles in parallel
|
||||
let futures = self.oracles.iter().map(|(_, oracle)| f(oracle.as_ref()));
|
||||
|
@ -62,13 +59,13 @@ impl Median {
|
|||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GasOracle for Median {
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let mut values = self.query_all(|oracle| oracle.fetch()).await?;
|
||||
// `query_all` guarantees `values` is not empty
|
||||
Ok(*weighted_fractile_by_key(0.5, &mut values, |fee| fee).unwrap())
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
let mut values = self.query_all(|oracle| oracle.estimate_eip1559_fees()).await?;
|
||||
// `query_all` guarantees `values` is not empty
|
||||
Ok((
|
||||
|
|
|
@ -4,8 +4,8 @@ use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
|
|||
use ethers_providers::{FromErr, Middleware, PendingTransaction};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`.
|
||||
#[derive(Debug)]
|
||||
/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`
|
||||
pub struct GasOracleMiddleware<M, G> {
|
||||
inner: M,
|
||||
gas_oracle: G,
|
||||
|
@ -21,7 +21,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MiddlewareError<M: Middleware> {
|
||||
#[error(transparent)]
|
||||
GasOracleError(#[from] GasOracleError),
|
||||
|
|
|
@ -1,54 +1,58 @@
|
|||
mod blocknative;
|
||||
pub mod blocknative;
|
||||
pub use blocknative::BlockNative;
|
||||
|
||||
mod eth_gas_station;
|
||||
pub mod eth_gas_station;
|
||||
#[allow(deprecated)]
|
||||
pub use eth_gas_station::EthGasStation;
|
||||
|
||||
mod etherchain;
|
||||
pub mod etherchain;
|
||||
pub use etherchain::Etherchain;
|
||||
|
||||
mod etherscan;
|
||||
pub mod etherscan;
|
||||
pub use etherscan::Etherscan;
|
||||
|
||||
mod middleware;
|
||||
pub mod middleware;
|
||||
pub use middleware::{GasOracleMiddleware, MiddlewareError};
|
||||
|
||||
mod median;
|
||||
pub mod median;
|
||||
pub use median::Median;
|
||||
|
||||
mod cache;
|
||||
pub mod cache;
|
||||
pub use cache::Cache;
|
||||
|
||||
mod polygon;
|
||||
pub mod polygon;
|
||||
pub use polygon::Polygon;
|
||||
|
||||
mod gas_now;
|
||||
pub mod gas_now;
|
||||
pub use gas_now::GasNow;
|
||||
|
||||
mod provider_oracle;
|
||||
pub mod provider_oracle;
|
||||
pub use provider_oracle::ProviderOracle;
|
||||
|
||||
use ethers_core::types::U256;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use auto_impl::auto_impl;
|
||||
use ethers_core::types::U256;
|
||||
use reqwest::Error as ReqwestError;
|
||||
use std::error::Error;
|
||||
use std::{error::Error, fmt::Debug};
|
||||
use thiserror::Error;
|
||||
|
||||
const GWEI_TO_WEI: u64 = 1000000000;
|
||||
pub(crate) const GWEI_TO_WEI: u64 = 1_000_000_000;
|
||||
pub(crate) const GWEI_TO_WEI_U256: U256 = U256([GWEI_TO_WEI, 0, 0, 0]);
|
||||
|
||||
/// Various gas price categories. Choose one of the available
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub type Result<T, E = GasOracleError> = std::result::Result<T, E>;
|
||||
|
||||
/// Generic [`GasOracle`] gas price categories.
|
||||
#[derive(Clone, Copy, Default, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum GasCategory {
|
||||
SafeLow,
|
||||
#[default]
|
||||
Standard,
|
||||
Fast,
|
||||
Fastest,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Error thrown when fetching data from the `GasOracle`
|
||||
/// Error thrown by a [`GasOracle`].
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GasOracleError {
|
||||
/// An internal error in the HTTP request made from the underlying
|
||||
/// gas oracle
|
||||
|
@ -83,48 +87,81 @@ pub enum GasOracleError {
|
|||
UnsupportedChain,
|
||||
|
||||
/// Error thrown when the provider failed.
|
||||
#[error("Chain is not supported by the oracle")]
|
||||
#[error("Provider error: {0}")]
|
||||
ProviderError(#[from] Box<dyn Error + Send + Sync>),
|
||||
}
|
||||
|
||||
/// `GasOracle` is a trait that an underlying gas oracle needs to implement.
|
||||
/// An Ethereum gas price oracle.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ethers_middleware::{
|
||||
/// gas_oracle::{EthGasStation, Etherscan, GasCategory, GasOracle},
|
||||
/// };
|
||||
/// use ethers_core::types::U256;
|
||||
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
|
||||
///
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let eth_gas_station_oracle = EthGasStation::new(Some("my-api-key"));
|
||||
/// let etherscan_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
|
||||
///
|
||||
/// let data_1 = eth_gas_station_oracle.fetch().await?;
|
||||
/// let data_2 = etherscan_oracle.fetch().await?;
|
||||
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
|
||||
/// let gas_price = oracle.fetch().await?;
|
||||
/// assert!(gas_price > U256::zero());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
#[auto_impl(&, Box, Arc)]
|
||||
pub trait GasOracle: Send + Sync + std::fmt::Debug {
|
||||
/// Makes an asynchronous HTTP query to the underlying `GasOracle`
|
||||
pub trait GasOracle: Send + Sync + Debug {
|
||||
/// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current gas
|
||||
/// price estimate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ethers_middleware::{
|
||||
/// gas_oracle::{Etherchain, GasCategory, GasOracle},
|
||||
/// };
|
||||
/// ```no_run
|
||||
/// use ethers_core::types::U256;
|
||||
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
|
||||
///
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let etherchain_oracle = Etherchain::new().category(GasCategory::Fastest);
|
||||
/// let data = etherchain_oracle.fetch().await?;
|
||||
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
|
||||
/// let gas_price = oracle.fetch().await?;
|
||||
/// assert!(gas_price > U256::zero());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError>;
|
||||
async fn fetch(&self) -> Result<U256>;
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError>;
|
||||
/// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current max
|
||||
/// gas fee and priority gas fee estimates.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ethers_core::types::U256;
|
||||
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
|
||||
///
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
|
||||
/// let (max_fee, priority_fee) = oracle.estimate_eip1559_fees().await?;
|
||||
/// assert!(max_fee > U256::zero());
|
||||
/// assert!(priority_fee > U256::zero());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)>;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub(crate) fn from_gwei_f64(gwei: f64) -> U256 {
|
||||
ethers_core::types::u256_from_f64_saturating(gwei) * GWEI_TO_WEI_U256
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gwei_wei_constants() {
|
||||
let as_u256: U256 = GWEI_TO_WEI.into();
|
||||
assert_eq!(as_u256, GWEI_TO_WEI_U256);
|
||||
assert_eq!(GWEI_TO_WEI_U256.as_u64(), GWEI_TO_WEI);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
|
||||
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::{u256_from_f64_saturating, Chain, U256};
|
||||
use ethers_core::types::{Chain, U256};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
const GAS_PRICE_ENDPOINT: &str = "https://gasstation-mainnet.matic.network/v2";
|
||||
const MUMBAI_GAS_PRICE_ENDPOINT: &str = "https://gasstation-mumbai.matic.today/v2";
|
||||
const MAINNET_URL: &str = "https://gasstation-mainnet.matic.network/v2";
|
||||
const MUMBAI_URL: &str = "https://gasstation-mumbai.matic.today/v2";
|
||||
|
||||
/// The [Polygon](https://docs.polygon.technology/docs/develop/tools/polygon-gas-station/) gas station API
|
||||
/// Queries over HTTP and implements the `GasOracle` trait
|
||||
/// Queries over HTTP and implements the `GasOracle` trait.
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct Polygon {
|
||||
client: Client,
|
||||
url: Url,
|
||||
|
@ -18,74 +19,98 @@ pub struct Polygon {
|
|||
}
|
||||
|
||||
/// The response from the Polygon gas station API.
|
||||
/// Gas prices are in Gwei.
|
||||
///
|
||||
/// Gas prices are in __Gwei__.
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Response {
|
||||
estimated_base_fee: f64,
|
||||
safe_low: GasEstimate,
|
||||
standard: GasEstimate,
|
||||
fast: GasEstimate,
|
||||
pub estimated_base_fee: f64,
|
||||
pub safe_low: GasEstimate,
|
||||
pub standard: GasEstimate,
|
||||
pub fast: GasEstimate,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GasEstimate {
|
||||
max_priority_fee: f64,
|
||||
max_fee: f64,
|
||||
pub max_priority_fee: f64,
|
||||
pub max_fee: f64,
|
||||
}
|
||||
|
||||
impl Polygon {
|
||||
pub fn new(chain: Chain) -> Result<Self, GasOracleError> {
|
||||
Self::with_client(Client::new(), chain)
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn estimate_from_category(&self, gas_category: GasCategory) -> GasEstimate {
|
||||
match gas_category {
|
||||
GasCategory::SafeLow => self.safe_low,
|
||||
GasCategory::Standard => self.standard,
|
||||
GasCategory::Fast => self.fast,
|
||||
GasCategory::Fastest => self.fast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_client(client: Client, chain: Chain) -> Result<Self, GasOracleError> {
|
||||
// TODO: Sniff chain from chain id.
|
||||
let url = match chain {
|
||||
Chain::Polygon => Url::parse(GAS_PRICE_ENDPOINT).unwrap(),
|
||||
Chain::PolygonMumbai => Url::parse(MUMBAI_GAS_PRICE_ENDPOINT).unwrap(),
|
||||
_ => return Err(GasOracleError::UnsupportedChain),
|
||||
};
|
||||
Ok(Self { client, url, gas_category: GasCategory::Standard })
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
#[must_use]
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform request to Blocknative, decode response
|
||||
pub async fn request(&self) -> Result<(f64, GasEstimate), GasOracleError> {
|
||||
let response: Response =
|
||||
self.client.get(self.url.as_ref()).send().await?.error_for_status()?.json().await?;
|
||||
let estimate = match self.gas_category {
|
||||
GasCategory::SafeLow => response.safe_low,
|
||||
GasCategory::Standard => response.standard,
|
||||
GasCategory::Fast => response.fast,
|
||||
GasCategory::Fastest => response.fast,
|
||||
};
|
||||
Ok((response.estimated_base_fee, estimate))
|
||||
impl Default for Polygon {
|
||||
fn default() -> Self {
|
||||
Self::new(Chain::Polygon).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GasOracle for Polygon {
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
let (base_fee, estimate) = self.request().await?;
|
||||
let fee = base_fee + estimate.max_priority_fee;
|
||||
Ok(from_gwei(fee))
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
let response = self.query().await?;
|
||||
let base = response.estimated_base_fee;
|
||||
let prio = response.estimate_from_category(self.gas_category).max_priority_fee;
|
||||
let fee = base + prio;
|
||||
Ok(from_gwei_f64(fee))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
let (_, estimate) = self.request().await?;
|
||||
Ok((from_gwei(estimate.max_fee), from_gwei(estimate.max_priority_fee)))
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
let response = self.query().await?;
|
||||
let estimate = response.estimate_from_category(self.gas_category);
|
||||
let max = from_gwei_f64(estimate.max_fee);
|
||||
let prio = from_gwei_f64(estimate.max_priority_fee);
|
||||
Ok((max, prio))
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gwei(gwei: f64) -> U256 {
|
||||
u256_from_f64_saturating(gwei * 1.0e9_f64)
|
||||
impl Polygon {
|
||||
pub fn new(chain: Chain) -> Result<Self> {
|
||||
Self::with_client(Client::new(), chain)
|
||||
}
|
||||
|
||||
pub fn with_client(client: Client, chain: Chain) -> Result<Self> {
|
||||
// TODO: Sniff chain from chain id.
|
||||
let url = match chain {
|
||||
Chain::Polygon => MAINNET_URL,
|
||||
Chain::PolygonMumbai => MUMBAI_URL,
|
||||
_ => return Err(GasOracleError::UnsupportedChain),
|
||||
};
|
||||
Ok(Self { client, url: Url::parse(url).unwrap(), gas_category: GasCategory::Standard })
|
||||
}
|
||||
|
||||
/// Sets the gas price category to be used when fetching the gas price.
|
||||
pub fn category(mut self, gas_category: GasCategory) -> Self {
|
||||
self.gas_category = gas_category;
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform a request to the gas price API and deserialize the response.
|
||||
pub async fn query(&self) -> Result<Response> {
|
||||
let response =
|
||||
self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_polygon_gas_station_response() {
|
||||
let s = r#"{"safeLow":{"maxPriorityFee":2.1267086610666666,"maxFee":2.1267086760666665},"standard":{"maxPriorityFee":2.3482958369333335,"maxFee":2.3482958519333335},"fast":{"maxPriorityFee":2.793454819,"maxFee":2.793454834},"estimatedBaseFee":1.5e-8,"blockTime":2,"blockNumber":30328888}"#;
|
||||
let _resp: Response = serde_json::from_str(s).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::gas_oracle::{GasOracle, GasOracleError};
|
||||
use super::{GasOracle, GasOracleError, Result};
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::U256;
|
||||
use ethers_providers::Middleware;
|
||||
|
@ -6,7 +6,8 @@ use std::fmt::Debug;
|
|||
|
||||
/// Gas oracle from a [`Middleware`] implementation such as an
|
||||
/// Ethereum RPC provider.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct ProviderOracle<M: Middleware> {
|
||||
provider: M,
|
||||
}
|
||||
|
@ -21,16 +22,16 @@ impl<M: Middleware> ProviderOracle<M> {
|
|||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<M: Middleware> GasOracle for ProviderOracle<M>
|
||||
where
|
||||
<M as Middleware>::Error: 'static,
|
||||
M::Error: 'static,
|
||||
{
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
self.provider
|
||||
.get_gas_price()
|
||||
.await
|
||||
.map_err(|err| GasOracleError::ProviderError(Box::new(err)))
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
// TODO: Allow configuring different estimation functions.
|
||||
self.provider
|
||||
.estimate_eip1559_fees(None)
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
use ethers_contract::Lazy;
|
||||
use ethers_core::types::*;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
|
||||
/// DsProxyFactory contract addresses as values
|
||||
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
let mut m = HashMap::with_capacity(1);
|
||||
|
||||
// mainnet
|
||||
let addr =
|
||||
Address::from_str("eefba1e63905ef1d7acba5a8513c70307c1ce441").expect("Decoding failed");
|
||||
m.insert(U256::from(1u8), addr);
|
||||
let addr = "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap();
|
||||
m.insert(U256::from(1_u64), addr);
|
||||
|
||||
m
|
||||
});
|
||||
|
||||
/// Generated with abigen:
|
||||
///
|
||||
/// Generated with
|
||||
/// ```ignore
|
||||
/// # use ethers_contract::abigen;
|
||||
/// abigen!(DsProxyFactory,
|
||||
|
@ -26,7 +25,6 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
|||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
// Auto-generated type-safe bindings
|
||||
pub use dsproxyfactory_mod::*;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
mod dsproxyfactory_mod {
|
||||
|
|
|
@ -1,69 +1,67 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
use ethers_core::{rand::thread_rng, types::U64};
|
||||
use ethers_middleware::{
|
||||
builder::MiddlewareBuilder,
|
||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||
gas_oracle::{EthGasStation, GasOracleMiddleware},
|
||||
nonce_manager::NonceManagerMiddleware,
|
||||
signer::SignerMiddleware,
|
||||
};
|
||||
use ethers_providers::{Middleware, Provider};
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_raw_middleware_stack() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
use ethers_core::{rand::thread_rng, types::U64};
|
||||
use ethers_middleware::{
|
||||
builder::MiddlewareBuilder,
|
||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||
gas_oracle::{GasNow, GasOracleMiddleware},
|
||||
nonce_manager::NonceManagerMiddleware,
|
||||
signer::SignerMiddleware,
|
||||
};
|
||||
use ethers_providers::{Middleware, Provider};
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
#[tokio::test]
|
||||
async fn build_raw_middleware_stack() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
|
||||
let provider = provider
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||
.wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||
.wrap_into(|p| NonceManagerMiddleware::new(p, address));
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
|
||||
// push a response
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let block: U64 = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(block.as_u64(), 12);
|
||||
let provider = provider
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
|
||||
.wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||
.wrap_into(|p| NonceManagerMiddleware::new(p, address));
|
||||
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
// push a response
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let block: U64 = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(block.as_u64(), 12);
|
||||
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_declarative_middleware_stack() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let gas_oracle = EthGasStation::new(None);
|
||||
|
||||
let provider = provider
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.gas_oracle(gas_oracle)
|
||||
.with_signer(signer)
|
||||
.nonce_manager(address);
|
||||
|
||||
// push a response
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let block: U64 = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(block.as_u64(), 12);
|
||||
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_declarative_middleware_stack() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let gas_oracle = GasNow::new();
|
||||
|
||||
let provider = provider
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.gas_oracle(gas_oracle)
|
||||
.with_signer(signer)
|
||||
.nonce_manager(address);
|
||||
|
||||
// push a response
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let block: U64 = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(block.as_u64(), 12);
|
||||
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use ethers_core::types::*;
|
||||
use ethers_middleware::{
|
||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||
|
@ -12,6 +13,7 @@ use std::time::Duration;
|
|||
#[ignore]
|
||||
async fn gas_escalator_live() {
|
||||
// connect to ropsten for getting bad block times
|
||||
#[allow(deprecated)]
|
||||
let provider = ethers_providers::ROPSTEN.ws().await;
|
||||
let provider = provider.interval(Duration::from_millis(2000u64));
|
||||
let wallet = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169"
|
||||
|
@ -33,7 +35,7 @@ async fn gas_escalator_live() {
|
|||
provider.send_transaction(tx.clone().nonce(nonce + 2), None).await.unwrap();
|
||||
|
||||
// Wait a bunch of seconds and refresh etherscan to see the transactions get bumped
|
||||
tokio::time::sleep(std::time::Duration::from_secs(100)).await;
|
||||
tokio::time::sleep(Duration::from_secs(100)).await;
|
||||
|
||||
// TODO: Figure out how to test this behavior properly in a local network. If the gas price was
|
||||
// bumped then the tx hash will be different
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use ethers_core::{types::*, utils::Anvil};
|
||||
use ethers_middleware::gas_oracle::{
|
||||
EthGasStation, Etherchain, Etherscan, GasCategory, GasOracle, GasOracleError,
|
||||
GasOracleMiddleware,
|
||||
BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
|
||||
GasOracleMiddleware, Polygon, ProviderOracle, Result,
|
||||
};
|
||||
use ethers_providers::{Http, Middleware, Provider};
|
||||
use serial_test::serial;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FakeGasOracle {
|
||||
|
@ -20,17 +18,18 @@ struct FakeGasOracle {
|
|||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GasOracle for FakeGasOracle {
|
||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||
async fn fetch(&self) -> Result<U256> {
|
||||
Ok(self.gas_price)
|
||||
}
|
||||
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
|
||||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
|
||||
Err(GasOracleError::Eip1559EstimationNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn using_gas_oracle() {
|
||||
#[serial]
|
||||
async fn provider_using_gas_oracle() {
|
||||
let anvil = Anvil::new().spawn();
|
||||
|
||||
let from = anvil.addresses()[0];
|
||||
|
@ -38,11 +37,11 @@ async fn using_gas_oracle() {
|
|||
// connect to the network
|
||||
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
|
||||
|
||||
// initial base fee
|
||||
let base_fee = 1_000_000_000u64;
|
||||
// assign a gas oracle to use
|
||||
let gas_oracle = FakeGasOracle { gas_price: (base_fee + 1337).into() };
|
||||
let expected_gas_price = gas_oracle.fetch().await.unwrap();
|
||||
let expected_gas_price = U256::from(1234567890_u64);
|
||||
let gas_oracle = FakeGasOracle { gas_price: expected_gas_price };
|
||||
let gas_price = gas_oracle.fetch().await.unwrap();
|
||||
assert_eq!(gas_price, expected_gas_price);
|
||||
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
|
||||
|
@ -55,35 +54,70 @@ async fn using_gas_oracle() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn eth_gas_station() {
|
||||
// initialize and fetch gas estimates from EthGasStation
|
||||
let eth_gas_station_oracle = EthGasStation::default();
|
||||
let data = eth_gas_station_oracle.fetch().await;
|
||||
data.unwrap();
|
||||
#[serial]
|
||||
async fn provider_oracle() {
|
||||
// spawn anvil and connect to it
|
||||
let anvil = Anvil::new().spawn();
|
||||
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
|
||||
|
||||
// assert that provider.get_gas_price() and oracle.fetch() return the same value
|
||||
let expected_gas_price = provider.get_gas_price().await.unwrap();
|
||||
let provider_oracle = ProviderOracle::new(provider);
|
||||
let gas = provider_oracle.fetch().await.unwrap();
|
||||
assert_eq!(gas, expected_gas_price);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn blocknative() {
|
||||
let gas_now_oracle = BlockNative::default();
|
||||
let gas_price = gas_now_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
|
||||
#[allow(deprecated)]
|
||||
async fn eth_gas_station() {
|
||||
let eth_gas_station_oracle = ethers_middleware::gas_oracle::EthGasStation::default();
|
||||
let gas_price = eth_gas_station_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Etherchain / beaconcha.in's `gasPriceOracle` API currently returns 404: https://www.etherchain.org/api/gasPriceOracle"]
|
||||
async fn etherchain() {
|
||||
let etherchain_oracle = Etherchain::default();
|
||||
let gas_price = etherchain_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn etherscan() {
|
||||
let etherscan_client = ethers_etherscan::Client::new_from_env(Chain::Mainnet).unwrap();
|
||||
|
||||
// initialize and fetch gas estimates from Etherscan
|
||||
// since etherscan does not support `fastest` category, we expect an error
|
||||
let etherscan_oracle = Etherscan::new(etherscan_client.clone()).category(GasCategory::Fastest);
|
||||
let data = etherscan_oracle.fetch().await;
|
||||
data.unwrap_err();
|
||||
let error = etherscan_oracle.fetch().await.unwrap_err();
|
||||
assert!(matches!(error, GasOracleError::GasCategoryNotSupported));
|
||||
|
||||
// but fetching the `standard` gas price should work fine
|
||||
let etherscan_oracle = Etherscan::new(etherscan_client).category(GasCategory::SafeLow);
|
||||
|
||||
let data = etherscan_oracle.fetch().await;
|
||||
data.unwrap();
|
||||
let gas_price = etherscan_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn etherchain() {
|
||||
// initialize and fetch gas estimates from Etherchain
|
||||
let etherchain_oracle = Etherchain::default().category(GasCategory::Fast);
|
||||
let data = etherchain_oracle.fetch().await;
|
||||
data.unwrap();
|
||||
async fn gas_now() {
|
||||
let gas_now_oracle = GasNow::default();
|
||||
let gas_price = gas_now_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn polygon() {
|
||||
let polygon_oracle = Polygon::default();
|
||||
let gas_price = polygon_oracle.fetch().await.unwrap();
|
||||
assert!(gas_price > U256::zero());
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
async fn nonce_manager() {
|
||||
use ethers_core::types::*;
|
||||
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
|
||||
use ethers_providers::Middleware;
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use std::time::Duration;
|
||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||
|
||||
let provider = ethers_providers::GOERLI.provider().interval(Duration::from_millis(2000u64));
|
||||
use ethers_core::types::*;
|
||||
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
|
||||
use ethers_providers::Middleware;
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn nonce_manager() {
|
||||
let provider = ethers_providers::GOERLI.provider().interval(Duration::from_millis(2000));
|
||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||
|
||||
let wallet = std::env::var("GOERLI_PRIVATE_KEY")
|
||||
.unwrap()
|
||||
.expect("GOERLI_PRIVATE_KEY is not defined")
|
||||
.parse::<LocalWallet>()
|
||||
.unwrap()
|
||||
.with_chain_id(chain_id);
|
||||
|
@ -44,12 +44,12 @@ async fn nonce_manager() {
|
|||
}
|
||||
|
||||
// sleep a bit to ensure there's no flakiness in the test
|
||||
std::thread::sleep(std::time::Duration::new(5, 0));
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
let mut nonces = Vec::with_capacity(num_tx);
|
||||
for tx_hash in tx_hashes {
|
||||
nonces.push(provider.get_transaction(tx_hash).await.unwrap().unwrap().nonce.as_u64());
|
||||
}
|
||||
|
||||
assert_eq!(nonces, (nonce..nonce + (num_tx as u64)).collect::<Vec<_>>())
|
||||
assert_eq!(nonces, (nonce..nonce + num_tx as u64).collect::<Vec<_>>())
|
||||
}
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
#![allow(unused)]
|
||||
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
|
||||
|
||||
use ethers_contract::ContractFactory;
|
||||
use ethers_core::{
|
||||
types::{BlockNumber, TransactionRequest},
|
||||
utils::parse_units,
|
||||
abi::Abi,
|
||||
types::*,
|
||||
utils::{parse_ether, parse_units, Anvil},
|
||||
};
|
||||
use ethers_middleware::signer::SignerMiddleware;
|
||||
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
|
||||
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
||||
use ethers_solc::Solc;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{convert::TryFrom, iter::Cycle, sync::atomic::AtomicU8, time::Duration};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
iter::Cycle,
|
||||
sync::{atomic::AtomicU8, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
||||
TestWallets {
|
||||
|
@ -22,8 +30,6 @@ static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
|||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
async fn send_eth() {
|
||||
use ethers_core::utils::Anvil;
|
||||
|
||||
let anvil = Anvil::new().spawn();
|
||||
|
||||
// this private key belongs to the above mnemonic
|
||||
|
@ -95,10 +101,6 @@ async fn pending_txs_with_confirmations_testnet() {
|
|||
generic_pending_txs_test(provider, address).await;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "celo"))]
|
||||
use ethers_core::types::{Address, Eip1559TransactionRequest};
|
||||
use ethers_core::utils::parse_ether;
|
||||
|
||||
// different keys to avoid nonce errors
|
||||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
|
@ -195,8 +197,6 @@ async fn test_send_transaction() {
|
|||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
async fn send_transaction_handles_tx_from_field() {
|
||||
use ethers_core::utils::Anvil;
|
||||
|
||||
// launch anvil
|
||||
let anvil = Anvil::new().spawn();
|
||||
|
||||
|
@ -240,14 +240,6 @@ async fn send_transaction_handles_tx_from_field() {
|
|||
#[tokio::test]
|
||||
#[cfg(feature = "celo")]
|
||||
async fn deploy_and_call_contract() {
|
||||
use ethers_contract::ContractFactory;
|
||||
use ethers_core::{
|
||||
abi::Abi,
|
||||
types::{BlockNumber, Bytes, H256, U256},
|
||||
};
|
||||
use ethers_solc::Solc;
|
||||
use std::sync::Arc;
|
||||
|
||||
// compiles the given contract and returns the ABI and Bytecode
|
||||
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
||||
let path = format!("./tests/solidity-contracts/{path}");
|
||||
|
@ -302,7 +294,7 @@ impl TestWallets {
|
|||
#[allow(unused)]
|
||||
pub async fn fund<T: JsonRpcClient, U: Into<u32>>(&self, provider: &Provider<T>, n: U) {
|
||||
let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::<Vec<_>>();
|
||||
// hardcoded funder address private key, goerli
|
||||
// hardcoded funder address private key, GOERLI
|
||||
let signer = "39aa18eeb5d12c071e5f19d8e9375a872e90cb1f2fa640384ffd8800a2f3e8f1"
|
||||
.parse::<LocalWallet>()
|
||||
.unwrap()
|
||||
|
|
|
@ -1,95 +1,93 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
||||
use ethers_middleware::{
|
||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||
gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
|
||||
nonce_manager::NonceManagerMiddleware,
|
||||
signer::SignerMiddleware,
|
||||
};
|
||||
use ethers_providers::{Http, Middleware, Provider};
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use std::convert::TryFrom;
|
||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||
|
||||
#[tokio::test]
|
||||
async fn mock_with_middleware() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
||||
use ethers_middleware::{
|
||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||
gas_oracle::{GasCategory, GasNow, GasOracleMiddleware},
|
||||
nonce_manager::NonceManagerMiddleware,
|
||||
signer::SignerMiddleware,
|
||||
};
|
||||
use ethers_providers::{Http, Middleware, Provider};
|
||||
use ethers_signers::{LocalWallet, Signer};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// add a bunch of middlewares
|
||||
let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
let provider = SignerMiddleware::new(provider, signer);
|
||||
let provider = NonceManagerMiddleware::new(provider, address);
|
||||
#[tokio::test]
|
||||
async fn mock_with_middleware() {
|
||||
let (provider, mock) = Provider::mocked();
|
||||
|
||||
// push a response
|
||||
use ethers_core::types::U64;
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let blk = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(blk.as_u64(), 12);
|
||||
// add a bunch of middlewares
|
||||
let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
|
||||
let signer = LocalWallet::new(&mut thread_rng());
|
||||
let address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
let provider = SignerMiddleware::new(provider, signer);
|
||||
let provider = NonceManagerMiddleware::new(provider, address);
|
||||
|
||||
// now that the response is gone, there's nothing left
|
||||
// TODO: This returns:
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// JsonRpcClientError(EmptyResponses)
|
||||
// ))))
|
||||
// Can we flatten it in any way? Maybe inherent to the middleware
|
||||
// infrastructure
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
// push a response
|
||||
use ethers_core::types::U64;
|
||||
mock.push(U64::from(12u64)).unwrap();
|
||||
let blk = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(blk.as_u64(), 12);
|
||||
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
// now that the response is gone, there's nothing left
|
||||
// TODO: This returns:
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// MiddlewareError(
|
||||
// JsonRpcClientError(EmptyResponses)
|
||||
// ))))
|
||||
// Can we flatten it in any way? Maybe inherent to the middleware
|
||||
// infrastructure
|
||||
provider.get_block_number().await.unwrap_err();
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_stack_middlewares() {
|
||||
let anvil = Anvil::new().block_time(5u64).spawn();
|
||||
let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
|
||||
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
||||
let address = signer.address();
|
||||
|
||||
// the base provider
|
||||
let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
|
||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||
let signer = signer.with_chain_id(chain_id);
|
||||
|
||||
// the Gas Price escalator middleware is the first middleware above the provider,
|
||||
// so that it receives the transaction last, after all the other middleware
|
||||
// have modified it accordingly
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
|
||||
|
||||
// The gas price middleware MUST be below the signing middleware for things to work
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
|
||||
// The signing middleware signs txs
|
||||
use std::sync::Arc;
|
||||
let provider = Arc::new(SignerMiddleware::new(provider, signer));
|
||||
|
||||
// The nonce manager middleware MUST be above the signing middleware so that it overrides
|
||||
// the nonce and the signer does not make any eth_getTransaction count calls
|
||||
let provider = NonceManagerMiddleware::new(provider, address);
|
||||
|
||||
let tx = TransactionRequest::new();
|
||||
let mut pending_txs = Vec::new();
|
||||
for _ in 0..10 {
|
||||
let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
|
||||
let hash = *pending;
|
||||
let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
|
||||
dbg!(gas_price);
|
||||
pending_txs.push(pending);
|
||||
}
|
||||
|
||||
let receipts = futures_util::future::join_all(pending_txs);
|
||||
dbg!(receipts.await);
|
||||
}
|
||||
// 2 calls were made
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_stack_middlewares() {
|
||||
let anvil = Anvil::new().block_time(5u64).spawn();
|
||||
let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
|
||||
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
||||
let address = signer.address();
|
||||
|
||||
// the base provider
|
||||
let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
|
||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||
let signer = signer.with_chain_id(chain_id);
|
||||
|
||||
// the Gas Price escalator middleware is the first middleware above the provider,
|
||||
// so that it receives the transaction last, after all the other middleware
|
||||
// have modified it accordingly
|
||||
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
|
||||
|
||||
// The gas price middleware MUST be below the signing middleware for things to work
|
||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||
|
||||
// The signing middleware signs txs
|
||||
use std::sync::Arc;
|
||||
let provider = Arc::new(SignerMiddleware::new(provider, signer));
|
||||
|
||||
// The nonce manager middleware MUST be above the signing middleware so that it overrides
|
||||
// the nonce and the signer does not make any eth_getTransaction count calls
|
||||
let provider = NonceManagerMiddleware::new(provider, address);
|
||||
|
||||
let tx = TransactionRequest::new();
|
||||
let mut pending_txs = Vec::new();
|
||||
for _ in 0..10 {
|
||||
let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
|
||||
let hash = *pending;
|
||||
let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
|
||||
dbg!(gas_price);
|
||||
pending_txs.push(pending);
|
||||
}
|
||||
|
||||
let receipts = futures_util::future::join_all(pending_txs);
|
||||
dbg!(receipts.await);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
#![allow(unused)]
|
||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||
|
||||
use ethers_contract::{BaseContract, ContractFactory};
|
||||
use ethers_core::{abi::Abi, types::*, utils::Anvil};
|
||||
use ethers_middleware::{
|
||||
|
@ -24,7 +24,6 @@ fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
async fn ds_proxy_transformer() {
|
||||
// randomness
|
||||
let mut rng = rand::thread_rng();
|
||||
|
@ -83,7 +82,6 @@ async fn ds_proxy_transformer() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
async fn ds_proxy_code() {
|
||||
// randomness
|
||||
let mut rng = rand::thread_rng();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "ethers-providers"
|
||||
version = "1.0.2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
@ -43,7 +43,7 @@ tracing = { version = "0.1.37", default-features = false }
|
|||
tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] }
|
||||
|
||||
bytes = { version = "1.3.0", default-features = false, optional = true }
|
||||
once_cell = "1.16.0"
|
||||
once_cell = "1.17.0"
|
||||
hashers = "1.0.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
|
|
@ -540,6 +540,7 @@ pub trait Middleware: Sync + Send + Debug {
|
|||
}
|
||||
|
||||
// Geth `trace` support
|
||||
|
||||
/// After replaying any previous transactions in the same block,
|
||||
/// Replays a transaction, returning the traces configured with passed options
|
||||
async fn debug_trace_transaction(
|
||||
|
@ -550,6 +551,16 @@ pub trait Middleware: Sync + Send + Debug {
|
|||
self.inner().debug_trace_transaction(tx_hash, trace_options).await.map_err(FromErr::from)
|
||||
}
|
||||
|
||||
/// Executes the given call and returns a number of possible traces for it
|
||||
async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
req: T,
|
||||
block: Option<BlockId>,
|
||||
trace_options: GethDebugTracingCallOptions,
|
||||
) -> Result<GethTrace, ProviderError> {
|
||||
self.inner().debug_trace_call(req, block, trace_options).await.map_err(FromErr::from)
|
||||
}
|
||||
|
||||
// Parity `trace` support
|
||||
|
||||
/// Executes the given call and returns a number of possible traces for it
|
||||
|
@ -712,7 +723,8 @@ pub trait CeloMiddleware: Middleware {
|
|||
}
|
||||
}
|
||||
|
||||
pub use test_provider::{GOERLI, MAINNET, ROPSTEN};
|
||||
#[allow(deprecated)]
|
||||
pub use test_provider::{GOERLI, MAINNET, ROPSTEN, SEPOLIA};
|
||||
|
||||
/// Pre-instantiated Infura HTTP clients which rotate through multiple API keys
|
||||
/// to prevent rate limits
|
||||
|
@ -732,9 +744,13 @@ pub mod test_provider {
|
|||
"5c812e02193c4ba793f8c214317582bd",
|
||||
];
|
||||
|
||||
pub static GOERLI: Lazy<TestProvider> = Lazy::new(|| TestProvider::new(INFURA_KEYS, "goerli"));
|
||||
pub static MAINNET: Lazy<TestProvider> =
|
||||
Lazy::new(|| TestProvider::new(INFURA_KEYS, "mainnet"));
|
||||
pub static GOERLI: Lazy<TestProvider> = Lazy::new(|| TestProvider::new(INFURA_KEYS, "goerli"));
|
||||
pub static SEPOLIA: Lazy<TestProvider> =
|
||||
Lazy::new(|| TestProvider::new(INFURA_KEYS, "sepolia"));
|
||||
|
||||
#[deprecated = "Ropsten testnet has been deprecated in favor of Goerli or Sepolia."]
|
||||
pub static ROPSTEN: Lazy<TestProvider> =
|
||||
Lazy::new(|| TestProvider::new(INFURA_KEYS, "ropsten"));
|
||||
|
||||
|
@ -745,16 +761,14 @@ pub mod test_provider {
|
|||
}
|
||||
|
||||
impl TestProvider {
|
||||
pub fn new(keys: &'static [&'static str], network: &str) -> Self {
|
||||
Self { keys: Mutex::new(keys.iter().cycle()), network: network.to_owned() }
|
||||
pub fn new(keys: &'static [&'static str], network: impl Into<String>) -> Self {
|
||||
Self { keys: keys.iter().cycle().into(), network: network.into() }
|
||||
}
|
||||
|
||||
pub fn url(&self) -> String {
|
||||
format!(
|
||||
"https://{}.infura.io/v3/{}",
|
||||
self.network,
|
||||
self.keys.lock().unwrap().next().unwrap()
|
||||
)
|
||||
let Self { network, keys } = self;
|
||||
let key = keys.lock().unwrap().next().unwrap();
|
||||
format!("https://{network}.infura.io/v3/{key}")
|
||||
}
|
||||
|
||||
pub fn provider(&self) -> Provider<Http> {
|
||||
|
|
|
@ -22,9 +22,10 @@ use ethers_core::{
|
|||
types::{
|
||||
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
|
||||
Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, EIP1186ProofResponse, FeeHistory,
|
||||
Filter, FilterBlockOption, GethDebugTracingOptions, GethTrace, Log, NameOrAddress,
|
||||
Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt,
|
||||
TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64,
|
||||
Filter, FilterBlockOption, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
|
||||
Log, NameOrAddress, Selector, Signature, Trace, TraceFilter, TraceType, Transaction,
|
||||
TransactionReceipt, TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus,
|
||||
H256, U256, U64,
|
||||
},
|
||||
utils,
|
||||
};
|
||||
|
@ -351,8 +352,19 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
if inner.max_fee_per_gas.is_none() || inner.max_priority_fee_per_gas.is_none() {
|
||||
let (max_fee_per_gas, max_priority_fee_per_gas) =
|
||||
self.estimate_eip1559_fees(None).await?;
|
||||
inner.max_fee_per_gas = Some(max_fee_per_gas);
|
||||
inner.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
|
||||
// we want to avoid overriding the user if either of these
|
||||
// are set. In order to do this, we refuse to override the
|
||||
// `max_fee_per_gas` if already set.
|
||||
// However, we must preserve the constraint that the tip
|
||||
// cannot be higher than max fee, so we override user
|
||||
// intent if that is so. We override by
|
||||
// - first: if set, set to the min(current value, MFPG)
|
||||
// - second, if still unset, use the RPC estimated amount
|
||||
let mfpg = inner.max_fee_per_gas.get_or_insert(max_fee_per_gas);
|
||||
inner
|
||||
.max_priority_fee_per_gas
|
||||
.map(|tip| std::cmp::min(tip, *mfpg))
|
||||
.get_or_insert(max_priority_fee_per_gas);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1048,6 +1060,20 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
self.request("debug_traceTransaction", [tx_hash, trace_options]).await
|
||||
}
|
||||
|
||||
/// Executes the given call and returns a number of possible traces for it
|
||||
async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
req: T,
|
||||
block: Option<BlockId>,
|
||||
trace_options: GethDebugTracingCallOptions,
|
||||
) -> Result<GethTrace, ProviderError> {
|
||||
let req = req.into();
|
||||
let req = utils::serialize(&req);
|
||||
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
|
||||
let trace_options = utils::serialize(&trace_options);
|
||||
self.request("debug_traceCall", [req, block, trace_options]).await
|
||||
}
|
||||
|
||||
/// Executes the given call and returns a number of possible traces for it
|
||||
async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
|
|
|
@ -9,7 +9,7 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
/// A client contains two clients.
|
||||
/// A client containing two clients.
|
||||
///
|
||||
/// One is used for _read_ operations
|
||||
/// One is used for _write_ operations that consume gas `["eth_sendTransaction",
|
||||
|
@ -48,7 +48,7 @@ impl<Read, Write> RwClient<Read, Write> {
|
|||
&self.r
|
||||
}
|
||||
|
||||
/// Returns the client used for read operations
|
||||
/// Returns the client used for write operations
|
||||
pub fn write_client(&self) -> &Write {
|
||||
&self.w
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "ethers-signers"
|
||||
version = "1.0.2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
@ -16,7 +16,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
|
||||
[dependencies]
|
||||
ethers-core = { version = "^1.0.0", path = "../ethers-core", features = ["eip712"] }
|
||||
thiserror = { version = "1.0.37", default-features = false }
|
||||
thiserror = { version = "1.0.38", default-features = false }
|
||||
coins-bip32 = "0.7.0"
|
||||
coins-bip39 = "0.7.0"
|
||||
coins-ledger = { version = "0.7.0", default-features = false, optional = true }
|
||||
|
@ -28,7 +28,7 @@ rand = { version = "0.8.5", default-features = false }
|
|||
yubihsm = { version = "0.41.0", features = ["secp256k1", "http", "usb"], optional = true }
|
||||
futures-util = { version = "^0.3", optional = true }
|
||||
futures-executor = { version = "^0.3", optional = true }
|
||||
semver = { version = "1.0.14", optional = true }
|
||||
semver = { version = "1.0.16", optional = true }
|
||||
trezor-client = { version = "0.0.7", optional = true, default-features = false, features = [
|
||||
"f_ethereum",
|
||||
] }
|
||||
|
|
|
@ -90,6 +90,13 @@ impl Wallet<SigningKey> {
|
|||
let address = secret_key_to_address(&signer);
|
||||
Self { signer, address, chain_id: 1 }
|
||||
}
|
||||
|
||||
/// Creates a new Wallet instance from a raw scalar value (big endian).
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, WalletError> {
|
||||
let signer = SigningKey::from_bytes(bytes)?;
|
||||
let address = secret_key_to_address(&signer);
|
||||
Ok(Self { signer, address, chain_id: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Wallet<SigningKey> {
|
||||
|
@ -305,4 +312,17 @@ mod tests {
|
|||
Address::from_str("6813Eb9362372EEF6200f3b1dbC3f819671cBA69").expect("Decoding failed")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_from_bytes() {
|
||||
let wallet: Wallet<SigningKey> =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
|
||||
|
||||
let key_as_bytes = wallet.signer.to_bytes();
|
||||
let wallet_from_bytes = Wallet::from_bytes(&key_as_bytes).unwrap();
|
||||
|
||||
assert_eq!(wallet.address, wallet_from_bytes.address);
|
||||
assert_eq!(wallet.chain_id, wallet_from_bytes.chain_id);
|
||||
assert_eq!(wallet.signer, wallet_from_bytes.signer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "ethers-solc"
|
||||
version = "1.0.2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = [
|
||||
"Matthias Seitz <matthias.seitz@outlook.de>",
|
||||
|
@ -19,11 +19,11 @@ keywords = ["ethereum", "web3", "solc", "solidity", "ethers"]
|
|||
ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false }
|
||||
serde_json = "1.0.68"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
semver = { version = "1.0.14", features = ["serde"] }
|
||||
semver = { version = "1.0.16", features = ["serde"] }
|
||||
walkdir = "2.3.2"
|
||||
tokio = { version = "1.18", default-features = false, features = ["rt"] }
|
||||
futures-util = { version = "^0.3", optional = true }
|
||||
once_cell = "1.16.0"
|
||||
once_cell = "1.17.0"
|
||||
regex = "1.7.0"
|
||||
md-5 = "0.10.5"
|
||||
thiserror = "1.0"
|
||||
|
@ -31,7 +31,7 @@ hex = "0.4.3"
|
|||
yansi = "0.5.1"
|
||||
glob = "0.3.0"
|
||||
tracing = "0.1.37"
|
||||
num_cpus = "1.14.0"
|
||||
num_cpus = "1.15.0"
|
||||
tiny-keccak = { version = "2.0.2", default-features = false }
|
||||
tempfile = { version = "3.3.0", optional = true }
|
||||
fs_extra = { version = "1.2.0", optional = true }
|
||||
|
@ -62,7 +62,7 @@ rand = "0.8.5"
|
|||
pretty_assertions = "1.3.0"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.18", features = ["full"] }
|
||||
serde_path_to_error = "0.1.8"
|
||||
serde_path_to_error = "0.1.9"
|
||||
|
||||
[[bench]]
|
||||
name = "compile_many"
|
||||
|
|
|
@ -36,7 +36,6 @@ fn load_compiler_inputs() -> Vec<CompilerInput> {
|
|||
let mut inputs = Vec::new();
|
||||
for file in std::fs::read_dir(Path::new(&env!("CARGO_MANIFEST_DIR")).join("test-data/in"))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.take(5)
|
||||
{
|
||||
let file = file.unwrap();
|
||||
|
|
|
@ -1086,11 +1086,11 @@ mod tests {
|
|||
let result: Result<SourceUnit, _> = serde_path_to_error::deserialize(deserializer);
|
||||
match result {
|
||||
Err(e) => {
|
||||
println!("... {} fail: {e}", path_str);
|
||||
println!("... {path_str} fail: {e}");
|
||||
panic!();
|
||||
}
|
||||
Ok(_) => {
|
||||
println!("... {} ok", path_str);
|
||||
println!("... {path_str} ok");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -26,7 +26,10 @@ pub mod contract;
|
|||
pub mod output_selection;
|
||||
pub mod serde_helpers;
|
||||
use crate::{
|
||||
artifacts::output_selection::{ContractOutputSelection, OutputSelection},
|
||||
artifacts::{
|
||||
lowfidelity::NodeType,
|
||||
output_selection::{ContractOutputSelection, OutputSelection},
|
||||
},
|
||||
filter::FilteredSources,
|
||||
};
|
||||
pub use bytecode::*;
|
||||
|
|
|
@ -457,7 +457,7 @@ impl Solc {
|
|||
|
||||
use sha2::Digest;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(&content);
|
||||
hasher.update(content);
|
||||
let checksum_calc = &hasher.finalize()[..];
|
||||
|
||||
let checksum_found = &RELEASES.0.get_checksum(&version).expect("checksum not found");
|
||||
|
|
|
@ -649,7 +649,7 @@ impl Graph {
|
|||
return Vec::new()
|
||||
}
|
||||
|
||||
let mut result = sets.pop().cloned().expect("not empty; qed.").clone();
|
||||
let mut result = sets.pop().cloned().expect("not empty; qed.");
|
||||
if !sets.is_empty() {
|
||||
result.retain(|item| sets.iter().all(|set| set.contains(item)));
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,81 @@
|
|||
# Examples
|
||||
- [ ] Address book
|
||||
- [ ] Anvil
|
||||
- [ ] Boot anvil
|
||||
- [ ] Deploy contracts
|
||||
- [x] Fork
|
||||
- [ ] Testing
|
||||
- [x] Big numbers
|
||||
- [x] Comparison and equivalence
|
||||
- [x] Conversion
|
||||
- [x] Creating Instances
|
||||
- [x] Math operations
|
||||
- [x] Utilities
|
||||
- [ ] Contracts
|
||||
- [x] Abigen
|
||||
- [x] Compile
|
||||
- [ ] Creating Instances
|
||||
- [x] Deploy Anvil
|
||||
- [x] Deploy from ABI and bytecode
|
||||
- [x] Deploy Moonbeam
|
||||
- [x] Events
|
||||
- [x] Events with meta
|
||||
- [ ] Methods
|
||||
- [ ] Events
|
||||
- [ ] Logs and filtering
|
||||
- [ ] Solidity topics
|
||||
- [ ] Middleware
|
||||
- [x] Builder
|
||||
- [x] Create custom middleware
|
||||
- [x] Gas escalator
|
||||
- [x] Gas oracle
|
||||
- [x] Nonce manager
|
||||
- [x] Policy
|
||||
- [x] Signer
|
||||
- [ ] Time lag
|
||||
- [ ] Transformer
|
||||
- [ ] Providers
|
||||
- [ ] Http
|
||||
- [x] IPC
|
||||
- [ ] Mock
|
||||
- [x] Quorum
|
||||
- [ ] Retry
|
||||
- [x] RW
|
||||
- [ ] WS
|
||||
- [ ] Queries
|
||||
- [ ] Blocks
|
||||
- [x] Contracts
|
||||
- [ ] Events
|
||||
- [x] Paginated logs
|
||||
- [x] UniswapV2 pair
|
||||
- [ ] Transactions
|
||||
- [x] Subscriptions
|
||||
- [x] Watch blocks
|
||||
- [x] Subscribe events by type
|
||||
- [x] Subscribe logs
|
||||
- [ ] Transactions
|
||||
- [x] Call override
|
||||
- [ ] Create raw transaction
|
||||
- [ ] Create typed transaction
|
||||
- [x] Decode input
|
||||
- [ ] EIP-1559
|
||||
- [x] ENS
|
||||
- [ ] Estimate gas
|
||||
- [ ] Get gas price
|
||||
- [x] Get gas price USD
|
||||
- [x] Remove liquidity
|
||||
- [ ] Set gas for a transaction
|
||||
- [ ] Send raw transaction
|
||||
- [ ] Send typed transaction
|
||||
- [x] Trace
|
||||
- [ ] Transaction receipt
|
||||
- [ ] Transaction status
|
||||
- [x] Transfer ETH
|
||||
- [x] Wallets
|
||||
- [x] Mnemonic
|
||||
- [x] Ledger
|
||||
- [x] Local
|
||||
- [x] Permit hash
|
||||
- [x] Sign message
|
||||
- [x] Trezor
|
||||
- [x] Yubi
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "examples-anvil"
|
||||
version = "1.0.2"
|
||||
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
ethers = { path = "../..", version = "1.0.0" }
|
||||
eyre = "0.6"
|
||||
tokio = { version = "1.18", features = ["full"] }
|
|
@ -1,9 +1,10 @@
|
|||
//! Spawn an [anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) instance in forking mode
|
||||
|
||||
use ethers::utils::Anvil;
|
||||
use eyre::Result;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
async fn main() -> Result<()> {
|
||||
// ensure `anvil` is available in $PATH
|
||||
let anvil =
|
||||
Anvil::new().fork("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").spawn();
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "examples-big-numbers"
|
||||
version = "1.0.2"
|
||||
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
#ethers-core = { version = "^1.0.0", path = "../../ethers-core" }
|
||||
ethers = { path = "../..", version = "1.0.0" }
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Big numbers
|
||||
Ethereum uses big numbers (also known as "bignums" or "arbitrary-precision integers") to represent certain values in its codebase and in blockchain transactions. This is necessary because [the EVM](https://ethereum.org/en/developers/docs/evm) operates on a 256-bit word size, which is different from the usual 32-bit or 64-bit of modern machines. This was chosen for the ease of use with 256-bit cryptography (such as Keccak-256 hashes or secp256k1 signatures).
|
||||
|
||||
It is worth noting that Ethereum is not the only blockchain or cryptocurrency that uses big numbers. Many other blockchains and cryptocurrencies also use big numbers to represent values in their respective systems.
|
||||
|
||||
## Utilities
|
||||
In order to create an application, it is often necessary to convert between the representation of values that is easily understood by humans (such as ether) and the machine-readable form that is used by contracts and math functions (such as wei). This is particularly important when working with Ethereum, as certain values, such as balances and gas prices, must be expressed in wei when sending transactions, even if they are displayed to the user in a different format, such as ether or gwei. To help with this conversion, ethers-rs provides two functions, `parse_units` and `format_units`, which allow you to easily convert between human-readable and machine-readable forms of values. `parse_units` can be used to convert a string representing a value in ether, such as "1.1", into a big number in wei, which can be used in contracts and math functions. `format_units` can be used to convert a big number value into a human-readable string, which is useful for displaying values to users.
|
|
@ -0,0 +1,32 @@
|
|||
use ethers::types::U256;
|
||||
|
||||
fn main() {
|
||||
// a == b
|
||||
let a = U256::from(100_u32);
|
||||
let b = U256::from(100_u32);
|
||||
assert!(a == b);
|
||||
|
||||
// a < b
|
||||
let a = U256::from(1_u32);
|
||||
let b = U256::from(100_u32);
|
||||
assert!(a < b);
|
||||
|
||||
// a <= b
|
||||
let a = U256::from(100_u32);
|
||||
let b = U256::from(100_u32);
|
||||
assert!(a <= b);
|
||||
|
||||
// a > b
|
||||
let a = U256::from(100_u32);
|
||||
let b = U256::from(1_u32);
|
||||
assert!(a > b);
|
||||
|
||||
// a >= b
|
||||
let a = U256::from(100_u32);
|
||||
let b = U256::from(100_u32);
|
||||
assert!(a >= b);
|
||||
|
||||
// a == 0
|
||||
let a = U256::zero();
|
||||
assert!(a.is_zero());
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use ethers::{types::U256, utils::format_units};
|
||||
|
||||
fn main() {
|
||||
let num = U256::from(42_u8);
|
||||
|
||||
let a: u128 = num.as_u128();
|
||||
assert_eq!(a, 42);
|
||||
|
||||
let b: u64 = num.as_u64();
|
||||
assert_eq!(b, 42);
|
||||
|
||||
let c: u32 = num.as_u32();
|
||||
assert_eq!(c, 42);
|
||||
|
||||
let d: usize = num.as_usize();
|
||||
assert_eq!(d, 42);
|
||||
|
||||
let e: String = num.to_string();
|
||||
assert_eq!(e, "42");
|
||||
|
||||
let f: String = format_units(num, 4).unwrap();
|
||||
assert_eq!(f, "0.0042");
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use ethers::{
|
||||
types::{serde_helpers::Numeric, U256},
|
||||
utils::{parse_units, ParseUnits},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
// From strings
|
||||
let a = U256::from_dec_str("42").unwrap();
|
||||
assert_eq!(format!("{a:?}"), "42");
|
||||
|
||||
let amount = "42";
|
||||
let units = 4;
|
||||
let pu: ParseUnits = parse_units(amount, units).unwrap();
|
||||
let b = U256::from(pu);
|
||||
assert_eq!(format!("{b:?}"), "420000");
|
||||
|
||||
// From numbers
|
||||
let c = U256::from(42_u8);
|
||||
assert_eq!(format!("{c:?}"), "42");
|
||||
|
||||
let d = U256::from(42_u16);
|
||||
assert_eq!(format!("{d:?}"), "42");
|
||||
|
||||
let e = U256::from(42_u32);
|
||||
assert_eq!(format!("{e:?}"), "42");
|
||||
|
||||
let f = U256::from(42_u64);
|
||||
assert_eq!(format!("{f:?}"), "42");
|
||||
|
||||
let g = U256::from(42_u128);
|
||||
assert_eq!(format!("{g:?}"), "42");
|
||||
|
||||
let h = U256::from(0x2a);
|
||||
assert_eq!(format!("{h:?}"), "42");
|
||||
|
||||
let i: U256 = 42.into();
|
||||
assert_eq!(format!("{i:?}"), "42");
|
||||
|
||||
// From `Numeric`
|
||||
let num: Numeric = Numeric::U256(U256::one());
|
||||
let l = U256::from(num);
|
||||
assert_eq!(format!("{l:?}"), "1");
|
||||
|
||||
let num: Numeric = Numeric::Num(42);
|
||||
let m = U256::from(num);
|
||||
assert_eq!(format!("{m:?}"), "42");
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
use ethers::{types::U256, utils::format_units};
|
||||
use std::ops::{Div, Mul};
|
||||
|
||||
fn main() {
|
||||
let a = U256::from(10);
|
||||
let b = U256::from(2);
|
||||
|
||||
// addition
|
||||
let sum = a + b;
|
||||
assert_eq!(sum, U256::from(12));
|
||||
|
||||
// subtraction
|
||||
let difference = a - b;
|
||||
assert_eq!(difference, U256::from(8));
|
||||
|
||||
// multiplication
|
||||
let product = a * b;
|
||||
assert_eq!(product, U256::from(20));
|
||||
|
||||
// division
|
||||
let quotient = a / b;
|
||||
assert_eq!(quotient, U256::from(5));
|
||||
|
||||
// modulo
|
||||
let remainder = a % b;
|
||||
assert_eq!(remainder, U256::zero()); // equivalent to `U256::from(0)`
|
||||
|
||||
// exponentiation
|
||||
let power = a.pow(b);
|
||||
assert_eq!(power, U256::from(100));
|
||||
// powers of 10 can also be expressed like this:
|
||||
let power_of_10 = U256::exp10(2);
|
||||
assert_eq!(power_of_10, U256::from(100));
|
||||
|
||||
// Multiply two 'ether' numbers:
|
||||
// Big numbers are integers, that can represent fixed point numbers.
|
||||
// For instance, 1 ether has 18 fixed
|
||||
// decimal places (1.000000000000000000), and its big number
|
||||
// representation is 10^18 = 1000000000000000000.
|
||||
// When we multiply such numbers we are summing up their exponents.
|
||||
// So if we multiply 10^18 * 10^18 we get 10^36, that is obviously incorrect.
|
||||
// In order to get the correct result we need to divide by 10^18.
|
||||
let eth1 = U256::from(10_000000000000000000_u128); // 10 ether
|
||||
let eth2 = U256::from(20_000000000000000000_u128); // 20 ether
|
||||
let base = U256::from(10).pow(18.into());
|
||||
let mul = eth1.mul(eth2).div(base); // We also divide by 10^18
|
||||
let s: String = format_units(mul, "ether").unwrap();
|
||||
assert_eq!(s, "200.000000000000000000"); // 200
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
use ethers::{
|
||||
types::U256,
|
||||
utils::{format_units, parse_units, ParseUnits},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
parse_units_example();
|
||||
format_units_example();
|
||||
}
|
||||
|
||||
/// DApps business logics handles big numbers in 'wei' units (i.e. sending transactions, on-chain
|
||||
/// math, etc.). We provide convenient methods to map user inputs (usually in 'ether' or 'gwei')
|
||||
/// into 'wei' format.
|
||||
fn parse_units_example() {
|
||||
let pu: ParseUnits = parse_units("1.0", "wei").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::one());
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "kwei").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "mwei").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "gwei").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000000));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "szabo").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000000000_u128));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "finney").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000000000000_u128));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", "ether").unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000000000000000_u128));
|
||||
|
||||
let pu: ParseUnits = parse_units("1.0", 18).unwrap();
|
||||
let num = U256::from(pu);
|
||||
assert_eq!(num, U256::from(1000000000000000000_u128));
|
||||
}
|
||||
|
||||
/// DApps business logics handles big numbers in 'wei' units (i.e. sending transactions, on-chain
|
||||
/// math, etc.). On the other hand it is useful to convert big numbers into user readable formats
|
||||
/// when displaying on a UI. Generally dApps display numbers in 'ether' and 'gwei' units,
|
||||
/// respectively for displaying amounts and gas. The `format_units` function will format a big
|
||||
/// number into a user readable string.
|
||||
fn format_units_example() {
|
||||
// 1 ETHER = 10^18 WEI
|
||||
let one_ether = U256::from(1000000000000000000_u128);
|
||||
|
||||
let num: String = format_units(one_ether, "wei").unwrap();
|
||||
assert_eq!(num, "1000000000000000000.0");
|
||||
|
||||
let num: String = format_units(one_ether, "gwei").unwrap();
|
||||
assert_eq!(num, "1000000000.000000000");
|
||||
|
||||
let num: String = format_units(one_ether, "ether").unwrap();
|
||||
assert_eq!(num, "1.000000000000000000");
|
||||
|
||||
// 1 GWEI = 10^9 WEI
|
||||
let one_gwei = U256::from(1000000000_u128);
|
||||
|
||||
let num: String = format_units(one_gwei, 0).unwrap();
|
||||
assert_eq!(num, "1000000000.0");
|
||||
|
||||
let num: String = format_units(one_gwei, "wei").unwrap();
|
||||
assert_eq!(num, "1000000000.0");
|
||||
|
||||
let num: String = format_units(one_gwei, "kwei").unwrap();
|
||||
assert_eq!(num, "1000000.000");
|
||||
|
||||
let num: String = format_units(one_gwei, "mwei").unwrap();
|
||||
assert_eq!(num, "1000.000000");
|
||||
|
||||
let num: String = format_units(one_gwei, "gwei").unwrap();
|
||||
assert_eq!(num, "1.000000000");
|
||||
|
||||
let num: String = format_units(one_gwei, "szabo").unwrap();
|
||||
assert_eq!(num, "0.001000000000");
|
||||
|
||||
let num: String = format_units(one_gwei, "finney").unwrap();
|
||||
assert_eq!(num, "0.000001000000000");
|
||||
|
||||
let num: String = format_units(one_gwei, "ether").unwrap();
|
||||
assert_eq!(num, "0.000000001000000000");
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "examples-contracts"
|
||||
version = "1.0.2"
|
||||
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["legacy"]
|
||||
legacy = []
|
||||
|
||||
[dev-dependencies]
|
||||
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "ethers-solc", "legacy", "rustls", "ws"] }
|
||||
|
||||
eyre = "0.6"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
tokio = { version = "1.18", features = ["macros"] }
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Contracts
|
||||
In this guide, we will go over some examples of using ethers-rs to work with contracts, including using abigen to generate Rust bindings for a contract, listening for contract events, calling contract methods, and instantiating contracts.
|
||||
|
||||
## Generating Rust bindings with abigen
|
||||
To use a contract with ethers-rs, you will need to generate Rust bindings using the abigen tool. abigen is included with the ethers-rs library and can be used to generate Rust bindings for any Solidity contract.
|
||||
|
||||
### Generate a Rust file
|
||||
This method takes a smart contract's Application Binary Interface (ABI) file and generates a Rust file to interact with it. This is useful if the smart contract is referenced in different places in a project. File generation from ABI can also be easily included as a build step of your application.
|
||||
|
||||
Running the code below will generate a file called `token.rs` containing the bindings inside, which exports an `ERC20Token` struct, along with all its events and methods. Put into a `build.rs` file this will generate the bindings during cargo build.
|
||||
|
||||
```rust
|
||||
Abigen::new("ERC20Token", "./abi.json")?.generate()?.write_to_file("token.rs")?;
|
||||
```
|
||||
|
||||
### Generate inline Rust bindings
|
||||
This method takes a smart contract's solidity definition and generates inline Rust code to interact with it. This is useful for fast prototyping and for tight scoped use-cases of your contracts. Inline Rust generation uses the `abigen!` macro to expand Rust contract bindings.
|
||||
|
||||
Running the code below will generate bindings for the `ERC20Token` struct, along with all its events and methods.
|
||||
```rust
|
||||
abigen!(
|
||||
ERC20Token,
|
||||
r#"[
|
||||
function approve(address spender, uint256 amount) external returns (bool)
|
||||
event Transfer(address indexed from, address indexed to, uint256 value)
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value)
|
||||
]"#,
|
||||
);
|
||||
```
|
||||
|
||||
Another way to get the same result, is to provide the ABI contract's definition as follows.
|
||||
```rust
|
||||
abigen!(ERC20Token, "./abi.json",);
|
||||
```
|
||||
|
||||
## Contract instances
|
||||
## Contract methods
|
||||
## Contract events
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [{ "name": "", "type": "string" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{ "name": "guy", "type": "address" },
|
||||
{ "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{ "name": "src", "type": "address" },
|
||||
{ "name": "dst", "type": "address" },
|
||||
{ "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [{ "name": "wad", "type": "uint256" }],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [{ "name": "", "type": "uint8" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{ "name": "", "type": "address" }],
|
||||
"name": "balanceOf",
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [{ "name": "", "type": "string" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{ "name": "dst", "type": "address" },
|
||||
{ "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "name": "", "type": "address" },
|
||||
{ "name": "", "type": "address" }
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": true, "name": "src", "type": "address" },
|
||||
{ "indexed": true, "name": "guy", "type": "address" },
|
||||
{ "indexed": false, "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": true, "name": "src", "type": "address" },
|
||||
{ "indexed": true, "name": "dst", "type": "address" },
|
||||
{ "indexed": false, "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": true, "name": "dst", "type": "address" },
|
||||
{ "indexed": false, "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": true, "name": "src", "type": "address" },
|
||||
{ "indexed": false, "name": "wad", "type": "uint256" }
|
||||
],
|
||||
"name": "Withdrawal",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,77 @@
|
|||
use ethers::{
|
||||
prelude::{abigen, Abigen},
|
||||
providers::{Http, Provider},
|
||||
types::Address,
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Abigen is used to generate Rust code to interact with smart contracts on the blockchain.
|
||||
/// It provides a way to encode and decode data that is passed to and from smart contracts.
|
||||
/// The output of abigen is Rust code, that is bound to the contract's interface, allowing
|
||||
/// developers to call its methods to read/write on-chain state and subscribe to realtime events.
|
||||
///
|
||||
/// The abigen tool can be used in two ways, addressing different use-cases scenarios and developer
|
||||
/// taste:
|
||||
///
|
||||
/// 1. **Rust file generation:** takes a smart contract's Application Binary Interface (ABI)
|
||||
/// file and generates a Rust file to interact with it. This is useful if the smart contract is
|
||||
/// referenced in different places in a project. File generation from ABI can also be easily
|
||||
/// included as a build step of your application.
|
||||
/// 2. **Rust inline generation:** takes a smart contract's solidity definition and generates inline
|
||||
/// Rust code to interact with it. This is useful for fast prototyping and for tight scoped
|
||||
/// use-cases of your contracts.
|
||||
/// 3. **Rust inline generation from ABI:** similar to the previous point but instead of Solidity
|
||||
/// code takes in input a smart contract's Application Binary Interface (ABI) file.
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
rust_file_generation()?;
|
||||
rust_inline_generation().await?;
|
||||
rust_inline_generation_from_abi();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rust_file_generation() -> Result<()> {
|
||||
let base_dir = "./examples/contracts/examples/abi";
|
||||
Abigen::new("IERC20", format!("{base_dir}/IERC20.json"))?
|
||||
.generate()?
|
||||
.write_to_file(format!("{base_dir}/ierc20.rs"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rust_inline_generation_from_abi() {
|
||||
abigen!(IERC20, "./examples/contracts/examples/abi/IERC20.json");
|
||||
}
|
||||
|
||||
async fn rust_inline_generation() -> Result<()> {
|
||||
// The abigen! macro expands the contract's code in the current scope
|
||||
// so that you can interface your Rust program with the blockchain
|
||||
// counterpart of the contract.
|
||||
abigen!(
|
||||
IERC20,
|
||||
r#"[
|
||||
function totalSupply() external view returns (uint256)
|
||||
function balanceOf(address account) external view returns (uint256)
|
||||
function transfer(address recipient, uint256 amount) external returns (bool)
|
||||
function allowance(address owner, address spender) external view returns (uint256)
|
||||
function approve(address spender, uint256 amount) external returns (bool)
|
||||
function transferFrom( address sender, address recipient, uint256 amount) external returns (bool)
|
||||
event Transfer(address indexed from, address indexed to, uint256 value)
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value)
|
||||
]"#,
|
||||
);
|
||||
|
||||
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||
const WETH_ADDRESS: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||
|
||||
let provider = Provider::<Http>::try_from(RPC_URL)?;
|
||||
let client = Arc::new(provider);
|
||||
let address: Address = WETH_ADDRESS.parse()?;
|
||||
let contract = IERC20::new(address, client);
|
||||
|
||||
if let Ok(total_supply) = contract.total_supply().call().await {
|
||||
println!("WETH total supply is {total_supply:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
use ethers::{contract::Abigen, solc::Solc};
|
||||
use ethers::{prelude::Abigen, solc::Solc};
|
||||
use eyre::Result;
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
fn main() -> Result<()> {
|
||||
let mut args = std::env::args();
|
||||
args.next().unwrap(); // skip program name
|
||||
|
||||
let contract_name = args.next().unwrap_or_else(|| "SimpleStorage".to_owned());
|
||||
let contract: String = args.next().unwrap_or_else(|| "examples/contract.sol".to_owned());
|
||||
let contract: String = args
|
||||
.next()
|
||||
.unwrap_or_else(|| "examples/contracts/examples/contracts/contract.sol".to_owned());
|
||||
|
||||
println!("Generating bindings for {contract}\n");
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity >=0.4.24;
|
||||
|
||||
contract SimpleStorage {
|
|
@ -1,10 +1,13 @@
|
|||
use ethers::{
|
||||
prelude::*,
|
||||
solc::{Project, ProjectPathsConfig},
|
||||
utils::Anvil,
|
||||
contract::{abigen, ContractFactory},
|
||||
core::utils::Anvil,
|
||||
middleware::SignerMiddleware,
|
||||
providers::{Http, Provider},
|
||||
signers::{LocalWallet, Signer},
|
||||
solc::{Artifact, Project, ProjectPathsConfig},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::{convert::TryFrom, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
// Generate the type-safe contract bindings by providing the ABI
|
||||
// definition in human readable format
|
|
@ -1,4 +1,10 @@
|
|||
use ethers::{prelude::*, utils::Anvil};
|
||||
use ethers::{
|
||||
contract::abigen,
|
||||
core::utils::Anvil,
|
||||
middleware::SignerMiddleware,
|
||||
providers::{Http, Provider},
|
||||
signers::{LocalWallet, Signer},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
use ethers::{prelude::*, utils::Anvil};
|
||||
use ethers::{
|
||||
contract::{abigen, ContractFactory},
|
||||
core::utils::Anvil,
|
||||
middleware::SignerMiddleware,
|
||||
providers::{Http, Provider},
|
||||
signers::{LocalWallet, Signer},
|
||||
solc::Solc,
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
|
||||
|
||||
|
@ -6,7 +13,7 @@ use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
|
|||
// definition
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
"./examples/contract_abi.json",
|
||||
"./examples/contracts/examples/abi/contract_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
|
||||
|
@ -17,8 +24,8 @@ async fn main() -> Result<()> {
|
|||
let anvil = Anvil::new().spawn();
|
||||
|
||||
// set the path to the contract, `CARGO_MANIFEST_DIR` points to the directory containing the
|
||||
// manifest of `ethers`. which will be `../` relative to this file
|
||||
let source = Path::new(&env!("CARGO_MANIFEST_DIR")).join("examples/contract.sol");
|
||||
// manifest of `example/contracts`. which will be `../` relative to this file
|
||||
let source = Path::new(&env!("CARGO_MANIFEST_DIR")).join("examples/contracts/contract.sol");
|
||||
let compiled = Solc::default().compile_source(source).expect("Could not compile contracts");
|
||||
let (abi, bytecode, _runtime_bytecode) =
|
||||
compiled.find("SimpleStorage").expect("could not find contract").into_parts_or_default();
|
|
@ -1,8 +1,8 @@
|
|||
use ethers::prelude::*;
|
||||
use ethers::contract::abigen;
|
||||
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
"./examples/contract_abi.json",
|
||||
"./examples/contracts/examples/abi/contract_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
|
||||
|
@ -19,7 +19,9 @@ abigen!(
|
|||
#[tokio::main]
|
||||
#[cfg(feature = "legacy")]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
use ethers::prelude::*;
|
||||
use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
|
||||
|
||||
const MOONBEAM_DEV_ENDPOINT: &str = "http://localhost:9933";
|
||||
|
||||
// set the path to the contract, `CARGO_MANIFEST_DIR` points to the directory containing the
|
|
@ -0,0 +1,68 @@
|
|||
use ethers::{
|
||||
contract::abigen,
|
||||
core::types::Address,
|
||||
providers::{Provider, StreamExt, Ws},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
abigen!(
|
||||
IERC20,
|
||||
r#"[
|
||||
event Transfer(address indexed from, address indexed to, uint256 value)
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value)
|
||||
]"#,
|
||||
);
|
||||
|
||||
const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||
const WETH_ADDRESS: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let provider = Provider::<Ws>::connect(WSS_URL).await?;
|
||||
let client = Arc::new(provider);
|
||||
let address: Address = WETH_ADDRESS.parse()?;
|
||||
let contract = IERC20::new(address, client);
|
||||
|
||||
listen_all_events(&contract).await?;
|
||||
listen_specific_events(&contract).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a contract instance, subscribe to all possible events.
|
||||
/// This allows to centralize the event handling logic and dispatch
|
||||
/// proper actions.
|
||||
///
|
||||
/// Note that all event bindings have been generated
|
||||
/// by abigen. Feel free to investigate the abigen expanded code to
|
||||
/// better understand types and functionalities.
|
||||
async fn listen_all_events(contract: &IERC20<Provider<Ws>>) -> Result<()> {
|
||||
let events = contract.events().from_block(16232696);
|
||||
let mut stream = events.stream().await?.take(1);
|
||||
|
||||
while let Some(Ok(evt)) = stream.next().await {
|
||||
match evt {
|
||||
IERC20Events::ApprovalFilter(f) => println!("{f:?}"),
|
||||
IERC20Events::TransferFilter(f) => println!("{f:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a contract instance subscribe to a single type of event.
|
||||
///
|
||||
/// Note that all event bindings have been generated
|
||||
/// by abigen. Feel free to investigate the abigen expanded code to
|
||||
/// better understand types and functionalities.
|
||||
async fn listen_specific_events(contract: &IERC20<Provider<Ws>>) -> Result<()> {
|
||||
let events = contract.event::<ApprovalFilter>().from_block(16232696);
|
||||
let mut stream = events.stream().await?.take(1);
|
||||
|
||||
while let Some(Ok(f)) = stream.next().await {
|
||||
println!("ApprovalFilter event: {f:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
use ethers::prelude::*;
|
||||
use ethers::{
|
||||
contract::abigen,
|
||||
core::types::Address,
|
||||
providers::{Provider, StreamExt, Ws},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -11,9 +15,6 @@ abigen!(
|
|||
]"#,
|
||||
);
|
||||
|
||||
// In order to run this example you need to include Ws and TLS features
|
||||
// Run this example with
|
||||
// `cargo run -p ethers --example subscribe_contract_events_with_meta --features="ws","rustls"`
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let client =
|
||||
|
@ -27,8 +28,8 @@ async fn main() -> Result<()> {
|
|||
let weth = ERC20::new(address, Arc::clone(&client));
|
||||
|
||||
// Subscribe Transfer events
|
||||
let events = weth.events();
|
||||
let mut stream = events.stream().await?.with_meta();
|
||||
let events = weth.events().from_block(16232698);
|
||||
let mut stream = events.stream().await?.with_meta().take(1);
|
||||
while let Some(Ok((event, meta))) = stream.next().await {
|
||||
println!("src: {:?}, dst: {:?}, wad: {:?}", event.src, event.dst, event.wad);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#[tokio::main]
|
||||
async fn main() {}
|
|
@ -0,0 +1,2 @@
|
|||
#[tokio::main]
|
||||
async fn main() {}
|
|
@ -1,7 +1,17 @@
|
|||
//! Main entry point for ContractMonitor
|
||||
|
||||
use ethers::{prelude::*, utils::Anvil};
|
||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use ethers::{
|
||||
contract::abigen,
|
||||
core::{
|
||||
types::{Address, U256},
|
||||
utils::Anvil,
|
||||
},
|
||||
middleware::SignerMiddleware,
|
||||
providers::{Http, Provider},
|
||||
signers::LocalWallet,
|
||||
};
|
||||
|
||||
abigen!(VerifierContract, "ethers-contract/tests/solidity-contracts/verifier_abi.json");
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "ethers-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.64"
|
||||
authors = ["Matthias Seitz <matthias.seitz@outlook.de>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]
|
|
@ -24,7 +24,7 @@ macro_rules! log {
|
|||
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
"./../contract_abi.json",
|
||||
"./abi/contract_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ wasm_bindgen_test_configure!(run_in_browser);
|
|||
// definition in human readable format
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
"../contract_abi.json",
|
||||
"./abi/contract_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "examples-events"
|
||||
version = "1.0.2"
|
||||
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
ethers = { path = "../..", version = "1.0.0" }
|
||||
|
||||
eyre = "0.6"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
tokio = { version = "1.18", features = ["macros"] }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue