refactor: examples (#1940)

* ToC

* Big numbers section

* Middleware examples: builder

* Middleware examples: gas_escalator

* Middleware examples: gas_oracle

* Middleware examples: signer

* Middleware examples: missing stubs

* review: applied DaniPopes suggestions to big numbers

* typo

* Middleware examples: nonce_manager

* cargo +nightly fmt

* update roadmap

* Middleware examples: policy

* Middleware examples: added docs

* Contracts examples: created folder; included abigen example

* Contracts examples: refactor abigen docs. Fixed cargo example reference

* Contracts examples: contract_events; minor docs changes

* Moved each example under its own crate.
Cargo builds locally
TODO: Fix broken examples CI

* Big numbers examples: used regular operators for math

* Single examples run correctly (missing overall CI execution)
Example crates dependencies
Removed duplicates

* review: Applied gakonst note to remove commented items in workspace manifest

* review: Applied gakonst note to restore visibility on contract constructor

* ci:
- Run/Build  examples in a single step to avoid duplicated scripts
- Removed ci.yaml step "Build all examples"

* cargo +nightly fmt

* ci: fix WASM step error

* Removed deprecated EthGasStation example

* WASM example uses local copy of `contract_abi.json`. In this way we keep the WASM example auto-consistent, at the cost of a small duplication

* Cargo.lock aligned to master branch

* Removed useless comments in examples

* review: Applied gakonst note to add panic!() on the policy middleware example

* review: Applied gakonst suggestion to add a custom middleware example

* typos in docs

* Update examples/big-numbers/examples/bn_math_operations.rs

review: Accepted commit suggested by DaniPopes

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>

* review: Applied DaniPopes suggestion on assert_eq!

* Update examples/big-numbers/README.md

review: Accepted DaniPopes suggestion on big-numbers docs

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>

* review: All imports now reference the "ethers" crate

* ci: added features ["ws", "rustls"] where needed
cargo +nigthly fmt

* Examples with special features (e.g. ipc, trezor etc.) are built alongside them. This is expressed as a "default" requirement in their respective Cargo.toml

* cargo +nightly fmt

* Examples: Gas oracle API keys from env
Added missing features in middleware Cargo.toml

* typo: use expect() instead of unwrap()

* Updated ToC
Moved 2 examples under more relevant folders

* Gas oracle examples raise panic on middleware errors

* review: removed useless [[example]] in Cargo.toml

* review: removed #[allow(unused_must_use)] from gas_escalator example

* review: Removed prefixes from file names

* review: removed useless [[example]] in Cargo.toml

* docs: Updated description to run examples in the workspace README.md

Co-authored-by: Andrea Simeoni <>
Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
This commit is contained in:
Andrea Simeoni 2022-12-29 13:53:11 +01:00 committed by GitHub
parent e26ede21f1
commit 10310ce3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 1623 additions and 207 deletions

View File

@ -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

105
Cargo.lock generated
View File

@ -1627,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"

View File

@ -21,7 +21,8 @@ members = [
"ethers-middleware",
"ethers-etherscan",
"ethers-solc",
"examples/ethers-wasm",
# Example crates
"examples/*",
]
default-members = [
@ -128,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"]

View File

@ -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

81
examples/README.md Normal file
View File

@ -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

10
examples/anvil/Cargo.toml Normal file
View File

@ -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"] }

0
examples/anvil/README.md Normal file
View File

View File

@ -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();

View File

@ -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" }

View File

@ -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.

View File

@ -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());
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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
}

View File

@ -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");
}

View File

@ -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"] }

View File

@ -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

View File

@ -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"
}
]

View File

@ -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(())
}

View File

@ -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");

View File

@ -1,3 +1,4 @@
// SPDX-License-Identifier: Unlicense
pragma solidity >=0.4.24;
contract SimpleStorage {

View File

@ -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

View File

@ -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};

View File

@ -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();

View File

@ -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

View File

@ -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(())
}

View File

@ -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);

View File

@ -0,0 +1,2 @@
#[tokio::main]
async fn main() {}

View File

@ -0,0 +1,2 @@
#[tokio::main]
async fn main() {}

View File

@ -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");

View File

@ -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"}]

View File

