From 44f6e7ad58c5f568736cf8253e1d33b70cfe77e6 Mon Sep 17 00:00:00 2001 From: Turan Ege Caner Date: Wed, 15 Jan 2025 14:46:15 +0800 Subject: [PATCH 1/5] Implementation Authority --- Scarb.lock | 1 + crates/factory/Scarb.toml | 1 + .../src/iimplementation_authority.cairo | 62 ++++ .../src/implementation_authority.cairo | 337 ++++++++++++++++++ crates/factory/src/lib.cairo | 2 + 5 files changed, 403 insertions(+) create mode 100644 crates/factory/src/iimplementation_authority.cairo create mode 100644 crates/factory/src/implementation_authority.cairo diff --git a/Scarb.lock b/Scarb.lock index 0294194..fa6dd2f 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -50,6 +50,7 @@ dependencies = [ "onchain_id_starknet", "openzeppelin_access", "openzeppelin_token", + "openzeppelin_upgrades", "registry", "roles", "snforge_std", diff --git a/crates/factory/Scarb.toml b/crates/factory/Scarb.toml index 2c3f03a..783e3ce 100644 --- a/crates/factory/Scarb.toml +++ b/crates/factory/Scarb.toml @@ -7,6 +7,7 @@ edition = "2024_07" starknet.workspace = true openzeppelin_access.workspace = true openzeppelin_token.workspace = true +openzeppelin_upgrades.workspace = true roles = { path = "../roles"} compliance = { path = "../compliance"} registry = { path = "../registry"} diff --git a/crates/factory/src/iimplementation_authority.cairo b/crates/factory/src/iimplementation_authority.cairo new file mode 100644 index 0000000..90b5b12 --- /dev/null +++ b/crates/factory/src/iimplementation_authority.cairo @@ -0,0 +1,62 @@ +use core::starknet::storage_access::StorePacking; +use starknet::{ClassHash, ContractAddress}; + +#[derive(Drop, Copy, Serde, PartialEq)] +pub struct Version { + pub major: u8, + pub minor: u8, + pub patch: u8, +} + +const SHIFT_8: u32 = 0x100; +const SHIFT_16: u32 = 0x10000; +const MASK_8: u32 = 0xff; + +pub impl VersionStorePacking of StorePacking { + fn pack(value: Version) -> u32 { + value.major.into() + (value.minor.into() * SHIFT_8) + (value.patch.into() * SHIFT_16) + } + + fn unpack(value: u32) -> Version { + let major = value & MASK_8; + let minor = (value / SHIFT_8) & MASK_8; + let patch = (value / SHIFT_16) & MASK_8; + + Version { + major: major.try_into().unwrap(), + minor: minor.try_into().unwrap(), + patch: patch.try_into().unwrap(), + } + } +} + +#[derive(Drop, Copy, Serde, starknet::Store)] +pub struct TREXImplementations { + pub token_implementation: ClassHash, + pub ctr_implementation: ClassHash, + pub ir_implementation: ClassHash, + pub irs_implementation: ClassHash, + pub tir_implementation: ClassHash, + pub mc_implementation: ClassHash, +} + +#[starknet::interface] +pub trait IImplementationAuthority { + fn add_trex_version( + ref self: TContractState, version: Version, implementations: TREXImplementations, + ); + fn add_and_use_trex_version( + ref self: TContractState, version: Version, implementations: TREXImplementations, + ); + fn use_trex_version(ref self: TContractState, version: Version); + fn upgrade_trex_suite(ref self: TContractState, token: ContractAddress, version: Version); + fn get_current_version(self: @TContractState) -> Version; + fn get_implementations(self: @TContractState, version: Version) -> TREXImplementations; + fn get_current_implementations(self: @TContractState) -> TREXImplementations; + fn get_token_implementation(self: @TContractState) -> ClassHash; + fn get_ctr_implementation(self: @TContractState) -> ClassHash; + fn get_ir_implementation(self: @TContractState) -> ClassHash; + fn get_irs_implementation(self: @TContractState) -> ClassHash; + fn get_tir_implementation(self: @TContractState) -> ClassHash; + fn get_mc_implementation(self: @TContractState) -> ClassHash; +} diff --git a/crates/factory/src/implementation_authority.cairo b/crates/factory/src/implementation_authority.cairo new file mode 100644 index 0000000..6eb3ca1 --- /dev/null +++ b/crates/factory/src/implementation_authority.cairo @@ -0,0 +1,337 @@ +#[starknet::contract] +mod ImplementationAuthority { + use core::num::traits::Zero; + use crate::iimplementation_authority::{ + IImplementationAuthority, TREXImplementations, Version, VersionStorePacking, + }; + use openzeppelin_access::ownable::{ + OwnableComponent, interface::{IOwnableDispatcher, IOwnableDispatcherTrait}, + }; + use openzeppelin_upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; + use registry::interface::iidentity_registry::{IIdentityRegistryDispatcherTrait}; + use starknet::{ + ClassHash, ContractAddress, + storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess}, + }; + use token::itoken::{ITokenDispatcher, ITokenDispatcherTrait}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + current_version: Version, + ///TODO: Consider making this enumerable to enumerate all available implementations + implementations: Map, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + + #[starknet::storage_node] + pub struct TREXImplementationsStore { + token_implementation: ClassHash, + ctr_implementation: ClassHash, + ir_implementation: ClassHash, + irs_implementation: ClassHash, + tir_implementation: ClassHash, + mc_implementation: ClassHash, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + TREXVersionAdded: TREXVersionAdded, + VersionUpdated: VersionUpdated, + TokenSuiteUpgraded: TokenSuiteUpgraded, + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + #[derive(Drop, starknet::Event)] + struct TREXVersionAdded { + #[key] + version: Version, + #[key] + implementations: TREXImplementations, + } + + #[derive(Drop, starknet::Event)] + struct VersionUpdated { + #[key] + version: Version, + } + + #[derive(Drop, starknet::Event)] + struct TokenSuiteUpgraded { + #[key] + version: Version, + #[key] + token: ContractAddress, + } + + pub mod Errors { + pub const VERSION_ALREADY_EXISTS: felt252 = 'Version already exists'; + pub const VERSION_DOES_NOT_EXISTS: felt252 = 'Version does not exists'; + pub const VERSION_ALREADY_IN_USE: felt252 = 'Version already in use'; + pub const INVALID_IMPLEMENTATION: felt252 = 'Invalid implementation: Zero'; + pub const CALLER_NOT_OWNER_OF_SUITE: felt252 = 'Caller is not owner of suite'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + version: Version, + implementations: TREXImplementations, + owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self._add_trex_version(version, implementations); + self._use_trex_version(version); + } + + #[abi(embed_v0)] + impl ImplementationAuthorityImpl of IImplementationAuthority { + fn add_trex_version( + ref self: ContractState, version: Version, implementations: TREXImplementations, + ) { + self.ownable.assert_only_owner(); + self._add_trex_version(version, implementations); + } + + fn add_and_use_trex_version( + ref self: ContractState, version: Version, implementations: TREXImplementations, + ) { + self.ownable.assert_only_owner(); + self._add_trex_version(version, implementations); + self._use_trex_version(version); + } + + fn use_trex_version(ref self: ContractState, version: Version) { + self.ownable.assert_only_owner(); + self._use_trex_version(version); + } + + fn upgrade_trex_suite(ref self: ContractState, token: ContractAddress, version: Version) { + let caller = starknet::get_caller_address(); + let token_dispatcher = ITokenDispatcher { contract_address: token }; + let identity_registry = token_dispatcher.identity_registry(); + let modular_compliance = token_dispatcher.compliance(); + let identity_registry_storage = identity_registry.identity_storage(); + let trusted_issuers_registry = identity_registry.issuers_registry(); + let claim_topics_registry = identity_registry.topics_registry(); + assert( + IOwnableDispatcher { contract_address: token }.owner() == caller + && IOwnableDispatcher { contract_address: identity_registry.contract_address } + .owner() == caller + && IOwnableDispatcher { + contract_address: identity_registry_storage.contract_address, + } + .owner() == caller + && IOwnableDispatcher { contract_address: modular_compliance.contract_address } + .owner() == caller + && IOwnableDispatcher { + contract_address: trusted_issuers_registry.contract_address, + } + .owner() == caller + && IOwnableDispatcher { + contract_address: claim_topics_registry.contract_address, + } + .owner() == caller, + Errors::CALLER_NOT_OWNER_OF_SUITE, + ); + let implementations_storage = self + .implementations + .entry(VersionStorePacking::pack(version)) + .deref(); + + let token_implementation = implementations_storage.token_implementation.read(); + assert(token_implementation.is_non_zero(), Errors::VERSION_DOES_NOT_EXISTS); + if starknet::syscalls::get_class_hash_at_syscall(token) + .unwrap() != token_implementation { + IUpgradeableDispatcher { contract_address: token }.upgrade(token_implementation); + } + + let ir_implementation = implementations_storage.ir_implementation.read(); + if starknet::syscalls::get_class_hash_at_syscall(identity_registry.contract_address) + .unwrap() == ir_implementation { + IUpgradeableDispatcher { contract_address: identity_registry.contract_address } + .upgrade(ir_implementation); + } + + let irs_implementation = implementations_storage.irs_implementation.read(); + if starknet::syscalls::get_class_hash_at_syscall( + identity_registry_storage.contract_address, + ) + .unwrap() == irs_implementation { + IUpgradeableDispatcher { + contract_address: identity_registry_storage.contract_address, + } + .upgrade(irs_implementation); + } + + let ctr_implementation = implementations_storage.ctr_implementation.read(); + if starknet::syscalls::get_class_hash_at_syscall(claim_topics_registry.contract_address) + .unwrap() == ctr_implementation { + IUpgradeableDispatcher { contract_address: claim_topics_registry.contract_address } + .upgrade(ctr_implementation); + } + + let tir_implementation = implementations_storage.tir_implementation.read(); + if starknet::syscalls::get_class_hash_at_syscall( + trusted_issuers_registry.contract_address, + ) + .unwrap() == tir_implementation { + IUpgradeableDispatcher { + contract_address: trusted_issuers_registry.contract_address, + } + .upgrade(tir_implementation); + } + + let mc_implementation = implementations_storage.mc_implementation.read(); + if starknet::syscalls::get_class_hash_at_syscall(modular_compliance.contract_address) + .unwrap() == mc_implementation { + IUpgradeableDispatcher { contract_address: modular_compliance.contract_address } + .upgrade(mc_implementation); + } + + self.emit(TokenSuiteUpgraded { version, token }); + } + + fn get_current_version(self: @ContractState) -> Version { + self.current_version.read() + } + + fn get_current_implementations(self: @ContractState) -> TREXImplementations { + let implementations_storage = self + .implementations + .entry(VersionStorePacking::pack(self.current_version.read())) + .deref(); + TREXImplementations { + token_implementation: implementations_storage.token_implementation.read(), + ctr_implementation: implementations_storage.ctr_implementation.read(), + ir_implementation: implementations_storage.ir_implementation.read(), + irs_implementation: implementations_storage.irs_implementation.read(), + tir_implementation: implementations_storage.tir_implementation.read(), + mc_implementation: implementations_storage.mc_implementation.read(), + } + } + + fn get_implementations(self: @ContractState, version: Version) -> TREXImplementations { + let implementations_storage = self + .implementations + .entry(VersionStorePacking::pack(version)) + .deref(); + TREXImplementations { + token_implementation: implementations_storage.token_implementation.read(), + ctr_implementation: implementations_storage.ctr_implementation.read(), + ir_implementation: implementations_storage.ir_implementation.read(), + irs_implementation: implementations_storage.irs_implementation.read(), + tir_implementation: implementations_storage.tir_implementation.read(), + mc_implementation: implementations_storage.mc_implementation.read(), + } + } + + fn get_token_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .token_implementation + .read() + } + + fn get_ctr_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .ctr_implementation + .read() + } + + fn get_ir_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .ir_implementation + .read() + } + + fn get_irs_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .irs_implementation + .read() + } + + fn get_tir_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .tir_implementation + .read() + } + + fn get_mc_implementation(self: @ContractState) -> ClassHash { + let current_version = self.current_version.read(); + self + .implementations + .entry(VersionStorePacking::pack(current_version)) + .mc_implementation + .read() + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _add_trex_version( + ref self: ContractState, version: Version, implementations: TREXImplementations, + ) { + let implementation_storage = self + .implementations + .entry(VersionStorePacking::pack(version)); + assert( + implementation_storage.token_implementation.read().is_zero(), + Errors::VERSION_ALREADY_EXISTS, + ); + assert( + implementations.ctr_implementation.is_non_zero() + && implementations.ir_implementation.is_non_zero() + && implementations.irs_implementation.is_non_zero() + && implementations.tir_implementation.is_non_zero() + && implementations.mc_implementation.is_non_zero() + && implementations.token_implementation.is_non_zero(), + Errors::INVALID_IMPLEMENTATION, + ); + implementation_storage.token_implementation.write(implementations.token_implementation); + implementation_storage.ctr_implementation.write(implementations.ctr_implementation); + implementation_storage.ir_implementation.write(implementations.ir_implementation); + implementation_storage.irs_implementation.write(implementations.irs_implementation); + implementation_storage.tir_implementation.write(implementations.tir_implementation); + implementation_storage.mc_implementation.write(implementations.mc_implementation); + self.emit(TREXVersionAdded { version, implementations }); + } + + fn _use_trex_version(ref self: ContractState, version: Version) { + assert( + self + .implementations + .entry(VersionStorePacking::pack(version)) + .token_implementation + .read() + .is_non_zero(), + Errors::VERSION_DOES_NOT_EXISTS, + ); + assert(self.current_version.read() != version, Errors::VERSION_ALREADY_IN_USE); + self.current_version.write(version); + self.emit(VersionUpdated { version }); + } + } +} diff --git a/crates/factory/src/lib.cairo b/crates/factory/src/lib.cairo index dc2192d..365482f 100644 --- a/crates/factory/src/lib.cairo +++ b/crates/factory/src/lib.cairo @@ -1,3 +1,5 @@ +pub mod iimplementation_authority; +pub mod implementation_authority; pub mod itrex_factory; pub mod itrex_gateway; pub mod trex_factory; From c120f134675aa8778c4c975d9c26d6154fda5f0c Mon Sep 17 00:00:00 2001 From: Turan Ege Caner Date: Wed, 15 Jan 2025 18:34:22 +0800 Subject: [PATCH 2/5] implementation authority propogations --- .../compliance/src/modular_compliance.cairo | 14 +- .../tests/modular_compliance_test.cairo | 7 +- crates/factory/src/itrex_factory.cairo | 14 +- crates/factory/src/trex_factory.cairo | 264 ++++++------------ .../registry/src/claim_topics_registry.cairo | 22 +- crates/registry/src/identity_registry.cairo | 12 +- .../src/identity_registry_storage.cairo | 22 +- .../src/trusted_issuers_registry.cairo | 22 +- .../tests/claim_topics_registry_test.cairo | 7 +- .../identity_registry_storage_test.cairo | 7 +- .../tests/identity_registry_test.cairo | 22 +- .../tests/trusted_issuers_registry_test.cairo | 7 +- crates/token/src/token.cairo | 10 +- 13 files changed, 215 insertions(+), 215 deletions(-) diff --git a/crates/compliance/src/modular_compliance.cairo b/crates/compliance/src/modular_compliance.cairo index 001d47c..6743bde 100644 --- a/crates/compliance/src/modular_compliance.cairo +++ b/crates/compliance/src/modular_compliance.cairo @@ -34,6 +34,7 @@ pub mod ModularCompliance { modules: StorageArrayContractAddress, /// Mapping of module binding status module_bound: Map, + implementation_authority: ContractAddress, #[substorage(v0)] upgrades: UpgradeableComponent::Storage, #[substorage(v0)] @@ -96,6 +97,7 @@ pub mod ModularCompliance { pub const ZERO_ADDRESS: felt252 = 'Zero address'; pub const NO_VALUE_TRANSFER: felt252 = 'No value transfer'; pub const ONLY_BOUND_TOKEN: felt252 = 'Only token bound can call'; + pub const CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY: felt252 = 'Caller is not IA'; } #[abi(embed_v0)] @@ -108,17 +110,23 @@ pub mod ModularCompliance { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + Errors::CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY, + ); self.upgrades.upgrade(new_class_hash); } } #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { + fn constructor( + ref self: ContractState, implementation_authority: ContractAddress, owner: ContractAddress, + ) { self.ownable.initializer(owner); + self.implementation_authority.write(implementation_authority); } #[abi(embed_v0)] diff --git a/crates/compliance/tests/modular_compliance_test.cairo b/crates/compliance/tests/modular_compliance_test.cairo index dcf5ead..2b01473 100644 --- a/crates/compliance/tests/modular_compliance_test.cairo +++ b/crates/compliance/tests/modular_compliance_test.cairo @@ -15,7 +15,12 @@ pub struct Setup { pub fn setup() -> Setup { let modular_compliance_contract = declare("ModularCompliance").unwrap().contract_class(); let (mc_address, _) = modular_compliance_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); Setup { diff --git a/crates/factory/src/itrex_factory.cairo b/crates/factory/src/itrex_factory.cairo index e45f168..2ebac86 100644 --- a/crates/factory/src/itrex_factory.cairo +++ b/crates/factory/src/itrex_factory.cairo @@ -1,4 +1,4 @@ -use starknet::{ClassHash, ContractAddress}; +use starknet::ContractAddress; #[derive(Serde, Drop, Clone)] pub struct TokenDetails { @@ -29,14 +29,10 @@ pub struct ClaimDetails { #[starknet::interface] pub trait ITREXFactory { - //fn set_implementation_authority(ref self: TContractState, implementation: ContractAddress); + fn set_implementation_authority( + ref self: TContractState, implementation_authority: ContractAddress, + ); fn set_id_factory(ref self: TContractState, id_factory: ContractAddress); - fn set_irs_implementation(ref self: TContractState, implementation: ClassHash); - fn set_ir_implementation(ref self: TContractState, implementation: ClassHash); - fn set_tir_implementation(ref self: TContractState, implementation: ClassHash); - fn set_ctr_implementation(ref self: TContractState, implementation: ClassHash); - fn set_mc_implementation(ref self: TContractState, implementation: ClassHash); - fn set_token_implementation(ref self: TContractState, implementation: ClassHash); fn deploy_TREX_suite( ref self: TContractState, salt: felt252, @@ -46,7 +42,7 @@ pub trait ITREXFactory { fn recover_contract_ownership( ref self: TContractState, contract: ContractAddress, new_owner: ContractAddress, ); - //fn get_implementation_authority(self: @TContractState) -> ContractAddress; + fn get_implementation_authority(self: @TContractState) -> ContractAddress; fn get_id_factory(self: @TContractState) -> ContractAddress; fn get_token(self: @TContractState, salt: felt252) -> ContractAddress; } diff --git a/crates/factory/src/trex_factory.cairo b/crates/factory/src/trex_factory.cairo index 5e86e39..731be62 100644 --- a/crates/factory/src/trex_factory.cairo +++ b/crates/factory/src/trex_factory.cairo @@ -4,7 +4,12 @@ pub mod TREXFactory { IModularComplianceDispatcher, IModularComplianceDispatcherTrait, }; use core::num::traits::Zero; - use factory::itrex_factory::{ClaimDetails, ITREXFactory, TokenDetails}; + use crate::{ + iimplementation_authority::{ + IImplementationAuthorityDispatcher, IImplementationAuthorityDispatcherTrait, + }, + itrex_factory::{ClaimDetails, ITREXFactory, TokenDetails}, + }; use onchain_id_starknet::factory::iid_factory::{ IIdFactoryDispatcher, IIdFactoryDispatcherTrait, }; @@ -38,16 +43,10 @@ pub mod TREXFactory { #[storage] struct Storage { - //implementation_authority: ContractAddress, + implementation_authority: ContractAddress, id_factory: ContractAddress, /// salt to token token_deployed: Map, - tir_implementation_class_hash: ClassHash, - ctr_implementation_class_hash: ClassHash, - irs_implementation_class_hash: ClassHash, - ir_implementation_class_hash: ClassHash, - mc_implementation_class_hash: ClassHash, - token_implementation_class_hash: ClassHash, #[substorage(v0)] ownable: OwnableComponent::Storage, } @@ -57,13 +56,7 @@ pub mod TREXFactory { pub enum Event { Deployed: Deployed, IdFactorySet: IdFactorySet, - //ImplementationAuthoritySet: ImplementationAuthoritySet, - IdentityRegistryImplementationUpdated: IdentityRegistryImplementationUpdated, - IdentityRegistryStorageImplementationUpdated: IdentityRegistryStorageImplementationUpdated, - TrustedIssuersRegistryImplementationUpdated: TrustedIssuersRegistryImplementationUpdated, - ClaimTopicsRegistryImplementationUpdated: ClaimTopicsRegistryImplementationUpdated, - ModularComplianceImplementationUpdated: ModularComplianceImplementationUpdated, - TokenImplementationUpdated: TokenImplementationUpdated, + ImplementationAuthoritySet: ImplementationAuthoritySet, TREXSuiteDeployed: TREXSuiteDeployed, #[flat] OwnableEvent: OwnableComponent::Event, @@ -82,45 +75,10 @@ pub mod TREXFactory { } #[derive(Drop, starknet::Event)] - struct IdentityRegistryImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, - } - - #[derive(Drop, starknet::Event)] - struct IdentityRegistryStorageImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, - } - - #[derive(Drop, starknet::Event)] - struct TrustedIssuersRegistryImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, - } - - #[derive(Drop, starknet::Event)] - struct ClaimTopicsRegistryImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, + pub struct ImplementationAuthoritySet { + pub implementation_authority: ContractAddress, } - #[derive(Drop, starknet::Event)] - struct ModularComplianceImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, - } - - #[derive(Drop, starknet::Event)] - struct TokenImplementationUpdated { - pub old_class_hash: ClassHash, - pub new_class_hash: ClassHash, - } - //#[derive(Drop, starknet::Event)] - //pub struct ImplementationAuthoritySet { - // pub implementation_authority: ContractAddress, - //} - #[derive(Drop, starknet::Event)] pub struct TREXSuiteDeployed { pub token: ContractAddress, @@ -133,14 +91,9 @@ pub mod TREXFactory { } pub mod Errors { - pub const TIR_CLASS_HASH_ZERO: felt252 = 'TIR: ClassHash Zero'; - pub const CTR_CLASS_HASH_ZERO: felt252 = 'CTR: ClassHash Zero'; - pub const IRS_CLASS_HASH_ZERO: felt252 = 'IRS: ClassHash Zero'; - pub const IR_CLASS_HASH_ZERO: felt252 = 'IR: ClassHash Zero'; - pub const MC_CLASS_HASH_ZERO: felt252 = 'MC: ClassHash Zero'; - pub const TOKEN_CLASS_HASH_ZERO: felt252 = 'Token: ClassHash Zero'; pub const ID_FACTORY_ZERO_ADDRESS: felt252 = 'id_factory: Zero Address'; pub const OWNER_ZERO_ADDRESS: felt252 = 'owner: Zero Address'; + pub const IMPLEMENTATION_AUTHORITY_ZERO_ADDRESS: felt252 = 'IA: Zero Address'; pub const TOKEN_ALREADY_DEPLOYED: felt252 = 'Token already deployed'; pub const INVALID_CLAIM_PATTERN: felt252 = 'Invalid claim pattern'; pub const INVALID_COMPLIANCE_PATTERN: felt252 = 'Invalid compliance pattern'; @@ -148,45 +101,35 @@ pub mod TREXFactory { pub const MAX_CLAIM_TOPICS: felt252 = 'Max 5 topics at deployment'; pub const MAX_COMPLIANCE_MODULES: felt252 = 'Max 30 compliance at deployment'; pub const MAX_AGENTS: felt252 = 'Max 5 agents at deployment'; + pub const INVALID_IMPLEMENTATION: felt252 = 'Invalid implementation: Zero'; } #[constructor] fn constructor( - ref self: ContractState, //implementation_authority: ContractAddress, + ref self: ContractState, + implementation_authority: ContractAddress, id_factory: ContractAddress, - tir_implementation: ClassHash, - ctr_implementation: ClassHash, - irs_implementation: ClassHash, - ir_implementation: ClassHash, - mc_implementation: ClassHash, - token_implementation: ClassHash, owner: ContractAddress, ) { assert(id_factory.is_non_zero(), Errors::ID_FACTORY_ZERO_ADDRESS); assert(owner.is_non_zero(), Errors::OWNER_ZERO_ADDRESS); - assert(tir_implementation.is_non_zero(), Errors::TIR_CLASS_HASH_ZERO); - assert(ctr_implementation.is_non_zero(), Errors::CTR_CLASS_HASH_ZERO); - assert(irs_implementation.is_non_zero(), Errors::IRS_CLASS_HASH_ZERO); - assert(ir_implementation.is_non_zero(), Errors::IR_CLASS_HASH_ZERO); - assert(mc_implementation.is_non_zero(), Errors::MC_CLASS_HASH_ZERO); - assert(token_implementation.is_non_zero(), Errors::TOKEN_CLASS_HASH_ZERO); /// Set id factory self.id_factory.write(id_factory); self.emit(IdFactorySet { id_factory }); + self._set_implementation_authority(implementation_authority); /// Init ownable self.ownable.initializer(owner); - /// Set implementations - self.tir_implementation_class_hash.write(tir_implementation); - self.ctr_implementation_class_hash.write(ctr_implementation); - self.irs_implementation_class_hash.write(irs_implementation); - self.ir_implementation_class_hash.write(ir_implementation); - self.mc_implementation_class_hash.write(mc_implementation); - self.token_implementation_class_hash.write(token_implementation); } #[abi(embed_v0)] impl TREXFactoryImpl of ITREXFactory { - //fn set_implementation_authority(ref self: ContractState, implementation: ContractAddress); + fn set_implementation_authority( + ref self: ContractState, implementation_authority: ContractAddress, + ) { + self.ownable.assert_only_owner(); + self._set_implementation_authority(implementation_authority); + } + fn set_id_factory(ref self: ContractState, id_factory: ContractAddress) { self.ownable.assert_only_owner(); assert(id_factory.is_non_zero(), Errors::ID_FACTORY_ZERO_ADDRESS); @@ -194,82 +137,6 @@ pub mod TREXFactory { self.emit(IdFactorySet { id_factory }); } - fn set_irs_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::IRS_CLASS_HASH_ZERO); - let old_class_hash = self.irs_implementation_class_hash.read(); - self.irs_implementation_class_hash.write(implementation); - self - .emit( - IdentityRegistryStorageImplementationUpdated { - old_class_hash, new_class_hash: implementation, - }, - ); - } - - fn set_ir_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::IR_CLASS_HASH_ZERO); - let old_class_hash = self.ir_implementation_class_hash.read(); - self.ir_implementation_class_hash.write(implementation); - self - .emit( - IdentityRegistryImplementationUpdated { - old_class_hash, new_class_hash: implementation, - }, - ); - } - - fn set_tir_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::TIR_CLASS_HASH_ZERO); - let old_class_hash = self.tir_implementation_class_hash.read(); - self.tir_implementation_class_hash.write(implementation); - self - .emit( - TrustedIssuersRegistryImplementationUpdated { - old_class_hash, new_class_hash: implementation, - }, - ); - } - - fn set_ctr_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::CTR_CLASS_HASH_ZERO); - let old_class_hash = self.ctr_implementation_class_hash.read(); - self.ctr_implementation_class_hash.write(implementation); - self - .emit( - ClaimTopicsRegistryImplementationUpdated { - old_class_hash, new_class_hash: implementation, - }, - ); - } - - fn set_mc_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::MC_CLASS_HASH_ZERO); - let old_class_hash = self.mc_implementation_class_hash.read(); - self.mc_implementation_class_hash.write(implementation); - self - .emit( - ModularComplianceImplementationUpdated { - old_class_hash, new_class_hash: implementation, - }, - ); - } - - fn set_token_implementation(ref self: ContractState, implementation: ClassHash) { - self.ownable.assert_only_owner(); - assert(implementation.is_non_zero(), Errors::TOKEN_CLASS_HASH_ZERO); - let old_class_hash = self.token_implementation_class_hash.read(); - self.token_implementation_class_hash.write(implementation); - self - .emit( - TokenImplementationUpdated { old_class_hash, new_class_hash: implementation }, - ); - } - fn deploy_TREX_suite( ref self: ContractState, salt: felt252, @@ -294,22 +161,39 @@ pub mod TREXFactory { token_details.compliance_modules.len() >= token_details.compliance_settings.len(), Errors::INVALID_COMPLIANCE_PATTERN, ); - let tir = ITrustedIssuersRegistryDispatcher { contract_address: self.deploy_TIR(salt) }; - let ctr = IClaimTopicsRegistryDispatcher { contract_address: self.deploy_CTR(salt) }; - let mc = IModularComplianceDispatcher { contract_address: self.deploy_MC(salt) }; + let implementations = IImplementationAuthorityDispatcher { + contract_address: self.implementation_authority.read(), + } + .get_current_implementations(); + let tir = ITrustedIssuersRegistryDispatcher { + contract_address: self.deploy_TIR(implementations.tir_implementation, salt), + }; + let ctr = IClaimTopicsRegistryDispatcher { + contract_address: self.deploy_CTR(implementations.ctr_implementation, salt), + }; + let mc = IModularComplianceDispatcher { + contract_address: self.deploy_MC(implementations.mc_implementation, salt), + }; let irs = if token_details.irs.is_zero() { - IIdentityRegistryStorageDispatcher { contract_address: self.deploy_IRS(salt) } + IIdentityRegistryStorageDispatcher { + contract_address: self.deploy_IRS(implementations.irs_implementation, salt), + } } else { IIdentityRegistryStorageDispatcher { contract_address: token_details.irs } }; let ir = IIdentityRegistryDispatcher { contract_address: self .deploy_IR( - salt, tir.contract_address, ctr.contract_address, irs.contract_address, + implementations.ir_implementation, + salt, + tir.contract_address, + ctr.contract_address, + irs.contract_address, ), }; let token_address = self .deploy_token( + implementations.token_implementation, salt, ir.contract_address, mc.contract_address, @@ -399,7 +283,10 @@ pub mod TREXFactory { IOwnableDispatcher { contract_address: contract }.transfer_ownership(new_owner); } - //fn get_implementation_authority(self: @ContractState) -> ContractAddress; + + fn get_implementation_authority(self: @ContractState) -> ContractAddress { + self.implementation_authority.read() + } fn get_id_factory(self: @ContractState) -> ContractAddress { self.id_factory.read() @@ -426,44 +313,53 @@ pub mod TREXFactory { deployed_address } - fn deploy_TIR(ref self: ContractState, salt: felt252) -> ContractAddress { + fn deploy_TIR( + ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ) -> ContractAddress { self .deploy( salt, - self.tir_implementation_class_hash.read(), + implementation_class_hash, [starknet::get_contract_address().into()].span(), ) } - fn deploy_CTR(ref self: ContractState, salt: felt252) -> ContractAddress { + fn deploy_CTR( + ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ) -> ContractAddress { self .deploy( salt, - self.ctr_implementation_class_hash.read(), + implementation_class_hash, [starknet::get_contract_address().into()].span(), ) } - fn deploy_MC(ref self: ContractState, salt: felt252) -> ContractAddress { + fn deploy_MC( + ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ) -> ContractAddress { self .deploy( salt, - self.mc_implementation_class_hash.read(), + implementation_class_hash, [starknet::get_contract_address().into()].span(), ) } - fn deploy_IRS(ref self: ContractState, salt: felt252) -> ContractAddress { + fn deploy_IRS( + ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ) -> ContractAddress { self .deploy( salt, - self.irs_implementation_class_hash.read(), + implementation_class_hash, [starknet::get_contract_address().into()].span(), ) } fn deploy_IR( ref self: ContractState, + implementation_class_hash: ClassHash, salt: felt252, trusted_issuers_registry: ContractAddress, claim_topics_registry: ContractAddress, @@ -472,7 +368,7 @@ pub mod TREXFactory { self .deploy( salt, - self.ir_implementation_class_hash.read(), + implementation_class_hash, [ trusted_issuers_registry.into(), claim_topics_registry.into(), identity_storage.into(), starknet::get_contract_address().into(), @@ -480,8 +376,10 @@ pub mod TREXFactory { .span(), ) } + fn deploy_token( ref self: ContractState, + implementation_class_hash: ClassHash, salt: felt252, identity_registry: ContractAddress, compliance: ContractAddress, @@ -497,7 +395,31 @@ pub mod TREXFactory { symbol.serialize(ref ctor_calldata); decimals.serialize(ref ctor_calldata); onchain_id.serialize(ref ctor_calldata); - self.deploy(salt, self.ir_implementation_class_hash.read(), ctor_calldata.span()) + self.deploy(salt, implementation_class_hash, ctor_calldata.span()) + } + + fn _set_implementation_authority( + ref self: ContractState, implementation_authority: ContractAddress, + ) { + assert( + implementation_authority.is_non_zero(), + Errors::IMPLEMENTATION_AUTHORITY_ZERO_ADDRESS, + ); + let implementations = IImplementationAuthorityDispatcher { + contract_address: self.implementation_authority.read(), + } + .get_current_implementations(); + assert( + implementations.ctr_implementation.is_non_zero() + && implementations.ir_implementation.is_non_zero() + && implementations.irs_implementation.is_non_zero() + && implementations.tir_implementation.is_non_zero() + && implementations.mc_implementation.is_non_zero() + && implementations.token_implementation.is_non_zero(), + Errors::INVALID_IMPLEMENTATION, + ); + self.implementation_authority.write(implementation_authority); + self.emit(ImplementationAuthoritySet { implementation_authority }); } } } diff --git a/crates/registry/src/claim_topics_registry.cairo b/crates/registry/src/claim_topics_registry.cairo index a13f599..b33215a 100644 --- a/crates/registry/src/claim_topics_registry.cairo +++ b/crates/registry/src/claim_topics_registry.cairo @@ -23,6 +23,7 @@ pub mod ClaimTopicsRegistry { #[storage] struct Storage { claim_topics: StorageArrayFelt252, + implementation_authority: ContractAddress, #[substorage(v0)] upgrades: UpgradeableComponent::Storage, #[substorage(v0)] @@ -55,11 +56,7 @@ pub mod ClaimTopicsRegistry { pub mod Errors { pub const MAX_CLAIM_TOPICS_EXCEEDED: felt252 = 'Max 15 claim topics exceeded'; pub const CLAIM_TOPIC_ALREADY_EXIST: felt252 = 'Claim topic already exist'; - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); + pub const CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY: felt252 = 'Caller is not IA'; } #[abi(embed_v0)] @@ -72,14 +69,25 @@ pub mod ClaimTopicsRegistry { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + Errors::CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY, + ); self.upgrades.upgrade(new_class_hash); } } + #[constructor] + fn constructor( + ref self: ContractState, implementation_authority: ContractAddress, owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self.implementation_authority.write(implementation_authority); + } + #[abi(embed_v0)] impl ClaimTopicsRegistryImpl of IClaimTopicsRegistry { fn add_claim_topic(ref self: ContractState, claim_topic: felt252) { diff --git a/crates/registry/src/identity_registry.cairo b/crates/registry/src/identity_registry.cairo index 139692d..1141169 100644 --- a/crates/registry/src/identity_registry.cairo +++ b/crates/registry/src/identity_registry.cairo @@ -46,6 +46,7 @@ pub mod IdentityRegistry { token_topics_registry: IClaimTopicsRegistryDispatcher, token_issuers_registry: ITrustedIssuersRegistryDispatcher, token_identity_storage: IIdentityRegistryStorageDispatcher, + implementation_authority: ContractAddress, #[substorage(v0)] upgrades: UpgradeableComponent::Storage, #[substorage(v0)] @@ -128,6 +129,7 @@ pub mod IdentityRegistry { pub const IDENTITY_STORAGE_ADDRESS_ZERO: felt252 = 'Zero Address: IRS'; pub const OWNER_ADDRESS_ZERO: felt252 = 'Zero Address: Owner'; pub const ARRAY_LEN_MISMATCH: felt252 = 'Arrays lenghts not equal'; + pub const CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY: felt252 = 'Caller is not IA'; } #[constructor] @@ -136,6 +138,7 @@ pub mod IdentityRegistry { trusted_issuers_registry: ContractAddress, claim_topics_registry: ContractAddress, identity_storage: ContractAddress, + implementation_authority: ContractAddress, owner: ContractAddress, ) { assert( @@ -156,6 +159,7 @@ pub mod IdentityRegistry { .token_identity_storage .write(IIdentityRegistryStorageDispatcher { contract_address: identity_storage }); self.ownable.initializer(owner); + self.implementation_authority.write(implementation_authority); self.emit(ClaimTopicsRegistrySet { claim_topics_registry }); self.emit(TrustedIssuersRegistrySet { trusted_issuers_registry }); self.emit(IdentityStorageSet { identity_storage }); @@ -171,13 +175,17 @@ pub mod IdentityRegistry { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + Errors::CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY, + ); self.upgrades.upgrade(new_class_hash); } } + #[abi(embed_v0)] impl IdentityRegistryImpl of IIdentityRegistry { fn register_identity( diff --git a/crates/registry/src/identity_registry_storage.cairo b/crates/registry/src/identity_registry_storage.cairo index a359b61..b4a3aae 100644 --- a/crates/registry/src/identity_registry_storage.cairo +++ b/crates/registry/src/identity_registry_storage.cairo @@ -34,6 +34,7 @@ pub mod IdentityRegistryStorage { struct Storage { identities: Map, identity_registries: StorageArrayContractAddress, + implementation_authority: ContractAddress, #[substorage(v0)] upgrades: UpgradeableComponent::Storage, #[substorage(v0)] @@ -116,11 +117,7 @@ pub mod IdentityRegistryStorage { pub const MAX_IR_EXCEEDED: felt252 = 'Cannot bind more than 300 IR'; pub const REGISTRY_ALREADY_BOUNDED: felt252 = 'Registry already binded'; pub const REGISTRY_NOT_BOUNDED: felt252 = 'Registry not bound'; - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); + pub const CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY: felt252 = 'Caller is not IA'; } #[abi(embed_v0)] @@ -133,14 +130,25 @@ pub mod IdentityRegistryStorage { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + Errors::CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY, + ); self.upgrades.upgrade(new_class_hash); } } + #[constructor] + fn constructor( + ref self: ContractState, implementation_authority: ContractAddress, owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self.implementation_authority.write(implementation_authority); + } + #[abi(embed_v0)] impl IdentityRegistryStorageImpl of IIdentityRegistryStorage { fn add_identity_to_storage( diff --git a/crates/registry/src/trusted_issuers_registry.cairo b/crates/registry/src/trusted_issuers_registry.cairo index 9fee2cd..4f70128 100644 --- a/crates/registry/src/trusted_issuers_registry.cairo +++ b/crates/registry/src/trusted_issuers_registry.cairo @@ -29,6 +29,7 @@ pub mod TrustedIssuersRegistry { trusted_issuers: StorageArrayContractAddress, trusted_issuer_claim_topics: Map, claim_topics_to_trusted_issuers: Map, + implementation_authority: ContractAddress, #[substorage(v0)] upgrades: UpgradeableComponent::Storage, #[substorage(v0)] @@ -74,11 +75,7 @@ pub mod TrustedIssuersRegistry { pub const MAX_TRUSTED_ISSUERS_EXCEEDED: felt252 = 'Max 50 trusted issuers'; pub const TRUSTED_ISSUER_ALREADY_EXISTS: felt252 = 'Trusted Issuer already exists'; pub const TRUSTED_ISSUER_DOES_NOT_EXISTS: felt252 = 'Trusted Issuer not exists'; - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); + pub const CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY: felt252 = 'Caller is not IA'; } #[abi(embed_v0)] @@ -91,14 +88,25 @@ pub mod TrustedIssuersRegistry { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + Errors::CALLER_IS_NOT_IMPLEMENTATION_AUTHORITY, + ); self.upgrades.upgrade(new_class_hash); } } + #[constructor] + fn constructor( + ref self: ContractState, implementation_authority: ContractAddress, owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self.implementation_authority.write(implementation_authority); + } + #[abi(embed_v0)] impl TrustedIssuersRegistryImpl of ITrustedIssuersRegistry { fn add_trusted_issuer( diff --git a/crates/registry/tests/claim_topics_registry_test.cairo b/crates/registry/tests/claim_topics_registry_test.cairo index 0da63e5..de3349e 100644 --- a/crates/registry/tests/claim_topics_registry_test.cairo +++ b/crates/registry/tests/claim_topics_registry_test.cairo @@ -4,7 +4,12 @@ use snforge_std::{ContractClassTrait, DeclareResultTrait, declare}; fn setup() -> IClaimTopicsRegistryDispatcher { let claim_topics_registry_contract = declare("ClaimTopicsRegistry").unwrap().contract_class(); let (deployed_address, _) = claim_topics_registry_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); IClaimTopicsRegistryDispatcher { contract_address: deployed_address } } diff --git a/crates/registry/tests/identity_registry_storage_test.cairo b/crates/registry/tests/identity_registry_storage_test.cairo index 1a3bbca..ec6fb3b 100644 --- a/crates/registry/tests/identity_registry_storage_test.cairo +++ b/crates/registry/tests/identity_registry_storage_test.cairo @@ -14,7 +14,12 @@ fn setup() -> Setup { .unwrap() .contract_class(); let (deployed_address, _) = identity_registry_storage_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); Setup { diff --git a/crates/registry/tests/identity_registry_test.cairo b/crates/registry/tests/identity_registry_test.cairo index 16b9e91..190341c 100644 --- a/crates/registry/tests/identity_registry_test.cairo +++ b/crates/registry/tests/identity_registry_test.cairo @@ -24,7 +24,12 @@ fn setup() -> Setup { .unwrap() .contract_class(); let (trusted_issuers_registry_address, _) = trusted_issuers_registry_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); let trusted_issuers_registry = ITrustedIssuersRegistryDispatcher { contract_address: trusted_issuers_registry_address, @@ -34,7 +39,12 @@ fn setup() -> Setup { .unwrap() .contract_class(); let (identity_registry_storage_address, _) = identity_registry_storage_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); let identity_registry_storage = IIdentityRegistryStorageDispatcher { contract_address: identity_registry_storage_address, @@ -42,7 +52,12 @@ fn setup() -> Setup { // Deploy claim topics registry let claim_topics_registry_contract = declare("ClaimTopicsRegistry").unwrap().contract_class(); let (claim_topics_registry_address, _) = claim_topics_registry_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); let claim_topics_registry = IClaimTopicsRegistryDispatcher { contract_address: claim_topics_registry_address, @@ -55,6 +70,7 @@ fn setup() -> Setup { trusted_issuers_registry_address.into(), claim_topics_registry_address.into(), identity_registry_storage_address.into(), + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), starknet::get_contract_address().into(), ], ) diff --git a/crates/registry/tests/trusted_issuers_registry_test.cairo b/crates/registry/tests/trusted_issuers_registry_test.cairo index 87ec038..1334842 100644 --- a/crates/registry/tests/trusted_issuers_registry_test.cairo +++ b/crates/registry/tests/trusted_issuers_registry_test.cairo @@ -6,7 +6,12 @@ fn setup() -> ITrustedIssuersRegistryDispatcher { .unwrap() .contract_class(); let (deployed_address, _) = trusted_issuers_registry_contract - .deploy(@array![starknet::get_contract_address().into()]) + .deploy( + @array![ + starknet::contract_address_const::<'IMPLEMENTATION_AUTHORITY'>().into(), + starknet::get_contract_address().into(), + ], + ) .unwrap(); ITrustedIssuersRegistryDispatcher { contract_address: deployed_address } } diff --git a/crates/token/src/token.cairo b/crates/token/src/token.cairo index 7705c5d..3667b47 100644 --- a/crates/token/src/token.cairo +++ b/crates/token/src/token.cairo @@ -64,6 +64,7 @@ pub mod Token { frozen_tokens: Map, token_identity_registry: IIdentityRegistryDispatcher, token_compliance: IModularComplianceDispatcher, + implementation_authority: ContractAddress, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -165,6 +166,7 @@ pub mod Token { symbol: ByteArray, decimals: u8, onchain_id: ContractAddress, + implementation_authority: ContractAddress, owner: ContractAddress, ) { assert(owner.is_non_zero(), 'Owner is Zero Address'); @@ -175,6 +177,7 @@ pub mod Token { self.erc20.initializer(name, symbol); self.token_decimals.write(decimals); self.token_onchain_id.write(onchain_id); + self.implementation_authority.write(implementation_authority); self.pausable.pause(); self.set_compliance(compliance); self.set_identity_registry(identity_registry); @@ -190,10 +193,13 @@ pub mod Token { /// /// # Requirements /// - /// - This function can only be called by the owner. + /// - This function can only be called by the implementation authority. /// - The `ClassHash` should already have been declared. fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); + assert( + self.implementation_authority.read() == starknet::get_caller_address(), + 'Caller is not IA', + ); self.upgrades.upgrade(new_class_hash); } } From a77b5135c6ec2df7e0e4de25454c3dda4bd0e7ed Mon Sep 17 00:00:00 2001 From: Turan Ege Caner Date: Wed, 15 Jan 2025 21:03:57 +0800 Subject: [PATCH 3/5] allowed-libfuncs-all --- crates/factory/Scarb.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/factory/Scarb.toml b/crates/factory/Scarb.toml index 783e3ce..151d043 100644 --- a/crates/factory/Scarb.toml +++ b/crates/factory/Scarb.toml @@ -20,6 +20,7 @@ snforge_std.workspace = true [[target.starknet-contract]] sierra = true build-external-contracts = ["onchain_id_starknet::*", "registry::*", "roles::*", "compliance::*", "token::*"] +allowed-libfuncs-list.name = "all" [tool] fmt.workspace = true From 9b9d7b6f6bebdc6edfd3eefe9b0f3d088fdeb78c Mon Sep 17 00:00:00 2001 From: Ege Caner <67071243+EgeCaner@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:34:22 +0300 Subject: [PATCH 4/5] Update crates/factory/src/iimplementation_authority.cairo higher order bits should be zeros at this point so it should be okay to not mask Co-authored-by: Julio <30329843+julio4@users.noreply.github.com> --- crates/factory/src/iimplementation_authority.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/factory/src/iimplementation_authority.cairo b/crates/factory/src/iimplementation_authority.cairo index 90b5b12..22fc2e6 100644 --- a/crates/factory/src/iimplementation_authority.cairo +++ b/crates/factory/src/iimplementation_authority.cairo @@ -20,7 +20,7 @@ pub impl VersionStorePacking of StorePacking { fn unpack(value: u32) -> Version { let major = value & MASK_8; let minor = (value / SHIFT_8) & MASK_8; - let patch = (value / SHIFT_16) & MASK_8; + let patch = value / SHIFT_16; Version { major: major.try_into().unwrap(), From 371b38339f30e982f38f8b31dbf7ff4d7ca0957e Mon Sep 17 00:00:00 2001 From: Turan Ege Caner Date: Thu, 16 Jan 2025 20:30:01 +0800 Subject: [PATCH 5/5] Factory fix ctor calldata --- .../src/iimplementation_authority.cairo | 2 +- crates/factory/src/trex_factory.cairo | 69 +++++++++++++++---- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/crates/factory/src/iimplementation_authority.cairo b/crates/factory/src/iimplementation_authority.cairo index 90b5b12..1c2ef2d 100644 --- a/crates/factory/src/iimplementation_authority.cairo +++ b/crates/factory/src/iimplementation_authority.cairo @@ -30,7 +30,7 @@ pub impl VersionStorePacking of StorePacking { } } -#[derive(Drop, Copy, Serde, starknet::Store)] +#[derive(Drop, Copy, Serde)] pub struct TREXImplementations { pub token_implementation: ClassHash, pub ctr_implementation: ClassHash, diff --git a/crates/factory/src/trex_factory.cairo b/crates/factory/src/trex_factory.cairo index 731be62..f88b856 100644 --- a/crates/factory/src/trex_factory.cairo +++ b/crates/factory/src/trex_factory.cairo @@ -161,22 +161,37 @@ pub mod TREXFactory { token_details.compliance_modules.len() >= token_details.compliance_settings.len(), Errors::INVALID_COMPLIANCE_PATTERN, ); + let implementation_authority_address = self.implementation_authority.read(); let implementations = IImplementationAuthorityDispatcher { - contract_address: self.implementation_authority.read(), + contract_address: implementation_authority_address, } .get_current_implementations(); let tir = ITrustedIssuersRegistryDispatcher { - contract_address: self.deploy_TIR(implementations.tir_implementation, salt), + contract_address: self + .deploy_TIR( + implementations.tir_implementation, salt, implementation_authority_address, + ), }; let ctr = IClaimTopicsRegistryDispatcher { - contract_address: self.deploy_CTR(implementations.ctr_implementation, salt), + contract_address: self + .deploy_CTR( + implementations.ctr_implementation, salt, implementation_authority_address, + ), }; let mc = IModularComplianceDispatcher { - contract_address: self.deploy_MC(implementations.mc_implementation, salt), + contract_address: self + .deploy_MC( + implementations.mc_implementation, salt, implementation_authority_address, + ), }; let irs = if token_details.irs.is_zero() { IIdentityRegistryStorageDispatcher { - contract_address: self.deploy_IRS(implementations.irs_implementation, salt), + contract_address: self + .deploy_IRS( + implementations.irs_implementation, + salt, + implementation_authority_address, + ), } } else { IIdentityRegistryStorageDispatcher { contract_address: token_details.irs } @@ -189,6 +204,7 @@ pub mod TREXFactory { tir.contract_address, ctr.contract_address, irs.contract_address, + implementation_authority_address, ), }; let token_address = self @@ -201,6 +217,7 @@ pub mod TREXFactory { token_details.symbol, token_details.decimals, token_details.onchain_id, + implementation_authority_address, ); salt_to_token_storage.write(token_address); @@ -314,46 +331,62 @@ pub mod TREXFactory { } fn deploy_TIR( - ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ref self: ContractState, + implementation_class_hash: ClassHash, + salt: felt252, + implementation_authority: ContractAddress, ) -> ContractAddress { self .deploy( salt, implementation_class_hash, - [starknet::get_contract_address().into()].span(), + [implementation_authority.into(), starknet::get_contract_address().into()] + .span(), ) } fn deploy_CTR( - ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ref self: ContractState, + implementation_class_hash: ClassHash, + salt: felt252, + implementation_authority: ContractAddress, ) -> ContractAddress { self .deploy( salt, implementation_class_hash, - [starknet::get_contract_address().into()].span(), + [implementation_authority.into(), starknet::get_contract_address().into()] + .span(), ) } fn deploy_MC( - ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ref self: ContractState, + implementation_class_hash: ClassHash, + salt: felt252, + implementation_authority: ContractAddress, ) -> ContractAddress { self .deploy( salt, implementation_class_hash, - [starknet::get_contract_address().into()].span(), + [implementation_authority.into(), starknet::get_contract_address().into()] + .span(), ) } fn deploy_IRS( - ref self: ContractState, implementation_class_hash: ClassHash, salt: felt252, + ref self: ContractState, + implementation_class_hash: ClassHash, + salt: felt252, + implementation_authority: ContractAddress, ) -> ContractAddress { self .deploy( salt, implementation_class_hash, - [starknet::get_contract_address().into()].span(), + [implementation_authority.into(), starknet::get_contract_address().into()] + .span(), ) } @@ -364,6 +397,7 @@ pub mod TREXFactory { trusted_issuers_registry: ContractAddress, claim_topics_registry: ContractAddress, identity_storage: ContractAddress, + implementation_authority: ContractAddress, ) -> ContractAddress { self .deploy( @@ -371,7 +405,8 @@ pub mod TREXFactory { implementation_class_hash, [ trusted_issuers_registry.into(), claim_topics_registry.into(), - identity_storage.into(), starknet::get_contract_address().into(), + identity_storage.into(), implementation_authority.into(), + starknet::get_contract_address().into(), ] .span(), ) @@ -387,6 +422,7 @@ pub mod TREXFactory { symbol: ByteArray, decimals: u8, onchain_id: ContractAddress, + implementation_authority: ContractAddress, ) -> ContractAddress { let mut ctor_calldata: Array = array![ identity_registry.into(), compliance.into(), @@ -395,6 +431,8 @@ pub mod TREXFactory { symbol.serialize(ref ctor_calldata); decimals.serialize(ref ctor_calldata); onchain_id.serialize(ref ctor_calldata); + implementation_authority.serialize(ref ctor_calldata); + starknet::get_contract_address().serialize(ref ctor_calldata); self.deploy(salt, implementation_class_hash, ctor_calldata.span()) } @@ -406,9 +444,10 @@ pub mod TREXFactory { Errors::IMPLEMENTATION_AUTHORITY_ZERO_ADDRESS, ); let implementations = IImplementationAuthorityDispatcher { - contract_address: self.implementation_authority.read(), + contract_address: implementation_authority, } .get_current_implementations(); + assert( implementations.ctr_implementation.is_non_zero() && implementations.ir_implementation.is_non_zero()