-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: erc165 support interface (#281)
Adds erc165 standard altogether with a proc macro, that lets to compute interface id in a simple way. <!-- Fill in with issue number --> Resolves #33 #### PR Checklist - [x] Tests - [x] Documentation --------- Co-authored-by: Daniel Bigos <[email protected]> (cherry picked from commit 9b04143)
- Loading branch information
1 parent
c7af5ab
commit 762a0e5
Showing
21 changed files
with
506 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "openzeppelin-stylus-proc" | ||
description = "Procedural macros for OpenZeppelin Stylus contracts" | ||
version.workspace = true | ||
authors.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
keywords.workspace = true | ||
repository.workspace = true | ||
|
||
|
||
[dependencies] | ||
proc-macro2.workspace = true | ||
quote.workspace = true | ||
syn.workspace = true | ||
convert_case = "0.6.0" | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[lib] | ||
proc-macro = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
//! Defines the `#[interface_id]` procedural macro. | ||
|
||
use std::mem; | ||
|
||
use convert_case::{Case, Casing}; | ||
use proc_macro::TokenStream; | ||
use proc_macro2::Ident; | ||
use quote::quote; | ||
use syn::{ | ||
parse::{Parse, ParseStream}, | ||
parse_macro_input, FnArg, ItemTrait, LitStr, Result, Token, TraitItem, | ||
}; | ||
|
||
/// Computes an interface id as an associated constant for the trait. | ||
pub(crate) fn interface_id( | ||
_attr: &TokenStream, | ||
input: TokenStream, | ||
) -> TokenStream { | ||
let mut input = parse_macro_input!(input as ItemTrait); | ||
|
||
let mut selectors = Vec::new(); | ||
for item in &mut input.items { | ||
let TraitItem::Fn(func) = item else { | ||
continue; | ||
}; | ||
|
||
let mut override_fn_name = None; | ||
for attr in mem::take(&mut func.attrs) { | ||
if attr.path().is_ident("selector") { | ||
if override_fn_name.is_some() { | ||
error!(attr.path(), "more than one selector attribute"); | ||
} | ||
let args: SelectorArgs = match attr.parse_args() { | ||
Ok(args) => args, | ||
Err(error) => error!(attr.path(), "{}", error), | ||
}; | ||
override_fn_name = Some(args.name); | ||
} else { | ||
// Put back any other attributes. | ||
func.attrs.push(attr); | ||
} | ||
} | ||
|
||
let solidity_fn_name = override_fn_name.unwrap_or_else(|| { | ||
let rust_fn_name = func.sig.ident.to_string(); | ||
rust_fn_name.to_case(Case::Camel) | ||
}); | ||
|
||
let arg_types = func.sig.inputs.iter().filter_map(|arg| match arg { | ||
FnArg::Typed(t) => Some(t.ty.clone()), | ||
// Opt out any `self` arguments. | ||
FnArg::Receiver(_) => None, | ||
}); | ||
|
||
// Store selector expression from every function in the trait. | ||
selectors.push( | ||
quote! { u32::from_be_bytes(stylus_sdk::function_selector!(#solidity_fn_name #(, #arg_types )*)) } | ||
); | ||
} | ||
|
||
let name = input.ident; | ||
let vis = input.vis; | ||
let attrs = input.attrs; | ||
let trait_items = input.items; | ||
let (_impl_generics, ty_generics, where_clause) = | ||
input.generics.split_for_impl(); | ||
|
||
// Keep the same trait with an additional associated constant | ||
// `INTERFACE_ID`. | ||
quote! { | ||
#(#attrs)* | ||
#vis trait #name #ty_generics #where_clause { | ||
#(#trait_items)* | ||
|
||
#[doc = concat!("Solidity interface id associated with ", stringify!(#name), " trait.")] | ||
#[doc = "Computed as a XOR of selectors for each function in the trait."] | ||
const INTERFACE_ID: u32 = { | ||
#(#selectors)^* | ||
}; | ||
} | ||
} | ||
.into() | ||
} | ||
|
||
/// Contains arguments of the `#[selector(..)]` attribute. | ||
struct SelectorArgs { | ||
name: String, | ||
} | ||
|
||
impl Parse for SelectorArgs { | ||
fn parse(input: ParseStream) -> Result<Self> { | ||
let ident: Ident = input.parse()?; | ||
|
||
if ident == "name" { | ||
let _: Token![=] = input.parse()?; | ||
let lit: LitStr = input.parse()?; | ||
Ok(SelectorArgs { name: lit.value() }) | ||
} else { | ||
error!(@ident, "expected identifier 'name'") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//! Procedural macro definitions used in `openzeppelin-stylus` smart contracts | ||
//! library. | ||
|
||
extern crate proc_macro; | ||
use proc_macro::TokenStream; | ||
|
||
/// Shorthand to print nice errors. | ||
/// | ||
/// Note that it's defined before the module declarations. | ||
macro_rules! error { | ||
($tokens:expr, $($msg:expr),+ $(,)?) => {{ | ||
let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); | ||
return error.to_compile_error().into(); | ||
}}; | ||
(@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ | ||
return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) | ||
}}; | ||
} | ||
|
||
mod interface_id; | ||
|
||
/// Computes the interface id as an associated constant `INTERFACE_ID` for the | ||
/// trait that describes contract's abi. | ||
/// | ||
/// Selector collision should be handled with | ||
/// macro `#[selector(name = "actualSolidityMethodName")]` on top of the method. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust,ignore | ||
/// #[interface_id] | ||
/// pub trait IErc721 { | ||
/// fn balance_of(&self, owner: Address) -> Result<U256, Vec<u8>>; | ||
/// | ||
/// fn owner_of(&self, token_id: U256) -> Result<Address, Vec<u8>>; | ||
/// | ||
/// fn safe_transfer_from( | ||
/// &mut self, | ||
/// from: Address, | ||
/// to: Address, | ||
/// token_id: U256, | ||
/// ) -> Result<(), Vec<u8>>; | ||
/// | ||
/// #[selector(name = "safeTransferFrom")] | ||
/// fn safe_transfer_from_with_data( | ||
/// &mut self, | ||
/// from: Address, | ||
/// to: Address, | ||
/// token_id: U256, | ||
/// data: Bytes, | ||
/// ) -> Result<(), Vec<u8>>; | ||
/// } | ||
/// | ||
/// impl IErc165 for Erc721 { | ||
/// fn supports_interface(interface_id: FixedBytes<4>) -> bool { | ||
/// <Self as IErc721>::INTERFACE_ID == u32::from_be_bytes(*interface_id) | ||
/// || Erc165::supports_interface(interface_id) | ||
/// } | ||
/// } | ||
/// ``` | ||
#[proc_macro_attribute] | ||
pub fn interface_id(attr: TokenStream, input: TokenStream) -> TokenStream { | ||
interface_id::interface_id(&attr, input) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.