@ -24,7 +24,7 @@ macro_rules! log {
abigen!(
SimpleContract,
"./../contract_abi.json",
"./abi/contract_abi.json",
event_derives(serde::Deserialize, serde::Serialize)
);

View File

@ -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)
);

View File

@ -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"] }

View File

View File

@ -0,0 +1,2 @@
#[tokio::main]
async fn main() {}

View File

@ -0,0 +1,15 @@
[package]
name = "examples-middleware"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["rustls", "ws"] }
async-trait = { version = "0.1.50", default-features = false }
eyre = "0.6"
serde_json = "1.0.61"
thiserror = { version = "1.0", default-features = false }
tokio = { version = "1.18", features = ["macros", "rt", "rt-multi-thread"] }

View File

View File

@ -0,0 +1,58 @@
use ethers::{
core::types::BlockNumber,
middleware::{gas_escalator::*, gas_oracle::*, *},
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
};
use std::convert::TryFrom;
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
const SIGNING_KEY: &str = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
/// In ethers-rs, middleware is a way to customize the behavior of certain aspects of the library by
/// injecting custom logic into the process of sending transactions and interacting with contracts
/// on the Ethereum blockchain. The MiddlewareBuilder trait provides a way to define a chain of
/// middleware that will be called at different points in this process, allowing you to customize
/// the behavior of the Provider based on your needs.
#[tokio::main]
async fn main() {
builder_example().await;
builder_example_raw_wrap().await;
}
async fn builder_example() {
let signer = SIGNING_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 provider = Provider::<Http>::try_from(RPC_URL)
.unwrap()
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.gas_oracle(gas_oracle)
.with_signer(signer)
.nonce_manager(address); // Outermost layer
match provider.get_block(BlockNumber::Latest).await {
Ok(Some(block)) => println!("{:?}", block.number),
_ => println!("Unable to get latest block"),
}
}
async fn builder_example_raw_wrap() {
let signer = SIGNING_KEY.parse::<LocalWallet>().unwrap();
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
let provider = Provider::<Http>::try_from(RPC_URL)
.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| NonceManagerMiddleware::new(p, address)); // Outermost layer
match provider.get_block(BlockNumber::Latest).await {
Ok(Some(block)) => println!("{:?}", block.number),
_ => println!("Unable to get latest block"),
}
}

View File

