Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(L-02): Inconsistent Implementations in ERC-721 Extension #331

Draft
wants to merge 5 commits into
base: v0.1.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/check-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: check-publish
# This workflow checks that the libraries can be published on crates.io.
permissions:
contents: read
on:
push:
branches: [ main ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
check-publish:
name: Check publish on crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: set up rust
uses: dtolnay/rust-toolchain@master
id: toolchain
with:
target: wasm32-unknown-unknown
components: rust-src
toolchain: nightly-2024-01-01

- uses: Swatinem/rust-cache@v2

- name: check motsu-proc
run: cargo publish -p motsu-proc --dry-run

- name: check motsu
run: cargo publish -p motsu --dry-run

- name: check openzeppelin-crypto
run: cargo publish -p openzeppelin-crypto --target wasm32-unknown-unknown --dry-run

- name: check openzeppelin-stylus-proc
run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --dry-run

# TODO: https://github.com/OpenZeppelin/rust-contracts-stylus/issues/291
# - name: check openzeppelin-stylus
# run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run
31 changes: 21 additions & 10 deletions Cargo.lock

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

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"contracts",
"contracts-proc",
"lib/crypto",
"lib/motsu",
"lib/motsu-proc",
Expand All @@ -21,6 +22,7 @@ members = [
]
default-members = [
"contracts",
"contracts-proc",
"lib/crypto",
"lib/motsu",
"lib/motsu-proc",
Expand Down Expand Up @@ -48,6 +50,7 @@ edition = "2021"
license = "MIT"
keywords = ["arbitrum", "ethereum", "stylus"]
repository = "https://github.com/OpenZeppelin/rust-contracts-stylus"
version = "0.1.0-rc"

[workspace.lints.rust]
missing_docs = "warn"
Expand Down Expand Up @@ -95,6 +98,15 @@ syn = { version = "2.0.58", features = ["full"] }
proc-macro2 = "1.0.79"
quote = "1.0.35"

# members
openzeppelin-stylus = { path = "contracts" }
openzeppelin-stylus-proc = { path = "contracts-proc" }
openzeppelin-crypto = { path = "lib/crypto" }
motsu = { path = "lib/motsu"}
motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" }
e2e = { path = "lib/e2e" }
e2e-proc = {path = "lib/e2e-proc"}

[profile.release]
codegen-units = 1
panic = "abort"
Expand Down
6 changes: 3 additions & 3 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ edition.workspace = true
license.workspace = true
repository.workspace = true
publish = false
version = "0.0.0"
version.workspace = true

[dependencies]
openzeppelin-stylus = { path = "../contracts" }
openzeppelin-stylus.workspace = true
alloy-primitives = { workspace = true, features = ["tiny-keccak"] }
alloy.workspace = true
tokio.workspace = true
eyre.workspace = true
koba.workspace = true
e2e = { path = "../lib/e2e" }
e2e.workspace = true
serde = "1.0.203"
keccak-const = "0.2.0"
22 changes: 22 additions & 0 deletions contracts-proc/Cargo.toml
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
102 changes: 102 additions & 0 deletions contracts-proc/src/interface_id.rs
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'")
}
}
}
Loading
Loading