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:
parent
e26ede21f1
commit
10310ce3ad
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
62
Cargo.toml
62
Cargo.toml
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
@ -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"] }
|
|
@ -0,0 +1,2 @@
|
|||
#[tokio::main]
|
||||
async fn main() {}
|
|
@ -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"] }
|
||||
|
|
@ -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"),
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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:?}"),
|
||||
}
|
||||
}*/
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#[tokio::main]
|
||||
async fn main() {}
|
|
@ -0,0 +1,2 @@
|
|||
#[tokio::main]
|
||||
async fn main() {}
|
|
@ -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"] }
|
|
@ -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
|
|
@ -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())?;
|
|
@ -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"] }
|
|
@ -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?;
|
|
@ -1,4 +1,8 @@
|
|||
use ethers::prelude::*;
|
||||
use ethers::{
|
||||
contract::abigen,
|
||||
core::types::Address,
|
||||
providers::{Http, Provider},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -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(())
|
||||
}
|
|
@ -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"] }
|
|
@ -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;
|
|
@ -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 =
|
|
@ -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!(
|
|
@ -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"] }
|
|
@ -1,14 +1,21 @@
|
|||
use ethers::{
|
||||
prelude::*,
|
||||
providers::call_raw::RawCall,
|
||||
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);
|
|
@ -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
|
|
@ -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<()> {
|
|
@ -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#"[
|
|
@ -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,
|
|
@ -1,4 +1,7 @@
|
|||
use ethers::prelude::*;
|
||||
use ethers::{
|
||||
core::types::{GethDebugTracingOptions, H256},
|
||||
providers::{Http, Middleware, Provider},
|
||||
};
|
||||
use eyre::Result;
|
||||
use std::str::FromStr;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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"] }
|
||||
|
|
@ -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;
|
||||
|
|
@ -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";
|
|
@ -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.
|
|
@ -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]
|
|
@ -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"
|
||||
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: $file"
|
||||
cargo r -p ethers --example "$(basename "$name")" --features "ethers-solc rustls ws"
|
||||
echo "running $crate:$file"
|
||||
cargo run -p $crate --example $file
|
||||
done
|
||||
done
|
||||
|
|
Loading…
Reference in New Issue