@ -0,0 +1,156 @@
use async_trait::async_trait;
use ethers::{
core::{
types::{transaction::eip2718::TypedTransaction, BlockId, TransactionRequest, U256},
utils::{parse_units, Anvil},
},
middleware::MiddlewareBuilder,
providers::{FromErr, Http, Middleware, PendingTransaction, Provider},
signers::{LocalWallet, Signer},
};
use thiserror::Error;
/// This example demonstrates the mechanisms for creating custom middlewares in ethers-rs.
/// The example includes explanations of the process and code snippets to illustrate the
/// concepts. It is intended for developers who want to learn how to customize the behavior of
/// ethers-rs providers by creating and using custom middlewares.
///
/// This custom middleware increases the gas value of transactions sent through an ethers-rs
/// provider by a specified percentage and will be called for each transaction before it is sent.
/// This can be useful if you want to ensure that transactions have a higher gas value than the
/// estimated, in order to improve the chances of them not to run out of gas when landing on-chain.
#[derive(Debug)]
struct GasMiddleware<M> {
inner: M,
/// This value is used to raise the gas value before sending transactions
contingency: U256,
}
/// Contingency is expressed with 4 units
/// e.g.
/// 50% => 1 + 0.5 => 15000
/// 20% => 1 + 0.2 => 12000
/// 1% => 1 + 0.01 => 10100
const CONTINGENCY_UNITS: usize = 4;
impl<M> GasMiddleware<M>
where
M: Middleware,
{
/// Creates an instance of GasMiddleware
/// `ìnner` the inner Middleware
/// `perc` This is an unsigned integer representing the percentage increase in the amount of gas
/// to be used for the transaction. The percentage is relative to the gas value specified in the
/// transaction. Valid contingency values are in range 1..=50. Otherwise a custom middleware
/// error is raised.
pub fn new(inner: M, perc: u32) -> Result<Self, GasMiddlewareError<M>> {
let contingency = match perc {
0 => Err(GasMiddlewareError::TooLowContingency(perc))?,
51.. => Err(GasMiddlewareError::TooHighContingency(perc))?,
1..=50 => {
let decimals = 2;
let perc = U256::from(perc) * U256::exp10(decimals); // e.g. 50 => 5000
let one = parse_units(1, CONTINGENCY_UNITS).unwrap();
let one = U256::from(one);
one + perc // e.g. 50% => 1 + 0.5 => 10000 + 5000 => 15000
}
};
Ok(Self { inner, contingency })
}
}
/// Let's implement the `Middleware` trait for our custom middleware.
/// All trait functions are derived automatically, so we just need to
/// override the needed functions.
#[async_trait]
impl<M> Middleware for GasMiddleware<M>
where
M: Middleware,
{
type Error = GasMiddlewareError<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &M {
&self.inner
}
/// In this function we bump the transaction gas value by the specified percentage
/// This can raise a custom middleware error if a gas amount was not set for
/// the transaction.
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let mut tx: TypedTransaction = tx.into();
let curr_gas: U256 = match tx.gas() {
Some(gas) => gas.to_owned(),
None => Err(GasMiddlewareError::NoGasSetForTransaction)?,
};
println!("Original transaction gas: {curr_gas:?} wei");
let units: U256 = U256::exp10(CONTINGENCY_UNITS.into());
let raised_gas: U256 = (curr_gas * self.contingency) / units;
tx.set_gas(raised_gas);
println!("Raised transaction gas: {raised_gas:?} wei");
// Dispatch the call to the inner layer
self.inner().send_transaction(tx, block).await.map_err(FromErr::from)
}
}
/// This example demonstrates how to handle errors in custom middlewares. It shows how to define
/// custom error types, use them in middleware implementations, and how to propagate the errors
/// through the middleware chain. This is intended for developers who want to create custom
/// middlewares that can handle and propagate errors in a consistent and robust way.
#[derive(Error, Debug)]
pub enum GasMiddlewareError<M: Middleware> {
/// Thrown when the internal middleware errors
#[error("{0}")]
MiddlewareError(M::Error),
/// Specific errors of this GasMiddleware.
/// Please refer to the `thiserror` crate for
/// further docs.
#[error("{0}")]
TooHighContingency(u32),
#[error("{0}")]
TooLowContingency(u32),
#[error("Cannot raise gas! Gas value not provided for this transaction.")]
NoGasSetForTransaction,
}
impl<M: Middleware> FromErr<M::Error> for GasMiddlewareError<M> {
fn from(src: M::Error) -> Self {
GasMiddlewareError::MiddlewareError(src)
}
}
#[tokio::main]
async fn main() -> eyre::Result<()> {
let anvil = Anvil::new().spawn();
let wallet: LocalWallet = anvil.keys()[0].clone().into();
let wallet2: LocalWallet = anvil.keys()[1].clone().into();
let signer = wallet.with_chain_id(anvil.chain_id());
let gas_raise_perc = 50; // 50%;
let provider = Provider::<Http>::try_from(anvil.endpoint())?
.with_signer(signer)
.wrap_into(|s| GasMiddleware::new(s, gas_raise_perc).unwrap());
let gas = 15000;
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).gas(gas);
let pending_tx = provider.send_transaction(tx, None).await?;
let receipt = pending_tx.await?.ok_or_else(|| eyre::format_err!("tx dropped from mempool"))?;
let tx = provider.get_transaction(receipt.transaction_hash).await?;
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
println!("Tx receipt: {}", serde_json::to_string(&receipt)?);
Ok(())
}

View File

