├── .cargo └── config ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── deny.yml │ ├── lint.yml │ ├── rust.yml │ └── typo.yml ├── .gitignore ├── .typos.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── config.toml ├── deny.toml ├── examples ├── bench.rs ├── contract.rs ├── contract_log_filter.rs ├── contract_log_pubsub.rs ├── contract_storage.rs ├── contracts │ ├── SimpleEvent.sol │ └── SimpleStorage.sol ├── pubsub.rs ├── readme.md ├── res │ ├── SimpleEvent.abi │ ├── SimpleEvent.bin │ ├── SimpleStorage.abi │ ├── SimpleStorage.bin │ └── contract_token.code ├── transaction_private.rs ├── transaction_public.rs ├── transport_batch.rs ├── transport_either.rs ├── transport_http.rs └── transport_ws.rs ├── rustfmt.toml └── src ├── api ├── accounts.rs ├── eth.rs ├── eth_filter.rs ├── eth_subscribe.rs ├── mod.rs ├── net.rs ├── parity.rs ├── parity_accounts.rs ├── parity_set.rs ├── personal.rs ├── traces.rs ├── txpool.rs └── web3.rs ├── confirm.rs ├── contract ├── deploy.rs ├── ens │ ├── DefaultReverseResolver.json │ ├── ENSRegistry.json │ ├── PublicResolver.json │ ├── eth_ens.rs │ ├── mod.rs │ ├── public_resolver.rs │ ├── registry.rs │ └── reverse_resolver.rs ├── error.rs ├── mod.rs ├── res │ ├── Main.json │ ├── MyLibrary.json │ └── token.json └── tokens.rs ├── error.rs ├── helpers.rs ├── lib.rs ├── signing.rs ├── transports ├── batch.rs ├── eip_1193.rs ├── either.rs ├── http.rs ├── ipc.rs ├── mod.rs ├── test.rs └── ws.rs └── types ├── block.rs ├── bytes.rs ├── bytes_array.rs ├── example-trace-str.rs ├── example-traces-str.rs ├── fee_history.rs ├── log.rs ├── mod.rs ├── parity_peers.rs ├── parity_pending_transaction.rs ├── proof.rs ├── recovery.rs ├── signed.rs ├── sync_state.rs ├── trace_filtering.rs ├── traces.rs ├── transaction.rs ├── transaction_id.rs ├── transaction_request.rs ├── txpool.rs ├── uint.rs └── work.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | ../config.toml -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=space 4 | indent_size=4 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | 12 | [.travis.yml] 13 | indent_style=space 14 | indent_size=2 15 | tab_width=8 16 | end_of_line=lf 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: arrayvec 10 | versions: 11 | - 0.6.0 12 | - dependency-name: tokio 13 | versions: 14 | - 1.1.0 15 | - 1.2.0 16 | - 1.3.0 17 | - 1.4.0 18 | - dependency-name: hyper-proxy 19 | versions: 20 | - 0.9.0 21 | - dependency-name: ethereum-types 22 | versions: 23 | - 0.11.0 24 | -------------------------------------------------------------------------------- /.github/workflows/deny.yml: -------------------------------------------------------------------------------- 1 | name: Cargo deny 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - v* 10 | paths-ignore: 11 | - 'README.md' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 15 | jobs: 16 | cargo-deny: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout sources & submodules 20 | uses: actions/checkout@master 21 | with: 22 | fetch-depth: 5 23 | submodules: recursive 24 | - name: Cargo deny 25 | uses: EmbarkStudios/cargo-deny-action@v1 26 | with: 27 | command: "check --hide-inclusion-graph" 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Check style 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - v* 10 | paths-ignore: 11 | - 'README.md' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 15 | jobs: 16 | ## Check stage 17 | check-fmt: 18 | name: Check RustFmt 19 | runs-on: ubuntu-latest 20 | env: 21 | RUST_BACKTRACE: full 22 | steps: 23 | - name: Checkout sources & submodules 24 | uses: actions/checkout@master 25 | with: 26 | fetch-depth: 5 27 | submodules: recursive 28 | - name: Add rustfmt 29 | run: rustup component add rustfmt 30 | - name: rust-fmt check 31 | uses: actions-rs/cargo@master 32 | with: 33 | command: fmt 34 | args: --all -- --check --config merge_imports=true 35 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Compilation and Testing Suite 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - v* 10 | paths-ignore: 11 | - 'README.md' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 15 | jobs: 16 | check-test-build: 17 | runs-on: ubuntu-latest 18 | name: Check, test and build 19 | env: 20 | RUST_BACKTRACE: full 21 | steps: 22 | - name: Checkout sources & submodules 23 | uses: actions/checkout@master 24 | with: 25 | fetch-depth: 5 26 | submodules: recursive 27 | ## Check Stage 28 | - name: Checking rust-stable 29 | uses: actions-rs/cargo@master 30 | with: 31 | command: check 32 | toolchain: stable 33 | args: --all --verbose 34 | 35 | ## Test Stage 36 | - name: Testing rust-stable 37 | uses: actions-rs/cargo@master 38 | with: 39 | command: test 40 | toolchain: stable 41 | args: --all --verbose 42 | - name: Testing rust-stable with arbitrary_precision 43 | uses: actions-rs/cargo@master 44 | with: 45 | command: test 46 | toolchain: stable 47 | args: --all --verbose --features arbitrary_precision 48 | 49 | ## Build Stage 50 | - name: Building rust-stable 51 | uses: actions-rs/cargo@master 52 | if: github.ref == 'refs/heads/master' 53 | with: 54 | command: build 55 | toolchain: stable 56 | args: --all --verbose 57 | 58 | check-wasm: 59 | name: Check WASM 60 | runs-on: ubuntu-latest 61 | env: 62 | RUST_BACKTRACE: full 63 | steps: 64 | - name: Checkout sources & submodules 65 | uses: actions/checkout@master 66 | with: 67 | fetch-depth: 5 68 | submodules: recursive 69 | - name: Add WASM Utilities 70 | run: rustup target add wasm32-unknown-unknown --toolchain stable && cargo install wasm-bindgen-cli 71 | ## Check Stage 72 | - name: Checking wasm32 73 | uses: actions-rs/cargo@master 74 | with: 75 | command: check 76 | toolchain: stable 77 | args: --target wasm32-unknown-unknown --no-default-features --features eip-1193 78 | - name: Checking wasm32 with http 79 | uses: actions-rs/cargo@master 80 | with: 81 | command: check 82 | toolchain: stable 83 | args: --target wasm32-unknown-unknown --no-default-features --features http,wasm 84 | - name: Testing wasm32 85 | uses: actions-rs/cargo@master 86 | with: 87 | command: test 88 | toolchain: stable 89 | args: --target wasm32-unknown-unknown --no-default-features --features eip-1193 --tests 90 | 91 | check-transports: 92 | name: Check Transports 93 | runs-on: ubuntu-latest 94 | env: 95 | RUST_BACKTRACE: full 96 | steps: 97 | - name: Checkout sources & submodules 98 | uses: actions/checkout@master 99 | with: 100 | fetch-depth: 5 101 | submodules: recursive 102 | ## Check Stage 103 | - name: Checking without transports 104 | uses: actions-rs/cargo@master 105 | with: 106 | command: check 107 | toolchain: stable 108 | args: --no-default-features 109 | - name: Checking http 110 | uses: actions-rs/cargo@master 111 | with: 112 | command: check 113 | toolchain: stable 114 | args: --no-default-features --features http 115 | - name: Checking http-tls 116 | uses: actions-rs/cargo@master 117 | with: 118 | command: check 119 | toolchain: stable 120 | args: --no-default-features --features http-tls 121 | - name: Checking http-native-tls 122 | uses: actions-rs/cargo@master 123 | with: 124 | command: check 125 | toolchain: stable 126 | args: --no-default-features --features http-native-tls 127 | - name: Checking http-rustls-tls 128 | uses: actions-rs/cargo@master 129 | with: 130 | command: check 131 | toolchain: stable 132 | args: --no-default-features --features http-rustls-tls 133 | - name: Checking ws-tokio 134 | uses: actions-rs/cargo@master 135 | with: 136 | command: check 137 | toolchain: stable 138 | args: --no-default-features --features ws-tokio 139 | - name: Checking ws-tls-tokio 140 | uses: actions-rs/cargo@master 141 | with: 142 | command: check 143 | toolchain: stable 144 | args: --no-default-features --features ws-tls-tokio 145 | - name: Checking ws-rustls-tokio 146 | uses: actions-rs/cargo@master 147 | with: 148 | command: check 149 | toolchain: stable 150 | args: --no-default-features --features ws-rustls-tokio 151 | - name: Checking ws-async-std 152 | uses: actions-rs/cargo@master 153 | with: 154 | command: check 155 | toolchain: stable 156 | args: --no-default-features --features ws-async-std 157 | - name: Checking ws-tls-async-std 158 | uses: actions-rs/cargo@master 159 | with: 160 | command: check 161 | toolchain: stable 162 | args: --no-default-features --features ws-tls-async-std 163 | - name: Checking ipc-tokio 164 | uses: actions-rs/cargo@master 165 | with: 166 | command: check 167 | toolchain: stable 168 | args: --no-default-features --features ipc-tokio 169 | -------------------------------------------------------------------------------- /.github/workflows/typo.yml: -------------------------------------------------------------------------------- 1 | name: Typo Checker 2 | on: [pull_request] 3 | 4 | jobs: 5 | run: 6 | name: Spell Check with Typos 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Actions Repository 10 | uses: actions/checkout@v2 11 | - name: Check spelling of the entire repository 12 | uses: crate-ci/typos@v1.11.1 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.swp 4 | .idea/ 5 | .vscode 6 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | "src/api/parity_accounts.rs", 4 | "src/contract/ens/registry.rs" 5 | ] 6 | 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web3" 3 | version = "0.20.0" 4 | description = "Ethereum JSON-RPC client." 5 | homepage = "https://github.com/tomusdrw/rust-web3" 6 | repository = "https://github.com/tomusdrw/rust-web3" 7 | documentation = "https://docs.rs/web3" 8 | license = "MIT" 9 | keywords = ["jsonrpc", "web3", "ethereum", "rpc", "client"] 10 | authors = ["Tomasz Drwięga "] 11 | readme = "README.md" 12 | edition = "2018" 13 | 14 | [dependencies] 15 | arrayvec = "0.7.1" 16 | derive_more = "0.99.1" 17 | ethabi = "18.0.0" 18 | ethereum-types = "0.14.1" 19 | futures = "0.3.5" 20 | futures-timer = "3.0.2" 21 | hex = "0.4" 22 | idna = "1.0" 23 | jsonrpc-core = "18.0.0" 24 | log = "0.4.6" 25 | parking_lot = "0.12.0" 26 | rlp = "0.5" 27 | serde = { version = "1.0.90", features = ["derive"] } 28 | serde_json = "1.0.39" 29 | tiny-keccak = { version = "2.0.1", features = ["keccak"] } 30 | pin-project = "1.0" 31 | # Optional deps 32 | secp256k1 = { version = "0.29", features = ["recovery"], optional = true } 33 | once_cell = { version = "1.8.0", optional = true } 34 | 35 | ## HTTP 36 | base64 = { version = "0.22", optional = true } 37 | bytes = { version = "1.0", optional = true } 38 | reqwest = { version = "0.12", optional = true, default-features = false, features = ["json"] } 39 | headers = { version = "0.4", optional = true } 40 | ## WS 41 | # async-native-tls = { git = "https://github.com/async-email/async-native-tls.git", rev = "b5b5562d6cea77f913d4cbe448058c031833bf17", optional = true, default-features = false } 42 | # Temporarily use forked version released to crates.io 43 | async-native-tls = { package = "web3-async-native-tls", version = "0.4", optional = true, default-features = false } 44 | tokio-rustls = { version = "0.26", optional = true } 45 | rustls-pki-types = { version = "1", optional = true } 46 | webpki-roots = { version = "0.26", optional = true } 47 | async-std = { version = "1.6", optional = true } 48 | tokio = { version = "1.0", optional = true, features = ["full"] } 49 | tokio-stream = { version = "0.1", optional = true } 50 | tokio-util = { version = "0.7", optional = true, features = ["compat", "io"] } 51 | soketto = { version = "0.8.0", optional = true } 52 | ## Shared (WS, HTTP) 53 | url = { version = "2.1", optional = true } 54 | ## EIP-1193 55 | js-sys = { version = "0.3.77", optional = true } 56 | ### This is a transitive dependency, only here so we can turn on its wasm_bindgen feature 57 | getrandom = { version = "0.2", features = ["js"], optional = true } 58 | rand = { version = "0.8.1", optional = true } 59 | serde-wasm-bindgen = { version = "0.6.0", optional = true } 60 | wasm-bindgen = { version = "0.2.100", optional = true } 61 | wasm-bindgen-futures = { version = "0.4.50", optional = true } 62 | 63 | [dev-dependencies] 64 | # For examples 65 | env_logger = "0.11" 66 | hex-literal = "0.4" 67 | wasm-bindgen-test = "0.3.50" 68 | 69 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 70 | hyper = { version = "1", default-features = false, features = ["server"] } 71 | hyper-util = { version = "0.1", features = ["full"] } 72 | http-body-util = "0.1" 73 | tokio = { version = "1.0", features = ["full"] } 74 | tokio-stream = { version = "0.1", features = ["net"] } 75 | 76 | [features] 77 | default = ["http-tls", "signing", "ws-tls-tokio", "ipc-tokio"] 78 | wasm = ["futures-timer/wasm-bindgen", "getrandom", "js-sys", "rand", "serde-wasm-bindgen", "wasm-bindgen", "wasm-bindgen-futures"] 79 | eip-1193 = ["wasm"] 80 | _http_base = ["reqwest", "bytes", "url", "base64"] 81 | http = ["_http_base"] 82 | http-tls = ["http", "reqwest/default-tls"] 83 | http-native-tls = ["http", "reqwest/native-tls"] 84 | http-rustls-tls = ["http", "reqwest/rustls-tls"] 85 | signing = ["secp256k1", "once_cell"] 86 | ws-tokio = ["soketto", "url", "tokio", "tokio-util", "headers"] 87 | ws-async-std = ["soketto", "url", "async-std", "headers"] 88 | ws-tls-tokio = ["async-native-tls", "async-native-tls/runtime-tokio", "ws-tokio"] 89 | ws-rustls-tokio = ["tokio-rustls", "webpki-roots", "rustls-pki-types", "ws-tokio"] 90 | ws-tls-async-std = ["async-native-tls", "async-native-tls/runtime-async-std", "ws-async-std"] 91 | ipc-tokio = ["tokio", "tokio-stream", "tokio-util"] 92 | arbitrary_precision = ["serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] 93 | test = [] 94 | allow-missing-fields = [] 95 | 96 | [workspace] 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tomasz Drwięga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web3 2 | 3 | Ethereum JSON-RPC multi-transport client. 4 | Rust implementation of Web3.js library. 5 | 6 | [![Build Status][ci-image]][ci-url] [![Crates.io](https://img.shields.io/crates/v/web3)](https://crates.io/crates/web3) 7 | 8 | [ci-image]: https://github.com/tomusdrw/rust-web3/workflows/Compilation%20and%20Testing%20Suite/badge.svg 9 | [ci-url]: https://github.com/tomusdrw/rust-web3/actions?query=workflow%3A%22Compilation+and+Testing+Suite%22 10 | [docs-rs-badge]: https://docs.rs/web3/badge.svg 11 | [docs-rs-url]: https://docs.rs/web3 12 | 13 | Documentation: [crates.io][docs-rs-url] 14 | 15 | ## Status 16 | 17 | Note this package is **barely maintained** and I am looking for an active maintainer (see #664). 18 | If you are starting a new project, I'd recommend choosing https://github.com/gakonst/ethers-rs instead. 19 | 20 | ## Usage 21 | 22 | First, add this to your `Cargo.toml`: 23 | 24 | ```toml 25 | [dependencies] 26 | web3 = "0.19.0" 27 | ``` 28 | 29 | ## Example 30 | ```rust 31 | #[tokio::main] 32 | async fn main() -> web3::Result<()> { 33 | let transport = web3::transports::Http::new("http://localhost:8545")?; 34 | let web3 = web3::Web3::new(transport); 35 | 36 | println!("Calling accounts."); 37 | let mut accounts = web3.eth().accounts().await?; 38 | println!("Accounts: {:?}", accounts); 39 | accounts.push("00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap()); 40 | 41 | println!("Calling balance."); 42 | for account in accounts { 43 | let balance = web3.eth().balance(account, None).await?; 44 | println!("Balance of {:?}: {}", account, balance); 45 | } 46 | 47 | Ok(()) 48 | } 49 | ``` 50 | 51 | If you want to deploy smart contracts you have written you can do something like this (make sure you have the solidity compiler installed): 52 | 53 | `solc -o build --bin --abi contracts/*.sol` 54 | 55 | The solidity compiler is generating the binary and abi code for the smart contracts in a directory called contracts and is being output to a directory called build. 56 | 57 | For more see [examples folder](./examples). 58 | 59 | ## Futures migration 60 | - [ ] Get rid of parking_lot (replace with async-aware locks if really needed). 61 | - [ ] Consider getting rid of `Unpin` requirements. (#361) 62 | - [x] WebSockets: TLS support (#360) 63 | - [ ] WebSockets: Reconnecting & Pings 64 | - [x] Consider using `tokio` instead of `async-std` for `ws.rs` transport (issue with test). 65 | - [x] Restore IPC Transport 66 | 67 | ## General 68 | - [ ] More flexible API (accept `Into`) 69 | - [x] Contract calls (ABI encoding; `debris/ethabi`) 70 | - [X] Batch Requests 71 | 72 | ## Transports 73 | - [x] HTTP transport 74 | - [x] IPC transport 75 | - [x] WebSockets transport 76 | 77 | ## Types 78 | - [x] Types for `U256,H256,Address(H160)` 79 | - [x] Index type (numeric, encoded to hex) 80 | - [x] Transaction type (`Transaction` from Parity) 81 | - [x] Transaction receipt type (`TransactionReceipt` from Parity) 82 | - [x] Block type (`RichBlock` from Parity) 83 | - [x] Work type (`Work` from Parity) 84 | - [X] Syncing type (`SyncStats` from Parity) 85 | 86 | ## APIs 87 | - [x] Eth: `eth_*` 88 | - [x] Eth filters: `eth_*` 89 | - [x] Eth pubsub: `eth_*` 90 | - [x] `net_*` 91 | - [x] `web3_*` 92 | - [x] `personal_*` 93 | - [ ] `traces_*` 94 | 95 | ### Parity-specific APIs 96 | - [ ] Parity read-only: `parity_*` 97 | - [ ] Parity accounts: `parity_*` (partially implemented) 98 | - [x] Parity set: `parity_*` 99 | - [ ] `signer_*` 100 | 101 | - [x] Own APIs (Extendable) 102 | ```rust 103 | let web3 = Web3::new(transport); 104 | web3.api::().custom_method().wait().unwrap() 105 | ``` 106 | 107 | # Installation on Windows 108 | 109 | Currently, Windows does not support IPC, which is enabled in the library by default. 110 | To compile, you need to disable the IPC feature: 111 | ```toml 112 | web3 = { version = "_", default-features = false, features = ["http"] } 113 | ``` 114 | 115 | # Avoiding OpenSSL dependency 116 | 117 | On Linux, `native-tls` is implemented using OpenSSL. To avoid that dependency 118 | for HTTPS or WSS use the corresponding features. 119 | ```toml 120 | web3 = { version = "_", default-features = false, features = ["http-rustls-tls", "ws-rustls-tokio"] } 121 | ``` 122 | 123 | _Note: To fully replicate the default features also add `signing` & `ipc-tokio` features_. 124 | 125 | # Cargo Features 126 | 127 | The library supports following features: 128 | - `http` - Enables HTTP transport (requires `tokio` runtime, because of `hyper`). 129 | - `http-tls` - Enables TLS support via `reqwest/default-tls` for HTTP transport (implies `http`; default). 130 | - `http-native-tls` - Enables TLS support via `reqwest/native-tls` for HTTP transport (implies `http`). 131 | - `http-rustls-tls` - Enables TLS support via `reqwest/rustls-tls` for HTTP transport (implies `http`). 132 | - `ws-tokio` - Enables WS transport using `tokio` runtime. 133 | - `ws-tls-tokio` - Enables TLS support for WS transport (implies `ws-tokio`; default). 134 | - `ws-rustls-tokio` - Enables rustls TLS support for WS transport (implies `ws-tokio`). 135 | - `ws-async-std` - Enables WS transport using `async-std` runtime. 136 | - `ws-tls-async-std` - Enables TLS support for WS transport (implies `ws-async-std`). 137 | - `ipc-tokio` - Enables IPC transport using `tokio` runtime (default). 138 | - `signing` - Enable account namespace and local-signing support (default). 139 | - `eip-1193` - Enable EIP-1193 support. 140 | - `wasm` - Compile for WASM (make sure to disable default features). 141 | - `arbitrary_precision` - Enable `arbitrary_precision` in `serde_json`. 142 | - `allow-missing-fields` - Some response fields are mandatory in Ethereum but not present in 143 | EVM-compatible chains such as Celo and Fantom. This feature enables compatibility by setting a 144 | default value on those fields. 145 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | runner = 'wasm-bindgen-test-runner' 3 | 4 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note will be 7 | 8 | # If 1 or more target triples (and optionally, target_features) are specified, only 9 | # the specified targets will be checked when running `cargo deny check`. This means, 10 | # if a particular package is only ever used as a target specific dependency, such 11 | # as, for example, the `nix` crate only being used via the `target_family = "unix"` 12 | # configuration, that only having windows targets in this list would mean the nix 13 | # crate, as well as any of its exclusive dependencies not shared by any other 14 | # crates, would be ignored, as the target list here is effectively saying which 15 | # targets you are building for. 16 | targets = [ 17 | # The triple can be any string, but only the target triples built in to 18 | # rustc (as of 1.40) can be checked against actual config expressions 19 | #{ triple = "x86_64-unknown-linux-musl" }, 20 | # You can also specify which target_features you promise are enabled for a particular 21 | # target. target_features are currently not validated against the actual valid 22 | # features supported by the target architecture. 23 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 24 | ] 25 | 26 | # This section is considered when running `cargo deny check advisories` 27 | # More documentation for the advisories section can be found here: 28 | # https://github.com/EmbarkStudios/cargo-deny#the-advisories-section 29 | [advisories] 30 | # The path where the advisory database is cloned/fetched into 31 | db-path = "~/.cargo/advisory-db" 32 | # The url of the advisory database to use 33 | db-urls = ["https://github.com/rustsec/advisory-db"] 34 | # The lint level for security vulnerabilities 35 | vulnerability = "warn" 36 | # The lint level for unmaintained crates 37 | unmaintained = "warn" 38 | # The lint level for crates with security notices. Note that as of 39 | # 2019-12-17 there are no security notice advisories in https://github.com/rustsec/advisory-db 40 | notice = "warn" 41 | # A list of advisory IDs to ignore. Note that ignored advisories will still output 42 | # a note when they are encountered. 43 | ignore = [] 44 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 45 | # lower than the range specified will be ignored. Note that ignored advisories 46 | # will still output a note when they are encountered. 47 | # * None - CVSS Score 0.0 48 | # * Low - CVSS Score 0.1 - 3.9 49 | # * Medium - CVSS Score 4.0 - 6.9 50 | # * High - CVSS Score 7.0 - 8.9 51 | # * Critical - CVSS Score 9.0 - 10.0 52 | #severity-threshold = 53 | 54 | # This section is considered when running `cargo deny check licenses` 55 | # More documentation for the licenses section can be found here: 56 | # https://github.com/EmbarkStudios/cargo-deny#the-licenses-section 57 | [licenses] 58 | # The lint level for crates which do not have a detectable license 59 | unlicensed = "warn" 60 | # List of explicitly allowed licenses 61 | # See https://spdx.org/licenses/ for list of possible licenses 62 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 63 | allow = ["CDLA-Permissive-2.0"] 64 | # List of explicitly disallowed licenses 65 | # See https://spdx.org/licenses/ for list of possible licenses 66 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 67 | deny = ["GPL-3.0"] 68 | # The lint level for licenses considered copyleft 69 | copyleft = "allow" 70 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 71 | # * both - The license will only be approved if it is both OSI-approved *AND* FSF/Free 72 | # * either - The license will be approved if it is either OSI-approved *OR* FSF/Free 73 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF/Free 74 | # * fsf-only - The license will be approved if is FSF/Free *AND NOT* OSI-approved 75 | # * neither - The license will be denied if is FSF/Free *OR* OSI-approved 76 | allow-osi-fsf-free = "either" 77 | # The confidence threshold for detecting a license from license text. 78 | # The higher the value, the more closely the license text must be to the 79 | # canonical license text of a valid SPDX license file. 80 | # [possible values: any between 0.0 and 1.0]. 81 | confidence-threshold = 0.8 82 | 83 | # This section is considered when running `cargo deny check bans`. 84 | # More documentation about the 'bans' section can be found here: 85 | # https://github.com/EmbarkStudios/cargo-deny#crate-bans-cargo-deny-check-ban 86 | [bans] 87 | # Lint level for when multiple versions of the same crate are detected 88 | multiple-versions = "warn" 89 | # The graph highlighting used when creating dotgraphs for crates 90 | # with multiple versions 91 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 92 | # * simplest-path - The path to the version with the fewest edges is highlighted 93 | # * all - Both lowest-version and simplest-path are used 94 | highlight = "all" 95 | # List of crates that are allowed. Use with care! 96 | allow = [ 97 | #{ name = "ansi_term", version = "=0.11.0" }, 98 | ] 99 | # List of crates to deny 100 | deny = [ 101 | # Each entry the name of a crate and a version range. If version is 102 | # not specified, all versions will be matched. 103 | #{ name = "ansi_term", version = "=0.11.0" }, 104 | ] 105 | # Certain crates/versions that will be skipped when doing duplicate detection. 106 | skip = [ 107 | #{ name = "ansi_term", version = "=0.11.0" }, 108 | ] 109 | # Similarly to `skip` allows you to skip certain crates during duplicate detection, 110 | # unlike skip, it also includes the entire tree of transitive dependencies starting at 111 | # the specified crate, up to a certain depth, which is by default infinite 112 | skip-tree = [ 113 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 114 | ] 115 | 116 | 117 | # This section is considered when running `cargo deny check sources`. 118 | # More documentation about the 'sources' section can be found here: 119 | # https://github.com/EmbarkStudios/cargo-deny#crate-sources-cargo-deny-check-sources 120 | [sources] 121 | # Lint level for what to happen when a crate from a crate registry that is not in the allow list is encountered 122 | unknown-registry = "warn" 123 | # Lint level for what to happen when a crate from a git repository that is not in the allow list is encountered 124 | unknown-git = "warn" 125 | # List of URLs for allowed crate registries, by default https://github.com/rust-lang/crates.io-index is included 126 | #allow-registry = [] 127 | # List of URLs for allowed Git repositories 128 | allow-git = [] 129 | -------------------------------------------------------------------------------- /examples/bench.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::Mutex; 2 | use std::{ 3 | sync::{atomic, Arc}, 4 | thread, time, 5 | }; 6 | 7 | #[tokio::main] 8 | async fn main() -> web3::Result { 9 | let _ = env_logger::try_init(); 10 | let requests = 200_000; 11 | 12 | let http = web3::transports::Http::new("http://localhost:8545/")?; 13 | bench("http", http, requests); 14 | 15 | let ipc = web3::transports::WebSocket::new("./jsonrpc.ipc").await?; 16 | bench(" ipc", ipc, requests); 17 | 18 | Ok(()) 19 | } 20 | 21 | fn bench(id: &str, transport: T, max: usize) 22 | where 23 | T::Out: Send + 'static, 24 | { 25 | use futures::FutureExt; 26 | 27 | let web3 = web3::Web3::new(transport); 28 | let ticker = Arc::new(Ticker::new(id)); 29 | 30 | for _ in 0..max { 31 | let ticker = ticker.clone(); 32 | ticker.start(); 33 | let accounts = web3.eth().block_number().then(move |res| { 34 | if let Err(e) = res { 35 | println!("Error: {:?}", e); 36 | } 37 | ticker.tick(); 38 | futures::future::ready(()) 39 | }); 40 | tokio::spawn(accounts); 41 | } 42 | 43 | ticker.wait(); 44 | } 45 | 46 | struct Ticker { 47 | id: String, 48 | started: atomic::AtomicUsize, 49 | reqs: atomic::AtomicUsize, 50 | time: Mutex, 51 | } 52 | 53 | impl Ticker { 54 | pub fn new(id: &str) -> Self { 55 | Ticker { 56 | id: id.to_owned(), 57 | time: Mutex::new(time::Instant::now()), 58 | started: Default::default(), 59 | reqs: Default::default(), 60 | } 61 | } 62 | 63 | pub fn start(&self) { 64 | self.started.fetch_add(1, atomic::Ordering::AcqRel); 65 | } 66 | 67 | pub fn tick(&self) { 68 | let reqs = self.reqs.fetch_add(1, atomic::Ordering::AcqRel) as u64; 69 | self.started.fetch_sub(1, atomic::Ordering::AcqRel); 70 | 71 | if reqs >= 100_000 { 72 | self.print_result(reqs); 73 | } 74 | } 75 | 76 | pub fn print_result(&self, reqs: u64) { 77 | fn as_millis(dur: time::Duration) -> u64 { 78 | dur.as_secs() * 1_000 + dur.subsec_nanos() as u64 / 1_000_000 79 | } 80 | 81 | let mut time = self.time.lock(); 82 | let elapsed = as_millis(time.elapsed()); 83 | let result = reqs * 1_000 / elapsed; 84 | 85 | println!("[{}] {} reqs/s ({} reqs in {} ms)", self.id, result, reqs, elapsed); 86 | 87 | self.reqs.store(0, atomic::Ordering::Release); 88 | *time = time::Instant::now(); 89 | } 90 | 91 | pub fn wait(&self) { 92 | while self.started.load(atomic::Ordering::Relaxed) > 0 { 93 | thread::sleep(time::Duration::from_millis(100)); 94 | } 95 | self.print_result(self.reqs.load(atomic::Ordering::Acquire) as u64); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/contract.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | use web3::{ 3 | contract::{Contract, Options}, 4 | types::U256, 5 | }; 6 | 7 | #[tokio::main] 8 | async fn main() -> web3::contract::Result<()> { 9 | let _ = env_logger::try_init(); 10 | let http = web3::transports::Http::new("http://localhost:8545")?; 11 | let web3 = web3::Web3::new(http); 12 | 13 | let my_account = hex!("d028d24f16a8893bd078259d413372ac01580769").into(); 14 | // Get the contract bytecode for instance from Solidity compiler 15 | let bytecode = include_str!("./res/contract_token.code").trim_end(); 16 | // Deploying a contract 17 | let contract = Contract::deploy(web3.eth(), include_bytes!("../src/contract/res/token.json"))? 18 | .confirmations(0) 19 | .options(Options::with(|opt| { 20 | opt.value = Some(5.into()); 21 | opt.gas_price = Some(5.into()); 22 | opt.gas = Some(3_000_000.into()); 23 | })) 24 | .execute( 25 | bytecode, 26 | (U256::from(1_000_000_u64), "My Token".to_owned(), 3u64, "MT".to_owned()), 27 | my_account, 28 | ) 29 | .await?; 30 | 31 | let result = contract.query("balanceOf", (my_account,), None, Options::default(), None); 32 | // Make sure to specify the expected return type, to prevent ambiguous compiler 33 | // errors about `Detokenize` missing for `()`. 34 | let balance_of: U256 = result.await?; 35 | assert_eq!(balance_of, 1_000_000.into()); 36 | 37 | // Accessing existing contract 38 | let contract_address = contract.address(); 39 | let contract = Contract::from_json( 40 | web3.eth(), 41 | contract_address, 42 | include_bytes!("../src/contract/res/token.json"), 43 | )?; 44 | 45 | let result = contract.query("balanceOf", (my_account,), None, Options::default(), None); 46 | let balance_of: U256 = result.await?; 47 | assert_eq!(balance_of, 1_000_000.into()); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/contract_log_filter.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | use std::time; 3 | use web3::{ 4 | contract::{Contract, Options}, 5 | futures::StreamExt, 6 | types::FilterBuilder, 7 | }; 8 | 9 | #[tokio::main] 10 | async fn main() -> web3::contract::Result<()> { 11 | let _ = env_logger::try_init(); 12 | let web3 = web3::Web3::new(web3::transports::Http::new("http://localhost:8545")?); 13 | 14 | // Get the contract bytecode for instance from Solidity compiler 15 | let bytecode = include_str!("./res/SimpleEvent.bin"); 16 | 17 | let accounts = web3.eth().accounts().await?; 18 | println!("accounts: {:?}", &accounts); 19 | 20 | let contract = Contract::deploy(web3.eth(), include_bytes!("./res/SimpleEvent.abi"))? 21 | .confirmations(1) 22 | .poll_interval(time::Duration::from_secs(10)) 23 | .options(Options::with(|opt| opt.gas = Some(3_000_000.into()))) 24 | .execute(bytecode, (), accounts[0]) 25 | .await?; 26 | 27 | println!("contract deployed at: {}", contract.address()); 28 | 29 | // Filter for Hello event in our contract 30 | let filter = FilterBuilder::default() 31 | .address(vec![contract.address()]) 32 | .topics( 33 | Some(vec![hex!( 34 | "d282f389399565f3671145f5916e51652b60eee8e5c759293a2f5771b8ddfd2e" 35 | ) 36 | .into()]), 37 | None, 38 | None, 39 | None, 40 | ) 41 | .build(); 42 | 43 | let filter = web3.eth_filter().create_logs_filter(filter).await?; 44 | 45 | let logs_stream = filter.stream(time::Duration::from_secs(1)); 46 | futures::pin_mut!(logs_stream); 47 | 48 | let tx = contract.call("hello", (), accounts[0], Options::default()).await?; 49 | println!("got tx: {:?}", tx); 50 | 51 | let log = logs_stream.next().await.unwrap(); 52 | println!("got log: {:?}", log); 53 | 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /examples/contract_log_pubsub.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | use std::time; 3 | use web3::{ 4 | contract::{Contract, Options}, 5 | futures::{future, StreamExt}, 6 | types::FilterBuilder, 7 | }; 8 | 9 | #[tokio::main] 10 | async fn main() -> web3::contract::Result<()> { 11 | let _ = env_logger::try_init(); 12 | let web3 = web3::Web3::new(web3::transports::WebSocket::new("ws://localhost:8546").await?); 13 | 14 | // Get the contract bytecode for instance from Solidity compiler 15 | let bytecode = include_str!("./res/SimpleEvent.bin"); 16 | 17 | let accounts = web3.eth().accounts().await?; 18 | println!("accounts: {:?}", &accounts); 19 | 20 | let contract = Contract::deploy(web3.eth(), include_bytes!("./res/SimpleEvent.abi"))? 21 | .confirmations(1) 22 | .poll_interval(time::Duration::from_secs(10)) 23 | .options(Options::with(|opt| opt.gas = Some(3_000_000.into()))) 24 | .execute(bytecode, (), accounts[0]); 25 | let contract = contract.await?; 26 | println!("contract deployed at: {}", contract.address()); 27 | 28 | // Filter for Hello event in our contract 29 | let filter = FilterBuilder::default() 30 | .address(vec![contract.address()]) 31 | .topics( 32 | Some(vec![hex!( 33 | "d282f389399565f3671145f5916e51652b60eee8e5c759293a2f5771b8ddfd2e" 34 | ) 35 | .into()]), 36 | None, 37 | None, 38 | None, 39 | ) 40 | .build(); 41 | 42 | let sub = web3.eth_subscribe().subscribe_logs(filter).await?; 43 | 44 | let tx = contract.call("hello", (), accounts[0], Options::default()).await?; 45 | println!("got tx: {:?}", tx); 46 | 47 | sub.for_each(|log| { 48 | println!("got log: {:?}", log); 49 | future::ready(()) 50 | }) 51 | .await; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /examples/contract_storage.rs: -------------------------------------------------------------------------------- 1 | //based on examples/contract.rs 2 | 3 | use std::time; 4 | use web3::{ 5 | contract::{Contract, Options}, 6 | types::U256, 7 | }; 8 | 9 | #[tokio::main] 10 | async fn main() -> web3::contract::Result<()> { 11 | let _ = env_logger::try_init(); 12 | let transport = web3::transports::Http::new("http://localhost:8545")?; 13 | let web3 = web3::Web3::new(transport); 14 | let accounts = web3.eth().accounts().await?; 15 | 16 | // Get current balance 17 | let balance = web3.eth().balance(accounts[0], None).await?; 18 | 19 | println!("Balance: {}", balance); 20 | 21 | // Get the contract bytecode for instance from Solidity compiler 22 | let bytecode = include_str!("./res/SimpleStorage.bin"); 23 | // Deploying a contract 24 | let contract = Contract::deploy(web3.eth(), include_bytes!("./res/SimpleStorage.abi"))? 25 | .confirmations(1) 26 | .poll_interval(time::Duration::from_secs(10)) 27 | .options(Options::with(|opt| opt.gas = Some(3_000_000.into()))) 28 | .execute(bytecode, (), accounts[0]) 29 | .await?; 30 | 31 | println!("Deployed at: {}", contract.address()); 32 | 33 | // interact with the contract 34 | let result = contract.query("get", (), None, Options::default(), None); 35 | let storage: U256 = result.await?; 36 | println!("Get Storage: {}", storage); 37 | 38 | // Change state of the contract 39 | let tx = contract.call("set", (42_u32,), accounts[0], Options::default()).await?; 40 | println!("TxHash: {}", tx); 41 | 42 | // consider using `async_std::task::sleep` instead. 43 | std::thread::sleep(std::time::Duration::from_secs(5)); 44 | 45 | // View changes made 46 | let result = contract.query("get", (), None, Options::default(), None); 47 | let storage: U256 = result.await?; 48 | println!("Get again: {}", storage); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /examples/contracts/SimpleEvent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | contract SimpleEvent { 4 | event Hello(address sender); 5 | 6 | function hello() public { 7 | emit Hello(msg.sender); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/contracts/SimpleStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | contract SimpleStorage { 4 | uint storedData; 5 | 6 | function set(uint x) { 7 | storedData = x; 8 | } 9 | 10 | function get() constant returns (uint) { 11 | return storedData; 12 | } 13 | } -------------------------------------------------------------------------------- /examples/pubsub.rs: -------------------------------------------------------------------------------- 1 | use web3::futures::{future, StreamExt}; 2 | 3 | #[tokio::main] 4 | async fn main() -> web3::Result { 5 | let ws = web3::transports::WebSocket::new("ws://localhost:8546").await?; 6 | let web3 = web3::Web3::new(ws.clone()); 7 | let mut sub = web3.eth_subscribe().subscribe_new_heads().await?; 8 | 9 | println!("Got subscription id: {:?}", sub.id()); 10 | 11 | (&mut sub) 12 | .take(5) 13 | .for_each(|x| { 14 | println!("Got: {:?}", x); 15 | future::ready(()) 16 | }) 17 | .await; 18 | 19 | sub.unsubscribe().await?; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | First, run ganache 2 | 3 | ganache-cli -b 3 -m "hamster coin cup brief quote trick stove draft hobby strong caught unable" 4 | 5 | Using this mnemonic makes the static account addresses in the example line up 6 | 7 | Now we can run an example 8 | 9 | cargo run --example contract 10 | -------------------------------------------------------------------------------- /examples/res/SimpleEvent.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[],"name":"hello","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"Hello","type":"event"}] -------------------------------------------------------------------------------- /examples/res/SimpleEvent.bin: -------------------------------------------------------------------------------- 1 | 6080604052348015600f57600080fd5b5060e98061001e6000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319ff1d21146044575b600080fd5b348015604f57600080fd5b5060566058565b005b7fd282f389399565f3671145f5916e51652b60eee8e5c759293a2f5771b8ddfd2e33604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15600a165627a7a7230582055392010c17991d1f1d7b60a934d6038103da718d9fccc7ec270634df8352ad70029 -------------------------------------------------------------------------------- /examples/res/SimpleStorage.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}] -------------------------------------------------------------------------------- /examples/res/SimpleStorage.bin: -------------------------------------------------------------------------------- 1 | 6060604052341561000c57fe5b5b60c68061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b11460445780636d4ce63c146061575bfe5b3415604b57fe5b605f60048080359060200190919050506084565b005b3415606857fe5b606e608f565b6040518082815260200191505060405180910390f35b806000819055505b50565b600060005490505b905600a165627a7a72305820616d9257b411248095799f7dd90840e3b07ae5c3b6083c0d78d14d826122d3c40029 -------------------------------------------------------------------------------- /examples/res/contract_token.code: -------------------------------------------------------------------------------- 1 | 606060405260408051908101604052600481527f48302e31000000000000000000000000000000000000000000000000000000006020820152600690805161004b9291602001906100e7565b50341561005757600080fd5b6040516109f83803806109f8833981016040528080519190602001805182019190602001805191906020018051600160a060020a0333166000908152600160205260408120879055869055909101905060038380516100ba9291602001906100e7565b506004805460ff191660ff841617905560058180516100dd9291602001906100e7565b5050505050610182565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061012857805160ff1916838001178555610155565b82800160010185558215610155579182015b8281111561015557825182559160200191906001019061013a565b50610161929150610165565b5090565b61017f91905b80821115610161576000815560010161016b565b90565b610867806101916000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde0381146100a3578063095ea7b31461012d57806318160ddd1461016357806323b872dd14610188578063313ce567146101b057806354fd4d50146101d957806370a08231146101ec57806395d89b411461020b578063a9059cbb1461021e578063cae9ca5114610240578063dd62ed3e146102a5575b341561009e57600080fd5b600080fd5b34156100ae57600080fd5b6100b66102ca565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100f25780820151838201526020016100da565b50505050905090810190601f16801561011f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013857600080fd5b61014f600160a060020a0360043516602435610368565b604051901515815260200160405180910390f35b341561016e57600080fd5b6101766103d5565b60405190815260200160405180910390f35b341561019357600080fd5b61014f600160a060020a03600435811690602435166044356103db565b34156101bb57600080fd5b6101c36104d3565b60405160ff909116815260200160405180910390f35b34156101e457600080fd5b6100b66104dc565b34156101f757600080fd5b610176600160a060020a0360043516610547565b341561021657600080fd5b6100b6610562565b341561022957600080fd5b61014f600160a060020a03600435166024356105cd565b341561024b57600080fd5b61014f60048035600160a060020a03169060248035919060649060443590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061067095505050505050565b34156102b057600080fd5b610176600160a060020a0360043581169060243516610810565b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103605780601f1061033557610100808354040283529160200191610360565b820191906000526020600020905b81548152906001019060200180831161034357829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294909152808220859055909291907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259085905190815260200160405180910390a35060015b92915050565b60005481565b600160a060020a03831660009081526001602052604081205482901080159061042b5750600160a060020a0380851660009081526002602090815260408083203390941683529290522054829010155b80156104375750600082115b156104c857600160a060020a03808416600081815260016020908152604080832080548801905588851680845281842080548990039055600283528184203390961684529490915290819020805486900390559091907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35060016104cc565b5060005b9392505050565b60045460ff1681565b60068054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103605780601f1061033557610100808354040283529160200191610360565b600160a060020a031660009081526001602052604090205490565b60058054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103605780601f1061033557610100808354040283529160200191610360565b600160a060020a0333166000908152600160205260408120548290108015906105f65750600082115b1561066857600160a060020a033381166000818152600160205260408082208054879003905592861680825290839020805486019055917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35060016103cf565b5060006103cf565b600160a060020a03338116600081815260026020908152604080832094881680845294909152808220869055909291907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259086905190815260200160405180910390a383600160a060020a03166040517f72656365697665417070726f76616c28616464726573732c75696e743235362c81527f616464726573732c6279746573290000000000000000000000000000000000006020820152602e01604051809103902060e060020a9004338530866040518563ffffffff1660e060020a0281526004018085600160a060020a0316600160a060020a0316815260200184815260200183600160a060020a0316600160a060020a03168152602001828051906020019080838360005b838110156107b1578082015183820152602001610799565b50505050905090810190601f1680156107de5780820380516001836020036101000a031916815260200191505b5094505050505060006040518083038160008761646e5a03f192505050151561080657600080fd5b5060019392505050565b600160a060020a039182166000908152600260209081526040808320939094168252919091522054905600a165627a7a723058204d5eeb1cfd7573cd14412d3639887ef3085966b5b1c42ab4d98ff3396dbb1ada0029 2 | -------------------------------------------------------------------------------- /examples/transaction_private.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use web3::{ 4 | ethabi::ethereum_types::U256, 5 | types::{Address, TransactionRequest}, 6 | }; 7 | 8 | /// Below sends a transaction to a local node that stores private keys (eg Ganache) 9 | /// For generating and signing a transaction offline, before transmitting it to a public node (eg Infura) see transaction_public 10 | #[tokio::main] 11 | async fn main() -> web3::Result { 12 | let transport = web3::transports::Http::new("http://localhost:7545")?; 13 | let web3 = web3::Web3::new(transport); 14 | 15 | // Insert the 20-byte "from" address in hex format (prefix with 0x) 16 | let from = Address::from_str("0xC48ad5fd060e1400a41bcf51db755251AD5A2475").unwrap(); 17 | 18 | // Insert the 20-byte "to" address in hex format (prefix with 0x) 19 | let to = Address::from_str("0xCd550E94040cEC1b33589eB99B0E1241Baa75D19").unwrap(); 20 | 21 | // Build the tx object 22 | let tx_object = TransactionRequest { 23 | from, 24 | to: Some(to), 25 | value: Some(U256::exp10(17)), //0.1 eth 26 | ..Default::default() 27 | }; 28 | 29 | // Send the tx to localhost 30 | let result = web3.eth().send_transaction(tx_object).await?; 31 | 32 | println!("Tx succeeded with hash: {}", result); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/transaction_public.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use web3::{ 4 | ethabi::ethereum_types::U256, 5 | signing::SecretKey, 6 | types::{Address, TransactionParameters}, 7 | }; 8 | 9 | /// Below generates and signs a transaction offline, before transmitting it to a public node (eg Infura) 10 | /// For sending a transaction to a local node that stores private keys (eg Ganache) see transaction_private 11 | #[tokio::main] 12 | async fn main() -> web3::Result { 13 | // Sign up at infura > choose the desired network (eg Rinkeby) > copy the endpoint url into the below 14 | // If you need test ether use a faucet, eg https://faucet.rinkeby.io/ 15 | let transport = web3::transports::Http::new("https://rinkeby.infura.io/v3/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")?; 16 | let web3 = web3::Web3::new(transport); 17 | 18 | // Insert the 20-byte "to" address in hex format (prefix with 0x) 19 | let to = Address::from_str("0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX").unwrap(); 20 | 21 | // Insert the 32-byte private key in hex format (do NOT prefix with 0x) 22 | let prvk = SecretKey::from_str("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX").unwrap(); 23 | 24 | // Build the tx object 25 | let tx_object = TransactionParameters { 26 | to: Some(to), 27 | value: U256::exp10(17), //0.1 eth 28 | ..Default::default() 29 | }; 30 | 31 | // Sign the tx (can be done offline) 32 | let signed = web3.accounts().sign_transaction(tx_object, &prvk).await?; 33 | 34 | // Send the tx to infura 35 | let result = web3.eth().send_raw_transaction(signed.raw_transaction).await?; 36 | 37 | println!("Tx succeeded with hash: {}", result); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /examples/transport_batch.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() -> web3::Result { 3 | let _ = env_logger::try_init(); 4 | let http = web3::transports::Http::new("http://localhost:8545")?; 5 | let web3 = web3::Web3::new(web3::transports::Batch::new(http)); 6 | 7 | let accounts = web3.eth().accounts(); 8 | let block = web3.eth().block_number(); 9 | 10 | let result = web3.transport().submit_batch().await?; 11 | println!("Result: {:?}", result); 12 | 13 | let accounts = accounts.await?; 14 | println!("Accounts: {:?}", accounts); 15 | 16 | let block = block.await?; 17 | println!("Block: {:?}", block); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /examples/transport_either.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | 3 | pub type Transport = web3::transports::Either; 4 | 5 | #[tokio::main] 6 | async fn main() -> web3::Result { 7 | let _ = env_logger::try_init(); 8 | let transport = web3::transports::Http::new("http://localhost:8545")?; 9 | 10 | run(web3::transports::Either::Right(transport)).await 11 | } 12 | 13 | async fn run(transport: Transport) -> web3::Result { 14 | let web3 = web3::Web3::new(transport); 15 | 16 | println!("Calling accounts."); 17 | let mut accounts = web3.eth().accounts().await?; 18 | println!("Accounts: {:?}", accounts); 19 | accounts.push(hex!("00a329c0648769a73afac7f9381e08fb43dbea72").into()); 20 | 21 | println!("Calling balance."); 22 | for account in accounts { 23 | let balance = web3.eth().balance(account, None).await?; 24 | println!("Balance of {:?}: {}", account, balance); 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/transport_http.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | 3 | #[tokio::main] 4 | async fn main() -> web3::Result<()> { 5 | let _ = env_logger::try_init(); 6 | let transport = web3::transports::Http::new("http://localhost:8545")?; 7 | let web3 = web3::Web3::new(transport); 8 | 9 | println!("Calling accounts."); 10 | let mut accounts = web3.eth().accounts().await?; 11 | println!("Accounts: {:?}", accounts); 12 | accounts.push(hex!("00a329c0648769a73afac7f9381e08fb43dbea72").into()); 13 | 14 | println!("Calling balance."); 15 | for account in accounts { 16 | let balance = web3.eth().balance(account, None).await?; 17 | println!("Balance of {:?}: {}", account, balance); 18 | } 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /examples/transport_ws.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | 3 | #[tokio::main] 4 | async fn main() -> web3::Result<()> { 5 | let _ = env_logger::try_init(); 6 | let transport = web3::transports::WebSocket::new("ws://localhost:8546").await?; 7 | let web3 = web3::Web3::new(transport); 8 | 9 | println!("Calling accounts."); 10 | let mut accounts = web3.eth().accounts().await?; 11 | println!("Accounts: {:?}", accounts); 12 | accounts.push(hex!("00a329c0648769a73afac7f9381e08fb43dbea72").into()); 13 | 14 | println!("Calling balance."); 15 | for account in accounts { 16 | let balance = web3.eth().balance(account, None).await?; 17 | println!("Balance of {:?}: {}", account, balance); 18 | } 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /src/api/eth_subscribe.rs: -------------------------------------------------------------------------------- 1 | //! `Eth` namespace, subscriptions 2 | 3 | use crate::{ 4 | api::Namespace, 5 | error, helpers, 6 | types::{BlockHeader, Filter, Log, SyncState, H256}, 7 | DuplexTransport, 8 | }; 9 | use futures::{ 10 | task::{Context, Poll}, 11 | Stream, 12 | }; 13 | use pin_project::{pin_project, pinned_drop}; 14 | use std::{marker::PhantomData, pin::Pin}; 15 | 16 | /// `Eth` namespace, subscriptions 17 | #[derive(Debug, Clone)] 18 | pub struct EthSubscribe { 19 | transport: T, 20 | } 21 | 22 | impl Namespace for EthSubscribe { 23 | fn new(transport: T) -> Self 24 | where 25 | Self: Sized, 26 | { 27 | EthSubscribe { transport } 28 | } 29 | 30 | fn transport(&self) -> &T { 31 | &self.transport 32 | } 33 | } 34 | 35 | /// ID of subscription returned from `eth_subscribe` 36 | #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] 37 | pub struct SubscriptionId(String); 38 | 39 | impl From for SubscriptionId { 40 | fn from(s: String) -> Self { 41 | SubscriptionId(s) 42 | } 43 | } 44 | 45 | /// Stream of notifications from a subscription 46 | /// Given a type deserializable from rpc::Value and a subscription id, yields items of that type as 47 | /// notifications are delivered. 48 | #[pin_project(PinnedDrop)] 49 | #[derive(Debug)] 50 | pub struct SubscriptionStream { 51 | transport: T, 52 | id: SubscriptionId, 53 | #[pin] 54 | rx: T::NotificationStream, 55 | _marker: PhantomData, 56 | } 57 | 58 | impl SubscriptionStream { 59 | fn new(transport: T, id: SubscriptionId) -> error::Result { 60 | let rx = transport.subscribe(id.clone())?; 61 | Ok(SubscriptionStream { 62 | transport, 63 | id, 64 | rx, 65 | _marker: PhantomData, 66 | }) 67 | } 68 | 69 | /// Return the ID of this subscription 70 | pub fn id(&self) -> &SubscriptionId { 71 | &self.id 72 | } 73 | 74 | /// Unsubscribe from the event represented by this stream 75 | pub async fn unsubscribe(self) -> error::Result { 76 | let &SubscriptionId(ref id) = &self.id; 77 | let id = helpers::serialize(&id); 78 | let response = self.transport.execute("eth_unsubscribe", vec![id]).await?; 79 | helpers::decode(response) 80 | } 81 | } 82 | 83 | impl Stream for SubscriptionStream 84 | where 85 | T: DuplexTransport, 86 | I: serde::de::DeserializeOwned, 87 | { 88 | type Item = error::Result; 89 | 90 | fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { 91 | let this = self.project(); 92 | let x = ready!(this.rx.poll_next(ctx)); 93 | Poll::Ready(x.map(|result| serde_json::from_value(result).map_err(Into::into))) 94 | } 95 | } 96 | 97 | #[pinned_drop] 98 | impl PinnedDrop for SubscriptionStream 99 | where 100 | T: DuplexTransport, 101 | { 102 | fn drop(self: Pin<&mut Self>) { 103 | let _ = self.transport.unsubscribe(self.id().clone()); 104 | } 105 | } 106 | 107 | impl EthSubscribe { 108 | /// Create a new heads subscription 109 | pub async fn subscribe_new_heads(&self) -> error::Result> { 110 | let subscription = helpers::serialize(&&"newHeads"); 111 | let response = self.transport.execute("eth_subscribe", vec![subscription]).await?; 112 | let id: String = helpers::decode(response)?; 113 | SubscriptionStream::new(self.transport.clone(), SubscriptionId(id)) 114 | } 115 | 116 | /// Create a logs subscription 117 | pub async fn subscribe_logs(&self, filter: Filter) -> error::Result> { 118 | let subscription = helpers::serialize(&&"logs"); 119 | let filter = helpers::serialize(&filter); 120 | let response = self 121 | .transport 122 | .execute("eth_subscribe", vec![subscription, filter]) 123 | .await?; 124 | let id: String = helpers::decode(response)?; 125 | SubscriptionStream::new(self.transport.clone(), SubscriptionId(id)) 126 | } 127 | 128 | /// Create a pending transactions subscription 129 | pub async fn subscribe_new_pending_transactions(&self) -> error::Result> { 130 | let subscription = helpers::serialize(&&"newPendingTransactions"); 131 | let response = self.transport.execute("eth_subscribe", vec![subscription]).await?; 132 | let id: String = helpers::decode(response)?; 133 | SubscriptionStream::new(self.transport.clone(), SubscriptionId(id)) 134 | } 135 | 136 | /// Create a sync status subscription 137 | pub async fn subscribe_syncing(&self) -> error::Result> { 138 | let subscription = helpers::serialize(&&"syncing"); 139 | let response = self.transport.execute("eth_subscribe", vec![subscription]).await?; 140 | let id: String = helpers::decode(response)?; 141 | SubscriptionStream::new(self.transport.clone(), SubscriptionId(id)) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | //! `Web3` implementation 2 | 3 | mod accounts; 4 | mod eth; 5 | mod eth_filter; 6 | mod eth_subscribe; 7 | mod net; 8 | mod parity; 9 | mod parity_accounts; 10 | mod parity_set; 11 | mod personal; 12 | mod traces; 13 | mod txpool; 14 | mod web3; 15 | 16 | pub use self::{ 17 | accounts::Accounts, 18 | eth::Eth, 19 | eth_filter::{BaseFilter, EthFilter}, 20 | eth_subscribe::{EthSubscribe, SubscriptionId, SubscriptionStream}, 21 | net::Net, 22 | parity::Parity, 23 | parity_accounts::ParityAccounts, 24 | parity_set::ParitySet, 25 | personal::Personal, 26 | traces::Traces, 27 | txpool::Txpool, 28 | web3::Web3 as Web3Api, 29 | }; 30 | 31 | use crate::{ 32 | confirm, error, 33 | types::{Bytes, TransactionReceipt, TransactionRequest, U64}, 34 | DuplexTransport, Transport, 35 | }; 36 | use futures::Future; 37 | use std::time::Duration; 38 | 39 | /// Common API for all namespaces 40 | pub trait Namespace: Clone { 41 | /// Creates new API namespace 42 | fn new(transport: T) -> Self; 43 | 44 | /// Borrows a transport. 45 | fn transport(&self) -> &T; 46 | } 47 | 48 | /// `Web3` wrapper for all namespaces 49 | #[derive(Debug, Clone)] 50 | pub struct Web3 { 51 | transport: T, 52 | } 53 | 54 | impl Web3 { 55 | /// Create new `Web3` with given transport 56 | pub fn new(transport: T) -> Self { 57 | Web3 { transport } 58 | } 59 | 60 | /// Borrows a transport. 61 | pub fn transport(&self) -> &T { 62 | &self.transport 63 | } 64 | 65 | /// Access methods from custom namespace 66 | pub fn api>(&self) -> A { 67 | A::new(self.transport.clone()) 68 | } 69 | 70 | /// Access methods from `accounts` namespace 71 | pub fn accounts(&self) -> accounts::Accounts { 72 | self.api() 73 | } 74 | 75 | /// Access methods from `eth` namespace 76 | pub fn eth(&self) -> eth::Eth { 77 | self.api() 78 | } 79 | 80 | /// Access methods from `net` namespace 81 | pub fn net(&self) -> net::Net { 82 | self.api() 83 | } 84 | 85 | /// Access methods from `web3` namespace 86 | pub fn web3(&self) -> web3::Web3 { 87 | self.api() 88 | } 89 | 90 | /// Access filter methods from `eth` namespace 91 | pub fn eth_filter(&self) -> eth_filter::EthFilter { 92 | self.api() 93 | } 94 | 95 | /// Access methods from `parity` namespace 96 | pub fn parity(&self) -> parity::Parity { 97 | self.api() 98 | } 99 | 100 | /// Access methods from `parity_accounts` namespace 101 | pub fn parity_accounts(&self) -> parity_accounts::ParityAccounts { 102 | self.api() 103 | } 104 | 105 | /// Access methods from `parity_set` namespace 106 | pub fn parity_set(&self) -> parity_set::ParitySet { 107 | self.api() 108 | } 109 | 110 | /// Access methods from `personal` namespace 111 | pub fn personal(&self) -> personal::Personal { 112 | self.api() 113 | } 114 | 115 | /// Access methods from `trace` namespace 116 | pub fn trace(&self) -> traces::Traces { 117 | self.api() 118 | } 119 | 120 | /// Access methods from `txpool` namespace 121 | pub fn txpool(&self) -> txpool::Txpool { 122 | self.api() 123 | } 124 | 125 | /// Should be used to wait for confirmations 126 | pub async fn wait_for_confirmations( 127 | &self, 128 | poll_interval: Duration, 129 | confirmations: usize, 130 | check: V, 131 | ) -> error::Result<()> 132 | where 133 | F: Future>>, 134 | V: confirm::ConfirmationCheck, 135 | { 136 | confirm::wait_for_confirmations(self.eth(), self.eth_filter(), poll_interval, confirmations, check).await 137 | } 138 | 139 | /// Sends transaction and returns future resolved after transaction is confirmed 140 | pub async fn send_transaction_with_confirmation( 141 | &self, 142 | tx: TransactionRequest, 143 | poll_interval: Duration, 144 | confirmations: usize, 145 | ) -> error::Result { 146 | confirm::send_transaction_with_confirmation(self.transport.clone(), tx, poll_interval, confirmations).await 147 | } 148 | 149 | /// Sends raw transaction and returns future resolved after transaction is confirmed 150 | pub async fn send_raw_transaction_with_confirmation( 151 | &self, 152 | tx: Bytes, 153 | poll_interval: Duration, 154 | confirmations: usize, 155 | ) -> error::Result { 156 | confirm::send_raw_transaction_with_confirmation(self.transport.clone(), tx, poll_interval, confirmations).await 157 | } 158 | } 159 | 160 | impl Web3 { 161 | /// Access subscribe methods from `eth` namespace 162 | pub fn eth_subscribe(&self) -> eth_subscribe::EthSubscribe { 163 | self.api() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/api/net.rs: -------------------------------------------------------------------------------- 1 | //! `Net` namespace 2 | 3 | use crate::{api::Namespace, helpers::CallFuture, types::U256, Transport}; 4 | 5 | /// `Net` namespace 6 | #[derive(Debug, Clone)] 7 | pub struct Net { 8 | transport: T, 9 | } 10 | 11 | impl Namespace for Net { 12 | fn new(transport: T) -> Self 13 | where 14 | Self: Sized, 15 | { 16 | Net { transport } 17 | } 18 | 19 | fn transport(&self) -> &T { 20 | &self.transport 21 | } 22 | } 23 | 24 | impl Net { 25 | /// Returns the network id. 26 | pub fn version(&self) -> CallFuture { 27 | CallFuture::new(self.transport.execute("net_version", vec![])) 28 | } 29 | 30 | /// Returns number of peers connected to node. 31 | pub fn peer_count(&self) -> CallFuture { 32 | CallFuture::new(self.transport.execute("net_peerCount", vec![])) 33 | } 34 | 35 | /// Whether the node is listening for network connections 36 | pub fn is_listening(&self) -> CallFuture { 37 | CallFuture::new(self.transport.execute("net_listening", vec![])) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::Net; 44 | use crate::{api::Namespace, rpc::Value, types::U256}; 45 | 46 | rpc_test! ( 47 | Net:version => "net_version"; 48 | Value::String("Test123".into()) => "Test123" 49 | ); 50 | 51 | rpc_test! ( 52 | Net:peer_count => "net_peerCount"; 53 | Value::String("0x123".into()) => U256::from(0x123) 54 | ); 55 | 56 | rpc_test! ( 57 | Net:is_listening => "net_listening"; 58 | Value::Bool(true) => true 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/api/parity.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::Namespace, 3 | helpers::{self, CallFuture}, 4 | rpc::Value, 5 | types::{Bytes, CallRequest, ParityPendingTransactionFilter, Transaction}, 6 | Transport, 7 | }; 8 | 9 | /// `Parity` namespace 10 | #[derive(Debug, Clone)] 11 | pub struct Parity { 12 | transport: T, 13 | } 14 | 15 | impl Namespace for Parity { 16 | fn new(transport: T) -> Self 17 | where 18 | Self: Sized, 19 | { 20 | Parity { transport } 21 | } 22 | 23 | fn transport(&self) -> &T { 24 | &self.transport 25 | } 26 | } 27 | 28 | impl Parity { 29 | /// Sequentially call multiple contract methods in one request without changing the state of the blockchain. 30 | pub fn call(&self, reqs: Vec) -> CallFuture, T::Out> { 31 | let reqs = helpers::serialize(&reqs); 32 | 33 | CallFuture::new(self.transport.execute("parity_call", vec![reqs])) 34 | } 35 | 36 | /// Get pending transactions 37 | /// Blocked by https://github.com/openethereum/openethereum/issues/159 38 | pub fn pending_transactions( 39 | &self, 40 | limit: Option, 41 | filter: Option, 42 | ) -> CallFuture, T::Out> { 43 | let limit = limit.map(Value::from); 44 | let filter = filter.as_ref().map(helpers::serialize); 45 | let params = match (limit, filter) { 46 | (l, Some(f)) => vec![l.unwrap_or(Value::Null), f], 47 | (Some(l), None) => vec![l], 48 | _ => vec![], 49 | }; 50 | 51 | CallFuture::new(self.transport.execute("parity_pendingTransactions", params)) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::Parity; 58 | use crate::{ 59 | api::Namespace, 60 | rpc::Value, 61 | types::{Address, CallRequest, FilterCondition, ParityPendingTransactionFilter, Transaction, U64}, 62 | }; 63 | use hex_literal::hex; 64 | 65 | const EXAMPLE_PENDING_TX: &str = r#"{ 66 | "blockHash": null, 67 | "blockNumber": null, 68 | "creates": null, 69 | "from": "0xee3ea02840129123d5397f91be0391283a25bc7d", 70 | "gas": "0x23b58", 71 | "gasPrice": "0xba43b7400", 72 | "hash": "0x160b3c30ab1cf5871083f97ee1cee3901cfba3b0a2258eb337dd20a7e816b36e", 73 | "input": "0x095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d5555555555", 74 | "condition": { 75 | "block": 1 76 | }, 77 | "chainId": 1, 78 | "nonce": "0x5", 79 | "publicKey": "0x96157302dade55a1178581333e57d60ffe6fdf5a99607890456a578b4e6b60e335037d61ed58aa4180f9fd747dc50d44a7924aa026acbfb988b5062b629d6c36", 80 | "r": "0x92e8beb19af2bad0511d516a86e77fa73004c0811b2173657a55797bdf8558e1", 81 | "raw": "0xf8aa05850ba43b740083023b5894bb9bc244d798123fde783fcc1c72d3bb8c18941380b844095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d555555555526a092e8beb19af2bad0511d516a86e77fa73004c0811b2173657a55797bdf8558e1a062b4d4d125bbcb9c162453bc36ca156537543bb4414d59d1805d37fb63b351b8", 82 | "s": "0x62b4d4d125bbcb9c162453bc36ca156537543bb4414d59d1805d37fb63b351b8", 83 | "standardV": "0x1", 84 | "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", 85 | "transactionIndex": null, 86 | "v": "0x26", 87 | "value": "0x0" 88 | }"#; 89 | 90 | rpc_test!( 91 | Parity:call, 92 | vec![ 93 | CallRequest { 94 | from: None, 95 | to: Some(Address::from_low_u64_be(0x123)), 96 | gas: None, 97 | gas_price: None, 98 | value: Some(0x1.into()), 99 | data: None, 100 | transaction_type: None, 101 | access_list: None, 102 | max_fee_per_gas: None, 103 | max_priority_fee_per_gas: None, 104 | }, 105 | CallRequest { 106 | from: Some(Address::from_low_u64_be(0x321)), 107 | to: Some(Address::from_low_u64_be(0x123)), 108 | gas: None, 109 | gas_price: None, 110 | value: None, 111 | data: Some(hex!("0493").into()), 112 | transaction_type: None, 113 | access_list: None, 114 | max_fee_per_gas: None, 115 | max_priority_fee_per_gas: None, 116 | }, 117 | CallRequest { 118 | from: None, 119 | to: Some(Address::from_low_u64_be(0x765)), 120 | gas: None, 121 | gas_price: None, 122 | value: Some(0x5.into()), 123 | data: Some(hex!("0723").into()), 124 | transaction_type: None, 125 | access_list: None, 126 | max_fee_per_gas: None, 127 | max_priority_fee_per_gas: None, 128 | } 129 | ] => "parity_call", vec![ 130 | r#"[{"to":"0x0000000000000000000000000000000000000123","value":"0x1"},{"data":"0x0493","from":"0x0000000000000000000000000000000000000321","to":"0x0000000000000000000000000000000000000123"},{"data":"0x0723","to":"0x0000000000000000000000000000000000000765","value":"0x5"}]"# 131 | ]; 132 | Value::Array(vec![Value::String("0x010203".into()), Value::String("0x7198ab".into()), Value::String("0xde763f".into())]) => vec![hex!("010203").into(), hex!("7198ab").into(), hex!("de763f").into()] 133 | ); 134 | 135 | rpc_test!( 136 | Parity:pending_transactions, 137 | 1, 138 | ParityPendingTransactionFilter::builder() 139 | .from(Address::from_low_u64_be(0x32)) 140 | .gas(U64::from(100_000)) 141 | .gas_price(FilterCondition::GreaterThan(U64::from(100_000_000_000_u64))) 142 | .build() 143 | => "parity_pendingTransactions", 144 | vec![r#"1"#, r#"{"from":{"eq":"0x0000000000000000000000000000000000000032"},"gas":{"eq":"0x186a0"},"gas_price":{"gt":"0x174876e800"}}"#] 145 | ; 146 | Value::Array(vec![::serde_json::from_str(EXAMPLE_PENDING_TX).unwrap()]) 147 | => vec![::serde_json::from_str::(EXAMPLE_PENDING_TX).unwrap()] 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /src/api/parity_accounts.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::Namespace, 3 | helpers::{self, CallFuture}, 4 | types::{Address, H256}, 5 | Transport, 6 | }; 7 | 8 | /// `Parity_Accounts` namespace 9 | #[derive(Debug, Clone)] 10 | pub struct ParityAccounts { 11 | transport: T, 12 | } 13 | 14 | impl Namespace for ParityAccounts { 15 | fn new(transport: T) -> Self 16 | where 17 | Self: Sized, 18 | { 19 | ParityAccounts { transport } 20 | } 21 | 22 | fn transport(&self) -> &T { 23 | &self.transport 24 | } 25 | } 26 | 27 | impl ParityAccounts { 28 | /// Given an address of an account and its password deletes the account from the parity node 29 | pub fn parity_kill_account(&self, address: &Address, pwd: &str) -> CallFuture { 30 | let address = helpers::serialize(&address); 31 | let pwd = helpers::serialize(&pwd); 32 | CallFuture::new(self.transport.execute("parity_killAccount", vec![address, pwd])) 33 | } 34 | /// Imports an account from a given seed/phrase 35 | /// Returns the address of the corresponding seed vinculated account 36 | pub fn parity_new_account_from_phrase(&self, seed: &str, pwd: &str) -> CallFuture { 37 | let seed = helpers::serialize(&seed); 38 | let pwd = helpers::serialize(&pwd); 39 | CallFuture::new(self.transport.execute("parity_newAccountFromPhrase", vec![seed, pwd])) 40 | } 41 | /// Imports an account from a given secret key. 42 | /// Returns the address of the corresponding Sk vinculated account. 43 | pub fn new_account_from_secret(&self, secret: &H256, pwd: &str) -> CallFuture { 44 | let secret = helpers::serialize(&secret); 45 | let pwd = helpers::serialize(&pwd); 46 | CallFuture::new(self.transport.execute("parity_newAccountFromSecret", vec![secret, pwd])) 47 | } 48 | /// Imports an account from a JSON encoded Wallet file. 49 | /// Returns the address of the corresponding wallet. 50 | pub fn parity_new_account_from_wallet(&self, wallet: &str, pwd: &str) -> CallFuture { 51 | let wallet = helpers::serialize(&wallet); 52 | let pwd = helpers::serialize(&pwd); 53 | CallFuture::new(self.transport.execute("parity_newAccountFromWallet", vec![wallet, pwd])) 54 | } 55 | /// Removes the address of the Parity node addressbook. 56 | /// Returns true if the operation succeeded. 57 | pub fn parity_remove_address(&self, address: &Address) -> CallFuture { 58 | let address = helpers::serialize(&address); 59 | CallFuture::new(self.transport.execute("parity_removeAddress", vec![address])) 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::ParityAccounts; 66 | use crate::{api::Namespace, rpc::Value}; 67 | use ethereum_types::{Address, H256}; 68 | 69 | rpc_test! ( 70 | ParityAccounts : parity_kill_account, &"9b776baeaf3896657a9ba0db5564623b3e0173e0".parse::
().unwrap(), "123456789" 71 | => "parity_killAccount", vec![r#""0x9b776baeaf3896657a9ba0db5564623b3e0173e0""#, r#""123456789""#]; 72 | Value::Bool(true) => true 73 | ); 74 | 75 | rpc_test! ( 76 | ParityAccounts : parity_new_account_from_phrase, "member funny cloth wrist ugly water tuition always fall recycle maze long", "123456789" 77 | => "parity_newAccountFromPhrase", vec![r#""member funny cloth wrist ugly water tuition always fall recycle maze long""#, r#""123456789""#]; 78 | Value::String("0xE43eD16390bd419d48B09d6E2aa20203D1eF93E1".into()) => "E43eD16390bd419d48B09d6E2aa20203D1eF93E1".parse::
().unwrap() 79 | ); 80 | 81 | rpc_test! ( 82 | ParityAccounts : new_account_from_secret, &"c6592108cc3577f6a2d6178bc6947b43db39057195802caa0120f26e39af4945".parse::().unwrap(), "123456789" 83 | => "parity_newAccountFromSecret", vec![r#""0xc6592108cc3577f6a2d6178bc6947b43db39057195802caa0120f26e39af4945""#, r#""123456789""#]; 84 | Value::String("0x9b776Baeaf3896657A9ba0db5564623B3E0173e0".into()) => "9b776Baeaf3896657A9ba0db5564623B3E0173e0".parse::
().unwrap() 85 | ); 86 | 87 | rpc_test! ( 88 | ParityAccounts : parity_new_account_from_wallet, r#"{"version":3,"id":"3b330c3b-b0b3-4e39-b62e-c2041a98d673","address":"4c8ab9d3e938285776d6717d7319f6a9b1d809dd","Crypto":{"ciphertext":"bb3a6dbf21f0bf2b5eb0b43426590f16650acee9462ab710cca18781691a5739","cipherparams":{"iv":"6a533f77fc5cb8a752a16ec6a3200da1"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"a58609853dec53c81feb165e346c700e714285771825bb4cbf87c4ea1996b682","n":8192,"r":8,"p":1},"mac":"a71edeb659ed628db13579ce9f75c80c9d386c1239b280548d9a0e58ad20d6c7"}}"#, "123456789" 89 | => "parity_newAccountFromWallet", vec![r#""{\"version\":3,\"id\":\"3b330c3b-b0b3-4e39-b62e-c2041a98d673\",\"address\":\"4c8ab9d3e938285776d6717d7319f6a9b1d809dd\",\"Crypto\":{\"ciphertext\":\"bb3a6dbf21f0bf2b5eb0b43426590f16650acee9462ab710cca18781691a5739\",\"cipherparams\":{\"iv\":\"6a533f77fc5cb8a752a16ec6a3200da1\"},\"cipher\":\"aes-128-ctr\",\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"salt\":\"a58609853dec53c81feb165e346c700e714285771825bb4cbf87c4ea1996b682\",\"n\":8192,\"r\":8,\"p\":1},\"mac\":\"a71edeb659ed628db13579ce9f75c80c9d386c1239b280548d9a0e58ad20d6c7\"}}""#, r#""123456789""#]; 90 | Value::String("0x4C8aB9d3e938285776d6717d7319F6a9B1d809DD".into()) => "4C8aB9d3e938285776d6717d7319F6a9B1d809DD".parse::
().unwrap() 91 | ); 92 | 93 | rpc_test! ( 94 | ParityAccounts : parity_remove_address, &"9b776baeaf3896657a9ba0db5564623b3e0173e0".parse::
().unwrap() 95 | => "parity_removeAddress", vec![r#""0x9b776baeaf3896657a9ba0db5564623b3e0173e0""#]; 96 | Value::Bool(true) => true 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/api/personal.rs: -------------------------------------------------------------------------------- 1 | //! `Personal` namespace 2 | 3 | use crate::{ 4 | api::Namespace, 5 | helpers::{self, CallFuture}, 6 | types::{Address, Bytes, RawTransaction, TransactionRequest, H256, H520}, 7 | Transport, 8 | }; 9 | 10 | /// `Personal` namespace 11 | #[derive(Debug, Clone)] 12 | pub struct Personal { 13 | transport: T, 14 | } 15 | 16 | impl Namespace for Personal { 17 | fn new(transport: T) -> Self 18 | where 19 | Self: Sized, 20 | { 21 | Personal { transport } 22 | } 23 | 24 | fn transport(&self) -> &T { 25 | &self.transport 26 | } 27 | } 28 | 29 | impl Personal { 30 | /// Returns a list of available accounts. 31 | pub fn list_accounts(&self) -> CallFuture, T::Out> { 32 | CallFuture::new(self.transport.execute("personal_listAccounts", vec![])) 33 | } 34 | 35 | /// Creates a new account and protects it with given password. 36 | /// Returns the address of created account. 37 | pub fn new_account(&self, password: &str) -> CallFuture { 38 | let password = helpers::serialize(&password); 39 | CallFuture::new(self.transport.execute("personal_newAccount", vec![password])) 40 | } 41 | 42 | /// Unlocks the account with given password for some period of time (or single transaction). 43 | /// Returns `true` if the call was successful. 44 | pub fn unlock_account(&self, address: Address, password: &str, duration: Option) -> CallFuture { 45 | let address = helpers::serialize(&address); 46 | let password = helpers::serialize(&password); 47 | let duration = helpers::serialize(&duration); 48 | CallFuture::new( 49 | self.transport 50 | .execute("personal_unlockAccount", vec![address, password, duration]), 51 | ) 52 | } 53 | 54 | /// Sends a transaction from locked account. 55 | /// Returns transaction hash. 56 | pub fn send_transaction(&self, transaction: TransactionRequest, password: &str) -> CallFuture { 57 | let transaction = helpers::serialize(&transaction); 58 | let password = helpers::serialize(&password); 59 | CallFuture::new( 60 | self.transport 61 | .execute("personal_sendTransaction", vec![transaction, password]), 62 | ) 63 | } 64 | 65 | /// Signs an Ethereum specific message with `sign(keccak256("\x19Ethereum Signed Message: " + len(data) + data)))` 66 | /// 67 | /// The account does not need to be unlocked to make this call, and will not be left unlocked after. 68 | /// Returns encoded signature. 69 | pub fn sign(&self, data: Bytes, account: Address, password: &str) -> CallFuture { 70 | let data = helpers::serialize(&data); 71 | let address = helpers::serialize(&account); 72 | let password = helpers::serialize(&password); 73 | CallFuture::new(self.transport.execute("personal_sign", vec![data, address, password])) 74 | } 75 | 76 | /// Signs a transaction without dispatching it to the network. 77 | /// The account does not need to be unlocked to make this call, and will not be left unlocked after. 78 | /// Returns a signed transaction in raw bytes along with it's details. 79 | pub fn sign_transaction( 80 | &self, 81 | transaction: TransactionRequest, 82 | password: &str, 83 | ) -> CallFuture { 84 | let transaction = helpers::serialize(&transaction); 85 | let password = helpers::serialize(&password); 86 | CallFuture::new( 87 | self.transport 88 | .execute("personal_signTransaction", vec![transaction, password]), 89 | ) 90 | } 91 | 92 | /// Imports a raw key and protects it with the given password. 93 | /// Returns the address of created account. 94 | pub fn import_raw_key(&self, private_key: &[u8; 32], password: &str) -> CallFuture { 95 | let private_key = hex::encode(private_key); 96 | let private_key = helpers::serialize(&private_key); 97 | let password = helpers::serialize(&password); 98 | 99 | CallFuture::new( 100 | self.transport 101 | .execute("personal_importRawKey", vec![private_key, password]), 102 | ) 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::Personal; 109 | use crate::{ 110 | api::Namespace, 111 | rpc::Value, 112 | types::{Address, Bytes, RawTransaction, TransactionRequest, H160, H520}, 113 | }; 114 | use hex_literal::hex; 115 | 116 | const EXAMPLE_TX: &str = r#"{ 117 | "raw": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", 118 | "tx": { 119 | "hash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", 120 | "nonce": "0x0", 121 | "blockHash": "0xbeab0aa2411b7ab17f30a99d3cb9c6ef2fc5426d6ad6fd9e2a26a6aed1d1055b", 122 | "blockNumber": "0x15df", 123 | "transactionIndex": "0x1", 124 | "from": "0x407d73d8a49eeb85d32cf465507dd71d507100c1", 125 | "to": "0x853f43d8a49eeb85d32cf465507dd71d507100c1", 126 | "value": "0x7f110", 127 | "gas": "0x7f110", 128 | "gasPrice": "0x09184e72a000", 129 | "input": "0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360" 130 | } 131 | }"#; 132 | 133 | rpc_test! ( 134 | Personal:list_accounts => "personal_listAccounts"; 135 | Value::Array(vec![Value::String("0x0000000000000000000000000000000000000123".into())]) => vec![Address::from_low_u64_be(0x123)] 136 | ); 137 | 138 | rpc_test! ( 139 | Personal:new_account, "hunter2" => "personal_newAccount", vec![r#""hunter2""#]; 140 | Value::String("0x0000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123) 141 | ); 142 | 143 | rpc_test! ( 144 | Personal:unlock_account, Address::from_low_u64_be(0x123), "hunter2", None 145 | => 146 | "personal_unlockAccount", vec![r#""0x0000000000000000000000000000000000000123""#, r#""hunter2""#, r#"null"#]; 147 | Value::Bool(true) => true 148 | ); 149 | 150 | rpc_test! ( 151 | Personal:send_transaction, TransactionRequest { 152 | from: Address::from_low_u64_be(0x123), to: Some(Address::from_low_u64_be(0x123)), 153 | gas: None, gas_price: Some(0x1.into()), 154 | value: Some(0x1.into()), data: None, 155 | nonce: None, condition: None, 156 | transaction_type: None, access_list: None, 157 | max_fee_per_gas: None, max_priority_fee_per_gas: None, 158 | }, "hunter2" 159 | => 160 | "personal_sendTransaction", vec![r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#""hunter2""#]; 161 | Value::String("0x0000000000000000000000000000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123) 162 | ); 163 | 164 | rpc_test! ( 165 | Personal:sign_transaction, TransactionRequest { 166 | from: hex!("407d73d8a49eeb85d32cf465507dd71d507100c1").into(), 167 | to: Some(hex!("853f43d8a49eeb85d32cf465507dd71d507100c1").into()), 168 | gas: Some(0x7f110.into()), 169 | gas_price: Some(0x09184e72a000u64.into()), 170 | value: Some(0x7f110.into()), 171 | data: Some(hex!("603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360").into()), 172 | nonce: Some(0x0.into()), 173 | condition: None, 174 | transaction_type: None, 175 | access_list: None, 176 | max_fee_per_gas: None, 177 | max_priority_fee_per_gas: None, 178 | }, "hunter2" 179 | => 180 | "personal_signTransaction", vec![r#"{"data":"0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360","from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","gas":"0x7f110","gasPrice":"0x9184e72a000","nonce":"0x0","to":"0x853f43d8a49eeb85d32cf465507dd71d507100c1","value":"0x7f110"}"#, r#""hunter2""#]; 181 | ::serde_json::from_str(EXAMPLE_TX).unwrap() 182 | => ::serde_json::from_str::(EXAMPLE_TX).unwrap() 183 | ); 184 | 185 | rpc_test! { 186 | Personal:import_raw_key, &[0u8; 32], "hunter2" => 187 | "personal_importRawKey", vec![r#""0000000000000000000000000000000000000000000000000000000000000000""#, r#""hunter2""#]; 188 | Value::String("0x0000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123) 189 | } 190 | 191 | rpc_test! { 192 | Personal:sign, Bytes(hex!("7f0d39b8347598e20466233ce2fb3e824f0f93dfbf233125d3ab09b172c62591ec24dc84049242e364895c3abdbbd843d4a0a188").to_vec()), H160(hex!("7f0d39b8347598e20466233ce2fb3e824f0f93df")), "hunter2" 193 | => 194 | "personal_sign", vec![r#""0x7f0d39b8347598e20466233ce2fb3e824f0f93dfbf233125d3ab09b172c62591ec24dc84049242e364895c3abdbbd843d4a0a188""#, r#""0x7f0d39b8347598e20466233ce2fb3e824f0f93df""#, r#""hunter2""#]; 195 | Value::String("0xdac1cba443d72e2088ed0cd2e6608ce696eb4728caf119dcfeea752f57a1163274de0b25007aa70201d0d80190071b26be2287b4a473767e5f7bc443c080b4fc1c".into()) => H520(hex!("dac1cba443d72e2088ed0cd2e6608ce696eb4728caf119dcfeea752f57a1163274de0b25007aa70201d0d80190071b26be2287b4a473767e5f7bc443c080b4fc1c")) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/api/txpool.rs: -------------------------------------------------------------------------------- 1 | //! `Txpool` namespace 2 | 3 | use crate::{ 4 | api::Namespace, 5 | helpers::CallFuture, 6 | types::{TxpoolContentInfo, TxpoolInspectInfo, TxpoolStatus}, 7 | Transport, 8 | }; 9 | 10 | /// `Txpool` namespace 11 | #[derive(Debug, Clone)] 12 | pub struct Txpool { 13 | transport: T, 14 | } 15 | 16 | impl Namespace for Txpool { 17 | fn new(transport: T) -> Self 18 | where 19 | Self: Sized, 20 | { 21 | Txpool { transport } 22 | } 23 | 24 | fn transport(&self) -> &T { 25 | &self.transport 26 | } 27 | } 28 | 29 | impl Txpool { 30 | /// returns txpool content info 31 | pub fn content(&self) -> CallFuture { 32 | CallFuture::new(self.transport.execute("txpool_content", vec![])) 33 | } 34 | 35 | /// returns txpool inspect info 36 | pub fn inspect(&self) -> CallFuture { 37 | CallFuture::new(self.transport.execute("txpool_inspect", vec![])) 38 | } 39 | 40 | /// returns txpool status 41 | pub fn status(&self) -> CallFuture { 42 | CallFuture::new(self.transport.execute("txpool_status", vec![])) 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::Txpool; 49 | use crate::{ 50 | api::Namespace, 51 | types::{TxpoolContentInfo, TxpoolInspectInfo, TxpoolStatus}, 52 | }; 53 | 54 | const EXAMPLE_CONTENT_INFO: &str = r#"{ 55 | "pending": { 56 | "0x0216d5032f356960cd3749c31ab34eeff21b3395": { 57 | "806": { 58 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 59 | "blockNumber": null, 60 | "from": "0x0216d5032f356960cd3749c31ab34eeff21b3395", 61 | "gas": "0x5208", 62 | "gasPrice": "0xba43b7400", 63 | "hash": "0xaf953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08586", 64 | "input": "0x", 65 | "nonce": "0x326", 66 | "to": "0x7f69a91a3cf4be60020fb58b893b7cbb65376db8", 67 | "transactionIndex": null, 68 | "value": "0x19a99f0cf456000" 69 | } 70 | }, 71 | "0x24d407e5a0b506e1cb2fae163100b5de01f5193c": { 72 | "34": { 73 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 74 | "blockNumber": null, 75 | "from": "0x24d407e5a0b506e1cb2fae163100b5de01f5193c", 76 | "gas": "0x44c72", 77 | "gasPrice": "0x4a817c800", 78 | "hash": "0xb5b8b853af32226755a65ba0602f7ed0e8be2211516153b75e9ed640a7d359fe", 79 | "input": "0xb61d27f600000000000000000000000024d407e5a0b506e1cb2fae163100b5de01f5193c00000000000000000000000000000000000000000000000053444835ec580000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 80 | "nonce": "0x22", 81 | "to": "0x7320785200f74861b69c49e4ab32399a71b34f1a", 82 | "transactionIndex": null, 83 | "value": "0x0" 84 | } 85 | } 86 | }, 87 | "queued": { 88 | "0x976a3fc5d6f7d259ebfb4cc2ae75115475e9867c": { 89 | "3": { 90 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 91 | "blockNumber": null, 92 | "from": "0x976a3fc5d6f7d259ebfb4cc2ae75115475e9867c", 93 | "gas": "0x15f90", 94 | "gasPrice": "0x4a817c800", 95 | "hash": "0x57b30c59fc39a50e1cba90e3099286dfa5aaf60294a629240b5bbec6e2e66576", 96 | "input": "0x", 97 | "nonce": "0x3", 98 | "to": "0x346fb27de7e7370008f5da379f74dd49f5f2f80f", 99 | "transactionIndex": null, 100 | "value": "0x1f161421c8e0000" 101 | } 102 | }, 103 | "0x9b11bf0459b0c4b2f87f8cebca4cfc26f294b63a": { 104 | "2": { 105 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 106 | "blockNumber": null, 107 | "from": "0x9b11bf0459b0c4b2f87f8cebca4cfc26f294b63a", 108 | "gas": "0x15f90", 109 | "gasPrice": "0xba43b7400", 110 | "hash": "0x3a3c0698552eec2455ed3190eac3996feccc806970a4a056106deaf6ceb1e5e3", 111 | "input": "0x", 112 | "nonce": "0x2", 113 | "to": "0x24a461f25ee6a318bdef7f33de634a67bb67ac9d", 114 | "transactionIndex": null, 115 | "value": "0xebec21ee1da40000" 116 | }, 117 | "6": { 118 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 119 | "blockNumber": null, 120 | "from": "0x9b11bf0459b0c4b2f87f8cebca4cfc26f294b63a", 121 | "gas": "0x15f90", 122 | "gasPrice": "0x4a817c800", 123 | "hash": "0xbbcd1e45eae3b859203a04be7d6e1d7b03b222ec1d66dfcc8011dd39794b147e", 124 | "input": "0x", 125 | "nonce": "0x6", 126 | "to": "0x6368f3f8c2b42435d6c136757382e4a59436a681", 127 | "transactionIndex": null, 128 | "value": "0xf9a951af55470000" 129 | } 130 | } 131 | } 132 | }"#; 133 | 134 | const EXAMPLE_INSPECT_INFO: &str = r#"{ 135 | "pending": { 136 | "0x26588a9301b0428d95e6fc3a5024fce8bec12d51": { 137 | "31813": "0x3375ee30428b2a71c428afa5e89e427905f95f7e: 0 wei + 500000 × 20000000000 gas" 138 | }, 139 | "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { 140 | "563662": "0x958c1fa64b34db746925c6f8a3dd81128e40355e: 1051546810000000000 wei + 90000 × 20000000000 gas", 141 | "563663": "0x77517b1491a0299a44d668473411676f94e97e34: 1051190740000000000 wei + 90000 × 20000000000 gas", 142 | "563664": "0x3e2a7fe169c8f8eee251bb00d9fb6d304ce07d3a: 1050828950000000000 wei + 90000 × 20000000000 gas", 143 | "563665": "0xaf6c4695da477f8c663ea2d8b768ad82cb6a8522: 1050544770000000000 wei + 90000 × 20000000000 gas", 144 | "563666": "0x139b148094c50f4d20b01caf21b85edb711574db: 1048598530000000000 wei + 90000 × 20000000000 gas", 145 | "563667": "0x48b3bd66770b0d1eecefce090dafee36257538ae: 1048367260000000000 wei + 90000 × 20000000000 gas", 146 | "563668": "0x468569500925d53e06dd0993014ad166fd7dd381: 1048126690000000000 wei + 90000 × 20000000000 gas", 147 | "563669": "0x3dcb4c90477a4b8ff7190b79b524773cbe3be661: 1047965690000000000 wei + 90000 × 20000000000 gas", 148 | "563670": "0x6dfef5bc94b031407ffe71ae8076ca0fbf190963: 1047859050000000000 wei + 90000 × 20000000000 gas" 149 | }, 150 | "0x9174e688d7de157c5c0583df424eaab2676ac162": { 151 | "3": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413: 30000000000000000000 wei + 85000 × 21000000000 gas" 152 | }, 153 | "0xb18f9d01323e150096650ab989cfecd39d757aec": { 154 | "777": "0xcd79c72690750f079ae6ab6ccd7e7aedc03c7720: 0 wei + 1000000 × 20000000000 gas" 155 | }, 156 | "0xb2916c870cf66967b6510b76c07e9d13a5d23514": { 157 | "2": "0x576f25199d60982a8f31a8dff4da8acb982e6aba: 26000000000000000000 wei + 90000 × 20000000000 gas" 158 | }, 159 | "0xbc0ca4f217e052753614d6b019948824d0d8688b": { 160 | "0": "0x2910543af39aba0cd09dbb2d50200b3e800a63d2: 1000000000000000000 wei + 50000 × 1171602790622 gas" 161 | }, 162 | "0xea674fdde714fd979de3edf0f56aa9716b898ec8": { 163 | "70148": "0xe39c55ead9f997f7fa20ebe40fb4649943d7db66: 1000767667434026200 wei + 90000 × 20000000000 gas" 164 | } 165 | }, 166 | "queued": { 167 | "0x0f6000de1578619320aba5e392706b131fb1de6f": { 168 | "6": "0x8383534d0bcd0186d326c993031311c0ac0d9b2d: 9000000000000000000 wei + 21000 × 20000000000 gas" 169 | }, 170 | "0x5b30608c678e1ac464a8994c3b33e5cdf3497112": { 171 | "6": "0x9773547e27f8303c87089dc42d9288aa2b9d8f06: 50000000000000000000 wei + 90000 × 50000000000 gas" 172 | }, 173 | "0x976a3fc5d6f7d259ebfb4cc2ae75115475e9867c": { 174 | "3": "0x346fb27de7e7370008f5da379f74dd49f5f2f80f: 140000000000000000 wei + 90000 × 20000000000 gas" 175 | }, 176 | "0x9b11bf0459b0c4b2f87f8cebca4cfc26f294b63a": { 177 | "2": "0x24a461f25ee6a318bdef7f33de634a67bb67ac9d: 17000000000000000000 wei + 90000 × 50000000000 gas", 178 | "6": "0x6368f3f8c2b42435d6c136757382e4a59436a681: 17990000000000000000 wei + 90000 × 20000000000 gas", 179 | "7": "0x6368f3f8c2b42435d6c136757382e4a59436a681: 17900000000000000000 wei + 90000 × 20000000000 gas" 180 | } 181 | } 182 | }"#; 183 | 184 | const EXAMPLE_STATUS: &str = r#"{ 185 | "pending": "0xa", 186 | "queued": "0x7" 187 | }"#; 188 | 189 | rpc_test! ( 190 | Txpool:content => "txpool_content"; 191 | ::serde_json::from_str(EXAMPLE_CONTENT_INFO).unwrap() 192 | => ::serde_json::from_str::(EXAMPLE_CONTENT_INFO).unwrap() 193 | ); 194 | 195 | rpc_test! ( 196 | Txpool:inspect => "txpool_inspect"; 197 | ::serde_json::from_str(EXAMPLE_INSPECT_INFO).unwrap() 198 | => ::serde_json::from_str::(EXAMPLE_INSPECT_INFO).unwrap() 199 | ); 200 | 201 | rpc_test! ( 202 | Txpool:status => "txpool_status"; 203 | ::serde_json::from_str(EXAMPLE_STATUS).unwrap() 204 | => ::serde_json::from_str::(EXAMPLE_STATUS).unwrap() 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /src/api/web3.rs: -------------------------------------------------------------------------------- 1 | //! `Web3` namespace 2 | 3 | use crate::{ 4 | api::Namespace, 5 | helpers::{self, CallFuture}, 6 | types::{Bytes, H256}, 7 | Transport, 8 | }; 9 | 10 | /// `Web3` namespace 11 | #[derive(Debug, Clone)] 12 | pub struct Web3 { 13 | transport: T, 14 | } 15 | 16 | impl Namespace for Web3 { 17 | fn new(transport: T) -> Self 18 | where 19 | Self: Sized, 20 | { 21 | Web3 { transport } 22 | } 23 | 24 | fn transport(&self) -> &T { 25 | &self.transport 26 | } 27 | } 28 | 29 | impl Web3 { 30 | /// Returns client version 31 | pub fn client_version(&self) -> CallFuture { 32 | CallFuture::new(self.transport.execute("web3_clientVersion", vec![])) 33 | } 34 | 35 | /// Returns sha3 of the given data 36 | pub fn sha3(&self, bytes: Bytes) -> CallFuture { 37 | let bytes = helpers::serialize(&bytes); 38 | CallFuture::new(self.transport.execute("web3_sha3", vec![bytes])) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::Web3; 45 | use crate::{api::Namespace, rpc::Value, types::H256}; 46 | use hex_literal::hex; 47 | 48 | rpc_test! ( 49 | Web3:client_version => "web3_clientVersion"; 50 | Value::String("Test123".into()) => "Test123" 51 | ); 52 | 53 | rpc_test! ( 54 | Web3:sha3, hex!("01020304") 55 | => 56 | "web3_sha3", vec![r#""0x01020304""#]; 57 | Value::String("0x0000000000000000000000000000000000000000000000000000000000000123".into()) => H256::from_low_u64_be(0x123) 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/confirm.rs: -------------------------------------------------------------------------------- 1 | //! Easy to use utilities for confirmations. 2 | 3 | use crate::{ 4 | api::{Eth, EthFilter, Namespace}, 5 | error, 6 | types::{Bytes, TransactionReceipt, TransactionRequest, H256, U64}, 7 | Transport, 8 | }; 9 | use futures::{Future, StreamExt}; 10 | use std::time::Duration; 11 | 12 | /// Checks whether an event has been confirmed. 13 | pub trait ConfirmationCheck { 14 | /// Future resolved when is known whether an event has been confirmed. 15 | type Check: Future>>; 16 | 17 | /// Should be called to get future which resolves when confirmation state is known. 18 | fn check(&self) -> Self::Check; 19 | } 20 | 21 | impl ConfirmationCheck for F 22 | where 23 | F: Fn() -> T, 24 | T: Future>>, 25 | { 26 | type Check = T; 27 | 28 | fn check(&self) -> Self::Check { 29 | (*self)() 30 | } 31 | } 32 | 33 | /// Should be used to wait for confirmations 34 | pub async fn wait_for_confirmations( 35 | eth: Eth, 36 | eth_filter: EthFilter, 37 | poll_interval: Duration, 38 | confirmations: usize, 39 | check: V, 40 | ) -> error::Result<()> 41 | where 42 | T: Transport, 43 | V: ConfirmationCheck, 44 | F: Future>>, 45 | { 46 | let filter = eth_filter.create_blocks_filter().await?; 47 | // TODO #396: The stream should have additional checks. 48 | // * We should not continue calling next on a stream that has completed (has returned None). We expect this to never 49 | // happen for the blocks filter but to be safe we should handle this case for example by `fuse`ing the stream or 50 | // erroring when it does complete. 51 | // * We do not handle the case where the stream returns an error which means we are wrongly counting it as a 52 | // confirmation. 53 | let filter_stream = filter.stream(poll_interval).skip(confirmations); 54 | futures::pin_mut!(filter_stream); 55 | loop { 56 | let _ = filter_stream.next().await; 57 | if let Some(confirmation_block_number) = check.check().await? { 58 | let block_number = eth.block_number().await?; 59 | if confirmation_block_number.low_u64() + confirmations as u64 <= block_number.low_u64() { 60 | return Ok(()); 61 | } 62 | } 63 | } 64 | } 65 | 66 | async fn transaction_receipt_block_number_check(eth: &Eth, hash: H256) -> error::Result> { 67 | let receipt = eth.transaction_receipt(hash).await?; 68 | Ok(receipt.and_then(|receipt| receipt.block_number)) 69 | } 70 | 71 | async fn send_transaction_with_confirmation_( 72 | hash: H256, 73 | transport: T, 74 | poll_interval: Duration, 75 | confirmations: usize, 76 | ) -> error::Result { 77 | let eth = Eth::new(transport.clone()); 78 | if confirmations > 0 { 79 | let confirmation_check = || transaction_receipt_block_number_check(ð, hash); 80 | let eth_filter = EthFilter::new(transport.clone()); 81 | let eth = eth.clone(); 82 | wait_for_confirmations(eth, eth_filter, poll_interval, confirmations, confirmation_check).await?; 83 | } 84 | // TODO #397: We should remove this `expect`. No matter what happens inside the node, this shouldn't be a panic. 85 | let receipt = eth 86 | .transaction_receipt(hash) 87 | .await? 88 | .expect("receipt can't be null after wait for confirmations; qed"); 89 | 90 | Ok(receipt) 91 | } 92 | 93 | /// Sends transaction and returns future resolved after transaction is confirmed 94 | pub async fn send_transaction_with_confirmation( 95 | transport: T, 96 | tx: TransactionRequest, 97 | poll_interval: Duration, 98 | confirmations: usize, 99 | ) -> error::Result 100 | where 101 | T: Transport, 102 | { 103 | let hash = Eth::new(&transport).send_transaction(tx).await?; 104 | send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await 105 | } 106 | 107 | /// Sends raw transaction and returns future resolved after transaction is confirmed 108 | pub async fn send_raw_transaction_with_confirmation( 109 | transport: T, 110 | tx: Bytes, 111 | poll_interval: Duration, 112 | confirmations: usize, 113 | ) -> error::Result 114 | where 115 | T: Transport, 116 | { 117 | let hash = Eth::new(&transport).send_raw_transaction(tx).await?; 118 | send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::send_transaction_with_confirmation; 124 | use crate::{ 125 | rpc::Value, 126 | transports::test::TestTransport, 127 | types::{Address, TransactionReceipt, TransactionRequest, H256, U64}, 128 | }; 129 | use serde_json::json; 130 | use std::time::Duration; 131 | 132 | #[test] 133 | fn test_send_transaction_with_confirmation() { 134 | let mut transport = TestTransport::default(); 135 | let confirmations = 3; 136 | let transaction_request = TransactionRequest { 137 | from: Address::from_low_u64_be(0x123), 138 | to: Some(Address::from_low_u64_be(0x123)), 139 | gas: None, 140 | gas_price: Some(1.into()), 141 | value: Some(1.into()), 142 | data: None, 143 | nonce: None, 144 | condition: None, 145 | transaction_type: None, 146 | access_list: None, 147 | max_fee_per_gas: None, 148 | max_priority_fee_per_gas: None, 149 | }; 150 | 151 | let transaction_receipt = TransactionReceipt { 152 | transaction_hash: H256::zero(), 153 | transaction_index: U64::zero(), 154 | block_hash: Some(H256::zero()), 155 | block_number: Some(2.into()), 156 | from: Address::from_low_u64_be(0x123), 157 | to: Some(Address::from_low_u64_be(0x123)), 158 | cumulative_gas_used: 0.into(), 159 | gas_used: Some(0.into()), 160 | contract_address: None, 161 | logs: vec![], 162 | status: Some(1.into()), 163 | root: Some(H256::zero()), 164 | logs_bloom: Default::default(), 165 | transaction_type: None, 166 | effective_gas_price: Default::default(), 167 | revert_reason: None, 168 | }; 169 | 170 | let poll_interval = Duration::from_secs(0); 171 | transport.add_response(Value::String( 172 | r#"0x0000000000000000000000000000000000000000000000000000000000000111"#.into(), 173 | )); 174 | transport.add_response(Value::String("0x123".into())); 175 | transport.add_response(Value::Array(vec![ 176 | Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000456"#.into()), 177 | Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000457"#.into()), 178 | ])); 179 | transport.add_response(Value::Array(vec![Value::String( 180 | r#"0x0000000000000000000000000000000000000000000000000000000000000458"#.into(), 181 | )])); 182 | transport.add_response(Value::Array(vec![Value::String( 183 | r#"0x0000000000000000000000000000000000000000000000000000000000000459"#.into(), 184 | )])); 185 | transport.add_response(Value::Null); 186 | transport.add_response(Value::Array(vec![ 187 | Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000460"#.into()), 188 | Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000461"#.into()), 189 | ])); 190 | transport.add_response(Value::Null); 191 | transport.add_response(json!(transaction_receipt)); 192 | transport.add_response(Value::String("0x6".into())); 193 | transport.add_response(json!(transaction_receipt)); 194 | transport.add_response(Value::Bool(true)); 195 | 196 | let confirmation = { 197 | let future = 198 | send_transaction_with_confirmation(&transport, transaction_request, poll_interval, confirmations); 199 | futures::executor::block_on(future) 200 | }; 201 | 202 | transport.assert_request("eth_sendTransaction", &[r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#.into()]); 203 | transport.assert_request("eth_newBlockFilter", &[]); 204 | transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]); 205 | transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]); 206 | transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]); 207 | transport.assert_request( 208 | "eth_getTransactionReceipt", 209 | &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()], 210 | ); 211 | transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]); 212 | transport.assert_request( 213 | "eth_getTransactionReceipt", 214 | &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()], 215 | ); 216 | transport.assert_request( 217 | "eth_getTransactionReceipt", 218 | &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()], 219 | ); 220 | transport.assert_request("eth_blockNumber", &[]); 221 | transport.assert_request( 222 | "eth_getTransactionReceipt", 223 | &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()], 224 | ); 225 | transport.assert_no_more_requests(); 226 | assert_eq!(confirmation, Ok(transaction_receipt)); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/contract/ens/DefaultReverseResolver.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"contract ENS","name":"ensAddr","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":true,"inputs":[],"name":"ens","outputs":[{"internalType":"contract ENS","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"_name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /src/contract/ens/ENSRegistry.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"contract ENS","name":"_old","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"label","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"old","outputs":[{"internalType":"contract ENS","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"recordExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setSubnodeRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /src/contract/ens/PublicResolver.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /src/contract/ens/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum Name Service Interface 2 | //! 3 | //! This interface provides most functions implemented in ENS. 4 | //! With it you can resolve ethereum addresses to domain names, domain name to blockchain addresses and more! 5 | //! 6 | //! # Example 7 | //! ```no_run 8 | //! ##[tokio::main] 9 | //! async fn main() -> web3::Result<()> { 10 | //! use crate::web3::api::Namespace; 11 | //! 12 | //! let transport = web3::transports::Http::new("http://localhost:8545")?; 13 | //! 14 | //! let ens = web3::contract::ens::Ens::new(transport); 15 | //! 16 | //! let address = ens.eth_address("vitalik.eth").await.unwrap(); 17 | //! 18 | //! println!("Address: {:?}", address); 19 | //! 20 | //! Ok(()) 21 | //! } 22 | //! ``` 23 | 24 | mod eth_ens; 25 | pub mod public_resolver; 26 | pub mod registry; 27 | pub mod reverse_resolver; 28 | 29 | pub use eth_ens::Ens; 30 | -------------------------------------------------------------------------------- /src/contract/ens/registry.rs: -------------------------------------------------------------------------------- 1 | //! ENS Registry contract interface. 2 | 3 | use crate::{ 4 | api::Eth, 5 | contract::{Contract, Options}, 6 | signing::NameHash, 7 | types::{Address, TransactionId}, 8 | Transport, 9 | }; 10 | 11 | type ContractError = crate::contract::Error; 12 | 13 | const ENS_REGISTRY_ADDRESS: &str = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; 14 | 15 | /// The ENS registry is the core contract that lies at the heart of ENS resolution. 16 | /// 17 | /// All ENS lookups start by querying the registry. 18 | /// The registry maintains a list of domains, recording the owner, resolver, and TTL for each, and allows the owner of a domain to make changes to that data. 19 | /// 20 | /// The ENS registry is specified in [EIP 137](https://eips.ethereum.org/EIPS/eip-137). 21 | /// 22 | /// [Source](https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol) 23 | #[derive(Debug, Clone)] 24 | pub struct Registry { 25 | contract: Contract, 26 | } 27 | 28 | impl Registry { 29 | /// Creates new instance of [`Registry`]. 30 | pub fn new(eth: Eth) -> Self { 31 | let address = ENS_REGISTRY_ADDRESS.parse().expect("Parsing Address"); 32 | 33 | // See https://github.com/ensdomains/ens-contracts for up to date contracts. 34 | let json = include_bytes!("ENSRegistry.json"); 35 | 36 | let contract = Contract::from_json(eth, address, json).expect("Contract Creation"); 37 | 38 | Self { contract } 39 | } 40 | } 41 | 42 | impl Registry { 43 | /// Returns the owner of the name specified by node. 44 | /// 45 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#get-owner) 46 | pub async fn owner(&self, node: NameHash) -> Result { 47 | let options = Options::default(); 48 | 49 | self.contract.query("owner", node, None, options, None).await 50 | } 51 | 52 | /// Returns the address of the resolver responsible for the name specified by node. 53 | /// 54 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#get-resolver) 55 | pub async fn resolver(&self, node: NameHash) -> Result { 56 | let options = Options::default(); 57 | 58 | self.contract.query("resolver", node, None, options, None).await 59 | } 60 | 61 | /// Returns the caching time-to-live of the name specified by node. 62 | /// 63 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#get-ttl) 64 | pub async fn ttl(&self, node: NameHash) -> Result { 65 | let options = Options::default(); 66 | 67 | self.contract.query("ttl", node, None, options, None).await 68 | } 69 | 70 | /// Reassigns ownership of the name identified by node to owner. 71 | /// 72 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-owner) 73 | pub async fn set_owner( 74 | &self, 75 | from: Address, 76 | node: NameHash, 77 | owner: Address, 78 | ) -> Result { 79 | let options = Options::default(); 80 | 81 | let id = self.contract.call("setOwner", (node, owner), from, options).await?; 82 | 83 | Ok(TransactionId::Hash(id)) 84 | } 85 | 86 | /// Updates the resolver associated with the name identified by node to resolver. 87 | /// 88 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-resolver) 89 | pub async fn set_resolver( 90 | &self, 91 | from: Address, 92 | node: NameHash, 93 | resolver: Address, 94 | ) -> Result { 95 | let options = Options::default(); 96 | 97 | let id = self 98 | .contract 99 | .call("setResolver", (node, resolver), from, options) 100 | .await?; 101 | 102 | Ok(TransactionId::Hash(id)) 103 | } 104 | 105 | /// Updates the caching time-to-live of the name identified by node. 106 | /// 107 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-ttl) 108 | pub async fn set_ttl(&self, from: Address, node: NameHash, ttl: u64) -> Result { 109 | let options = Options::default(); 110 | 111 | let id = self.contract.call("setTTL", (node, ttl), from, options).await?; 112 | 113 | Ok(TransactionId::Hash(id)) 114 | } 115 | 116 | /// Creates a new subdomain of node, assigning ownership of it to the specified owner. 117 | /// 118 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-subdomain-owner) 119 | pub async fn set_subnode_owner( 120 | &self, 121 | from: Address, 122 | node: NameHash, 123 | label: [u8; 32], 124 | owner: Address, 125 | ) -> Result { 126 | let options = Options::default(); 127 | 128 | let id = self 129 | .contract 130 | .call("setSubnodeOwner", (node, label, owner), from, options) 131 | .await?; 132 | 133 | Ok(TransactionId::Hash(id)) 134 | } 135 | 136 | /// Sets the owner, resolver, and TTL for an ENS record in a single operation. 137 | /// 138 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-record) 139 | pub async fn set_record( 140 | &self, 141 | from: Address, 142 | node: NameHash, 143 | owner: Address, 144 | resolver: Address, 145 | ttl: u64, 146 | ) -> Result { 147 | let options = Options::default(); 148 | 149 | let id = self 150 | .contract 151 | .call("setRecord", (node, owner, resolver, ttl), from, options) 152 | .await?; 153 | 154 | Ok(TransactionId::Hash(id)) 155 | } 156 | 157 | /// Sets the owner, resolver and TTL for a subdomain, creating it if necessary. 158 | /// 159 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-subdomain-record) 160 | pub async fn set_subnode_record( 161 | &self, 162 | from: Address, 163 | node: NameHash, 164 | label: [u8; 32], 165 | owner: Address, 166 | resolver: Address, 167 | ttl: u64, 168 | ) -> Result { 169 | let options = Options::default(); 170 | 171 | let id = self 172 | .contract 173 | .call("setSubnodeRecord", (node, label, owner, resolver, ttl), from, options) 174 | .await?; 175 | 176 | Ok(TransactionId::Hash(id)) 177 | } 178 | 179 | /// Sets or clears an approval. 180 | /// 181 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#set-approval) 182 | pub async fn set_approval_for_all( 183 | &self, 184 | from: Address, 185 | operator: Address, 186 | approved: bool, 187 | ) -> Result { 188 | let options = Options::default(); 189 | 190 | let id = self 191 | .contract 192 | .call("setApprovalForAll", (operator, approved), from, options) 193 | .await?; 194 | 195 | Ok(TransactionId::Hash(id)) 196 | } 197 | 198 | /// Returns true if operator is approved to make ENS registry operations on behalf of owner. 199 | /// 200 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#check-approval) 201 | pub async fn check_approval(&self, owner: Address, operator: Address) -> Result { 202 | let options = Options::default(); 203 | 204 | self.contract 205 | .query("isApprovedForAll", (owner, operator), None, options, None) 206 | .await 207 | } 208 | 209 | /// Returns true if node exists in this ENS registry. 210 | /// 211 | /// [Specification](https://docs.ens.domains/contract-api-reference/ens#check-record-existence) 212 | pub async fn check_record_existence(&self, node: NameHash) -> Result { 213 | let options = Options::default(); 214 | 215 | self.contract.query("recordExists", node, None, options, None).await 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/contract/ens/reverse_resolver.rs: -------------------------------------------------------------------------------- 1 | //! Reverse Resolver ENS contract interface. 2 | 3 | use crate::{ 4 | api::Eth, 5 | contract::{Contract, Options}, 6 | signing::NameHash, 7 | types::{Address, TransactionId}, 8 | Transport, 9 | }; 10 | 11 | type ContractError = crate::contract::Error; 12 | 13 | /// Reverse resolution in ENS - the process of mapping from an Ethereum address (eg, 0x1234...) to an ENS name - is handled using a special namespace, *.addr.reverse*. 14 | /// A special-purpose registrar controls this namespace and allocates subdomains to any caller based on their address. 15 | /// 16 | /// For example, the account *0x314159265dd8dbb310642f98f50c066173c1259b* can claim *314159265dd8dbb310642f98f50c066173c1259b.addr.reverse*. After doing so, it can configure a resolver and expose metadata, such as a canonical ENS name for this address. 17 | /// 18 | /// The reverse registrar provides functions to claim a reverse record, as well as a convenience function to configure the record as it's most commonly used, as a way of specifying a canonical name for an address. 19 | /// 20 | /// The reverse registrar is specified in [EIP 181](https://eips.ethereum.org/EIPS/eip-181). 21 | /// 22 | /// [Source](https://github.com/ensdomains/ens/blob/master/contracts/ReverseRegistrar.sol) 23 | #[derive(Debug, Clone)] 24 | pub struct ReverseResolver { 25 | contract: Contract, 26 | } 27 | 28 | impl ReverseResolver { 29 | /// Creates new instance of [`ReverseResolver`] given contract address. 30 | pub fn new(eth: Eth, resolver_addr: Address) -> Self { 31 | // See https://github.com/ensdomains/ens-contracts for up to date contracts. 32 | let bytes = include_bytes!("DefaultReverseResolver.json"); 33 | 34 | let contract = Contract::from_json(eth, resolver_addr, bytes).expect("Contract Creation Failed"); 35 | 36 | Self { contract } 37 | } 38 | } 39 | 40 | impl ReverseResolver { 41 | /// Returns the canonical ENS name associated with the provided node. 42 | pub async fn canonical_name(&self, node: NameHash) -> Result { 43 | let options = Options::default(); 44 | 45 | self.contract.query("name", node, None, options, None).await 46 | } 47 | 48 | /// Sets the canonical ENS name for the provided node to name. 49 | pub async fn set_canonical_name( 50 | &self, 51 | from: Address, 52 | node: NameHash, 53 | name: String, 54 | ) -> Result { 55 | let options = Options::default(); 56 | 57 | let id = self.contract.call("setName", (node, name), from, options).await?; 58 | 59 | Ok(TransactionId::Hash(id)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/contract/error.rs: -------------------------------------------------------------------------------- 1 | //! Contract call/query error. 2 | 3 | use crate::error::Error as ApiError; 4 | use derive_more::{Display, From}; 5 | use ethabi::Error as EthError; 6 | 7 | /// Contract error. 8 | #[derive(Debug, Display, From)] 9 | pub enum Error { 10 | /// invalid output type requested by the caller 11 | #[display(fmt = "Invalid output type: {}", _0)] 12 | InvalidOutputType(String), 13 | /// eth abi error 14 | #[display(fmt = "Abi error: {}", _0)] 15 | Abi(EthError), 16 | /// Rpc error 17 | #[display(fmt = "Api error: {}", _0)] 18 | Api(ApiError), 19 | /// An error during deployment. 20 | #[display(fmt = "Deployment error: {}", _0)] 21 | Deployment(crate::contract::deploy::Error), 22 | /// Contract does not support this interface. 23 | InterfaceUnsupported, 24 | } 25 | 26 | impl std::error::Error for Error { 27 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 28 | match *self { 29 | Error::InvalidOutputType(_) => None, 30 | Error::Abi(ref e) => Some(e), 31 | Error::Api(ref e) => Some(e), 32 | Error::Deployment(ref e) => Some(e), 33 | Error::InterfaceUnsupported => None, 34 | } 35 | } 36 | } 37 | 38 | pub mod deploy { 39 | use crate::{error::Error as ApiError, types::H256}; 40 | use derive_more::{Display, From}; 41 | 42 | /// Contract deployment error. 43 | #[derive(Debug, Display, From)] 44 | pub enum Error { 45 | /// eth abi error 46 | #[display(fmt = "Abi error: {}", _0)] 47 | Abi(ethabi::Error), 48 | /// Rpc error 49 | #[display(fmt = "Api error: {}", _0)] 50 | Api(ApiError), 51 | /// Contract deployment failed 52 | #[display(fmt = "Failure during deployment.Tx hash: {:?}", _0)] 53 | ContractDeploymentFailure(H256), 54 | } 55 | 56 | impl std::error::Error for Error { 57 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 58 | match *self { 59 | Error::Abi(ref e) => Some(e), 60 | Error::Api(ref e) => Some(e), 61 | Error::ContractDeploymentFailure(_) => None, 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/contract/res/Main.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Main", 3 | "abi": [ 4 | { 5 | "inputs": [], 6 | "payable": false, 7 | "stateMutability": "nonpayable", 8 | "type": "constructor", 9 | "signature": "constructor" 10 | }, 11 | { 12 | "constant": false, 13 | "inputs": [], 14 | "name": "test", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "uint256" 19 | } 20 | ], 21 | "payable": false, 22 | "stateMutability": "nonpayable", 23 | "type": "function", 24 | "signature": "0xf8a8fd6d" 25 | } 26 | ], 27 | "bytecode": "0x608060405234801561001057600080fd5b5061013f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14610046575b600080fd5b34801561005257600080fd5b5061005b610071565b6040518082815260200191505060405180910390f35b600073__MyLibrary_____________________________63f8a8fd6d6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156100d357600080fd5b505af41580156100e7573d6000803e3d6000fd5b505050506040513d60208110156100fd57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820580d3776b3d132142f431e141a2e20bd4dd4907fa304feea7b604e8f39ed59520029" 28 | } -------------------------------------------------------------------------------- /src/contract/res/MyLibrary.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "MyLibrary", 3 | "abi": [ 4 | { 5 | "constant": true, 6 | "inputs": [], 7 | "name": "test", 8 | "outputs": [ 9 | { 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "payable": false, 15 | "stateMutability": "pure", 16 | "type": "function", 17 | "signature": "0xf8a8fd6d" 18 | } 19 | ], 20 | "bytecode": "0x60ad61002f600b82828239805160001a6073146000811461001f57610021565bfe5b5030600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14605b575b600080fd5b60616077565b6040518082815260200191505060405180910390f35b600061010090509056fea165627a7a72305820b50091adcb7ef9987dd8daa665cec572801bf8243530d70d52631f9d5ddb943e0029" 21 | } -------------------------------------------------------------------------------- /src/contract/res/token.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "name", 5 | "constant": true, 6 | "inputs": [], 7 | "outputs": [ 8 | { 9 | "name": "", 10 | "type": "string" 11 | } 12 | ] 13 | }, 14 | { 15 | "type": "function", 16 | "name": "decimals", 17 | "constant": true, 18 | "inputs": [], 19 | "outputs": [ 20 | { 21 | "name": "", 22 | "type": "uint8" 23 | } 24 | ] 25 | }, 26 | { 27 | "type": "function", 28 | "name": "balanceOf", 29 | "constant": true, 30 | "inputs": [ 31 | { 32 | "name": "", 33 | "type": "address" 34 | } 35 | ], 36 | "outputs": [ 37 | { 38 | "name": "", 39 | "type": "uint256" 40 | } 41 | ] 42 | }, 43 | { 44 | "type": "function", 45 | "name": "symbol", 46 | "constant": true, 47 | "inputs": [], 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "string" 52 | } 53 | ] 54 | }, 55 | { 56 | "type": "function", 57 | "name": "transfer", 58 | "constant": false, 59 | "inputs": [ 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "outputs": [] 70 | }, 71 | { 72 | "type": "constructor", 73 | "inputs": [ 74 | { 75 | "name": "_supply", 76 | "type": "uint256" 77 | }, 78 | { 79 | "name": "_name", 80 | "type": "string" 81 | }, 82 | { 83 | "name": "_decimals", 84 | "type": "uint8" 85 | }, 86 | { 87 | "name": "_symbol", 88 | "type": "string" 89 | } 90 | ] 91 | }, 92 | { 93 | "name": "Transfer", 94 | "type": "event", 95 | "anonymous": false, 96 | "inputs": [ 97 | { 98 | "indexed": true, 99 | "name": "from", 100 | "type": "address" 101 | }, 102 | { 103 | "indexed": true, 104 | "name": "to", 105 | "type": "address" 106 | }, 107 | { 108 | "indexed": false, 109 | "name": "value", 110 | "type": "uint256" 111 | } 112 | ] 113 | }, 114 | { 115 | "constant":false, 116 | "inputs":[ 117 | { 118 | "name":"_spender", 119 | "type":"address" 120 | }, 121 | { 122 | "name":"_value", 123 | "type":"uint256" 124 | } 125 | ], 126 | "name":"approve", 127 | "outputs":[ 128 | { 129 | "name":"success", 130 | "type":"bool" 131 | } 132 | ], 133 | "type":"function" 134 | }, 135 | { 136 | "constant":true, 137 | "inputs":[ 138 | { 139 | "name":"", 140 | "type":"address" 141 | }, 142 | { 143 | "name":"", 144 | "type":"address" 145 | } 146 | ], 147 | "name":"allowance", 148 | "outputs":[ 149 | { 150 | "name":"", 151 | "type":"uint256" 152 | } 153 | ], 154 | "type":"function" 155 | } 156 | ] 157 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Web3 Error 2 | use crate::rpc::error::Error as RPCError; 3 | use derive_more::{Display, From}; 4 | use serde_json::Error as SerdeError; 5 | use std::io::Error as IoError; 6 | 7 | /// Web3 `Result` type. 8 | pub type Result = std::result::Result; 9 | 10 | /// Transport-depended error. 11 | #[derive(Display, Debug, Clone, PartialEq)] 12 | pub enum TransportError { 13 | /// Transport-specific error code. 14 | #[display(fmt = "code {}", _0)] 15 | Code(u16), 16 | /// Arbitrary, developer-readable description of the occurred error. 17 | #[display(fmt = "{}", _0)] 18 | Message(String), 19 | } 20 | 21 | /// Errors which can occur when attempting to generate resource uri. 22 | #[derive(Debug, Display, From)] 23 | pub enum Error { 24 | /// server is unreachable 25 | #[display(fmt = "Server is unreachable")] 26 | Unreachable, 27 | /// decoder error 28 | #[display(fmt = "Decoder error: {}", _0)] 29 | Decoder(String), 30 | /// invalid response 31 | #[display(fmt = "Got invalid response: {}", _0)] 32 | #[from(ignore)] 33 | InvalidResponse(String), 34 | /// transport error 35 | #[display(fmt = "Transport error: {}" _0)] 36 | #[from(ignore)] 37 | Transport(TransportError), 38 | /// rpc error 39 | #[display(fmt = "RPC error: {:?}", _0)] 40 | Rpc(RPCError), 41 | /// io error 42 | #[display(fmt = "IO error: {}", _0)] 43 | Io(IoError), 44 | /// recovery error 45 | #[display(fmt = "Recovery error: {}", _0)] 46 | Recovery(crate::signing::RecoveryError), 47 | /// web3 internal error 48 | #[display(fmt = "Internal Web3 error")] 49 | Internal, 50 | /// Transaction reverted 51 | #[display(fmt = "Transaction reverted: {}", _0)] 52 | #[from(ignore)] 53 | Revert(String), 54 | } 55 | 56 | impl std::error::Error for Error { 57 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 58 | use self::Error::*; 59 | match *self { 60 | Unreachable | Decoder(_) | InvalidResponse(_) | Transport { .. } | Internal | Revert(_) => None, 61 | Rpc(ref e) => Some(e), 62 | Io(ref e) => Some(e), 63 | Recovery(ref e) => Some(e), 64 | } 65 | } 66 | } 67 | 68 | impl From for Error { 69 | fn from(err: SerdeError) -> Self { 70 | Error::Decoder(format!("{:?}", err)) 71 | } 72 | } 73 | 74 | impl Clone for Error { 75 | fn clone(&self) -> Self { 76 | use self::Error::*; 77 | match self { 78 | Unreachable => Unreachable, 79 | Decoder(s) => Decoder(s.clone()), 80 | InvalidResponse(s) => InvalidResponse(s.clone()), 81 | Transport(s) => Transport(s.clone()), 82 | Rpc(e) => Rpc(e.clone()), 83 | Io(e) => Io(IoError::from(e.kind())), 84 | Recovery(e) => Recovery(e.clone()), 85 | Internal => Internal, 86 | Revert(s) => Revert(s.clone()), 87 | } 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | impl PartialEq for Error { 93 | fn eq(&self, other: &Self) -> bool { 94 | use self::Error::*; 95 | match (self, other) { 96 | (Unreachable, Unreachable) | (Internal, Internal) => true, 97 | (Decoder(a), Decoder(b)) | (InvalidResponse(a), InvalidResponse(b)) => a == b, 98 | (Transport(a), Transport(b)) => a == b, 99 | (Rpc(a), Rpc(b)) => a == b, 100 | (Io(a), Io(b)) => a.kind() == b.kind(), 101 | (Recovery(a), Recovery(b)) => a == b, 102 | (Revert(a), Revert(b)) => a == b, 103 | _ => false, 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | //! Web3 helpers. 2 | 3 | use crate::{error, rpc, Error}; 4 | use futures::{ 5 | task::{Context, Poll}, 6 | Future, 7 | }; 8 | use pin_project::pin_project; 9 | use serde::de::DeserializeOwned; 10 | use std::{marker::PhantomData, pin::Pin}; 11 | 12 | /// Takes any type which is deserializable from rpc::Value and such a value and 13 | /// yields the deserialized value 14 | pub fn decode(value: rpc::Value) -> error::Result { 15 | serde_json::from_value(value).map_err(Into::into) 16 | } 17 | 18 | /// Calls decode on the result of the wrapped future. 19 | #[pin_project] 20 | #[derive(Debug)] 21 | pub struct CallFuture { 22 | #[pin] 23 | inner: F, 24 | _marker: PhantomData, 25 | } 26 | 27 | impl CallFuture { 28 | /// Create a new CallFuture wrapping the inner future. 29 | pub fn new(inner: F) -> Self { 30 | CallFuture { 31 | inner, 32 | _marker: PhantomData, 33 | } 34 | } 35 | } 36 | 37 | impl Future for CallFuture 38 | where 39 | T: serde::de::DeserializeOwned, 40 | F: Future>, 41 | { 42 | type Output = error::Result; 43 | 44 | fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 45 | let this = self.project(); 46 | let x = ready!(this.inner.poll(ctx)); 47 | Poll::Ready(x.and_then(decode)) 48 | } 49 | } 50 | 51 | /// Serialize a type. Panics if the type is returns error during serialization. 52 | pub fn serialize(t: &T) -> rpc::Value { 53 | serde_json::to_value(t).expect("Types never fail to serialize.") 54 | } 55 | 56 | /// Serializes a request to string. Panics if the type returns error during serialization. 57 | pub fn to_string(request: &T) -> String { 58 | serde_json::to_string(&request).expect("String serialization never fails.") 59 | } 60 | 61 | /// Build a JSON-RPC request. 62 | pub fn build_request(id: usize, method: &str, params: Vec) -> rpc::Call { 63 | rpc::Call::MethodCall(rpc::MethodCall { 64 | jsonrpc: Some(rpc::Version::V2), 65 | method: method.into(), 66 | params: rpc::Params::Array(params), 67 | id: rpc::Id::Num(id as u64), 68 | }) 69 | } 70 | 71 | /// Parse bytes slice into JSON-RPC response. 72 | /// It looks for arbitrary_precision feature as a temporary workaround for https://github.com/tomusdrw/rust-web3/issues/460. 73 | pub fn to_response_from_slice(response: &[u8]) -> error::Result { 74 | arbitrary_precision_deserialize_workaround(response).map_err(|e| Error::InvalidResponse(format!("{:?}", e))) 75 | } 76 | 77 | /// Deserialize bytes into T. 78 | /// It looks for arbitrary_precision feature as a temporary workaround for https://github.com/tomusdrw/rust-web3/issues/460. 79 | pub fn arbitrary_precision_deserialize_workaround(bytes: &[u8]) -> Result 80 | where 81 | T: DeserializeOwned, 82 | { 83 | if cfg!(feature = "arbitrary_precision") { 84 | serde_json::from_value(serde_json::from_slice(bytes)?) 85 | } else { 86 | serde_json::from_slice(bytes) 87 | } 88 | } 89 | 90 | /// Parse bytes slice into JSON-RPC notification. 91 | pub fn to_notification_from_slice(notification: &[u8]) -> error::Result { 92 | serde_json::from_slice(notification).map_err(|e| error::Error::InvalidResponse(format!("{:?}", e))) 93 | } 94 | 95 | /// Parse a Vec of `rpc::Output` into `Result`. 96 | pub fn to_results_from_outputs(outputs: Vec) -> error::Result>> { 97 | Ok(outputs.into_iter().map(to_result_from_output).collect()) 98 | } 99 | 100 | /// Parse `rpc::Output` into `Result`. 101 | pub fn to_result_from_output(output: rpc::Output) -> error::Result { 102 | match output { 103 | rpc::Output::Success(success) => Ok(success.result), 104 | rpc::Output::Failure(failure) => Err(error::Error::Rpc(failure.error)), 105 | } 106 | } 107 | 108 | #[macro_use] 109 | #[cfg(test)] 110 | mod tests { 111 | macro_rules! rpc_test { 112 | // With parameters 113 | ( 114 | $namespace: ident : $name: ident : $test_name: ident $(, $param: expr)+ => $method: expr, $results: expr; 115 | $returned: expr => $expected: expr 116 | ) => { 117 | #[test] 118 | fn $test_name() { 119 | // given 120 | let mut transport = $crate::transports::test::TestTransport::default(); 121 | transport.set_response($returned); 122 | let result = { 123 | let eth = $namespace::new(&transport); 124 | 125 | // when 126 | eth.$name($($param.into(), )+) 127 | }; 128 | 129 | // then 130 | transport.assert_request($method, &$results.into_iter().map(Into::into).collect::>()); 131 | transport.assert_no_more_requests(); 132 | let result = futures::executor::block_on(result); 133 | assert_eq!(result, Ok($expected.into())); 134 | } 135 | }; 136 | // With parameters (implicit test name) 137 | ( 138 | $namespace: ident : $name: ident $(, $param: expr)+ => $method: expr, $results: expr; 139 | $returned: expr => $expected: expr 140 | ) => { 141 | rpc_test! ( 142 | $namespace : $name : $name $(, $param)+ => $method, $results; 143 | $returned => $expected 144 | ); 145 | }; 146 | 147 | // No params entry point (explicit name) 148 | ( 149 | $namespace: ident: $name: ident: $test_name: ident => $method: expr; 150 | $returned: expr => $expected: expr 151 | ) => { 152 | #[test] 153 | fn $test_name() { 154 | // given 155 | let mut transport = $crate::transports::test::TestTransport::default(); 156 | transport.set_response($returned); 157 | let result = { 158 | let eth = $namespace::new(&transport); 159 | 160 | // when 161 | eth.$name() 162 | }; 163 | 164 | // then 165 | transport.assert_request($method, &[]); 166 | transport.assert_no_more_requests(); 167 | let result = futures::executor::block_on(result); 168 | assert_eq!(result, Ok($expected.into())); 169 | } 170 | }; 171 | 172 | // No params entry point 173 | ( 174 | $namespace: ident: $name: ident => $method: expr; 175 | $returned: expr => $expected: expr 176 | ) => { 177 | rpc_test! ( 178 | $namespace: $name: $name => $method; 179 | $returned => $expected 180 | ); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum JSON-RPC client (Web3). 2 | 3 | #![allow( 4 | clippy::type_complexity, 5 | clippy::wrong_self_convention, 6 | clippy::single_match, 7 | clippy::let_unit_value, 8 | clippy::match_wild_err_arm 9 | )] 10 | #![warn(missing_docs)] 11 | // select! in WS transport 12 | #![recursion_limit = "256"] 13 | 14 | use jsonrpc_core as rpc; 15 | 16 | /// Re-export of the `futures` crate. 17 | #[macro_use] 18 | pub extern crate futures; 19 | pub use futures::executor::{block_on, block_on_stream}; 20 | 21 | pub use ethabi; 22 | 23 | // it needs to be before other modules 24 | // otherwise the macro for tests is not available. 25 | #[macro_use] 26 | pub mod helpers; 27 | 28 | pub mod api; 29 | pub mod confirm; 30 | pub mod contract; 31 | pub mod error; 32 | pub mod signing; 33 | pub mod transports; 34 | pub mod types; 35 | 36 | pub use crate::{ 37 | api::Web3, 38 | error::{Error, Result}, 39 | }; 40 | 41 | /// Assigned RequestId 42 | pub type RequestId = usize; 43 | 44 | // TODO [ToDr] The transport most likely don't need to be thread-safe. 45 | // (though it has to be Send) 46 | /// Transport implementation 47 | pub trait Transport: std::fmt::Debug + Clone { 48 | /// The type of future this transport returns when a call is made. 49 | type Out: futures::Future>; 50 | 51 | /// Prepare serializable RPC call for given method with parameters. 52 | fn prepare(&self, method: &str, params: Vec) -> (RequestId, rpc::Call); 53 | 54 | /// Execute prepared RPC call. 55 | fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out; 56 | 57 | /// Execute remote method with given parameters. 58 | fn execute(&self, method: &str, params: Vec) -> Self::Out { 59 | let (id, request) = self.prepare(method, params); 60 | self.send(id, request) 61 | } 62 | } 63 | 64 | /// A transport implementation supporting batch requests. 65 | pub trait BatchTransport: Transport { 66 | /// The type of future this transport returns when a call is made. 67 | type Batch: futures::Future>>>; 68 | 69 | /// Sends a batch of prepared RPC calls. 70 | fn send_batch(&self, requests: T) -> Self::Batch 71 | where 72 | T: IntoIterator; 73 | } 74 | 75 | /// A transport implementation supporting pub sub subscriptions. 76 | pub trait DuplexTransport: Transport { 77 | /// The type of stream this transport returns 78 | type NotificationStream: futures::Stream; 79 | 80 | /// Add a subscription to this transport 81 | fn subscribe(&self, id: api::SubscriptionId) -> error::Result; 82 | 83 | /// Remove a subscription from this transport 84 | fn unsubscribe(&self, id: api::SubscriptionId) -> error::Result<()>; 85 | } 86 | 87 | impl Transport for X 88 | where 89 | T: Transport + ?Sized, 90 | X: std::ops::Deref, 91 | X: std::fmt::Debug, 92 | X: Clone, 93 | { 94 | type Out = T::Out; 95 | 96 | fn prepare(&self, method: &str, params: Vec) -> (RequestId, rpc::Call) { 97 | (**self).prepare(method, params) 98 | } 99 | 100 | fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out { 101 | (**self).send(id, request) 102 | } 103 | } 104 | 105 | impl BatchTransport for X 106 | where 107 | T: BatchTransport + ?Sized, 108 | X: std::ops::Deref, 109 | X: std::fmt::Debug, 110 | X: Clone, 111 | { 112 | type Batch = T::Batch; 113 | 114 | fn send_batch(&self, requests: I) -> Self::Batch 115 | where 116 | I: IntoIterator, 117 | { 118 | (**self).send_batch(requests) 119 | } 120 | } 121 | 122 | impl DuplexTransport for X 123 | where 124 | T: DuplexTransport + ?Sized, 125 | X: std::ops::Deref, 126 | X: std::fmt::Debug, 127 | X: Clone, 128 | { 129 | type NotificationStream = T::NotificationStream; 130 | 131 | fn subscribe(&self, id: api::SubscriptionId) -> error::Result { 132 | (**self).subscribe(id) 133 | } 134 | 135 | fn unsubscribe(&self, id: api::SubscriptionId) -> error::Result<()> { 136 | (**self).unsubscribe(id) 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use super::{error, rpc, RequestId, Transport}; 143 | 144 | use crate::api::Web3; 145 | use futures::future::BoxFuture; 146 | use std::sync::Arc; 147 | 148 | #[derive(Debug, Clone)] 149 | struct FakeTransport; 150 | 151 | impl Transport for FakeTransport { 152 | type Out = BoxFuture<'static, error::Result>; 153 | 154 | fn prepare(&self, _method: &str, _params: Vec) -> (RequestId, rpc::Call) { 155 | unimplemented!() 156 | } 157 | 158 | fn send(&self, _id: RequestId, _request: rpc::Call) -> Self::Out { 159 | unimplemented!() 160 | } 161 | } 162 | 163 | #[test] 164 | fn should_allow_to_use_arc_as_transport() { 165 | let transport = Arc::new(FakeTransport); 166 | let transport2 = transport.clone(); 167 | 168 | let _web3_1 = Web3::new(transport); 169 | let _web3_2 = Web3::new(transport2); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/transports/batch.rs: -------------------------------------------------------------------------------- 1 | //! Batching Transport 2 | 3 | use crate::{ 4 | error::{self, Error}, 5 | rpc, BatchTransport, RequestId, Transport, 6 | }; 7 | use futures::{ 8 | channel::oneshot, 9 | task::{Context, Poll}, 10 | Future, FutureExt, 11 | }; 12 | use parking_lot::Mutex; 13 | use std::{collections::BTreeMap, pin::Pin, sync::Arc}; 14 | 15 | type Pending = oneshot::Sender>; 16 | type PendingRequests = Arc>>; 17 | 18 | /// Transport allowing to batch queries together. 19 | /// 20 | /// Note: cloned instances of [Batch] share the queues of pending and unsent requests. 21 | /// If you want to avoid it, use [Batch::new] repeatedly instead. 22 | #[derive(Debug, Clone)] 23 | pub struct Batch { 24 | transport: T, 25 | pending: PendingRequests, 26 | batch: Arc>>, 27 | } 28 | 29 | impl Batch 30 | where 31 | T: BatchTransport, 32 | { 33 | /// Creates new Batch transport given existing transport supporting batch requests. 34 | pub fn new(transport: T) -> Self { 35 | Batch { 36 | transport, 37 | pending: Default::default(), 38 | batch: Default::default(), 39 | } 40 | } 41 | 42 | /// Sends all requests as a batch. 43 | pub fn submit_batch(&self) -> impl Future>>> { 44 | let batch = std::mem::take(&mut *self.batch.lock()); 45 | let ids = batch.iter().map(|&(id, _)| id).collect::>(); 46 | 47 | let batch = self.transport.send_batch(batch); 48 | let pending = self.pending.clone(); 49 | 50 | async move { 51 | let res = batch.await; 52 | let mut pending = pending.lock(); 53 | for (idx, request_id) in ids.into_iter().enumerate() { 54 | if let Some(rx) = pending.remove(&request_id) { 55 | // Ignore sending error 56 | let _ = match res { 57 | Ok(ref results) if results.len() > idx => rx.send(results[idx].clone()), 58 | Err(ref err) => rx.send(Err(err.clone())), 59 | _ => rx.send(Err(Error::Internal)), 60 | }; 61 | } 62 | } 63 | res 64 | } 65 | } 66 | } 67 | 68 | impl Transport for Batch 69 | where 70 | T: BatchTransport, 71 | { 72 | type Out = SingleResult; 73 | 74 | fn prepare(&self, method: &str, params: Vec) -> (RequestId, rpc::Call) { 75 | self.transport.prepare(method, params) 76 | } 77 | 78 | fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out { 79 | let (tx, rx) = oneshot::channel(); 80 | self.pending.lock().insert(id, tx); 81 | self.batch.lock().push((id, request)); 82 | 83 | SingleResult(rx) 84 | } 85 | } 86 | 87 | /// Result of calling a single method that will be part of the batch. 88 | /// Converts `oneshot::Receiver` error into `Error::Internal` 89 | pub struct SingleResult(oneshot::Receiver>); 90 | 91 | impl Future for SingleResult { 92 | type Output = error::Result; 93 | 94 | fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 95 | Poll::Ready(ready!(self.0.poll_unpin(ctx)).map_err(|_| Error::Internal)?) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/transports/either.rs: -------------------------------------------------------------------------------- 1 | //! A strongly-typed transport alternative. 2 | 3 | use crate::{api, error, rpc, BatchTransport, DuplexTransport, RequestId, Transport}; 4 | use futures::{ 5 | future::{BoxFuture, FutureExt}, 6 | stream::{BoxStream, StreamExt}, 7 | }; 8 | 9 | /// A wrapper over two possible transports. 10 | /// 11 | /// This type can be used to write semi-generic 12 | /// code without the hassle of making all functions generic. 13 | /// 14 | /// See the `examples` folder for an example how to use it. 15 | #[derive(Debug, Clone)] 16 | pub enum Either { 17 | /// First possible transport. 18 | Left(A), 19 | /// Second possible transport. 20 | Right(B), 21 | } 22 | 23 | impl Transport for Either 24 | where 25 | A: Transport, 26 | B: Transport, 27 | AOut: futures::Future> + 'static + Send, 28 | BOut: futures::Future> + 'static + Send, 29 | { 30 | type Out = BoxFuture<'static, error::Result>; 31 | 32 | fn prepare(&self, method: &str, params: Vec) -> (RequestId, rpc::Call) { 33 | match *self { 34 | Self::Left(ref a) => a.prepare(method, params), 35 | Self::Right(ref b) => b.prepare(method, params), 36 | } 37 | } 38 | 39 | fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out { 40 | match *self { 41 | Self::Left(ref a) => a.send(id, request).boxed(), 42 | Self::Right(ref b) => b.send(id, request).boxed(), 43 | } 44 | } 45 | } 46 | 47 | impl BatchTransport for Either 48 | where 49 | A: BatchTransport, 50 | B: BatchTransport, 51 | A::Out: 'static + Send, 52 | B::Out: 'static + Send, 53 | ABatch: futures::Future>>> + 'static + Send, 54 | BBatch: futures::Future>>> + 'static + Send, 55 | { 56 | type Batch = BoxFuture<'static, error::Result>>>; 57 | 58 | fn send_batch(&self, requests: T) -> Self::Batch 59 | where 60 | T: IntoIterator, 61 | { 62 | match *self { 63 | Self::Left(ref a) => a.send_batch(requests).boxed(), 64 | Self::Right(ref b) => b.send_batch(requests).boxed(), 65 | } 66 | } 67 | } 68 | 69 | impl DuplexTransport for Either 70 | where 71 | A: DuplexTransport, 72 | B: DuplexTransport, 73 | A::Out: 'static + Send, 74 | B::Out: 'static + Send, 75 | AStream: futures::Stream + 'static + Send, 76 | BStream: futures::Stream + 'static + Send, 77 | { 78 | type NotificationStream = BoxStream<'static, rpc::Value>; 79 | 80 | fn subscribe(&self, id: api::SubscriptionId) -> error::Result { 81 | Ok(match *self { 82 | Self::Left(ref a) => a.subscribe(id)?.boxed(), 83 | Self::Right(ref b) => b.subscribe(id)?.boxed(), 84 | }) 85 | } 86 | 87 | fn unsubscribe(&self, id: api::SubscriptionId) -> error::Result { 88 | match *self { 89 | Self::Left(ref a) => a.unsubscribe(id), 90 | Self::Right(ref b) => b.unsubscribe(id), 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/transports/mod.rs: -------------------------------------------------------------------------------- 1 | //! Supported Ethereum JSON-RPC transports. 2 | 3 | pub mod batch; 4 | 5 | pub use self::batch::Batch; 6 | pub mod either; 7 | pub use self::either::Either; 8 | 9 | #[cfg(any(feature = "http", feature = "http-rustls-tls"))] 10 | pub mod http; 11 | #[cfg(any(feature = "http", feature = "http-rustls-tls"))] 12 | pub use self::http::Http; 13 | 14 | #[cfg(any(feature = "ws-tokio", feature = "ws-async-std"))] 15 | pub mod ws; 16 | #[cfg(any(feature = "ws-tokio", feature = "ws-async-std"))] 17 | pub use self::ws::WebSocket; 18 | 19 | #[cfg(feature = "ipc-tokio")] 20 | pub mod ipc; 21 | #[cfg(feature = "ipc-tokio")] 22 | pub use self::ipc::Ipc; 23 | 24 | #[cfg(any(feature = "test", test))] 25 | pub mod test; 26 | 27 | #[cfg(feature = "url")] 28 | impl From for crate::Error { 29 | fn from(err: url::ParseError) -> Self { 30 | use crate::error::TransportError; 31 | crate::Error::Transport(TransportError::Message(format!("failed to parse url: {}", err))) 32 | } 33 | } 34 | 35 | #[cfg(feature = "async-native-tls")] 36 | impl From for crate::Error { 37 | fn from(err: async_native_tls::Error) -> Self { 38 | use crate::error::TransportError; 39 | crate::Error::Transport(TransportError::Message(format!("{:?}", err))) 40 | } 41 | } 42 | 43 | #[cfg(feature = "eip-1193")] 44 | pub mod eip_1193; 45 | -------------------------------------------------------------------------------- /src/transports/test.rs: -------------------------------------------------------------------------------- 1 | //! Test Transport 2 | 3 | use crate::{ 4 | error::{self, Error}, 5 | helpers, rpc, RequestId, Transport, 6 | }; 7 | use futures::future::{self, BoxFuture, FutureExt}; 8 | use std::{cell::RefCell, collections::VecDeque, rc::Rc}; 9 | 10 | type Result = BoxFuture<'static, error::Result>; 11 | 12 | /// Test Transport 13 | #[derive(Debug, Default, Clone)] 14 | pub struct TestTransport { 15 | asserted: usize, 16 | requests: Rc)>>>, 17 | responses: Rc>>, 18 | } 19 | 20 | impl Transport for TestTransport { 21 | type Out = Result; 22 | 23 | fn prepare(&self, method: &str, params: Vec) -> (RequestId, rpc::Call) { 24 | let request = helpers::build_request(1, method, params.clone()); 25 | self.requests.borrow_mut().push((method.into(), params)); 26 | (self.requests.borrow().len(), request) 27 | } 28 | 29 | fn send(&self, id: RequestId, request: rpc::Call) -> Result { 30 | future::ready(match self.responses.borrow_mut().pop_front() { 31 | Some(response) => Ok(response), 32 | None => { 33 | println!("Unexpected request (id: {:?}): {:?}", id, request); 34 | Err(Error::Unreachable) 35 | } 36 | }) 37 | .boxed() 38 | } 39 | } 40 | 41 | impl TestTransport { 42 | /// Set response 43 | pub fn set_response(&mut self, value: rpc::Value) { 44 | *self.responses.borrow_mut() = vec![value].into(); 45 | } 46 | 47 | /// Add response 48 | pub fn add_response(&mut self, value: rpc::Value) { 49 | self.responses.borrow_mut().push_back(value); 50 | } 51 | 52 | /// Assert request 53 | pub fn assert_request(&mut self, method: &str, params: &[String]) { 54 | let idx = self.asserted; 55 | self.asserted += 1; 56 | 57 | let (m, p) = self.requests.borrow().get(idx).expect("Expected result.").clone(); 58 | assert_eq!(&m, method); 59 | let p: Vec = p.into_iter().map(|p| serde_json::to_string(&p).unwrap()).collect(); 60 | assert_eq!(p, params); 61 | } 62 | 63 | /// Assert no more requests 64 | pub fn assert_no_more_requests(&self) { 65 | let requests = self.requests.borrow(); 66 | assert_eq!( 67 | self.asserted, 68 | requests.len(), 69 | "Expected no more requests, got: {:?}", 70 | &requests[self.asserted..] 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/types/bytes.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::{Error, Unexpected, Visitor}, 3 | Deserialize, Deserializer, Serialize, Serializer, 4 | }; 5 | use std::fmt; 6 | 7 | /// Raw bytes wrapper 8 | #[derive(Clone, Default, PartialEq, Eq, Hash)] 9 | pub struct Bytes(pub Vec); 10 | 11 | impl>> From for Bytes { 12 | fn from(data: T) -> Self { 13 | Bytes(data.into()) 14 | } 15 | } 16 | 17 | impl Serialize for Bytes { 18 | fn serialize(&self, serializer: S) -> Result 19 | where 20 | S: Serializer, 21 | { 22 | let mut serialized = "0x".to_owned(); 23 | serialized.push_str(&hex::encode(&self.0)); 24 | serializer.serialize_str(serialized.as_ref()) 25 | } 26 | } 27 | 28 | impl<'a> Deserialize<'a> for Bytes { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: Deserializer<'a>, 32 | { 33 | deserializer.deserialize_identifier(BytesVisitor) 34 | } 35 | } 36 | 37 | impl fmt::Debug for Bytes { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | let serialized = format!("0x{}", hex::encode(&self.0)); 40 | f.debug_tuple("Bytes").field(&serialized).finish() 41 | } 42 | } 43 | 44 | struct BytesVisitor; 45 | 46 | impl<'a> Visitor<'a> for BytesVisitor { 47 | type Value = Bytes; 48 | 49 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 50 | write!(formatter, "a 0x-prefixed hex-encoded vector of bytes") 51 | } 52 | 53 | fn visit_str(self, value: &str) -> Result 54 | where 55 | E: Error, 56 | { 57 | if let Some(value) = value.strip_prefix("0x") { 58 | let bytes = hex::decode(value).map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?; 59 | Ok(Bytes(bytes)) 60 | } else { 61 | Err(Error::invalid_value(Unexpected::Str(value), &"0x prefix")) 62 | } 63 | } 64 | 65 | fn visit_string(self, value: String) -> Result 66 | where 67 | E: Error, 68 | { 69 | self.visit_str(value.as_ref()) 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn deserialize() { 79 | assert_eq!(serde_json::from_str::(r#""0x00""#).unwrap(), Bytes(vec![0x00])); 80 | assert_eq!( 81 | serde_json::from_str::(r#""0x0123456789AaBbCcDdEeFf""#).unwrap(), 82 | Bytes(vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]) 83 | ); 84 | assert_eq!(serde_json::from_str::(r#""0x""#).unwrap(), Bytes(vec![])); 85 | 86 | assert!(serde_json::from_str::("0").is_err(), "Not a string"); 87 | assert!(serde_json::from_str::(r#""""#).is_err(), "Empty string"); 88 | assert!(serde_json::from_str::(r#""0xZZ""#).is_err(), "Invalid hex"); 89 | assert!( 90 | serde_json::from_str::(r#""deadbeef""#).is_err(), 91 | "Missing 0x prefix" 92 | ); 93 | assert!(serde_json::from_str::(r#""数字""#).is_err(), "Non-ASCII"); 94 | assert!(serde_json::from_str::(r#""0x数字""#).is_err(), "Non-ASCII"); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/types/bytes_array.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A wrapper type for array of bytes. 4 | /// 5 | /// Implements `Tokenizable` so can be used to retrieve data from `Solidity` contracts returning `byte8[]`. 6 | #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Hash, Serialize)] 7 | pub struct BytesArray(pub Vec); 8 | -------------------------------------------------------------------------------- /src/types/example-traces-str.rs: -------------------------------------------------------------------------------- 1 | r#"[{ 2 | "output": "0x", 3 | "stateDiff": { 4 | "0x5df9b87991262f6ba471f09758cde1c0fc1de734": { 5 | "balance": { 6 | "+": "0x7a69" 7 | }, 8 | "code": { 9 | "+": "0x" 10 | }, 11 | "nonce": { 12 | "+": "0x0" 13 | }, 14 | "storage": {} 15 | }, 16 | "0xa1e4380a3b1f749673e270229993ee55f35663b4": { 17 | "balance": { 18 | "*": { 19 | "from": "0x6c6b935b8bbd400000", 20 | "to": "0x6c5d01021be7168597" 21 | } 22 | }, 23 | "code": "=", 24 | "nonce": { 25 | "*": { 26 | "from": "0x0", 27 | "to": "0x1" 28 | } 29 | }, 30 | "storage": {} 31 | }, 32 | "0xe6a7a1d47ff21b6321162aea7c6cb457d5476bca": { 33 | "balance": { 34 | "*": { 35 | "from": "0xf3426785a8ab466000", 36 | "to": "0xf350f9df18816f6000" 37 | } 38 | }, 39 | "code": "=", 40 | "nonce": "=", 41 | "storage": {} 42 | } 43 | }, 44 | "trace": [ 45 | { 46 | "action": { 47 | "callType": "call", 48 | "from": "0xa1e4380a3b1f749673e270229993ee55f35663b4", 49 | "gas": "0x0", 50 | "input": "0x", 51 | "to": "0x5df9b87991262f6ba471f09758cde1c0fc1de734", 52 | "value": "0x7a69" 53 | }, 54 | "result": { 55 | "gasUsed": "0x0", 56 | "output": "0x" 57 | }, 58 | "subtraces": 0, 59 | "traceAddress": [], 60 | "type": "call" 61 | } 62 | ], 63 | "transactionHash": "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", 64 | "vmTrace": { 65 | "code": "0x", 66 | "ops": [] 67 | } 68 | }]"# 69 | -------------------------------------------------------------------------------- /src/types/fee_history.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{BlockNumber, U256}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The fee history type returned from `eth_feeHistory` call. 5 | #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct FeeHistory { 8 | /// Lowest number block of the returned range. 9 | pub oldest_block: BlockNumber, 10 | /// A vector of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks. 11 | pub base_fee_per_gas: Vec, 12 | /// A vector of block gas used ratios. These are calculated as the ratio of gas used and gas limit. 13 | pub gas_used_ratio: Vec, 14 | /// A vector of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty. Returned only if requested. 15 | pub reward: Option>>, 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::*; 21 | 22 | #[test] 23 | fn fee_history() { 24 | let fee_history = FeeHistory { 25 | oldest_block: BlockNumber::Number(123456.into()), 26 | base_fee_per_gas: vec![100.into(), 110.into()], 27 | gas_used_ratio: vec![1.0, 2.0, 3.0], 28 | reward: None, 29 | }; 30 | 31 | let serialized = serde_json::to_value(fee_history.clone()).unwrap(); 32 | assert_eq!(serialized.to_string(), "{\"baseFeePerGas\":[\"0x64\",\"0x6e\"],\"gasUsedRatio\":[1.0,2.0,3.0],\"oldestBlock\":\"0x1e240\",\"reward\":null}"); 33 | 34 | let deserialized = serde_json::from_value(serialized).unwrap(); 35 | assert_eq!(fee_history, deserialized); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! Web3 Types 2 | 3 | mod block; 4 | mod bytes; 5 | mod bytes_array; 6 | mod fee_history; 7 | mod log; 8 | mod parity_peers; 9 | mod parity_pending_transaction; 10 | mod proof; 11 | mod recovery; 12 | mod signed; 13 | mod sync_state; 14 | mod trace_filtering; 15 | mod traces; 16 | mod transaction; 17 | mod transaction_id; 18 | mod transaction_request; 19 | mod txpool; 20 | mod uint; 21 | mod work; 22 | 23 | pub use self::{ 24 | block::{Block, BlockHeader, BlockId, BlockNumber}, 25 | bytes::Bytes, 26 | bytes_array::BytesArray, 27 | fee_history::FeeHistory, 28 | log::{Filter, FilterBuilder, Log}, 29 | parity_peers::{ 30 | EthProtocolInfo, ParityPeerInfo, ParityPeerType, PeerNetworkInfo, PeerProtocolsInfo, PipProtocolInfo, 31 | }, 32 | parity_pending_transaction::{ 33 | FilterCondition, ParityPendingTransactionFilter, ParityPendingTransactionFilterBuilder, ToFilter, 34 | }, 35 | proof::Proof, 36 | recovery::{ParseSignatureError, Recovery, RecoveryMessage}, 37 | signed::{SignedData, SignedTransaction, TransactionParameters}, 38 | sync_state::{SyncInfo, SyncState}, 39 | trace_filtering::{ 40 | Action, ActionType, Call, CallResult, CallType, Create, CreateResult, Res, Reward, RewardType, Suicide, Trace, 41 | TraceFilter, TraceFilterBuilder, 42 | }, 43 | traces::{ 44 | AccountDiff, BlockTrace, ChangedType, Diff, MemoryDiff, StateDiff, StorageDiff, TraceType, TransactionTrace, 45 | VMExecutedOperation, VMOperation, VMTrace, 46 | }, 47 | transaction::{AccessList, AccessListItem, RawTransaction, Receipt as TransactionReceipt, Transaction}, 48 | transaction_id::TransactionId, 49 | transaction_request::{CallRequest, TransactionCondition, TransactionRequest}, 50 | txpool::{TxpoolContentInfo, TxpoolInspectInfo, TxpoolStatus}, 51 | uint::{H128, H160, H2048, H256, H512, H520, H64, U128, U256, U64}, 52 | work::Work, 53 | }; 54 | 55 | /// Address 56 | pub type Address = H160; 57 | /// Index in block 58 | pub type Index = U64; 59 | -------------------------------------------------------------------------------- /src/types/parity_peers.rs: -------------------------------------------------------------------------------- 1 | //! Types for getting peer information 2 | use ethereum_types::U256; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Stores active peer count, connected count, max connected peers 6 | /// and a list of peers for parity node 7 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 8 | pub struct ParityPeerType { 9 | /// number of active peers 10 | pub active: usize, 11 | /// number of connected peers 12 | pub connected: usize, 13 | /// maximum number of peers that can connect 14 | pub max: u32, 15 | /// list of all peers with details 16 | pub peers: Vec, 17 | } 18 | 19 | /// details of a peer 20 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 21 | pub struct ParityPeerInfo { 22 | /// id of peer 23 | pub id: Option, 24 | /// name of peer if set by user 25 | pub name: String, 26 | /// sync logic for protocol messaging 27 | pub caps: Vec, 28 | /// remote address and local address 29 | pub network: PeerNetworkInfo, 30 | /// protocol version of peer 31 | pub protocols: PeerProtocolsInfo, 32 | } 33 | 34 | /// ip address of both local and remote 35 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 36 | #[serde(rename_all = "camelCase")] 37 | pub struct PeerNetworkInfo { 38 | /// remote peer address 39 | pub remote_address: String, 40 | /// local peer address 41 | pub local_address: String, 42 | } 43 | 44 | /// chain protocol info 45 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 46 | pub struct PeerProtocolsInfo { 47 | /// chain info 48 | pub eth: Option, 49 | /// chain info 50 | pub pip: Option, 51 | } 52 | 53 | /// eth chain version, difficulty, and head of chain 54 | /// which soft fork? Olympic, Frontier, Homestead, Metropolis, Serenity, etc. 55 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 56 | pub struct EthProtocolInfo { 57 | /// version 58 | pub version: u32, 59 | /// difficulty 60 | pub difficulty: Option, 61 | /// head of chain 62 | pub head: String, 63 | } 64 | 65 | /// pip version, difficulty, and head 66 | #[derive(Serialize, PartialEq, Clone, Deserialize, Debug)] 67 | pub struct PipProtocolInfo { 68 | /// version 69 | pub version: u32, 70 | /// difficulty 71 | pub difficulty: U256, 72 | /// head of chain 73 | pub head: String, 74 | } 75 | -------------------------------------------------------------------------------- /src/types/parity_pending_transaction.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | ser::{SerializeMap, Serializer}, 3 | Serialize, 4 | }; 5 | 6 | use super::{Address, U256, U64}; 7 | 8 | /// Condition to filter pending transactions 9 | #[derive(Clone, Serialize)] 10 | pub enum FilterCondition { 11 | /// Lower Than 12 | #[serde(rename(serialize = "lt"))] 13 | LowerThan(T), 14 | /// Equal 15 | #[serde(rename(serialize = "eq"))] 16 | Equal(T), 17 | /// Greater Than 18 | #[serde(rename(serialize = "gt"))] 19 | GreaterThan(T), 20 | } 21 | 22 | impl From for FilterCondition { 23 | fn from(t: T) -> Self { 24 | FilterCondition::Equal(t) 25 | } 26 | } 27 | 28 | /// To Filter 29 | #[derive(Clone)] 30 | pub enum ToFilter { 31 | /// Address 32 | Address(Address), 33 | /// Action (i.e. contract creation) 34 | Action, 35 | } 36 | 37 | /// Filter for pending transactions (only openethereum/Parity) 38 | #[derive(Clone, Default, Serialize)] 39 | pub struct ParityPendingTransactionFilter { 40 | /// From address 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub from: Option>, 43 | /// To address or action, i.e. contract creation 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub to: Option, 46 | /// Gas 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | pub gas: Option>, 49 | /// Gas Price 50 | #[serde(skip_serializing_if = "Option::is_none")] 51 | pub gas_price: Option>, 52 | /// Value 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | pub value: Option>, 55 | /// Nonce 56 | #[serde(skip_serializing_if = "Option::is_none")] 57 | pub nonce: Option>, 58 | } 59 | 60 | impl Serialize for ToFilter { 61 | fn serialize(&self, serializer: S) -> Result 62 | where 63 | S: Serializer, 64 | { 65 | let mut map = serializer.serialize_map(Some(1))?; 66 | 67 | match self { 68 | Self::Address(a) => map.serialize_entry("eq", a)?, 69 | Self::Action => map.serialize_entry("action", "contract_creation")?, 70 | } 71 | map.end() 72 | } 73 | } 74 | 75 | impl ParityPendingTransactionFilter { 76 | /// Returns a filter builder 77 | pub fn builder() -> ParityPendingTransactionFilterBuilder { 78 | Default::default() 79 | } 80 | } 81 | 82 | /// Filter Builder 83 | #[derive(Default, Clone)] 84 | pub struct ParityPendingTransactionFilterBuilder { 85 | filter: ParityPendingTransactionFilter, 86 | } 87 | 88 | impl ParityPendingTransactionFilterBuilder { 89 | /// Sets `from` 90 | pub fn from(mut self, from: Address) -> Self { 91 | self.filter.from = Some(FilterCondition::Equal(from)); 92 | self 93 | } 94 | 95 | /// Sets `to` 96 | pub fn to(mut self, to_or_action: ToFilter) -> Self { 97 | self.filter.to = Some(to_or_action); 98 | self 99 | } 100 | 101 | /// Sets `gas` 102 | pub fn gas(mut self, gas: impl Into>) -> Self { 103 | self.filter.gas = Some(gas.into()); 104 | self 105 | } 106 | 107 | /// Sets `gas_price` 108 | pub fn gas_price(mut self, gas_price: impl Into>) -> Self { 109 | self.filter.gas_price = Some(gas_price.into()); 110 | self 111 | } 112 | 113 | /// Sets `value` 114 | pub fn value(mut self, value: impl Into>) -> Self { 115 | self.filter.value = Some(value.into()); 116 | self 117 | } 118 | 119 | /// Sets `nonce` 120 | pub fn nonce(mut self, nonce: impl Into>) -> Self { 121 | self.filter.nonce = Some(nonce.into()); 122 | self 123 | } 124 | 125 | /// Returns filter 126 | pub fn build(&self) -> ParityPendingTransactionFilter { 127 | self.filter.clone() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/types/proof.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Bytes; 2 | use ethereum_types::{H256, U256}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | ///Proof struct returned by eth_getProof method 6 | /// 7 | /// https://eips.ethereum.org/EIPS/eip-1186 8 | #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] 9 | pub struct Proof { 10 | /// the balance of the account. See eth_getBalance 11 | pub balance: U256, 12 | /// hash of the code of the account 13 | #[serde(rename = "codeHash")] 14 | pub code_hash: H256, 15 | /// nonce of the account. See eth_getTransactionCount 16 | pub nonce: U256, 17 | /// SHA3 of the StorageRoot. 18 | #[serde(rename = "storageHash")] 19 | pub storage_hash: H256, 20 | /// Array of rlp-serialized MerkleTree-Nodes, starting with the stateRoot-Node, following the path of the SHA3 (address) as key. 21 | #[serde(rename = "accountProof")] 22 | pub account_proof: Vec, 23 | /// Array of storage-entries as requested 24 | #[serde(rename = "storageProof")] 25 | pub storage_proof: Vec, 26 | } 27 | 28 | /// A key-value pair and it's state proof. 29 | #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] 30 | pub struct StorageProof { 31 | /// the requested storage key 32 | pub key: U256, 33 | /// the storage value 34 | pub value: U256, 35 | /// Array of rlp-serialized MerkleTree-Nodes, starting with the storageHash-Node, following the path of the SHA3 (key) as path. 36 | pub proof: Vec, 37 | } 38 | -------------------------------------------------------------------------------- /src/types/recovery.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{SignedData, SignedTransaction, H256}; 2 | use std::{ 3 | error::Error, 4 | fmt::{Display, Formatter, Result as FmtResult}, 5 | }; 6 | 7 | /// Data for recovering the public address of signed data. 8 | /// 9 | /// Note that the signature data is in 'Electrum' notation and may have chain 10 | /// replay protection applied. That means that `v` is expected to be `27`, `28`, 11 | /// or `35 + chain_id * 2` or `36 + chain_id * 2`. 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub struct Recovery { 14 | /// The message to recover 15 | pub message: RecoveryMessage, 16 | /// V value. 17 | pub v: u64, 18 | /// R value. 19 | pub r: H256, 20 | /// S value. 21 | pub s: H256, 22 | } 23 | 24 | impl Recovery { 25 | /// Creates new recovery data from its parts. 26 | pub fn new(message: M, v: u64, r: H256, s: H256) -> Recovery 27 | where 28 | M: Into, 29 | { 30 | Recovery { 31 | message: message.into(), 32 | v, 33 | r, 34 | s, 35 | } 36 | } 37 | 38 | /// Creates new recovery data from a raw signature. 39 | /// 40 | /// This parses a raw signature which is expected to be 65 bytes long where 41 | /// the first 32 bytes is the `r` value, the second 32 bytes the `s` value 42 | /// and the final byte is the `v` value in 'Electrum' notation. 43 | pub fn from_raw_signature(message: M, raw_signature: B) -> Result 44 | where 45 | M: Into, 46 | B: AsRef<[u8]>, 47 | { 48 | let bytes = raw_signature.as_ref(); 49 | 50 | if bytes.len() != 65 { 51 | return Err(ParseSignatureError); 52 | } 53 | 54 | let v = bytes[64]; 55 | let r = H256::from_slice(&bytes[0..32]); 56 | let s = H256::from_slice(&bytes[32..64]); 57 | 58 | Ok(Recovery::new(message, v as _, r, s)) 59 | } 60 | 61 | /// Retrieve the Recovery Id ("Standard V") 62 | /// 63 | /// Returns `None` if `v` value is invalid 64 | /// (equivalent of returning `4` in some implementations). 65 | pub fn recovery_id(&self) -> Option { 66 | match self.v { 67 | 27 => Some(0), 68 | 28 => Some(1), 69 | v if v >= 35 => Some(((v - 1) % 2) as _), 70 | _ => None, 71 | } 72 | } 73 | 74 | /// Retrieves the recovery id & compact signature in it's raw form. 75 | pub fn as_signature(&self) -> Option<([u8; 64], i32)> { 76 | let recovery_id = self.recovery_id()?; 77 | let signature = { 78 | let mut sig = [0u8; 64]; 79 | sig[..32].copy_from_slice(self.r.as_bytes()); 80 | sig[32..].copy_from_slice(self.s.as_bytes()); 81 | sig 82 | }; 83 | 84 | Some((signature, recovery_id)) 85 | } 86 | } 87 | 88 | impl<'a> From<&'a SignedData> for Recovery { 89 | fn from(signed: &'a SignedData) -> Self { 90 | Recovery::new(signed.message_hash, signed.v as _, signed.r, signed.s) 91 | } 92 | } 93 | 94 | impl<'a> From<&'a SignedTransaction> for Recovery { 95 | fn from(tx: &'a SignedTransaction) -> Self { 96 | Recovery::new(tx.message_hash, tx.v, tx.r, tx.s) 97 | } 98 | } 99 | 100 | /// Recovery message data. 101 | /// 102 | /// The message data can either be a binary message that is first hashed 103 | /// according to EIP-191 and then recovered based on the signature or a 104 | /// precomputed hash. 105 | #[derive(Clone, Debug, PartialEq)] 106 | pub enum RecoveryMessage { 107 | /// Message bytes 108 | Data(Vec), 109 | /// Message hash 110 | Hash(H256), 111 | } 112 | 113 | impl From<&[u8]> for RecoveryMessage { 114 | fn from(s: &[u8]) -> Self { 115 | s.to_owned().into() 116 | } 117 | } 118 | 119 | impl From> for RecoveryMessage { 120 | fn from(s: Vec) -> Self { 121 | RecoveryMessage::Data(s) 122 | } 123 | } 124 | 125 | impl From<&str> for RecoveryMessage { 126 | fn from(s: &str) -> Self { 127 | s.as_bytes().to_owned().into() 128 | } 129 | } 130 | 131 | impl From for RecoveryMessage { 132 | fn from(s: String) -> Self { 133 | RecoveryMessage::Data(s.into_bytes()) 134 | } 135 | } 136 | 137 | impl From<[u8; 32]> for RecoveryMessage { 138 | fn from(hash: [u8; 32]) -> Self { 139 | H256(hash).into() 140 | } 141 | } 142 | 143 | impl From for RecoveryMessage { 144 | fn from(hash: H256) -> Self { 145 | RecoveryMessage::Hash(hash) 146 | } 147 | } 148 | 149 | /// An error parsing a raw signature. 150 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 151 | pub struct ParseSignatureError; 152 | 153 | impl Display for ParseSignatureError { 154 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 155 | write!(f, "error parsing raw signature: wrong number of bytes, expected 65") 156 | } 157 | } 158 | 159 | impl Error for ParseSignatureError {} 160 | 161 | #[cfg(test)] 162 | mod tests { 163 | use super::*; 164 | use hex_literal::hex; 165 | 166 | #[test] 167 | fn recovery_signature() { 168 | let message = "Some data"; 169 | let v = 0x1cu8; 170 | let r = hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd").into(); 171 | let s = hex!("6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029").into(); 172 | 173 | let signed = SignedData { 174 | message: message.as_bytes().to_owned(), 175 | message_hash: hex!("1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655").into(), 176 | v, 177 | r, 178 | s, 179 | signature: hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c").into(), 180 | }; 181 | let expected_signature = ( 182 | hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029").into(), 1 183 | ); 184 | let (sig, id) = Recovery::from(&signed).as_signature().unwrap(); 185 | assert_eq!((sig.to_vec(), id), expected_signature); 186 | let (sig, id) = Recovery::new(message, v as _, r, s).as_signature().unwrap(); 187 | assert_eq!((sig.to_vec(), id), expected_signature); 188 | let (sig, id) = Recovery::from_raw_signature(message, &signed.signature.0) 189 | .unwrap() 190 | .as_signature() 191 | .unwrap(); 192 | assert_eq!((sig.to_vec(), id), expected_signature); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/types/signed.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{AccessList, Address, Bytes, CallRequest, H256, U256, U64}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Struct representing signed data returned from `Accounts::sign` method. 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] 6 | pub struct SignedData { 7 | /// The original message that was signed. 8 | pub message: Vec, 9 | /// The keccak256 hash of the signed data. 10 | #[serde(rename = "messageHash")] 11 | pub message_hash: H256, 12 | /// V value in 'Electrum' notation. 13 | pub v: u8, 14 | /// R value. 15 | pub r: H256, 16 | /// S value. 17 | pub s: H256, 18 | /// The signature bytes. 19 | pub signature: Bytes, 20 | } 21 | 22 | /// Transaction data for signing. 23 | /// 24 | /// The `Accounts::sign_transaction` method will fill optional fields with sane 25 | /// defaults when they are omitted. Specifically the signing account's current 26 | /// transaction count will be used for the `nonce`, the estimated recommended 27 | /// gas price will be used for `gas_price`, and the current network ID will be 28 | /// used for the `chain_id`. 29 | /// 30 | /// It is worth noting that the chain ID is not equivalent to the network ID. 31 | /// They happen to be the same much of the time but it is recommended to set 32 | /// this for signing transactions. 33 | /// 34 | /// `TransactionParameters` implements `Default` and uses `100_000` as the 35 | /// default `gas` to use for the transaction. This is more than enough for 36 | /// simple transactions sending ETH between accounts but may not be enough when 37 | /// interacting with complex contracts. It is recommended when interacting 38 | /// with contracts to use `Eth::estimate_gas` to estimate the required gas for 39 | /// the transaction. 40 | #[derive(Clone, Debug, PartialEq)] 41 | pub struct TransactionParameters { 42 | /// Transaction nonce (None for account transaction count) 43 | pub nonce: Option, 44 | /// To address 45 | pub to: Option
, 46 | /// Supplied gas 47 | pub gas: U256, 48 | /// Gas price (None for estimated gas price) 49 | pub gas_price: Option, 50 | /// Transferred value 51 | pub value: U256, 52 | /// Data 53 | pub data: Bytes, 54 | /// The chain ID (None for network ID) 55 | pub chain_id: Option, 56 | /// Transaction type, Some(1) for AccessList transaction, None for Legacy 57 | pub transaction_type: Option, 58 | /// Access list 59 | pub access_list: Option, 60 | /// Max fee per gas 61 | pub max_fee_per_gas: Option, 62 | /// miner bribe 63 | pub max_priority_fee_per_gas: Option, 64 | } 65 | 66 | /// The default fas for transactions. 67 | /// 68 | /// Unfortunately there is no way to construct `U256`s with const functions for 69 | /// constants so we just build it from it's `u64` words. Note that there is a 70 | /// unit test to verify that it is constructed correctly and holds the expected 71 | /// value of 100_000. 72 | const TRANSACTION_DEFAULT_GAS: U256 = U256([100_000, 0, 0, 0]); 73 | 74 | impl Default for TransactionParameters { 75 | fn default() -> Self { 76 | TransactionParameters { 77 | nonce: None, 78 | to: None, 79 | gas: TRANSACTION_DEFAULT_GAS, 80 | gas_price: None, 81 | value: U256::zero(), 82 | data: Bytes::default(), 83 | chain_id: None, 84 | transaction_type: None, 85 | access_list: None, 86 | max_fee_per_gas: None, 87 | max_priority_fee_per_gas: None, 88 | } 89 | } 90 | } 91 | 92 | impl From for TransactionParameters { 93 | fn from(call: CallRequest) -> Self { 94 | TransactionParameters { 95 | nonce: None, 96 | to: call.to, 97 | gas: call.gas.unwrap_or(TRANSACTION_DEFAULT_GAS), 98 | gas_price: call.gas_price, 99 | value: call.value.unwrap_or_default(), 100 | data: call.data.unwrap_or_default(), 101 | chain_id: None, 102 | transaction_type: call.transaction_type, 103 | access_list: call.access_list, 104 | max_fee_per_gas: call.max_fee_per_gas, 105 | max_priority_fee_per_gas: call.max_priority_fee_per_gas, 106 | } 107 | } 108 | } 109 | 110 | impl From for CallRequest { 111 | fn from(val: TransactionParameters) -> Self { 112 | CallRequest { 113 | from: None, 114 | to: val.to, 115 | gas: Some(val.gas), 116 | gas_price: val.gas_price, 117 | value: Some(val.value), 118 | data: Some(val.data), 119 | transaction_type: val.transaction_type, 120 | access_list: val.access_list, 121 | max_fee_per_gas: val.max_fee_per_gas, 122 | max_priority_fee_per_gas: val.max_priority_fee_per_gas, 123 | } 124 | } 125 | } 126 | 127 | /// Data for offline signed transaction 128 | #[derive(Clone, Debug, PartialEq)] 129 | pub struct SignedTransaction { 130 | /// The given message hash 131 | pub message_hash: H256, 132 | /// V value with chain replay protection. 133 | pub v: u64, 134 | /// R value. 135 | pub r: H256, 136 | /// S value. 137 | pub s: H256, 138 | /// The raw signed transaction ready to be sent with `send_raw_transaction` 139 | pub raw_transaction: Bytes, 140 | /// The transaction hash for the RLP encoded transaction. 141 | pub transaction_hash: H256, 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use super::*; 147 | 148 | #[test] 149 | fn verify_transaction_default_gas() { 150 | assert_eq!(TRANSACTION_DEFAULT_GAS, U256::from(100_000)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/types/sync_state.rs: -------------------------------------------------------------------------------- 1 | use crate::types::U256; 2 | use serde::{ 3 | de::{Deserializer, Error}, 4 | ser::Serializer, 5 | Deserialize, Serialize, 6 | }; 7 | 8 | /// Information about current blockchain syncing operations. 9 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct SyncInfo { 12 | /// The block at which import began. 13 | pub starting_block: U256, 14 | 15 | /// The highest currently synced block. 16 | pub current_block: U256, 17 | 18 | /// The estimated highest block. 19 | pub highest_block: U256, 20 | } 21 | 22 | /// The current state of blockchain syncing operations. 23 | #[derive(Debug, Clone, PartialEq)] 24 | pub enum SyncState { 25 | /// Blockchain is syncing. 26 | Syncing(SyncInfo), 27 | 28 | /// Blockchain is not syncing. 29 | NotSyncing, 30 | } 31 | 32 | // Sync info from subscription has a different key format 33 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 34 | #[serde(rename_all = "PascalCase")] 35 | struct SubscriptionSyncInfo { 36 | /// The block at which import began. 37 | pub starting_block: U256, 38 | 39 | /// The highest currently synced block. 40 | pub current_block: U256, 41 | 42 | /// The estimated highest block. 43 | pub highest_block: U256, 44 | } 45 | 46 | impl From for SyncInfo { 47 | fn from(s: SubscriptionSyncInfo) -> Self { 48 | Self { 49 | starting_block: s.starting_block, 50 | current_block: s.current_block, 51 | highest_block: s.highest_block, 52 | } 53 | } 54 | } 55 | 56 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 57 | struct SubscriptionSyncState { 58 | pub syncing: bool, 59 | pub status: Option, 60 | } 61 | 62 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 63 | #[serde(untagged)] 64 | enum SyncStateVariants { 65 | Rpc(SyncInfo), 66 | Subscription(SubscriptionSyncState), 67 | Boolean(bool), 68 | } 69 | 70 | // The `eth_syncing` method returns either `false` or an instance of the sync info object. 71 | // This doesn't play particularly well with the features exposed by `serde_derive`, 72 | // so we use the custom impls below to ensure proper behavior. 73 | impl<'de> Deserialize<'de> for SyncState { 74 | fn deserialize(deserializer: D) -> Result 75 | where 76 | D: Deserializer<'de>, 77 | { 78 | let v: SyncStateVariants = Deserialize::deserialize(deserializer)?; 79 | match v { 80 | SyncStateVariants::Rpc(info) => Ok(SyncState::Syncing(info)), 81 | SyncStateVariants::Subscription(state) => match state.status { 82 | None if !state.syncing => Ok(SyncState::NotSyncing), 83 | Some(ref info) if state.syncing => Ok(SyncState::Syncing(info.clone().into())), 84 | _ => Err(D::Error::custom( 85 | "expected object or `syncing = false`, got `syncing = true`", 86 | )), 87 | }, 88 | SyncStateVariants::Boolean(boolean) => { 89 | if !boolean { 90 | Ok(SyncState::NotSyncing) 91 | } else { 92 | Err(D::Error::custom("expected object or `false`, got `true`")) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl Serialize for SyncState { 100 | fn serialize(&self, serializer: S) -> Result 101 | where 102 | S: Serializer, 103 | { 104 | match *self { 105 | SyncState::Syncing(ref info) => info.serialize(serializer), 106 | SyncState::NotSyncing => false.serialize(serializer), 107 | } 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::{SyncInfo, SyncState}; 114 | 115 | #[test] 116 | fn should_deserialize_rpc_sync_info() { 117 | let sync_state = r#"{ 118 | "currentBlock": "0x42", 119 | "highestBlock": "0x9001", 120 | "knownStates": "0x1337", 121 | "pulledStates": "0x13", 122 | "startingBlock": "0x0" 123 | }"#; 124 | 125 | let value: SyncState = serde_json::from_str(sync_state).unwrap(); 126 | 127 | assert_eq!( 128 | value, 129 | SyncState::Syncing(SyncInfo { 130 | starting_block: 0x0.into(), 131 | current_block: 0x42.into(), 132 | highest_block: 0x9001.into() 133 | }) 134 | ); 135 | } 136 | 137 | #[test] 138 | fn should_deserialize_subscription_sync_info() { 139 | let sync_state = r#"{ 140 | "syncing": true, 141 | "status": { 142 | "CurrentBlock": "0x42", 143 | "HighestBlock": "0x9001", 144 | "KnownStates": "0x1337", 145 | "PulledStates": "0x13", 146 | "StartingBlock": "0x0" 147 | } 148 | }"#; 149 | 150 | let value: SyncState = serde_json::from_str(sync_state).unwrap(); 151 | 152 | assert_eq!( 153 | value, 154 | SyncState::Syncing(SyncInfo { 155 | starting_block: 0x0.into(), 156 | current_block: 0x42.into(), 157 | highest_block: 0x9001.into() 158 | }) 159 | ); 160 | } 161 | 162 | #[test] 163 | fn should_deserialize_boolean_not_syncing() { 164 | let sync_state = r#"false"#; 165 | let value: SyncState = serde_json::from_str(sync_state).unwrap(); 166 | 167 | assert_eq!(value, SyncState::NotSyncing); 168 | } 169 | 170 | #[test] 171 | fn should_deserialize_subscription_not_syncing() { 172 | let sync_state = r#"{ 173 | "syncing": false 174 | }"#; 175 | 176 | let value: SyncState = serde_json::from_str(sync_state).unwrap(); 177 | 178 | assert_eq!(value, SyncState::NotSyncing); 179 | } 180 | 181 | #[test] 182 | fn should_not_deserialize_invalid_boolean_syncing() { 183 | let sync_state = r#"true"#; 184 | let res: Result = serde_json::from_str(sync_state); 185 | assert!(res.is_err()); 186 | } 187 | 188 | #[test] 189 | fn should_not_deserialize_invalid_subscription_syncing() { 190 | let sync_state = r#"{ 191 | "syncing": true 192 | }"#; 193 | 194 | let res: Result = serde_json::from_str(sync_state); 195 | assert!(res.is_err()); 196 | } 197 | 198 | #[test] 199 | fn should_not_deserialize_invalid_subscription_not_syncing() { 200 | let sync_state = r#"{ 201 | "syncing": false, 202 | "status": { 203 | "CurrentBlock": "0x42", 204 | "HighestBlock": "0x9001", 205 | "KnownStates": "0x1337", 206 | "PulledStates": "0x13", 207 | "StartingBlock": "0x0" 208 | } 209 | }"#; 210 | 211 | let res: Result = serde_json::from_str(sync_state); 212 | assert!(res.is_err()); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/types/traces.rs: -------------------------------------------------------------------------------- 1 | //! Types for the Parity Ad-Hoc Trace API 2 | 3 | use crate::types::{Action, ActionType, Bytes, Res, H160, H256, U256}; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::BTreeMap; 6 | 7 | #[derive(Debug, Clone, Serialize)] 8 | /// Description of the type of trace to make 9 | pub enum TraceType { 10 | /// Transaction Trace 11 | #[serde(rename = "trace")] 12 | Trace, 13 | /// Virtual Machine Execution Trace 14 | #[serde(rename = "vmTrace")] 15 | VmTrace, 16 | /// State Difference 17 | #[serde(rename = "stateDiff")] 18 | StateDiff, 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 22 | /// Ad-Hoc trace API type 23 | pub struct BlockTrace { 24 | /// Output Bytes 25 | pub output: Bytes, 26 | /// Transaction Trace 27 | pub trace: Option>, 28 | /// Virtual Machine Execution Trace 29 | #[serde(rename = "vmTrace")] 30 | pub vm_trace: Option, 31 | /// State Difference 32 | #[serde(rename = "stateDiff")] 33 | pub state_diff: Option, 34 | /// Transaction Hash 35 | #[serde(rename = "transactionHash")] 36 | pub transaction_hash: Option, 37 | } 38 | 39 | //---------------- State Diff ---------------- 40 | /// Aux type for Diff::Changed. 41 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 42 | pub struct ChangedType { 43 | /// Previous value. 44 | pub from: T, 45 | /// Current value. 46 | pub to: T, 47 | } 48 | 49 | /// Serde-friendly `Diff` shadow. 50 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 51 | pub enum Diff { 52 | /// No change. 53 | #[serde(rename = "=")] 54 | Same, 55 | /// A new value has been set. 56 | #[serde(rename = "+")] 57 | Born(T), 58 | /// A value has been removed. 59 | #[serde(rename = "-")] 60 | Died(T), 61 | /// Value changed. 62 | #[serde(rename = "*")] 63 | Changed(ChangedType), 64 | } 65 | 66 | /// Serde-friendly `AccountDiff` shadow. 67 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 68 | pub struct AccountDiff { 69 | /// Account balance. 70 | pub balance: Diff, 71 | /// Account nonce. 72 | pub nonce: Diff, 73 | /// Account code. 74 | pub code: Diff, 75 | /// Account storage. 76 | pub storage: BTreeMap>, 77 | } 78 | 79 | /// Serde-friendly `StateDiff` shadow. 80 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 81 | pub struct StateDiff(pub BTreeMap); 82 | 83 | // ------------------ Trace ------------- 84 | /// Trace 85 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 86 | pub struct TransactionTrace { 87 | /// Trace address 88 | #[serde(rename = "traceAddress")] 89 | pub trace_address: Vec, 90 | /// Subtraces 91 | pub subtraces: usize, 92 | /// Action 93 | pub action: Action, 94 | /// Action Type 95 | #[serde(rename = "type")] 96 | pub action_type: ActionType, 97 | /// Result 98 | pub result: Option, 99 | /// Error 100 | pub error: Option, 101 | } 102 | 103 | // ---------------- VmTrace ------------------------------ 104 | #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)] 105 | /// A record of a full VM trace for a CALL/CREATE. 106 | pub struct VMTrace { 107 | /// The code to be executed. 108 | pub code: Bytes, 109 | /// The operations executed. 110 | pub ops: Vec, 111 | } 112 | 113 | #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)] 114 | /// A record of the execution of a single VM operation. 115 | pub struct VMOperation { 116 | /// The program counter. 117 | pub pc: usize, 118 | /// The gas cost for this instruction. 119 | pub cost: u64, 120 | /// Information concerning the execution of the operation. 121 | pub ex: Option, 122 | /// Subordinate trace of the CALL/CREATE if applicable. 123 | // #[serde(bound="VMTrace: Deserialize")] 124 | pub sub: Option, 125 | } 126 | 127 | #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)] 128 | /// A record of an executed VM operation. 129 | pub struct VMExecutedOperation { 130 | /// The total gas used. 131 | #[serde(rename = "used")] 132 | pub used: u64, 133 | /// The stack item placed, if any. 134 | pub push: Vec, 135 | /// If altered, the memory delta. 136 | #[serde(rename = "mem")] 137 | pub mem: Option, 138 | /// The altered storage value, if any. 139 | #[serde(rename = "store")] 140 | pub store: Option, 141 | } 142 | 143 | #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)] 144 | /// A diff of some chunk of memory. 145 | pub struct MemoryDiff { 146 | /// Offset into memory the change begins. 147 | pub off: usize, 148 | /// The changed data. 149 | pub data: Bytes, 150 | } 151 | 152 | #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)] 153 | /// A diff of some storage value. 154 | pub struct StorageDiff { 155 | /// Which key in storage is changed. 156 | pub key: U256, 157 | /// What the value has been changed to. 158 | pub val: U256, 159 | } 160 | 161 | #[cfg(test)] 162 | mod tests { 163 | use super::*; 164 | 165 | // tx: https://etherscan.io/tx/0x4a91b11dbd2b11c308cfe7775eac2036f20c501691e3f8005d83b2dcce62d6b5 166 | // using the 'trace_replayTransaction' API function 167 | // with 'trace', 'vmTrace', 'stateDiff' 168 | const EXAMPLE_TRACE: &str = include!("./example-trace-str.rs"); 169 | 170 | // block: https://etherscan.io/block/46147 171 | // using the 'trace_replayBlockTransactions' API function 172 | // with 'trace', 'vmTrace', 'stateDiff' 173 | const EXAMPLE_TRACES: &str = include!("./example-traces-str.rs"); 174 | 175 | #[test] 176 | fn test_serialize_trace_type() { 177 | let trace_type_str = r#"["trace","vmTrace","stateDiff"]"#; 178 | let trace_type = vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff]; 179 | 180 | let se_trace_str: String = serde_json::to_string(&trace_type).unwrap(); 181 | assert_eq!(trace_type_str, se_trace_str); 182 | } 183 | 184 | #[test] 185 | fn test_deserialize_blocktrace() { 186 | let _trace: BlockTrace = serde_json::from_str(EXAMPLE_TRACE).unwrap(); 187 | } 188 | 189 | #[test] 190 | fn test_deserialize_blocktraces() { 191 | let _traces: Vec = serde_json::from_str(EXAMPLE_TRACES).unwrap(); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/types/transaction_id.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{BlockId, Index, H256}; 2 | 3 | /// Transaction Identifier 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub enum TransactionId { 6 | /// By hash 7 | Hash(H256), 8 | /// By block and index 9 | Block(BlockId, Index), 10 | } 11 | 12 | impl From for TransactionId { 13 | fn from(hash: H256) -> Self { 14 | TransactionId::Hash(hash) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/types/uint.rs: -------------------------------------------------------------------------------- 1 | pub use ethereum_types::{BigEndianHash, Bloom as H2048, H128, H160, H256, H512, H520, H64, U128, U256, U64}; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use super::*; 6 | use wasm_bindgen_test::*; 7 | 8 | type Res = Result; 9 | 10 | #[test] 11 | fn should_compare_correctly() { 12 | let mut arr = [0u8; 32]; 13 | arr[31] = 0; 14 | arr[30] = 15; 15 | arr[29] = 1; 16 | arr[28] = 0; 17 | arr[27] = 10; 18 | let a = U256::from(arr.as_ref()); 19 | arr[27] = 9; 20 | let b = U256::from(arr.as_ref()); 21 | let c = U256::from(0); 22 | let d = U256::from(10_000); 23 | 24 | assert!(b < a); 25 | assert!(d < a); 26 | assert!(d < b); 27 | assert!(c < a); 28 | assert!(c < b); 29 | assert!(c < d); 30 | } 31 | 32 | #[test] 33 | fn should_display_correctly() { 34 | let mut arr = [0u8; 32]; 35 | arr[31] = 0; 36 | arr[30] = 15; 37 | arr[29] = 1; 38 | arr[28] = 0; 39 | arr[27] = 10; 40 | let a = U256::from(arr.as_ref()); 41 | let b = U256::from(1023); 42 | let c = U256::from(0); 43 | let d = U256::from(10000); 44 | 45 | // Debug 46 | assert_eq!(&format!("{:?}", a), "42949742336"); 47 | assert_eq!(&format!("{:?}", b), "1023"); 48 | assert_eq!(&format!("{:?}", c), "0"); 49 | assert_eq!(&format!("{:?}", d), "10000"); 50 | 51 | // Display 52 | assert_eq!(&format!("{}", a), "42949742336"); 53 | assert_eq!(&format!("{}", b), "1023"); 54 | assert_eq!(&format!("{}", c), "0"); 55 | assert_eq!(&format!("{}", d), "10000"); 56 | 57 | // Lowerhex 58 | assert_eq!(&format!("{:x}", a), "a00010f00"); 59 | assert_eq!(&format!("{:x}", b), "3ff"); 60 | assert_eq!(&format!("{:x}", c), "0"); 61 | assert_eq!(&format!("{:x}", d), "2710"); 62 | 63 | // Prefixed Lowerhex 64 | assert_eq!(&format!("{:#x}", a), "0xa00010f00"); 65 | assert_eq!(&format!("{:#x}", b), "0x3ff"); 66 | assert_eq!(&format!("{:#x}", c), "0x0"); 67 | assert_eq!(&format!("{:#x}", d), "0x2710"); 68 | } 69 | 70 | #[test] 71 | fn should_display_hash_correctly() { 72 | let mut arr = [0; 16]; 73 | arr[15] = 0; 74 | arr[14] = 15; 75 | arr[13] = 1; 76 | arr[12] = 0; 77 | arr[11] = 10; 78 | let a = H128::from_uint(&arr.into()); 79 | let b = H128::from_uint(&1023.into()); 80 | let c = H128::from_uint(&0.into()); 81 | let d = H128::from_uint(&10000.into()); 82 | 83 | // Debug 84 | assert_eq!(&format!("{:?}", a), "0x00000000000000000000000a00010f00"); 85 | assert_eq!(&format!("{:?}", b), "0x000000000000000000000000000003ff"); 86 | assert_eq!(&format!("{:?}", c), "0x00000000000000000000000000000000"); 87 | assert_eq!(&format!("{:?}", d), "0x00000000000000000000000000002710"); 88 | 89 | // Display 90 | assert_eq!(&format!("{}", a), "0x0000…0f00"); 91 | assert_eq!(&format!("{}", b), "0x0000…03ff"); 92 | assert_eq!(&format!("{}", c), "0x0000…0000"); 93 | assert_eq!(&format!("{}", d), "0x0000…2710"); 94 | 95 | // Lowerhex 96 | assert_eq!(&format!("{:x}", a), "00000000000000000000000a00010f00"); 97 | assert_eq!(&format!("{:x}", b), "000000000000000000000000000003ff"); 98 | assert_eq!(&format!("{:x}", c), "00000000000000000000000000000000"); 99 | assert_eq!(&format!("{:x}", d), "00000000000000000000000000002710"); 100 | } 101 | 102 | #[test] 103 | fn should_deserialize_hash_correctly() { 104 | let deserialized1: H128 = serde_json::from_str(r#""0x00000000000000000000000a00010f00""#).unwrap(); 105 | 106 | assert_eq!(deserialized1, H128::from_low_u64_be(0xa00010f00)); 107 | } 108 | 109 | #[test] 110 | fn should_serialize_u256() { 111 | let serialized1 = serde_json::to_string(&U256::from(0)).unwrap(); 112 | let serialized2 = serde_json::to_string(&U256::from(1)).unwrap(); 113 | let serialized3 = serde_json::to_string(&U256::from(16)).unwrap(); 114 | let serialized4 = serde_json::to_string(&U256::from(256)).unwrap(); 115 | 116 | assert_eq!(serialized1, r#""0x0""#); 117 | assert_eq!(serialized2, r#""0x1""#); 118 | assert_eq!(serialized3, r#""0x10""#); 119 | assert_eq!(serialized4, r#""0x100""#); 120 | } 121 | 122 | #[test] 123 | fn should_successfully_deserialize_decimals() { 124 | let deserialized1: Res = serde_json::from_str(r#""""#); 125 | let deserialized2: Res = serde_json::from_str(r#""0""#); 126 | let deserialized3: Res = serde_json::from_str(r#""10""#); 127 | let deserialized4: Res = serde_json::from_str(r#""1000000""#); 128 | let deserialized5: Res = serde_json::from_str(r#""1000000000000000000""#); 129 | 130 | assert!(deserialized1.is_err()); 131 | assert!(deserialized2.is_ok()); 132 | assert!(deserialized3.is_ok()); 133 | assert!(deserialized4.is_ok()); 134 | assert!(deserialized5.is_ok()); 135 | } 136 | 137 | #[test] 138 | fn should_deserialize_u256() { 139 | let deserialized1: Result = serde_json::from_str(r#""0x""#); 140 | let deserialized2: U256 = serde_json::from_str(r#""0x0""#).unwrap(); 141 | let deserialized3: U256 = serde_json::from_str(r#""0x1""#).unwrap(); 142 | let deserialized4: U256 = serde_json::from_str(r#""0x01""#).unwrap(); 143 | let deserialized5: U256 = serde_json::from_str(r#""0x100""#).unwrap(); 144 | 145 | assert!(deserialized1.is_err()); 146 | assert_eq!(deserialized2, 0.into()); 147 | assert_eq!(deserialized3, 1.into()); 148 | assert_eq!(deserialized4, 1.into()); 149 | assert_eq!(deserialized5, 256.into()); 150 | } 151 | 152 | #[test] 153 | fn test_to_from_u64() { 154 | assert_eq!(1u64, U256::from(1u64).low_u64()); 155 | assert_eq!(11u64, U256::from(11u64).low_u64()); 156 | assert_eq!(111u64, U256::from(111u64).low_u64()); 157 | } 158 | 159 | // Getting random numbers uses a different code path in JS, so we sanity 160 | // check it here. 161 | #[wasm_bindgen_test] 162 | fn random_doesnt_panic() { 163 | H160::random(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/types/work.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{H256, U256}; 2 | use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; 3 | use serde_json::{self, Value}; 4 | 5 | /// Miner's work package 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub struct Work { 8 | /// The proof-of-work hash. 9 | pub pow_hash: H256, 10 | /// The seed hash. 11 | pub seed_hash: H256, 12 | /// The target. 13 | pub target: H256, 14 | /// The block number: this isn't always stored. 15 | pub number: Option, 16 | } 17 | 18 | impl<'a> Deserialize<'a> for Work { 19 | fn deserialize(deserializer: D) -> Result 20 | where 21 | D: Deserializer<'a>, 22 | { 23 | let v: Value = Deserialize::deserialize(deserializer)?; 24 | 25 | let (pow_hash, seed_hash, target, number) = serde_json::from_value::<(H256, H256, H256, u64)>(v.clone()) 26 | .map(|(pow_hash, seed_hash, target, number)| (pow_hash, seed_hash, target, Some(number))) 27 | .or_else(|_| { 28 | serde_json::from_value::<(H256, H256, H256)>(v) 29 | .map(|(pow_hash, seed_hash, target)| (pow_hash, seed_hash, target, None)) 30 | }) 31 | .map_err(|e| D::Error::custom(format!("Cannot deserialize Work: {:?}", e)))?; 32 | 33 | Ok(Work { 34 | pow_hash, 35 | seed_hash, 36 | target, 37 | number, 38 | }) 39 | } 40 | } 41 | 42 | impl Serialize for Work { 43 | fn serialize(&self, s: S) -> Result 44 | where 45 | S: Serializer, 46 | { 47 | match self.number.as_ref() { 48 | Some(num) => (&self.pow_hash, &self.seed_hash, &self.target, U256::from(*num)).serialize(s), 49 | None => (&self.pow_hash, &self.seed_hash, &self.target).serialize(s), 50 | } 51 | } 52 | } 53 | --------------------------------------------------------------------------------