From a9ca8ae9f4156808ecc5e1c868ef80badcf4f701 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Aug 2023 12:42:17 -0700 Subject: [PATCH 1/5] docs: add readmes --- .github/CODEOWNERS | 6 + .github/ISSUE_TEMPLATE/BUG-FORM.yml | 43 +++ .github/ISSUE_TEMPLATE/FEATURE-FORM.yml | 31 ++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/PULL_REQUEST_TEMPLATE.md | 34 ++ CONTRIBUTING.md | 308 ++++++++++++++++++ README.md | 121 ++++++- crates/json-rpc/README.md | 36 ++ crates/json-rpc/src/lib.rs | 4 +- crates/json-rpc/src/request.rs | 17 +- crates/json-rpc/src/response.rs | 16 +- crates/json-rpc/src/result.rs | 10 +- crates/networks/README.md | 64 ++++ crates/provider/src/lib.rs | 119 ------- crates/{provider => providers}/Cargo.toml | 3 +- crates/providers/README.md | 29 ++ crates/{provider => providers}/src/builder.rs | 36 +- crates/providers/src/lib.rs | 165 ++++++++++ crates/transports/README.md | 57 +++- crates/transports/src/batch.rs | 10 +- crates/transports/src/call.rs | 12 +- crates/transports/src/client.rs | 6 +- .../transports/src/transports/json_service.rs | 8 +- 23 files changed, 968 insertions(+), 172 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/BUG-FORM.yml create mode 100644 .github/ISSUE_TEMPLATE/FEATURE-FORM.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md create mode 100644 crates/json-rpc/README.md create mode 100644 crates/networks/README.md delete mode 100644 crates/provider/src/lib.rs rename crates/{provider => providers}/Cargo.toml (91%) create mode 100644 crates/providers/README.md rename crates/{provider => providers}/src/builder.rs (69%) create mode 100644 crates/providers/src/lib.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..aa54b7a1043 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +* @danipopes @gakonst @prestwich + +crates/json-rpc @prestwich +crates/transports @prestwich +crates/networks @prestwich +crates/providers @prestwich \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml new file mode 100644 index 00000000000..9cc25c35ef8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -0,0 +1,43 @@ +name: Bug report +description: File a bug report +labels: ["bug"] +title: "[Bug] " +body: + - type: markdown + attributes: + value: | + Please ensure that the bug has not already been filed in the issue tracker. + + Thanks for taking the time to report this bug! + - type: dropdown + attributes: + label: Component + description: What component is the bug in? + multiple: true + options: + - json-rpc + - transports + - networks + - providers + validations: + required: true + - type: input + attributes: + label: What version of Alloy are you on? + placeholder: "Run `carge tree | grep alloy` and paste the output here" + - type: dropdown + attributes: + label: Operating System + description: What operating system are you on? + options: + - Windows + - macOS (Intel) + - macOS (Apple Silicon) + - Linux + - Other (please specify) + - type: textarea + attributes: + label: Describe the bug + description: Please include code snippets as well if relevant. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml new file mode 100644 index 00000000000..dd62018175a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml @@ -0,0 +1,31 @@ +name: Feature request +description: Suggest a feature +labels: ["enhancement"] +title: "[Feature] " +body: + - type: markdown + attributes: + value: | + Please ensure that the feature has not already been requested in the issue tracker. + - type: dropdown + attributes: + label: Component + description: What component is the feature for? + multiple: true + options: + - json-rpc + - transports + - networks + - providers + validations: + required: true + - type: textarea + attributes: + label: Describe the feature you would like + description: Please also describe your goals for the feature. What problems it solves, how it would be used, etc. + validations: + required: true + - type: textarea + attributes: + label: Additional context + description: Add any other context to the feature (like screenshots, resources) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..bbfbdcc0182 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Support + url: https://t.me/ethers_rs + about: This issue tracker is only for bugs and feature requests. Support is available on Telegram! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..278309881ed --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ + + + + +## Motivation + + + +## Solution + + + +## PR Checklist + +- [ ] Added Tests +- [ ] Added Documentation +- [ ] Breaking changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..3f18c94eb14 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,308 @@ +# Contributing to Alloy + +:balloon: Thanks for your help improving the project! We are so happy to have +you! + +There are opportunities to contribute to Alloy at any level. It doesn't +matter if you are just getting started with Rust or are the most weathered +expert, we can use your help. + +**No contribution is too small and all contributions are valued.** + +This guide will help you get started. **Do not let this guide intimidate you**. +It should be considered a map to help you navigate the process. + +The [dev channel][dev] is available for any concerns not covered in this guide, +please join us! + +[dev]: https://t.me/ethers_rs + +## Conduct + +The Alloy project adheres to the [Rust Code of Conduct][coc]. This describes +the _minimum_ behavior expected from all contributors. Instances of violations +of the Code of Conduct can be reported by contacting the project team at +[james@prestwich](mailto:james@prestwi.ch). + +[coc]: https://www.rust-lang.org/policies/code-of-conduct + +## Contributing in Issues + +For any issue, there are fundamentally three ways an individual can contribute: + +1. By opening the issue for discussion: For instance, if you believe that you + have uncovered a bug in Alloy, creating a new issue in the Alloy + issue tracker is the way to report it. + +2. By helping to triage the issue: This can be done by providing + supporting details (a test case that demonstrates a bug), providing + suggestions on how to address the issue, or ensuring that the issue is tagged + correctly. + +3. By helping to resolve the issue: Typically this is done either in the form of + demonstrating that the issue reported is not a problem after all, or more + often, by opening a Pull Request that changes some bit of something in + Alloy in a concrete and reviewable manner. + +**Anybody can participate in any stage of contribution**. We urge you to +participate in the discussion around bugs and participate in reviewing PRs. + +### Asking for General Help + +If you have reviewed existing documentation and still have questions or are +having problems, you can open an issue asking for help. + +In exchange for receiving help, we ask that you contribute back a documentation +PR that helps others avoid the problems that you encountered. + +### Submitting a Bug Report + +When opening a new issue in the Alloy issue tracker, users will be presented +with a [basic template][template] that should be filled in. If you believe that +you have uncovered a bug, please fill out this form, following the template to +the best of your ability. Do not worry if you cannot answer every detail, just +fill in what you can. + +The two most important pieces of information we need in order to properly +evaluate the report is a description of the behavior you are seeing and a simple +test case we can use to recreate the problem on our own. If we cannot recreate +the issue, it becomes impossible for us to fix. + +In order to rule out the possibility of bugs introduced by userland code, test +cases should be limited, as much as possible, to using only Alloy APIs. + +See [How to create a Minimal, Complete, and Verifiable example][mcve]. + +[mcve]: https://stackoverflow.com/help/mcve +[template]: .github/PULL_REQUEST_TEMPLATE.md + +### Triaging a Bug Report + +Once an issue has been opened, it is not uncommon for there to be discussion +around it. Some contributors may have differing opinions about the issue, +including whether the behavior being seen is a bug or a feature. This discussion +is part of the process and should be kept focused, helpful, and professional. + +Short, clipped responses—that provide neither additional context nor supporting +detail—are not helpful or professional. To many, such responses are simply +annoying and unfriendly. + +Contributors are encouraged to help one another make forward progress as much as +possible, empowering one another to solve issues collaboratively. If you choose +to comment on an issue that you feel either is not a problem that needs to be +fixed, or if you encounter information in an issue that you feel is incorrect, +explain why you feel that way with additional supporting context, and be willing +to be convinced that you may be wrong. By doing so, we can often reach the +correct outcome much faster. + +### Resolving a Bug Report + +In the majority of cases, issues are resolved by opening a Pull Request. The +process for opening and reviewing a Pull Request is similar to that of opening +and triaging issues, but carries with it a necessary review and approval +workflow that ensures that the proposed changes meet the minimal quality and +functional guidelines of the Alloy project. + +## Pull Requests + +Pull Requests are the way concrete changes are made to the code, documentation, +and dependencies in the Alloy repository. + +Even tiny pull requests (e.g., one character pull request fixing a typo in API +documentation) are greatly appreciated. Before making a large change, it is +usually a good idea to first open an issue describing the change to solicit +feedback and guidance. This will increase the likelihood of the PR getting +merged. + +When opening a PR **please select the "Allow Edits From Maintainers" option**. +Alloy maintains strict standards for code quality and style, as well as +commit signing. This option allows us to make small changes to your PR to bring +it in line with these standards. It helps us get your PR in faster, and with +less work from you. + +### Cargo Commands + +This section lists some commonly needed commands. + +```sh +cargo check --all-features +cargo +nightly fmt --all +cargo build --all-features +cargo test --all-features +cargo +nightly clippy --all-features +``` + +### Tests + +If the change being proposed alters code (as opposed to only documentation for +example), it is either adding new functionality to Alloy or it is fixing +existing, broken functionality. In both of these cases, the pull request should +include one or more tests to ensure that Alloy does not regress in the +future. + +#### Unit Tests + +Functions which have very specific tasks should be unit tested. We encourage +using table tests to cover a large number of cases in a succinct readable +manner. + +#### Integration tests + +Integration tests go in the same crate as the code they are testing, in the +`tests/` directory. + +The best strategy for writing a new integration test is to look at existing +integration tests in the crate and follow the style. + +#### Documentation tests + +Ideally, every API has at least one [documentation test] that demonstrates how +to use the API. Documentation tests are run with `cargo test --doc`. This +ensures that the example is correct and provides additional test coverage. + +The trick to documentation tests is striking a balance between being succinct +for a reader to understand and actually testing the API. + +Lines that start with `/// #` are removed when the documentation is generated. +They are only there to get the test to run. Use this trick to keep your example +succinct in the user-facing tests :) + +### Commits + +It is a recommended best practice to keep your changes as logically grouped as +possible within individual commits. There is no limit to the number of commits +any single Pull Request may have, and many contributors find it easier to review +changes that are split across multiple commits. + +That said, if you have a number of commits that are "checkpoints" and don't +represent a single logical change, please squash those together. + +Note that multiple commits often get squashed when they are landed (see the +notes about [commit squashing](#commit-squashing)). + +#### Commit message guidelines + +Commit messages should follow the +[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +specification. + +Here's a few examples from the master branch's commit log: + +- feat(abigen): support empty events +- chore: bump crypto deps +- test: simplify test cleanup +- fmt: run rustfmt + +### Opening the Pull Request + +From within GitHub, opening a new Pull Request will present you with a +[template] that should be filled out. Please try to do your best at filling out +the details, but feel free to skip parts if you're not sure what to put. + +[template]: .github/PULL_REQUEST_TEMPLATE.md + +### Discuss and update + +You will probably get feedback or requests for changes to your Pull Request. +This is a big part of the submission process so don't be discouraged! Some +contributors may sign off on the Pull Request right away, others may have +more detailed comments or feedback. This is a necessary part of the process +in order to evaluate whether the changes are correct and necessary. + +**Any community member can review a PR and you might get conflicting feedback**. +Keep an eye out for comments from code owners to provide guidance on conflicting +feedback. + +**Once the PR is open, do not rebase the commits**. See +[Commit Squashing](#commit-squashing) for more details. + +### Commit Squashing + +In most cases, **do not squash commits that you add to your Pull Request during +the review process**. When the commits in your Pull Request land, they may be +squashed into one commit per logical change. Metadata will be added to the +commit message (including links to the Pull Request, links to relevant issues, +and the names of the reviewers). The commit history of your Pull Request, +however, will stay intact on the Pull Request page. + +## Reviewing Pull Requests + +**Any Alloy community member is welcome to review any pull request**. + +All Alloy contributors who choose to review and provide feedback on Pull +Requests have a responsibility to both the project and the individual making the +contribution. Reviews and feedback must be helpful, insightful, and geared +towards improving the contribution as opposed to simply blocking it. If there +are reasons why you feel the PR should not land, explain what those are. Do not +expect to be able to block a Pull Request from advancing simply because you say +"No" without giving an explanation. Be open to having your mind changed. Be open +to working with the contributor to make the Pull Request better. + +Reviews that are dismissive or disrespectful of the contributor or any other +reviewers are strictly counter to the Code of Conduct. + +When reviewing a Pull Request, the primary goals are for the codebase to improve +and for the person submitting the request to succeed. **Even if a Pull Request +does not land, the submitters should come away from the experience feeling like +their effort was not wasted or unappreciated**. Every Pull Request from a new +contributor is an opportunity to grow the community. + +### Review a bit at a time. + +Do not overwhelm new contributors. + +It is tempting to micro-optimize and make everything about relative performance, +perfect grammar, or exact style matches. Do not succumb to that temptation. + +Focus first on the most significant aspects of the change: + +1. Does this change make sense for Alloy? +2. Does this change make Alloy better, even if only incrementally? +3. Are there clear bugs or larger scale issues that need attending to? +4. Is the commit message readable and correct? If it contains a breaking change + is it clear enough? + +Note that only **incremental** improvement is needed to land a PR. This means +that the PR does not need to be perfect, only better than the status quo. Follow +up PRs may be opened to continue iterating. + +When changes are necessary, _request_ them, do not _demand_ them, and **do not +assume that the submitter already knows how to add a test or run a benchmark**. + +Specific performance optimization techniques, coding styles and conventions +change over time. The first impression you give to a new contributor never does. + +Nits (requests for small changes that are not essential) are fine, but try to +avoid stalling the Pull Request. Most nits can typically be fixed by the +Alloy Collaborator landing the Pull Request but they can also be an +opportunity for the contributor to learn a bit more about the project. + +It is always good to clearly indicate nits when you comment: e.g. +`Nit: change foo() to bar(). But this is not blocking.` + +If your comments were addressed but were not folded automatically after new +commits or if they proved to be mistaken, please, [hide them][hiding-a-comment] +with the appropriate reason to keep the conversation flow concise and relevant. + +### Be aware of the person behind the code + +Be aware that _how_ you communicate requests and reviews in your feedback can +have a significant impact on the success of the Pull Request. Yes, we may land +a particular change that makes Alloy better, but the individual might just +not want to have anything to do with Alloy ever again. The goal is not just +having good code. + +### Abandoned or Stalled Pull Requests + +If a Pull Request appears to be abandoned or stalled, it is polite to first +check with the contributor to see if they intend to continue the work before +checking if they would mind if you took it over (especially if it just has nits +left). When doing so, it is courteous to give the original contributor credit +for the work they started (either by preserving their name and email address in +the commit log, or by using an `Author: ` meta-data tag in the commit. + +_Adapted from the [Tokio contributing guide]_. + +[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment +[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html +[Tokio contributing guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md diff --git a/README.md b/README.md index 099098b390c..fde93167cfc 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,109 @@ -# alloy-next - -### Layout - -- alloy-json-rpc - - Core data types for JSON-RPC 2.0 -- alloy-transports - - Transports and RPC call futures. -- alloy-networks - - Network abstraction for RPC types. Allows capturing different RPC param and response types on a per-network basis. -- alloy-provider - - Based on ethers::middleware::Middleware, but abstract over , and object-safe. +# Alloy + +Alloy connects applications to blockchains. + +Alloy is a rewrite of [`ethers-rs`] from the ground up, with exciting new +features, high performance, and excellent docs. + +[`ethers-rs`] will continue to be maintained until we have achieved +feature-parity in Alloy. No action is currently needed from devs. + +[`ethers-rs`]: https://github.com/gakonst/ethers-rs + +[![Telegram chat][telegram-badge]][telegram-url] + +## Overview + +This repository contains the following crates: + +- [`alloy-json-rpc`] - Core data types for JSON-RPC 2.0 clients. +- [`alloy-transports`] - Transport implementations for JSON-RPC 2.0 clients. +- [`alloy-networks`] - Network abstraction for RPC types. Allows capturing + different RPC param and response types on a per-network basis. +- [`alloy-providers`] - Based on `ethers::middleware::Middleware`, but abstract + over a `Network`, and object-safe. + +[`alloy-json-rpc`]: ./crates/json-rpc +[`alloy-transports`]: ./crates/transports +[`alloy-networks`]: ./crates/networks +[`alloy-providers`]: ./crates/providers + +## Supported Rust Versions + + + +Alloy will keep a rolling MSRV (minimum supported rust version) policy of **at +least** 6 months. When increasing the MSRV, the new Rust version must have been +released at least six months ago. The current MSRV is 1.65.0. + +Note that the MSRV is not increased automatically, and only as part of a minor +release. + +## Contributing + +Thanks for your help improving the project! We are so happy to have you! We have +[a contributing guide](./CONTRIBUTING.md) to help you get involved in the +Alloy project. + +Pull requests will not be merged unless CI passes, so please ensure that your +contribution follows the linting rules and passes clippy. + +## WASM support + +We provide full support for all the `wasm*-*` targets. If a crate does not +build on a WASM target, please [open an issue]. + +When building for the `wasm32-unknown-unknown` target and the `"getrandom"` +feature is enabled, compilation for the `getrandom` crate will fail. This is +expected: see [their documentation][getrandom] for more details. + +To fix this, either disable the `"getrandom"` feature on `alloy-core` or add +`getrandom` to your dependencies with the `"js"` feature enabled: + +```toml +getrandom = { version = "0.2", features = ["js"] } +``` + +There is currently no plan to provide an official JS/TS-accessible library +interface, as we believe [viem] or [ethers.js] serve that need very well. + +[open an issue]: https://github.com/alloy-rs/core/issues/new/choose +[getrandom]: https://docs.rs/getrandom/#webassembly-support +[viem]: https://viem.sh +[ethers.js]: https://docs.ethers.io/v6/ + +## Note on `no_std` + +Because these crates are primarily json-rpc focused, we do not intend to support +`no_std` for them at this time. + +## Credits + +None of these crates would have been possible without the great work done in: + +- [`ethers.js`](https://github.com/ethers-io/ethers.js/) +- [`rust-web3`](https://github.com/tomusdrw/rust-web3/) +- [`ruint`](https://github.com/recmo/uint) +- [`ethabi`](https://github.com/rust-ethereum/ethabi) +- [`ethcontract-rs`](https://github.com/gnosis/ethcontract-rs/) +- [`guac_rs`](https://github.com/althea-net/guac_rs/) + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in these crates by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. + diff --git a/crates/json-rpc/README.md b/crates/json-rpc/README.md new file mode 100644 index 00000000000..1ae63679c34 --- /dev/null +++ b/crates/json-rpc/README.md @@ -0,0 +1,36 @@ +# alloy-json-rpc + +Core types for JSON-RPC2.0 clients. + +This crate includes data types and traits for JSON-RPC2.0 requests and +responses, targeted at RPC client usage. + +### Core Model + + + +A JSON-RPC2.0 request is a JSON object containing an ID, a method name, and +an arbitrary parameters object. The parameters object may be omitted if empty. + +Any object that may be Serialized and Cloned may be used as RPC Parameters. + +Requests are sent via transports (see [alloy-transports]). This results in 1 of +3 outcomes, captured in the `RpcResult` enum: + +- `Ok(Response)` - The request was successful, and the server returned a + response. +- `ErrResp(ErrorPayload)` - The request was received by the server. Server-side + handling was unsuccessful, and the server returned an error response. This + indicates a server-side error. +- `Err(E)` - Some client-side error prevented the request from being received + by the server, or prevented the response from being processed. This indicates a client-side or transport-related error. + +[alloy-transports]: ../transports + +### Limitiations + +- This library models the method name as a `&'static str`, and is therefore + unsuitable for use with RPC servers with dynamic method names. +- This library does not support borrowing response data from the deserializer. + This is intended to simplify client implementations, but makes the library + poorly suited for use in a high-performance server. diff --git a/crates/json-rpc/src/lib.rs b/crates/json-rpc/src/lib.rs index 4dd4d843dc8..d02f8e29502 100644 --- a/crates/json-rpc/src/lib.rs +++ b/crates/json-rpc/src/lib.rs @@ -12,10 +12,10 @@ use serde::{de::DeserializeOwned, Serialize}; mod request; -pub use request::JsonRpcRequest; +pub use request::Request; mod response; -pub use response::{ErrorPayload, JsonRpcResponse, ResponsePayload}; +pub use response::{ErrorPayload, Response, ResponsePayload}; mod common; pub use common::Id; diff --git a/crates/json-rpc/src/request.rs b/crates/json-rpc/src/request.rs index edbaf9641b2..5e6983e9cb2 100644 --- a/crates/json-rpc/src/request.rs +++ b/crates/json-rpc/src/request.rs @@ -12,13 +12,15 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; /// /// The value of `method` must be known at compile time. #[derive(Debug, Deserialize, Clone)] -pub struct JsonRpcRequest { +pub struct Request { pub method: &'static str, pub params: Params, pub id: Id, } -impl Serialize for JsonRpcRequest +// manually implemented to avoid adding a type for the protocol-required +// `jsonrpc` field +impl Serialize for Request where Params: RpcParam, { @@ -28,9 +30,18 @@ where { let mut map = serializer.serialize_map(Some(4))?; map.serialize_entry("method", self.method)?; - map.serialize_entry("params", &self.params)?; + + // Params may be omitted if it is 0-sized + if !is_zst::() { + map.serialize_entry("params", &self.params)?; + } + map.serialize_entry("id", &self.id)?; map.serialize_entry("jsonrpc", "2.0")?; map.end() } } + +fn is_zst() -> bool { + std::mem::size_of::() == 0 +} diff --git a/crates/json-rpc/src/response.rs b/crates/json-rpc/src/response.rs index dacd9f2a0eb..46be72521cc 100644 --- a/crates/json-rpc/src/response.rs +++ b/crates/json-rpc/src/response.rs @@ -32,12 +32,12 @@ pub enum ResponsePayload { } /// A JSONRPC-2.0 response object. -pub struct JsonRpcResponse { +pub struct Response { pub id: Id, pub payload: ResponsePayload, } -impl<'de> Deserialize<'de> for JsonRpcResponse { +impl<'de> Deserialize<'de> for Response { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -82,7 +82,7 @@ impl<'de> Deserialize<'de> for JsonRpcResponse { struct JsonRpcResponseVisitor; impl<'de> Visitor<'de> for JsonRpcResponseVisitor { - type Value = JsonRpcResponse; + type Value = Response; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str( @@ -126,11 +126,11 @@ impl<'de> Deserialize<'de> for JsonRpcResponse { let id = id.unwrap_or(Id::None); match (result, error) { - (Some(result), None) => Ok(JsonRpcResponse { + (Some(result), None) => Ok(Response { id, payload: ResponsePayload::Success(result), }), - (None, Some(error)) => Ok(JsonRpcResponse { + (None, Some(error)) => Ok(Response { id, payload: ResponsePayload::Error(error), }), @@ -155,7 +155,7 @@ mod test { "result": "california", "id": 1 }"#; - let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap(); + let response: super::Response = serde_json::from_str(response).unwrap(); assert_eq!(response.id, super::Id::Number(1)); assert!(matches!( response.payload, @@ -173,7 +173,7 @@ mod test { }, "id": null }"#; - let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap(); + let response: super::Response = serde_json::from_str(response).unwrap(); assert_eq!(response.id, super::Id::None); assert!(matches!(response.payload, super::ResponsePayload::Error(_))); } @@ -190,7 +190,7 @@ mod test { ] } }"#; - let response: super::JsonRpcResponse = serde_json::from_str(response).unwrap(); + let response: super::Response = serde_json::from_str(response).unwrap(); assert_eq!(response.id, super::Id::None); assert!(matches!( response.payload, diff --git a/crates/json-rpc/src/result.rs b/crates/json-rpc/src/result.rs index b7f1ba76b94..67bcd3c3ca5 100644 --- a/crates/json-rpc/src/result.rs +++ b/crates/json-rpc/src/result.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use serde_json::value::RawValue; -use crate::{response::ErrorPayload, JsonRpcResponse, ResponsePayload, RpcReturn}; +use crate::{response::ErrorPayload, Response, ResponsePayload, RpcReturn}; /// The result of a JSON-RPC request. /// @@ -187,8 +187,8 @@ impl RpcResult, E> { } } -impl From for RpcResult, E> { - fn from(value: JsonRpcResponse) -> Self { +impl From for RpcResult, E> { + fn from(value: Response) -> Self { match value.payload { ResponsePayload::Success(res) => Self::Ok(res), ResponsePayload::Error(e) => Self::ErrResp(e), @@ -196,8 +196,8 @@ impl From for RpcResult, E> { } } -impl From> for RpcResult, E> { - fn from(value: Result) -> Self { +impl From> for RpcResult, E> { + fn from(value: Result) -> Self { match value { Ok(res) => res.into(), Err(err) => Self::Err(err), diff --git a/crates/networks/README.md b/crates/networks/README.md new file mode 100644 index 00000000000..9685e164495 --- /dev/null +++ b/crates/networks/README.md @@ -0,0 +1,64 @@ +# alloy-networks + +Ethereum blockchain RPC behavior abstraction for Alloy. + +This crate contains a simple abstraction of the RPC behavior of an +Ethereum-like blockchain. It is intended to be used by the Alloy client to +provide a consistent interface to the rest of the library, regardless of +changes the underlying blockchain makes to the RPC interface. + +## Core Model + +This crate handles abstracting RPC types. It does not handle the actual +networking. The core model is as follows: + +- `Transaction` - A trait that defines an abstract interface for EVM-like + transactions. +- `Network` - A trait that defines the RPC types for a given blockchain. + Providers are parameterized by a `Network` type, and use the associated + types to define the input and output types of the RPC methods. +- TODO: More!!! + +## Usage + +This crate is not intended to be used directly. It is used by the +[alloy-providers] library to modify the input and output types of the RPC +methods. + +This crate will primarily be used by blockchain maintainers to add bespoke RPC +types to the Alloy provider. This is done by implementing the `Network` trait, +and then parameterizing the `Provider` type with the new network type. + +For example, to add a new network called `Foo`: + +```rust,ignore +// Foo must be a ZST. It is a compile error to use a non-ZST type. +struct Foo; + +impl Network for Foo { + type Transaction = FooTransaction; + type Block = FooBlock; + type Header = FooHeader; + type Receipt = FooReceipt; + + // etc. +} +``` + +The user may then instantiate a `Provider` and use it as normal. This +allows the user to use the same API for all networks, regardless of the +underlying RPC types. + +**Note:** If you need to also add custom _methods_ to your network, you should +make an extension trait for `Provider` as follows: + +```rust,ignore +#[async_trait] +trait FooProviderExt: Provider { + async fn custom_foo_method(&self) -> RpcResult; + + async fn another_custom_method(&self) -> RpcResult; +} +``` + +[alloy-providers]: ../providers diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs deleted file mode 100644 index 3d8bb9e02b6..00000000000 --- a/crates/provider/src/lib.rs +++ /dev/null @@ -1,119 +0,0 @@ -mod builder; -pub use builder::{ProviderBuilder, ProviderLayer, Stack}; - -use alloy_json_rpc::RpcResult; -use alloy_networks::{Network, Transaction}; -use alloy_primitives::Address; -use alloy_transports::{BoxTransport, RpcClient, Transport, TransportError}; - -use std::{borrow::Cow, future::Future, pin::Pin}; - -pub type MwareFut<'a, T, E> = Pin> + Send + 'a>>; - -/// Middleware is parameterized with a network and a transport. The default -/// transport is type-erased, but you can do `Middleware`. -pub trait Provider: Send + Sync { - fn client(&self) -> &RpcClient; - - /// Return a reference to the inner Middleware. - /// - /// Middleware are object safe now :) - fn inner(&self) -> &dyn Provider; - - fn estimate_gas<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - tx: &'a N::TransactionRequest, - ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> - where - Self: Sync + 'fut, - { - self.inner().estimate_gas(tx) - } - - /// Get the transaction count for an address. Used for finding the - /// appropriate nonce. - /// - /// TODO: block number/hash/tag - fn get_transaction_count<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - address: Address, - ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> - where - Self: Sync + 'fut, - { - self.inner().get_transaction_count(address) - } - - /// Send a transaction to the network. - /// - /// The transaction type is defined by the network. - fn send_transaction<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - tx: &'a N::TransactionRequest, - ) -> MwareFut<'fut, N::Receipt, TransportError> { - self.inner().send_transaction(tx) - } - - fn populate_gas<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - tx: &'a mut N::TransactionRequest, - ) -> MwareFut<'fut, (), TransportError> - where - Self: Sync, - { - Box::pin(async move { - let gas = self.estimate_gas(&*tx).await; - - gas.map(|gas| tx.set_gas(gas)) - }) - } -} - -impl Provider for RpcClient { - fn client(&self) -> &RpcClient { - self - } - - fn inner(&self) -> &dyn Provider { - panic!("called inner on ") - } - - fn estimate_gas<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - tx: &'a ::TransactionRequest, - ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> { - self.prepare("eth_estimateGas", Cow::Borrowed(tx)).boxed() - } - - fn get_transaction_count<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - address: Address, - ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> - where - Self: Sync + 'fut, - { - self.prepare( - "eth_getTransactionCount", - Cow::<(Address, &'static str)>::Owned((address, "latest")), - ) - .boxed() - } - - fn send_transaction<'s: 'fut, 'a: 'fut, 'fut>( - &'s self, - tx: &'a N::TransactionRequest, - ) -> MwareFut<'fut, N::Receipt, TransportError> { - self.prepare("eth_sendTransaction", Cow::Borrowed(tx)) - .boxed() - } -} - -#[cfg(test)] -mod test { - use crate::Provider; - use alloy_networks::Network; - - fn __compile_check() -> Box> { - unimplemented!() - } -} diff --git a/crates/provider/Cargo.toml b/crates/providers/Cargo.toml similarity index 91% rename from crates/provider/Cargo.toml rename to crates/providers/Cargo.toml index 914a30389e4..674cd07eeec 100644 --- a/crates/provider/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "alloy-middleware" +name = "alloy-providers" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -16,4 +16,5 @@ alloy-json-rpc.workspace = true alloy-networks.workspace = true alloy-primitives.workspace = true alloy-transports.workspace = true +async-trait = "0.1.73" futures-util = "0.3.28" diff --git a/crates/providers/README.md b/crates/providers/README.md new file mode 100644 index 00000000000..9b2d6fbb63e --- /dev/null +++ b/crates/providers/README.md @@ -0,0 +1,29 @@ +# alloy-providers + + + +RPC Providers for Alloy apps. + +This crate contains the `Provider` trait, which exposes Ethereum JSON-RPC +methods. Providers in alloy are similar to ethers.js Providers. They manage an +`RpcClient` and allow other parts of the program to easily make RPC calls. + +Unlike an ethers.js Provider, an alloy Provider is network-aware. It is +parameterized with a `Network` from [`alloy-networks`]. This allows the Provider +to expose a consistent interface to the rest of the program, while adjusting +request and response types to match the underlying blockchain. + +Providers can be composed via stacking. For example, a `Provider` that tracks +the nonce for a given address can be stacked onto a `Provider` that signs +transactions to create a `Provider` that can send signed transactions with +correct nonces. + +The `ProviderBuilder` struct can quickly create a stacked provider, similar to +[tower] `ServiceBuilder`. + +[alloy-networks]: ../networks/ +[tower]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html + +## Usage + +TODO :) diff --git a/crates/provider/src/builder.rs b/crates/providers/src/builder.rs similarity index 69% rename from crates/provider/src/builder.rs rename to crates/providers/src/builder.rs index ecb368e4985..c3446de87a9 100644 --- a/crates/provider/src/builder.rs +++ b/crates/providers/src/builder.rs @@ -1,9 +1,9 @@ use std::marker::PhantomData; use alloy_networks::Network; -use alloy_transports::Transport; +use alloy_transports::{RpcClient, Transport}; -use crate::Provider; +use crate::{NetworkRpcClient, Provider}; /// A layering abstraction in the vein of [`tower::Layer`] /// @@ -49,6 +49,9 @@ where } /// A builder for constructing a [`Provider`] from various layers. +/// +/// This type is similar to [`tower::ServiceBuilder`], with extra complication +/// around maintaining the network and transport types. pub struct ProviderBuilder { layer: L, @@ -57,6 +60,11 @@ pub struct ProviderBuilder { } impl ProviderBuilder { + /// Add a layer to the stack being built. This is similar to + /// [`tower::ServiceBuilder::layer`]. + /// + /// Note: layers are added in outer-to-inner order, as in + /// [`tower::ServiceBuilder`]. pub fn layer(self, layer: Inner) -> ProviderBuilder> { ProviderBuilder { layer: Stack::new(layer, self.layer), @@ -66,6 +74,14 @@ impl ProviderBuilder { } /// Change the network. + /// + /// By default, the network is invalid, and contains the unit type `()`. + /// This method MUST be called before the provider is built. The `client` + /// and `provider` methods only exist when the network is valid. + /// + /// ```rust,ignore + /// builder.network::() + /// ``` pub fn network(self) -> ProviderBuilder { ProviderBuilder { layer: self.layer, @@ -74,6 +90,22 @@ impl ProviderBuilder { } } + /// Finish the layer stack by providing a root [`RpcClient`], outputting + /// the final [`Provider`] type with all stack components. + /// + /// This is a convenience function for + /// `ProviderBuilder::provider`. + pub fn client(self, client: RpcClient) -> L::Provider + where + L: ProviderLayer, N, T>, + T: Transport + Clone, + N: Network, + { + self.provider(NetworkRpcClient::from(client)) + } + + /// Finish the layer stack by providing a root [`Provider`], outputting + /// the final [`Provider`] type with all stack components. pub fn provider

