Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
qalisander committed Sep 19, 2024
1 parent 252264f commit de539d3
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use syn::{
};

/// Computes interface id as an associated constant for the trait.
pub(crate) fn interface(_attr: TokenStream, input: TokenStream) -> TokenStream {
pub(crate) fn interface_id(
_attr: &TokenStream,
input: TokenStream,
) -> TokenStream {
let mut input = parse_macro_input!(input as ItemTrait);
let mut output = quote! {};

Expand Down Expand Up @@ -48,7 +51,7 @@ pub(crate) fn interface(_attr: TokenStream, input: TokenStream) -> TokenStream {
let arg_types: Vec<_> = args
.filter_map(|arg| match arg {
FnArg::Typed(t) => Some(t.ty.clone()),
_ => None,
FnArg::Receiver(_) => None,
})
.collect();

Expand Down
50 changes: 45 additions & 5 deletions contracts-proc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// TODO#q: add crate documentation.
//! 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),+));
Expand All @@ -14,10 +17,47 @@ macro_rules! error {
}};
}

mod interface;
mod interface_id;

/// Computes interface id as an associated constant for the trait.
/// Computes interface id as an associated constant `INTERFACE_ID` for the

Check warning on line 22 in contracts-proc/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] contracts-proc/src/lib.rs#L22

warning: first doc comment paragraph is too long --> contracts-proc/src/lib.rs:22:1 | 22 | / /// Computes interface id as an associated constant `INTERFACE_ID` for the 23 | | /// trait that describes contract's abi. 24 | | /// Selector collision should be handled with macro `#[selector(name = 25 | | /// "actualSolidityMethodName")]` on top of the method. 26 | | /// 27 | | /// # Examples | |_ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph = note: `-W clippy::too-long-first-doc-paragraph` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::too_long_first_doc_paragraph)]`
Raw output
contracts-proc/src/lib.rs:22:1:w:warning: first doc comment paragraph is too long
  --> contracts-proc/src/lib.rs:22:1
   |
22 | / /// Computes interface id as an associated constant `INTERFACE_ID` for the
23 | | /// trait that describes contract's abi.
24 | | /// Selector collision should be handled with macro `#[selector(name =
25 | | /// "actualSolidityMethodName")]` on top of the method.
26 | | ///
27 | | /// # Examples
   | |_
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph
   = note: `-W clippy::too-long-first-doc-paragraph` implied by `-W clippy::all`
   = help: to override `-W clippy::all` add `#[allow(clippy::too_long_first_doc_paragraph)]`


__END__

Check warning on line 22 in contracts-proc/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] contracts-proc/src/lib.rs#L22

warning: first doc comment paragraph is too long --> contracts-proc/src/lib.rs:22:1 | 22 | / /// Computes interface id as an associated constant `INTERFACE_ID` for the 23 | | /// trait that describes contract's abi. 24 | | /// Selector collision should be handled with macro `#[selector(name = 25 | | /// "actualSolidityMethodName")]` on top of the method. 26 | | /// 27 | | /// # Examples | |_ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph = note: `-W clippy::too-long-first-doc-paragraph` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::too_long_first_doc_paragraph)]`
Raw output
contracts-proc/src/lib.rs:22:1:w:warning: first doc comment paragraph is too long
  --> contracts-proc/src/lib.rs:22:1
   |
22 | / /// Computes interface id as an associated constant `INTERFACE_ID` for the
23 | | /// trait that describes contract's abi.
24 | | /// Selector collision should be handled with macro `#[selector(name =
25 | | /// "actualSolidityMethodName")]` on top of the method.
26 | | ///
27 | | /// # Examples
   | |_
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_long_first_doc_paragraph
   = note: `-W clippy::too-long-first-doc-paragraph` implied by `-W clippy::all`
   = help: to override `-W clippy::all` add `#[allow(clippy::too_long_first_doc_paragraph)]`


__END__
/// 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, alloc::vec::Vec<u8>>;
///
/// fn owner_of(&self, token_id: U256) -> Result<Address, alloc::vec::Vec<u8>>;
///
/// fn safe_transfer_from(
/// &mut self,
/// from: Address,
/// to: Address,
/// token_id: U256,
/// ) -> Result<(), alloc::vec::Vec<u8>>;
///
/// #[selector(name = "safeTransferFrom")]
/// fn safe_transfer_from_with_data(
/// &mut self,
/// from: Address,
/// to: Address,
/// token_id: U256,
/// data: Bytes,
/// ) -> Result<(), alloc::vec::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(attr: TokenStream, input: TokenStream) -> TokenStream {
interface::interface(attr, input)
pub fn interface_id(attr: TokenStream, input: TokenStream) -> TokenStream {
interface_id::interface_id(&attr, input)
}
12 changes: 12 additions & 0 deletions contracts/src/token/erc20/extensions/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

use alloc::string::String;

use alloy_primitives::FixedBytes;
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::{public, sol_storage};

use crate::utils::introspection::erc165::IErc165;

/// Number of decimals used by default on implementors of [`Metadata`].
pub const DEFAULT_DECIMALS: u8 = 18;

