From 74a9b3342513eebaa9238b56aaddebd615abe545 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 7 Aug 2026 14:36:06 +0200 Subject: [PATCH] fix: L-01 --- .../attribute/with_components/diagnostics.rs | 10 +++ .../src/attribute/with_components/parser.rs | 8 +++ ...components__with_erc6909_token_supply.snap | 71 +++++++++++++++++++ ...ith_erc6909_token_supply_no_hook_call.snap | 65 +++++++++++++++++ .../macros/src/tests/test_with_components.rs | 59 +++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply_no_hook_call.snap diff --git a/packages/macros/src/attribute/with_components/diagnostics.rs b/packages/macros/src/attribute/with_components/diagnostics.rs index 5926e60f8..27852dcb4 100644 --- a/packages/macros/src/attribute/with_components/diagnostics.rs +++ b/packages/macros/src/attribute/with_components/diagnostics.rs @@ -136,6 +136,16 @@ pub mod warnings { " }; + /// Warning when the ERC6909TokenSupply hook call is missing. + pub const ERC6909_TOKEN_SUPPLY_HOOKS_MISSING: &str = indoc! { + "The ERC6909TokenSupply component requires calling + `self.erc6909_token_supply.update_token_supply(...)` from an `ERC6909HooksTrait` hook, + and it looks like it is missing. + + This may lead to incorrect total supply tracking. + " + }; + /// Warning when the Upgradeable component is not used. pub const UPGRADEABLE_NOT_USED: &str = indoc! { "It looks like the `self.upgradeable.upgrade(new_class_hash)` function is not used in the contract. If diff --git a/packages/macros/src/attribute/with_components/parser.rs b/packages/macros/src/attribute/with_components/parser.rs index 77bc8be8e..0979d3334 100644 --- a/packages/macros/src/attribute/with_components/parser.rs +++ b/packages/macros/src/attribute/with_components/parser.rs @@ -664,6 +664,14 @@ fn add_per_component_warnings( warnings.push(warning); } } + AllowedComponents::ERC6909TokenSupply => { + let hook_called = facts.has_call(&["erc6909_token_supply", "update_token_supply"]) + || facts.has_call(&["ERC6909TokenSupplyInternalImpl", "update_token_supply"]); + if !hook_called { + let warning = Diagnostic::warn(warnings::ERC6909_TOKEN_SUPPLY_HOOKS_MISSING); + warnings.push(warning); + } + } AllowedComponents::Upgradeable => { // Check that the upgrade function is called let upgrade_function_called = facts.has_call(&["self", "upgradeable", "upgrade"]); diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply.snap new file mode 100644 index 000000000..ecd253457 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply.snap @@ -0,0 +1,71 @@ +--- +source: src/tests/test_with_components.rs +expression: result +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use starknet::ContractAddress; + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc6909: ERC6909Component::Storage, + #[substorage(v0)] + pub erc6909_token_supply: ERC6909TokenSupplyComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + #[constructor] + fn constructor(ref self: ContractState) { + self.erc6909.initializer(); + self.erc6909_token_supply.initializer(); + } + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait { + fn before_update( + ref self: ERC6909Component::ComponentState, + sender: ContractAddress, + receiver: ContractAddress, + id: u256, + amount: u256, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc6909_token_supply.update_token_supply(sender, receiver, id, amount); + } + } + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc6909::ERC6909Component; + use openzeppelin_token::erc6909::extensions::ERC6909TokenSupplyComponent; + + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event); + component!( + path: ERC6909TokenSupplyComponent, + storage: erc6909_token_supply, + event: ERC6909TokenSupplyEvent, + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + impl ERC6909InternalImpl = ERC6909Component::InternalImpl; + impl ERC6909TokenSupplyInternalImpl = ERC6909TokenSupplyComponent::InternalImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC6909Event: ERC6909Component::Event, + #[flat] + ERC6909TokenSupplyEvent: ERC6909TokenSupplyComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } +} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply_no_hook_call.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply_no_hook_call.snap new file mode 100644 index 000000000..a5b8071fc --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc6909_token_supply_no_hook_call.snap @@ -0,0 +1,65 @@ +--- +source: src/tests/test_with_components.rs +expression: result +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl; + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc6909: ERC6909Component::Storage, + #[substorage(v0)] + pub erc6909_token_supply: ERC6909TokenSupplyComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + #[constructor] + fn constructor(ref self: ContractState) { + self.erc6909.initializer(); + self.erc6909_token_supply.initializer(); + } + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc6909::ERC6909Component; + use openzeppelin_token::erc6909::extensions::ERC6909TokenSupplyComponent; + + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event); + component!( + path: ERC6909TokenSupplyComponent, + storage: erc6909_token_supply, + event: ERC6909TokenSupplyEvent, + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + impl ERC6909InternalImpl = ERC6909Component::InternalImpl; + impl ERC6909TokenSupplyInternalImpl = ERC6909TokenSupplyComponent::InternalImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC6909Event: ERC6909Component::Event, + #[flat] + ERC6909TokenSupplyEvent: ERC6909TokenSupplyComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } +} + + +Diagnostics: + +==== +Warning: The ERC6909TokenSupply component requires calling +`self.erc6909_token_supply.update_token_supply(...)` from an `ERC6909HooksTrait` hook, +and it looks like it is missing. + +This may lead to incorrect total supply tracking. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs index fcab9b786..2433f50ee 100644 --- a/packages/macros/src/tests/test_with_components.rs +++ b/packages/macros/src/tests/test_with_components.rs @@ -1503,6 +1503,65 @@ fn test_with_timelock_controller_no_initializer() { assert_snapshot!(result); } +#[test] +fn test_with_erc6909_token_supply() { + let attribute = quote! { (ERC6909, ERC6909TokenSupply, SRC5) }; + let item = quote! { + #[starknet::contract] + pub mod MyToken { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc6909.initializer(); + self.erc6909_token_supply.initializer(); + } + + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait { + fn before_update( + ref self: ERC6909Component::ComponentState, + sender: ContractAddress, + receiver: ContractAddress, + id: u256, + amount: u256, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state + .erc6909_token_supply + .update_token_supply(sender, receiver, id, amount); + } + } + } + }; + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_erc6909_token_supply_no_hook_call() { + let attribute = quote! { (ERC6909, ERC6909TokenSupply, SRC5) }; + let item = quote! { + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc6909.initializer(); + self.erc6909_token_supply.initializer(); + } + } + }; + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + #[test] fn test_with_votes() { let attribute = quote! { (Votes) };