@ -0,0 +1,59 @@
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
middleware::gas_escalator::*,
providers::{Http, Middleware, Provider},
};
use eyre::Result;
/// The gas escalator middleware in ethers-rs is designed to automatically increase the gas cost of
/// transactions if they get stuck in the mempool. This can be useful if you want to
/// ensure that transactions are processed in a timely manner without having to manually adjust the
/// gas cost yourself.
#[tokio::main]
async fn main() -> Result<()> {
let every_secs: u64 = 60;
let max_price: Option<i32> = None;
// Linearly increase gas price:
// Start with `initial_price`, then increase it by fixed amount `increase_by` every `every_secs`
// seconds until the transaction gets confirmed. There is an optional upper limit.
let increase_by: i32 = 100;
let linear_escalator = LinearGasPrice::new(increase_by, every_secs, max_price);
send_escalating_transaction(linear_escalator).await?;
// Geometrically increase gas price:
// Start with `initial_price`, then increase it every 'every_secs' seconds by a fixed
// coefficient. Coefficient defaults to 1.125 (12.5%), the minimum increase for Parity to
// replace a transaction. Coefficient can be adjusted, and there is an optional upper limit.
let coefficient: f64 = 1.125;
let geometric_escalator = GeometricGasPrice::new(coefficient, every_secs, max_price);
send_escalating_transaction(geometric_escalator).await?;
Ok(())
}
async fn send_escalating_transaction<E>(escalator: E) -> Result<()>
where
E: GasEscalator + Clone + 'static,
{
// Spawn local node
let anvil = Anvil::new().spawn();
let endpoint = anvil.endpoint();
// Connect to the node
let provider = Provider::<Http>::try_from(endpoint)?;
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
let accounts = provider.get_accounts().await?;
let from = accounts[0];
let to = accounts[1];
let tx = TransactionRequest::new().from(from).to(to).value(1000);
// Bumps the gas price until transaction gets mined
let pending_tx = provider.send_transaction(tx, None).await?;
let receipt = pending_tx.await?;
println!("{receipt:?}");
Ok(())
}

View File

@ -0,0 +1,88 @@
use ethers::{
core::types::Chain,
etherscan::Client,
middleware::gas_oracle::{
BlockNative, Etherscan, GasCategory, GasNow, GasOracle, Polygon, ProviderOracle,
},
providers::{Http, Provider},
};
/// In Ethereum, the "gas" of a transaction refers to the amount of computation required to execute
/// the transaction on the blockchain. Gas is typically measured in units of "gas," and the cost of
/// a transaction is determined by the amount of gas it consumes.
///
/// A "gas oracle" is a tool or service that provides information about the current price of gas on
/// the Ethereum network. Gas oracles are often used to help determine the appropriate amount of gas
/// to include in a transaction, in order to ensure that it will be processed in a timely manner
/// without running out of gas.
///
/// Ethers-rs includes a feature called "gas oracle middleware" that allows you to customize the
/// behavior of the library when it comes to determining the gas cost of transactions.
#[tokio::main]
async fn main() {
blocknative().await;
etherscan().await;
gas_now().await;
polygon().await;
provider_oracle().await;
//etherchain().await; // FIXME: Etherchain URL is broken (Http 404)
}
async fn blocknative() {
let api_key: Option<String> = std::env::var("BLOCK_NATIVE_API_KEY").ok();
let oracle = BlockNative::new(api_key).category(GasCategory::Fastest);
match oracle.fetch().await {
Ok(gas_price) => println!("[Blocknative]: Gas price is {gas_price:?}"),
Err(e) => panic!("[Blocknative]: Cannot estimate gas: {e:?}"),
}
}
async fn etherscan() {
let chain = Chain::Mainnet;
let api_key: String = std::env::var("ETHERSCAN_API_KEY_ETHEREUM").expect("Provide an API key");
if let Ok(client) = Client::new(chain, api_key) {
let oracle = Etherscan::new(client).category(GasCategory::Fast);
match oracle.fetch().await {
Ok(gas_price) => println!("[Etherscan]: Gas price is {gas_price:?}"),
Err(e) => panic!("[Etherscan]: Cannot estimate gas: {e:?}"),
}
}
}
async fn gas_now() {
let oracle = GasNow::new().category(GasCategory::Fast);
match oracle.fetch().await {
Ok(gas_price) => println!("[GasNow]: Gas price is {gas_price:?}"),
Err(e) => panic!("[GasNow]: Cannot estimate gas: {e:?}"),
}
}
async fn polygon() {
let chain = Chain::Polygon;
if let Ok(oracle) = Polygon::new(chain) {
match oracle.category(GasCategory::SafeLow).fetch().await {
Ok(gas_price) => println!("[Polygon]: Gas price is {gas_price:?}"),
Err(e) => panic!("[Polygon]: Cannot estimate gas: {e:?}"),
}
}
}
async fn provider_oracle() {
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let provider = Provider::<Http>::try_from(RPC_URL).unwrap();
let oracle = ProviderOracle::new(provider);
match oracle.fetch().await {
Ok(gas_price) => println!("[Provider oracle]: Gas price is {gas_price:?}"),
Err(e) => panic!("[Provider oracle]: Cannot estimate gas: {e:?}"),
}
}
/*
// FIXME: Etherchain URL is broken (Http 404)
async fn etherchain() {
let oracle = Etherchain::new().category(GasCategory::Standard);
match oracle.fetch().await {
Ok(gas_price) => println!("[Etherchain]: Gas price is {gas_price:?}"),
Err(e) => panic!("[Etherchain]: Cannot estimate gas: {e:?}"),
}
}*/