Expand All @@ -20,6 +24,7 @@ sol_storage! {
}

/// Interface for the optional metadata functions from the ERC-20 standard.
#[interface_id]
pub trait IErc20Metadata {
/// Returns the name of the token.
///
Expand Down Expand Up @@ -76,3 +81,10 @@ impl IErc20Metadata for Erc20Metadata {
DEFAULT_DECIMALS
}
}

impl IErc165 for Erc20Metadata {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc20Metadata>::INTERFACE_ID
== u32::from_be_bytes(*interface_id)
}
}
26 changes: 21 additions & 5 deletions contracts/src/token/erc20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
//! revert instead of returning `false` on failure. This behavior is
//! nonetheless conventional and does not conflict with the expectations of
//! [`Erc20`] applications.
use alloy_primitives::{Address, U256};
use alloy_primitives::{Address, FixedBytes, U256};
use alloy_sol_types::sol;
use openzeppelin_stylus_proc::interface;
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::SolidityError;
use stylus_sdk::{
call::MethodError,
evm, msg,
stylus_proc::{public, sol_storage},
};

use crate::utils::introspection::erc165::{Erc165, IErc165};

pub mod extensions;

sol! {
Expand Down Expand Up @@ -112,7 +114,7 @@ sol_storage! {
}

/// Required interface of an [`Erc20`] compliant contract.
#[interface]
#[interface_id]
pub trait IErc20 {
/// The error type associated to this ERC-20 trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;
Expand Down Expand Up @@ -288,6 +290,13 @@ impl IErc20 for Erc20 {
}
}

impl IErc165 for Erc20 {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc20>::INTERFACE_ID == u32::from_be_bytes(*interface_id)
|| Erc165::supports_interface(interface_id)
}
}

