From 10310ce3ad8562476196e9ec06f78c2a27417739 Mon Sep 17 00:00:00 2001 From: Andrea Simeoni Date: Thu, 29 Dec 2022 13:53:11 +0100 Subject: [PATCH] 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> --- .github/workflows/ci.yml | 27 +-- Cargo.lock | 105 ++++++++++++ Cargo.toml | 62 +------ README.md | 7 +- examples/README.md | 81 +++++++++ examples/anvil/Cargo.toml | 10 ++ examples/anvil/README.md | 0 examples/{ => anvil/examples}/anvil_fork.rs | 3 +- examples/big-numbers/Cargo.toml | 10 ++ examples/big-numbers/README.md | 7 + .../examples/comparison_equivalence.rs | 32 ++++ examples/big-numbers/examples/conversion.rs | 23 +++ .../big-numbers/examples/create_instances.rs | 47 ++++++ .../big-numbers/examples/math_operations.rs | 49 ++++++ examples/big-numbers/examples/utilities.rs | 92 +++++++++++ examples/contracts/Cargo.toml | 19 +++ examples/contracts/README.md | 39 +++++ examples/contracts/examples/abi/IERC20.json | 153 +++++++++++++++++ .../examples/abi}/contract_abi.json | 0 examples/contracts/examples/abigen.rs | 77 +++++++++ .../examples/compile.rs} | 9 +- .../examples/contracts}/contract.sol | 1 + .../examples/deploy_anvil.rs} | 11 +- .../examples/deploy_from_abi_and_bytecode.rs} | 8 +- .../examples/deploy_from_solidity.rs} | 15 +- .../examples/deploy_moonbeam.rs} | 6 +- examples/contracts/examples/events.rs | 68 ++++++++ .../examples/events_with_meta.rs} | 13 +- examples/contracts/examples/instances.rs | 2 + examples/contracts/examples/methods.rs | 2 + .../examples/methods_abi_and_structs.rs} | 14 +- examples/ethers-wasm/abi/contract_abi.json | 1 + examples/ethers-wasm/src/lib.rs | 2 +- .../ethers-wasm/tests/contract_with_abi.rs | 2 +- examples/events/Cargo.toml | 13 ++ examples/events/README.md | 0 examples/events/examples/placeholder.rs | 2 + examples/middleware/Cargo.toml | 15 ++ examples/middleware/README.md | 0 examples/middleware/examples/builder.rs | 58 +++++++ .../examples/create_custom_middleware.rs | 156 ++++++++++++++++++ examples/middleware/examples/gas_escalator.rs | 59 +++++++ examples/middleware/examples/gas_oracle.rs | 88 ++++++++++ examples/middleware/examples/nonce_manager.rs | 46 ++++++ examples/middleware/examples/policy.rs | 39 +++++ examples/middleware/examples/signer.rs | 40 +++++ examples/middleware/examples/timelag.rs | 2 + examples/middleware/examples/transformer.rs | 2 + examples/providers/Cargo.toml | 17 ++ examples/providers/README.md | 0 examples/{ => providers/examples}/ipc.rs | 0 examples/{ => providers/examples}/quorum.rs | 8 +- examples/{ => providers/examples}/rw.rs | 8 +- examples/queries/Cargo.toml | 13 ++ examples/queries/README.md | 0 .../{ => queries/examples}/paginated_logs.rs | 10 +- .../examples/uniswapv2_pair.rs} | 6 +- examples/subscribe_contract_events.rs | 38 ----- examples/subscriptions/Cargo.toml | 13 ++ examples/subscriptions/README.md | 0 .../examples}/subscribe_events_by_type.rs | 11 +- .../examples}/subscribe_logs.rs | 10 +- .../examples}/watch_blocks.rs | 11 +- examples/transactions/Cargo.toml | 13 ++ examples/transactions/README.md | 0 .../examples}/call_override.rs | 15 +- .../examples/decode_input.rs} | 5 +- examples/{ => transactions/examples}/ens.rs | 6 +- .../examples}/gas_price_usd.rs | 10 +- .../examples}/remove_liquidity.rs | 11 +- .../examples/trace.rs} | 5 +- .../examples}/transfer_eth.rs | 5 +- examples/wallets/Cargo.toml | 20 +++ examples/wallets/README.md | 0 examples/{ => wallets/examples}/ledger.rs | 0 .../{ => wallets/examples}/local_signer.rs | 7 +- examples/{ => wallets/examples}/mnemonic.rs | 8 +- .../{ => wallets/examples}/permit_hash.rs | 6 +- .../examples/sign_message.rs} | 4 +- examples/{ => wallets/examples}/trezor.rs | 0 examples/{ => wallets/examples}/yubi.rs | 0 scripts/examples.sh | 53 ++++-- 82 files changed, 1623 insertions(+), 207 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/anvil/Cargo.toml create mode 100644 examples/anvil/README.md rename examples/{ => anvil/examples}/anvil_fork.rs (88%) create mode 100644 examples/big-numbers/Cargo.toml create mode 100644 examples/big-numbers/README.md create mode 100644 examples/big-numbers/examples/comparison_equivalence.rs create mode 100644 examples/big-numbers/examples/conversion.rs create mode 100644 examples/big-numbers/examples/create_instances.rs create mode 100644 examples/big-numbers/examples/math_operations.rs create mode 100644 examples/big-numbers/examples/utilities.rs create mode 100644 examples/contracts/Cargo.toml create mode 100644 examples/contracts/README.md create mode 100644 examples/contracts/examples/abi/IERC20.json rename examples/{ => contracts/examples/abi}/contract_abi.json (100%) create mode 100644 examples/contracts/examples/abigen.rs rename examples/{abigen.rs => contracts/examples/compile.rs} (77%) rename examples/{ => contracts/examples/contracts}/contract.sol (93%) rename examples/{contract_human_readable.rs => contracts/examples/deploy_anvil.rs} (90%) rename examples/{contract_with_abi_and_bytecode.rs => contracts/examples/deploy_from_abi_and_bytecode.rs} (89%) rename examples/{contract_with_abi.rs => contracts/examples/deploy_from_solidity.rs} (85%) rename examples/{moonbeam_with_abi.rs => contracts/examples/deploy_moonbeam.rs} (95%) create mode 100644 examples/contracts/examples/events.rs rename examples/{subscribe_contract_events_with_meta.rs => contracts/examples/events_with_meta.rs} (80%) create mode 100644 examples/contracts/examples/instances.rs create mode 100644 examples/contracts/examples/methods.rs rename examples/{contract_with_abi_and_structs.rs => contracts/examples/methods_abi_and_structs.rs} (82%) create mode 100644 examples/ethers-wasm/abi/contract_abi.json create mode 100644 examples/events/Cargo.toml create mode 100644 examples/events/README.md create mode 100644 examples/events/examples/placeholder.rs create mode 100644 examples/middleware/Cargo.toml create mode 100644 examples/middleware/README.md create mode 100644 examples/middleware/examples/builder.rs create mode 100644 examples/middleware/examples/create_custom_middleware.rs create mode 100644 examples/middleware/examples/gas_escalator.rs create mode 100644 examples/middleware/examples/gas_oracle.rs create mode 100644 examples/middleware/examples/nonce_manager.rs create mode 100644 examples/middleware/examples/policy.rs create mode 100644 examples/middleware/examples/signer.rs create mode 100644 examples/middleware/examples/timelag.rs create mode 100644 examples/middleware/examples/transformer.rs create mode 100644 examples/providers/Cargo.toml create mode 100644 examples/providers/README.md rename examples/{ => providers/examples}/ipc.rs (100%) rename examples/{ => providers/examples}/quorum.rs (85%) rename examples/{ => providers/examples}/rw.rs (76%) create mode 100644 examples/queries/Cargo.toml create mode 100644 examples/queries/README.md rename examples/{ => queries/examples}/paginated_logs.rs (84%) rename examples/{uniswapv2.rs => queries/examples/uniswapv2_pair.rs} (91%) delete mode 100644 examples/subscribe_contract_events.rs create mode 100644 examples/subscriptions/Cargo.toml create mode 100644 examples/subscriptions/README.md rename examples/{ => subscriptions/examples}/subscribe_events_by_type.rs (82%) rename examples/{ => subscriptions/examples}/subscribe_logs.rs (82%) rename examples/{ => subscriptions/examples}/watch_blocks.rs (58%) create mode 100644 examples/transactions/Cargo.toml create mode 100644 examples/transactions/README.md rename examples/{ => transactions/examples}/call_override.rs (90%) rename examples/{decode_tx_input.rs => transactions/examples/decode_input.rs} (94%) rename examples/{ => transactions/examples}/ens.rs (87%) rename examples/{ => transactions/examples}/gas_price_usd.rs (92%) rename examples/{ => transactions/examples}/remove_liquidity.rs (92%) rename examples/{geth_trace_transaction.rs => transactions/examples/trace.rs} (85%) rename examples/{ => transactions/examples}/transfer_eth.rs (90%) create mode 100644 examples/wallets/Cargo.toml create mode 100644 examples/wallets/README.md rename examples/{ => wallets/examples}/ledger.rs (100%) rename examples/{ => wallets/examples}/local_signer.rs (84%) rename examples/{ => wallets/examples}/mnemonic.rs (87%) rename examples/{ => wallets/examples}/permit_hash.rs (89%) rename examples/{sign.rs => wallets/examples/sign_message.rs} (91%) rename examples/{ => wallets/examples}/trezor.rs (100%) rename examples/{ => wallets/examples}/yubi.rs (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcfaa4ba..16d62793 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 0121253b..bbb66218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index f4c2adf4..45c2d1d1 100644 --- a/Cargo.toml +++ b/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"] diff --git a/README.md b/README.md index 9d98bb16..c85e9f9e 100644 --- a/README.md +++ b/README.md @@ -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 ` +Examples are organized into individual crates under the `/examples` folder. +You can run any of the examples by executing: +```bash +# cargo run -p --example +cargo run -p examples-big-numbers --example math_operations +``` ## Add ethers-rs to your repository diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..87fbfc19 --- /dev/null +++ b/examples/README.md @@ -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 diff --git a/examples/anvil/Cargo.toml b/examples/anvil/Cargo.toml new file mode 100644 index 00000000..7e1fb044 --- /dev/null +++ b/examples/anvil/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "examples-anvil" +version = "1.0.2" +authors = ["Andrea Simeoni "] +edition = "2021" + +[dev-dependencies] +ethers = { path = "../..", version = "1.0.0" } +eyre = "0.6" +tokio = { version = "1.18", features = ["full"] } diff --git a/examples/anvil/README.md b/examples/anvil/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/anvil_fork.rs b/examples/anvil/examples/anvil_fork.rs similarity index 88% rename from examples/anvil_fork.rs rename to examples/anvil/examples/anvil_fork.rs index 31da8670..4fecb8e8 100644 --- a/examples/anvil_fork.rs +++ b/examples/anvil/examples/anvil_fork.rs @@ -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(); diff --git a/examples/big-numbers/Cargo.toml b/examples/big-numbers/Cargo.toml new file mode 100644 index 00000000..04505c19 --- /dev/null +++ b/examples/big-numbers/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "examples-big-numbers" +version = "1.0.2" +authors = ["Andrea Simeoni "] +edition = "2021" + +[dev-dependencies] +#ethers-core = { version = "^1.0.0", path = "../../ethers-core" } +ethers = { path = "../..", version = "1.0.0" } + diff --git a/examples/big-numbers/README.md b/examples/big-numbers/README.md new file mode 100644 index 00000000..7ac9428e --- /dev/null +++ b/examples/big-numbers/README.md @@ -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. diff --git a/examples/big-numbers/examples/comparison_equivalence.rs b/examples/big-numbers/examples/comparison_equivalence.rs new file mode 100644 index 00000000..214a81e7 --- /dev/null +++ b/examples/big-numbers/examples/comparison_equivalence.rs @@ -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()); +} diff --git a/examples/big-numbers/examples/conversion.rs b/examples/big-numbers/examples/conversion.rs new file mode 100644 index 00000000..99eb6ea0 --- /dev/null +++ b/examples/big-numbers/examples/conversion.rs @@ -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"); +} diff --git a/examples/big-numbers/examples/create_instances.rs b/examples/big-numbers/examples/create_instances.rs new file mode 100644 index 00000000..35fea7f1 --- /dev/null +++ b/examples/big-numbers/examples/create_instances.rs @@ -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"); +} diff --git a/examples/big-numbers/examples/math_operations.rs b/examples/big-numbers/examples/math_operations.rs new file mode 100644 index 00000000..45938331 --- /dev/null +++ b/examples/big-numbers/examples/math_operations.rs @@ -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 +} diff --git a/examples/big-numbers/examples/utilities.rs b/examples/big-numbers/examples/utilities.rs new file mode 100644 index 00000000..58493584 --- /dev/null +++ b/examples/big-numbers/examples/utilities.rs @@ -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"); +} diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml new file mode 100644 index 00000000..4f0e12bd --- /dev/null +++ b/examples/contracts/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "examples-contracts" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } + + diff --git a/examples/contracts/README.md b/examples/contracts/README.md new file mode 100644 index 00000000..472592f7 --- /dev/null +++ b/examples/contracts/README.md @@ -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 + diff --git a/examples/contracts/examples/abi/IERC20.json b/examples/contracts/examples/abi/IERC20.json new file mode 100644 index 00000000..4827e872 --- /dev/null +++ b/examples/contracts/examples/abi/IERC20.json @@ -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" + } +] diff --git a/examples/contract_abi.json b/examples/contracts/examples/abi/contract_abi.json similarity index 100% rename from examples/contract_abi.json rename to examples/contracts/examples/abi/contract_abi.json diff --git a/examples/contracts/examples/abigen.rs b/examples/contracts/examples/abigen.rs new file mode 100644 index 00000000..b29fa2b2 --- /dev/null +++ b/examples/contracts/examples/abigen.rs @@ -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::::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(()) +} diff --git a/examples/abigen.rs b/examples/contracts/examples/compile.rs similarity index 77% rename from examples/abigen.rs rename to examples/contracts/examples/compile.rs index e0886687..21b7586a 100644 --- a/examples/abigen.rs +++ b/examples/contracts/examples/compile.rs @@ -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"); diff --git a/examples/contract.sol b/examples/contracts/examples/contracts/contract.sol similarity index 93% rename from examples/contract.sol rename to examples/contracts/examples/contracts/contract.sol index 9d04f2f4..e9910f32 100644 --- a/examples/contract.sol +++ b/examples/contracts/examples/contracts/contract.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Unlicense pragma solidity >=0.4.24; contract SimpleStorage { diff --git a/examples/contract_human_readable.rs b/examples/contracts/examples/deploy_anvil.rs similarity index 90% rename from examples/contract_human_readable.rs rename to examples/contracts/examples/deploy_anvil.rs index d0ca18c7..7ef77176 100644 --- a/examples/contract_human_readable.rs +++ b/examples/contracts/examples/deploy_anvil.rs @@ -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 diff --git a/examples/contract_with_abi_and_bytecode.rs b/examples/contracts/examples/deploy_from_abi_and_bytecode.rs similarity index 89% rename from examples/contract_with_abi_and_bytecode.rs rename to examples/contracts/examples/deploy_from_abi_and_bytecode.rs index 2748e252..1dd91f37 100644 --- a/examples/contract_with_abi_and_bytecode.rs +++ b/examples/contracts/examples/deploy_from_abi_and_bytecode.rs @@ -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}; diff --git a/examples/contract_with_abi.rs b/examples/contracts/examples/deploy_from_solidity.rs similarity index 85% rename from examples/contract_with_abi.rs rename to examples/contracts/examples/deploy_from_solidity.rs index c77d094f..fa6fb365 100644 --- a/examples/contract_with_abi.rs +++ b/examples/contracts/examples/deploy_from_solidity.rs @@ -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(); diff --git a/examples/moonbeam_with_abi.rs b/examples/contracts/examples/deploy_moonbeam.rs similarity index 95% rename from examples/moonbeam_with_abi.rs rename to examples/contracts/examples/deploy_moonbeam.rs index e17500bc..a26464c8 100644 --- a/examples/moonbeam_with_abi.rs +++ b/examples/contracts/examples/deploy_moonbeam.rs @@ -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 diff --git a/examples/contracts/examples/events.rs b/examples/contracts/examples/events.rs new file mode 100644 index 00000000..4039d883 --- /dev/null +++ b/examples/contracts/examples/events.rs @@ -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::::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>) -> 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>) -> Result<()> { + let events = contract.event::().from_block(16232696); + let mut stream = events.stream().await?.take(1); + + while let Some(Ok(f)) = stream.next().await { + println!("ApprovalFilter event: {f:?}"); + } + + Ok(()) +} diff --git a/examples/subscribe_contract_events_with_meta.rs b/examples/contracts/examples/events_with_meta.rs similarity index 80% rename from examples/subscribe_contract_events_with_meta.rs rename to examples/contracts/examples/events_with_meta.rs index c81b4da8..ceb0c68e 100644 --- a/examples/subscribe_contract_events_with_meta.rs +++ b/examples/contracts/examples/events_with_meta.rs @@ -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); diff --git a/examples/contracts/examples/instances.rs b/examples/contracts/examples/instances.rs new file mode 100644 index 00000000..7f755fb7 --- /dev/null +++ b/examples/contracts/examples/instances.rs @@ -0,0 +1,2 @@ +#[tokio::main] +async fn main() {} diff --git a/examples/contracts/examples/methods.rs b/examples/contracts/examples/methods.rs new file mode 100644 index 00000000..7f755fb7 --- /dev/null +++ b/examples/contracts/examples/methods.rs @@ -0,0 +1,2 @@ +#[tokio::main] +async fn main() {} diff --git a/examples/contract_with_abi_and_structs.rs b/examples/contracts/examples/methods_abi_and_structs.rs similarity index 82% rename from examples/contract_with_abi_and_structs.rs rename to examples/contracts/examples/methods_abi_and_structs.rs index b74c9ea9..87b08c52 100644 --- a/examples/contract_with_abi_and_structs.rs +++ b/examples/contracts/examples/methods_abi_and_structs.rs @@ -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"); diff --git a/examples/ethers-wasm/abi/contract_abi.json b/examples/ethers-wasm/abi/contract_abi.json new file mode 100644 index 00000000..12de10c8 --- /dev/null +++ b/examples/ethers-wasm/abi/contract_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"}] diff --git a/examples/ethers-wasm/src/lib.rs b/examples/ethers-wasm/src/lib.rs index 2e18ad03..273836ce 100644 --- a/examples/ethers-wasm/src/lib.rs +++ b/examples/ethers-wasm/src/lib.rs @@ -24,7 +24,7 @@ macro_rules! log { abigen!( SimpleContract, - "./../contract_abi.json", + "./abi/contract_abi.json", event_derives(serde::Deserialize, serde::Serialize) ); diff --git a/examples/ethers-wasm/tests/contract_with_abi.rs b/examples/ethers-wasm/tests/contract_with_abi.rs index 5530df67..df080b38 100644 --- a/examples/ethers-wasm/tests/contract_with_abi.rs +++ b/examples/ethers-wasm/tests/contract_with_abi.rs @@ -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) ); diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml new file mode 100644 index 00000000..4330bdf7 --- /dev/null +++ b/examples/events/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "examples-events" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } \ No newline at end of file diff --git a/examples/events/README.md b/examples/events/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/events/examples/placeholder.rs b/examples/events/examples/placeholder.rs new file mode 100644 index 00000000..7f755fb7 --- /dev/null +++ b/examples/events/examples/placeholder.rs @@ -0,0 +1,2 @@ +#[tokio::main] +async fn main() {} diff --git a/examples/middleware/Cargo.toml b/examples/middleware/Cargo.toml new file mode 100644 index 00000000..0c6eb85d --- /dev/null +++ b/examples/middleware/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "examples-middleware" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } + diff --git a/examples/middleware/README.md b/examples/middleware/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/middleware/examples/builder.rs b/examples/middleware/examples/builder.rs new file mode 100644 index 00000000..f8c54ad4 --- /dev/null +++ b/examples/middleware/examples/builder.rs @@ -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::().unwrap(); + let address = signer.address(); + let escalator = GeometricGasPrice::new(1.125, 60_u64, None::); + let gas_oracle = EthGasStation::new(None); + + let provider = Provider::::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::().unwrap(); + let address = signer.address(); + let escalator = GeometricGasPrice::new(1.125, 60_u64, None::); + + let provider = Provider::::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"), + } +} diff --git a/examples/middleware/examples/create_custom_middleware.rs b/examples/middleware/examples/create_custom_middleware.rs new file mode 100644 index 00000000..7e19ee3a --- /dev/null +++ b/examples/middleware/examples/create_custom_middleware.rs @@ -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 { + 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 GasMiddleware +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> { + 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 Middleware for GasMiddleware +where + M: Middleware, +{ + type Error = GasMiddlewareError; + 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 + Send + Sync>( + &self, + tx: T, + block: Option, + ) -> Result, 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 { + /// 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 FromErr for GasMiddlewareError { + 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::::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(()) +} diff --git a/examples/middleware/examples/gas_escalator.rs b/examples/middleware/examples/gas_escalator.rs new file mode 100644 index 00000000..c60d304c --- /dev/null +++ b/examples/middleware/examples/gas_escalator.rs @@ -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 = 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(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::::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(()) +} diff --git a/examples/middleware/examples/gas_oracle.rs b/examples/middleware/examples/gas_oracle.rs new file mode 100644 index 00000000..95105aeb --- /dev/null +++ b/examples/middleware/examples/gas_oracle.rs @@ -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 = 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::::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:?}"), + } +}*/ diff --git a/examples/middleware/examples/nonce_manager.rs b/examples/middleware/examples/nonce_manager.rs new file mode 100644 index 00000000..e76bcc1c --- /dev/null +++ b/examples/middleware/examples/nonce_manager.rs @@ -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::::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(()) +} diff --git a/examples/middleware/examples/policy.rs b/examples/middleware/examples/policy.rs new file mode 100644 index 00000000..3e2e0a4b --- /dev/null +++ b/examples/middleware/examples/policy.rs @@ -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::::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(()) +} diff --git a/examples/middleware/examples/signer.rs b/examples/middleware/examples/signer.rs new file mode 100644 index 00000000..38ad057c --- /dev/null +++ b/examples/middleware/examples/signer.rs @@ -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::::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(()) +} diff --git a/examples/middleware/examples/timelag.rs b/examples/middleware/examples/timelag.rs new file mode 100644 index 00000000..7f755fb7 --- /dev/null +++ b/examples/middleware/examples/timelag.rs @@ -0,0 +1,2 @@ +#[tokio::main] +async fn main() {} diff --git a/examples/middleware/examples/transformer.rs b/examples/middleware/examples/transformer.rs new file mode 100644 index 00000000..7f755fb7 --- /dev/null +++ b/examples/middleware/examples/transformer.rs @@ -0,0 +1,2 @@ +#[tokio::main] +async fn main() {} diff --git a/examples/providers/Cargo.toml b/examples/providers/Cargo.toml new file mode 100644 index 00000000..71a92210 --- /dev/null +++ b/examples/providers/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "examples-providers" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } diff --git a/examples/providers/README.md b/examples/providers/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/ipc.rs b/examples/providers/examples/ipc.rs similarity index 100% rename from examples/ipc.rs rename to examples/providers/examples/ipc.rs diff --git a/examples/quorum.rs b/examples/providers/examples/quorum.rs similarity index 85% rename from examples/quorum.rs rename to examples/providers/examples/quorum.rs index 95f62af1..a1d50c8c 100644 --- a/examples/quorum.rs +++ b/examples/providers/examples/quorum.rs @@ -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 diff --git a/examples/rw.rs b/examples/providers/examples/rw.rs similarity index 76% rename from examples/rw.rs rename to examples/providers/examples/rw.rs index 89733a8f..89f1f439 100644 --- a/examples/rw.rs +++ b/examples/providers/examples/rw.rs @@ -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())?; diff --git a/examples/queries/Cargo.toml b/examples/queries/Cargo.toml new file mode 100644 index 00000000..fa92ea52 --- /dev/null +++ b/examples/queries/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "examples-queries" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } \ No newline at end of file diff --git a/examples/queries/README.md b/examples/queries/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/paginated_logs.rs b/examples/queries/examples/paginated_logs.rs similarity index 84% rename from examples/paginated_logs.rs rename to examples/queries/examples/paginated_logs.rs index 9f94f9e0..563b90f1 100644 --- a/examples/paginated_logs.rs +++ b/examples/queries/examples/paginated_logs.rs @@ -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?; diff --git a/examples/uniswapv2.rs b/examples/queries/examples/uniswapv2_pair.rs similarity index 91% rename from examples/uniswapv2.rs rename to examples/queries/examples/uniswapv2_pair.rs index 8b956112..c9d306da 100644 --- a/examples/uniswapv2.rs +++ b/examples/queries/examples/uniswapv2_pair.rs @@ -1,4 +1,8 @@ -use ethers::prelude::*; +use ethers::{ + contract::abigen, + core::types::Address, + providers::{Http, Provider}, +}; use eyre::Result; use std::sync::Arc; diff --git a/examples/subscribe_contract_events.rs b/examples/subscribe_contract_events.rs deleted file mode 100644 index a6f769f9..00000000 --- a/examples/subscribe_contract_events.rs +++ /dev/null @@ -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::::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27") - .await?; - - let client = Arc::new(client); - - // WETH Token - let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse::
()?; - 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(()) -} diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml new file mode 100644 index 00000000..9dd76878 --- /dev/null +++ b/examples/subscriptions/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "examples-subscriptions" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } \ No newline at end of file diff --git a/examples/subscriptions/README.md b/examples/subscriptions/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/subscribe_events_by_type.rs b/examples/subscriptions/examples/subscribe_events_by_type.rs similarity index 82% rename from examples/subscribe_events_by_type.rs rename to examples/subscriptions/examples/subscribe_events_by_type.rs index 76e394ac..355404a3 100644 --- a/examples/subscribe_events_by_type.rs +++ b/examples/subscriptions/examples/subscribe_events_by_type.rs @@ -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> { let client = get_client().await; diff --git a/examples/subscribe_logs.rs b/examples/subscriptions/examples/subscribe_logs.rs similarity index 82% rename from examples/subscribe_logs.rs rename to examples/subscriptions/examples/subscribe_logs.rs index b8cb1891..3ee71259 100644 --- a/examples/subscribe_logs.rs +++ b/examples/subscriptions/examples/subscribe_logs.rs @@ -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 = diff --git a/examples/watch_blocks.rs b/examples/subscriptions/examples/watch_blocks.rs similarity index 58% rename from examples/watch_blocks.rs rename to examples/subscriptions/examples/watch_blocks.rs index af1385da..38fb90e5 100644 --- a/examples/watch_blocks.rs +++ b/examples/subscriptions/examples/watch_blocks.rs @@ -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!( diff --git a/examples/transactions/Cargo.toml b/examples/transactions/Cargo.toml new file mode 100644 index 00000000..6bdf9412 --- /dev/null +++ b/examples/transactions/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "examples-transactions" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } \ No newline at end of file diff --git a/examples/transactions/README.md b/examples/transactions/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/call_override.rs b/examples/transactions/examples/call_override.rs similarity index 90% rename from examples/call_override.rs rename to examples/transactions/examples/call_override.rs index 2237ee1e..b80191e8 100644 --- a/examples/call_override.rs +++ b/examples/transactions/examples/call_override.rs @@ -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::::try_from(geth.endpoint()).unwrap(); let client = Arc::new(provider); diff --git a/examples/decode_tx_input.rs b/examples/transactions/examples/decode_input.rs similarity index 94% rename from examples/decode_tx_input.rs rename to examples/transactions/examples/decode_input.rs index f11b6f1b..0eca8c37 100644 --- a/examples/decode_tx_input.rs +++ b/examples/transactions/examples/decode_input.rs @@ -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 diff --git a/examples/ens.rs b/examples/transactions/examples/ens.rs similarity index 87% rename from examples/ens.rs rename to examples/transactions/examples/ens.rs index 5874d5c9..0606288e 100644 --- a/examples/ens.rs +++ b/examples/transactions/examples/ens.rs @@ -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<()> { diff --git a/examples/gas_price_usd.rs b/examples/transactions/examples/gas_price_usd.rs similarity index 92% rename from examples/gas_price_usd.rs rename to examples/transactions/examples/gas_price_usd.rs index 307baf82..eef46a72 100644 --- a/examples/gas_price_usd.rs +++ b/examples/transactions/examples/gas_price_usd.rs @@ -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#"[ diff --git a/examples/remove_liquidity.rs b/examples/transactions/examples/remove_liquidity.rs similarity index 92% rename from examples/remove_liquidity.rs rename to examples/transactions/examples/remove_liquidity.rs index 0d3e889d..587f6436 100644 --- a/examples/remove_liquidity.rs +++ b/examples/transactions/examples/remove_liquidity.rs @@ -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, diff --git a/examples/geth_trace_transaction.rs b/examples/transactions/examples/trace.rs similarity index 85% rename from examples/geth_trace_transaction.rs rename to examples/transactions/examples/trace.rs index ef20f738..35ea370d 100644 --- a/examples/geth_trace_transaction.rs +++ b/examples/transactions/examples/trace.rs @@ -1,4 +1,7 @@ -use ethers::prelude::*; +use ethers::{ + core::types::{GethDebugTracingOptions, H256}, + providers::{Http, Middleware, Provider}, +}; use eyre::Result; use std::str::FromStr; diff --git a/examples/transfer_eth.rs b/examples/transactions/examples/transfer_eth.rs similarity index 90% rename from examples/transfer_eth.rs rename to examples/transactions/examples/transfer_eth.rs index 3be81cc9..00a4e6fc 100644 --- a/examples/transfer_eth.rs +++ b/examples/transactions/examples/transfer_eth.rs @@ -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; diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml new file mode 100644 index 00000000..c3112132 --- /dev/null +++ b/examples/wallets/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "examples-wallets" +version = "1.0.2" +authors = ["Andrea Simeoni "] +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"] } + diff --git a/examples/wallets/README.md b/examples/wallets/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/ledger.rs b/examples/wallets/examples/ledger.rs similarity index 100% rename from examples/ledger.rs rename to examples/wallets/examples/ledger.rs diff --git a/examples/local_signer.rs b/examples/wallets/examples/local_signer.rs similarity index 84% rename from examples/local_signer.rs rename to examples/wallets/examples/local_signer.rs index 764de4f5..b7193076 100644 --- a/examples/local_signer.rs +++ b/examples/wallets/examples/local_signer.rs @@ -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; diff --git a/examples/mnemonic.rs b/examples/wallets/examples/mnemonic.rs similarity index 87% rename from examples/mnemonic.rs rename to examples/wallets/examples/mnemonic.rs index 2c14b69d..b04aaea9 100644 --- a/examples/mnemonic.rs +++ b/examples/wallets/examples/mnemonic.rs @@ -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"; diff --git a/examples/permit_hash.rs b/examples/wallets/examples/permit_hash.rs similarity index 89% rename from examples/permit_hash.rs rename to examples/wallets/examples/permit_hash.rs index b313b3a5..ac28588c 100644 --- a/examples/permit_hash.rs +++ b/examples/wallets/examples/permit_hash.rs @@ -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. diff --git a/examples/sign.rs b/examples/wallets/examples/sign_message.rs similarity index 91% rename from examples/sign.rs rename to examples/wallets/examples/sign_message.rs index 6f6b592e..07d50d87 100644 --- a/examples/sign.rs +++ b/examples/wallets/examples/sign_message.rs @@ -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] diff --git a/examples/trezor.rs b/examples/wallets/examples/trezor.rs similarity index 100% rename from examples/trezor.rs rename to examples/wallets/examples/trezor.rs diff --git a/examples/yubi.rs b/examples/wallets/examples/yubi.rs similarity index 100% rename from examples/yubi.rs rename to examples/wallets/examples/yubi.rs diff --git a/scripts/examples.sh b/scripts/examples.sh index ee50628e..90537870 100755 --- a/scripts/examples.sh +++ b/scripts/examples.sh @@ -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