alloy_sol_macro/
lib.rs

1//! # alloy-sol-macro
2//!
3//! This crate provides the [`sol!`] procedural macro, which parses Solidity
4//! syntax to generate types that implement [`alloy-sol-types`] traits.
5//!
6//! Refer to the [macro's documentation](sol!) for more information.
7//!
8//! [`alloy-sol-types`]: https://docs.rs/alloy-sol-types
9
10#![doc(
11    html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
12    html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
13)]
14#![cfg_attr(not(test), warn(unused_crate_dependencies))]
15#![cfg_attr(docsrs, feature(doc_cfg))]
16
17#[macro_use]
18extern crate proc_macro_error2;
19
20use alloy_sol_macro_expander::expand;
21use alloy_sol_macro_input::{SolAttrs, SolInput, SolInputExpander, SolInputKind};
22use proc_macro::TokenStream;
23use quote::quote;
24use syn::parse_macro_input;
25
26/// Generate types that implement [`alloy-sol-types`] traits, which can be used
27/// for type-safe [ABI] and [EIP-712] serialization to interface with Ethereum
28/// smart contracts.
29///
30/// Note that you will likely want to use this macro through a re-export in another crate,
31/// as it will also set the correct paths for the required dependencies by using a `macro_rules!`
32/// wrapper.
33///
34/// [ABI]: https://docs.soliditylang.org/en/latest/abi-spec.html
35/// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
36///
37/// # Examples
38///
39/// > Note: the following example code blocks cannot be tested here because the
40/// > generated code references [`alloy-sol-types`], so they are [tested in that
41/// > crate][tests] and included with [`include_str!`] in this doc instead.
42///
43/// [tests]: https://github.com/alloy-rs/core/tree/main/crates/sol-types/tests/doctests
44/// [`alloy-sol-types`]: https://docs.rs/alloy-sol-types
45///
46/// There are two main ways to use this macro:
47/// - you can [write Solidity code](#solidity), or provide a path to a Solidity file,
48/// - if you enable the `json` feature, you can provide [an ABI, or a path to one, in JSON
49///   format](#json-abi).
50///
51/// Note:
52/// - relative file system paths are rooted at the `CARGO_MANIFEST_DIR` environment variable by
53///   default; you can specify absolute paths using the `concat!` and `env!` macros,
54/// - no casing convention is enforced for any identifier,
55/// - unnamed arguments will be given a name based on their index in the list, e.g. `_0`, `_1`...
56/// - a current limitation for certain items is that custom types, like structs, must be defined in
57///   the same macro scope, otherwise a signature cannot be generated at compile time. You can bring
58///   them in scope with a [Solidity type alias](#udvt-and-type-aliases).
59///
60/// ## Solidity
61///
62/// This macro uses [`syn-solidity`][ast] to parse Solidity-like syntax. See
63/// [its documentation][ast] for more.
64///
65/// Solidity input can be either one of the following:
66/// - a Solidity item, which is a [Solidity source unit][sol-item] which generates one or more Rust
67///   items,
68/// - a [Solidity type name][sol-types], which simply expands to the corresponding Rust type.
69///
70/// **IMPORTANT!** This is **NOT** a Solidity compiler, or a substitute for one! It only parses a
71/// Solidity-like syntax to generate Rust types, designed for simple interfaces defined inline with
72/// your other Rust code.
73///
74/// Further, this macro does not resolve imports or dependencies, and it does not handle
75/// inheritance. All required types must be provided in the same macro scope.
76///
77/// [sol-item]: https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.sourceUnit
78/// [sol-types]: https://docs.soliditylang.org/en/latest/types.html
79/// [ast]: https://docs.rs/syn-solidity/latest/syn_solidity
80///
81/// ### Visibility
82///
83/// Visibility modifiers (`private`, `internal`, `public`, `external`) are supported in all items
84/// that Solidity supports them in. However, they are only taken into consideration when deciding
85/// whether to generate a getter for a state variable or not. They are ignored in all other places.
86///
87/// ### State mutability
88///
89/// State mutability modifiers (`pure`, `view`, `payable`, `nonpayable`) are parsed, but ignored for
90/// the purposes of this macro.
91///
92/// ### Attributes
93///
94/// Inner attributes (`#![...]`) are parsed at the top of the input, just like a
95/// Rust module. These can only be `sol` attributes, and they will apply to the
96/// entire input.
97///
98/// Outer attributes (`#[...]`) are parsed as part of each individual item, like
99/// structs, enums, etc. These can be any Rust attribute, and they will be added
100/// to every Rust item generated from the Solidity item.
101///
102/// This macro provides the `sol` attribute, which can be used to customize the
103/// generated code. Note that unused attributes are currently silently ignored,
104/// but this may change in the future.
105///
106/// Note that the `sol` attribute does not compose like other Rust attributes, for example
107/// `#[cfg_attr]` will **NOT** work, as it is parsed and extracted from the input separately.
108/// This is a limitation of the proc-macro API.
109///
110/// Wherever a string literal is expected, common standard library macros that operate on string
111/// literals are also supported, such as `concat!` and `env!`.
112///
113/// List of all `#[sol(...)]` supported values:
114/// - `rpc [ = <bool = false>]` (contracts and alike only): generates a structs with methods to
115///   construct `eth_call`s to an on-chain contract through Ethereum JSON RPC, similar to the
116///   default behavior of [`abigen`]. This makes use of the [`alloy-contract`] crate.
117///
118///   Generates the following items inside of the `{contract_name}` module:
119///   - `struct {contract_name}Instance<P: Provider> { ... }`
120///     - `pub fn new(...) -> {contract_name}Instance<P>` + getters and setters
121///     - `pub fn call_builder<C: SolCall>(&self, call: &C) -> SolCallBuilder<P, C>`, as a generic
122///       way to call any function of the contract, even if not generated by the macro; prefer the
123///       other methods when possible
124///     - `pub fn <functionName>(&self, <parameters>...) -> CallBuilder<P, functionReturn>` for each
125///       function in the contract
126///     - `pub fn <eventName>_filter(&self) -> Event<P, eventName>` for each event in the contract
127///   - `pub fn new ...`, same as above just as a free function in the contract module
128/// - `abi [ = <bool = false>]`: generates functions which return the dynamic ABI representation
129///   (provided by [`alloy_json_abi`](https://docs.rs/alloy-json-abi)) of all the generated items.
130///   Requires the `"json"` feature. For:
131///   - contracts: generates an `abi` module nested inside of the contract module, which contains:
132///     - `pub fn contract() -> JsonAbi`,
133///     - `pub fn constructor() -> Option<Constructor>`
134///     - `pub fn fallback() -> Option<Fallback>`
135///     - `pub fn receive() -> Option<Receive>`
136///     - `pub fn functions() -> BTreeMap<String, Vec<Function>>`
137///     - `pub fn events() -> BTreeMap<String, Vec<Event>>`
138///     - `pub fn errors() -> BTreeMap<String, Vec<Error>>`
139///   - items: generates implementations of the `SolAbiExt` trait, alongside the existing
140///     [`alloy-sol-types`] traits
141/// - `alloy_sol_types = <path = ::alloy_sol_types>` (inner attribute only): specifies the path to
142///   the required dependency [`alloy-sol-types`].
143/// - `alloy_contract = <path = ::alloy_contract>` (inner attribute only): specifies the path to the
144///   optional dependency [`alloy-contract`]. This is only used by the `rpc` attribute.
145/// - `all_derives [ = <bool = false>]`: adds all possible standard library `#[derive(...)]`
146///   attributes to all generated types. May significantly increase compile times due to all the
147///   extra generated code. This is the default behavior of [`abigen`]
148/// - `extra_derives(<paths...>)`: adds extra `#[derive(...)]` attributes to all generated types.
149/// - `extra_methods [ = <bool = false>]`: adds extra implementations and methods to all applicable
150///   generated types, such as `From` impls and `as_<variant>` methods. May significantly increase
151///   compile times due to all the extra generated code. This is the default behavior of [`abigen`]
152/// - `docs [ = <bool = true>]`: adds doc comments to all generated types. This is the default
153///   behavior of [`abigen`]
154/// - `bytecode = <hex string literal>` (contract-like only): specifies the creation/init bytecode
155///   of a contract. This will emit a `static` item with the specified bytes.
156/// - `deployed_bytecode = <hex string literal>` (contract-like only): specifies the deployed
157///   bytecode of a contract. This will emit a `static` item with the specified bytes.
158/// - `type_check = <string literal>` (UDVT only): specifies a function to be used to check an User
159///   Defined Type.
160/// - `ignore_unlinked [ = <bool = false>]`: ignores unlinked bytecode in contract artifacts.
161///
162/// ### Structs and enums
163///
164/// Structs and enums generate their corresponding Rust types. Enums are
165/// additionally annotated with `#[repr(u8)]`, and as such can have a maximum of
166/// 256 variants.
167/// ```ignore
168#[doc = include_str!("../doctests/structs.rs")]
169/// ```
170/// 
171/// ### UDVT and type aliases
172///
173/// User defined value types (UDVT) generate a tuple struct with the type as
174/// its only field, and type aliases simply expand to the corresponding Rust
175/// type.
176/// ```ignore
177#[doc = include_str!("../doctests/types.rs")]
178/// ```
179/// 
180/// ### State variables
181///
182/// Public and external state variables will generate a getter function just like in Solidity.
183///
184/// See the [functions](#functions-and-errors) and [contracts](#contractsinterfaces)
185/// sections for more information.
186///
187/// ### Functions and errors
188///
189/// Functions generate two structs that implement `SolCall`: `<name>Call` for
190/// the function arguments, and `<name>Return` for the return values.
191///
192/// In the case where the solidity returns multiple values, the `<name>Return` is returned by the call which contains the return values as fields in the struct.
193///
194/// Take Uniswap v3's slot0 function as an example:
195/// ```ignore
196/// sol! {
197///     function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16
198/// observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8
199/// feeProtocol, bool unlocked);
200/// }
201///
202/// pub struct slot0Return {
203///     pub sqrtPriceX96: Uint<160, 3>,
204///     pub tick: Signed<24, 1>,
205///     pub observationIndex: u16,
206///     pub observationCardinality: u16,
207///     pub observationCardinalityNext: u16,
208///     pub feeProtocol: u8,
209///     pub unlocked: bools,
210/// }
211/// ```
212/// 
213/// Whereas, if the solidity function returns a single value, the singular return value will yielded by the call.
214/// ```ignore
215/// sol! {
216///   #[sol(rpc)]
217///   contract ERC20 {
218///       function balanceOf(address owner) external view returns (uint256);
219///   }
220/// }
221///
222/// let balance: U256 = erc20.balanceOf(owner).call().await?;
223/// ```
224/// 
225/// In the case of overloaded functions, an underscore and the index of the
226/// function will be appended to `<name>` (like `foo_0`, `foo_1`...) for
227/// disambiguation, but the signature will remain the same.
228///
229/// E.g. if there are two functions named `foo`, the generated types will be
230/// `foo_0Call` and `foo_1Call`, each of which will implement `SolCall`
231/// with their respective signatures.
232/// ```ignore
233#[doc = include_str!("../doctests/function_like.rs")]
234/// ```
235/// 
236/// ### Events
237///
238/// Events generate a struct that implements `SolEvent`.
239///
240/// Note that events have special encoding rules in Solidity. For example,
241/// `string indexed` will be encoded in the topics as its `bytes32` Keccak-256
242/// hash, and as such the generated field for this argument will be `bytes32`,
243/// and not `string`.
244/// ```ignore
245#[doc = include_str!("../doctests/events.rs")]
246/// ```
247/// 
248/// ### Contracts/interfaces
249///
250/// Contracts generate a module with the same name, which contains all the items.
251/// This module will also contain 3 container enums which implement `SolInterface`, one for each:
252/// - functions: `<contract_name>Calls`
253/// - errors: `<contract_name>Errors`
254/// - events: `<contract_name>Events`
255/// Note that by default only ABI encoding are generated. In order to generate bindings for RPC
256/// calls, you must enable the `#[sol(rpc)]` attribute.
257/// ```ignore
258#[doc = include_str!("../doctests/contracts.rs")]
259/// ```
260/// 
261/// ## JSON ABI
262///
263/// Contracts can also be generated from ABI JSON strings and files, similar to
264/// the [ethers-rs `abigen!` macro][abigen].
265///
266/// JSON objects containing the `abi`, `evm`, `bytecode`, `deployedBytecode`,
267/// and similar keys are also supported.
268///
269/// Note that only valid JSON is supported, and not the human-readable ABI
270/// format, also used by [`abigen!`][abigen]. This should instead be easily converted to
271/// [normal Solidity input](#solidity).
272///
273/// Both the raw JSON input and the file system path can be specified with standard library macros
274/// like `concat!` and `env!`.
275///
276/// Prefer using [Solidity input](#solidity) when possible, as the JSON ABI
277/// format omits some information which is useful to this macro, such as enum
278/// variants and visibility modifiers on functions.
279///
280/// [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html
281/// [`abigen`]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html
282/// ```ignore
283#[doc = include_str!("../doctests/json.rs")]
284/// ```
285/// 
286/// [`alloy-contract`]: https://github.com/alloy-rs/alloy
287#[proc_macro]
288#[proc_macro_error]
289pub fn sol(input: TokenStream) -> TokenStream {
290    let input = parse_macro_input!(input as alloy_sol_macro_input::SolInput);
291
292    SolMacroExpander.expand(&input).unwrap_or_else(syn::Error::into_compile_error).into()
293}
294
295struct SolMacroExpander;
296
297impl SolInputExpander for SolMacroExpander {
298    fn expand(&mut self, input: &SolInput) -> syn::Result<proc_macro2::TokenStream> {
299        let input = input.clone();
300
301        #[cfg(feature = "json")]
302        let is_json = matches!(input.kind, SolInputKind::Json { .. });
303        #[cfg(not(feature = "json"))]
304        let is_json = false;
305
306        // Convert JSON input to Solidity input
307        #[cfg(feature = "json")]
308        let input = input.normalize_json()?;
309
310        let SolInput { attrs, path, kind } = input;
311        let include = path.map(|p| {
312            let p = p.to_str().unwrap();
313            quote! { const _: &'static [u8] = ::core::include_bytes!(#p); }
314        });
315
316        let tokens = match kind {
317            SolInputKind::Sol(mut file) => {
318                // Attributes have already been added to the inner contract generated in
319                // `normalize_json`.
320                if !is_json {
321                    file.attrs.extend(attrs);
322                }
323
324                crate::expand::expand(file)
325            }
326            SolInputKind::Type(ty) => {
327                let (sol_attrs, rest) = SolAttrs::parse(&attrs)?;
328                if !rest.is_empty() {
329                    return Err(syn::Error::new_spanned(
330                        rest.first().unwrap(),
331                        "only `#[sol]` attributes are allowed here",
332                    ));
333                }
334
335                let mut crates = crate::expand::ExternCrates::default();
336                crates.fill(&sol_attrs);
337                Ok(crate::expand::expand_type(&ty, &crates))
338            }
339            #[cfg(feature = "json")]
340            SolInputKind::Json(_, _) => unreachable!("input already normalized"),
341        }?;
342
343        Ok(quote! {
344            #include
345            #tokens
346        })
347    }
348}