(self, provider: P) -> L::Provider where L: ProviderLayer, diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs new file mode 100644 index 00000000000..7fc5ca27065 --- /dev/null +++ b/crates/providers/src/lib.rs @@ -0,0 +1,165 @@ +mod builder; +pub use builder::{ProviderBuilder, ProviderLayer, Stack}; + +use alloy_json_rpc::RpcResult; +use alloy_networks::{Network, Transaction}; +use alloy_primitives::Address; +use alloy_transports::{BoxTransport, RpcClient, Transport, TransportError}; + +use std::{borrow::Cow, marker::PhantomData}; + +/// A network-wrapped RPC client. +/// +/// This type allows you to specify (at the type-level) that the RPC client is +/// for a specific network. This helps avoid accidentally using the wrong +/// connection to access a network. +#[derive(Debug)] +pub struct NetworkRpcClient { + pub network: PhantomData N>, + pub client: RpcClient, +} + +impl std::ops::Deref for NetworkRpcClient +where + N: Network, + T: Transport, +{ + type Target = RpcClient; + + fn deref(&self) -> &Self::Target { + &self.client + } +} + +impl From> for NetworkRpcClient +where + N: Network, + T: Transport, +{ + fn from(client: RpcClient) -> Self { + Self { + network: PhantomData, + client, + } + } +} + +impl From> for RpcClient +where + N: Network, + T: Transport, +{ + fn from(client: NetworkRpcClient) -> Self { + client.client + } +} + +#[async_trait::async_trait] +/// Provider is parameterized with a network and a transport. The default +/// transport is type-erased, but you can do `Provider`. +pub trait Provider: Send + Sync { + fn raw_client(&self) -> &RpcClient { + &self.client().client + } + + /// Return a reference to the inner RpcClient. + fn client(&self) -> &NetworkRpcClient; + + /// Return a reference to the inner Provider. + /// + /// Providers are object safe now :) + fn inner(&self) -> &dyn Provider; + + async fn estimate_gas( + &self, + tx: &N::TransactionRequest, + ) -> RpcResult + where + Self: Sync, + { + self.inner().estimate_gas(tx).await + } + + /// Get the transaction count for an address. Used for finding the + /// appropriate nonce. + /// + /// TODO: block number/hash/tag + async fn get_transaction_count( + &self, + address: Address, + ) -> RpcResult + where + Self: Sync, + { + self.inner().get_transaction_count(address).await + } + + /// Send a transaction to the network. + /// + /// The transaction type is defined by the network. + async fn send_transaction( + &self, + tx: &N::TransactionRequest, + ) -> RpcResult { + self.inner().send_transaction(tx).await + } + + async fn populate_gas(&self, tx: &mut N::TransactionRequest) -> RpcResult<(), TransportError> + where + Self: Sync, + { + let gas = self.estimate_gas(&*tx).await; + + gas.map(|gas| tx.set_gas(gas)) + } +} + +#[async_trait::async_trait] +impl Provider for NetworkRpcClient { + fn client(&self) -> &NetworkRpcClient { + self + } + + fn inner(&self) -> &dyn Provider { + panic!("called inner on ") + } + + async fn estimate_gas( + &self, + tx: &::TransactionRequest, + ) -> RpcResult { + self.prepare("eth_estimateGas", Cow::Borrowed(tx)).await + } + + async fn get_transaction_count( + &self, + address: Address, + ) -> RpcResult + where + Self: Sync, + { + self.prepare( + "eth_getTransactionCount", + Cow::<(Address, &'static str)>::Owned((address, "latest")), + ) + .await + } + + async fn send_transaction( + &self, + tx: &N::TransactionRequest, + ) -> RpcResult { + self.prepare("eth_sendTransaction", Cow::Borrowed(tx)).await + } +} + +#[cfg(test)] +mod test { + use crate::Provider; + use alloy_networks::Network; + + // checks that `Provider` is object-safe + fn __compile_check() -> Box> { + unimplemented!() + } +} diff --git a/crates/transports/README.md b/crates/transports/README.md index 4adfcba471a..87a2c849ade 100644 --- a/crates/transports/README.md +++ b/crates/transports/README.md @@ -1 +1,56 @@ -## Ethers-transports +## alloy-transports + + + +Transport implementations for Alloy providers + +This crate handles RPC connection and request management. It builds an +`RpcClient` on top of the [tower `Service`] abstraction, and provides +futures for simple and batch RPC requests as well as a unified `TransportError` +type. + +[alloy-providers]: ../providers/ +[tower `Service`]: https://docs.rs/tower/latest/tower/trait.Service.html + +## Usage + +Usage of this crate typically means instantiating an `RpcClient` over some +`Transport`. The RPC client can then be used to make requests to the RPC +server. Requests are captured as `RpcCall` futures, which can be polled to +completion. + +For example, to make a simple request: + +```rust,ignore +// Instantiate a new client over a transport. +let client: RpcClient = "https://mainnet.infura.io/v3/...".parse().unwrap(); + +// Prepare a request to the server. +let request = client.request("eth_blockNumber", ()); + +// Poll the request to completion. +let block_number = request.await.unwrap(); +``` + +Batch requests are also supported: + +```rust,ignore +// Instantiate a new client over a transport. +let client: RpcClient = "https://mainnet.infura.io/v3/...".parse().unwrap(); + +// Prepare a batch request to the server. +let batch = client.new_batch(); + +// Batches serialize params immediately. So we need to handle the result when +// adding calls. +let block_number_fut = batch.add_call("eth_blockNumber", ()).unwrap(); +let balance_fut = batch.add_call("eth_getBalance", address).unwrap(); + +// Make sure to send the batch! +batch.send().await.unwrap(); + +// After the batch is complete, we can get the results. +// Note that requests may error separately! +let block_number = block_number_fut.await.unwrap(); +let balance = balance_fut.await.unwrap(); +``` diff --git a/crates/transports/src/batch.rs b/crates/transports/src/batch.rs index 27201c3556e..3215cebfb00 100644 --- a/crates/transports/src/batch.rs +++ b/crates/transports/src/batch.rs @@ -11,7 +11,7 @@ use futures_channel::oneshot; use serde_json::value::RawValue; use crate::{error::TransportError, transports::Transport, utils::to_json_raw_value, RpcClient}; -use alloy_json_rpc::{Id, JsonRpcRequest, JsonRpcResponse, RpcParam, RpcResult, RpcReturn}; +use alloy_json_rpc::{Id, Request, Response, RpcParam, RpcResult, RpcReturn}; type Channel = oneshot::Sender, TransportError>>; type ChannelMap = HashMap; @@ -101,7 +101,7 @@ impl<'a, T> BatchRequest<'a, T> { fn push( &mut self, - request: JsonRpcRequest, + request: Request, ) -> Result, TransportError> { to_json_raw_value(&request).map(|rv| self.push_raw(request.id, rv).into()) } @@ -127,7 +127,7 @@ where } /// Send the batch future via its connection. - pub fn send_batch(self) -> BatchFuture { + pub fn send(self) -> BatchFuture { BatchFuture::Prepared { transport: self.transport.transport.clone(), requests: self.requests, @@ -144,7 +144,7 @@ where type IntoFuture = BatchFuture; fn into_future(self) -> Self::IntoFuture { - self.send_batch() + self.send() } } @@ -206,7 +206,7 @@ where } }; - let responses: Vec = match serde_json::from_str(responses.get()) { + let responses: Vec = match serde_json::from_str(responses.get()) { Ok(responses) => responses, Err(err) => { self.set(BatchFuture::Complete); diff --git a/crates/transports/src/call.rs b/crates/transports/src/call.rs index 00061a988c8..818fe8c9002 100644 --- a/crates/transports/src/call.rs +++ b/crates/transports/src/call.rs @@ -3,7 +3,7 @@ use crate::{ transports::{JsonRpcLayer, JsonRpcService, Transport}, }; -use alloy_json_rpc::{JsonRpcRequest, RpcParam, RpcResult, RpcReturn}; +use alloy_json_rpc::{Request, RpcParam, RpcResult, RpcReturn}; use core::panic; use serde_json::value::RawValue; use std::{future::Future, marker::PhantomData, pin::Pin, task}; @@ -19,12 +19,12 @@ where Params: RpcParam, { Prepared { - request: Option>, + request: Option>, connection: JsonRpcService, }, AwaitingResponse { #[pin] - fut: as Service>>::Future, + fut: as Service>>::Future, }, Complete, } @@ -48,9 +48,7 @@ where unreachable!("Called poll_prepared in incorrect state") }; - if let Err(e) = task::ready!(Service::>::poll_ready( - connection, cx - )) { + if let Err(e) = task::ready!(Service::>::poll_ready(connection, cx)) { self.set(CallState::Complete); return task::Poll::Ready(RpcResult::Err(e)); } @@ -137,7 +135,7 @@ where Params: RpcParam, { #[doc(hidden)] - pub fn new(req: JsonRpcRequest, connection: Conn) -> Self { + pub fn new(req: Request, connection: Conn) -> Self { Self { state: CallState::Prepared { request: Some(req), diff --git a/crates/transports/src/client.rs b/crates/transports/src/client.rs index 1b93c0fa7e1..7ce833c3d68 100644 --- a/crates/transports/src/client.rs +++ b/crates/transports/src/client.rs @@ -1,4 +1,4 @@ -use alloy_json_rpc::{Id, JsonRpcRequest, RpcParam, RpcReturn}; +use alloy_json_rpc::{Id, Request, RpcParam, RpcReturn}; use reqwest::Url; use serde_json::value::RawValue; use tower::{layer::util::Stack, Layer, ServiceBuilder}; @@ -91,8 +91,8 @@ where &self, method: &'static str, params: Cow<'a, Params>, - ) -> JsonRpcRequest> { - JsonRpcRequest { + ) -> Request> { + Request { method, params, id: self.next_id(), diff --git a/crates/transports/src/transports/json_service.rs b/crates/transports/src/transports/json_service.rs index 4bae1855ef7..dbd911ea8bd 100644 --- a/crates/transports/src/transports/json_service.rs +++ b/crates/transports/src/transports/json_service.rs @@ -1,6 +1,6 @@ use crate::{utils::to_json_raw_value, Transport, TransportError}; -use alloy_json_rpc::{JsonRpcRequest, JsonRpcResponse, RpcParam}; +use alloy_json_rpc::{Request, Response, RpcParam}; use serde::de::DeserializeOwned; use serde_json::value::RawValue; use std::{future::Future, pin::Pin, task}; @@ -25,12 +25,12 @@ impl tower::Layer for JsonRpcLayer { } } -impl Service> for JsonRpcService +impl Service> for JsonRpcService where S: Transport + Clone, Param: RpcParam, { - type Response = JsonRpcResponse; + type Response = Response; type Error = TransportError; @@ -40,7 +40,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, req: JsonRpcRequest) -> Self::Future { + fn call(&mut self, req: Request) -> Self::Future { let replacement = self.inner.clone(); let mut client = std::mem::replace(&mut self.inner, replacement); From b2ddae3dbe2fc4c811fe95f4bfd5c89f9591c574 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Aug 2023 13:06:01 -0700 Subject: [PATCH 2/5] chore: CI and more rustdoc --- .github/workflows/ci.yml | 134 +++++++++++++++++++------------- .github/workflows/deny.yml | 21 ----- .github/workflows/deps.yml | 18 +++++ crates/json-rpc/src/common.rs | 2 +- crates/json-rpc/src/lib.rs | 18 ++++- crates/json-rpc/src/request.rs | 2 +- crates/json-rpc/src/response.rs | 17 +++- 7 files changed, 131 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/deny.yml create mode 100644 .github/workflows/deps.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd6c5ed9e71..f0e64cd59ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,60 +1,88 @@ name: CI on: - push: - branches: [main] - pull_request: + push: + branches: [main] + pull_request: env: - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always jobs: - test: - name: test ${{ matrix.os }} ${{ matrix.flags }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest", "macos-latest", "windows-latest"] - flags: ["--no-default-features", "", "--all-features"] - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: test - run: cargo +stable test --workspace ${{ matrix.flags }} - - clippy: - name: clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@clippy - - uses: Swatinem/rust-cache@v2 - - name: clippy - run: cargo clippy --workspace --tests - env: - RUSTFLAGS: -Dwarnings - - docs: - name: docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-docs - - uses: Swatinem/rust-cache@v2 - - name: doc - run: cargo doc --workspace --no-deps --document-private-items - - fmt: - name: fmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - name: fmt --check - run: cargo fmt --all --check + test: + name: test ${{ matrix.rust }} ${{ matrix.flags }} + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + rust: ["stable", "beta", "nightly", "1.65"] # MSRV + flags: ["--no-default-features", "", "--all-features"] + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: test + run: cargo test --workspace ${{ matrix.flags }} + + wasm: + name: check WASM + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: check + run: cargo check --workspace --target wasm32-unknown-unknown + + feature-checks: + name: feature checks + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + - name: cargo hack + run: cargo hack check --feature-powerset --depth 2 --all-targets + + clippy: + name: clippy + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@clippy + - run: cargo clippy --workspace --all-targets --all-features + env: + RUSTFLAGS: -Dwarnings + + docs: + name: docs + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-docs + - run: cargo doc --workspace --all-features --no-deps --document-private-items + env: + RUSTDOCFLAGS: "--cfg docsrs -D warnings" + + fmt: + name: fmt + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - run: cargo fmt --all --check diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml deleted file mode 100644 index 56855c87a9d..00000000000 --- a/.github/workflows/deny.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: deps - -on: - push: - branches: [main] - paths: [Cargo.lock] - pull_request: - branches: [main] - paths: [Cargo.lock] - -concurrency: deps-${{ github.head_ref || github.run_id }} - -jobs: - deny: - name: deny - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check all diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml new file mode 100644 index 00000000000..0b6448d2f3c --- /dev/null +++ b/.github/workflows/deps.yml @@ -0,0 +1,18 @@ +name: deps + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: [cron: "00 00 * * *"] + +jobs: + cargo-deny: + name: cargo deny check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check all diff --git a/crates/json-rpc/src/common.rs b/crates/json-rpc/src/common.rs index b1892c9be36..fa2ce4898f4 100644 --- a/crates/json-rpc/src/common.rs +++ b/crates/json-rpc/src/common.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -/// A JSON-RPC 2.0 ID object. This may be a number, string, or null. +/// A JSON-RPC 2.0 ID object. This may be a number, a string, or null. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[serde(untagged)] pub enum Id { diff --git a/crates/json-rpc/src/lib.rs b/crates/json-rpc/src/lib.rs index d02f8e29502..e535ea31ea6 100644 --- a/crates/json-rpc/src/lib.rs +++ b/crates/json-rpc/src/lib.rs @@ -24,15 +24,29 @@ mod result; pub use result::RpcResult; /// An object that can be used as a JSON-RPC parameter. +/// +/// This marker trait is blanket-implemented for every qualifying type. It is +/// used to indicate that a type can be used as a JSON-RPC parameter. pub trait RpcParam: Serialize + Clone + Send + Sync + Unpin {} impl RpcParam for T where T: Serialize + Clone + Send + Sync + Unpin {} /// An object that can be used as a JSON-RPC return value. -// Note: we add `'static` here to indicate that the Resp is wholly owned. It -// may not borrow. +/// +/// This marker trait is blanket-implemented for every qualifying type. It is +/// used to indicate that a type can be used as a JSON-RPC return value. +/// +/// # Note +/// +/// We add the `'static` lifetime bound to indicate that the type can't borrow. +/// This is a simplification that makes it easier to use the types in client +/// code. It is not suitable for use in server code. pub trait RpcReturn: DeserializeOwned + Send + Sync + Unpin + 'static {} impl RpcReturn for T where T: DeserializeOwned + Send + Sync + Unpin + 'static {} /// An object that can be used as a JSON-RPC parameter and return value. +/// +/// This marker trait is blanket-implemented for every qualifying type. It is +/// used to indicate that a type can be used as both a JSON-RPC parameter and +/// return value. pub trait RpcObject: RpcParam + RpcReturn {} impl RpcObject for T where T: RpcParam + RpcReturn {} diff --git a/crates/json-rpc/src/request.rs b/crates/json-rpc/src/request.rs index 5e6983e9cb2..00b3cf95e85 100644 --- a/crates/json-rpc/src/request.rs +++ b/crates/json-rpc/src/request.rs @@ -10,7 +10,7 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; /// /// ### Note /// -/// The value of `method` must be known at compile time. +/// The value of `method` should be known at compile time. #[derive(Debug, Deserialize, Clone)] pub struct Request { pub method: &'static str, diff --git a/crates/json-rpc/src/response.rs b/crates/json-rpc/src/response.rs index 46be72521cc..fd834d522bc 100644 --- a/crates/json-rpc/src/response.rs +++ b/crates/json-rpc/src/response.rs @@ -12,7 +12,7 @@ use crate::common::Id; /// /// This response indicates that the server received and handled the request, /// but that there was an error in the processing of it. The error should be -/// included in the `message` field of the response. +/// included in the `message` field of the response payload. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ErrorPayload { pub code: i64, @@ -24,14 +24,25 @@ pub struct ErrorPayload { /// A JSONRPC-2.0 response payload. /// /// This enum covers both the success and error cases of a JSONRPC-2.0 -/// response. +/// response. It is used to represent the `result` and `error` fields of a +/// response object. +/// +/// ### Note +/// +/// This type does not implement `Serialize` or `Deserialize` directly. It is +/// deserialized as part of the [`Response`] type. #[derive(Debug, Clone)] pub enum ResponsePayload { Success(Box), Error(ErrorPayload), } -/// A JSONRPC-2.0 response object. +/// A JSONRPC-2.0 response object containing a [`ResponsePayload`]. +/// +/// This object is used to represent a JSONRPC-2.0 response. It may contain +/// either a successful result or an error. The `id` field is used to match +/// the response to the request that it is responding to, and should be +/// mirrored from the response. pub struct Response { pub id: Id, pub payload: ResponsePayload, From 14e1b32f62fe7c32ad585e4725b39fe14f6c5759 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Aug 2023 13:10:40 -0700 Subject: [PATCH 3/5] doc: improve readme --- README.md | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index fde93167cfc..8c7268c5287 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ This repository contains the following crates: - [`alloy-transports`] - Transport implementations for JSON-RPC 2.0 clients. - [`alloy-networks`] - Network abstraction for RPC types. Allows capturing different RPC param and response types on a per-network basis. -- [`alloy-providers`] - Based on `ethers::middleware::Middleware`, but abstract - over a `Network`, and object-safe. +- [`alloy-providers`] - A client trait for interacting with Ethereum-like RPC + endpoints. Abstract over `alloy_networks::Network`, which allows capturing + different RPC types on a per-network basis. [`alloy-json-rpc`]: ./crates/json-rpc [`alloy-transports`]: ./crates/transports @@ -53,30 +54,6 @@ Alloy project. Pull requests will not be merged unless CI passes, so please ensure that your contribution follows the linting rules and passes clippy. -## WASM support - -We provide full support for all the `wasm*-*` targets. If a crate does not -build on a WASM target, please [open an issue]. - -When building for the `wasm32-unknown-unknown` target and the `"getrandom"` -feature is enabled, compilation for the `getrandom` crate will fail. This is -expected: see [their documentation][getrandom] for more details. - -To fix this, either disable the `"getrandom"` feature on `alloy-core` or add -`getrandom` to your dependencies with the `"js"` feature enabled: - -```toml -getrandom = { version = "0.2", features = ["js"] } -``` - -There is currently no plan to provide an official JS/TS-accessible library -interface, as we believe [viem] or [ethers.js] serve that need very well. - -[open an issue]: https://github.com/alloy-rs/core/issues/new/choose -[getrandom]: https://docs.rs/getrandom/#webassembly-support -[viem]: https://viem.sh -[ethers.js]: https://docs.ethers.io/v6/ - ## Note on `no_std` Because these crates are primarily json-rpc focused, we do not intend to support From 1e3d6e4655e30d185c9cfc41fde9ac22c3c2112a Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Aug 2023 13:13:23 -0700 Subject: [PATCH 4/5] chore: update link in provider readme --- README.md | 6 ++++-- crates/providers/README.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8c7268c5287..c88bfe77552 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ features, high performance, and excellent docs. [`ethers-rs`] will continue to be maintained until we have achieved feature-parity in Alloy. No action is currently needed from devs. -[`ethers-rs`]: https://github.com/gakonst/ethers-rs - [![Telegram chat][telegram-badge]][telegram-url] +[`ethers-rs`]: https://github.com/gakonst/ethers-rs +[telegram-badge]: https://img.shields.io/endpoint?color=neon&style=for-the-badge&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fethers_rs +[telegram-url]: https://t.me/ethers_rs + ## Overview This repository contains the following crates: diff --git a/crates/providers/README.md b/crates/providers/README.md index 9b2d6fbb63e..8df65731ad0 100644 --- a/crates/providers/README.md +++ b/crates/providers/README.md @@ -19,10 +19,10 @@ transactions to create a `Provider` that can send signed transactions with correct nonces. The `ProviderBuilder` struct can quickly create a stacked provider, similar to -[tower] `ServiceBuilder`. +[`tower::ServiceBuilder`]. [alloy-networks]: ../networks/ -[tower]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html +[`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html ## Usage From 88222950eee00d797505e6c1f9aaabd20c01960a Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Aug 2023 13:16:33 -0700 Subject: [PATCH 5/5] docs: resolve links --- crates/providers/src/builder.rs | 14 ++++++++++++-- crates/transports/src/call.rs | 2 +- crates/transports/src/transports/json_service.rs | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/providers/src/builder.rs b/crates/providers/src/builder.rs index c3446de87a9..946951d40e2 100644 --- a/crates/providers/src/builder.rs +++ b/crates/providers/src/builder.rs @@ -52,6 +52,8 @@ where /// /// This type is similar to [`tower::ServiceBuilder`], with extra complication /// around maintaining the network and transport types. +/// +/// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html pub struct ProviderBuilder { layer: L, @@ -63,8 +65,16 @@ impl ProviderBuilder { /// Add a layer to the stack being built. This is similar to /// [`tower::ServiceBuilder::layer`]. /// - /// Note: layers are added in outer-to-inner order, as in - /// [`tower::ServiceBuilder`]. + /// ## Note: + /// + /// Layers are added in outer-to-inner order, as in + /// [`tower::ServiceBuilder`]. The first layer added will be the first to + /// see the request. + /// + /// + /// [`tower::ServiceBuilder::layer`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer + /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html + pub fn layer(self, layer: Inner) -> ProviderBuilder> { ProviderBuilder { layer: Stack::new(layer, self.layer), diff --git a/crates/transports/src/call.rs b/crates/transports/src/call.rs index 818fe8c9002..854e2f420af 100644 --- a/crates/transports/src/call.rs +++ b/crates/transports/src/call.rs @@ -100,7 +100,7 @@ where /// A prepared, but unsent, RPC call. /// /// This is a future that will send the request when polled. It contains a -/// [`JsonRpcRequest`], a [`Transport`], and knowledge of its expected response +/// [`Request`], a [`Transport`], and knowledge of its expected response /// type. Upon awaiting, it will send the request and wait for the response. It /// will then deserialize the response into the expected type. /// diff --git a/crates/transports/src/transports/json_service.rs b/crates/transports/src/transports/json_service.rs index dbd911ea8bd..a0394f3a46d 100644 --- a/crates/transports/src/transports/json_service.rs +++ b/crates/transports/src/transports/json_service.rs @@ -6,7 +6,7 @@ use serde_json::value::RawValue; use std::{future::Future, pin::Pin, task}; use tower::Service; -/// A service layer that transforms [`JsonRpcRequest`] into [`JsonRpcResponse`] +/// A service layer that transforms [`Request`] into [`Response`] /// by wrapping an inner service that implements [`Transport`]. #[derive(Debug, Clone)] pub(crate) struct JsonRpcService {