View File

@ -0,0 +1,46 @@
use ethers::{
core::{
types::{BlockNumber, TransactionRequest},
utils::Anvil,
},
middleware::MiddlewareBuilder,
providers::{Http, Middleware, Provider},
};
use eyre::Result;
/// In Ethereum, the nonce of a transaction is a number that represents the number of transactions
/// that have been sent from a particular account. The nonce is used to ensure that transactions are
/// processed in the order they are intended, and to prevent the same transaction from being
/// processed multiple times.
///
/// The nonce manager in ethers-rs is a middleware that helps you manage the nonce
/// of transactions by keeping track of the current nonce for a given account and automatically
/// incrementing it as needed. This can be useful if you want to ensure that transactions are sent
/// in the correct order, or if you want to avoid having to manually manage the nonce yourself.
#[tokio::main]
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
let endpoint = anvil.endpoint();
let provider = Provider::<Http>::try_from(endpoint)?;
let accounts = provider.get_accounts().await?;
let account = accounts[0];
let to = accounts[1];
let tx = TransactionRequest::new().from(account).to(to).value(1000);
let nonce_manager = provider.nonce_manager(account);
let curr_nonce = nonce_manager
.get_transaction_count(account, Some(BlockNumber::Pending.into()))
.await?
.as_u64();
assert_eq!(curr_nonce, 0);
nonce_manager.send_transaction(tx, None).await?;
let next_nonce = nonce_manager.next().as_u64();
assert_eq!(next_nonce, 1);
Ok(())
}

View File

@ -0,0 +1,39 @@
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
middleware::{
policy::{PolicyMiddlewareError, RejectEverything},
MiddlewareBuilder, PolicyMiddleware,
},
providers::{Http, Middleware, Provider},
};
use eyre::Result;
/// Policy middleware is a way to inject custom logic into the process of sending transactions and
/// interacting with contracts on the Ethereum blockchain. It allows you to define rules or policies
/// that should be followed when performing these actions, and to customize the behavior of the
/// library based on these policies.
#[tokio::main]
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
let endpoint = anvil.endpoint();
let provider = Provider::<Http>::try_from(endpoint)?;
let accounts = provider.get_accounts().await?;
let account = accounts[0];
let to = accounts[1];
let tx = TransactionRequest::new().from(account).to(to).value(1000);
let policy = RejectEverything;
let policy_middleware = provider.wrap_into(|p| PolicyMiddleware::new(p, policy));
match policy_middleware.send_transaction(tx, None).await {
Err(e) => {
// Given the RejectEverything policy, we expect to execute this branch
assert!(matches!(e, PolicyMiddlewareError::PolicyError(())))
}
_ => panic!("We don't expect this to happen!"),
}
Ok(())
}

View File