impl Erc20 {
/// Sets a `value` number of tokens as the allowance of `spender` over the
/// caller's tokens.
Expand Down Expand Up @@ -552,7 +561,10 @@ mod tests {
use stylus_sdk::msg;

use super::{Erc20, Error, IErc20};
use crate::token::erc721::{Erc721, IErc721};
use crate::{
token::erc721::{Erc721, IErc721},
utils::introspection::erc165::IErc165,
};

#[motsu::test]
fn reads_balance(contract: Erc20) {
Expand Down Expand Up @@ -889,7 +901,11 @@ mod tests {
#[motsu::test]
fn interface_id() {
let actual = <Erc20 as IErc20>::INTERFACE_ID;
let expected = 0x_36372b07;
let expected = 0x36372b07;
assert_eq!(actual, expected);

let actual = <Erc20 as IErc165>::INTERFACE_ID;
let expected = 0x01ffc9a7;
assert_eq!(actual, expected);
}
}
16 changes: 14 additions & 2 deletions contracts/src/token/erc721/extensions/enumerable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
//! [`Erc721Enumerable`].
// TODO: Add link for `Erc721Consecutive` to module docs.

use alloy_primitives::{uint, Address, U256};
use alloy_primitives::{uint, Address, FixedBytes, U256};
use alloy_sol_types::sol;
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::{public, sol_storage, SolidityError};

use crate::token::{erc721, erc721::IErc721};
use crate::{
token::{erc721, erc721::IErc721},
utils::introspection::erc165::IErc165,
};

sol! {
/// Indicates an error when an `owner`'s token query
Expand Down Expand Up @@ -63,6 +67,7 @@ sol_storage! {

/// This is the interface of the optional `Enumerable` extension
/// of the ERC-721 standard.
#[interface_id]
pub trait IErc721Enumerable {
/// The error type associated to this ERC-721 enumerable trait
/// implementation.
Expand Down Expand Up @@ -144,6 +149,13 @@ impl IErc721Enumerable for Erc721Enumerable {
}
}

impl IErc165 for Erc721Enumerable {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc721Enumerable>::INTERFACE_ID
== u32::from_be_bytes(*interface_id)
}
}

impl Erc721Enumerable {
/// Function to add a token to this extension's
/// ownership-tracking data structures.
Expand Down
15 changes: 14 additions & 1 deletion contracts/src/token/erc721/extensions/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

use alloc::string::String;

use alloy_primitives::FixedBytes;
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::{public, sol_storage};

use crate::utils::Metadata;
use crate::{
token::erc20::extensions::IErc20Metadata,

Check warning on line 10 in contracts/src/token/erc721/extensions/metadata.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] contracts/src/token/erc721/extensions/metadata.rs#L10

warning: unused import: `token::erc20::extensions::IErc20Metadata` --> contracts/src/token/erc721/extensions/metadata.rs:10:5 | 10 | token::erc20::extensions::IErc20Metadata, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
Raw output
contracts/src/token/erc721/extensions/metadata.rs:10:5:w:warning: unused import: `token::erc20::extensions::IErc20Metadata`
  --> contracts/src/token/erc721/extensions/metadata.rs:10:5
   |
10 |     token::erc20::extensions::IErc20Metadata,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default


__END__
utils::{introspection::erc165::IErc165, Metadata},
};

sol_storage! {
/// Metadata of an [`crate::token::erc721::Erc721`] token.
Expand All @@ -17,6 +22,7 @@ sol_storage! {
}

/// Interface for the optional metadata functions from the ERC-721 standard.
#[interface_id]
pub trait IErc721Metadata {
/// Returns the token collection name.
///
Expand Down Expand Up @@ -58,3 +64,10 @@ impl IErc721Metadata for Erc721Metadata {
self._base_uri.get_string()
}
}

impl IErc165 for Erc721Metadata {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc721Metadata>::INTERFACE_ID
== u32::from_be_bytes(*interface_id)
}
}
23 changes: 19 additions & 4 deletions contracts/src/token/erc721/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use alloc::vec;

use alloy_primitives::{fixed_bytes, uint, Address, FixedBytes, U128, U256};
use openzeppelin_stylus_proc::interface;
use openzeppelin_stylus_proc::interface_id;
use stylus_sdk::{
abi::Bytes,
alloy_sol_types::sol,
Expand All @@ -11,7 +11,10 @@ use stylus_sdk::{
prelude::*,
};

use crate::utils::math::storage::{AddAssignUnchecked, SubAssignUnchecked};
use crate::utils::{
introspection::erc165::{Erc165, IErc165},
math::storage::{AddAssignUnchecked, SubAssignUnchecked},
};

pub mod extensions;

Expand Down Expand Up @@ -199,7 +202,7 @@ sol_storage! {
unsafe impl TopLevelStorage for Erc721 {}

/// Required interface of an [`Erc721`] compliant contract.
#[interface]
#[interface_id]
pub trait IErc721 {
/// The error type associated to this ERC-721 trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;
Expand Down Expand Up @@ -552,6 +555,13 @@ impl IErc721 for Erc721 {
}
}

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)
}
}

impl Erc721 {
/// Returns the owner of the `token_id`. Does NOT revert if the token
/// doesn't exist.
Expand Down Expand Up @@ -1156,6 +1166,7 @@ mod tests {
ERC721InvalidReceiver, ERC721InvalidSender, ERC721NonexistentToken,
Erc721, Error, IErc721,
};
use crate::utils::introspection::erc165::IErc165;

const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526");
const DAVE: Address = address!("0BB78F7e7132d1651B4Fd884B7624394e92156F1");
Expand Down Expand Up @@ -2494,7 +2505,11 @@ mod tests {
#[motsu::test]
fn interface_id() {
let actual = <Erc721 as IErc721>::INTERFACE_ID;
let expected = 0x_80ac58cd;
let expected = 0x80ac58cd;
assert_eq!(actual, expected);

let actual = <Erc721 as IErc165>::INTERFACE_ID;
let expected = 0x01ffc9a7;
assert_eq!(actual, expected);
}
}
64 changes: 64 additions & 0 deletions contracts/src/utils/introspection/erc165.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Trait and implementation of the ERC-165 standard, as defined in the [ERC].
//!
//! [ERC]: https://eips.ethereum.org/EIPS/eip-165

use alloy_primitives::FixedBytes;
use openzeppelin_stylus_proc::interface_id;

/// Interface of the ERC-165 standard, as defined in the [ERC].
///
/// Implementers can declare support of contract interfaces, which others can
/// query.
///
/// For an implementation, see [`Erc165`].
///
/// [ERC]: https://eips.ethereum.org/EIPS/eip-165
#[interface_id]
pub trait IErc165 {
/// Returns true if this contract implements the interface defined by
/// `interfaceId`. See the corresponding [ERC section]
/// to learn more about how these ids are created.
///
/// Method [`IErc165::supports_interface`] should be reexported with
/// `#[public]` macro manually like this:
///
/// ```rust,ignore
/// #[public]
/// impl Erc20Example {
/// fn supports_interface(interface_id: FixedBytes<4>) -> bool {
/// Erc20::supports_interface(interface_id)
/// || Erc20Metadata::supports_interface(interface_id)
/// }
/// }
/// ```
///
/// # Arguments
///
/// * `interface_id` - The interface identifier, as specified in [ERC
/// section]
///
/// [ERC section]: https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified
fn supports_interface(interface_id: FixedBytes<4>) -> bool;
}

/// Implementation of the [`IErc165`] trait.
///
/// Contracts that want to support ERC-165 should implement the [`IErc165`]
/// trait for the additional interface id that will be supported and call
/// [`Erc165::supports_interface`] like:
///
/// ```rust,ignore
/// impl IErc165 for Erc20 {
/// fn supports_interface(interface_id: FixedBytes<4>) -> bool {
/// crate::token::erc20::INTERFACE_ID == u32::from_be_bytes(*interface_id)
/// || Erc165::supports_interface(interface_id)
/// }
/// }
/// ```
pub struct Erc165;

impl IErc165 for Erc165 {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
Self::INTERFACE_ID == u32::from_be_bytes(*interface_id)
}
}
2 changes: 2 additions & 0 deletions contracts/src/utils/introspection/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//! Stylus contract's introspection helpers library.
pub mod erc165;
Loading

0 comments on commit de539d3

Please sign in to comment.