From 6fc4863cf1401906d1feb18cbe9cfcaf7190c16d Mon Sep 17 00:00:00 2001 From: Turan Ege Caner Date: Fri, 13 Dec 2024 17:47:31 +0700 Subject: [PATCH] Trusted Issuers Registry --- crates/registry/src/identity_registry.cairo | 4 +- .../interface/itrusted_issuers_registry.cairo | 42 +-- .../src/trusted_issuers_registry.cairo | 241 +++++++++++++++++- 3 files changed, 243 insertions(+), 44 deletions(-) diff --git a/crates/registry/src/identity_registry.cairo b/crates/registry/src/identity_registry.cairo index c9138ef..4941774 100644 --- a/crates/registry/src/identity_registry.cairo +++ b/crates/registry/src/identity_registry.cairo @@ -313,7 +313,9 @@ pub mod IdentityRegistry { for trusted_issuer in trusted_issuers { claim_ids .append( - poseidon_hash_span(array![trusted_issuer.into(), claim_topic].span()), + poseidon_hash_span( + array![(*trusted_issuer).into(), claim_topic].span(), + ), ); }; diff --git a/crates/registry/src/interface/itrusted_issuers_registry.cairo b/crates/registry/src/interface/itrusted_issuers_registry.cairo index 0fb39d2..1892fee 100644 --- a/crates/registry/src/interface/itrusted_issuers_registry.cairo +++ b/crates/registry/src/interface/itrusted_issuers_registry.cairo @@ -1,33 +1,5 @@ use starknet::ContractAddress; -#[event] -#[derive(Drop, starknet::Event)] -pub enum TrustedIssuersRegistryEvent { - TrustedIssuerAdded: TrustedIssuerAdded, - TrustedIssuerRemoved: TrustedIssuerRemoved, - ClaimTopicsUpdated: ClaimTopicsUpdated, -} - -#[derive(Drop, starknet::Event)] -pub struct TrustedIssuerAdded { - #[key] - trusted_issuer: ContractAddress, - claim_topics: Array, -} - -#[derive(Drop, starknet::Event)] -pub struct TrustedIssuerRemoved { - #[key] - trusted_issuer: ContractAddress, -} - -#[derive(Drop, starknet::Event)] -pub struct ClaimTopicsUpdated { - #[key] - trusted_issuer: ContractAddress, - claim_topics: Array, -} - #[starknet::interface] pub trait ITrustedIssuersRegistry { fn add_trusted_issuer( @@ -37,15 +9,15 @@ pub trait ITrustedIssuersRegistry { fn update_issuer_claim_topics( ref self: TContractState, trusted_issuer: ContractAddress, claim_topics: Span, ); - fn get_trusted_issuers(self: @TContractState) -> Array; + fn has_claim_topic( + self: @TContractState, issuer: ContractAddress, claim_topic: felt252, + ) -> bool; + fn is_trusted_issuer(self: @TContractState, issuer: ContractAddress) -> bool; + fn get_trusted_issuers(self: @TContractState) -> Span; fn get_trusted_issuers_for_claim_topic( self: @TContractState, claim_topic: felt252, - ) -> Array; - fn is_trusted_issuer(self: @TContractState, issuer: ContractAddress) -> bool; + ) -> Span; fn get_trusted_issuer_claim_topics( self: @TContractState, trusted_issuer: ContractAddress, - ) -> Array; - fn has_claim_topic( - self: @TContractState, issuer: ContractAddress, claim_topic: felt252, - ) -> bool; + ) -> Span; } diff --git a/crates/registry/src/trusted_issuers_registry.cairo b/crates/registry/src/trusted_issuers_registry.cairo index b8857aa..db5afa0 100644 --- a/crates/registry/src/trusted_issuers_registry.cairo +++ b/crates/registry/src/trusted_issuers_registry.cairo @@ -1,14 +1,239 @@ #[starknet::contract] mod TrustedIssuersRegistry { - //use registry::interface::itrusted_issuers_registry::{ITrustedIssuersRegistry}; - //use starknet::ContractAddress; + use core::num::traits::Zero; + use crate::interface::itrusted_issuers_registry::ITrustedIssuersRegistry; + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::{ + ClassHash, ContractAddress, + storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess}, + }; + use storage::storage_array::{ + ContractAddressVecToContractAddressArray as ContractAddressVecInto, + Felt252VecToFelt252Array as FeltVecInto, MutableStorageArrayTrait, + StorageArrayContractAddress, StorageArrayFelt252, StorageArrayTrait, + }; + + component!(path: UpgradeableComponent, storage: upgrades, event: UpgradeableEvent); + + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + #[storage] - struct Storage {} + struct Storage { + trusted_issuers: StorageArrayContractAddress, + trusted_issuer_claim_topics: Map, + claim_topics_to_trusted_issuers: Map, + #[substorage(v0)] + upgrades: UpgradeableComponent::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + #[event] #[derive(Drop, starknet::Event)] - enum Event {} - //#[abi(embed_v0)] -//impl TrustedIssuersRegistryImpl of ITrustedIssuersRegistry{ -// -//} + pub enum Event { + TrustedIssuerAdded: TrustedIssuerAdded, + TrustedIssuerRemoved: TrustedIssuerRemoved, + ClaimTopicsUpdated: ClaimTopicsUpdated, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + #[derive(Drop, starknet::Event)] + pub struct TrustedIssuerAdded { + #[key] + trusted_issuer: ContractAddress, + claim_topics: Span, + } + + #[derive(Drop, starknet::Event)] + pub struct TrustedIssuerRemoved { + #[key] + trusted_issuer: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct ClaimTopicsUpdated { + #[key] + trusted_issuer: ContractAddress, + claim_topics: Span, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + /// Upgrades the implementation used by this contract. + /// + /// # Arguments + /// + /// - `new_class_hash` A `ClassHash` representing the implementation to update to. + /// + /// # Requirements + /// + /// - This function can only be called by the owner. + /// - The `ClassHash` should already have been declared. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgrades.upgrade(new_class_hash); + } + } + + #[abi(embed_v0)] + impl TrustedIssuersRegistryImpl of ITrustedIssuersRegistry { + fn add_trusted_issuer( + ref self: ContractState, trusted_issuer: ContractAddress, claim_topics: Span, + ) { + self.ownable.assert_only_owner(); + assert(trusted_issuer.is_non_zero(), 'Zero Address: Trusted Issuer'); + let trusted_issuer_claim_topics_storage_path = self + .trusted_issuer_claim_topics + .entry(trusted_issuer); + assert( + trusted_issuer_claim_topics_storage_path.len().is_zero(), + 'Trusted Issuer already exists', + ); + let claim_topics_len = claim_topics.len(); + assert(claim_topics_len.is_non_zero(), 'Claim topics cannot be empty'); + assert(claim_topics_len <= 15, 'Max 15 claim topics'); + let trusted_issuers_storage_path = self.trusted_issuers.deref(); + assert(trusted_issuers_storage_path.len() < 50, 'Max 50 trusted issuers'); + trusted_issuers_storage_path.append().write(trusted_issuer); + + for claim_topic in claim_topics { + trusted_issuer_claim_topics_storage_path.append().write(*claim_topic); + self + .claim_topics_to_trusted_issuers + .entry(*claim_topic) + .append() + .write(trusted_issuer); + }; + + self.emit(TrustedIssuerAdded { trusted_issuer, claim_topics }) + } + + fn remove_trusted_issuer(ref self: ContractState, trusted_issuer: ContractAddress) { + self.ownable.assert_only_owner(); + assert(trusted_issuer.is_non_zero(), 'Zero Address: Trusted Issuer'); + let claim_topics_storage_path = self.trusted_issuer_claim_topics.entry(trusted_issuer); + assert(claim_topics_storage_path.len().is_non_zero(), 'Trusted Issuer not exists'); + + /// Remove from trusted issuers vec + let trusted_issuers_storage_path = self.trusted_issuers.deref(); + for i in 0..trusted_issuers_storage_path.len() { + if trusted_issuers_storage_path.at(i).read() == trusted_issuer { + trusted_issuers_storage_path.delete(i); + break; + } + }; + + /// Clear claim topics to trusted issuers + for i in 0..claim_topics_storage_path.len() { + let claim_topic = claim_topics_storage_path.at(i).read(); + let claim_topic_trusted_issuers = self + .claim_topics_to_trusted_issuers + .entry(claim_topic); + for j in 0..claim_topic_trusted_issuers.len() { + let trusted_issuer_at_j = claim_topic_trusted_issuers.at(j).read(); + if trusted_issuer_at_j == trusted_issuer { + claim_topic_trusted_issuers.delete(j); + break; + } + }; + }; + + /// Clear trusted issuer claim topics + /// .clear() method leaves the storage dirty and just set len to 0. + self.trusted_issuer_claim_topics.entry(trusted_issuer).clear(); + self.emit(TrustedIssuerRemoved { trusted_issuer }); + } + + /// NOTE: This method first clears all the claim topics and related data then populates + /// again with newer data. inefficient for unchanging data. + fn update_issuer_claim_topics( + ref self: ContractState, trusted_issuer: ContractAddress, claim_topics: Span, + ) { + self.ownable.assert_only_owner(); + assert(trusted_issuer.is_non_zero(), 'Zero Address: Trusted Issuer'); + let claim_topics_storage_path = self.trusted_issuer_claim_topics.entry(trusted_issuer); + assert(claim_topics_storage_path.len().is_non_zero(), 'Trusted Issuer not exists'); + let claim_topics_len = claim_topics.len(); + assert(claim_topics_len.is_non_zero(), 'Claim topics cannot be empty'); + assert(claim_topics_len <= 15, 'Max 15 claim topics'); + + let claim_topics_storage_path = self.trusted_issuer_claim_topics.entry(trusted_issuer); + /// Deletes claim topics to + for i in 0..claim_topics_storage_path.len() { + let claim_topic = claim_topics_storage_path.at(i).read(); + let claim_topic_trusted_issuers = self + .claim_topics_to_trusted_issuers + .entry(claim_topic); + for j in 0..claim_topic_trusted_issuers.len() { + if claim_topic_trusted_issuers.at(j).read() == trusted_issuer { + claim_topic_trusted_issuers.delete(i); + break; + } + }; + }; + + claim_topics_storage_path.clear(); + /// Update trusted_issuer_claim_topics and claim_topics_to_trusted_issuers + for claim_topic in claim_topics { + claim_topics_storage_path.append().write(*claim_topic); + self + .claim_topics_to_trusted_issuers + .entry(*claim_topic) + .append() + .write(trusted_issuer); + }; + + self.emit(ClaimTopicsUpdated { trusted_issuer, claim_topics }); + } + + fn has_claim_topic( + self: @ContractState, issuer: ContractAddress, claim_topic: felt252, + ) -> bool { + let mut has_topic = false; + let claim_topics_storage_path = self.trusted_issuer_claim_topics.entry(issuer); + for i in 0..claim_topics_storage_path.len() { + if claim_topics_storage_path.at(i).read() == claim_topic { + has_topic = true; + break; + } + }; + has_topic + } + + fn is_trusted_issuer(self: @ContractState, issuer: ContractAddress) -> bool { + self.trusted_issuer_claim_topics.entry(issuer).len().is_non_zero() + } + + fn get_trusted_issuers(self: @ContractState) -> Span { + ContractAddressVecInto::into(self.trusted_issuers.deref()).span() + } + + fn get_trusted_issuers_for_claim_topic( + self: @ContractState, claim_topic: felt252, + ) -> Span { + ContractAddressVecInto::into(self.claim_topics_to_trusted_issuers.entry(claim_topic)) + .span() + } + + fn get_trusted_issuer_claim_topics( + self: @ContractState, trusted_issuer: ContractAddress, + ) -> Span { + FeltVecInto::into(self.trusted_issuer_claim_topics.entry(trusted_issuer)).span() + } + } }