@ -0,0 +1,40 @@
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
};
use eyre::Result;
use std::convert::TryFrom;
/// In Ethereum, transactions must be signed with a private key before they can be broadcast to the
/// network. Ethers-rs provides a way to customize this process by allowing
/// you to define a signer, called to sign transactions before they are sent.
#[tokio::main]
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
let wallet: LocalWallet = anvil.keys()[0].clone().into();
let wallet2: LocalWallet = anvil.keys()[1].clone().into();
// connect to the network
let provider = Provider::<Http>::try_from(anvil.endpoint())?;
// connect the wallet to the provider
let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
// craft the transaction
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
// send it!
let pending_tx = client.send_transaction(tx, None).await?;
// get the mined tx
let receipt = pending_tx.await?.ok_or_else(|| eyre::format_err!("tx dropped from mempool"))?;
let tx = client.get_transaction(receipt.transaction_hash).await?;
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
println!("Tx receipt: {}", serde_json::to_string(&receipt)?);
Ok(())
}

View File

@ -0,0 +1,2 @@
#[tokio::main]
async fn main() {}

View File

@ -0,0 +1,2 @@
#[tokio::main]
async fn main() {}

View File

@ -0,0 +1,17 @@
[package]
name = "examples-providers"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[features]
default = ["ipc"]
ipc = []
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "ipc", "rustls", "ws"] }
eyre = "0.6"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }

View File

View File

@ -1,11 +1,15 @@
//! Example usage for the `QuorumProvider` that requests multiple backends and only returns
//! a value if the configured `Quorum` was reached.
use ethers::{prelude::*, utils::Anvil};
use ethers::{
core::utils::Anvil,
providers::{Http, Middleware, Provider, Quorum, QuorumProvider, WeightedProvider, Ws},
};
use eyre::Result;
use std::{str::FromStr, time::Duration};
#[tokio::main]
async fn main() -> eyre::Result<()> {
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
// create a quorum provider with some providers

View File

@ -1,11 +1,15 @@
//! Example usage for the `RwClient` that uses a didicated client to send transaction and nother one
//! for read ops
use ethers::{prelude::*, utils::Anvil};
use ethers::{
core::utils::Anvil,
providers::{Http, Middleware, Provider, Ws},
};
use eyre::Result;
use std::{str::FromStr, time::Duration};
#[tokio::main]
async fn main() -> eyre::Result<()> {
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
let http = Http::from_str(&anvil.endpoint())?;

View File

@ -0,0 +1,13 @@
[package]
name = "examples-queries"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "rustls", "ws"] }
eyre = "0.6"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }

View File

View File

@ -1,4 +1,10 @@
use ethers::{abi::AbiDecode, prelude::*, providers::Middleware};
use ethers::{
core::{
abi::AbiDecode,
types::{BlockNumber, Filter, U256},
},
providers::{Middleware, Provider, StreamExt, Ws},
};
use eyre::Result;
use std::sync::Arc;
@ -15,7 +21,7 @@ async fn main() -> Result<()> {
let erc20_transfer_filter =
Filter::new().from_block(last_block - 10000).event("Transfer(address,address,uint256)");
let mut stream = client.get_logs_paginated(&erc20_transfer_filter, 10);
let mut stream = client.get_logs_paginated(&erc20_transfer_filter, 10).take(5);
while let Some(res) = stream.next().await {
let log = res?;

View File

@ -1,4 +1,8 @@
use ethers::prelude::*;
use ethers::{
contract::abigen,
core::types::Address,
providers::{Http, Provider},
};
use eyre::Result;
use std::sync::Arc;

View File

@ -1,38 +0,0 @@
use ethers::prelude::*;
use eyre::Result;
use std::sync::Arc;
// Generate the type-safe contract bindings by providing the ABI
// definition in human readable format
abigen!(
ERC20,
r#"[
event Transfer(address indexed src, address indexed dst, uint wad)
]"#,
);
// 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 --features="ws","rustls"`
#[tokio::main]
async fn main() -> Result<()> {
let client =
Provider::<Ws>::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
.await?;
let client = Arc::new(client);
// WETH Token
let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse::<Address>()?;
let weth = ERC20::new(address, Arc::clone(&client));
// Subscribe Transfer events
let events = weth.events();
let mut stream = events.stream().await?;
while let Some(Ok(event)) = stream.next().await {
println!("src: {:?}, dst: {:?}, wad: {:?}", event.src, event.dst, event.wad);
}
Ok(())
}

View File

@ -0,0 +1,13 @@
[package]
name = "examples-subscriptions"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "rustls", "ws"] }
eyre = "0.6"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }

View File

View File

