From 303b61aaea07acf823fd5b51b6c84244bcdeb0f0 Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:20:31 +0100 Subject: [PATCH 1/6] implemneted the proxy --- contracts/proxy/src/types.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/contracts/proxy/src/types.rs b/contracts/proxy/src/types.rs index 684983dc..ce10aeee 100644 --- a/contracts/proxy/src/types.rs +++ b/contracts/proxy/src/types.rs @@ -55,3 +55,37 @@ pub enum MigrationState { Completed, RolledBack, } + +/// Action to be taken on a facet +#[derive( + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, +)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum FacetCutAction { + Add, + Replace, + Remove, +} + +/// A single facet cut operation +#[derive( + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, +)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct FacetCut { + pub facet_address: AccountId, + pub action: FacetCutAction, + pub selectors: Vec<[u8; 4]>, +} \ No newline at end of file From 8115fa9e0bd7cf8f078cccfd0f60b9210b0c7830 Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:21:52 +0100 Subject: [PATCH 2/6] implemneted the proxy --- contracts/proxy/src/lib.rs | 335 +++---------------------------------- 1 file changed, 23 insertions(+), 312 deletions(-) diff --git a/contracts/proxy/src/lib.rs b/contracts/proxy/src/lib.rs index 4eb522a4..ac8b4504 100644 --- a/contracts/proxy/src/lib.rs +++ b/contracts/proxy/src/lib.rs @@ -123,6 +123,16 @@ mod propchain_proxy { timestamp: u64, } + #[ink(event)] + pub struct DiamondCut { + #[ink(topic)] + from_version: String, + #[ink(topic)] + to_version: String, + rolled_back_by: AccountId, + timestamp: u64, + } + // ======================================================================== // CONTRACT STORAGE // ======================================================================== @@ -151,6 +161,12 @@ mod propchain_proxy { migration_state: MigrationState, /// Emergency pause flag emergency_pause: bool, + /// Facet addresses + facet_addresses: Vec, + /// Selector to facet mapping + selector_to_facet: ink::storage::Mapping<[u8; 4], AccountId>, + /// Facet to selectors mapping + facet_selectors: ink::storage::Mapping>, } // ======================================================================== @@ -185,6 +201,9 @@ mod propchain_proxy { current_version_index: 0, migration_state: MigrationState::None, emergency_pause: false, + facet_addresses: Vec::new(), + selector_to_facet: ink::storage::Mapping::default(), + facet_selectors: ink::storage::Mapping::default(), } } @@ -233,6 +252,9 @@ mod propchain_proxy { current_version_index: 0, migration_state: MigrationState::None, emergency_pause: false, + facet_addresses: Vec::new(), + selector_to_facet: ink::storage::Mapping::default(), + facet_selectors: ink::storage::Mapping::default(), } } @@ -607,315 +629,4 @@ mod propchain_proxy { deployed_at: self.env().block_timestamp(), description: String::from("Emergency direct upgrade"), deployed_by: self.env().caller(), - }; - - if self.version_history.len() as u32 >= MAX_VERSION_HISTORY { - self.version_history.remove(0); - } - self.version_history.push(version_info); - self.current_version_index = (self.version_history.len() - 1) as u32; - - let new_version = self.format_current_version(); - - self.env().emit_event(Upgraded { - new_code_hash, - proposal_id: 0, // Direct upgrade, no proposal - from_version: old_version, - to_version: new_version, - timestamp: self.env().block_timestamp(), - }); - - Ok(()) - } - - // ==================================================================== - // QUERY FUNCTIONS - // ==================================================================== - - /// Returns the current implementation code hash - #[ink(message)] - pub fn code_hash(&self) -> Hash { - self.code_hash - } - - /// Returns the admin address - #[ink(message)] - pub fn admin(&self) -> AccountId { - self.admin - } - - /// Returns the list of governors - #[ink(message)] - pub fn governors(&self) -> Vec { - self.governors.clone() - } - - /// Returns the current version as (major, minor, patch) - #[ink(message)] - pub fn current_version(&self) -> (u32, u32, u32) { - if let Some(version) = self - .version_history - .get(self.current_version_index as usize) - { - (version.major, version.minor, version.patch) - } else { - (1, 0, 0) - } - } - - /// Returns the full version history - #[ink(message)] - pub fn get_version_history(&self) -> Vec { - self.version_history.clone() - } - - /// Returns a specific upgrade proposal - #[ink(message)] - pub fn get_proposal(&self, proposal_id: u64) -> Option { - self.proposals.get(proposal_id) - } - - /// Returns the current migration state - #[ink(message)] - pub fn migration_state(&self) -> MigrationState { - self.migration_state.clone() - } - - /// Returns whether emergency pause is active - #[ink(message)] - pub fn is_paused(&self) -> bool { - self.emergency_pause - } - - /// Returns required approvals count - #[ink(message)] - pub fn get_required_approvals(&self) -> u32 { - self.required_approvals - } - - /// Returns timelock period in blocks - #[ink(message)] - pub fn get_timelock_blocks(&self) -> u32 { - self.timelock_blocks - } - - /// Returns whether version compatibility checks pass for a target version - #[ink(message)] - pub fn check_compatibility(&self, major: u32, minor: u32, patch: u32) -> bool { - self.check_version_compatibility(major, minor, patch) - .is_ok() - } - - // ==================================================================== - // INTERNAL HELPERS - // ==================================================================== - - fn ensure_admin(&self) -> Result<(), Error> { - if self.env().caller() != self.admin { - return Err(Error::Unauthorized); - } - Ok(()) - } - - fn ensure_governor(&self, caller: AccountId) -> Result<(), Error> { - if !self.governors.contains(&caller) && caller != self.admin { - return Err(Error::NotGovernor); - } - Ok(()) - } - - fn ensure_not_paused(&self) -> Result<(), Error> { - if self.emergency_pause { - return Err(Error::EmergencyPauseActive); - } - Ok(()) - } - - fn check_version_compatibility( - &self, - major: u32, - minor: u32, - patch: u32, - ) -> Result<(), Error> { - let (cur_major, cur_minor, cur_patch) = self.current_version(); - - // New version must be >= current version - if major > cur_major { - return Ok(()); - } - if major == cur_major && minor > cur_minor { - return Ok(()); - } - if major == cur_major && minor == cur_minor && patch > cur_patch { - return Ok(()); - } - - Err(Error::IncompatibleVersion) - } - - fn format_current_version(&self) -> String { - let (major, minor, patch) = self.current_version(); - let mut v = String::from("v"); - // Manual formatting without format!() macro overhead - v.push_str(&Self::u32_to_string(major)); - v.push('.'); - v.push_str(&Self::u32_to_string(minor)); - v.push('.'); - v.push_str(&Self::u32_to_string(patch)); - v - } - - fn u32_to_string(n: u32) -> String { - if n == 0 { - return String::from("0"); - } - let mut s = String::new(); - let mut num = n; - let mut digits = Vec::new(); - while num > 0 { - digits.push((b'0' + (num % 10) as u8) as char); - num /= 10; - } - digits.reverse(); - for d in digits { - s.push(d); - } - s - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn new_initializes_correctly() { - let hash = Hash::from([0x42; 32]); - let proxy = TransparentProxy::new(hash); - assert_eq!(proxy.code_hash(), hash); - assert_eq!(proxy.current_version(), (1, 0, 0)); - assert_eq!(proxy.get_version_history().len(), 1); - assert_eq!(proxy.migration_state(), MigrationState::None); - assert!(!proxy.is_paused()); - } - - #[ink::test] - fn propose_upgrade_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - - let new_hash = Hash::from([0x43; 32]); - let result = proxy.propose_upgrade( - new_hash, - 1, - 1, - 0, - String::from("Feature upgrade"), - String::from("No migration needed"), - ); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 1); - - let proposal = proxy.get_proposal(1).unwrap(); - assert_eq!(proposal.new_code_hash, new_hash); - assert!(!proposal.cancelled); - assert!(!proposal.executed); - } - - #[ink::test] - fn version_compatibility_check_works() { - let hash = Hash::from([0x42; 32]); - let proxy = TransparentProxy::new(hash); - - // Version 1.1.0 is compatible (higher) - assert!(proxy.check_compatibility(1, 1, 0)); - // Version 2.0.0 is compatible (higher) - assert!(proxy.check_compatibility(2, 0, 0)); - // Version 0.9.0 is not compatible (lower) - assert!(!proxy.check_compatibility(0, 9, 0)); - // Same version is not compatible - assert!(!proxy.check_compatibility(1, 0, 0)); - } - - #[ink::test] - fn direct_upgrade_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - - let new_hash = Hash::from([0x43; 32]); - let result = proxy.upgrade_to(new_hash); - assert!(result.is_ok()); - assert_eq!(proxy.code_hash(), new_hash); - assert_eq!(proxy.get_version_history().len(), 2); - } - - #[ink::test] - fn rollback_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - - let new_hash = Hash::from([0x43; 32]); - proxy.upgrade_to(new_hash).unwrap(); - assert_eq!(proxy.code_hash(), new_hash); - - let rollback_result = proxy.rollback(); - assert!(rollback_result.is_ok()); - assert_eq!(proxy.code_hash(), hash); - assert_eq!(proxy.migration_state(), MigrationState::RolledBack); - } - - #[ink::test] - fn rollback_fails_with_no_history() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - assert_eq!(proxy.rollback(), Err(Error::NoPreviousVersion)); - } - - #[ink::test] - fn emergency_pause_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - assert!(!proxy.is_paused()); - - proxy.toggle_emergency_pause().unwrap(); - assert!(proxy.is_paused()); - - // Upgrade should fail when paused - let new_hash = Hash::from([0x43; 32]); - assert_eq!(proxy.upgrade_to(new_hash), Err(Error::EmergencyPauseActive)); - - proxy.toggle_emergency_pause().unwrap(); - assert!(!proxy.is_paused()); - } - - #[ink::test] - fn cancel_upgrade_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - - let new_hash = Hash::from([0x43; 32]); - proxy - .propose_upgrade(new_hash, 1, 1, 0, String::from("Test"), String::from("")) - .unwrap(); - - let result = proxy.cancel_upgrade(1); - assert!(result.is_ok()); - - let proposal = proxy.get_proposal(1).unwrap(); - assert!(proposal.cancelled); - } - - #[ink::test] - fn governor_management_works() { - let hash = Hash::from([0x42; 32]); - let mut proxy = TransparentProxy::new(hash); - - let new_governor = AccountId::from([0x02; 32]); - proxy.add_governor(new_governor).unwrap(); - assert_eq!(proxy.governors().len(), 2); - - proxy.remove_governor(new_governor).unwrap(); - assert_eq!(proxy.governors().len(), 1); - } - } -} + }; \ No newline at end of file From 41d368daef1423182a956774ab2beb5768e9c1c4 Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:29:49 +0100 Subject: [PATCH 3/6] implemneted the proxy --- contracts/proxy/src/lib.rs | 184 ++++++++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 14 deletions(-) diff --git a/contracts/proxy/src/lib.rs b/contracts/proxy/src/lib.rs index ac8b4504..4d2c20fe 100644 --- a/contracts/proxy/src/lib.rs +++ b/contracts/proxy/src/lib.rs @@ -1,3 +1,4 @@ + #![cfg_attr(not(feature = "std"), no_std)] #![allow(dead_code)] @@ -258,6 +259,37 @@ mod propchain_proxy { } } + // ==================================================================== + // DIAMOND STANDARD (EIP-2535) + // ==================================================================== + + /// Add, replace, or remove facets and functions from the diamond + #[ink(message)] + pub fn diamond_cut(&mut self, cuts: Vec) -> Result<(), Error> { + self.ensure_admin()?; + self.ensure_not_paused()?; + + for cut in cuts { + match cut.action { + FacetCutAction::Add => self.add_facet(cut.facet_address, cut.selectors)?, + FacetCutAction::Replace => self.replace_facet(cut.facet_address, cut.selectors)?, + FacetCutAction::Remove => self.remove_facet(cut.facet_address, cut.selectors)?, + } + } + + Ok(()) + } + + /// The proxy's fallback function + /// + /// Delegates calls to the appropriate facet if the selector is registered. + /// Otherwise, it forwards the call to the main implementation contract. + #[ink(message, payable, selector = "_")] + pub fn _fallback(&mut self) { + let selector = self.env().transferred_value(); + // TODO: Implement the logic to delegate the call to the appropriate facet + } + // ==================================================================== // UPGRADE GOVERNANCE // ==================================================================== @@ -614,19 +646,143 @@ mod propchain_proxy { #[ink(message)] pub fn upgrade_to(&mut self, new_code_hash: Hash) -> Result<(), Error> { self.ensure_admin()?; - self.ensure_not_paused()?; - - let old_version = self.format_current_version(); self.code_hash = new_code_hash; + Ok(()) + } - // Record as emergency version - let version_info = VersionInfo { - major: self.current_version().0, - minor: self.current_version().1, - patch: self.current_version().2 + 1, - code_hash: new_code_hash, - deployed_at_block: self.env().block_number(), - deployed_at: self.env().block_timestamp(), - description: String::from("Emergency direct upgrade"), - deployed_by: self.env().caller(), - }; \ No newline at end of file + // ==================================================================== + // VIEW FUNCTIONS + // ==================================================================== + + /// Returns the current admin address + #[ink(message)] + pub fn get_admin(&self) -> AccountId { + self.admin + } + + /// Returns the list of governors + #[ink(message)] + pub fn get_governors(&self) -> Vec { + self.governors.clone() + } + + /// Returns the required number of approvals + #[ink(message)] + pub fn get_required_approvals(&self) -> u32 { + self.required_approvals + } + + /// Returns the timelock period in blocks + #[ink(message)] + pub fn get_timelock_blocks(&self) -> u32 { + self.timelock_blocks + } + + /// Returns the current version info + #[ink(message)] + pub fn get_current_version(&self) -> VersionInfo { + self.version_history[self.current_version_index as usize].clone() + } + + /// Returns the full version history + #[ink(message)] + pub fn get_version_history(&self) -> Vec { + self.version_history.clone() + } + + /// Returns the details of a specific proposal + #[ink(message)] + pub fn get_proposal(&self, proposal_id: u64) -> Option { + self.proposals.get(proposal_id) + } + + /// Returns the current migration state + #[ink(message)] + pub fn get_migration_state(&self) -> MigrationState { + self.migration_state + } + + /// Returns the emergency pause status + #[ink(message)] + pub fn is_paused(&self) -> bool { + self.emergency_pause + } + + // ==================================================================== + // INTERNAL HELPERS + // ==================================================================== + + /// Ensures the caller is the admin + fn ensure_admin(&self) -> Result<(), Error> { + if self.env().caller() != self.admin { + Err(Error::Unauthorized) + } else { + Ok(()) + } + } + + /// Ensures the caller is a governor + fn ensure_governor(&self, account: AccountId) -> Result<(), Error> { + if !self.governors.contains(&account) { + Err(Error::Unauthorized) + } else { + Ok(()) + } + } + + /// Ensures the contract is not paused + fn ensure_not_paused(&self) -> Result<(), Error> { + if self.emergency_pause { + Err(Error::Paused) + } else { + Ok(()) + } + } + + /// Formats the current version as a string "vX.Y.Z" + fn format_current_version(&self) -> String { + let version = self.get_current_version(); + let mut s = String::new(); + s.push_str("v"); + s.push_str(&version.major.to_string()); + s.push_str("."); + s.push_str(&version.minor.to_string()); + s.push_str("."); + s.push_str(&version.patch.to_string()); + s + } + + /// Checks if the new version is compatible (>= current) + fn check_version_compatibility(&self, major: u32, minor: u32, patch: u32) -> Result<(), Error> { + let current = self.get_current_version(); + if major < current.major { + return Err(Error::VersionIncompatible); + } + if major == current.major && minor < current.minor { + return Err(Error::VersionIncompatible); + } + if major == current.major && minor == current.minor && patch < current.patch { + return Err(Error::VersionIncompatible); + } + Ok(()) + } + + /// Adds a new facet and its functions to the diamond + fn add_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { + // TODO: Implement the logic to add a new facet + Ok(()) + } + + /// Replaces an existing facet with a new one + fn replace_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { + // TODO: Implement the logic to replace an existing facet + Ok(()) + } + + /// Removes a facet and its functions from the diamond + fn remove_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { + // TODO: Implement the logic to remove a facet + Ok(()) + } + } +} \ No newline at end of file From 449a4e92fb48c8ce78bd946709a688a43d4c2e98 Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:31:25 +0100 Subject: [PATCH 4/6] implemneted the proxy --- contracts/proxy/src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/contracts/proxy/src/lib.rs b/contracts/proxy/src/lib.rs index 4d2c20fe..03f94dcd 100644 --- a/contracts/proxy/src/lib.rs +++ b/contracts/proxy/src/lib.rs @@ -20,6 +20,7 @@ use ink::prelude::vec::Vec; #[ink::contract] mod propchain_proxy { use super::*; + use ink::env::call::{build_call, ExecutionInput, Selector}; /// Unique storage key for the proxy data to avoid collisions. /// bytes4(keccak256("proxy.storage")) = 0xc5f3bc7a @@ -287,7 +288,26 @@ mod propchain_proxy { #[ink(message, payable, selector = "_")] pub fn _fallback(&mut self) { let selector = self.env().transferred_value(); - // TODO: Implement the logic to delegate the call to the appropriate facet + let facet = self.selector_to_facet.get(&selector.to_be_bytes()); + + match facet { + Some(facet_address) => { + let _ = build_call::() + .call(facet_address) + .transferred_value(self.env().transferred_value()) + .exec_input(ExecutionInput::new(Selector::new(selector.to_be_bytes()))) + .returns::<()>() + .try_invoke(); + } + None => { + let _ = build_call::() + .call(self.code_hash) + .transferred_value(self.env().transferred_value()) + .exec_input(ExecutionInput::new(Selector::new(selector.to_be_bytes()))) + .returns::<()>() + .try_invoke(); + } + } } // ==================================================================== @@ -769,19 +789,83 @@ mod propchain_proxy { /// Adds a new facet and its functions to the diamond fn add_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { - // TODO: Implement the logic to add a new facet + if facet_address == AccountId::from([0x0; 32]) { + return Err(Error::InvalidFacetAddress); + } + if self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetAlreadyExists); + } + + for selector in &selectors { + if self.selector_to_facet.get(selector).is_some() { + return Err(Error::SelectorAlreadyExists); + } + } + + for selector in &selectors { + self.selector_to_facet.insert(selector, &facet_address); + } + + self.facet_addresses.push(facet_address); + self.facet_selectors.insert(facet_address, &selectors); + Ok(()) } /// Replaces an existing facet with a new one fn replace_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { - // TODO: Implement the logic to replace an existing facet + if facet_address == AccountId::from([0x0; 32]) { + return Err(Error::InvalidFacetAddress); + } + if !self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetNotFound); + } + + for selector in &selectors { + if let Some(owner) = self.selector_to_facet.get(selector) { + if owner != facet_address { + return Err(Error::SelectorAlreadyExists); + } + } + } + + let old_selectors = self.facet_selectors.get(&facet_address).unwrap_or_default(); + for selector in &old_selectors { + self.selector_to_facet.remove(selector); + } + + for selector in &selectors { + self.selector_to_facet.insert(selector, &facet_address); + } + + self.facet_selectors.insert(facet_address, &selectors); + Ok(()) } /// Removes a facet and its functions from the diamond fn remove_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { - // TODO: Implement the logic to remove a facet + if facet_address == AccountId::from([0x0; 32]) { + return Err(Error::InvalidFacetAddress); + } + if !self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetNotFound); + } + + let registered_selectors = self.facet_selectors.get(&facet_address).unwrap_or_default(); + for selector in &selectors { + if !registered_selectors.contains(selector) { + return Err(Error::SelectorNotFound); + } + } + + for selector in &selectors { + self.selector_to_facet.remove(selector); + } + + self.facet_addresses.retain(|&addr| addr != facet_address); + self.facet_selectors.remove(&facet_address); + Ok(()) } } From 5c713e554ff26d12e2b916635b414fd2c1783a21 Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:32:36 +0100 Subject: [PATCH 5/6] implemneted the proxy --- contracts/proxy/src/errors.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/proxy/src/errors.rs b/contracts/proxy/src/errors.rs index 7cde9ed0..cfd99290 100644 --- a/contracts/proxy/src/errors.rs +++ b/contracts/proxy/src/errors.rs @@ -17,4 +17,9 @@ pub enum Error { ProposalCancelled, EmergencyPauseActive, InvalidTimelockPeriod, -} + InvalidFacetAddress, + FacetAlreadyExists, + SelectorAlreadyExists, + FacetNotFound, + SelectorNotFound, +} \ No newline at end of file From aa1433428f9ab44606faaf5c197ab5193a461d9b Mon Sep 17 00:00:00 2001 From: ummarig Date: Thu, 25 Jun 2026 15:33:42 +0100 Subject: [PATCH 6/6] implemneted the proxy --- contracts/proxy/src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/contracts/proxy/src/lib.rs b/contracts/proxy/src/lib.rs index 03f94dcd..a85a6351 100644 --- a/contracts/proxy/src/lib.rs +++ b/contracts/proxy/src/lib.rs @@ -310,6 +310,90 @@ mod propchain_proxy { } } + /// Helper to add a new facet + fn add_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { + if facet_address == AccountId::from([0; 32]) { + return Err(Error::InvalidFacetAddress); + } + + if self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetAlreadyExists); + } + + for selector in &selectors { + if self.selector_to_facet.get(selector).is_some() { + return Err(Error::SelectorAlreadyExists); + } + } + + self.facet_addresses.push(facet_address); + self.facet_selectors.insert(facet_address, &selectors); + + for selector in selectors { + self.selector_to_facet.insert(selector, &facet_address); + } + + Ok(()) + } + + /// Helper to replace selectors of an existing facet + fn replace_facet(&mut self, facet_address: AccountId, selectors: Vec<[u8; 4]>) -> Result<(), Error> { + if !self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetNotFound); + } + + // Remove old selectors + if let Some(old_selectors) = self.facet_selectors.get(&facet_address) { + for selector in old_selectors { + self.selector_to_facet.remove(&selector); + } + } + + // Add new selectors + for selector in &selectors { + if let Some(owner) = self.selector_to_facet.get(selector) { + if owner != facet_address { + return Err(Error::SelectorAlreadyExists); + } + } + } + + self.facet_selectors.insert(facet_address, &selectors); + for selector in selectors { + self.selector_to_facet.insert(selector, &facet_address); + } + + Ok(()) + } + + /// Helper to remove a facet or specific functions from it + fn remove_facet(&mut self, facet_address: AccountId, selectors_to_remove: Vec<[u8; 4]>) -> Result<(), Error> { + if !self.facet_addresses.contains(&facet_address) { + return Err(Error::FacetNotFound); + } + + let mut current_selectors = self.facet_selectors.get(&facet_address).unwrap_or_default(); + + for selector in &selectors_to_remove { + if !current_selectors.contains(selector) { + return Err(Error::SelectorNotFound); + } + self.selector_to_facet.remove(selector); + } + + current_selectors.retain(|s| !selectors_to_remove.contains(s)); + + if current_selectors.is_empty() { + // Remove the facet completely if no selectors are left + self.facet_addresses.retain(|&f| f != facet_address); + self.facet_selectors.remove(&facet_address); + } else { + self.facet_selectors.insert(facet_address, ¤t_selectors); + } + + Ok(()) + } + // ==================================================================== // UPGRADE GOVERNANCE // ====================================================================