diff --git a/Scarb.lock b/Scarb.lock index d5a51d4f0..d750b3223 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -30,6 +30,7 @@ dependencies = [ "openzeppelin_introspection", "openzeppelin_test_common", "openzeppelin_testing", + "openzeppelin_utils", "snforge_std", ] @@ -125,6 +126,7 @@ dependencies = [ "openzeppelin_interfaces", "openzeppelin_test_common", "openzeppelin_testing", + "openzeppelin_utils", "snforge_std", ] diff --git a/packages/access/Scarb.toml b/packages/access/Scarb.toml index ad77c9e25..dae7d4388 100644 --- a/packages/access/Scarb.toml +++ b/packages/access/Scarb.toml @@ -32,6 +32,7 @@ assert_macros.workspace = true snforge_std.workspace = true openzeppelin_testing = { path = "../testing" } openzeppelin_test_common = { path = "../test_common" } +openzeppelin_utils = { path = "../utils" } [lib] diff --git a/packages/access/src/tests/test_accesscontrol.cairo b/packages/access/src/tests/test_accesscontrol.cairo index b9906721d..237e97480 100644 --- a/packages/access/src/tests/test_accesscontrol.cairo +++ b/packages/access/src/tests/test_accesscontrol.cairo @@ -6,12 +6,10 @@ use openzeppelin_test_common::mocks::access::DualCaseAccessControlMock; use openzeppelin_testing::constants::{ ADMIN, AUTHORIZED, OTHER, OTHER_ADMIN, OTHER_ROLE, ROLE, TIMESTAMP, ZERO, }; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_block_timestamp_global, start_cheat_caller_address, test_address}; use starknet::ContractAddress; -use crate::accesscontrol::AccessControlComponent::{ - InternalImpl, RoleAdminChanged, RoleGranted, RoleGrantedWithDelay, RoleRevoked, -}; +use crate::accesscontrol::AccessControlComponent::InternalImpl; use crate::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE}; // @@ -700,9 +698,12 @@ pub impl AccessControlSpyHelpersImpl of AccessControlSpyHelpers { account: ContractAddress, sender: ContractAddress, ) { - let expected = AccessControlComponent::Event::RoleRevoked( - RoleRevoked { role, account, sender }, - ); + let expected = ExpectedEvent::new() + .key(selector!("RoleRevoked")) + .data(role) + .data(account) + .data(sender); + self.assert_emitted_single(contract, expected); } @@ -713,9 +714,12 @@ pub impl AccessControlSpyHelpersImpl of AccessControlSpyHelpers { account: ContractAddress, sender: ContractAddress, ) { - let expected = AccessControlComponent::Event::RoleRevoked( - RoleRevoked { role, account, sender }, - ); + let expected = ExpectedEvent::new() + .key(selector!("RoleRevoked")) + .data(role) + .data(account) + .data(sender); + self.assert_only_event(contract, expected); } @@ -726,9 +730,12 @@ pub impl AccessControlSpyHelpersImpl of AccessControlSpyHelpers { account: ContractAddress, sender: ContractAddress, ) { - let expected = AccessControlComponent::Event::RoleGranted( - RoleGranted { role, account, sender }, - ); + let expected = ExpectedEvent::new() + .key(selector!("RoleGranted")) + .data(role) + .data(account) + .data(sender); + self.assert_only_event(contract, expected); } @@ -740,9 +747,13 @@ pub impl AccessControlSpyHelpersImpl of AccessControlSpyHelpers { sender: ContractAddress, delay: u64, ) { - let expected = AccessControlComponent::Event::RoleGrantedWithDelay( - RoleGrantedWithDelay { role, account, sender, delay }, - ); + let expected = ExpectedEvent::new() + .key(selector!("RoleGrantedWithDelay")) + .data(role) + .data(account) + .data(sender) + .data(delay); + self.assert_only_event(contract, expected); } @@ -753,9 +764,12 @@ pub impl AccessControlSpyHelpersImpl of AccessControlSpyHelpers { previous_admin_role: felt252, new_admin_role: felt252, ) { - let expected = AccessControlComponent::Event::RoleAdminChanged( - RoleAdminChanged { role, previous_admin_role, new_admin_role }, - ); + let expected = ExpectedEvent::new() + .key(selector!("RoleAdminChanged")) + .data(role) + .data(previous_admin_role) + .data(new_admin_role); + self.assert_only_event(from_address, expected); } } diff --git a/packages/access/src/tests/test_accesscontrol_default_admin_rules.cairo b/packages/access/src/tests/test_accesscontrol_default_admin_rules.cairo index 092e1e6fa..0525921be 100644 --- a/packages/access/src/tests/test_accesscontrol_default_admin_rules.cairo +++ b/packages/access/src/tests/test_accesscontrol_default_admin_rules.cairo @@ -10,13 +10,10 @@ use openzeppelin_test_common::mocks::access::DualCaseAccessControlDefaultAdminRu use openzeppelin_testing::constants::{ ADMIN, AUTHORIZED, OTHER, OTHER_ADMIN, OTHER_ROLE, ROLE, TIMESTAMP, ZERO, }; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_block_timestamp_global, start_cheat_caller_address, test_address}; use starknet::ContractAddress; -use crate::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent::{ - DefaultAdminDelayChangeCanceled, DefaultAdminDelayChangeScheduled, DefaultAdminTransferCanceled, - DefaultAdminTransferScheduled, InternalTrait, -}; +use crate::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent::InternalTrait; use crate::accesscontrol::extensions::{ AccessControlDefaultAdminRulesComponent, DEFAULT_ADMIN_ROLE, DefaultConfig, }; @@ -1169,39 +1166,38 @@ impl AccessControlDefaultAdminRulesSpyHelpersImpl of AccessControlDefaultAdminRu new_admin: ContractAddress, accept_schedule: u64, ) { - let expected = - AccessControlDefaultAdminRulesComponent::Event::DefaultAdminTransferScheduled( - DefaultAdminTransferScheduled { new_admin, accept_schedule }, - ); + let expected = ExpectedEvent::new() + .key(selector!("DefaultAdminTransferScheduled")) + .key(new_admin) + .data(accept_schedule); + self.assert_only_event(contract, expected); } fn assert_only_event_default_admin_transfer_canceled( ref self: EventSpy, contract: ContractAddress, ) { - let expected = AccessControlDefaultAdminRulesComponent::Event::DefaultAdminTransferCanceled( - DefaultAdminTransferCanceled {}, - ); + let expected = ExpectedEvent::new().key(selector!("DefaultAdminTransferCanceled")); + self.assert_only_event(contract, expected); } fn assert_only_event_default_admin_delay_change_scheduled( ref self: EventSpy, contract: ContractAddress, new_delay: u64, effect_schedule: u64, ) { - let expected = - AccessControlDefaultAdminRulesComponent::Event::DefaultAdminDelayChangeScheduled( - DefaultAdminDelayChangeScheduled { new_delay, effect_schedule }, - ); + let expected = ExpectedEvent::new() + .key(selector!("DefaultAdminDelayChangeScheduled")) + .data(new_delay) + .data(effect_schedule); + self.assert_only_event(contract, expected); } fn assert_only_event_default_admin_delay_change_canceled( ref self: EventSpy, contract: ContractAddress, ) { - let expected = - AccessControlDefaultAdminRulesComponent::Event::DefaultAdminDelayChangeCanceled( - DefaultAdminDelayChangeCanceled {}, - ); + let expected = ExpectedEvent::new().key(selector!("DefaultAdminDelayChangeCanceled")); + self.assert_only_event(contract, expected); } } diff --git a/packages/access/src/tests/test_ownable_twostep.cairo b/packages/access/src/tests/test_ownable_twostep.cairo index 69afa2edd..73872f098 100644 --- a/packages/access/src/tests/test_ownable_twostep.cairo +++ b/packages/access/src/tests/test_ownable_twostep.cairo @@ -3,12 +3,12 @@ use openzeppelin_interfaces::ownable::{IOwnableTwoStep, IOwnableTwoStepCamelOnly use openzeppelin_test_common::mocks::access::DualCaseTwoStepOwnableMock; use openzeppelin_test_common::ownable::OwnableSpyHelpers; use openzeppelin_testing::constants::{NEW_OWNER, OTHER, OWNER, ZERO}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use crate::ownable::OwnableComponent; -use crate::ownable::OwnableComponent::{InternalTrait, OwnershipTransferStarted}; +use crate::ownable::OwnableComponent::InternalTrait; // // Setup @@ -291,9 +291,10 @@ impl TwoStepSpyHelpersImpl of TwoStepSpyHelpers { previous_owner: ContractAddress, new_owner: ContractAddress, ) { - let expected = OwnableComponent::Event::OwnershipTransferStarted( - OwnershipTransferStarted { previous_owner, new_owner }, - ); + let expected = ExpectedEvent::new() + .key(selector!("OwnershipTransferStarted")) + .key(previous_owner) + .key(new_owner); self.assert_emitted_single(from_address, expected); } } diff --git a/packages/governance/src/governor/proposal_core.cairo b/packages/governance/src/governor/proposal_core.cairo index ee95a6195..484b02a07 100644 --- a/packages/governance/src/governor/proposal_core.cairo +++ b/packages/governance/src/governor/proposal_core.cairo @@ -84,8 +84,7 @@ mod tests { use core::num::traits::Bounded; use openzeppelin_testing::constants::ALICE; use openzeppelin_testing::{FuzzableBool, FuzzableContractAddress}; - use starknet::ContractAddress; - use super::{ProposalCore, ProposalCoreStorePacking as StorePacking}; + use super::{ContractAddress, ProposalCore, ProposalCoreStorePacking as StorePacking}; #[test] fn test_pack_and_unpack() { diff --git a/packages/governance/src/tests/governor/block_number/test_governor_settings.cairo b/packages/governance/src/tests/governor/block_number/test_governor_settings.cairo index 31da16fb6..657b55e8b 100644 --- a/packages/governance/src/tests/governor/block_number/test_governor_settings.cairo +++ b/packages/governance/src/tests/governor/block_number/test_governor_settings.cairo @@ -1,12 +1,11 @@ use openzeppelin_test_common::mocks::governor::GovernorTimelockedMock::SNIP12MetadataImpl; use openzeppelin_testing::constants::OTHER; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::storage::StoragePointerWriteAccess; use crate::governor::DefaultConfig; use crate::governor::GovernorComponent::InternalImpl; -use crate::governor::extensions::GovernorSettingsComponent; use crate::governor::extensions::GovernorSettingsComponent::{ GovernorSettings, GovernorSettingsAdminImpl, InternalImpl as GovernorSettingsInternalImpl, }; @@ -15,6 +14,7 @@ use crate::tests::governor::block_number::common::{ set_executor, }; + // // Extensions // @@ -395,9 +395,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { fn assert_event_voting_delay_updated( ref self: EventSpy, contract: ContractAddress, old_voting_delay: u64, new_voting_delay: u64, ) { - let expected = GovernorSettingsComponent::Event::VotingDelayUpdated( - GovernorSettingsComponent::VotingDelayUpdated { old_voting_delay, new_voting_delay }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VotingDelayUpdated")) + .data(old_voting_delay) + .data(new_voting_delay); self.assert_emitted_single(contract, expected); } @@ -414,9 +415,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_voting_period: u64, new_voting_period: u64, ) { - let expected = GovernorSettingsComponent::Event::VotingPeriodUpdated( - GovernorSettingsComponent::VotingPeriodUpdated { old_voting_period, new_voting_period }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VotingPeriodUpdated")) + .data(old_voting_period) + .data(new_voting_period); self.assert_emitted_single(contract, expected); } @@ -436,11 +438,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_proposal_threshold: u256, new_proposal_threshold: u256, ) { - let expected = GovernorSettingsComponent::Event::ProposalThresholdUpdated( - GovernorSettingsComponent::ProposalThresholdUpdated { - old_proposal_threshold, new_proposal_threshold, - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ProposalThresholdUpdated")) + .data(old_proposal_threshold) + .data(new_proposal_threshold); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/governor/block_number/test_governor_votes_quorum_fraction.cairo b/packages/governance/src/tests/governor/block_number/test_governor_votes_quorum_fraction.cairo index 23c4f5a37..b972de85b 100644 --- a/packages/governance/src/tests/governor/block_number/test_governor_votes_quorum_fraction.cairo +++ b/packages/governance/src/tests/governor/block_number/test_governor_votes_quorum_fraction.cairo @@ -1,17 +1,17 @@ use openzeppelin_test_common::mocks::governor::GovernorQuorumFractionMock; use openzeppelin_test_common::mocks::governor::GovernorQuorumFractionMock::SNIP12MetadataImpl; use openzeppelin_testing::constants::{OTHER, VOTES_TOKEN, ZERO}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_block_number_global, start_mock_call, test_address}; use starknet::ContractAddress; use crate::governor::GovernorComponent::InternalImpl; -use crate::governor::extensions::GovernorVotesQuorumFractionComponent; use crate::governor::extensions::GovernorVotesQuorumFractionComponent::{ GovernorQuorum, GovernorVotes, InternalTrait, QuorumFractionImpl, }; use crate::governor::{DefaultConfig, GovernorComponent}; use crate::tests::governor::block_number::common::deploy_votes_token; + pub type ComponentState = GovernorComponent::ComponentState; @@ -240,11 +240,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_quorum_numerator: u256, new_quorum_numerator: u256, ) { - let expected = GovernorVotesQuorumFractionComponent::Event::QuorumNumeratorUpdated( - GovernorVotesQuorumFractionComponent::QuorumNumeratorUpdated { - old_quorum_numerator, new_quorum_numerator, - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("QuorumNumeratorUpdated")) + .data(old_quorum_numerator) + .data(new_quorum_numerator); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/governor/common.cairo b/packages/governance/src/tests/governor/common.cairo index 8c3539c03..487ae4822 100644 --- a/packages/governance/src/tests/governor/common.cairo +++ b/packages/governance/src/tests/governor/common.cairo @@ -1,7 +1,6 @@ -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; use starknet::account::Call; -use crate::governor::GovernorComponent; // // Event helpers @@ -20,17 +19,15 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { vote_end: u64, description: @ByteArray, ) { - let expected = GovernorComponent::Event::ProposalCreated( - GovernorComponent::ProposalCreated { - proposal_id, - proposer, - calls, - signatures, - vote_start, - vote_end, - description: description.clone(), - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ProposalCreated")) + .key(proposal_id) + .key(proposer) + .data(calls) + .data(signatures) + .data(vote_start) + .data(vote_end) + .data(description.clone()); self.assert_emitted_single(contract, expected); } @@ -68,11 +65,13 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { weight: u256, reason: @ByteArray, ) { - let expected = GovernorComponent::Event::VoteCast( - GovernorComponent::VoteCast { - voter, proposal_id, support, weight, reason: reason.clone(), - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VoteCast")) + .key(voter) + .data(proposal_id) + .data(support) + .data(weight) + .data(reason.clone()); self.assert_emitted_single(contract, expected); } @@ -99,11 +98,14 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { reason: @ByteArray, params: Span, ) { - let expected = GovernorComponent::Event::VoteCastWithParams( - GovernorComponent::VoteCastWithParams { - voter, proposal_id, support, weight, reason: reason.clone(), params, - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VoteCastWithParams")) + .key(voter) + .data(proposal_id) + .data(support) + .data(weight) + .data(reason.clone()) + .data(params); self.assert_emitted_single(contract, expected); } @@ -127,9 +129,10 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { fn assert_event_proposal_queued( ref self: EventSpy, contract: ContractAddress, proposal_id: felt252, eta_seconds: u64, ) { - let expected = GovernorComponent::Event::ProposalQueued( - GovernorComponent::ProposalQueued { proposal_id, eta_seconds }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ProposalQueued")) + .key(proposal_id) + .data(eta_seconds); self.assert_emitted_single(contract, expected); } @@ -143,9 +146,7 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { fn assert_event_proposal_executed( ref self: EventSpy, contract: ContractAddress, proposal_id: felt252, ) { - let expected = GovernorComponent::Event::ProposalExecuted( - GovernorComponent::ProposalExecuted { proposal_id }, - ); + let expected = ExpectedEvent::new().key(selector!("ProposalExecuted")).key(proposal_id); self.assert_emitted_single(contract, expected); } @@ -159,9 +160,7 @@ pub(crate) impl GovernorSpyHelpersImpl of GovernorSpyHelpers { fn assert_event_proposal_canceled( ref self: EventSpy, contract: ContractAddress, proposal_id: felt252, ) { - let expected = GovernorComponent::Event::ProposalCanceled( - GovernorComponent::ProposalCanceled { proposal_id }, - ); + let expected = ExpectedEvent::new().key(selector!("ProposalCanceled")).key(proposal_id); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/governor/timestamp/test_governor_settings.cairo b/packages/governance/src/tests/governor/timestamp/test_governor_settings.cairo index fcbb3228d..871dfa0f7 100644 --- a/packages/governance/src/tests/governor/timestamp/test_governor_settings.cairo +++ b/packages/governance/src/tests/governor/timestamp/test_governor_settings.cairo @@ -1,12 +1,11 @@ use openzeppelin_test_common::mocks::governor::GovernorTimelockedMock::SNIP12MetadataImpl; use openzeppelin_testing::constants::OTHER; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::storage::StoragePointerWriteAccess; use crate::governor::DefaultConfig; use crate::governor::GovernorComponent::InternalImpl; -use crate::governor::extensions::GovernorSettingsComponent; use crate::governor::extensions::GovernorSettingsComponent::{ GovernorSettings, GovernorSettingsAdminImpl, InternalImpl as GovernorSettingsInternalImpl, }; @@ -395,9 +394,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { fn assert_event_voting_delay_updated( ref self: EventSpy, contract: ContractAddress, old_voting_delay: u64, new_voting_delay: u64, ) { - let expected = GovernorSettingsComponent::Event::VotingDelayUpdated( - GovernorSettingsComponent::VotingDelayUpdated { old_voting_delay, new_voting_delay }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VotingDelayUpdated")) + .data(old_voting_delay) + .data(new_voting_delay); self.assert_emitted_single(contract, expected); } @@ -414,9 +414,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_voting_period: u64, new_voting_period: u64, ) { - let expected = GovernorSettingsComponent::Event::VotingPeriodUpdated( - GovernorSettingsComponent::VotingPeriodUpdated { old_voting_period, new_voting_period }, - ); + let expected = ExpectedEvent::new() + .key(selector!("VotingPeriodUpdated")) + .data(old_voting_period) + .data(new_voting_period); self.assert_emitted_single(contract, expected); } @@ -436,11 +437,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_proposal_threshold: u256, new_proposal_threshold: u256, ) { - let expected = GovernorSettingsComponent::Event::ProposalThresholdUpdated( - GovernorSettingsComponent::ProposalThresholdUpdated { - old_proposal_threshold, new_proposal_threshold, - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ProposalThresholdUpdated")) + .data(old_proposal_threshold) + .data(new_proposal_threshold); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/governor/timestamp/test_governor_timelock_execution.cairo b/packages/governance/src/tests/governor/timestamp/test_governor_timelock_execution.cairo index 465c381b1..2da5cb596 100644 --- a/packages/governance/src/tests/governor/timestamp/test_governor_timelock_execution.cairo +++ b/packages/governance/src/tests/governor/timestamp/test_governor_timelock_execution.cairo @@ -15,7 +15,7 @@ use openzeppelin_test_common::mocks::timelock::{ }; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{OTHER, TIMELOCK, VOTES_TOKEN}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use openzeppelin_utils::bytearray::ByteArrayExtTrait; use openzeppelin_utils::serde::SerializedAppend; use snforge_std::{ @@ -26,7 +26,6 @@ use starknet::account::Call; use starknet::storage::{StorageMapWriteAccess, StoragePathEntry, StoragePointerWriteAccess}; use crate::governor::DefaultConfig; use crate::governor::GovernorComponent::{InternalExtendedImpl, InternalImpl}; -use crate::governor::extensions::GovernorTimelockExecutionComponent; use crate::governor::extensions::GovernorTimelockExecutionComponent::GovernorExecution; use crate::governor::extensions::GovernorVotesComponent::InternalTrait; use crate::tests::governor::common::GovernorSpyHelpersImpl; @@ -833,9 +832,10 @@ pub(crate) impl GovernorTimelockExecutionSpyHelpersImpl of GovernorTimelockExecu old_timelock: ContractAddress, new_timelock: ContractAddress, ) { - let expected = GovernorTimelockExecutionComponent::Event::TimelockUpdated( - GovernorTimelockExecutionComponent::TimelockUpdated { old_timelock, new_timelock }, - ); + let expected = ExpectedEvent::new() + .key(selector!("TimelockUpdated")) + .data(old_timelock) + .data(new_timelock); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/governor/timestamp/test_governor_votes_quorum_fraction.cairo b/packages/governance/src/tests/governor/timestamp/test_governor_votes_quorum_fraction.cairo index edac75311..215b09db1 100644 --- a/packages/governance/src/tests/governor/timestamp/test_governor_votes_quorum_fraction.cairo +++ b/packages/governance/src/tests/governor/timestamp/test_governor_votes_quorum_fraction.cairo @@ -1,11 +1,10 @@ use openzeppelin_test_common::mocks::governor::GovernorQuorumFractionMock; use openzeppelin_test_common::mocks::governor::GovernorQuorumFractionMock::SNIP12MetadataImpl; use openzeppelin_testing::constants::{OTHER, VOTES_TOKEN, ZERO}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_block_timestamp_global, start_mock_call, test_address}; use starknet::ContractAddress; use crate::governor::GovernorComponent::InternalImpl; -use crate::governor::extensions::GovernorVotesQuorumFractionComponent; use crate::governor::extensions::GovernorVotesQuorumFractionComponent::{ GovernorQuorum, GovernorVotes, InternalTrait, QuorumFractionImpl, }; @@ -262,11 +261,10 @@ pub(crate) impl GovernorSettingsSpyHelpersImpl of GovernorSettingsSpyHelpers { old_quorum_numerator: u256, new_quorum_numerator: u256, ) { - let expected = GovernorVotesQuorumFractionComponent::Event::QuorumNumeratorUpdated( - GovernorVotesQuorumFractionComponent::QuorumNumeratorUpdated { - old_quorum_numerator, new_quorum_numerator, - }, - ); + let expected = ExpectedEvent::new() + .key(selector!("QuorumNumeratorUpdated")) + .data(old_quorum_numerator) + .data(new_quorum_numerator); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/test_multisig.cairo b/packages/governance/src/tests/test_multisig.cairo index f390c2fd0..a06879bbb 100644 --- a/packages/governance/src/tests/test_multisig.cairo +++ b/packages/governance/src/tests/test_multisig.cairo @@ -6,16 +6,13 @@ use openzeppelin_test_common::mocks::multisig::{ }; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{ALICE, BLOCK_NUMBER, BOB, CHARLIE, OTHER, SALT, ZERO}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_block_number_global, start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::account::Call; use starknet::storage_access::StorePacking; use crate::multisig::MultisigComponent; -use crate::multisig::MultisigComponent::{ - CallSalt, ConfirmationRevoked, Event, InternalImpl, MultisigImpl, QuorumUpdated, SignerAdded, - SignerRemoved, TransactionConfirmed, TransactionExecuted, TransactionSubmitted, -}; +use crate::multisig::MultisigComponent::{InternalImpl, MultisigImpl}; use crate::multisig::storage_utils::{SignersInfo, SignersInfoStorePackingV2}; // @@ -1554,7 +1551,7 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_signer_added( ref self: EventSpy, contract: ContractAddress, signer: ContractAddress, ) { - let expected = Event::SignerAdded(SignerAdded { signer }); + let expected = ExpectedEvent::new().key(selector!("SignerAdded")).key(signer); self.assert_emitted_single(contract, expected); } @@ -1572,7 +1569,7 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_signer_removed( ref self: EventSpy, contract: ContractAddress, signer: ContractAddress, ) { - let expected = Event::SignerRemoved(SignerRemoved { signer }); + let expected = ExpectedEvent::new().key(selector!("SignerRemoved")).key(signer); self.assert_emitted_single(contract, expected); } @@ -1590,7 +1587,10 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_quorum_updated( ref self: EventSpy, contract: ContractAddress, old_quorum: u32, new_quorum: u32, ) { - let expected = Event::QuorumUpdated(QuorumUpdated { old_quorum, new_quorum }); + let expected = ExpectedEvent::new() + .key(selector!("QuorumUpdated")) + .data(old_quorum) + .data(new_quorum); self.assert_emitted_single(contract, expected); } @@ -1608,7 +1608,10 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_tx_submitted( ref self: EventSpy, contract: ContractAddress, id: TransactionID, signer: ContractAddress, ) { - let expected = Event::TransactionSubmitted(TransactionSubmitted { id, signer }); + let expected = ExpectedEvent::new() + .key(selector!("TransactionSubmitted")) + .key(id) + .key(signer); self.assert_emitted_single(contract, expected); } @@ -1626,7 +1629,10 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_tx_confirmed( ref self: EventSpy, contract: ContractAddress, id: TransactionID, signer: ContractAddress, ) { - let expected = Event::TransactionConfirmed(TransactionConfirmed { id, signer }); + let expected = ExpectedEvent::new() + .key(selector!("TransactionConfirmed")) + .key(id) + .key(signer); self.assert_emitted_single(contract, expected); } @@ -1644,7 +1650,10 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_confirmation_revoked( ref self: EventSpy, contract: ContractAddress, id: TransactionID, signer: ContractAddress, ) { - let expected = Event::ConfirmationRevoked(ConfirmationRevoked { id, signer }); + let expected = ExpectedEvent::new() + .key(selector!("ConfirmationRevoked")) + .key(id) + .key(signer); self.assert_emitted_single(contract, expected); } @@ -1660,7 +1669,7 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { // fn assert_event_tx_executed(ref self: EventSpy, contract: ContractAddress, id: TransactionID) { - let expected = Event::TransactionExecuted(TransactionExecuted { id }); + let expected = ExpectedEvent::new().key(selector!("TransactionExecuted")).key(id); self.assert_emitted_single(contract, expected); } @@ -1678,7 +1687,7 @@ impl MultisigSpyHelpersImpl of MultisigSpyHelpers { fn assert_event_call_salt( ref self: EventSpy, contract: ContractAddress, id: TransactionID, salt: felt252, ) { - let expected = Event::CallSalt(CallSalt { id, salt }); + let expected = ExpectedEvent::new().key(selector!("CallSalt")).key(id).data(salt); self.assert_emitted_single(contract, expected); } } diff --git a/packages/governance/src/tests/test_timelock.cairo b/packages/governance/src/tests/test_timelock.cairo index 70b0a926c..3ce18cd4d 100644 --- a/packages/governance/src/tests/test_timelock.cairo +++ b/packages/governance/src/tests/test_timelock.cairo @@ -16,7 +16,9 @@ use openzeppelin_test_common::mocks::timelock::{ }; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{ADMIN, FELT_VALUE as VALUE, OTHER, SALT, ZERO}; -use openzeppelin_testing::{AsAddressTrait, EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{ + AsAddressTrait, EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events, +}; use openzeppelin_utils::contract_clock::ERC6372TimestampClock; use openzeppelin_utils::serde::SerializedAppend; use snforge_std::{ @@ -27,8 +29,7 @@ use starknet::ContractAddress; use starknet::account::Call; use starknet::storage::{StorageMapReadAccess, StorageMapWriteAccess, StoragePointerWriteAccess}; use crate::timelock::TimelockControllerComponent::{ - CallCancelled, CallExecuted, CallSalt, CallScheduled, InternalImpl as TimelockInternalImpl, - MinDelayChanged, TimelockImpl, + InternalImpl as TimelockInternalImpl, TimelockImpl, }; use crate::timelock::{CANCELLER_ROLE, EXECUTOR_ROLE, PROPOSER_ROLE, TimelockControllerComponent}; @@ -1654,9 +1655,13 @@ pub(crate) impl TimelockSpyHelpersImpl of TimelockSpyHelpers { predecessor: felt252, delay: u64, ) { - let expected = TimelockControllerComponent::Event::CallScheduled( - CallScheduled { id, index, call, predecessor, delay }, - ); + let expected = ExpectedEvent::new() + .key(selector!("CallScheduled")) + .key(id) + .key(index) + .data(call) + .data(predecessor) + .data(delay); self.assert_emitted_single(contract, expected); } @@ -1710,7 +1715,7 @@ pub(crate) impl TimelockSpyHelpersImpl of TimelockSpyHelpers { fn assert_event_call_salt( ref self: EventSpy, contract: ContractAddress, id: felt252, salt: felt252, ) { - let expected = TimelockControllerComponent::Event::CallSalt(CallSalt { id, salt }); + let expected = ExpectedEvent::new().key(selector!("CallSalt")).key(id).data(salt); self.assert_emitted_single(contract, expected); } @@ -1726,7 +1731,7 @@ pub(crate) impl TimelockSpyHelpersImpl of TimelockSpyHelpers { // fn assert_event_call_cancelled(ref self: EventSpy, contract: ContractAddress, id: felt252) { - let expected = TimelockControllerComponent::Event::CallCancelled(CallCancelled { id }); + let expected = ExpectedEvent::new().key(selector!("CallCancelled")).key(id); self.assert_emitted_single(contract, expected); } @@ -1744,9 +1749,11 @@ pub(crate) impl TimelockSpyHelpersImpl of TimelockSpyHelpers { fn assert_event_call_executed( ref self: EventSpy, contract: ContractAddress, id: felt252, index: felt252, call: Call, ) { - let expected = TimelockControllerComponent::Event::CallExecuted( - CallExecuted { id, index, call }, - ); + let expected = ExpectedEvent::new() + .key(selector!("CallExecuted")) + .key(id) + .key(index) + .data(call); self.assert_emitted_single(contract, expected); } @@ -1781,9 +1788,10 @@ pub(crate) impl TimelockSpyHelpersImpl of TimelockSpyHelpers { fn assert_event_delay_changed( ref self: EventSpy, contract: ContractAddress, old_duration: u64, new_duration: u64, ) { - let expected = TimelockControllerComponent::Event::MinDelayChanged( - MinDelayChanged { old_duration, new_duration }, - ); + let expected = ExpectedEvent::new() + .key(selector!("MinDelayChanged")) + .data(old_duration) + .data(new_duration); self.assert_emitted_single(contract, expected); } diff --git a/packages/governance/src/tests/votes/common.cairo b/packages/governance/src/tests/votes/common.cairo index b7193ad88..8493a90f4 100644 --- a/packages/governance/src/tests/votes/common.cairo +++ b/packages/governance/src/tests/votes/common.cairo @@ -1,7 +1,5 @@ -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; -use crate::votes::VotesComponent; -use crate::votes::VotesComponent::{DelegateChanged, DelegateVotesChanged}; #[generate_trait] pub(crate) impl VotesSpyHelpersImpl of VotesSpyHelpers { @@ -12,9 +10,11 @@ pub(crate) impl VotesSpyHelpersImpl of VotesSpyHelpers { from_delegate: ContractAddress, to_delegate: ContractAddress, ) { - let expected = VotesComponent::Event::DelegateChanged( - DelegateChanged { delegator, from_delegate, to_delegate }, - ); + let expected = ExpectedEvent::new() + .key(selector!("DelegateChanged")) + .key(delegator) + .key(from_delegate) + .key(to_delegate); self.assert_emitted_single(contract, expected); } @@ -25,9 +25,11 @@ pub(crate) impl VotesSpyHelpersImpl of VotesSpyHelpers { previous_votes: u256, new_votes: u256, ) { - let expected = VotesComponent::Event::DelegateVotesChanged( - DelegateVotesChanged { delegate, previous_votes, new_votes }, - ); + let expected = ExpectedEvent::new() + .key(selector!("DelegateVotesChanged")) + .key(delegate) + .data(previous_votes) + .data(new_votes); self.assert_emitted_single(contract, expected); } diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index dfa3c60a9..f8efcda01 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -50,6 +50,26 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -92,6 +112,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "cfg_aliases", +] + [[package]] name = "bstr" version = "1.11.3" @@ -110,9 +139,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "cairo-lang-casm" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f666086bfda27b0b1c708d4c9aca097508870381ddfcc1c97f442b2430d079" +checksum = "b4d4c4d35619430a42278b858364cd655e2544cf11086d8d93c9be2457cc814a" dependencies = [ "cairo-lang-utils", "indoc", @@ -124,19 +153,20 @@ dependencies = [ [[package]] name = "cairo-lang-debug" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e438aea97ef0d360b0e1eff98c9bd7077407993fae735ac450031faf0a7cb1" +checksum = "bf9da3e821fd0aa724963aa9e9034d60e38c8e18aef8a1f56d093d8e33a427bb" dependencies = [ "cairo-lang-utils", ] [[package]] name = "cairo-lang-defs" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9c075e393b2e120ab32e981a2af9c36e9ec78405e86d6c8a2c4c11a3df9257" +checksum = "85bd972126413ee8516db68300cb5355b12b45e1861b9529f9ef989d4f847018" dependencies = [ + "bincode", "cairo-lang-debug", "cairo-lang-diagnostics", "cairo-lang-filesystem", @@ -145,14 +175,17 @@ dependencies = [ "cairo-lang-utils", "itertools", "rust-analyzer-salsa", + "serde", "smol_str", + "typetag", + "xxhash-rust", ] [[package]] name = "cairo-lang-diagnostics" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0366f2ba4f0d523e1da8bae918566bb67a0030b15958b7185a889df58475dc7" +checksum = "12ec9787c46ceda3676f8ba2b8f1e253d242c68d2b0c3cde70573cebfe40cad4" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -162,9 +195,9 @@ dependencies = [ [[package]] name = "cairo-lang-eq-solver" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b540174610928874781672632c2a84a7352201a6a12f635362468b98e71050" +checksum = "4159473d4716ca8c80c2a9cd3c6c1536b62e5d96690500f8e7717fb960634454" dependencies = [ "cairo-lang-utils", "good_lp", @@ -172,9 +205,9 @@ dependencies = [ [[package]] name = "cairo-lang-filesystem" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de819fdd43ea61c051c7a876d0ea44626c7ee7447a72ef63f48672c7745c86b5" +checksum = "891c976745b04b9c37fdb1c0c8599b544c141ae4fb6fd83d6e004d079e281cdc" dependencies = [ "cairo-lang-debug", "cairo-lang-utils", @@ -188,9 +221,9 @@ dependencies = [ [[package]] name = "cairo-lang-formatter" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f5327e5cc48880249d8bc9edc8f5ab534a18e002b5069f1a67f4a8f3f2003a" +checksum = "1aae69538736123b030aa6621769f4aa42e9e9c1a1246418b49299dfa86b5d12" dependencies = [ "anyhow", "cairo-lang-diagnostics", @@ -201,7 +234,6 @@ dependencies = [ "diffy", "ignore", "itertools", - "rust-analyzer-salsa", "serde", "thiserror 2.0.12", ] @@ -225,7 +257,7 @@ checksum = "e32e958decd95ae122ee64daa26721da2f76e83231047f947fd9cdc5d3c90cc6" dependencies = [ "quote", "scarb-stable-hash", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -236,9 +268,9 @@ checksum = "c49906d6b1c215e5814be7c5c65ecf2328898b335bee8c2409ec07cfb5530daf" [[package]] name = "cairo-lang-parser" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0154b6c5bf6e75cc672a5a47775829f4248946c96855a2483e0b41f7e43c0248" +checksum = "10a250db6b5a8af39c67b26d62546c1dc3955a5078ca2f8d72151b0096d83e61" dependencies = [ "cairo-lang-diagnostics", "cairo-lang-filesystem", @@ -257,9 +289,9 @@ dependencies = [ [[package]] name = "cairo-lang-plugins" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed019a1665d85930366bd2cc76a9c5bedb6052dbc82c55a141c3066077c00cb" +checksum = "488c227d3997552ff0a29053b0a370c596d420b4d21917e42525ab1646a2c752" dependencies = [ "cairo-lang-defs", "cairo-lang-diagnostics", @@ -282,14 +314,14 @@ checksum = "123ac0ecadf31bacae77436d72b88fa9caef2b8e92c89ce63a125ae911a12fae" [[package]] name = "cairo-lang-sierra" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461500d262ac78744c7b39087b033c36a448e047390c71f5c92ac27ef6a6edda" +checksum = "4e8eb3e014faa26a2b1b8f69dd2c735254d85ba96b5baff3ed79467331dfa98d" dependencies = [ "anyhow", "cairo-lang-utils", "const-fnv1a-hash", - "convert_case 0.7.1", + "convert_case", "derivative", "itertools", "lalrpop", @@ -309,9 +341,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-ap-change" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7153bdd6d3f46c3045c0b3e23c9f5e8458fe4da673864f95ddfc5ff9f4ef981c" +checksum = "6e18b0197cf7cea960e3d71f97fe7f76f427d5272121df315e0132a66f34b8d1" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -325,9 +357,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-gas" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0330f6846da0d59b32feae23dfc97887e81b4f51cf9fe17a39ef493ed8cabe23" +checksum = "0d66cb4ced9d63185f825c07083a4810f0e168562f6cfe2b72bc8fc1a37ec48d" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -341,9 +373,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-to-casm" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91e32d7eeac0e4c7837e87bb75375c2d42eaa05dd314a636b162c68f5326e56" +checksum = "420cae89a2f0a0ce8c2fe49210539d5c47654141f86211e63728cd1fb0098a40" dependencies = [ "assert_matches", "cairo-lang-casm", @@ -362,9 +394,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-type-size" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40197fac8db5540008ae6c4e9ba40ebf0e402f8cf7c709e9959b48913ebd18ff" +checksum = "246dc32e2ea9f419cee43e213a58a23ab302eaea492487a3fc2d9684b5cf03d0" dependencies = [ "cairo-lang-sierra", "cairo-lang-utils", @@ -372,15 +404,15 @@ dependencies = [ [[package]] name = "cairo-lang-starknet-classes" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1d950413579e309fc2952c263281dc0dfce8491c1480f84d8d8a4376b2d960" +checksum = "49c34bd8d894d9412b2a632c337bfbc5d3726e620fa732e086478939bb240101" dependencies = [ "cairo-lang-casm", "cairo-lang-sierra", "cairo-lang-sierra-to-casm", "cairo-lang-utils", - "convert_case 0.7.1", + "convert_case", "itertools", "num-bigint", "num-integer", @@ -395,9 +427,9 @@ dependencies = [ [[package]] name = "cairo-lang-syntax" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0e2287233244b6a49835724c932726d9e951fdfdbcc24ca5402f667d745209" +checksum = "7ffb74953193cd56cfb71527c69e7dabce90b7550823e18bf3ea54474fe2209e" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -413,9 +445,9 @@ dependencies = [ [[package]] name = "cairo-lang-syntax-codegen" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c69eb6919b69a9a7bb92f79d73c568d09ef77efa0b5d265efb763234ec979d4" +checksum = "a36fad6859a594f62c1dcdb7a65cd1360461a79162f07a1f74beeee3617a42c6" dependencies = [ "genco", "xshell", @@ -423,9 +455,9 @@ dependencies = [ [[package]] name = "cairo-lang-utils" -version = "2.11.4" +version = "2.12.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c387a696a29cea52ddedb3097706dd8168958ef5388157d6e5b388a2c0de061" +checksum = "07f5f439b336d78c4d0e4f11e07dca9622f88ad08dac09252c4212d4bb24629d" dependencies = [ "hashbrown 0.15.2", "indexmap 2.8.0", @@ -434,6 +466,7 @@ dependencies = [ "num-traits", "schemars", "serde", + "smol_str", ] [[package]] @@ -442,6 +475,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "colored" version = "3.0.0" @@ -489,15 +528,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "convert_case" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "convert_case" version = "0.8.0" @@ -620,13 +650,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "d6215aee357f8c7c989ebb4b8466ca4d7dc93b3957039f2fc3ea2ade8ea5f279" dependencies = [ "bit-set", + "derivative", "regex-automata", "regex-syntax", ] @@ -674,7 +715,7 @@ checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -767,7 +808,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -816,6 +857,15 @@ dependencies = [ "similar", ] +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + [[package]] name = "itertools" version = "0.14.0" @@ -929,7 +979,7 @@ checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1065,11 +1115,12 @@ dependencies = [ "cairo-lang-plugins", "cairo-lang-starknet-classes", "cairo-lang-syntax", - "convert_case 0.8.0", + "convert_case", "fancy-regex", "indoc", "insta", "regex", + "syn 2.0.104", ] [[package]] @@ -1097,7 +1148,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1290,7 +1341,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1352,7 +1403,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1387,7 +1438,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1398,7 +1449,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1463,10 +1514,11 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol_str" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" dependencies = [ + "borsh", "serde", ] @@ -1528,9 +1580,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1579,7 +1631,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1590,7 +1642,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1646,7 +1698,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1668,12 +1720,42 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "typetag" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "unescaper" version = "0.1.5" @@ -1701,12 +1783,24 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index e526fdf7e..267f0cd4e 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -8,14 +8,15 @@ crate-type = ["cdylib"] [dependencies] cairo-lang-macro = "0.1" -cairo-lang-parser = "2.11" -cairo-lang-plugins = "2.11" -cairo-lang-syntax = "2.11" -cairo-lang-defs = "2.11" -cairo-lang-formatter = "2.11" -cairo-lang-starknet-classes = "2.11" +cairo-lang-parser = "2.12.0-dev.1" +cairo-lang-plugins = "2.12.0-dev.1" +cairo-lang-syntax = "2.12.0-dev.1" +cairo-lang-defs = "2.12.0-dev.1" +cairo-lang-formatter = "2.12.0-dev.1" +cairo-lang-starknet-classes = "2.12.0-dev.1" indoc = "2.0.5" regex = "1.11.1" insta = "1.42.0" convert_case = "0.8.0" -fancy-regex = "0.14.0" +fancy-regex = "0.15.0" +syn = "2.0.104" diff --git a/packages/macros/src/attribute/type_hash/definition.rs b/packages/macros/src/attribute/type_hash/definition.rs index b868aee0a..a8965d66f 100644 --- a/packages/macros/src/attribute/type_hash/definition.rs +++ b/packages/macros/src/attribute/type_hash/definition.rs @@ -147,9 +147,9 @@ fn handle_node( args: &TypeHashArgs, ) -> Result { let typed = ast::SyntaxFile::from_syntax_node(db, node); - let items = typed.items(db).elements(db); + let mut items = typed.items(db).elements(db); - let Some(item_ast) = items.first() else { + let Some(item_ast) = items.next() else { let error = Diagnostic::error(errors::EMPTY_TYPE_FOUND); return Err(error); }; @@ -158,7 +158,7 @@ fn handle_node( match item_ast { ast::ModuleItem::Struct(_) | ast::ModuleItem::Enum(_) => { // It is safe to unwrap here because we know the item is a struct - let plugin_type_info = PluginTypeInfo::new(db, item_ast).unwrap(); + let plugin_type_info = PluginTypeInfo::new(db, &item_ast).unwrap(); generate_code(db, &plugin_type_info, args) } _ => { diff --git a/packages/macros/src/attribute/type_hash/parser.rs b/packages/macros/src/attribute/type_hash/parser.rs index 3c9f86123..8a7a5261c 100644 --- a/packages/macros/src/attribute/type_hash/parser.rs +++ b/packages/macros/src/attribute/type_hash/parser.rs @@ -52,7 +52,7 @@ impl<'a> TypeHashParser<'a> { .members_info .iter() .map(|member| { - let attributes = member.attributes.elements(db); + let attributes = member.attributes.elements(db).collect::>(); let args = match get_name_and_type_from_attributes(db, &attributes) { Ok(args) => args, Err(e) => { diff --git a/packages/macros/src/attribute/with_components/definition.rs b/packages/macros/src/attribute/with_components/definition.rs index f3947f4eb..7f91f3337 100644 --- a/packages/macros/src/attribute/with_components/definition.rs +++ b/packages/macros/src/attribute/with_components/definition.rs @@ -3,7 +3,7 @@ use cairo_lang_macro::{attribute_macro, Diagnostic, ProcMacroResult, TokenStream use cairo_lang_parser::utils::SimpleParserDatabase; use regex::Regex; -use super::{components::AllowedComponents, parser::WithComponentsParser}; +use crate::with_components::{components::AllowedComponents, parser::WithComponentsParser}; /// Inserts multiple component dependencies into a modules codebase. #[attribute_macro] diff --git a/packages/macros/src/attribute/with_components/diagnostics.rs b/packages/macros/src/attribute/with_components/diagnostics.rs index 31dcf9c65..7fefd0e5a 100644 --- a/packages/macros/src/attribute/with_components/diagnostics.rs +++ b/packages/macros/src/attribute/with_components/diagnostics.rs @@ -118,4 +118,48 @@ pub mod warnings { it looks like it is missing. " }; + + /// 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 + it looks like it is missing. + + You can use the ERC4626EmptyHooks implementation by importing it: + + `use openzeppelin_token::erc20::extensions::erc4626::ERC4626EmptyHooks;` + " + }; + + /// Warning when the ERC4626 component is missing an implementation of the FeeConfigTrait. + pub const ERC4626_FEE_CONFIG_IMPL_MISSING: &str = indoc! { + "The ERC4626 component requires an implementation of the FeeConfigTrait in scope and + it looks like it is missing. + + You can use the ERC4626DefaultNoFees implementation by importing it: + + `use openzeppelin_token::erc20::extensions::erc4626::ERC4626DefaultNoFees;` + " + }; + + /// Warning when the ERC4626 component is missing an implementation of the LimitConfigTrait. + pub const ERC4626_LIMIT_CONFIG_IMPL_MISSING: &str = indoc! { + "The ERC4626 component requires an implementation of the LimitConfigTrait in scope and + it looks like it is missing. + + You can use the ERC4626DefaultNoLimits implementation by importing it: + + `use openzeppelin_token::erc20::extensions::erc4626::ERC4626DefaultNoLimits;` + " + }; + + /// Warning when the ERC4626 component is missing an implementation of the AssetsManagementTrait. + pub const ERC4626_ASSETS_MANAGEMENT_IMPL_MISSING: &str = indoc! { + "The ERC4626 component requires an implementation of the AssetsManagementTrait in scope and + it looks like it is missing. + + You can use the ERC4626SelfAssetsManagement implementation by importing it: + + `use openzeppelin_token::erc20::extensions::erc4626::ERC4626SelfAssetsManagement;` + " + }; } diff --git a/packages/macros/src/attribute/with_components/parser.rs b/packages/macros/src/attribute/with_components/parser.rs index 14ce8a5a3..cd79cfac1 100644 --- a/packages/macros/src/attribute/with_components/parser.rs +++ b/packages/macros/src/attribute/with_components/parser.rs @@ -9,7 +9,7 @@ use crate::{ }; use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; use cairo_lang_macro::{Diagnostic, Diagnostics}; -use cairo_lang_syntax::node::helpers::{BodyItems, QueryAttrs}; +use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{ ast::{self, MaybeModuleBody}, db::SyntaxGroup, @@ -42,7 +42,7 @@ impl<'a> WithComponentsParser<'a> { /// Parses the module and returns the patched code. pub fn parse(&mut self, db: &dyn SyntaxGroup) -> (String, Diagnostics) { - let base_node = self.base_node.clone(); + let base_node = self.base_node; let mut builder = PatchBuilder::new_ex(db, &base_node); let typed = ast::SyntaxFile::from_syntax_node(db, base_node); @@ -98,7 +98,7 @@ fn validate_contract_module( let mut warnings = vec![]; if let RewriteNode::Copied(copied) = node { - let item = ast::ItemModule::from_syntax_node(db, copied.clone()); + let item = ast::ItemModule::from_syntax_node(db, *copied); // 1. Check that the module has a body (error) let MaybeModuleBody::Some(body) = item.body(db) else { @@ -119,13 +119,13 @@ fn validate_contract_module( .collect::>(); if !components_with_initializer.is_empty() { - let constructor = body.items_vec(db).into_iter().find(|item| { + let constructor = body.items(db).elements(db).find(|item| { matches!(item, ast::ModuleItem::FreeFunction(function_ast) if function_ast.has_attr(db, CONSTRUCTOR_ATTRIBUTE)) }); let constructor_code = if let Some(constructor) = constructor { // Get the constructor code (maybe we can do this without the builder) let constructor_ast = constructor.as_syntax_node(); - let typed = ast::ModuleItem::from_syntax_node(db, constructor_ast.clone()); + let typed = ast::ModuleItem::from_syntax_node(db, constructor_ast); let constructor_rnode = RewriteNode::from_ast(&typed); let mut builder = PatchBuilder::new_ex(db, &constructor_ast); builder.add_modified(constructor_rnode); @@ -155,7 +155,7 @@ fn validate_contract_module( 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.clone()); + 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); @@ -231,6 +231,36 @@ fn add_per_component_warnings(code: &str, component_info: &ComponentInfo) -> Vec warnings.push(warning); } } + AllowedComponents::ERC4626 => { + // 1. Check that the ERC4626HooksTrait is implemented + let hooks_trait_used = code.contains("ERC4626HooksTrait"); + let hooks_empty_impl_used = code.contains("ERC4626EmptyHooks"); + if !hooks_trait_used && !hooks_empty_impl_used { + let warning = Diagnostic::warn(warnings::ERC4626_HOOKS_IMPL_MISSING); + warnings.push(warning); + } + // 2. Check that the FeeConfigTrait is implemented + let fee_config_trait_used = code.contains("FeeConfigTrait"); + let fee_config_empty_impl_used = code.contains("ERC4626DefaultNoFees"); + if !fee_config_trait_used && !fee_config_empty_impl_used { + let warning = Diagnostic::warn(warnings::ERC4626_FEE_CONFIG_IMPL_MISSING); + warnings.push(warning); + } + // 3. Check that the LimitConfigTrait is implemented + let limit_config_trait_used = code.contains("LimitConfigTrait"); + let limit_config_empty_impl_used = code.contains("ERC4626DefaultNoLimits"); + if !limit_config_trait_used && !limit_config_empty_impl_used { + let warning = Diagnostic::warn(warnings::ERC4626_LIMIT_CONFIG_IMPL_MISSING); + warnings.push(warning); + } + // 4. Check that the AssetsManagementTrait is implemented + let assets_management_trait_used = code.contains("AssetsManagementTrait"); + let self_assets_management_impl_used = code.contains("ERC4626SelfAssetsManagement"); + if !assets_management_trait_used && !self_assets_management_impl_used { + let warning = Diagnostic::warn(warnings::ERC4626_ASSETS_MANAGEMENT_IMPL_MISSING); + warnings.push(warning); + } + } AllowedComponents::ERC721 => { // Check that the ERC721HooksTrait is implemented let hooks_trait_used = code.contains("ERC721HooksTrait"); @@ -282,7 +312,7 @@ fn process_module_items( for item_rnode in items_mnode.children.as_mut().unwrap() { if let RewriteNode::Copied(copied) = item_rnode { - let item = ast::ModuleItem::from_syntax_node(db, copied.clone()); + let item = ast::ModuleItem::from_syntax_node(db, *copied); match item { ast::ModuleItem::Struct(item_struct) diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index c3b94f223..1ddaf3bc5 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -2,7 +2,7 @@ pub(crate) mod attribute; pub(crate) mod constants; pub(crate) mod utils; -pub(crate) use attribute::type_hash; +pub(crate) use attribute::{type_hash, with_components}; #[cfg(test)] mod tests; diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs index ddc6a87d3..eb3796fb3 100644 --- a/packages/macros/src/tests/test_with_components.rs +++ b/packages/macros/src/tests/test_with_components.rs @@ -1,4 +1,4 @@ -use crate::attribute::with_components::definition::with_components_avevetedp5blk as with_components; +use crate::with_components::definition::with_components_avevetedp5blk as with_components; use cairo_lang_macro::TokenStream; use indoc::indoc; use insta::assert_snapshot; diff --git a/packages/macros/src/with_components.rs b/packages/macros/src/with_components.rs deleted file mode 100644 index 2da3ac10d..000000000 --- a/packages/macros/src/with_components.rs +++ /dev/null @@ -1,906 +0,0 @@ -use crate::constants::{ - CONSTRUCTOR_ATTRIBUTE, CONTRACT_ATTRIBUTE, EVENT_ENUM_NAME, FLAT_ATTRIBUTE, - STORAGE_STRUCT_NAME, SUBSTORAGE_ATTRIBUTE, -}; -use crate::utils::tabs; -use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; -use cairo_lang_formatter::format_string; -use cairo_lang_macro::{attribute_macro, Diagnostic, Diagnostics, ProcMacroResult, TokenStream}; -use cairo_lang_parser::utils::SimpleParserDatabase; -use cairo_lang_syntax::node::ast::MaybeModuleBody; -use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::helpers::{BodyItems, QueryAttrs}; -use cairo_lang_syntax::node::{ast, SyntaxNode, Terminal, TypedSyntaxNode}; -use indoc::{formatdoc, indoc}; -use regex::Regex; - -const ALLOWED_COMPONENTS: [&str; 22] = [ - "Account", - "EthAccount", - "SRC9", - "AccessControl", - "Ownable", - "Vesting", - "SRC5", - "Initializable", - "Pausable", - "ReentrancyGuard", - "ERC20", - "ERC4626", - "ERC721", - "ERC721Enumerable", - "ERC721Receiver", - "ERC1155", - "ERC1155Receiver", - "ERC2981", - "Upgradeable", - "Nonces", - "Multisig", - "TimelockController", - "Votes", - // "TODO:Governor", - // "TODO:GovernorCoreExecution", - // "TODO:GovernorCountingSimple", - // "TODO:GovernorSettings", - // "TODO:GovernorTimelockExecution", - // "TODO:GovernorVotesQuorumFraction", - // "TODO:GovernorVotes", -]; - -/// Inserts multiple component dependencies into a modules codebase. -#[attribute_macro] -pub fn with_components(attribute_stream: TokenStream, item_stream: TokenStream) -> ProcMacroResult { - let args = parse_args(&attribute_stream.to_string()); - - // 1. Get the components info (if valid) - let mut components_info = vec![]; - for arg in args { - let component_info_result = get_component_info(&arg); - if let Ok(info) = component_info_result { - components_info.push(info); - } else { - return ProcMacroResult::new(TokenStream::empty()) - .with_diagnostics(component_info_result.unwrap_err()); - } - } - - // 2. Parse the item stream - let db = SimpleParserDatabase::default(); - let (content, mut diagnostics) = match db.parse_virtual(item_stream.to_string()) { - Ok(node) => build_patch(&db, node, components_info.clone()), - Err(err) => { - let error = Diagnostic::error(err.format(&db)); - return ProcMacroResult::new(TokenStream::empty()).with_diagnostics(error.into()); - } - }; - - // 3. Add warnings for each component - for component_info in components_info.iter() { - let component_warnings = add_per_component_warnings(&content, component_info); - diagnostics.extend(component_warnings); - } - - let formatted_content = if !content.is_empty() { - format_string(&db, content) - } else { - content - }; - - ProcMacroResult::new(TokenStream::new(formatted_content)).with_diagnostics(diagnostics) -} - -/// Parses the arguments from the attribute stream. -fn parse_args(text: &str) -> Vec { - let re = Regex::new(r"(\w+)").unwrap(); - let matches = re.find_iter(text); - matches.map(|m| m.as_str().to_string()).collect() -} - -/// Builds the patch for a given node and component info. -fn build_patch( - db: &dyn SyntaxGroup, - node: SyntaxNode, - components_info: Vec, -) -> (String, Diagnostics) { - let mut builder = PatchBuilder::new_ex(db, &node); - - let typed = ast::SyntaxFile::from_syntax_node(db, node); - let mut base_rnode = RewriteNode::from_ast(&typed); - let module_rnode = base_rnode - .modify_child(db, ast::SyntaxFile::INDEX_ITEMS) - .modify_child(db, 0); - - // Validate the contract module - let (errors, warnings) = validate_contract_module(db, module_rnode, &components_info); - if !errors.is_empty() { - return (String::new(), errors.into()); - } - - let body_rnode = module_rnode.modify_child(db, ast::ItemModule::INDEX_BODY); - - process_module_items(body_rnode, db, &components_info); - add_use_clauses_and_macros(body_rnode, db, &components_info); - - builder.add_modified(base_rnode); - let (content, _) = builder.build(); - - (content, warnings.into()) -} - -/// Validates that the contract module: -/// -/// - Has the `#[starknet::contract]` attribute. -/// - Has a constructor calling the corresponding initializers. -/// - Has the corresponding immutable configs. -/// -/// NOTE: Missing initializers and configs are added as Warnings. -/// NOTE: When an error is found, the functions doesn't return any warnings to avoid noise. -/// -/// # Returns -/// -/// * `errors` - The errors that arose during the validation. -/// * `warnings` - The warnings that arose during the validation. -fn validate_contract_module( - db: &dyn SyntaxGroup, - node: &mut RewriteNode, - components_info: &[ComponentInfo], -) -> (Vec, Vec) { - let mut warnings = vec![]; - - if let RewriteNode::Copied(copied) = node { - let item = ast::ItemModule::from_syntax_node(db, copied.clone()); - - // 1. Check that the module has a body (error) - let MaybeModuleBody::Some(body) = item.body(db) else { - let error = Diagnostic::error(indoc! {" - Contract module must have a body. - "}); - return (vec![error], vec![]); - }; - - // 2. Check that the module has the `#[starknet::contract]` attribute (error) - if !item.has_attr(db, CONTRACT_ATTRIBUTE) { - let error = Diagnostic::error(formatdoc! {" - Contract module must have the `#[{CONTRACT_ATTRIBUTE}]` attribute. - "}); - return (vec![error], vec![]); - } - - // 3. Check that the module has the corresponding initializers (warning) - let components_with_initializer = components_info - .iter() - .filter(|c| c.has_initializer) - .collect::>(); - - if !components_with_initializer.is_empty() { - let constructor = body.items_vec(db).into_iter().find(|item| { - matches!(item, ast::ModuleItem::FreeFunction(function_ast) if function_ast.has_attr(db, CONSTRUCTOR_ATTRIBUTE)) - }); - let constructor_code = if let Some(constructor) = constructor { - // Get the constructor code (maybe we can do this without the builder) - let constructor_ast = constructor.as_syntax_node(); - let typed = ast::ModuleItem::from_syntax_node(db, constructor_ast.clone()); - let constructor_rnode = RewriteNode::from_ast(&typed); - let mut builder = PatchBuilder::new_ex(db, &constructor_ast); - builder.add_modified(constructor_rnode); - let (code, _) = builder.build(); - code - } else { - String::new() - }; - let mut components_with_initializer_missing = vec![]; - for component in components_with_initializer.iter() { - if !constructor_code.contains(&format!("self.{}.initializer(", component.storage)) { - components_with_initializer_missing.push(component.short_name()); - } - } - - if !components_with_initializer_missing.is_empty() { - let components_with_initializer_missing_str = - components_with_initializer_missing.join(", "); - let warning = Diagnostic::warn(formatdoc! {" - It looks like the initializers for the following components are missing: - - {components_with_initializer_missing_str} - - This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. - "}); - warnings.push(warning); - } - } - - // 4. 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.clone()); - 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 - .strip_suffix(&component.name) - .expect("Component path must end with the component name"); - let default_config_path = format!("{component_parent_path}DefaultConfig"); - let default_config_used = code.contains(&default_config_path); - if !default_config_used { - // Check if the ImmutableConfig is implemented - let immutable_config_implemented = - code.contains(&format!("of {}::ImmutableConfig", component.name)); - if !immutable_config_implemented { - let warning = Diagnostic::warn(formatdoc! {" - The {} component requires an ImmutableConfig implementation in scope. It looks like this implementation is missing. - - You can use the default implementation by importing: - - `use {};` - ", component.short_name(), default_config_path}); - warnings.push(warning); - } - } - } - } - - (vec![], warnings) -} - -/// Adds warnings that may be helpful for users. -fn add_per_component_warnings(code: &str, component_info: &ComponentInfo) -> Vec { - let mut warnings = vec![]; - - match component_info.short_name() { - "Vesting" => { - // 1. Check that the VestingScheduleTrait is implemented - let linear_impl_used = code.contains("LinearVestingSchedule"); - let vesting_trait_used = code.contains("VestingScheduleTrait"); - if !linear_impl_used && !vesting_trait_used { - let warning = Diagnostic::warn(indoc! {" - The Vesting component requires an implementation of the VestingScheduleTrait in scope. It looks like this implementation is missing. - - You can use the LinearVestingSchedule implementation by importing it: - - `use openzeppelin_finance::vesting::LinearVestingSchedule;` - "}); - warnings.push(warning); - } - } - "Initializable" => { - // 1. Check that the initialize internal function is called - let initialize_internal_function_called = - code.contains("self.initializable.initialize()"); - if !initialize_internal_function_called { - let warning = Diagnostic::warn(indoc! {" - It looks like the `self.initializable.initialize()` function is not used in the contract. If - this is intentional, you may consider removing the Initializable component. - "}); - warnings.push(warning); - } - } - "Pausable" => { - // 1. Check that the pause and unpause functions are called - let pause_function_called = code.contains("self.pausable.pause()"); - let unpause_function_called = code.contains("self.pausable.unpause()"); - if !pause_function_called || !unpause_function_called { - let warning = Diagnostic::warn(indoc! {" - It looks like either the `self.pausable.pause()` or `self.pausable.unpause()` mechanisms are not implemented in the contract. If - this is intentional, you may consider removing the Pausable component. - "}); - warnings.push(warning); - } - } - "ERC20" => { - // 1. Check that the ERC20HooksTrait is implemented - let hooks_trait_used = code.contains("ERC20HooksTrait"); - let hooks_empty_impl_used = code.contains("ERC20HooksEmptyImpl"); - if !hooks_trait_used && !hooks_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC20 component requires an implementation of the ERC20HooksTrait in scope. It looks like this implementation is missing. - - You can use the ERC20HooksEmptyImpl implementation by importing it: - - `use openzeppelin_token::erc20::ERC20HooksEmptyImpl;` - "}); - warnings.push(warning); - } - } - "ERC4626" => { - // 1. Check that the ERC4626HooksTrait is implemented - let hooks_trait_used = code.contains("ERC4626HooksTrait"); - let hooks_empty_impl_used = code.contains("ERC4626EmptyHooks"); - if !hooks_trait_used && !hooks_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC4626 component requires an implementation of the ERC4626HooksTrait in scope. It looks like this implementation is missing. - - You can use the ERC4626EmptyHooks implementation by importing it: - - `use openzeppelin_token::erc20::extensions::erc4626::ERC4626EmptyHooks;` - "}); - warnings.push(warning); - } - // 2. Check that the FeeConfigTrait is implemented - let fee_config_trait_used = code.contains("FeeConfigTrait"); - let fee_config_empty_impl_used = code.contains("ERC4626DefaultNoFees"); - if !fee_config_trait_used && !fee_config_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC4626 component requires an implementation of the FeeConfigTrait in scope. It looks like this implementation is missing. - - You can use the ERC4626DefaultNoFees implementation by importing it: - - `use openzeppelin_token::erc20::extensions::erc4626::ERC4626DefaultNoFees;` - "}); - warnings.push(warning); - } - // 3. Check that the LimitConfigTrait is implemented - let limit_config_trait_used = code.contains("LimitConfigTrait"); - let limit_config_empty_impl_used = code.contains("ERC4626DefaultNoLimits"); - if !limit_config_trait_used && !limit_config_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC4626 component requires an implementation of the LimitConfigTrait in scope. It looks like this implementation is missing. - - You can use the ERC4626DefaultNoLimits implementation by importing it: - - `use openzeppelin_token::erc20::extensions::erc4626::ERC4626DefaultNoLimits;` - "}); - warnings.push(warning); - } - // 4. Check that the AssetsManagementTrait is implemented - let assets_management_trait_used = code.contains("AssetsManagementTrait"); - let self_assets_management_impl_used = code.contains("ERC4626SelfAssetsManagement"); - if !assets_management_trait_used && !self_assets_management_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC4626 component requires an implementation of the AssetsManagementTrait in scope. It looks like this implementation is missing. - - You can use the ERC4626SelfAssetsManagement implementation by importing it: - - `use openzeppelin_token::erc20::extensions::erc4626::ERC4626SelfAssetsManagement;` - "}); - warnings.push(warning); - } - } - "ERC721" => { - // 1. Check that the ERC721HooksTrait is implemented - let hooks_trait_used = code.contains("ERC721HooksTrait"); - let hooks_empty_impl_used = code.contains("ERC721HooksEmptyImpl"); - if !hooks_trait_used && !hooks_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC721 component requires an implementation of the ERC721HooksTrait in scope. It looks like this implementation is missing. - - You can use the ERC721HooksEmptyImpl implementation by importing it: - - `use openzeppelin_token::erc721::ERC721HooksEmptyImpl;` - "}); - warnings.push(warning); - } - } - "ERC1155" => { - // 1. Check that the ERC1155HooksTrait is implemented - let hooks_trait_used = code.contains("ERC1155HooksTrait"); - let hooks_empty_impl_used = code.contains("ERC1155HooksEmptyImpl"); - if !hooks_trait_used && !hooks_empty_impl_used { - let warning = Diagnostic::warn(indoc! {" - The ERC1155 component requires an implementation of the ERC1155HooksTrait in scope. It looks like this implementation is missing. - - You can use the ERC1155HooksEmptyImpl implementation by importing it: - - `use openzeppelin_token::erc1155::ERC1155HooksEmptyImpl;` - "}); - warnings.push(warning); - } - } - "Upgradeable" => { - // 1. Check that the upgrade function is called - let upgrade_function_called = code.contains("self.upgradeable.upgrade"); - if !upgrade_function_called { - let warning = Diagnostic::warn(indoc! {" - It looks like the `self.upgradeable.upgrade(new_class_hash)` function is not used in the contract. If - this is intentional, you may consider removing the Upgradeable component. - "}); - warnings.push(warning); - } - } - "Votes" => { - // 1. Check that the SNIP12Metadata is implemented - let snip12_metadata_implemented = code.contains("of SNIP12Metadata"); - if !snip12_metadata_implemented { - let warning = Diagnostic::warn(indoc! {" - The Votes component requires an implementation of the SNIP12Metadata trait. It looks like this implementation is missing. - "}); - warnings.push(warning); - } - } - _ => {} - } - warnings -} - -/// Iterates over the items in the body node and processes them. -fn process_module_items( - body_rnode: &mut RewriteNode, - db: &dyn SyntaxGroup, - components_info: &Vec, -) { - let items_rnode = body_rnode.modify_child(db, ast::ModuleBody::INDEX_ITEMS); - let items_mnode = items_rnode.modify(db); - let mut event_enum_found = false; - - for item_rnode in items_mnode.children.as_mut().unwrap() { - if let RewriteNode::Copied(copied) = item_rnode { - let item = ast::ModuleItem::from_syntax_node(db, copied.clone()); - - match item { - ast::ModuleItem::Struct(item_struct) - if item_struct.name(db).text(db) == STORAGE_STRUCT_NAME => - { - process_storage_struct(item_rnode, db, components_info); - } - ast::ModuleItem::Enum(item_enum) - if item_enum.name(db).text(db) == EVENT_ENUM_NAME => - { - process_event_enum(item_rnode, db, components_info); - event_enum_found = true; - } - _ => {} - } - } - } - - // If the event enum is not found, add it. - if !event_enum_found { - add_event_enum(body_rnode, db, components_info); - } -} - -/// Modifies the storage struct to add the component entries. -fn process_storage_struct( - item_struct: &mut RewriteNode, - db: &dyn SyntaxGroup, - components_info: &Vec, -) { - let item_struct_mnode = item_struct.modify(db); - let item_struct_children = item_struct_mnode.children.as_mut().unwrap(); - let components_rnode = - ComponentsGenerationData(components_info).generate_for_storage_struct(db); - - // Insert the components at the beginning of the struct body. - item_struct_children.insert(ast::ItemStruct::INDEX_LBRACE + 1, components_rnode); -} - -/// Modifies the event enum to add the component events. -fn process_event_enum( - item_enum: &mut RewriteNode, - db: &dyn SyntaxGroup, - components_info: &Vec, -) { - let item_enum_mnode = item_enum.modify(db); - let item_enum_children = item_enum_mnode.children.as_mut().unwrap(); - let components_rnode = ComponentsGenerationData(components_info).generate_for_event_enum(db); - - // Insert the components at the beginning of the enum body. - item_enum_children.insert(ast::ItemEnum::INDEX_LBRACE + 1, components_rnode); -} - -fn add_event_enum( - body_rnode: &mut RewriteNode, - db: &dyn SyntaxGroup, - components_info: &Vec, -) { - let body_mnode = body_rnode.modify(db); - let event_enum_rnode = ComponentsGenerationData(components_info).generate_event_enum(db); - - // It is safe to unwrap here because we know that the node has at least the storage struct children - body_mnode - .children - .as_mut() - .unwrap() - .insert(ast::ModuleBody::INDEX_RBRACE, event_enum_rnode); -} - -/// Modifies the body node to add the use clauses and the `component!` macros to the module. -fn add_use_clauses_and_macros( - body_rnode: &mut RewriteNode, - db: &dyn SyntaxGroup, - components_info: &Vec, -) { - let body_mnode = body_rnode.modify(db); - let components_rnode = ComponentsGenerationData(components_info).generate_for_module(db); - - // It is safe to unwrap here because we know that the node has at least the storage struct children - body_mnode - .children - .as_mut() - .unwrap() - .insert(ast::ModuleBody::INDEX_RBRACE, components_rnode); -} - -/// Information about a component. -/// -/// # Members -/// -/// * `name` - The name of the component (e.g. `ERC20Component`) -/// * `path` - The path from where the component is imported (e.g. `openzeppelin_token::erc20::ERC20Component`) -/// * `storage` - The path to reference the component in storage (e.g. `erc20`) -/// * `event` - The path to reference the component events (e.g. `ERC20Event`) -/// * `has_initializer` - Whether the component requires an initializer (e.g. `true`) -/// * `internal_impls` - The internal implementations of the component to be added to -/// the module by default (e.g. `["InternalImpl1", "InternalImpl2"]`) -#[derive(Debug, Clone)] -pub struct ComponentInfo<'a> { - pub name: &'a str, - pub path: &'a str, - pub storage: &'a str, - pub event: &'a str, - pub has_initializer: bool, - pub has_immutable_config: bool, - pub internal_impls: Vec<&'a str>, -} - -impl<'a> ComponentInfo<'a> { - fn short_name(&self) -> &'a str { - self.name - .split("Component") - .next() - .expect("Component name must end with 'Component'") - } -} - -/// Returns the component info for a given component name. -/// -/// # Arguments -/// -/// * `name` - The name of the component (e.g. `ERC20`). -fn get_component_info<'a>(name: &str) -> Result, Diagnostics> { - match name { - "Account" => Ok(ComponentInfo { - name: "AccountComponent", - path: "openzeppelin_account::AccountComponent", - storage: "account", - event: "AccountEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "EthAccount" => Ok(ComponentInfo { - name: "EthAccountComponent", - path: "openzeppelin_account::EthAccountComponent", - storage: "eth_account", - event: "EthAccountEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "SRC9" => Ok(ComponentInfo { - name: "SRC9Component", - path: "openzeppelin_account::extensions::SRC9Component", - storage: "src9", - event: "SRC9Event", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Ownable" => Ok(ComponentInfo { - name: "OwnableComponent", - path: "openzeppelin_access::ownable::OwnableComponent", - storage: "ownable", - event: "OwnableEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "AccessControl" => Ok(ComponentInfo { - name: "AccessControlComponent", - path: "openzeppelin_access::accesscontrol::AccessControlComponent", - storage: "access_control", - event: "AccessControlEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Vesting" => Ok(ComponentInfo { - name: "VestingComponent", - path: "openzeppelin_finance::vesting::VestingComponent", - storage: "vesting", - event: "VestingEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "SRC5" => Ok(ComponentInfo { - name: "SRC5Component", - path: "openzeppelin_introspection::src5::SRC5Component", - storage: "src5", - event: "SRC5Event", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Initializable" => Ok(ComponentInfo { - name: "InitializableComponent", - path: "openzeppelin_security::InitializableComponent", - storage: "initializable", - event: "InitializableEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Pausable" => Ok(ComponentInfo { - name: "PausableComponent", - path: "openzeppelin_security::PausableComponent", - storage: "pausable", - event: "PausableEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ReentrancyGuard" => Ok(ComponentInfo { - name: "ReentrancyGuardComponent", - path: "openzeppelin_security::ReentrancyGuardComponent", - storage: "reentrancy_guard", - event: "ReentrancyGuardEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC20" => Ok(ComponentInfo { - name: "ERC20Component", - path: "openzeppelin_token::erc20::ERC20Component", - storage: "erc20", - event: "ERC20Event", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC721" => Ok(ComponentInfo { - name: "ERC721Component", - path: "openzeppelin_token::erc721::ERC721Component", - storage: "erc721", - event: "ERC721Event", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC721Enumerable" => Ok(ComponentInfo { - name: "ERC721EnumerableComponent", - path: "openzeppelin_token::erc721::extensions::ERC721EnumerableComponent", - storage: "erc721_enumerable", - event: "ERC721EnumerableEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC721Receiver" => Ok(ComponentInfo { - name: "ERC721ReceiverComponent", - path: "openzeppelin_token::erc721::ERC721ReceiverComponent", - storage: "erc721_receiver", - event: "ERC721ReceiverEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC1155" => Ok(ComponentInfo { - name: "ERC1155Component", - path: "openzeppelin_token::erc1155::ERC1155Component", - storage: "erc1155", - event: "ERC1155Event", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC1155Receiver" => Ok(ComponentInfo { - name: "ERC1155ReceiverComponent", - path: "openzeppelin_token::erc1155::ERC1155ReceiverComponent", - storage: "erc1155_receiver", - event: "ERC1155ReceiverEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "ERC2981" => Ok(ComponentInfo { - name: "ERC2981Component", - path: "openzeppelin_token::common::erc2981::ERC2981Component", - storage: "erc2981", - event: "ERC2981Event", - has_initializer: true, - has_immutable_config: true, - internal_impls: vec!["InternalImpl"], - }), - "Upgradeable" => Ok(ComponentInfo { - name: "UpgradeableComponent", - path: "openzeppelin_upgrades::UpgradeableComponent", - storage: "upgradeable", - event: "UpgradeableEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Nonces" => Ok(ComponentInfo { - name: "NoncesComponent", - path: "openzeppelin_utils::nonces::NoncesComponent", - storage: "nonces", - event: "NoncesEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Multisig" => Ok(ComponentInfo { - name: "MultisigComponent", - path: "openzeppelin_governance::multisig::MultisigComponent", - storage: "multisig", - event: "MultisigEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "TimelockController" => Ok(ComponentInfo { - name: "TimelockControllerComponent", - path: "openzeppelin_governance::timelock::TimelockControllerComponent", - storage: "timelock_controller", - event: "TimelockControllerEvent", - has_initializer: true, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - "Votes" => Ok(ComponentInfo { - name: "VotesComponent", - path: "openzeppelin_governance::votes::VotesComponent", - storage: "votes", - event: "VotesEvent", - has_initializer: false, - has_immutable_config: false, - internal_impls: vec!["InternalImpl"], - }), - _ => { - let allowed_components = ALLOWED_COMPONENTS.join(", "); - let error_message = formatdoc! {" - Invalid component: {name} - - Allowed components are: - {allowed_components} - "}; - let error = Diagnostic::error(error_message); - Err(error.into()) - } - } -} - -/// Set of component information to be used for code generation. -struct ComponentsGenerationData<'a>(&'a Vec>); - -impl ComponentsGenerationData<'_> { - fn generate_for_module(self, _db: &dyn SyntaxGroup) -> RewriteNode { - RewriteNode::interpolate_patched( - indoc! {" - - $component_use_clause_entries$ - - $component_macro_entries$ - - $component_internal_impls_entries$ - "}, - &[ - ( - "component_use_clause_entries".to_string(), - self.component_use_clause_entries(), - ), - ( - "component_macro_entries".to_string(), - self.component_macro_entries(), - ), - ( - "component_internal_impls_entries".to_string(), - self.component_internal_impls_entries(), - ), - ] - .into(), - ) - } - - fn generate_for_storage_struct(self, _db: &dyn SyntaxGroup) -> RewriteNode { - let mut entries = vec![]; - for component in self.0.iter() { - entries.push(format!("{}#[{}]", tabs(2), SUBSTORAGE_ATTRIBUTE)); - entries.push(format!( - "{}pub {}: {}::Storage,", - tabs(2), - component.storage, - component.name - )); - } - RewriteNode::Text(entries.join("\n") + "\n") - } - - fn generate_for_event_enum(self, _db: &dyn SyntaxGroup) -> RewriteNode { - let mut entries = vec![]; - for component in self.0.iter() { - entries.push(format!("{}#[{}]", tabs(2), FLAT_ATTRIBUTE)); - entries.push(format!( - "{}{}: {}::Event,", - tabs(2), - component.event, - component.name - )); - } - RewriteNode::Text(entries.join("\n") + "\n") - } - - fn generate_event_enum(self, _db: &dyn SyntaxGroup) -> RewriteNode { - let mut entries = vec![]; - - entries.push(format!("\n{}#[event]", tabs(1))); - entries.push(format!("{}#[derive(Drop, starknet::Event)]", tabs(1))); - entries.push(format!("{}enum {} {{", tabs(1), EVENT_ENUM_NAME)); - for component in self.0.iter() { - entries.push(format!("{}#[{}]", tabs(2), FLAT_ATTRIBUTE)); - entries.push(format!( - "{}{}: {}::Event,", - tabs(2), - component.event, - component.name - )); - } - entries.push(format!("{}}}", tabs(1))); - RewriteNode::Text(entries.join("\n")) - } - - fn component_use_clause_entries(&self) -> RewriteNode { - let mut entries = vec![]; - for component in self.0.iter() { - entries.push(format!("{}use {};", tabs(1), component.path)); - } - RewriteNode::Text(entries.join("\n")) - } - - fn component_macro_entries(&self) -> RewriteNode { - let mut entries = vec![]; - for component in self.0.iter() { - entries.push(format!( - "{}component!(path: {}, storage: {}, event: {});", - tabs(1), - component.name, - component.storage, - component.event - )); - } - RewriteNode::Text(entries.join("\n")) - } - - fn component_internal_impls_entries(&self) -> RewriteNode { - let mut entries = vec![]; - for component in self.0.iter() { - for implementation in component.internal_impls.iter() { - entries.push(format!( - "{}impl {}{} = {}::{};", - tabs(1), - component.short_name(), - implementation, - component.name, - implementation - )); - } - } - RewriteNode::Text(entries.join("\n")) - } -} - -#[cfg(test)] -mod tests { - use super::parse_args; - - #[test] - fn test_parse_args() { - let attribute = "(ERC20, Ownable)"; - let result = parse_args(attribute); - assert_eq!(result, vec!["ERC20", "Ownable"]); - - let attribute = "ERC20"; - let result = parse_args(attribute); - assert_eq!(result, vec!["ERC20"]); - - let attribute = "(Ownable, ERC20, Other, Another)"; - let result = parse_args(attribute); - assert_eq!(result, vec!["Ownable", "ERC20", "Other", "Another"]); - } -} diff --git a/packages/merkle_tree/src/merkle_proof.cairo b/packages/merkle_tree/src/merkle_proof.cairo index ee43c09d4..9b19b75c7 100644 --- a/packages/merkle_tree/src/merkle_proof.cairo +++ b/packages/merkle_tree/src/merkle_proof.cairo @@ -88,10 +88,7 @@ pub fn process_multi_proof( let proof_flags_len = proof_flags.len(); // Check proof validity. - if (leaves_len + proof.len() != proof_flags_len + 1) { - #[allow(panic)] - panic!("MerkleProof: invalid multi proof"); - } + assert!(leaves_len + proof.len() == proof_flags_len + 1, "MerkleProof: invalid multi proof"); // The x_pos values are "pointers" to the next value to consume in each array. // By incrementing the value, we simulate a queue's pop operation. diff --git a/packages/presets/src/tests/test_universal_deployer.cairo b/packages/presets/src/tests/test_universal_deployer.cairo index 12c813f2f..5950c322a 100644 --- a/packages/presets/src/tests/test_universal_deployer.cairo +++ b/packages/presets/src/tests/test_universal_deployer.cairo @@ -4,13 +4,11 @@ use openzeppelin_interfaces::deployments::{ use openzeppelin_interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{CALLER, NAME, RECIPIENT, SALT, SUPPLY, SYMBOL}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use openzeppelin_utils::deployments::{DeployerInfo, calculate_contract_address_from_udc}; use openzeppelin_utils::serde::SerializedAppend; use snforge_std::start_cheat_caller_address; use starknet::{ClassHash, ContractAddress}; -use crate::universal_deployer::UniversalDeployer; -use crate::universal_deployer::UniversalDeployer::ContractDeployed; fn ERC20_CLASS_HASH() -> ClassHash { utils::declare_class("DualCaseERC20Mock").class_hash @@ -157,9 +155,15 @@ impl UniversalDeployerHelpersImpl of UniversalDeployerSpyHelpers { calldata: Span, salt: felt252, ) { - let expected = UniversalDeployer::Event::ContractDeployed( - ContractDeployed { address, deployer, not_from_zero, class_hash, calldata, salt }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ContractDeployed")) + .data(address) + .data(deployer) + .data(not_from_zero) + .data(class_hash) + .data(calldata) + .data(salt); + self.assert_only_event(contract, expected); } } diff --git a/packages/security/Scarb.toml b/packages/security/Scarb.toml index 33fa5fca7..5ec25f149 100644 --- a/packages/security/Scarb.toml +++ b/packages/security/Scarb.toml @@ -31,6 +31,7 @@ assert_macros.workspace = true snforge_std.workspace = true openzeppelin_testing = { path = "../testing" } openzeppelin_test_common = { path = "../test_common" } +openzeppelin_utils = { path = "../utils" } [lib] diff --git a/packages/security/src/tests/test_pausable.cairo b/packages/security/src/tests/test_pausable.cairo index 7f5d70c7d..76ad2ba2d 100644 --- a/packages/security/src/tests/test_pausable.cairo +++ b/packages/security/src/tests/test_pausable.cairo @@ -1,10 +1,10 @@ use openzeppelin_test_common::mocks::security::PausableMock; use openzeppelin_testing::constants::CALLER; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use crate::PausableComponent; -use crate::PausableComponent::{InternalImpl, PausableImpl, Paused, Unpaused}; +use crate::PausableComponent::{InternalImpl, PausableImpl}; type ComponentState = PausableComponent::ComponentState; @@ -125,7 +125,7 @@ impl PausableSpyHelpersImpl of PausableSpyHelpers { fn assert_event_paused( ref self: EventSpy, contract: ContractAddress, account: ContractAddress, ) { - let expected = PausableComponent::Event::Paused(Paused { account }); + let expected = ExpectedEvent::new().key(selector!("Paused")).data(account); self.assert_emitted_single(contract, expected); } @@ -139,7 +139,7 @@ impl PausableSpyHelpersImpl of PausableSpyHelpers { fn assert_event_unpaused( ref self: EventSpy, contract: ContractAddress, account: ContractAddress, ) { - let expected = PausableComponent::Event::Unpaused(Unpaused { account }); + let expected = ExpectedEvent::new().key(selector!("Unpaused")).data(account); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/account.cairo b/packages/test_common/src/account.cairo index 0a83b0316..09d5d6785 100644 --- a/packages/test_common/src/account.cairo +++ b/packages/test_common/src/account.cairo @@ -1,10 +1,8 @@ use core::hash::{HashStateExTrait, HashStateTrait}; use core::poseidon::PoseidonTrait; -use openzeppelin_account::AccountComponent; -use openzeppelin_account::AccountComponent::{OwnerAdded, OwnerRemoved}; use openzeppelin_testing::constants::TRANSACTION_HASH; use openzeppelin_testing::signing::StarkKeyPair; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use snforge_std::signature::stark_curve::StarkCurveSignerImpl; use starknet::ContractAddress; @@ -39,14 +37,14 @@ pub impl AccountSpyHelpersImpl of AccountSpyHelpers { fn assert_event_owner_removed( ref self: EventSpy, contract: ContractAddress, removed_owner_guid: felt252, ) { - let expected = AccountComponent::Event::OwnerRemoved(OwnerRemoved { removed_owner_guid }); + let expected = ExpectedEvent::new().key(selector!("OwnerRemoved")).key(removed_owner_guid); self.assert_emitted_single(contract, expected); } fn assert_event_owner_added( ref self: EventSpy, contract: ContractAddress, new_owner_guid: felt252, ) { - let expected = AccountComponent::Event::OwnerAdded(OwnerAdded { new_owner_guid }); + let expected = ExpectedEvent::new().key(selector!("OwnerAdded")).key(new_owner_guid); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/erc1155.cairo b/packages/test_common/src/erc1155.cairo index 2cedfbaa6..ca6673a1a 100644 --- a/packages/test_common/src/erc1155.cairo +++ b/packages/test_common/src/erc1155.cairo @@ -1,8 +1,6 @@ use openzeppelin_testing as utils; use openzeppelin_testing::constants::{PUBKEY, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE, TOKEN_VALUE_2}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; -use openzeppelin_token::erc1155::ERC1155Component; -use openzeppelin_token::erc1155::ERC1155Component::{ApprovalForAll, TransferBatch, TransferSingle}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; pub fn setup_receiver() -> ContractAddress { @@ -44,9 +42,11 @@ pub impl ERC1155SpyHelpersImpl of ERC1155SpyHelpers { operator: ContractAddress, approved: bool, ) { - let expected = ERC1155Component::Event::ApprovalForAll( - ApprovalForAll { owner, operator, approved }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ApprovalForAll")) + .key(owner) + .key(operator) + .data(approved); self.assert_emitted_single(contract, expected); } @@ -70,9 +70,13 @@ pub impl ERC1155SpyHelpersImpl of ERC1155SpyHelpers { token_id: u256, value: u256, ) { - let expected = ERC1155Component::Event::TransferSingle( - TransferSingle { operator, from, to, id: token_id, value }, - ); + let expected = ExpectedEvent::new() + .key(selector!("TransferSingle")) + .key(operator) + .key(from) + .key(to) + .data(token_id) + .data(value); self.assert_emitted_single(contract, expected); } @@ -98,9 +102,13 @@ pub impl ERC1155SpyHelpersImpl of ERC1155SpyHelpers { token_ids: Span, values: Span, ) { - let expected = ERC1155Component::Event::TransferBatch( - TransferBatch { operator, from, to, ids: token_ids, values }, - ); + let expected = ExpectedEvent::new() + .key(selector!("TransferBatch")) + .key(operator) + .key(from) + .key(to) + .data(token_ids) + .data(values); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/erc20.cairo b/packages/test_common/src/erc20.cairo index 4c6104dd7..85bbb7dd4 100644 --- a/packages/test_common/src/erc20.cairo +++ b/packages/test_common/src/erc20.cairo @@ -1,9 +1,7 @@ use openzeppelin_interfaces::erc20::IERC20Dispatcher; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{NAME, SYMBOL}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; -use openzeppelin_token::erc20::ERC20Component; -use openzeppelin_token::erc20::ERC20Component::{Approval, Transfer}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use openzeppelin_utils::serde::SerializedAppend; use starknet::ContractAddress; @@ -27,7 +25,12 @@ pub impl ERC20SpyHelpersImpl of ERC20SpyHelpers { spender: ContractAddress, value: u256, ) { - let expected = ERC20Component::Event::Approval(Approval { owner, spender, value }); + let expected = ExpectedEvent::new() + .key(selector!("Approval")) + .key(owner) + .key(spender) + .data(value); + self.assert_emitted_single(contract, expected); } @@ -49,7 +52,12 @@ pub impl ERC20SpyHelpersImpl of ERC20SpyHelpers { to: ContractAddress, value: u256, ) { - let expected = ERC20Component::Event::Transfer(Transfer { from, to, value }); + let expected = ExpectedEvent::new() + .key(selector!("Transfer")) + .key(from) + .key(to) + .data(value); + self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/erc721.cairo b/packages/test_common/src/erc721.cairo index 9a710cb96..61fc52d96 100644 --- a/packages/test_common/src/erc721.cairo +++ b/packages/test_common/src/erc721.cairo @@ -1,6 +1,4 @@ -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; -use openzeppelin_token::erc721::ERC721Component; -use openzeppelin_token::erc721::ERC721Component::{Approval, ApprovalForAll, Transfer}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; #[generate_trait] @@ -12,9 +10,11 @@ pub impl ERC721SpyHelpersImpl of ERC721SpyHelpers { operator: ContractAddress, approved: bool, ) { - let expected = ERC721Component::Event::ApprovalForAll( - ApprovalForAll { owner, operator, approved }, - ); + let expected = ExpectedEvent::new() + .key(selector!("ApprovalForAll")) + .key(owner) + .key(operator) + .data(approved); self.assert_emitted_single(contract, expected); } @@ -36,7 +36,11 @@ pub impl ERC721SpyHelpersImpl of ERC721SpyHelpers { approved: ContractAddress, token_id: u256, ) { - let expected = ERC721Component::Event::Approval(Approval { owner, approved, token_id }); + let expected = ExpectedEvent::new() + .key(selector!("Approval")) + .key(owner) + .key(approved) + .key(token_id); self.assert_emitted_single(contract, expected); } @@ -58,7 +62,11 @@ pub impl ERC721SpyHelpersImpl of ERC721SpyHelpers { to: ContractAddress, token_id: u256, ) { - let expected = ERC721Component::Event::Transfer(Transfer { from, to, token_id }); + let expected = ExpectedEvent::new() + .key(selector!("Transfer")) + .key(from) + .key(to) + .key(token_id); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/eth_account.cairo b/packages/test_common/src/eth_account.cairo index 8cdd82eda..f42fbb10c 100644 --- a/packages/test_common/src/eth_account.cairo +++ b/packages/test_common/src/eth_account.cairo @@ -1,12 +1,10 @@ use core::hash::{HashStateExTrait, HashStateTrait}; use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -use openzeppelin_account::EthAccountComponent; -use openzeppelin_account::EthAccountComponent::{OwnerAdded, OwnerRemoved}; use openzeppelin_account::utils::signature::Secp256Signature; use openzeppelin_interfaces::accounts::EthPublicKey; use openzeppelin_testing::constants::TRANSACTION_HASH; use openzeppelin_testing::signing::{Secp256k1KeyPair, Secp256k1SerializedSigning}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use snforge_std::signature::secp256k1_curve::Secp256k1CurveSignerImpl; use starknet::secp256_trait::Secp256PointTrait; use starknet::{ContractAddress, SyscallResultTrait}; @@ -50,9 +48,7 @@ pub impl EthAccountSpyHelpersImpl of EthAccountSpyHelpers { ref self: EventSpy, contract: ContractAddress, public_key: EthPublicKey, ) { let removed_owner_guid = get_guid_from_public_key(public_key); - let expected = EthAccountComponent::Event::OwnerRemoved( - OwnerRemoved { removed_owner_guid }, - ); + let expected = ExpectedEvent::new().key(selector!("OwnerRemoved")).key(removed_owner_guid); self.assert_emitted_single(contract, expected); } @@ -60,7 +56,7 @@ pub impl EthAccountSpyHelpersImpl of EthAccountSpyHelpers { ref self: EventSpy, contract: ContractAddress, public_key: EthPublicKey, ) { let new_owner_guid = get_guid_from_public_key(public_key); - let expected = EthAccountComponent::Event::OwnerAdded(OwnerAdded { new_owner_guid }); + let expected = ExpectedEvent::new().key(selector!("OwnerAdded")).key(new_owner_guid); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/mocks/security.cairo b/packages/test_common/src/mocks/security.cairo index b8dd25dbc..f11fd0a15 100644 --- a/packages/test_common/src/mocks/security.cairo +++ b/packages/test_common/src/mocks/security.cairo @@ -7,6 +7,11 @@ pub mod InitializableMock { impl InitializableImpl = InitializableComponent::InitializableImpl; + #[constructor] + fn constructor(ref self: ContractState) { + self.initializable.initialize(); + } + #[storage] pub struct Storage {} } @@ -17,6 +22,20 @@ pub mod PausableMock { #[abi(embed_v0)] impl PausableImpl = PausableComponent::PausableImpl; + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn pause(ref self: ContractState) { + self.pausable.pause(); + } + + #[external(v0)] + fn unpause(ref self: ContractState) { + self.pausable.unpause(); + } + } + #[storage] pub struct Storage {} } diff --git a/packages/test_common/src/mocks/simple.cairo b/packages/test_common/src/mocks/simple.cairo index dc41de41a..1cea70c60 100644 --- a/packages/test_common/src/mocks/simple.cairo +++ b/packages/test_common/src/mocks/simple.cairo @@ -22,10 +22,7 @@ pub mod SimpleMock { } fn set_balance(ref self: ContractState, value: felt252, panic: bool) { - if panic { - #[allow(panic)] - panic!("Some error"); - } + assert!(!panic, "Some error"); self.balance.write(value); } diff --git a/packages/test_common/src/ownable.cairo b/packages/test_common/src/ownable.cairo index d5fe76b84..e9ef3f70b 100644 --- a/packages/test_common/src/ownable.cairo +++ b/packages/test_common/src/ownable.cairo @@ -1,6 +1,4 @@ -use openzeppelin_access::ownable::OwnableComponent; -use openzeppelin_access::ownable::OwnableComponent::OwnershipTransferred; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; #[generate_trait] @@ -21,9 +19,10 @@ pub impl OwnableSpyHelpersImpl of OwnableSpyHelpers { previous_owner: ContractAddress, new_owner: ContractAddress, ) { - let expected = OwnableComponent::Event::OwnershipTransferred( - OwnershipTransferred { previous_owner, new_owner }, - ); + let expected = ExpectedEvent::new() + .key(selector!("OwnershipTransferred")) + .key(previous_owner) + .key(new_owner); self.assert_emitted_single(contract, expected); } } diff --git a/packages/test_common/src/upgrades.cairo b/packages/test_common/src/upgrades.cairo index 2a2576df8..50376ae4a 100644 --- a/packages/test_common/src/upgrades.cairo +++ b/packages/test_common/src/upgrades.cairo @@ -1,12 +1,10 @@ -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; -use openzeppelin_upgrades::UpgradeableComponent; -use openzeppelin_upgrades::UpgradeableComponent::Upgraded; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::{ClassHash, ContractAddress}; #[generate_trait] pub impl UpgradeableSpyHelpersImpl of UpgradeableSpyHelpers { fn assert_event_upgraded(ref self: EventSpy, contract: ContractAddress, class_hash: ClassHash) { - let expected = UpgradeableComponent::Event::Upgraded(Upgraded { class_hash }); + let expected = ExpectedEvent::new().key(selector!("Upgraded")).data(class_hash); self.assert_emitted_single(contract, expected); } diff --git a/packages/test_common/src/vesting.cairo b/packages/test_common/src/vesting.cairo index 44861ae95..121eeacfe 100644 --- a/packages/test_common/src/vesting.cairo +++ b/packages/test_common/src/vesting.cairo @@ -1,6 +1,4 @@ -use openzeppelin_finance::vesting::VestingComponent; -use openzeppelin_finance::vesting::VestingComponent::AmountReleased; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent}; use starknet::ContractAddress; #[generate_trait] @@ -8,7 +6,10 @@ pub impl VestingSpyHelpersImpl of VestingSpyHelpers { fn assert_only_event_amount_released( ref self: EventSpy, contract: ContractAddress, token: ContractAddress, amount: u256, ) { - let expected = VestingComponent::Event::AmountReleased(AmountReleased { token, amount }); + let expected = ExpectedEvent::new() + .key(selector!("AmountReleased")) + .key(token) + .data(amount); self.assert_only_event(contract, expected); } } diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index cb272fc7f..4115fa0d5 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- The `ExpectedEvent` trait and impl (#1472) + ### Changed - Bump snforge to v0.48.1 (#1498) diff --git a/packages/testing/src/events.cairo b/packages/testing/src/events.cairo index b871a1a46..fcbc4acce 100644 --- a/packages/testing/src/events.cairo +++ b/packages/testing/src/events.cairo @@ -90,6 +90,70 @@ pub impl EventSpyQueueImpl of EventSpyExt { } } +/// The `ExpectedEvent` trait provides a convenient API for constructing +/// expected events in tests. +/// +/// This implementation is generated by the `#[generate_trait]` macro and offers +/// three methods: +/// +/// * `new()`: creates a fresh `Event` with empty `keys` and `data` arrays. +/// * `key(value)`: serializes the given value and appends it to the `keys` array. +/// * `data(value)`: serializes the given value and appends it to the `data` array. +/// +/// By using these methods, you can construct events in a fluent style without +/// manually managing the `keys` and `data` arrays. For example, instead of: +/// +/// ```cairo +/// let mut keys = array![]; +/// keys.append_serde(selector!("Transfer")); // event selector +/// keys.append_serde(from); // key #1 +/// keys.append_serde(to); // key #2 +/// +/// let mut data = array![]; +/// data.append_serde(value); // non-key data +/// let expected = Event { keys, data }; +/// +/// spy.assert_emitted( +/// @array![(contract_address, expected)], +/// ); +/// ``` +/// +/// you can now write: +/// +/// ```cairo +/// let expected = ExpectedEvent::new() +/// .key(selector!("Transfer")) +/// .key(from) +/// .key(to) +/// .data(value); +/// spy.assert_emitted( +/// @array![(contract_address, expected)] +/// ); +/// ``` +/// +/// NOTE: `key` and `data` accept any serializable value as input. +#[generate_trait] +pub impl ExpectedEvent of ExpectedEventTrait { + /// Creates a new `Event` with empty `keys` and `data` arrays. + fn new() -> Event { + Event { keys: array![], data: array![] } + } + + /// Serializes the given value and appends it to the `keys` array. + fn key, +Drop>(self: Event, value: T) -> Event { + let Event { mut keys, data } = self; + value.serialize(ref keys); + Event { keys, data } + } + + /// Serializes the given value and appends it to the `data` array. + fn data, +Drop>(self: Event, value: T) -> Event { + let Event { keys, mut data } = self; + value.serialize(ref data); + Event { keys, data } + } +} + #[generate_trait] pub impl EventSpyQueueDebugImpl of EventSpyQueueDebug { /// Prints out all events remaining on the queue. diff --git a/packages/testing/src/lib.cairo b/packages/testing/src/lib.cairo index 74137a380..557c3b3cc 100644 --- a/packages/testing/src/lib.cairo +++ b/packages/testing/src/lib.cairo @@ -13,4 +13,4 @@ pub use deployment::{ declare_and_deploy, declare_and_deploy_at, declare_class, deploy, deploy_another_at, deploy_at, }; -pub use events::{EventSpyExt, EventSpyQueue, spy_events}; +pub use events::{EventSpyExt, EventSpyQueue, ExpectedEvent, spy_events}; diff --git a/packages/token/src/erc20/extensions/erc4626/erc4626.cairo b/packages/token/src/erc20/extensions/erc4626/erc4626.cairo index 1486f9620..fd87a94f7 100644 --- a/packages/token/src/erc20/extensions/erc4626/erc4626.cairo +++ b/packages/token/src/erc20/extensions/erc4626/erc4626.cairo @@ -34,9 +34,7 @@ #[starknet::component] pub mod ERC4626Component { use core::num::traits::{Bounded, Pow, Zero}; - use openzeppelin_interfaces::erc20::{ - IERC20, IERC20Dispatcher, IERC20DispatcherTrait, IERC20Metadata, - }; + use openzeppelin_interfaces::erc20::{IERC20, IERC20Metadata}; use openzeppelin_interfaces::erc4626::IERC4626; use openzeppelin_utils::math; use openzeppelin_utils::math::Rounding; diff --git a/packages/token/src/tests/erc1155/test_erc1155.cairo b/packages/token/src/tests/erc1155/test_erc1155.cairo index f07930a91..c07419285 100644 --- a/packages/token/src/tests/erc1155/test_erc1155.cairo +++ b/packages/token/src/tests/erc1155/test_erc1155.cairo @@ -10,7 +10,7 @@ use openzeppelin_testing::constants::{ EMPTY_DATA, OPERATOR, OTHER, OWNER, RECIPIENT, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE, TOKEN_VALUE_2, ZERO, }; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::storage::StoragePointerReadAccess; @@ -1392,9 +1392,12 @@ impl ERC1155HooksSpyHelpersImpl of ERC1155HooksSpyHelpers { token_ids: Span, values: Span, ) { - let expected = SnakeERC1155MockWithHooks::Event::BeforeUpdate( - SnakeERC1155MockWithHooks::BeforeUpdate { from, to, token_ids, values }, - ); + let expected = ExpectedEvent::new() + .key(selector!("BeforeUpdate")) + .data(from) + .data(to) + .data(token_ids) + .data(values); self.assert_emitted_single(contract, expected); } @@ -1406,9 +1409,12 @@ impl ERC1155HooksSpyHelpersImpl of ERC1155HooksSpyHelpers { token_ids: Span, values: Span, ) { - let expected = SnakeERC1155MockWithHooks::Event::AfterUpdate( - SnakeERC1155MockWithHooks::AfterUpdate { from, to, token_ids, values }, - ); + let expected = ExpectedEvent::new() + .key(selector!("AfterUpdate")) + .data(from) + .data(to) + .data(token_ids) + .data(values); self.assert_emitted_single(contract, expected); } } diff --git a/packages/token/src/tests/erc20/test_erc20.cairo b/packages/token/src/tests/erc20/test_erc20.cairo index 3b0c9fcfe..31500a46e 100644 --- a/packages/token/src/tests/erc20/test_erc20.cairo +++ b/packages/token/src/tests/erc20/test_erc20.cairo @@ -2,7 +2,7 @@ use core::num::traits::Bounded; use openzeppelin_test_common::erc20::ERC20SpyHelpers; use openzeppelin_test_common::mocks::erc20::{DualCaseERC20Mock, SnakeERC20MockWithHooks}; use openzeppelin_testing::constants::{NAME, OWNER, RECIPIENT, SPENDER, SUPPLY, SYMBOL, VALUE, ZERO}; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use crate::erc20::ERC20Component; @@ -668,9 +668,11 @@ impl ERC20HooksSpyHelpersImpl of ERC20HooksSpyHelpers { recipient: ContractAddress, amount: u256, ) { - let expected = SnakeERC20MockWithHooks::Event::BeforeUpdate( - SnakeERC20MockWithHooks::BeforeUpdate { from, recipient, amount }, - ); + let expected = ExpectedEvent::new() + .key(selector!("BeforeUpdate")) + .data(from) + .data(recipient) + .data(amount); self.assert_emitted_single(contract, expected); } @@ -681,9 +683,11 @@ impl ERC20HooksSpyHelpersImpl of ERC20HooksSpyHelpers { recipient: ContractAddress, amount: u256, ) { - let expected = SnakeERC20MockWithHooks::Event::AfterUpdate( - SnakeERC20MockWithHooks::AfterUpdate { from, recipient, amount }, - ); + let expected = ExpectedEvent::new() + .key(selector!("AfterUpdate")) + .data(from) + .data(recipient) + .data(amount); self.assert_emitted_single(contract, expected); } } diff --git a/packages/token/src/tests/erc4626/test_erc4626.cairo b/packages/token/src/tests/erc4626/test_erc4626.cairo index 3444e5024..b129d39e2 100644 --- a/packages/token/src/tests/erc4626/test_erc4626.cairo +++ b/packages/token/src/tests/erc4626/test_erc4626.cairo @@ -4,18 +4,18 @@ use openzeppelin_test_common::erc20::ERC20SpyHelpers; use openzeppelin_test_common::mocks::erc20::{ IERC20ReentrantDispatcher, IERC20ReentrantDispatcherTrait, Type, }; -use openzeppelin_test_common::mocks::erc4626::{ - ERC4626LimitsMock, ERC4626Mock, ERC4626MockWithHooks, -}; +use openzeppelin_test_common::mocks::erc4626::{ERC4626LimitsMock, ERC4626Mock}; use openzeppelin_testing as utils; use openzeppelin_testing::constants::{ALICE, BOB, NAME, OTHER, RECIPIENT, SPENDER, SYMBOL, ZERO}; -use openzeppelin_testing::{AsAddressTrait, EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{ + AsAddressTrait, EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events, +}; use openzeppelin_utils::serde::SerializedAppend; use snforge_std::{CheatSpan, cheat_caller_address}; use starknet::ContractAddress; use crate::erc20::ERC20Component::InternalImpl as ERC20InternalImpl; use crate::erc20::extensions::erc4626::ERC4626Component::{ - Deposit, ERC4626Impl, ERC4626MetadataImpl, InternalImpl, Withdraw, + ERC4626Impl, ERC4626MetadataImpl, InternalImpl, }; use crate::erc20::extensions::erc4626::{DefaultConfig, ERC4626Component}; @@ -2092,7 +2092,12 @@ pub impl ERC4626SpyHelpersImpl of ERC4626SpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626Component::Event::Deposit(Deposit { sender, owner, assets, shares }); + let expected = ExpectedEvent::new() + .key(selector!("Deposit")) + .key(sender) + .key(owner) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } @@ -2117,9 +2122,13 @@ pub impl ERC4626SpyHelpersImpl of ERC4626SpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626Component::Event::Withdraw( - Withdraw { sender, receiver, owner, assets, shares }, - ); + let expected = ExpectedEvent::new() + .key(selector!("Withdraw")) + .key(sender) + .key(receiver) + .key(owner) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } @@ -2147,9 +2156,12 @@ impl ERC4626HooksSpyHelpersImpl of ERC4626HooksSpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626MockWithHooks::Event::BeforeDeposit( - ERC4626MockWithHooks::BeforeDeposit { caller, receiver, assets, shares }, - ); + let expected = ExpectedEvent::new() + .key(selector!("BeforeDeposit")) + .data(caller) + .data(receiver) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } @@ -2161,9 +2173,12 @@ impl ERC4626HooksSpyHelpersImpl of ERC4626HooksSpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626MockWithHooks::Event::AfterDeposit( - ERC4626MockWithHooks::AfterDeposit { caller, receiver, assets, shares }, - ); + let expected = ExpectedEvent::new() + .key(selector!("AfterDeposit")) + .data(caller) + .data(receiver) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } @@ -2176,9 +2191,13 @@ impl ERC4626HooksSpyHelpersImpl of ERC4626HooksSpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626MockWithHooks::Event::BeforeWithdraw( - ERC4626MockWithHooks::BeforeWithdraw { caller, receiver, owner, assets, shares }, - ); + let expected = ExpectedEvent::new() + .key(selector!("BeforeWithdraw")) + .data(caller) + .data(receiver) + .data(owner) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } @@ -2191,9 +2210,13 @@ impl ERC4626HooksSpyHelpersImpl of ERC4626HooksSpyHelpers { assets: u256, shares: u256, ) { - let expected = ERC4626MockWithHooks::Event::AfterWithdraw( - ERC4626MockWithHooks::AfterWithdraw { caller, receiver, owner, assets, shares }, - ); + let expected = ExpectedEvent::new() + .key(selector!("AfterWithdraw")) + .data(caller) + .data(receiver) + .data(owner) + .data(assets) + .data(shares); self.assert_emitted_single(contract, expected); } } diff --git a/packages/token/src/tests/erc721/test_erc721.cairo b/packages/token/src/tests/erc721/test_erc721.cairo index ce097b2fe..3e557eb69 100644 --- a/packages/token/src/tests/erc721/test_erc721.cairo +++ b/packages/token/src/tests/erc721/test_erc721.cairo @@ -8,7 +8,7 @@ use openzeppelin_testing::constants::{ BASE_URI, BASE_URI_2, CALLER, DATA, NAME, OPERATOR, OTHER, OWNER, PUBKEY, RECIPIENT, SPENDER, SYMBOL, TOKEN_ID, TOKEN_ID_2, ZERO, }; -use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, spy_events}; +use openzeppelin_testing::{EventSpyExt, EventSpyQueue as EventSpy, ExpectedEvent, spy_events}; use snforge_std::{start_cheat_caller_address, test_address}; use starknet::ContractAddress; use starknet::storage::StorageMapReadAccess; @@ -1488,9 +1488,11 @@ impl ERC721HooksSpyHelpersImpl of ERC721HooksSpyHelpers { token_id: u256, auth: ContractAddress, ) { - let expected = SnakeERC721MockWithHooks::Event::BeforeUpdate( - SnakeERC721MockWithHooks::BeforeUpdate { to, token_id, auth }, - ); + let expected = ExpectedEvent::new() + .key(selector!("BeforeUpdate")) + .data(to) + .data(token_id) + .data(auth); self.assert_emitted_single(contract, expected); } @@ -1501,9 +1503,11 @@ impl ERC721HooksSpyHelpersImpl of ERC721HooksSpyHelpers { token_id: u256, auth: ContractAddress, ) { - let expected = SnakeERC721MockWithHooks::Event::AfterUpdate( - SnakeERC721MockWithHooks::AfterUpdate { to, token_id, auth }, - ); + let expected = ExpectedEvent::new() + .key(selector!("AfterUpdate")) + .data(to) + .data(token_id) + .data(auth); self.assert_emitted_single(contract, expected); } } diff --git a/sncast_scripts/Scarb.lock b/sncast_scripts/Scarb.lock index a6c2fac6c..697006ac7 100644 --- a/sncast_scripts/Scarb.lock +++ b/sncast_scripts/Scarb.lock @@ -77,6 +77,9 @@ version = "3.0.0-alpha.1" [[package]] name = "openzeppelin_utils" version = "3.0.0-alpha.1" +dependencies = [ + "openzeppelin_interfaces", +] [[package]] name = "sncast_scripts"