@ -1,5 +1,10 @@
use ethers::{contract::Contract, prelude::*};
use ethers::{
contract::{abigen, Contract},
core::types::ValueOrArray,
providers::{Provider, StreamExt, Ws},
};
use std::{error::Error, sync::Arc};
abigen!(
AggregatorInterface,
r#"[
@ -14,10 +19,6 @@ const PRICE_FEED_3: &str = "0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81";
/// Subscribe to a typed event stream without requiring a `Contract` instance.
/// In this example we subscribe Chainlink price feeds and filter out them
/// by address.
/// -------------------------------------------------------------------------------
/// In order to run this example you need to include Ws and TLS features
/// Run this example with
/// `cargo run -p ethers --example subscribe_events_by_type --features="ws","rustls"`
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = get_client().await;

View File

@ -1,9 +1,13 @@
use ethers::{abi::AbiDecode, prelude::*};
use ethers::{
core::{
abi::AbiDecode,
types::{Address, BlockNumber, Filter, U256},
},
providers::{Middleware, Provider, StreamExt, Ws},
};
use eyre::Result;
use std::sync::Arc;
// In order to run this example you need to include Ws and TLS features
// Run this example with `cargo run -p ethers --example subscribe_logs --features="ws","rustls"`
#[tokio::main]
async fn main() -> Result<()> {
let client =

View File

@ -1,12 +1,13 @@
use ethers::{prelude::*, utils::Anvil};
use ethers::providers::{Middleware, Provider, StreamExt, Ws};
use eyre::Result;
use std::time::Duration;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let anvil = Anvil::new().block_time(1u64).spawn();
let ws = Ws::connect(anvil.ws_endpoint()).await?;
async fn main() -> Result<()> {
let ws_endpoint = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let ws = Ws::connect(ws_endpoint).await?;
let provider = Provider::new(ws).interval(Duration::from_millis(2000));
let mut stream = provider.watch_blocks().await?.take(5);
let mut stream = provider.watch_blocks().await?.take(1);
while let Some(block) = stream.next().await {
let block = provider.get_block(block).await?.unwrap();
println!(

View File

@ -0,0 +1,13 @@
[package]
name = "examples-transactions"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "rustls", "ws"] }
eyre = "0.6"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }

View File

View File

@ -1,14 +1,21 @@
use ethers::{
prelude::*,
providers::call_raw::RawCall,
utils::{parse_ether, Geth},
contract::abigen,
core::{
types::{Address, TransactionRequest, H256},
utils::{parse_ether, Geth},
},
providers::{
call_raw::{self, RawCall},
Http, Provider,
},
};
use eyre::Result;
use std::sync::Arc;
abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter.json",);
#[tokio::main]
async fn main() -> eyre::Result<()> {
async fn main() -> Result<()> {
let geth = Geth::new().spawn();
let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
let client = Arc::new(provider);

View File

@ -1,4 +1,7 @@
use ethers::{abi::AbiDecode, prelude::*};
use ethers::{
contract::abigen,
core::{abi::AbiDecode, types::Bytes},
};
use eyre::Result;
// Abigen creates a SwapExactTokensForTokensCall struct that can be used to decode

View File

@ -1,6 +1,8 @@
use ethers::{prelude::*, utils::Anvil};
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
providers::{Http, Middleware, Provider},
};
use eyre::Result;
use std::convert::TryFrom;
#[tokio::main]
async fn main() -> Result<()> {

View File

@ -1,10 +1,18 @@
use ethers::{prelude::*, utils::format_units};
use std::{
error::Error,
ops::{Div, Mul},
sync::Arc,
};
use ethers::{
contract::abigen,
core::{
types::{Address, I256, U256},
utils::format_units,
},
providers::{Http, Middleware, Provider},
};
abigen!(
AggregatorInterface,
r#"[

View File

@ -1,6 +1,13 @@
use ethers::prelude::*;
use std::sync::Arc;
use ethers::{
contract::abigen,
core::types::{Address, U256},
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
};
use eyre::Result;
use std::{convert::TryFrom, sync::Arc};
abigen!(
UniswapV2Router,

View File

@ -1,4 +1,7 @@
use ethers::prelude::*;
use ethers::{
core::types::{GethDebugTracingOptions, H256},
providers::{Http, Middleware, Provider},
};
use eyre::Result;
use std::str::FromStr;

View File

@ -1,4 +1,7 @@
use ethers::{prelude::*, utils::Anvil};
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
providers::{Http, Middleware, Provider},
};
use eyre::Result;
use std::convert::TryFrom;

View File

@ -0,0 +1,20 @@
[package]
name = "examples-wallets"
version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[features]
default = ["ledger", "trezor", "yubi"]
ledger = []
trezor = []
yubi = []
[dev-dependencies]
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "eip712", "ledger", "rustls", "trezor", "ws", "yubi"] }
eyre = "0.6"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }

View File

View File

@ -1,4 +1,9 @@
use ethers::{prelude::*, utils::Anvil};
use ethers::{
core::{types::TransactionRequest, utils::Anvil},
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
};
use eyre::Result;
use std::convert::TryFrom;

View File

@ -1,6 +1,10 @@
use ethers::signers::{coins_bip39::English, MnemonicBuilder};
use ethers::{
core::rand,
signers::{coins_bip39::English, MnemonicBuilder},
};
use eyre::Result;
fn main() -> eyre::Result<()> {
fn main() -> Result<()> {
let phrase = "work man father plunge mystery proud hollow address reunion sauce theory bonus";
let index = 0u32;
let password = "TREZOR123";

View File

@ -1,7 +1,9 @@
use ethers::{
contract::{Eip712, EthAbiType},
core::types::transaction::eip712::Eip712,
types::{Address, U256},
core::{
types::{transaction::eip712::Eip712, Address, U256},
utils::hex,
},
};
// Generate the EIP712 permit hash to sign for a Uniswap V2 pair.

View File

@ -1,9 +1,9 @@
// use the eyre crate for easy idiomatic error handling
use eyre::Result;
// use the ethers_core rand for rng
use ethers_core::rand::thread_rng;
use ethers::core::rand::thread_rng;
// use the ethers_signers crate to manage LocalWallet and Signer
use ethers_signers::{LocalWallet, Signer};
use ethers::signers::{LocalWallet, Signer};
// Use the `tokio::main` macro for using async on the main function
#[tokio::main]

View File

@ -1,25 +1,42 @@
set -e
#!/bin/bash
set -e; # Fail fast
# shellcheck shell=bash
# examples that we can't run because they require some additional infra, docker or ledger for example
ignored=(
"moonbeam_with_abi"
"ipc"
"ledger"
"paginated_logs"
"subscribe_logs"
"trezor"
"yubi"
"remove_liquidity"
"examples-contracts:deploy_moonbeam"
"examples-providers:ipc"
"examples-wallets:ledger"
"examples-wallets:trezor"
"examples-wallets:yubi"
"examples-transactions:remove_liquidity"
)
# run all examples
for file in examples/*.rs; do
name="$(echo "$file" | cut -f 1 -d '.')"
if [[ "${ignored[*]}" =~ $(basename "$name") ]]; then
echo "skipping: $file"
continue
fi
echo "running: $file"
cargo r -p ethers --example "$(basename "$name")" --features "ethers-solc rustls ws"
example_crates=$(cargo metadata --format-version 1 |
jq -c '.workspace_members' |
jq -r 'map(select(startswith("examples")) |
sub("\\s.*$";"")) | .[]')
for crate in $example_crates; do
# Remove "examples-" prefix from crate name (e.g. examples-contracts => contracts)
cratedir="${crate#examples-}"
srcdir="examples/$cratedir/examples"
# Retrieve all example files in crate:
# Transform the absolute path into the filename (e.g. some-path/deploy_anvil.rs => deploy_anvil)
example_files=$(find $srcdir -type f -name '*.rs' -exec basename {} \; | sed 's/\.[^.]*$//')
for file in $example_files; do
# Build the example
echo "Building example: $crate:$file"
cargo build -p $crate --example $file
# Run the example
if [[ "${ignored[*]}" =~ $(basename "$crate:$file") ]]; then
echo "skipping: $crate:$file"
continue
fi
echo "running $crate:$file"
cargo run -p $crate --example $file
done
done