Skip to content
11 changes: 11 additions & 0 deletions packages/macros/src/attribute/with_components/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum AllowedComponents {
ERC721,
ERC721Enumerable,
ERC721Receiver,
ERC721URIStorage,
ERC1155,
ERC1155Supply,
ERC1155Receiver,
Expand Down Expand Up @@ -63,6 +64,7 @@ impl AllowedComponents {
"ERC721" => Ok(AllowedComponents::ERC721),
"ERC721Enumerable" => Ok(AllowedComponents::ERC721Enumerable),
"ERC721Receiver" => Ok(AllowedComponents::ERC721Receiver),
"ERC721URIStorage" => Ok(AllowedComponents::ERC721URIStorage),
"ERC1155" => Ok(AllowedComponents::ERC1155),
"ERC1155Supply" => Ok(AllowedComponents::ERC1155Supply),
"ERC1155Receiver" => Ok(AllowedComponents::ERC1155Receiver),
Expand Down Expand Up @@ -227,6 +229,15 @@ impl AllowedComponents {
has_immutable_config: false,
internal_impls: vec!["InternalImpl"],
},
AllowedComponents::ERC721URIStorage => ComponentInfo {
name: "ERC721URIStorageComponent",
path: "openzeppelin_token::erc721::extensions::ERC721URIStorageComponent",
storage: "erc721_uri_storage",
event: "ERC721URIStorageEvent",
has_initializer: false,
has_immutable_config: false,
internal_impls: vec!["InternalImpl"],
},
AllowedComponents::ERC1155 => ComponentInfo {
name: "ERC1155Component",
path: "openzeppelin_token::erc1155::ERC1155Component",
Expand Down
9 changes: 9 additions & 0 deletions packages/macros/src/attribute/with_components/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ pub mod warnings {
"
};

/// Warning when the ERC721URIStorage hook call is missing.
pub const ERC721_URI_STORAGE_HOOKS_MISSING: &str = indoc! {
"The ERC721URIStorage component requires calling `self.erc721_uri_storage.after_update(...)`
inside the `ERC721HooksTrait::after_update` hook, and it looks like it is missing.

This may lead to incorrect token URI data.
"
};

/// Warning when the ERC4626 component is missing an implementation of the ERC4626HooksTrait.
pub const ERC4626_HOOKS_IMPL_MISSING: &str = indoc! {
"The ERC4626 component requires an implementation of the ERC4626HooksTrait in scope and
Expand Down
47 changes: 34 additions & 13 deletions packages/macros/src/attribute/with_components/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,36 @@ fn validate_contract_module(
.path
.strip_suffix(&component.name)
.expect("Component path must end with the component name");
let re = Regex::new(&format!(
r"use {component_parent_path}[{{\w, \n]*DefaultConfig[{{\w}}, \n]*;"
let default_config_import_re = Regex::new(&format!(
r"use {component_parent_path}[{{\w:, \n]*DefaultConfig(\s+as\s+\w+)?[{{\w}}, \n]*;"
))
.unwrap();

let default_config_used = re.is_match(&code);
if !default_config_used {
let immutable_config_implemented =
code.contains(&format!("of {}::ImmutableConfig", component.name));
if !immutable_config_implemented {
let warning = Diagnostic::warn(warnings::IMMUTABLE_CONFIG_MISSING(
component.short_name(),
&format!("{component_parent_path}DefaultConfig"),
));
warnings.push(warning);
}
let default_config_used = default_config_import_re.is_match(&code);
if default_config_used {
continue;
}
let immutable_config_implemented =
code.contains(&format!("of {}::ImmutableConfig", component.name));
if immutable_config_implemented {
continue;
}
Comment thread
immrsd marked this conversation as resolved.
let immutable_config_import_re = Regex::new(&format!(
r"use {component_parent_path}[\w:]*\w+::[{{\w, \n]*ImmutableConfig(\s+as\s+\w+)?[{{\w}}, \n]*;"
))
.unwrap();
let immutable_config_imported = immutable_config_import_re.is_match(&code);
let imported_immutable_config_implemented =
code.contains("of ImmutableConfig");
if immutable_config_imported && imported_immutable_config_implemented {
continue;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
// Add warning for missing immutable config
let warning = Diagnostic::warn(warnings::IMMUTABLE_CONFIG_MISSING(
component.short_name(),
&format!("{component_parent_path}DefaultConfig"),
));
warnings.push(warning);
}
}

Expand Down Expand Up @@ -348,6 +361,14 @@ fn add_per_component_warnings(code: &str, component_info: &ComponentInfo) -> Vec
let warning = Diagnostic::warn(warnings::ERC721_ENUMERABLE_HOOKS_MISSING);
warnings.push(warning);
}
},
AllowedComponents::ERC721URIStorage => {
let hook_called = code.contains("erc721_uri_storage.after_update(")
|| code.contains("ERC721URIStorageInternalImpl::after_update(");
if !hook_called {
let warning = Diagnostic::warn(warnings::ERC721_URI_STORAGE_HOOKS_MISSING);
warnings.push(warning);
}
}
Comment thread
immrsd marked this conversation as resolved.
Outdated
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/presets/src/erc721.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub mod ERC721Upgradeable {
use openzeppelin_interfaces::upgrades::IUpgradeable;
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin_token::erc721::{
ERC721Component, ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl,
ERC721Component, ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
use openzeppelin_upgrades::UpgradeableComponent;
use starknet::{ClassHash, ContractAddress};
Expand Down
91 changes: 85 additions & 6 deletions packages/test_common/src/mocks/erc721.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const SUCCESS: felt252 = 'SUCCESS';
#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod DualCaseERC721Mock {
use openzeppelin_token::erc721::{ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl};
use openzeppelin_token::erc721::{
ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
use starknet::ContractAddress;

// ERC721
Expand Down Expand Up @@ -43,7 +45,9 @@ pub mod DualCaseERC721Mock {
#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod SnakeERC721Mock {
use openzeppelin_token::erc721::{ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl};
use openzeppelin_token::erc721::{
ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
use starknet::ContractAddress;

// ERC721
Expand Down Expand Up @@ -78,7 +82,7 @@ pub mod SnakeERC721Mock {
#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod SnakeERC721MockWithHooks {
use openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;
use openzeppelin_token::erc721::{ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl};
use starknet::ContractAddress;

// ERC721
Expand Down Expand Up @@ -207,7 +211,7 @@ pub mod DualCaseERC721ReceiverMock {
#[starknet::contract]
#[with_components(ERC721, ERC721Enumerable, SRC5)]
pub mod ERC721EnumerableMock {
use openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;
use openzeppelin_token::erc721::{ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl};
use starknet::ContractAddress;

// ERC721
Expand Down Expand Up @@ -285,7 +289,9 @@ pub trait IERC721Burnable<TState> {
#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod ERC721MintableMock {
use openzeppelin_token::erc721::{ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl};
use openzeppelin_token::erc721::{
ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
use starknet::ContractAddress;
use super::IERC721Mintable;

Expand Down Expand Up @@ -318,7 +324,9 @@ pub mod ERC721MintableMock {
#[starknet::contract]
#[with_components(ERC721, SRC5, ERC721Wrapper)]
pub mod ERC721WrapperMock {
use openzeppelin_token::erc721::{ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl};
use openzeppelin_token::erc721::{
ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
use starknet::ContractAddress;
use super::IERC721WrapperRecoverer;

Expand Down Expand Up @@ -360,9 +368,79 @@ pub mod ERC721WrapperMock {
}
}

#[starknet::interface]
pub trait ERC721URIStorageMockABI<TState> {
fn mint(ref self: TState, to: ContractAddress, token_id: u256);
fn burn(ref self: TState, token_id: u256);
fn set_token_uri(ref self: TState, token_id: u256, uri: ByteArray);
}

#[starknet::contract]
#[with_components(ERC721, ERC721URIStorage, SRC5)]
pub mod ERC721URIStorageMock {
use openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;
use openzeppelin_token::erc721::extensions::erc721_uri_storage::ERC721URIStorageComponent::ERC721TokenURIStorageImpl;
use starknet::ContractAddress;
use super::ERC721URIStorageMockABI;

// ERC721
#[abi(embed_v0)]
impl ERC721Impl = ERC721Component::ERC721Impl<ContractState>;
#[abi(embed_v0)]
impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl<ContractState>;

// SRC5
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;

#[storage]
pub struct Storage {}

#[constructor]
fn constructor(
ref self: ContractState,
name: ByteArray,
symbol: ByteArray,
base_uri: ByteArray,
recipient: ContractAddress,
token_id: u256,
) {
self.erc721.initializer(name, symbol, base_uri);
self.erc721.mint(recipient, token_id);
}

impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait<ContractState> {
fn after_update(
ref self: ERC721Component::ComponentState<ContractState>,
to: ContractAddress,
token_id: u256,
auth: ContractAddress,
) {
let mut contract_state = self.get_contract_mut();
contract_state.erc721_uri_storage.after_update(to, token_id, auth);
}
}

#[abi(embed_v0)]
impl ExternalImpl of ERC721URIStorageMockABI<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, token_id: u256) {
self.erc721.mint(to, token_id);
}

fn burn(ref self: ContractState, token_id: u256) {
self.erc721.burn(token_id);
}

fn set_token_uri(ref self: ContractState, token_id: u256, uri: ByteArray) {
self.erc721_uri_storage.set_token_uri(token_id, uri);
}
}
}

#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod ERC721ConsecutiveMock {
use openzeppelin_token::erc721::ERC721TokenURIDefaultImpl;
use openzeppelin_token::erc721::extensions::erc721_consecutive::ERC721ConsecutiveComponent::InternalImpl;
use openzeppelin_token::erc721::extensions::erc721_consecutive::{
DefaultConfig, ERC721ConsecutiveComponent,
Expand Down Expand Up @@ -447,6 +525,7 @@ pub mod ERC721ConsecutiveMock {
#[starknet::contract]
#[with_components(ERC721, SRC5)]
pub mod ERC721ConsecutiveMultiBatchMock {
use openzeppelin_token::erc721::ERC721TokenURIDefaultImpl;
use openzeppelin_token::erc721::extensions::erc721_consecutive::ERC721ConsecutiveComponent::InternalImpl;
use openzeppelin_token::erc721::extensions::erc721_consecutive::{
DefaultConfig, ERC721ConsecutiveComponent,
Expand Down
4 changes: 2 additions & 2 deletions packages/test_common/src/mocks/votes.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ pub mod ERC20BlockNumberVotesMock {
#[starknet::contract]
#[with_components(ERC721, Votes, SRC5, Nonces)]
pub mod ERC721TimestampVotesMock {
use openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;
use openzeppelin_token::erc721::{ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl};
use openzeppelin_utils::contract_clock::ERC6372TimestampClock;
use openzeppelin_utils::cryptography::snip12::SNIP12Metadata;
use starknet::ContractAddress;
Expand Down Expand Up @@ -246,7 +246,7 @@ pub mod ERC721TimestampVotesMock {
#[starknet::contract]
#[with_components(ERC721, Votes, SRC5, Nonces)]
pub mod ERC721BlockNumberVotesMock {
use openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;
use openzeppelin_token::erc721::{ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl};
use openzeppelin_utils::contract_clock::ERC6372BlockNumberClock;
use openzeppelin_utils::cryptography::snip12::SNIP12Metadata;
use starknet::ContractAddress;
Expand Down
1 change: 1 addition & 0 deletions packages/token/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ build-external-contracts = [
"openzeppelin_test_common::mocks::erc721::ERC721MintableMock",
"openzeppelin_test_common::mocks::erc721::ERC721ConsecutiveMock",
"openzeppelin_test_common::mocks::erc721::ERC721ConsecutiveMultiBatchMock",
"openzeppelin_test_common::mocks::erc721::ERC721URIStorageMock",
"openzeppelin_test_common::mocks::erc721::ERC721WrapperMock",
"openzeppelin_test_common::mocks::erc1155::DualCaseERC1155ReceiverMock",
"openzeppelin_test_common::mocks::non_implementing::NonImplementingMock",
Expand Down
6 changes: 2 additions & 4 deletions packages/token/src/erc20/extensions/erc4626/erc4626.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -838,10 +838,8 @@ pub mod ERC4626Component {
// Burn shares first
let shares_to_burn = match fee {
Option::None => shares,
Option::Some(fee) => match fee {
Fee::Assets(_) => shares,
Fee::Shares(shares_fee) => shares - shares_fee,
},
Option::Some(Fee::Assets(_)) => shares,
Option::Some(Fee::Shares(shares_fee)) => shares - shares_fee,
};
let mut erc20_component = get_dep_component_mut!(ref self, ERC20);
if caller != owner {
Expand Down
4 changes: 3 additions & 1 deletion packages/token/src/erc721.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ pub mod erc721;
pub mod erc721_receiver;
pub mod extensions;

pub use erc721::{ERC721Component, ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl};
pub use erc721::{
ERC721Component, ERC721HooksEmptyImpl, ERC721OwnerOfDefaultImpl, ERC721TokenURIDefaultImpl,
};
pub use erc721_receiver::ERC721ReceiverComponent;
Loading
Loading