From b777992922a5d57675d3c4080d9ffd5ffb0a16f4 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 26 Jan 2026 12:29:26 +0100 Subject: [PATCH 1/6] feat: add disclaimers --- packages/token/src/erc721/extensions/erc721_consecutive.cairo | 3 +++ .../extensions/erc721_enumerable/erc721_enumerable.cairo | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/token/src/erc721/extensions/erc721_consecutive.cairo b/packages/token/src/erc721/extensions/erc721_consecutive.cairo index ee9aab553..4b5b7ea74 100644 --- a/packages/token/src/erc721/extensions/erc721_consecutive.cairo +++ b/packages/token/src/erc721/extensions/erc721_consecutive.cairo @@ -7,6 +7,9 @@ /// Implementation of the ERC-2309 "Consecutive Transfer Extension". /// This allows batch minting of consecutive token IDs during construction. /// +/// CAUTION: This extension does not call the `ERC721Component::update` function for tokens minted in +/// batch. Any logic added to this function through overrides will not be triggered when tokens are minted in batch. +/// /// IMPORTANT: To properly track sequential burns and enforce consecutive minting rules, this /// extension requires that `ERC721ConsecutiveComponent::before_update` and /// `ERC721ConsecutiveComponent::after_update` are called after every transfer, mint, or burn diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index bd6ad8f27..1296f22d4 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -10,6 +10,9 @@ /// /// NOTE: Implementing ERC721Component is a requirement for this component to be implemented. /// +/// CAUTION: `ERC721` extensions that implement custom `balanceOf` logic, such as `ERC721Consecutive`, +/// interfere with enumerability and should not be used together with `ERC721Enumerable`. +/// /// IMPORTANT: To properly track token ids, this extension requires that /// the ERC721EnumerableComponent::before_update function is called after /// every transfer, mint, or burn operation. From 4b49a6d18d5b066eae7e1fdc9e567b389275ad30 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 26 Jan 2026 12:30:02 +0100 Subject: [PATCH 2/6] feat: format files --- .../token/src/erc721/extensions/erc721_consecutive.cairo | 5 +++-- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/token/src/erc721/extensions/erc721_consecutive.cairo b/packages/token/src/erc721/extensions/erc721_consecutive.cairo index 4b5b7ea74..04326b136 100644 --- a/packages/token/src/erc721/extensions/erc721_consecutive.cairo +++ b/packages/token/src/erc721/extensions/erc721_consecutive.cairo @@ -7,8 +7,9 @@ /// Implementation of the ERC-2309 "Consecutive Transfer Extension". /// This allows batch minting of consecutive token IDs during construction. /// -/// CAUTION: This extension does not call the `ERC721Component::update` function for tokens minted in -/// batch. Any logic added to this function through overrides will not be triggered when tokens are minted in batch. +/// CAUTION: This extension does not call the `ERC721Component::update` function for tokens minted +/// in batch. Any logic added to this function through overrides will not be triggered when tokens +/// are minted in batch. /// /// IMPORTANT: To properly track sequential burns and enforce consecutive minting rules, this /// extension requires that `ERC721ConsecutiveComponent::before_update` and diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 1296f22d4..ba78ae89a 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -10,8 +10,9 @@ /// /// NOTE: Implementing ERC721Component is a requirement for this component to be implemented. /// -/// CAUTION: `ERC721` extensions that implement custom `balanceOf` logic, such as `ERC721Consecutive`, -/// interfere with enumerability and should not be used together with `ERC721Enumerable`. +/// CAUTION: `ERC721` extensions that implement custom `balanceOf` logic, such as +/// `ERC721Consecutive`, interfere with enumerability and should not be used together with +/// `ERC721Enumerable`. /// /// IMPORTANT: To properly track token ids, this extension requires that /// the ERC721EnumerableComponent::before_update function is called after From 9bc01521dc97b0fec13cae1392f2c3e1f13ba0fc Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 26 Jan 2026 12:31:28 +0100 Subject: [PATCH 3/6] fix: wording --- packages/token/src/erc721/extensions/erc721_consecutive.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/token/src/erc721/extensions/erc721_consecutive.cairo b/packages/token/src/erc721/extensions/erc721_consecutive.cairo index 04326b136..8c08b916e 100644 --- a/packages/token/src/erc721/extensions/erc721_consecutive.cairo +++ b/packages/token/src/erc721/extensions/erc721_consecutive.cairo @@ -8,7 +8,7 @@ /// This allows batch minting of consecutive token IDs during construction. /// /// CAUTION: This extension does not call the `ERC721Component::update` function for tokens minted -/// in batch. Any logic added to this function through overrides will not be triggered when tokens +/// in batch. Any logic added to this function through hooks will not be triggered when tokens /// are minted in batch. /// /// IMPORTANT: To properly track sequential burns and enforce consecutive minting rules, this From bbb549f7a75aa9f26110a1c496870b10513b448e Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 26 Jan 2026 13:27:36 +0100 Subject: [PATCH 4/6] fix: fuzz test --- packages/utils/src/tests/test_fuzz_checkpoint.cairo | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/tests/test_fuzz_checkpoint.cairo b/packages/utils/src/tests/test_fuzz_checkpoint.cairo index 4a9a1208d..54c2207cf 100644 --- a/packages/utils/src/tests/test_fuzz_checkpoint.cairo +++ b/packages/utils/src/tests/test_fuzz_checkpoint.cairo @@ -59,9 +59,17 @@ fn test_lower_lookup(checkpoints: Span) { for i in 1..checkpoints.len() { let index: usize = i.try_into().unwrap(); let checkpoint = *checkpoints.at(index); + let prev_checkpoint = *checkpoints.at(index - 1); let search_key = checkpoint.key - 1; let found_value = mock_trace.lower_lookup(search_key); - assert_eq!(found_value, checkpoint.value); + // If search_key equals the previous checkpoint's key, lower_lookup returns that value. + // Otherwise, it returns the current checkpoint's value (first with key >= search_key). + let expected = if search_key == prev_checkpoint.key { + prev_checkpoint.value + } else { + checkpoint.value + }; + assert_eq!(found_value, expected); } } From 915ae5e19a10ac5b5425ed57fe789d6f19ea44fc Mon Sep 17 00:00:00 2001 From: immrsd Date: Fri, 30 Jan 2026 23:53:53 +0300 Subject: [PATCH 5/6] Add error for ERC721Enumerable + ERC721Consecutive --- .../attribute/with_components/diagnostics.rs | 4 ++ .../src/attribute/with_components/parser.rs | 38 ++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/macros/src/attribute/with_components/diagnostics.rs b/packages/macros/src/attribute/with_components/diagnostics.rs index 9f1224dd0..973b80266 100644 --- a/packages/macros/src/attribute/with_components/diagnostics.rs +++ b/packages/macros/src/attribute/with_components/diagnostics.rs @@ -18,6 +18,10 @@ pub mod errors { pub fn MULTIPLE_ACCESS_CONTROL_COMPONENTS(components: &str) -> String { format!("Only one AccessControl component can be used. Found: [{components}].\n") } + + /// Error when `ERC721Enumerable` is used together with `ERC721Consecutive`. + pub const ERC721_BALANCE_OF_INCOPATIBILITY: &str = + "ERC721Enumerable and ERC721Consecutive interfere with each other in token ownership tracking and should not be used together.\n"; } #[allow(non_snake_case)] diff --git a/packages/macros/src/attribute/with_components/parser.rs b/packages/macros/src/attribute/with_components/parser.rs index dc03ebdb0..ea952cac7 100644 --- a/packages/macros/src/attribute/with_components/parser.rs +++ b/packages/macros/src/attribute/with_components/parser.rs @@ -117,6 +117,14 @@ fn validate_contract_module( return (vec![error], vec![]); }; + // Keep a stringified version of the module body around for validations below. + let body_ast = body.as_syntax_node(); + let typed = ast::ModuleBody::from_syntax_node(db, body_ast); + let body_rnode = RewriteNode::from_ast(&typed); + let mut builder = PatchBuilder::new_ex(db, &body_ast); + builder.add_modified(body_rnode); + let (body_code, _) = builder.build(); + // 2. Check that the module has the `#[starknet::contract]` attribute (error) if !item.has_attr(db, CONTRACT_ATTRIBUTE) { let error = Diagnostic::error(errors::NO_CONTRACT_ATTRIBUTE(CONTRACT_ATTRIBUTE)); @@ -141,7 +149,20 @@ fn validate_contract_module( return (vec![error], vec![]); } - // 4. Check that the module has the corresponding initializers (warning) + // 4. Disallow ERC721Enumerable and ERC721Consecutive being used together (error) + let uses_erc721_enumerable = components_info + .iter() + .any(|c| matches!(c.kind(), AllowedComponents::ERC721Enumerable)); + let uses_erc721_consecutive = body_code.contains("ERC721Consecutive"); + if uses_erc721_enumerable && uses_erc721_consecutive + { + let error = Diagnostic::error( + errors::ERC721_BALANCE_OF_INCOPATIBILITY, + ); + return (vec![error], vec![]); + } + + // 5. Check that the module has the corresponding initializers (warning) let components_with_initializer = components_info .iter() .filter(|c| c.has_initializer) @@ -180,17 +201,8 @@ fn validate_contract_module( } } - // 5. Check that the contract has the corresponding immutable configs + // 6. Check that the contract has the corresponding immutable configs for component in components_info.iter().filter(|c| c.has_immutable_config) { - // Get the body code (maybe we can do this without the builder) - let body_ast = body.as_syntax_node(); - let typed = ast::ModuleBody::from_syntax_node(db, body_ast); - let body_rnode = RewriteNode::from_ast(&typed); - - let mut builder = PatchBuilder::new_ex(db, &body_ast); - builder.add_modified(body_rnode); - let (code, _) = builder.build(); - // Check if the DefaultConfig is used let component_parent_path = component .path @@ -201,10 +213,10 @@ fn validate_contract_module( )) .unwrap(); - let default_config_used = re.is_match(&code); + let default_config_used = re.is_match(&body_code); if !default_config_used { let immutable_config_implemented = - code.contains(&format!("of {}::ImmutableConfig", component.name)); + body_code.contains(&format!("of {}::ImmutableConfig", component.name)); if !immutable_config_implemented { let warning = Diagnostic::warn(warnings::IMMUTABLE_CONFIG_MISSING( component.short_name(), From 7406982717783c70bf86b5630d17308ff19aae2b Mon Sep 17 00:00:00 2001 From: immrsd Date: Fri, 30 Jan 2026 23:54:15 +0300 Subject: [PATCH 6/6] Add test case for ERC721Enumerable + ERC721Consecutive --- ...721_enumerable_with_erc721_consecutive.snap | 18 ++++++++++++++++++ .../macros/src/tests/test_with_components.rs | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__cannot_use_erc721_enumerable_with_erc721_consecutive.snap diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__cannot_use_erc721_enumerable_with_erc721_consecutive.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__cannot_use_erc721_enumerable_with_erc721_consecutive.snap new file mode 100644 index 000000000..78e817c74 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__cannot_use_erc721_enumerable_with_erc721_consecutive.snap @@ -0,0 +1,18 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +None + +Diagnostics: + +==== +Error: ERC721Enumerable and ERC721Consecutive interfere with each other in token ownership tracking and should not be used together. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs index c3a53f0b9..f1a2cd4c6 100644 --- a/packages/macros/src/tests/test_with_components.rs +++ b/packages/macros/src/tests/test_with_components.rs @@ -998,6 +998,22 @@ fn test_with_erc721_enumerable_no_initializer() { assert_snapshot!(result); } +#[test] +fn test_cannot_use_erc721_enumerable_with_erc721_consecutive() { + let attribute = quote! { (ERC721Enumerable) }; + let item = quote! { + #[starknet::contract] + pub mod MyContract { + use openzeppelin_token::erc721::extensions::ERC721ConsecutiveComponent; + + #[storage] + pub struct Storage {} + } + }; + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + #[test] fn test_with_erc721_receiver() { let attribute = quote! { (ERC721Receiver) };