Skip to content

Commit

Permalink
Merge pull request #97 from OffchainLabs/SolidityError
Browse files Browse the repository at this point in the history
SolidityError derive macro
  • Loading branch information
rachel-bousfield authored Feb 22, 2024
2 parents 25430af + ad8b9b7 commit 3a84d2b
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 29 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

The Stylus SDK enables smart contract developers to write programs for **Arbitrum chains** written in the [Rust](https://www.rust-lang.org/tools/install) programming language. Stylus programs are compiled to [WebAssembly](https://webassembly.org/) and can then be deployed on-chain to execute alongside Solidity smart contracts. Stylus programs are not only orders of magnitude cheaper and faster but also enable what was thought to be previously impossible for WebAssembly: **EVM-interoperability**.

For information about deploying Rust smart contracts, see the [Cargo Stylus CLI Tool](CargoStylus). For more information about Stylus, see [Stylus: A Gentle Introduction](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). For a simpler intro to Stylus Rust development, see the [Quick Start guide](https://docs.arbitrum.io/stylus/stylus-quickstart).
For information about deploying Rust smart contracts, see the [Cargo Stylus CLI Tool](https://github.com/OffchainLabs/cargo-stylus). For more information about Stylus, see [Stylus: A Gentle Introduction](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). For a simpler intro to Stylus Rust development, see the [Quick Start guide](https://docs.arbitrum.io/stylus/stylus-quickstart).

Comprehensive documentation on the Rust SDK can be found [here](https://docs.arbitrum.io/stylus/rust-sdk-guide).

Expand Down
61 changes: 54 additions & 7 deletions examples/erc20/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 2 additions & 11 deletions examples/erc20/src/erc20.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::{string::String, vec::Vec};
use alloc::string::String;
use core::marker::PhantomData;
use stylus_sdk::{
alloy_primitives::{Address, U256},
Expand Down Expand Up @@ -36,21 +36,12 @@ sol! {
error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
}

#[derive(SolidityError)]
pub enum Erc20Error {
InsufficientBalance(InsufficientBalance),
InsufficientAllowance(InsufficientAllowance),
}

// We will soon provide a #[derive(SolidityError)] to clean this up
impl From<Erc20Error> for Vec<u8> {
fn from(err: Erc20Error) -> Vec<u8> {
match err {
Erc20Error::InsufficientBalance(e) => e.encode(),
Erc20Error::InsufficientAllowance(e) => e.encode(),
}
}
}

// These methods aren't exposed to other contracts
// Note: modifying storage will become much prettier soon
impl<T: Erc20Params> Erc20<T> {
Expand Down
29 changes: 29 additions & 0 deletions stylus-proc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,35 @@ pub fn derive_erase(input: TokenStream) -> TokenStream {
storage::derive_erase(input)
}

/// Allows an error `enum` to be used in method signatures.
///
/// ```ignore
/// sol! {
/// error InsufficientBalance(address from, uint256 have, uint256 want);
/// error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
/// }
///
/// #[derive(SolidityError)]
/// pub enum Erc20Error {
/// InsufficientBalance(InsufficientBalance),
/// InsufficientAllowance(InsufficientAllowance),
/// }
///
/// #[external]
/// impl Contract {
/// pub fn fallible_method() -> Result<(), Erc20Error> {
/// // code that might revert
/// }
/// }
/// ```
///
/// Under the hood, the above macro works by implementing `From<Erc20Error>` for `Vec<u8>`
/// along with printing code for abi-export.
#[proc_macro_derive(SolidityError)]
pub fn derive_solidity_error(input: TokenStream) -> TokenStream {
methods::error::derive_solidity_error(input)
}

/// Defines the entrypoint, which is where Stylus execution begins.
/// Without it the contract will fail to pass [`cargo stylus check`][check].
/// Most commonly this macro is used to annotate the top level storage `struct`.
Expand Down
57 changes: 57 additions & 0 deletions stylus-proc/src/methods/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Fields, ItemEnum};

pub fn derive_solidity_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemEnum);
let name = &input.ident;
let mut match_arms = quote!();
let mut errors = vec![];
for variant in input.variants {
let variant_name = variant.ident;
let error = match variant.fields {
Fields::Unnamed(e) if variant.fields.len() == 1 => e.unnamed.first().unwrap().clone(),
_ => error!(variant.fields, "Variant not a 1-tuple"),
};
match_arms.extend(quote! {
#name::#variant_name(e) => stylus_sdk::alloy_sol_types::SolError::encode(&e),
});
errors.push(error);
}
let mut output = quote! {
impl From<#name> for alloc::vec::Vec<u8> {
fn from(err: #name) -> alloc::vec::Vec<u8> {
match err {
#match_arms
}
}
}
};

if cfg!(feature = "export-abi") {
output.extend(quote! {
impl stylus_sdk::abi::export::internal::InnerTypes for #name {
fn inner_types() -> alloc::vec::Vec<stylus_sdk::abi::export::internal::InnerType> {
use alloc::{format, vec};
use core::any::TypeId;
use stylus_sdk::abi::export::internal::InnerType;
use stylus_sdk::alloy_sol_types::SolError;

vec![
#(
InnerType {
name: format!("error {};", <#errors as SolError>::SIGNATURE.replace(',', ", ")),
id: TypeId::of::<#errors>(),
}
),*
]
}
}
});
}

output.into()
}
23 changes: 22 additions & 1 deletion stylus-proc/src/methods/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn external(_attr: TokenStream, input: TokenStream) -> TokenStream {
let mut selectors = quote!();
let mut match_selectors = quote!();
let mut abi = quote!();
let mut types = vec![];

for item in input.items.iter_mut() {
let ImplItem::Method(method) = item else {
Expand Down Expand Up @@ -278,6 +279,24 @@ pub fn external(_attr: TokenStream, input: TokenStream) -> TokenStream {
return router.into();
}

for item in input.items.iter_mut() {
let ImplItem::Method(method) = item else {
continue;
};
if let ReturnType::Type(_, ty) = &method.sig.output {
types.push(ty);
}
}

let type_decls = quote! {
let mut seen = HashSet::new();
for item in [].iter() #(.chain(&<#types as InnerTypes>::inner_types()))* {
if seen.insert(item.id) {
writeln!(f, "\n {}", item.name)?;
}
}
};

let name = match *self_ty.clone() {
Type::Path(path) => path.path.segments.last().unwrap().ident.clone().to_string(),
_ => error!(self_ty, "Can't generate ABI for unnamed type"),
Expand Down Expand Up @@ -309,12 +328,14 @@ pub fn external(_attr: TokenStream, input: TokenStream) -> TokenStream {
fn fmt_abi(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use stylus_sdk::abi::{AbiType, GenerateAbi};
use stylus_sdk::abi::internal::write_solidity_returns;
use stylus_sdk::abi::export::{underscore_if_sol};
use stylus_sdk::abi::export::{underscore_if_sol, internal::InnerTypes};
use std::collections::HashSet;
#(#inherited_abis)*
write!(f, "interface I{}", #name)?;
#is_clause
write!(f, " {{")?;
#abi
#type_decls
writeln!(f, "}}")?;
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions stylus-proc/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md

pub mod entrypoint;
pub mod error;
pub mod external;
8 changes: 4 additions & 4 deletions stylus-proc/src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023, Offchain Labs, Inc.
// Copyright 2023-2024, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md

use crate::storage::proc::{SolidityField, SolidityFields, SolidityStruct, SolidityStructs};
Expand Down Expand Up @@ -214,12 +214,12 @@ pub fn derive_erase(input: TokenStream) -> TokenStream {
self.#ident.erase();
});
}
let output = quote! {
quote! {
impl #impl_generics stylus_sdk::storage::Erase for #name #ty_generics #where_clause {
fn erase(&mut self) {
#erase_fields
}
}
};
output.into()
}
.into()
}
Loading

0 comments on commit 3a84d2b

Please sign in to comment.