stylus_proc/
lib.rs

1// Copyright 2022-2024, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4//! Procedural macros for [The Stylus SDK][sdk].
5//!
6//! You can import these via
7//!
8//! ```ignore
9//! use stylus_sdk::prelude::*;
10//! ```
11//!
12//! For a guided exploration of the features, please see the comprehensive [Feature Overview][overview].
13//!
14//! [overview]: https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#calls
15//! [sdk]: https://docs.rs/stylus-sdk/latest/stylus_sdk/index.html
16
17#![warn(missing_docs)]
18
19use proc_macro::TokenStream;
20
21/// Generates a pretty error message.
22/// Note that this macro is declared before all modules so that they can use it.
23macro_rules! error {
24    ($tokens:expr, $($msg:expr),+ $(,)?) => {{
25        let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+));
26        return error.to_compile_error().into();
27    }};
28    (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{
29        return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)))
30    }};
31}
32
33mod calls;
34mod methods;
35mod storage;
36mod types;
37
38/// Allows a Rust `struct` to be used in persistent storage.
39///
40/// ```ignore
41/// #[storage]
42/// pub struct Contract {
43///    owner: StorageAddress,
44///    active: StorageBool,
45///    sub_struct: SubStruct,
46///}
47///
48///#[storage]
49///pub struct SubStruct {
50///    // types implementing the `StorageType` trait.
51///}
52/// ```
53///
54/// Each field must implement [`StorageType`]. This includes other structs, which will
55/// implement the `trait` automatically when [`#[storage]`][storage] is applied.
56///
57/// One may even implement [`StorageType`] to define custom storage entries, though this is rarely necessary
58/// since the [Stylus SDK][sdk] intends to include all standard Solidity types out-of-the-box.
59///
60/// Please refer to the [SDK Feature Overview][overview] for more information on defining storage.
61///
62/// [storage]: macro@storage
63/// [`StorageType`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.StorageType.html
64/// [overview]: https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#storage
65/// [sdk]: https://docs.rs/stylus-sdk/latest/stylus_sdk/index.html
66#[proc_macro_attribute]
67pub fn storage(attr: TokenStream, input: TokenStream) -> TokenStream {
68    storage::storage(attr, input)
69}
70
71#[doc(hidden)]
72#[deprecated = "please use `#[storage]` instead"]
73#[proc_macro_attribute]
74pub fn solidity_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
75    storage::storage(attr, input)
76}
77
78/// The types in [`#[storage]`][storage] are laid out in the EVM state trie exactly
79/// as they are in [Solidity][solidity]. This means that the fields of a `struct` definition will map
80/// to the same storage slots as they would in EVM programming languages. Hence, it is often nice to
81/// define types using Solidity syntax, which makes this guarantee easier to see.
82///
83/// ```ignore
84/// sol_storage! {
85///     pub struct Contract {
86///         address owner;                      // becomes a StorageAddress
87///         bool active;                        // becomes a StorageBool
88///         SubStruct sub_struct,
89///     }
90///
91///     pub struct SubStruct {
92///         // other solidity fields, such as
93///         mapping(address => uint) balances;  // becomes a StorageMap
94///         Delegate delegates[];               // becomes a StorageVec
95///     }
96/// }
97/// ```
98///
99/// The above will expand to equivalent definitions in Rust, with each structure implementing the [`StorageType`]
100/// `trait`. Many contracts, like [the ERC 20 example][erc20], do exactly this.
101///
102/// Because the layout is identical to [Solidity's][solidity], existing Solidity smart contracts can
103/// upgrade to Rust without fear of storage slots not lining up. You simply copy-paste your type definitions.
104///
105/// Note that one exception to this storage layout guarantee is contracts which utilize
106/// inheritance. The current solution in Stylus using `#[borrow]` and `#[inherits(...)]` packs
107/// nested (inherited) structs into their own slots. This is consistent with regular struct nesting
108/// in solidity, but not inherited structs. We plan to revisit this behavior in an upcoming
109/// release.
110///
111/// Consequently, the order of fields will affect the JSON ABIs produced that explorers and tooling might use.
112/// Most developers don't need to worry about this though and can freely order their types when working on a
113/// Rust contract from scratch.
114///
115///
116/// Please refer to the [SDK Feature Overview][overview] for more information on defining storage.
117///
118/// [storage]: macro@storage
119/// [`StorageType`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.StorageType.html
120/// [solidity]: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html
121/// [overview]: https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#erase-and-deriveerase
122/// [erc20]: https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/examples/erc20/src/main.rs
123#[proc_macro]
124pub fn sol_storage(input: TokenStream) -> TokenStream {
125    storage::sol_storage(input)
126}
127
128/// Facilitates calls to other contracts.
129///
130/// This macro defines a `struct` for each of the Solidity interfaces provided.
131///
132/// ```ignore
133/// sol_interface! {
134///     interface IService {
135///         function makePayment(address user) payable returns (string);
136///         function getConstant() pure returns (bytes32)
137///     }
138///
139///     interface ITree {
140///         // other interface methods
141///     }
142/// }
143/// ```
144///
145/// The above will define `IService` and `ITree` for calling the methods of the two contracts.
146///
147/// For example, `IService` will have a `make_payment` method that accepts an [`Address`] and returns a [`B256`].
148///
149/// Currently only functions are supported, and any other items in the interface will cause an
150/// error.
151///
152/// ```ignore
153/// pub fn do_call(account: IService, user: Address) -> Result<String, Error> {
154///     let config = Call::new()
155///         .gas(evm::gas_left() / 2)       // limit to half the gas left
156///         .value(msg::value());           // set the callvalue
157///
158///     account.make_payment(config, user)  // note the snake case
159/// }
160/// ```
161///
162/// Observe the casing change. [`sol_interface!`] computes the selector based on the exact name passed in,
163/// which should almost always be `CamelCase`. For aesthetics, the rust functions will instead use `snake_case`.
164///
165/// # Reentrant calls
166///
167/// Contracts that opt into reentrancy via the `reentrant` feature flag require extra care.
168/// When enabled, cross-contract calls must [`flush`] or [`clear`] the [`StorageCache`] to safeguard state.
169/// This happens automatically via the type system.
170///
171/// ```ignore
172/// sol_interface! {
173///     interface IMethods {
174///         function pureFoo() external pure;
175///         function viewFoo() external view;
176///         function writeFoo() external;
177///         function payableFoo() external payable;
178///     }
179/// }
180///
181/// #[public]
182/// impl Contract {
183///     pub fn call_pure(&self, methods: IMethods) -> Result<(), Vec<u8>> {
184///         Ok(methods.pure_foo(self)?)    // `pure` methods might lie about not being `view`
185///     }
186///
187///     pub fn call_view(&self, methods: IMethods) -> Result<(), Vec<u8>> {
188///         Ok(methods.view_foo(self)?)
189///     }
190///
191///     pub fn call_write(&mut self, methods: IMethods) -> Result<(), Vec<u8>> {
192///         methods.view_foo(self)?;       // allows `pure` and `view` methods too
193///         Ok(methods.write_foo(self)?)
194///     }
195///
196///     #[payable]
197///     pub fn call_payable(&mut self, methods: IMethods) -> Result<(), Vec<u8>> {
198///         methods.write_foo(Call::new_in(self))?;   // these are the same
199///         Ok(methods.payable_foo(self)?)            // ------------------
200///     }
201/// }
202/// ```
203///
204/// In the above, we're able to pass `&self` and `&mut self` because `Contract` implements
205/// [`TopLevelStorage`], which means that a reference to it entails access to the entirety of
206/// the contract's state. This is the reason it is sound to make a call, since it ensures all
207/// cached values are invalidated and/or persisted to state at the right time.
208///
209/// When writing Stylus libraries, a type might not be [`TopLevelStorage`] and therefore
210/// `&self` or `&mut self` won't work. Building a [`Call`] from a generic parameter is the usual solution.
211///
212/// ```ignore
213/// pub fn do_call(
214///     storage: &mut impl TopLevelStorage,  // can be generic, but often just &mut self
215///     account: IService,                   // serializes as an Address
216///     user: Address,
217/// ) -> Result<String, Error> {
218///
219///     let config = Call::new_in(storage)
220///         .gas(evm::gas_left() / 2)        // limit to half the gas left
221///         .value(msg::value());            // set the callvalue
222///
223///     account.make_payment(config, user)   // note the snake case
224/// }
225/// ```
226///
227/// Note that in the context of an [`#[public]`][public] call, the `&mut impl` argument will correctly
228/// distinguish the method as being `write` or `payable`. This means you can write library code that will
229/// work regardless of whether the `reentrant` feature flag is enabled.
230///
231/// [sol_interface]: macro@sol_interface
232/// [public]: macro@public
233/// [`TopLevelStorage`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.TopLevelStorage.html
234/// [`StorageCache`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/struct.StorageCache.html
235/// [`flush`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/struct.StorageCache.html#method.flush
236/// [`clear`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/struct.StorageCache.html#method.clear
237/// [`Address`]: https://docs.rs/alloy-primitives/latest/alloy_primitives/struct.Address.html
238/// [`B256`]: https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.B256.html
239/// [`Call`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/call/struct.Call.html
240#[proc_macro]
241pub fn sol_interface(input: TokenStream) -> TokenStream {
242    calls::sol_interface(input)
243}
244
245/// Some [`StorageType`] values implement [`Erase`], which provides an [`erase()`] method for clearing state.
246/// [The Stylus SDK][sdk] implements [`Erase`] for all primitives, and for vectors of primitives, but not for maps.
247/// This is because a Solidity mapping does not provide iteration, and so it's generally impossible to
248/// know which slots to clear.
249///
250/// Structs may also be [`Erase`] if all of the fields are. `#[derive(Erase)]`
251/// lets you do this automatically.
252///
253/// ```ignore
254/// sol_storage! {
255///    #[derive(Erase)]
256///    pub struct Contract {
257///        address owner;              // can erase primitive
258///        uint256[] hashes;           // can erase vector of primitive
259///    }
260///
261///    pub struct NotErase {
262///        mapping(address => uint) balances; // can't erase a map
263///        mapping(uint => uint)[] roots;     // can't erase vector of maps
264///    }
265/// }
266/// ```
267///
268/// You can also implement [`Erase`] manually if desired. Note that the reason we care about [`Erase`]
269/// at all is that you get storage refunds when clearing state, lowering fees. There's also
270/// minor implications for storage patterns using `unsafe` Rust.
271///
272/// Please refer to the [SDK Feature Overview][overview] for more information on defining storage.
273///
274/// [`StorageType`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.StorageType.html
275/// [`Erase`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.Erase.html
276/// [`erase()`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.Erase.html#tymethod.erase
277/// [overview]: https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#storage
278/// [sdk]: https://docs.rs/stylus-sdk/latest/stylus_sdk/index.html
279#[proc_macro_derive(Erase)]
280pub fn derive_erase(input: TokenStream) -> TokenStream {
281    storage::derive_erase(input)
282}
283
284/// Allows an error `enum` to be used in method signatures.
285///
286/// ```ignore
287/// sol! {
288///     error InsufficientBalance(address from, uint256 have, uint256 want);
289///     error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
290/// }
291///
292/// #[derive(SolidityError)]
293/// pub enum Erc20Error {
294///     InsufficientBalance(InsufficientBalance),
295///     InsufficientAllowance(InsufficientAllowance),
296/// }
297///
298/// #[public]
299/// impl Contract {
300///     pub fn fallible_method() -> Result<(), Erc20Error> {
301///         // code that might revert
302///     }
303/// }
304/// ```
305///
306/// Under the hood, the above macro works by implementing `From<Erc20Error>` for `Vec<u8>`
307/// along with printing code for abi-export.
308#[proc_macro_derive(SolidityError)]
309pub fn derive_solidity_error(input: TokenStream) -> TokenStream {
310    methods::error::derive_solidity_error(input)
311}
312
313/// Defines the entrypoint, which is where Stylus execution begins.
314/// Without it the contract will fail to pass [`cargo stylus check`][check].
315/// Most commonly this macro is used to annotate the top level storage `struct`.
316///
317/// ```ignore
318/// sol_storage! {
319///     #[entrypoint]
320///     pub struct Contract {
321///         ...
322///     }
323///
324///     // only one entrypoint is allowed
325///     pub struct SubStruct {
326///         ...
327///     }
328/// }
329/// ```
330///
331/// The above will make the public methods of Contract the first to consider during invocation.
332/// See [`#[public]`][public] for more information on method selection.
333///
334/// # Bytes-in, bytes-out programming
335///
336/// A less common usage of [`#[entrypoint]`][entrypoint] is for low-level, bytes-in bytes-out programming.
337/// When applied to a free-standing function, a different way of writing smart contracts becomes possible,
338/// wherein the Stylus SDK's macros and storage types are entirely optional.
339///
340/// ```ignore
341/// #[entrypoint]
342/// fn entrypoint(calldata: Vec<u8>) -> ArbResult {
343///     // bytes-in, bytes-out programming
344/// }
345/// ```
346///
347/// # Reentrancy
348///
349/// If a contract calls another that then calls the first, it is said to be reentrant. By default,
350/// all Stylus programs revert when this happens. However, you can opt out of this behavior by
351/// recompiling with the `reentrant` flag.
352///
353/// ```toml
354/// stylus_sdk = { version = "0.3.0", features = ["reentrant"] }
355/// ```
356///
357/// This is dangerous, and should be done only after careful review -- ideally by 3rd-party auditors.
358/// Numerous exploits and hacks have in Web3 are attributable to developers misusing or not fully
359/// understanding reentrant patterns.
360///
361/// If enabled, the Stylus SDK will flush the storage cache in between reentrant calls, persisting values
362/// to state that might be used by inner calls. Note that preventing storage invalidation is only part
363/// of the battle in the fight against exploits. You can tell if a call is reentrant via
364/// [`msg::reentrant`][reentrant], and condition your business logic accordingly.
365///
366/// # [`TopLevelStorage`]
367///
368/// The [`#[entrypoint]`][entrypoint] macro will automatically implement the [`TopLevelStorage`] `trait`
369/// for the annotated `struct`. The single type implementing [`TopLevelStorage`] is special in that
370/// mutable access to it represents mutable access to the entire program's state.
371/// This has implications for calls via [`sol_interface`].
372///
373/// [`TopLevelStorage`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/trait.TopLevelStorage.html
374/// [`sol_interface`]: macro@sol_interface
375/// [entrypoint]: macro@entrypoint
376/// [reentrant]: https://docs.rs/stylus-sdk/latest/stylus_sdk/msg/fn.reentrant.html
377/// [public]: macro@public
378/// [check]: https://github.com/OffchainLabs/cargo-stylus#developing-with-stylus
379#[proc_macro_attribute]
380pub fn entrypoint(attr: TokenStream, input: TokenStream) -> TokenStream {
381    methods::entrypoint::entrypoint(attr, input)
382}
383
384/// Just as with storage, Stylus SDK methods are Solidity ABI-equivalent. This means that contracts written
385/// in different programming languages are fully interoperable. You can even automatically export your
386/// Rust contract as a Solidity interface so that others can add it to their Solidity projects.
387///
388/// This macro makes methods "public" so that other contracts can call them by implementing the [`Router`] trait.
389///
390/// ```ignore
391/// #[public]
392/// impl Contract {
393///     // our owner method is now callable by other contracts
394///     pub fn owner(&self) -> Result<Address, Vec<u8>> {
395///         Ok(self.owner.get())
396///     }
397/// }
398///
399/// impl Contract {
400///     // our set_owner method is not
401///     pub fn set_owner(&mut self, new_owner: Address) -> Result<(), Vec<u8>> {
402///         ...
403///     }
404/// }
405/// ```
406///
407/// In is example, [`Vec<u8>`] becomes the program's revert data.
408///
409/// # [`#[payable]`][payable]
410///
411/// As in Solidity, methods may accept ETH as call value.
412///
413/// ```ignore
414/// #[public]
415/// impl Contract {
416///     #[payable]
417///     pub fn credit(&mut self) -> Result<(), Vec<u8> {
418///         self.erc20.add_balance(msg::sender(), msg::value())
419///     }
420/// }
421/// ```
422///
423/// In the above, [msg::value][value] is the amount of ETH passed to the contract in wei, which may be used
424/// to pay for something depending on the contract's business logic. Note that you have to annotate the method
425/// with [`#[payable]`][payable], or else calls to it will revert. This is required as a safety measure
426/// to prevent users losing funds to methods that didn't intend to accept ether.
427///
428/// # [`pure`][pure] [`view`][view], and `write`
429///
430/// For non-payable methods the [`#[public]`][public] macro can figure state mutability out for you based
431/// on the types of the arguments. Functions with `&self` will be considered `view`, those with
432/// `&mut self` will be considered `write`, and those with neither will be considered `pure`. Please note that
433/// `pure` and `view` functions may change the state of other contracts by calling into them, or
434/// even this one if the `reentrant` feature is enabled.
435///
436/// Please refer to the [SDK Feature Overview][overview] for more information on defining methods.
437///
438/// # Inheritance, `#[inherit]`, and `#[borrow]`
439///
440/// Composition in Rust follows that of Solidity. Types that implement [`Router`], the trait that
441/// [`#[public]`][public] provides, can be connected via inheritance.
442///
443/// ```ignore
444/// #[public]
445/// #[inherit(Erc20)]
446/// impl Token {
447///     pub fn mint(&mut self, amount: U256) -> Result<(), Vec<u8>> {
448///         ...
449///     }
450/// }
451///
452/// #[public]
453/// impl Erc20 {
454///     pub fn balance_of() -> Result<U256> {
455///         ...
456///     }
457/// }
458/// ```
459///
460/// Because `Token` inherits `Erc20` in the above, if `Token` has the [`#[entrypoint]`][entrypoint], calls to the
461/// contract will first check if the requested method exists within `Token`. If a matching function is not found,
462/// it will then try the `Erc20`. Only after trying everything `Token` inherits will the call revert.
463///
464/// Note that because methods are checked in that order, if both implement the same method, the one in `Token`
465/// will override the one in `Erc20`, which won't be callable. This allows for patterns where the developer
466/// imports a crate implementing a standard, like ERC 20, and then adds or overrides just the methods they
467/// want to without modifying the imported `Erc20` type.
468///
469/// Stylus does not currently contain explicit `override` or `virtual` keywords for explicitly
470/// marking override functions. It is important, therefore, to carefully ensure that contracts are
471/// only overriding the functions.
472///
473/// Inheritance can also be chained. `#[inherit(Erc20, Erc721)]` will inherit both `Erc20` and `Erc721`, checking
474/// for methods in that order. `Erc20` and `Erc721` may also inherit other types themselves. Method resolution
475/// finds the first matching method by [`Depth First Search`][dfs].
476///
477/// Note that for the above to work, Token must implement [`Borrow<Erc20>`][Borrow] and
478/// [`BorrowMut<Erc20>`][BorrowMut]. You can implement this yourself, but for simplicity,
479/// [`#[storage]`][storage] and [`sol_storage!`][sol_storage] provide a
480/// `#[borrow]` annotation.
481///
482/// ```ignore
483/// sol_storage! {
484///     #[entrypoint]
485///     pub struct Token {
486///         #[borrow]
487///         Erc20 erc20;
488///         ...
489///     }
490///
491///     pub struct Erc20 {
492///         ...
493///     }
494/// }
495/// ```
496///
497/// In the future we plan to simplify the SDK so that [`Borrow`][Borrow] isn't needed and so that
498/// [`Router`] composition is more configurable. The motivation for this becomes clearer in complex
499/// cases of multi-level inheritance, which we intend to improve.
500///
501/// # Exporting a Solidity interface
502///
503/// Recall that Stylus contracts are fully interoperable across all languages, including Solidity.
504/// The Stylus SDK provides tools for exporting a Solidity interface for your contract so that others
505/// can call it. This is usually done with the cargo stylus [CLI tool][cli].
506///
507/// The SDK does this automatically via a feature flag called `export-abi` that causes the
508/// [`#[public]`][public] and [`#[entrypoint]`][entrypoint] macros to generate a `main` function
509/// that prints the Solidity ABI to the console.
510///
511/// ```sh
512/// cargo run --features export-abi --target <triple>
513/// ```
514///
515/// Note that because the above actually generates a `main` function that you need to run, the target
516/// can't be `wasm32-unknown-unknown` like normal. Instead you'll need to pass in your target triple,
517/// which cargo stylus figures out for you. This `main` function is also why the following commonly
518/// appears in the `main.rs` file of Stylus contracts.
519///
520/// ```ignore
521/// #![cfg_attr(not(feature = "export-abi"), no_main)]
522/// ```
523///
524/// Here's an example output. Observe that the method names change from Rust's `snake_case` to Solidity's
525/// `camelCase`. For compatibility reasons, onchain method selectors are always `camelCase`. We'll provide
526/// the ability to customize selectors very soon. Note too that you can use argument names like "address"
527/// without fear. The SDK will prepend an `_` when necessary.
528///
529/// ```solidity
530/// interface Erc20 {
531///     function name() external pure returns (string memory);
532///
533///     function balanceOf(address _address) external view returns (uint256);
534/// }
535///
536/// interface Weth is Erc20 {
537///     function mint() external payable;
538///
539///     function burn(uint256 amount) external;
540/// }
541/// ```
542///
543/// [storage]: macro@storage
544/// [sol_storage]: macro@sol_storage
545/// [entrypoint]: macro@entrypoint
546/// [public]: macro@public
547/// [overview]: https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#methods
548/// [`Router`]: https://docs.rs/stylus-sdk/latest/stylus_sdk/abi/trait.Router.html
549/// [Borrow]: https://doc.rust-lang.org/std/borrow/trait.Borrow.html
550/// [BorrowMut]: https://doc.rust-lang.org/std/borrow/trait.BorrowMut.html
551/// [value]: https://docs.rs/stylus-sdk/latest/stylus_sdk/msg/fn.value.html
552/// [payable]: https://docs.alchemy.com/docs/solidity-payable-functions
553/// [view]: https://docs.soliditylang.org/en/develop/contracts.html#view-functions
554/// [pure]: https://docs.soliditylang.org/en/develop/contracts.html#pure-functions
555/// [cli]: https://github.com/OffchainLabs/cargo-stylus#exporting-solidity-abis
556/// [dfs]: https://en.wikipedia.org/wiki/Depth-first_search
557#[proc_macro_attribute]
558pub fn public(attr: TokenStream, input: TokenStream) -> TokenStream {
559    methods::public::public(attr, input)
560}
561
562#[doc(hidden)]
563#[deprecated = "please use `#[public]` instead"]
564#[proc_macro_attribute]
565pub fn external(attr: TokenStream, input: TokenStream) -> TokenStream {
566    public(attr, input)
567}