diff --git a/starknet/src/factory/factory.cairo b/starknet/src/factory/factory.cairo index a701e053..855327aa 100644 --- a/starknet/src/factory/factory.cairo +++ b/starknet/src/factory/factory.cairo @@ -7,7 +7,7 @@ trait IFactory { self: @TContractState, class_hash: ClassHash, contract_address_salt: felt252, - calldata: Span + initialize_calldata: Span ) -> ContractAddress; } @@ -17,9 +17,11 @@ mod Factory { use super::IFactory; use starknet::ContractAddress; use starknet::contract_address_const; - use starknet::syscalls::deploy_syscall; + use starknet::syscalls::{deploy_syscall, call_contract_syscall}; use starknet::ClassHash; use result::ResultTrait; + use array::{ArrayTrait, SpanTrait}; + use sx::utils::constants::INITIALIZE_SELECTOR; #[event] fn SpaceDeployed(class_hash: ClassHash, space_address: ContractAddress) {} @@ -33,13 +35,17 @@ mod Factory { self: @ContractState, class_hash: ClassHash, contract_address_salt: felt252, - calldata: Span + initialize_calldata: Span ) -> ContractAddress { let (space_address, _) = deploy_syscall( - class_hash, contract_address_salt, calldata, false + class_hash, contract_address_salt, array![].span(), false ) .unwrap(); + // Call initializer. + call_contract_syscall(space_address, INITIALIZE_SELECTOR, initialize_calldata) + .unwrap_syscall(); + SpaceDeployed(class_hash, space_address); space_address diff --git a/starknet/src/space/space.cairo b/starknet/src/space/space.cairo index fd2f18a3..31c4c720 100644 --- a/starknet/src/space/space.cairo +++ b/starknet/src/space/space.cairo @@ -29,6 +29,20 @@ trait ISpace { fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); fn renounce_ownership(ref self: TContractState); // Actions + fn initialize( + ref self: TContractState, + owner: ContractAddress, + min_voting_duration: u32, + max_voting_duration: u32, + voting_delay: u32, + proposal_validation_strategy: Strategy, + proposal_validation_strategy_metadata_URI: Array, + voting_strategies: Array, + voting_strategy_metadata_URIs: Array>, + authenticators: Array, + metadata_URI: Array, + dao_URI: Array, + ); fn propose( ref self: TContractState, author: UserAddress, @@ -53,13 +67,15 @@ trait ISpace { metadata_URI: Array, ); fn cancel_proposal(ref self: TContractState, proposal_id: u256); - fn upgrade(ref self: TContractState, class_hash: ClassHash); + fn upgrade( + ref self: TContractState, class_hash: ClassHash, initialize_calldata: Array + ); } #[starknet::contract] mod Space { use super::ISpace; - use starknet::{ClassHash, ContractAddress, info, Store}; + use starknet::{ClassHash, ContractAddress, info, Store, syscalls}; use zeroable::Zeroable; use array::{ArrayTrait, SpanTrait}; use clone::Clone; @@ -77,14 +93,17 @@ mod Space { IndexedStrategyTrait, IndexedStrategyImpl, UpdateSettingsCalldata, NoUpdateU32, NoUpdateStrategy, NoUpdateArray }; + use sx::utils::reinitializable::Reinitializable; + use sx::utils::ReinitializableImpl; use sx::utils::bits::BitSetter; use sx::utils::legacy_hash::LegacyHashChoice; use sx::external::ownable::Ownable; + use sx::utils::constants::INITIALIZE_SELECTOR; #[storage] struct Storage { - _max_voting_duration: u32, _min_voting_duration: u32, + _max_voting_duration: u32, _next_proposal_id: u256, _voting_delay: u32, _active_voting_strategies: u256, @@ -101,9 +120,9 @@ mod Space { fn SpaceCreated( _space: ContractAddress, _owner: ContractAddress, - _voting_delay: u32, _min_voting_duration: u32, _max_voting_duration: u32, + _voting_delay: u32, _proposal_validation_strategy: @Strategy, _proposal_validation_strategy_metadata_URI: @Array, _voting_strategies: @Array, @@ -179,10 +198,56 @@ mod Space { fn VotingDelayUpdated(_new_voting_delay: u32) {} #[event] - fn Upgraded(class_hash: ClassHash) {} + fn Upgraded(class_hash: ClassHash, initialize_calldata: Array) {} #[external(v0)] impl Space of ISpace { + fn initialize( + ref self: ContractState, + owner: ContractAddress, + min_voting_duration: u32, + max_voting_duration: u32, + voting_delay: u32, + proposal_validation_strategy: Strategy, + proposal_validation_strategy_metadata_URI: Array, + voting_strategies: Array, + voting_strategy_metadata_URIs: Array>, + authenticators: Array, + metadata_URI: Array, + dao_URI: Array, + ) { + SpaceCreated( + info::get_contract_address(), + owner, + min_voting_duration, + max_voting_duration, + voting_delay, + @proposal_validation_strategy, + @proposal_validation_strategy_metadata_URI, + @voting_strategies, + @voting_strategy_metadata_URIs, + @authenticators, + @metadata_URI, + @dao_URI + ); + // Checking that the contract is not already initialized + //TODO: temporary component syntax (see imports too) + let mut state: Reinitializable::ContractState = + Reinitializable::unsafe_new_contract_state(); + ReinitializableImpl::initialize(ref state); + + //TODO: temporary component syntax + let mut state: Ownable::ContractState = Ownable::unsafe_new_contract_state(); + Ownable::initializer(ref state); + Ownable::transfer_ownership(ref state, owner); + _set_min_voting_duration(ref self, min_voting_duration); + _set_max_voting_duration(ref self, max_voting_duration); + _set_voting_delay(ref self, voting_delay); + _set_proposal_validation_strategy(ref self, proposal_validation_strategy); + _add_voting_strategies(ref self, voting_strategies); + _add_authenticators(ref self, authenticators); + self._next_proposal_id.write(1_u256); + } fn propose( ref self: ContractState, author: UserAddress, @@ -344,15 +409,26 @@ mod Space { ProposalCancelled(proposal_id); } - fn upgrade(ref self: ContractState, class_hash: ClassHash) { + fn upgrade( + ref self: ContractState, class_hash: ClassHash, initialize_calldata: Array + ) { let state: Ownable::ContractState = Ownable::unsafe_new_contract_state(); Ownable::assert_only_owner(@state); - assert( - class_hash.is_non_zero(), 'Class Hash cannot be zero' - ); // TODO: not sure this is needed + assert(class_hash.is_non_zero(), 'Class Hash cannot be zero'); starknet::replace_class_syscall(class_hash).unwrap_syscall(); - Upgraded(class_hash); + + // Allowing initializer to be called again. + let mut state: Reinitializable::ContractState = + Reinitializable::unsafe_new_contract_state(); + ReinitializableImpl::reinitialize(ref state); + + // Call initializer on the new version. + syscalls::call_contract_syscall( + info::get_contract_address(), INITIALIZE_SELECTOR, initialize_calldata.span() + ) + .unwrap_syscall(); + Upgraded(class_hash, initialize_calldata); } fn owner(self: @ContractState) -> ContractAddress { @@ -485,48 +561,6 @@ mod Space { } } - #[constructor] - fn constructor( - ref self: ContractState, - _owner: ContractAddress, - _max_voting_duration: u32, - _min_voting_duration: u32, - _voting_delay: u32, - _proposal_validation_strategy: Strategy, - _proposal_validation_strategy_metadata_URI: Array, - _voting_strategies: Array, - _voting_strategies_metadata_URIs: Array>, - _authenticators: Array, - _metadata_URI: Array, - _dao_URI: Array - ) { - //TODO: temporary component syntax - let mut state: Ownable::ContractState = Ownable::unsafe_new_contract_state(); - Ownable::initializer(ref state); - Ownable::transfer_ownership(ref state, _owner); - _set_max_voting_duration(ref self, _max_voting_duration); - _set_min_voting_duration(ref self, _min_voting_duration); - _set_voting_delay(ref self, _voting_delay); - _set_proposal_validation_strategy(ref self, _proposal_validation_strategy.clone()); - _add_voting_strategies(ref self, _voting_strategies.clone()); - _add_authenticators(ref self, _authenticators.clone()); - self._next_proposal_id.write(1_u256); - SpaceCreated( - info::get_contract_address(), - _owner, - _voting_delay, - _min_voting_duration, - _max_voting_duration, - @_proposal_validation_strategy, - @_proposal_validation_strategy_metadata_URI, - @_voting_strategies, - @_voting_strategies_metadata_URIs, - @_authenticators, - @_metadata_URI, - @_dao_URI - ); - } - /// /// Internals /// diff --git a/starknet/src/tests/mocks.cairo b/starknet/src/tests/mocks.cairo index d372cbef..8ceecdbf 100644 --- a/starknet/src/tests/mocks.cairo +++ b/starknet/src/tests/mocks.cairo @@ -1,2 +1,3 @@ mod proposal_validation_always_fail; mod executor; +mod space_v2; diff --git a/starknet/src/tests/mocks/space_v2.cairo b/starknet/src/tests/mocks/space_v2.cairo new file mode 100644 index 00000000..702d1b50 --- /dev/null +++ b/starknet/src/tests/mocks/space_v2.cairo @@ -0,0 +1,30 @@ +#[starknet::interface] +trait ISpaceV2 { + fn initialize(ref self: TContractState, var: felt252); + fn get_var(self: @TContractState) -> felt252; +} + +#[starknet::contract] +mod SpaceV2 { + use super::ISpaceV2; + use sx::utils::reinitializable::Reinitializable; + use sx::utils::ReinitializableImpl; + #[storage] + struct Storage { + _var: felt252 + } + + #[external(v0)] + impl SpaceV2 of ISpaceV2 { + fn initialize(ref self: ContractState, var: felt252) { + // TODO: Temp component syntax + let mut state: Reinitializable::ContractState = + Reinitializable::unsafe_new_contract_state(); + ReinitializableImpl::initialize(ref state); + self._var.write(var); + } + fn get_var(self: @ContractState) -> felt252 { + self._var.read() + } + } +} diff --git a/starknet/src/tests/setup/setup.cairo b/starknet/src/tests/setup/setup.cairo index dd7bccd5..38d4e412 100644 --- a/starknet/src/tests/setup/setup.cairo +++ b/starknet/src/tests/setup/setup.cairo @@ -72,12 +72,12 @@ mod setup { .append(Strategy { address: vanilla_voting_strategy_address, params: array![] }); // Deploy Vanilla Execution Strategy - let mut constructor_calldata = array![]; - quorum.serialize(ref constructor_calldata); + let mut initializer_calldata = array![]; + quorum.serialize(ref initializer_calldata); let (vanilla_execution_strategy_address, _) = deploy_syscall( VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), 0, - constructor_calldata.span(), + initializer_calldata.span(), false ) .unwrap(); @@ -96,7 +96,7 @@ mod setup { } } - fn get_constructor_calldata( + fn get_initialize_calldata( owner: @ContractAddress, min_voting_duration: @u64, max_voting_duration: @u64, @@ -106,20 +106,20 @@ mod setup { authenticators: @Array ) -> Array { // Using empty arrays for all the metadata fields - let mut constructor_calldata = array![]; - constructor_calldata.append((*owner).into()); - constructor_calldata.append((*max_voting_duration).into()); - constructor_calldata.append((*min_voting_duration).into()); - constructor_calldata.append((*voting_delay).into()); - proposal_validation_strategy.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - voting_strategies.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - authenticators.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - - constructor_calldata + let mut initializer_calldata = array![]; + owner.serialize(ref initializer_calldata); + min_voting_duration.serialize(ref initializer_calldata); + max_voting_duration.serialize(ref initializer_calldata); + voting_delay.serialize(ref initializer_calldata); + proposal_validation_strategy.serialize(ref initializer_calldata); + ArrayTrait::::new().serialize(ref initializer_calldata); + voting_strategies.serialize(ref initializer_calldata); + ArrayTrait::::new().serialize(ref initializer_calldata); + authenticators.serialize(ref initializer_calldata); + ArrayTrait::::new().serialize(ref initializer_calldata); + ArrayTrait::::new().serialize(ref initializer_calldata); + + initializer_calldata } fn deploy(config: @Config) -> (IFactoryDispatcher, ISpaceDispatcher) { @@ -133,7 +133,7 @@ mod setup { let factory = IFactoryDispatcher { contract_address: factory_address }; - let constructor_calldata = get_constructor_calldata( + let initializer_calldata = get_initialize_calldata( config.owner, config.min_voting_duration, config.max_voting_duration, @@ -143,7 +143,7 @@ mod setup { config.authenticators ); let space_address = factory - .deploy(space_class_hash, contract_address_salt, constructor_calldata.span()); + .deploy(space_class_hash, contract_address_salt, initializer_calldata.span()); let space = ISpaceDispatcher { contract_address: space_address }; diff --git a/starknet/src/tests/test_factory.cairo b/starknet/src/tests/test_factory.cairo index d6dd87d6..8b798c76 100644 --- a/starknet/src/tests/test_factory.cairo +++ b/starknet/src/tests/test_factory.cairo @@ -10,7 +10,7 @@ mod tests { use sx::types::Strategy; use starknet::ClassHash; - use sx::tests::setup::setup::setup::{setup, get_constructor_calldata, deploy}; + use sx::tests::setup::setup::setup::{setup, get_initialize_calldata, deploy}; #[test] #[available_gas(10000000000)] @@ -39,7 +39,7 @@ mod tests { let contract_address_salt = 0; let config = setup(); - let constructor_calldata = get_constructor_calldata( + let constructor_calldata = get_initialize_calldata( @config.owner, @config.min_voting_duration, @config.max_voting_duration, diff --git a/starknet/src/tests/test_space.cairo b/starknet/src/tests/test_space.cairo index 7ff7556a..1c36f42a 100644 --- a/starknet/src/tests/test_space.cairo +++ b/starknet/src/tests/test_space.cairo @@ -29,18 +29,17 @@ mod tests { use Space::Space as SpaceImpl; - #[test] #[available_gas(100000000)] - fn test_constructor() { + fn test_initialize() { let deployer = contract_address_const::<0xdead>(); testing::set_caller_address(deployer); testing::set_contract_address(deployer); // Space Settings let owner = contract_address_const::<0x123456789>(); - let max_voting_duration = 2_u32; let min_voting_duration = 1_u32; + let max_voting_duration = 2_u32; let voting_delay = 1_u32; // Deploy Vanilla Authenticator @@ -72,30 +71,31 @@ mod tests { ]; // Deploy Space - let mut constructor_calldata = array![ - owner.into(), - max_voting_duration.into(), - min_voting_duration.into(), - voting_delay.into() - ]; - vanilla_proposal_validation_strategy.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - voting_strategies.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - authenticators.serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - ArrayTrait::::new().serialize(ref constructor_calldata); - let (space_address, _) = deploy_syscall( - Space::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_calldata.span(), false + Space::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false ) .unwrap(); let space = ISpaceDispatcher { contract_address: space_address }; + space + .initialize( + owner, + min_voting_duration, + max_voting_duration, + voting_delay, + vanilla_proposal_validation_strategy.clone(), + array![], + voting_strategies, + array![], + authenticators, + array![], + array![] + ); + assert(space.owner() == owner, 'owner incorrect'); - assert(space.max_voting_duration() == max_voting_duration, 'max incorrect'); assert(space.min_voting_duration() == min_voting_duration, 'min incorrect'); + assert(space.max_voting_duration() == max_voting_duration, 'max incorrect'); assert(space.voting_delay() == voting_delay, 'voting delay incorrect'); assert( space.proposal_validation_strategy() == vanilla_proposal_validation_strategy, @@ -103,6 +103,99 @@ mod tests { ); } + #[test] + #[available_gas(100000000)] + #[should_panic(expected: ('Already Initialized', 'ENTRYPOINT_FAILED'))] + fn test_reinitialize() { + let deployer = contract_address_const::<0xdead>(); + + testing::set_caller_address(deployer); + testing::set_contract_address(deployer); + // Space Settings + let owner = contract_address_const::<0x123456789>(); + let min_voting_duration = 1_u32; + let max_voting_duration = 2_u32; + let voting_delay = 1_u32; + + // Deploy Vanilla Authenticator + let (vanilla_authenticator_address, _) = deploy_syscall( + VanillaAuthenticator::TEST_CLASS_HASH.try_into().unwrap(), + 0, + array::ArrayTrait::::new().span(), + false + ) + .unwrap(); + let mut authenticators = ArrayTrait::::new(); + authenticators.append(vanilla_authenticator_address); + + // Deploy Vanilla Proposal Validation Strategy + let (vanilla_proposal_validation_address, _) = deploy_syscall( + VanillaProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + array::ArrayTrait::::new().span(), + false + ) + .unwrap(); + let vanilla_proposal_validation_strategy = StrategyImpl::from_address( + vanilla_proposal_validation_address + ); + + // Deploy Vanilla Voting Strategy + let (vanilla_voting_strategy_address, _) = deploy_syscall( + VanillaVotingStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + array::ArrayTrait::::new().span(), + false + ) + .unwrap(); + let mut voting_strategies = ArrayTrait::::new(); + voting_strategies + .append( + Strategy { + address: vanilla_voting_strategy_address, params: ArrayTrait::::new() + } + ); + + // Deploy Space + let (space_address, _) = deploy_syscall( + Space::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap(); + + let space = ISpaceDispatcher { contract_address: space_address }; + + space + .initialize( + owner, + min_voting_duration, + max_voting_duration, + voting_delay, + vanilla_proposal_validation_strategy.clone(), + array![], + voting_strategies.clone(), + array![], + authenticators.clone(), + array![], + array![] + ); + + // Atempting to call the initialize function again + space + .initialize( + owner, + min_voting_duration, + max_voting_duration, + voting_delay, + vanilla_proposal_validation_strategy, + array![], + voting_strategies, + array![], + authenticators, + array![], + array![] + ); + } + #[test] #[available_gas(10000000000)] fn test__propose_update_vote_execute() { diff --git a/starknet/src/tests/test_upgrade.cairo b/starknet/src/tests/test_upgrade.cairo index 4cec393b..fe321210 100644 --- a/starknet/src/tests/test_upgrade.cairo +++ b/starknet/src/tests/test_upgrade.cairo @@ -21,7 +21,6 @@ mod tests { use sx::execution_strategies::vanilla::VanillaExecutionStrategy; use sx::voting_strategies::vanilla::VanillaVotingStrategy; use sx::proposal_validation_strategies::vanilla::VanillaProposalValidationStrategy; - use sx::tests::mocks::proposal_validation_always_fail::AlwaysFailProposalValidationStrategy; use sx::types::{ UserAddress, Strategy, IndexedStrategy, Choice, FinalizationStatus, Proposal, UpdateSettingsCalldataImpl @@ -34,6 +33,7 @@ mod tests { use sx::tests::mocks::executor::{ ExecutorExecutionStrategy, ExecutorExecutionStrategy::Transaction }; + use sx::tests::mocks::space_v2::{SpaceV2, ISpaceV2Dispatcher, ISpaceV2DispatcherTrait}; use starknet::ClassHash; use Space::Space as SpaceImpl; @@ -44,24 +44,17 @@ mod tests { let config = setup(); let (factory, space) = deploy(@config); - // New implementation is not a proposer space but a random contract (here, a proposal validation strategy). - let new_implem = AlwaysFailProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(); + let new_implem = SpaceV2::TEST_CLASS_HASH.try_into().unwrap(); testing::set_contract_address(config.owner); // Now upgrade the implementation - space.upgrade(new_implem); + space.upgrade(new_implem, array![7]); // Ensure it works - let new_space = IProposalValidationStrategyDispatcher { - contract_address: space.contract_address - }; + let new_space = ISpaceV2Dispatcher { contract_address: space.contract_address }; - let author = UserAddress::Starknet(contract_address_const::<0x7777777777>()); - let params = array![]; - let user_params = array![]; - let res = new_space.validate(author, params, user_params); - assert(res == false, 'Strategy did not return false'); + assert(new_space.get_var() == 7, 'New implementation did not work'); } #[test] @@ -72,9 +65,7 @@ mod tests { let proposal_id = space.next_proposal_id(); // New implementation is not a proposer space but a random contract (here, a proposal validation strategy). - let new_implem: ClassHash = AlwaysFailProposalValidationStrategy::TEST_CLASS_HASH - .try_into() - .unwrap(); + let new_implem: ClassHash = SpaceV2::TEST_CLASS_HASH.try_into().unwrap(); let (execution_contract_address, _) = deploy_syscall( ExecutorExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), @@ -92,6 +83,7 @@ mod tests { let selector = 0xf2f7c15cbe06c8d94597cd91fd7f3369eae842359235712def5584f8d270cd; let mut tx_calldata = array![]; new_implem.serialize(ref tx_calldata); + array![7].serialize(ref tx_calldata); // initialize calldata let tx = Transaction { target: space.contract_address, selector, data: tx_calldata, }; let mut execution_params = array![]; @@ -117,14 +109,23 @@ mod tests { space.execute(proposal_id, execution_params); // Ensure it works - let new_space = IProposalValidationStrategyDispatcher { - contract_address: space.contract_address - }; + let new_space = ISpaceV2Dispatcher { contract_address: space.contract_address }; + + assert(new_space.get_var() == 7, 'New implementation did not work'); + } + + #[test] + #[available_gas(10000000000)] + #[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] + fn test_upgrade_unauthorized() { + let config = setup(); + let (factory, space) = deploy(@config); + + let new_implem = SpaceV2::TEST_CLASS_HASH.try_into().unwrap(); + + testing::set_contract_address(contract_address_const::<0xdead>()); - let author = UserAddress::Starknet(contract_address_const::<0x7777777777>()); - let params = array![]; - let user_params = array![]; - let res = new_space.validate(author, params, user_params); - assert(res == false, 'Strategy did not return false'); + // Upgrade should fail as caller is not owner + space.upgrade(new_implem, array![7]); } } diff --git a/starknet/src/utils.cairo b/starknet/src/utils.cairo index 29bf44f6..9cd3b565 100644 --- a/starknet/src/utils.cairo +++ b/starknet/src/utils.cairo @@ -17,3 +17,7 @@ mod single_slot_proof; mod signatures; mod stark_eip712; + +// TODO: proper component syntax will have a better way to do this +mod reinitializable; +use reinitializable::Reinitializable::Reinitializable as ReinitializableImpl; diff --git a/starknet/src/utils/constants.cairo b/starknet/src/utils/constants.cairo index 42ab1ad9..2938e212 100644 --- a/starknet/src/utils/constants.cairo +++ b/starknet/src/utils/constants.cairo @@ -1,3 +1,5 @@ +const INITIALIZE_SELECTOR: felt252 = + 0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463; const PROPOSE_SELECTOR: felt252 = 0x1bfd596ae442867ef71ca523061610682af8b00fc2738329422f4ad8d220b81; const VOTE_SELECTOR: felt252 = 0x132bdf85fc8aa10ac3c22f02317f8f53d4b4f52235ed1eabb3a4cbbe08b5c41; const UPDATE_PROPOSAL_SELECTOR: felt252 = diff --git a/starknet/src/utils/reinitializable.cairo b/starknet/src/utils/reinitializable.cairo new file mode 100644 index 00000000..d044d172 --- /dev/null +++ b/starknet/src/utils/reinitializable.cairo @@ -0,0 +1,40 @@ +#[starknet::interface] +trait IReinitializable { + fn initialize(ref self: TContractState); + fn reinitialize(ref self: TContractState); + fn initialized(self: @TContractState); + fn not_initialized(self: @TContractState); +} + +#[starknet::contract] +mod Reinitializable { + use super::IReinitializable; + use starknet::ContractAddress; + use starknet::syscalls::call_contract_syscall; + use core::array::{ArrayTrait, SpanTrait}; + + #[storage] + struct Storage { + _initialized: bool + } + + #[external(v0)] + impl Reinitializable of IReinitializable { + fn initialize(ref self: ContractState) { + self.not_initialized(); + self._initialized.write(true); + } + + fn reinitialize(ref self: ContractState) { + self._initialized.write(false); + } + + fn initialized(self: @ContractState) { + assert(self._initialized.read() == true, 'Not Initialized'); + } + + fn not_initialized(self: @ContractState) { + assert(self._initialized.read() == false, 'Already Initialized'); + } + } +}