From 78239de1e2e93abc3d2ca3c3cb6a7095c73891ab Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Fri, 11 Oct 2024 02:50:25 +0100 Subject: [PATCH 1/6] feat: kick off channel comp --- src/base/constants/errors.cairo | 6 + src/base/constants/types.cairo | 83 ++- src/base/token_uris.cairo | 1 + src/base/token_uris/channel_token_uri.cairo | 5 + src/channel.cairo | 2 + src/channel/channel.cairo | 567 ++++++++++++++++++++ src/channel/channelNFT.cairo | 134 +++++ src/community/community.cairo | 8 +- src/interfaces.cairo | 2 +- src/interfaces/IChannel.cairo | 29 + src/lib.cairo | 1 + src/presets.cairo | 1 + src/presets/channel.cairo | 19 + tests/test_channel.cairo | 462 ++++++++++++++++ 14 files changed, 1285 insertions(+), 35 deletions(-) create mode 100644 src/base/token_uris/channel_token_uri.cairo create mode 100644 src/channel.cairo create mode 100644 src/channel/channel.cairo create mode 100644 src/channel/channelNFT.cairo create mode 100644 src/interfaces/IChannel.cairo create mode 100644 src/presets/channel.cairo create mode 100644 tests/test_channel.cairo diff --git a/src/base/constants/errors.cairo b/src/base/constants/errors.cairo index 504fbe4..1dc96e9 100644 --- a/src/base/constants/errors.cairo +++ b/src/base/constants/errors.cairo @@ -36,4 +36,10 @@ pub mod Errors { pub const AUTO_RENEW_DURATION_ENDED: felt252 = 'Karst: auto renew ended!'; pub const INVALID_JOLT: felt252 = 'Karst: invalid jolt!'; pub const INVALID_JOLT_RECIPIENT: felt252 = 'Karst: not request recipient!'; + pub const NOT_CHANNEL_OWNER: felt252 = 'Channel: not channel owner'; + pub const NOT_CHANNEL_MODERATOR: felt252 = 'Channel: not channel moderator'; + pub const NOT_CHANNEL_MEMBER: felt252 = 'Channel: not channel member'; + pub const BANNED_FROM_CHANNEL: felt252 = 'Channel: banned from channel'; + pub const CHANNEL_HAS_NO_MEMBER: felt252 = 'Channel has no members'; + pub const UNAUTHORIZED_ACESS: felt252 = 'Karst : Unauthorized access'; } diff --git a/src/base/constants/types.cairo b/src/base/constants/types.cairo index 7fce36c..c29cc48 100644 --- a/src/base/constants/types.cairo +++ b/src/base/constants/types.cairo @@ -19,6 +19,27 @@ pub struct Profile { pub follow_nft: ContractAddress } +// ************************************************************************* +// FOLLOW +// ************************************************************************* + +// /** +// * @notice A struct containing token follow-related data. +// * +// * @param followed_profile_address The ID of the profile being followed. +// * @param follower_profile_address The ID of the profile following. +// * @param followTimestamp The timestamp of the current follow, if a profile is using the token to +// follow. +// * @param block_status true if follower is blocked, false otherwise +// */ +#[derive(Drop, Serde, starknet::Store)] +pub struct FollowData { + pub followed_profile_address: ContractAddress, + pub follower_profile_address: ContractAddress, + pub follow_timestamp: u64, + pub block_status: bool, +} + // ************************************************************************* // PUBLICATION // ************************************************************************* @@ -144,6 +165,20 @@ pub struct QuoteParams { pub reference_pub_type: PublicationType } +#[derive(Debug, Drop, Serde, starknet::Store, Clone)] +pub struct Upvote { + pub publication_id: u256, + pub transaction_executor: ContractAddress, + pub block_timestamp: u64, +} + +#[derive(Debug, Drop, Serde, starknet::Store, Clone)] +pub struct Downvote { + pub publication_id: u256, + pub transaction_executor: ContractAddress, + pub block_timestamp: u64, +} + // ************************************************************************* // COMMUNITY // ************************************************************************* @@ -201,39 +236,29 @@ pub enum CommunityType { Business } -#[derive(Debug, Drop, Serde, starknet::Store, Clone)] -pub struct Upvote { - pub publication_id: u256, - pub transaction_executor: ContractAddress, - pub block_timestamp: u64, -} +// ************************************************************************* +// CHANNEL +// ************************************************************************* -#[derive(Debug, Drop, Serde, starknet::Store, Clone)] -pub struct Downvote { - pub publication_id: u256, - pub transaction_executor: ContractAddress, - pub block_timestamp: u64, +#[derive(Drop, Serde, Clone, starknet::Store)] +pub struct channelDetails { + pub channel_id: u256, + pub community_id: u256, + pub channel_owner: ContractAddress, + pub channel_metadata_uri: ByteArray, + pub channel_nft_address: ContractAddress, + pub channel_total_members: u256, + pub channel_censorship: bool, } -// ************************************************************************* -// FOLLOW -// ************************************************************************* -// /** -// * @notice A struct containing token follow-related data. -// * -// * @param followed_profile_address The ID of the profile being followed. -// * @param follower_profile_address The ID of the profile following. -// * @param followTimestamp The timestamp of the current follow, if a profile is using the token to -// follow. -// * @param block_status true if follower is blocked, false otherwise -// */ -#[derive(Drop, Serde, starknet::Store)] -pub struct FollowData { - pub followed_profile_address: ContractAddress, - pub follower_profile_address: ContractAddress, - pub follow_timestamp: u64, - pub block_status: bool, +#[derive(Drop, Serde, Clone, starknet::Store)] +pub struct channelMember { + pub profile: ContractAddress, + pub channel_id: u256, + pub total_publications: u256, + pub channel_token_id: u256, + pub ban_status: bool, } // ************************************************************************* diff --git a/src/base/token_uris.cairo b/src/base/token_uris.cairo index 3f6fcdc..4e17bb0 100644 --- a/src/base/token_uris.cairo +++ b/src/base/token_uris.cairo @@ -2,4 +2,5 @@ pub mod follow_token_uri; pub mod handle_token_uri; pub mod profile_token_uri; pub mod community_token_uri; +pub mod channel_token_uri; pub mod traits; diff --git a/src/base/token_uris/channel_token_uri.cairo b/src/base/token_uris/channel_token_uri.cairo new file mode 100644 index 0000000..be300dc --- /dev/null +++ b/src/base/token_uris/channel_token_uri.cairo @@ -0,0 +1,5 @@ +pub mod ChannelTokenUri { + pub fn get_token_uri(token_id: u256, mint_timestamp: u64) -> ByteArray { + "TODO" + } +} diff --git a/src/channel.cairo b/src/channel.cairo new file mode 100644 index 0000000..4852e47 --- /dev/null +++ b/src/channel.cairo @@ -0,0 +1,2 @@ +pub mod channel; +pub mod channelNFT; \ No newline at end of file diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo new file mode 100644 index 0000000..db176a3 --- /dev/null +++ b/src/channel/channel.cairo @@ -0,0 +1,567 @@ +#[starknet::component] +pub mod ChannelComponent { + // ************************************************************************* + // IMPORT + // ************************************************************************* + use core::clone::Clone; + use core::starknet::{ + ContractAddress, contract_address_const, get_caller_address, get_block_timestamp, ClassHash, syscalls::deploy_syscall, SyscallResultTrait + }; + use starknet::storage::{ + StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry, Map, + StorageMapReadAccess, StorageMapWriteAccess, Vec, VecTrait, MutableVecTrait + }; + use karst::interfaces::IChannel::IChannel; + use karst::interfaces::ICommunity::ICommunity; + use karst::interfaces::ICommunityNft::{ICommunityNftDispatcher, ICommunityNftDispatcherTrait}; + use karst::community::community::CommunityComponent; + use karst::base::{ + constants::errors::Errors::{ + NOT_CHANNEL_OWNER, ALREADY_MEMBER, NOT_CHANNEL_MEMBER, NOT_MEMBER, BANNED_FROM_CHANNEL, + CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED_ACESS + }, + constants::types::{channelDetails, channelMember} + }; + + // ************************************************************************* + // STORAGE + // ************************************************************************* + #[storage] + pub struct Storage { + channels: Map, + channel_counter: u256, + channel_members: Map<(u256, ContractAddress), channelMember>, + channel_moderators: Map>, + channel_nft_classhash: ClassHash, + } + + // ************************************************************************* + // EVENTS + // ************************************************************************* + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + ChannelCreated: ChannelCreated, + JoinedChannel: JoinedChannel, + LeftChannel: LeftChannel, + ChannelModAdded: ChannelModAdded, + ChannelModRemoved: ChannelModRemoved, + ChannelBanStatusUpdated: ChannelBanStatusUpdated, + DeployedChannelNFT: DeployedChannelNFT + } + + #[derive(Drop, starknet::Event)] + pub struct ChannelCreated { + channel_id: u256, + channel_owner: ContractAddress, + channel_nft_address: ContractAddress, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct JoinedChannel { + channel_id: u256, + transaction_executor: ContractAddress, + token_id: u256, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct LeftChannel { + channel_id: u256, + transaction_executor: ContractAddress, + token_id: u256, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct ChannelModAdded { + channel_id: u256, + transaction_executor: ContractAddress, + mod_address: Array, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct ChannelModRemoved { + channel_id: u256, + transaction_executor: ContractAddress, + mod_address: Array, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct ChannelBanStatusUpdated { + channel_id: u256, + transaction_executor: ContractAddress, + profile: ContractAddress, + block_timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct DeployedChannelNFT { + pub channel_id: u256, + pub channel_nft: ContractAddress, + pub block_timestamp: u64, + } + + // ************************************************************************* + // EXTERNAL FUNCTIONS + // ************************************************************************* + #[embeddable_as(KarstChannel)] + impl ChannelImpl< + TContractState, + +HasComponent, + +Drop, + impl Community: CommunityComponent::HasComponent + > of IChannel> { + /// @notice creates a new channel + fn create_channel( + ref self: ComponentState, community_id: u256 + ) -> u256 { + let channel_id = self.channel_counter.read() + 1; + let channel_owner = get_caller_address(); + + let new_channel = channelDetails { + channel_id: channel_id, + community_id: community_id, + channel_owner: channel_owner, + channel_metadata_uri: "", + channel_nft_address: 1.try_into().unwrap(), + channel_total_members: 0, + channel_censorship: false, + }; + + // include channel owner as first member + self._join_channel(channel_owner, channel_id); + + // update storage + self.channels.write(channel_id, new_channel.clone()); + self.channel_counter.write(channel_id); + + // emit event + self + .emit( + ChannelCreated { + channel_id: new_channel.channel_id, + channel_owner: new_channel.channel_owner, + channel_nft_address: new_channel.channel_nft_address, + block_timestamp: get_block_timestamp(), + } + ); + + channel_id + } + + /// @notice adds a new user to a channel + /// @param channel_id id of channel to be joined + fn join_channel(ref self: ComponentState, channel_id: u256) { + let profile = get_caller_address(); + + // check user is not already a channel member and wasn't previously banned + let is_channel_member = self.is_channel_member(profile, channel_id); + let is_banned = self.get_ban_status(profile, channel_id); + assert(!is_channel_member, ALREADY_MEMBER); + assert(!is_banned, BANNED_FROM_CHANNEL); + + // join channel + self._join_channel(profile, channel_id); + } + + + /// @notice removes a member from a channel + /// @param channel_id id of channel to be left + fn leave_channel(ref self: ComponentState, channel_id: u256) { + // check that profile is a channel member + assert( + self + .channel_members + .read((channel_id, get_caller_address())) + .channel_id == channel_id + && self + .channel_members + .read((channel_id, get_caller_address())) + .profile == get_caller_address(), + NOT_CHANNEL_MEMBER + ); + + // check that channel has members + let total_members: u256 = self.channels.read(channel_id).channel_total_members; + assert(total_members > 1, CHANNEL_HAS_NO_MEMBER); + + // update storage + self + .channel_members + .write( + (channel_id, get_caller_address()), + channelMember { + profile: contract_address_const::<0>(), + channel_id: 0, + total_publications: 0, + channel_token_id: 0, + ban_status: false, + } + ); + + let mut channel: channelDetails = self.channels.read(channel_id); + channel.channel_total_members -= 1; + self.channels.write(channel_id, channel); + + //TODO Delete the mapping at the caller address + //TODO : burn the NFT + self + .emit( + LeftChannel { + channel_id: channel_id, + transaction_executor: get_caller_address(), + token_id: 0, //TODO impl token gating self.get_user_token_id(get_caller_address()), + block_timestamp: get_block_timestamp(), + } + ) + } + + + /// @notice Set the metadata URI of the channel + /// @param channel_id: The id of the channel + /// @param metadata_uri: The new metadata URI + /// @dev Only the owner of the channel can set the metadata URI + fn set_channel_metadata_uri( + ref self: ComponentState, channel_id: u256, metadata_uri: ByteArray + ) { + let channel_member: channelDetails = self.channels.read(channel_id); + assert( + channel_member.channel_owner == get_caller_address() + || self.is_channel_mod(get_caller_address(), channel_id), + UNAUTHORIZED_ACESS + ); + let mut channel: channelDetails = self.channels.read(channel_id); + channel.channel_metadata_uri = metadata_uri; + self.channels.write(channel_id, channel); + } + + + /// @notice Add a moderator to the channel + /// @param channel_id: The id of the channel + /// @param Array: The address of the moderator + /// dev only primary moderator/owner can add the moderators + fn add_channel_mods( + ref self: ComponentState, + channel_id: u256, + moderator: Array + ) { + assert( + self.channels.read(channel_id).channel_owner == get_caller_address(), + NOT_CHANNEL_OWNER + ); + + for i in 0 + ..moderator + .len() { + if (!self.is_channel_mod(*moderator.at(i), channel_id)) { + self + .channel_moderators + .entry(channel_id) + .append() + .write(*moderator.at(i)); + } + }; + self + .emit( + ChannelModAdded { + channel_id: channel_id, + transaction_executor: get_caller_address(), + mod_address: moderator, + block_timestamp: get_block_timestamp(), + } + ) + } + + + /// @notice Remove a moderator from the channel + /// @param channel_id: The id of the channel + /// @param moderator: The address of the moderator + /// dev only primary moderator/owner can remove the moderators + fn remove_channel_mods( + ref self: ComponentState, + channel_id: u256, + moderator: Array + ) { + assert( + self.channels.read(channel_id).channel_owner == get_caller_address(), + NOT_CHANNEL_OWNER + ); + for i in 0 + ..moderator + .len() { + if (self.is_channel_mod(*moderator.at(i), channel_id)) { + let mut channe_moderators = self.channel_moderators.entry(channel_id); + for j in 0 + ..channe_moderators + .len() { + if (channe_moderators.at(j).read() == *moderator.at(i)) { + // todo zero address set + channe_moderators + .at(j) + .write(contract_address_const::<0>()); + } + }; + } + }; + + // remove at the index thus making the best thing which i can made is the person who i + // can make the best place to make the system which is todo + // first know the element and then remove the function and delete the person + + self + .emit( + ChannelModRemoved { + channel_id: channel_id, + transaction_executor: get_caller_address(), + mod_address: moderator, + block_timestamp: get_block_timestamp(), + } + ) + } + + /// @notice Set the censorship status of the channel + /// @param channel_id: The id of the channel + fn set_channel_censorship_status( + ref self: ComponentState, channel_id: u256, censorship_status: bool + ) { + // let channel_member: channelDetails = self.channels.read(channel_id); + assert( + self.channels.read(channel_id).channel_owner == get_caller_address(), + NOT_CHANNEL_OWNER + ); + let mut channel: channelDetails = self.channels.read(channel_id); + channel.channel_censorship = censorship_status; + self.channels.write(channel_id, channel); + } + + + ///@notice Set the ban status of a profile in the channel + /// @param channel_id: The id of the channel + /// @param profile: The address of the profile + /// @param ban_status: The ban status of the profile + fn set_ban_status( + ref self: ComponentState, + channel_id: u256, + profile: ContractAddress, + ban_status: bool + ) { + // let channel_member: channelDetails = self.channels.read(channel_id); + assert( + self.channels.read(channel_id).channel_owner == get_caller_address() + || self.is_channel_mod(get_caller_address(), channel_id), + UNAUTHORIZED_ACESS + ); + // check that channel exits and the profile is a member of the channel + assert( + self.channel_members.read((channel_id, profile)).profile == profile + && self.channel_members.read((channel_id, profile)).channel_id == channel_id, + NOT_CHANNEL_MEMBER + ); + + let mut channel_member: channelMember = self + .channel_members + .read((channel_id, profile)); + channel_member.ban_status = ban_status; + self.channel_members.write((channel_id, profile), channel_member); + + self + .emit( + ChannelBanStatusUpdated { + channel_id: channel_id, + transaction_executor: get_caller_address(), + profile: profile, + block_timestamp: get_block_timestamp(), + } + ) + } + + + ///@notice Get the channel parameters + /// @param channel_id: The id of the channel + /// @return The channel parameters + fn get_channel(self: @ComponentState, channel_id: u256) -> channelDetails { + self.channels.read(channel_id) + } + + ///@notice Get the metadata URI of the channel + /// @param channel_id: The id of the channel + /// @return The metadata URI + fn get_channel_metadata_uri( + self: @ComponentState, channel_id: u256 + ) -> ByteArray { + let channel: channelDetails = self.channels.read(channel_id); + channel.channel_metadata_uri + } + + ///@notice Check if the profile is a member of the channel + ///@param profile: The address of the profile + ///@return A tuple of the membership status and the channel id + fn is_channel_member( + self: @ComponentState, profile: ContractAddress, channel_id: u256 + ) -> bool { + let channel_member: channelMember = self.channel_members.read((channel_id, profile)); + if (channel_member.channel_id == channel_id) { + true + } else { + false + } + } + + + ///TODO :get the total number of mener of the channel + fn get_total_members(self: @ComponentState, channel_id: u256) -> u256 { + let channel: channelDetails = self.channels.read(channel_id); + channel.channel_total_members + } + + ///@notice check for moderator + /// @param channel id + /// @param profile addresss + fn is_channel_mod( + self: @ComponentState, profile: ContractAddress, channel_id: u256 + ) -> bool { + let mut dd = self.channel_moderators.entry(channel_id); + let mut flag: bool = false; + for i in 0..dd.len() { + if (dd.at(i).read() == profile) { + flag = true; + break; + } + }; + flag + } + + fn get_channel_censorship_status( + self: @ComponentState, channel_id: u256 + ) -> bool { + let channel: channelDetails = self.channels.read(channel_id); + channel.channel_censorship + } + + fn get_ban_status( + self: @ComponentState, profile: ContractAddress, channel_id: u256 + ) -> bool { + let channel_member: channelMember = self.channel_members.read((channel_id, profile)); + channel_member.ban_status + } + } + + // ************************************************************************* + // PRIVATE FUNCTIONS + // ************************************************************************* + #[generate_trait] + pub impl InternalImpl< + TContractState, + +HasComponent, + +Drop, + impl Community: CommunityComponent::HasComponent + > of InternalTrait { + /// @notice initalizes channel component + /// @param channel_nft_classhash classhash of channel NFT + fn _initializer( + ref self: ComponentState, channel_nft_classhash: felt252 + ) { + self.channel_counter.write(0); + self.channel_nft_classhash.write(channel_nft_classhash.try_into().unwrap()); + } + + fn _join_channel( + ref self: ComponentState, + profile: ContractAddress, + channel_id: u256 + ) { + let mut channel: channelDetails = self.channels.read(channel_id); + + // check that user is a member of the community this channel belongs to + let community_instance = get_dep_component!(@self, Community); + let membership_status = community_instance.is_community_member(profile, channel.community_id); + assert(membership_status, NOT_MEMBER); + + let channel_member = channelMember { + profile: get_caller_address(), + channel_id: channel_id, + total_publications: 0, + channel_token_id: 0, + ban_status: false, + }; + + // mint a channel token to new joiner + let minted_token_id = self + ._mint_channel_nft(profile, channel.channel_nft_address); + + // update storage + channel.channel_total_members += 1; + self.channels.write(channel_id, channel); + self.channel_members.write((channel_id, profile), channel_member); + + // emit events + self + .emit( + JoinedChannel { + channel_id: channel_id, + transaction_executor: get_caller_address(), + token_id: minted_token_id, + block_timestamp: get_block_timestamp(), + } + ) + } + + /// @notice internal function to deploy a channel nft + /// @param channel_id id of channel + /// @param salt for randomization + fn _deploy_channel_nft( + ref self: ComponentState, + channel_id: u256, + channel_nft_impl_class_hash: ClassHash, + salt: felt252 + ) -> ContractAddress { + let mut constructor_calldata: Array = array![ + channel_id.low.into(), channel_id.high.into() + ]; + + let (account_address, _) = deploy_syscall( + channel_nft_impl_class_hash, salt, constructor_calldata.span(), true + ) + .unwrap_syscall(); + + self + .emit( + DeployedChannelNFT { + channel_id: channel_id, + channel_nft: account_address, + block_timestamp: get_block_timestamp() + } + ); + account_address + } + + /// @notice internal function to mint a channel nft + /// @param profile profile to be minted to + /// @param channel_nft_address address of channel nft + fn _mint_channel_nft( + ref self: ComponentState, + profile: ContractAddress, + community_nft_address: ContractAddress + ) -> u256 { + let token_id = ICommunityNftDispatcher { contract_address: community_nft_address } + .mint_nft(profile); + token_id + } + + /// @notice internal function to burn a channel nft + /// @param channel_nft_address address of channel nft + /// @param token_id to burn + fn _burn_community_nft( + ref self: ComponentState, + community_nft_address: ContractAddress, + token_id: u256 + ) { + ICommunityNftDispatcher { contract_address: community_nft_address } + .burn_nft(get_caller_address(), token_id); + } + } +} \ No newline at end of file diff --git a/src/channel/channelNFT.cairo b/src/channel/channelNFT.cairo new file mode 100644 index 0000000..77e480b --- /dev/null +++ b/src/channel/channelNFT.cairo @@ -0,0 +1,134 @@ +#[starknet::contract] +pub mod ChannelNFT { + // ************************************************************************* + // IMPORTS + // ************************************************************************* + use starknet::{ContractAddress, get_block_timestamp}; + use core::num::traits::zero::Zero; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; + + use karst::interfaces::ICommunityNft::ICommunityNft; + + use karst::base::{ + constants::errors::Errors::{ALREADY_MINTED, NOT_TOKEN_OWNER, TOKEN_DOES_NOT_EXIST}, + utils::base64_extended::convert_into_byteArray, + token_uris::channel_token_uri::ChannelTokenUri::get_token_uri, + }; + use starknet::storage::{ + Map, StoragePointerWriteAccess, StoragePointerReadAccess, StorageMapReadAccess, + StorageMapWriteAccess + }; + + // ************************************************************************* + // COMPONENTS + // ************************************************************************* + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 Mixin + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // ************************************************************************* + // STORAGE + // ************************************************************************* + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + last_minted_id: u256, + mint_timestamp: Map, + user_token_id: Map, + channel_id: u256 + } + + // ************************************************************************* + // EVENTS + // ************************************************************************* + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + // ************************************************************************* + // CONSTRUCTOR + // ************************************************************************* + #[constructor] + fn constructor(ref self: ContractState, channel_id: u256) { + self.channel_id.write(channel_id); + } + + #[abi(embed_v0)] + impl ChannelNFT of ICommunityNft { + // ************************************************************************* + // EXTERNAL + // ************************************************************************* + + /// @notice mints a channel NFT + /// @param address address of user trying to mint the channel NFT token + fn mint_nft(ref self: ContractState, user_address: ContractAddress) -> u256 { + let balance = self.erc721.balance_of(user_address); + assert(balance.is_zero(), ALREADY_MINTED); + + let mut token_id = self.last_minted_id.read() + 1; + self.erc721.mint(user_address, token_id); + let timestamp: u64 = get_block_timestamp(); + self.user_token_id.write(user_address, token_id); + + self.last_minted_id.write(token_id); + self.mint_timestamp.write(token_id, timestamp); + self.last_minted_id.read() + } + + /// @notice burns a channel NFT + /// @param address address of user trying to burn the channel NFT token + fn burn_nft(ref self: ContractState, user_address: ContractAddress, token_id: u256) { + let user_token_id = self.user_token_id.read(user_address); + assert(user_token_id == token_id, NOT_TOKEN_OWNER); + // check the token exists + assert(self.erc721.exists(token_id), TOKEN_DOES_NOT_EXIST); + self.erc721.burn(token_id); + self.user_token_id.write(user_address, 0); + } + + // ************************************************************************* + // GETTERS + // ************************************************************************* + /// @notice gets the token ID for a user address + /// @param user address of user to retrieve token ID for + fn get_user_token_id(self: @ContractState, user_address: ContractAddress) -> u256 { + self.user_token_id.read(user_address) + } + + // ************************************************************************* + // METADATA + // ************************************************************************* + /// @notice returns the channel name + fn name(self: @ContractState) -> ByteArray { + let mut collection_name = ArrayTrait::::new(); + let channel_id_felt252: felt252 = self.channel_id.read().try_into().unwrap(); + collection_name.append('Karst Channel | #'); + collection_name.append(channel_id_felt252); + let collection_name_byte = convert_into_byteArray(ref collection_name); + collection_name_byte + } + + /// @notice returns the collection symbol + fn symbol(self: @ContractState) -> ByteArray { + return "KARST:CHANNEL"; + } + + /// @notice returns the token_uri for a particular token_id + fn token_uri(self: @ContractState, token_id: u256) -> ByteArray { + let token_mint_timestamp = self.mint_timestamp.read(token_id); + get_token_uri(token_id, token_mint_timestamp) + } + } +} diff --git a/src/community/community.cairo b/src/community/community.cairo index 7c8b3fd..9f62b37 100644 --- a/src/community/community.cairo +++ b/src/community/community.cairo @@ -30,7 +30,6 @@ pub mod CommunityComponent { pub struct Storage { community_owner: Map, // map communities: Map, // map - member_community_id: Map, // map community_member: Map< (u256, ContractAddress), CommunityMember >, // map<(community_id, member address), Member_details> @@ -151,9 +150,8 @@ pub mod CommunityComponent { ref self: ComponentState, community_type: CommunityType ) -> u256 { let community_owner = get_caller_address(); - let community_counter = self.community_counter.read(); let community_nft_classhash = self.community_nft_classhash.read(); - let community_id = community_counter + 1; + let community_id = self.community_counter.read() + 1; let community_nft_address = self ._deploy_community_nft( @@ -181,7 +179,7 @@ pub mod CommunityComponent { self.communities.write(community_id, community_details); self.community_owner.write(community_id, community_owner); self.community_gate_keep.write(community_id, gate_keep_details); - self.community_counter.write(community_counter + 1); + self.community_counter.write(community_id); // upgrade if community type is not free if (community_type != CommunityType::Free) { @@ -266,7 +264,7 @@ pub mod CommunityComponent { community_id: 0, total_publications: 0, community_token_id: 0, - ban_status: true + ban_status: false }; self.community_member.write((community_id, profile), updated_member_details); let community_total_members = community.community_total_members - 1; diff --git a/src/interfaces.cairo b/src/interfaces.cairo index abf4891..f35a0c3 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -13,4 +13,4 @@ pub mod ICollectNFT; pub mod ICommunityNft; pub mod IJolt; pub mod IUpgradeable; - +pub mod IChannel; diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo new file mode 100644 index 0000000..476da21 --- /dev/null +++ b/src/interfaces/IChannel.cairo @@ -0,0 +1,29 @@ +use starknet::ContractAddress; +use karst::base::constants::types::channelDetails; + +#[starknet::interface] +pub trait IChannel { + // ************************************************************************* + // EXTERNALS + // ************************************************************************* + fn create_channel(ref self: TState, community_id: u256) -> u256; + fn join_channel(ref self: TState, channel_id: u256); + fn leave_channel(ref self: TState, channel_id: u256); + fn set_channel_metadata_uri(ref self: TState, channel_id: u256, metadata_uri: ByteArray); + fn add_channel_mods(ref self: TState, channel_id: u256, moderator: Array); + fn remove_channel_mods(ref self: TState, channel_id: u256, moderator: Array); + fn set_channel_censorship_status(ref self: TState, channel_id: u256, censorship_status: bool); + fn set_ban_status( + ref self: TState, channel_id: u256, profile: ContractAddress, ban_status: bool + ); + // ************************************************************************* + // GETTERS + // ************************************************************************* + fn get_channel(self: @TState, channel_id: u256) -> channelDetails; + fn get_channel_metadata_uri(self: @TState, channel_id: u256) -> ByteArray; + fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; + fn get_total_members(self: @TState, channel_id: u256) -> u256; + fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; + fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool; + fn get_ban_status(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index a521798..c443612 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -11,3 +11,4 @@ pub mod hub; pub mod jolt; pub mod collectnft; pub mod community; +pub mod channel; \ No newline at end of file diff --git a/src/presets.cairo b/src/presets.cairo index f6fee3f..c99076b 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,3 +1,4 @@ pub mod profile; pub mod publication; pub mod community; +pub mod channel; \ No newline at end of file diff --git a/src/presets/channel.cairo b/src/presets/channel.cairo new file mode 100644 index 0000000..6de505f --- /dev/null +++ b/src/presets/channel.cairo @@ -0,0 +1,19 @@ +#[starknet::contract] +pub mod KarstChannel { + use karst::channel::channel::ChannelComponent; + component!(path: ChannelComponent, storage: channel, event: ChannelEvent); + #[abi(embed_v0)] + impl channelImpl = ChannelComponent::KarstChannel; + #[storage] + struct Storage { + #[substorage(v0)] + channel: ChannelComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ChannelEvent: ChannelComponent::Event + } +} diff --git a/tests/test_channel.cairo b/tests/test_channel.cairo new file mode 100644 index 0000000..09812e8 --- /dev/null +++ b/tests/test_channel.cairo @@ -0,0 +1,462 @@ +// // ************************************************************************* +// // CHANNEL TEST +// // ************************************************************************* +// use core::option::OptionTrait; +// use core::starknet::SyscallResultTrait; +// use core::result::ResultTrait; +// use core::traits::{TryInto, Into}; +// use starknet::ContractAddress; + +// use snforge_std::{ +// declare, start_cheat_caller_address, stop_cheat_caller_address, spy_events, +// EventSpyAssertionsTrait, ContractClassTrait, DeclareResultTrait, EventSpy +// }; + + +// use karst::channel::channel::ChannelComponent::{ +// Event as ChannelEvent, ChannelCreated, JoinedChannel, LeftChannel, ChannelModAdded, +// ChannelModRemoved, ChannelBanStatusUpdated +// }; +// use karst::base::constants::types::{channelParams, channelMember}; +// use karst::interfaces::IChannel::{IChannelDispatcher, IChannelDispatcherTrait}; +// use karst::presets::channel; + +// const HUB_ADDRESS: felt252 = 'HUB'; +// const USER_ONE: felt252 = 'BOB'; +// const USER_TWO: felt252 = 'ALICE'; +// // these are the channel users +// const USER_THREE: felt252 = 'ROB'; +// const USER_FOUR: felt252 = 'DAN'; +// const USER_FIVE: felt252 = 'RANDY'; +// const USER_SIX: felt252 = 'JOE'; +// const MODERATOR1: felt252 = 'MOD1'; +// const MODERATOR2: felt252 = 'MOD2'; +// const NOTOWNER: felt252 = 'NOTOWNER'; +// const NOTMODERATOR: felt252 = 'NOTMODERATOR'; +// const MEMBER1: felt252 = 'MEMBER1'; +// const MEMBER2: felt252 = 'MEMBER2'; + +// fn __setup__() -> (ContractAddress, u256, ContractAddress, ByteArray) { +// let nft_contract = declare("KarstNFT").unwrap().contract_class(); +// let mut calldata: Array = array![USER_ONE]; + +// let (nft_contract_address, _) = nft_contract.deploy(@calldata).unwrap_syscall(); + +// let registry_class_hash = declare("Registry").unwrap().contract_class(); +// let (registry_contract_address, _) = registry_class_hash.deploy(@array![]).unwrap_syscall(); + +// let channel_contract = declare("KarstChannel").unwrap().contract_class(); + +// let mut channel_constructor_calldata = array![]; + +// let (channel_contract_address, _) = channel_contract +// .deploy(@channel_constructor_calldata) +// .unwrap_syscall(); +// let account_class_hash = declare("Account").unwrap().contract_class(); +// let follow_nft_classhash = declare("Follow").unwrap().contract_class(); +// let collect_nft_classhash = declare("CollectNFT").unwrap().contract_class(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// start_cheat_caller_address(channel_contract_address, USER_ONE.try_into().unwrap()); +// let mut spy = spy_events(); +// let metadat_uri: ByteArray = "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gQ/"; +// let channel_id: u256 = dispatcher +// .create_channel( +// channelParams { +// channel_id: 0, +// channel_owner: USER_ONE.try_into().unwrap(), +// channel_metadata_uri: metadat_uri.clone(), +// channel_nft_address: nft_contract_address, +// channel_total_members: 1, +// channel_censorship: false, +// } +// ); +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); + +// start_cheat_caller_address(channel_contract_address, MEMBER1.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// start_cheat_caller_address(channel_contract_address, MEMBER2.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); +// return (channel_contract_address, channel_id, USER_ONE.try_into().unwrap(), metadat_uri); +// } + + +// // writing the test for the set_channel_metadata_uri : +// #[test] +// fn test_set_channel_metadata_uri_check_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let metadat_uri: ByteArray = "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gH/"; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.set_channel_metadata_uri(channel_id, metadat_uri.clone()); +// stop_cheat_caller_address(channel_contract_address); +// let channel_metadata_uri = dispatcher.get_channel_metadata_uri(channel_id); +// assert(channel_metadata_uri == metadat_uri, 'invalid channel uri '); +// } + +// #[test] +// fn test_set_channel_metadata_uri_check_moderator() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let metadat_uri: ByteArray = "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gH/"; +// start_cheat_caller_address(channel_contract_address, MODERATOR1.try_into().unwrap()); +// dispatcher.set_channel_metadata_uri(channel_id, metadat_uri.clone()); +// stop_cheat_caller_address(channel_contract_address); +// // check the metadata uri +// let channel_metadata_uri = dispatcher.get_channel_metadata_uri(channel_id); +// assert(channel_metadata_uri == metadat_uri, 'invalid channel uri '); +// } + +// #[test] +// #[should_panic(expected: ('Karst : Unauthorized access',))] +// fn test_set_channel_metadata_uri_check_not_owner_or_moderator() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let metadat_uri: ByteArray = "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gH/"; +// start_cheat_caller_address(channel_contract_address, NOTOWNER.try_into().unwrap()); +// dispatcher.set_channel_metadata_uri(channel_id, metadat_uri.clone()); +// stop_cheat_caller_address(channel_contract_address); +// } + + +// // writing the test for the add_channel_mods() +// #[test] +// fn test_add_channel_mods_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); +// // check the moderator +// assert( +// dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == true, +// 'user_two is not mod' +// ); +// assert( +// dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == true, +// 'user_three isnt mod' +// ); +// } + + +// #[test] +// #[should_panic(expected: ('Channel: not channel owner',))] +// fn test_add_channel_mods_not_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, NOTOWNER.try_into().unwrap()); +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); +// } + +// // total number of the moderator fucntion is usefull . +// // then this test is usefull +// // #[test] +// // fn test_add_channel_mods_duplicate_moderator() { +// // let (channel_contract_address , channel_id , owner , metadata_uri ) = __setup__(); +// // let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// // let mut moderator_array = array![MODERATOR1.try_into().unwrap() , +// // MODERATOR1.try_into().unwrap()]; +// // start_cheat_caller_address(channel_contract_address , owner); +// // dispatcher.add_channel_mods(channel_id , moderator_array); +// // stop_cheat_caller_address(channel_contract_address); +// // // check the moderator +// // assert( +// // dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == true, +// // 'user_two is not mod' +// // ); +// // assert( +// // dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == true, +// // 'user_three isnt mod' +// // ); + +// // // add the moderator again +// // start_cheat_caller_address(channel_contract_address , owner); +// // dispatcher.add_channel_mods(channel_id , moderator_array); +// // stop_cheat_caller_address(channel_contract_address); +// // // check the moderator +// // let total_mod = dispatcher.total_mod_of_channel(channel_id); + +// // assert( +// // total_mod.len() == 2 , +// // 'user_two is not mod' +// // ); +// // } + +// // now writing the test for the remove channel mods +// #[test] +// fn test_remove_channel_mods_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); + +// // remove the moderator +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.remove_channel_mods(channel_id, array![MODERATOR1.try_into().unwrap()]); +// stop_cheat_caller_address(channel_contract_address); +// // check the moderator +// assert( +// dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == false, +// 'user_two is not mod' +// ); +// assert( +// dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == true, +// 'user_three isnt mod' +// ); +// } + +// // not the owner of the channel +// #[test] +// #[should_panic(expected: ('Channel: not channel owner',))] +// fn test_remove_channel_mods_not_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); + +// // remove the moderator +// start_cheat_caller_address(channel_contract_address, NOTOWNER.try_into().unwrap()); +// dispatcher.remove_channel_mods(channel_id, array![MODERATOR1.try_into().unwrap()]); +// stop_cheat_caller_address(channel_contract_address); +// // check the moderator +// assert( +// dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == true, +// 'user_two is not mod' +// ); +// assert( +// dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == true, +// 'user_three isnt mod' +// ); +// } +// // also test the moderator is not in the channel +// #[test] +// fn test_remove_channel_mods_not_moderator() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.add_channel_mods(channel_id, moderator_array); +// stop_cheat_caller_address(channel_contract_address); + +// // remove the moderator +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher +// .remove_channel_mods( +// channel_id, array![NOTMODERATOR.try_into().unwrap(), MODERATOR2.try_into().unwrap()] +// ); +// stop_cheat_caller_address(channel_contract_address); +// // check the moderator + +// assert( +// dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == true, +// 'user_two is not mod' +// ); +// assert( +// dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == false, +// 'user_three isnt mod' +// ); +// } + +// // checking about the persistance state of the moderator +// #[test] +// fn test_remove_channel_mods_persistance_state() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// let mut moderator_array = array![ +// MODERATOR1.try_into().unwrap(), MODERATOR2.try_into().unwrap() +// ]; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.add_channel_mods(channel_id, moderator_array.clone()); +// dispatcher.remove_channel_mods(channel_id, moderator_array.clone()); +// stop_cheat_caller_address(channel_contract_address); +// // check the moderator +// assert( +// dispatcher.is_channel_mod(MODERATOR1.try_into().unwrap(), channel_id) == false, +// 'user_two is not mod' +// ); +// assert( +// dispatcher.is_channel_mod(MODERATOR2.try_into().unwrap(), channel_id) == false, +// 'user_three isnt mod' +// ); +// } + + +// // set censorship to test +// #[test] +// fn test_set_channel_censorship_status_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.set_channel_censorship_status(channel_id, true); +// stop_cheat_caller_address(channel_contract_address); +// // check the censorship status +// let censorship_status = dispatcher.get_channel_censorship_status(channel_id); +// assert(censorship_status == true, 'invalid censorship status'); +// } +// // not owner of the channel +// #[test] +// #[should_panic(expected: ('Channel: not channel owner',))] +// fn test_set_channel_censorship_status_not_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// start_cheat_caller_address(channel_contract_address, NOTOWNER.try_into().unwrap()); +// dispatcher.set_channel_censorship_status(channel_id, true); +// stop_cheat_caller_address(channel_contract_address); +// // check the censorship status +// let censorship_status = dispatcher.get_channel_censorship_status(channel_id); +// assert(censorship_status == true, 'invalid censorship status'); +// } + +// // test for set_ban_status +// #[test] +// fn test_set_ban_status_owner() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.set_ban_status(channel_id, MEMBER1.try_into().unwrap(), true); +// stop_cheat_caller_address(channel_contract_address); +// // check the ban status +// let ban_status = dispatcher.get_ban_status(MEMBER1.try_into().unwrap(), channel_id); +// assert(ban_status == true, 'invalid ban status'); +// } + + +// #[test] +// #[should_panic(expected: ('Karst : Unauthorized access',))] +// fn test_set_ban_status_owner_or_moderator() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// start_cheat_caller_address(channel_contract_address, NOTOWNER.try_into().unwrap()); + +// dispatcher.set_ban_status(channel_id, MEMBER1.try_into().unwrap(), true); +// stop_cheat_caller_address(channel_contract_address); +// } + +// #[test] +// #[should_panic(expected: ('Channel: not channel member',))] +// fn test_set_ban_status_profile_is_not_member() { +// let (channel_contract_address, channel_id, owner, metadata_uri) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; +// start_cheat_caller_address(channel_contract_address, owner); +// dispatcher.set_ban_status(channel_id, USER_THREE.try_into().unwrap(), true); +// stop_cheat_caller_address(channel_contract_address); +// } + +// #[test] +// fn test_leave_channel_member() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// start_cheat_caller_address(channel_contract_address, MEMBER1.try_into().unwrap()); +// dispatcher.leave_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // not the member of that +// assert( +// !dispatcher.is_channel_member(MEMBER1.try_into().unwrap(), channel_id) == true, +// 'channel not leaved' +// ); +// } + + +// #[test] +// #[should_panic(expected: ('Channel: not channel member',))] +// fn test_leave_channel_not_member() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// start_cheat_caller_address(channel_contract_address, USER_FOUR.try_into().unwrap()); +// dispatcher.leave_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); +// } + + +// // joining the channel testing +// #[test] +// fn test_joining_channel() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// let is_channel_member = dispatcher.is_channel_member(MEMBER1.try_into().unwrap(), channel_id); +// assert(is_channel_member == true, 'invalid channel member 1'); + +// let is_channel_member = dispatcher.is_channel_member(MEMBER2.try_into().unwrap(), channel_id); +// assert(is_channel_member == true, 'invalid channel member 2'); +// } + + +// // // counting of the member of the channel . +// #[test] +// fn test_joining_channel_total_members() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// // join the channel +// start_cheat_caller_address(channel_contract_address, USER_TWO.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // join the channel +// start_cheat_caller_address(channel_contract_address, USER_THREE.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // join the channel +// start_cheat_caller_address(channel_contract_address, USER_FOUR.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // join the channel +// start_cheat_caller_address(channel_contract_address, USER_FIVE.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // join the channel +// start_cheat_caller_address(channel_contract_address, USER_SIX.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // check the total member of the channel +// let total_members = dispatcher.get_total_members(channel_id); +// assert(total_members == 7, 'invalid total members'); +// } + +// #[test] +// #[should_panic(expected: ('Channel has no members',))] +// fn test_leave_channel_less_then_one() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// start_cheat_caller_address(channel_contract_address, MEMBER1.try_into().unwrap()); +// dispatcher.leave_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// start_cheat_caller_address(channel_contract_address, MEMBER2.try_into().unwrap()); +// dispatcher.leave_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); +// } From 2cf634bb1cc0dc18969218097ff8dc65c2c9648f Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Fri, 11 Oct 2024 16:52:07 +0100 Subject: [PATCH 2/6] feat: complete channel --- src/base/constants/errors.cairo | 12 +- src/base/constants/types.cairo | 4 +- src/channel/channel.cairo | 373 ++++++++++++++++++-------------- src/interfaces/IChannel.cairo | 15 +- src/presets/channel.cairo | 11 +- 5 files changed, 236 insertions(+), 179 deletions(-) diff --git a/src/base/constants/errors.cairo b/src/base/constants/errors.cairo index 1dc96e9..3d26edb 100644 --- a/src/base/constants/errors.cairo +++ b/src/base/constants/errors.cairo @@ -36,10 +36,10 @@ pub mod Errors { pub const AUTO_RENEW_DURATION_ENDED: felt252 = 'Karst: auto renew ended!'; pub const INVALID_JOLT: felt252 = 'Karst: invalid jolt!'; pub const INVALID_JOLT_RECIPIENT: felt252 = 'Karst: not request recipient!'; - pub const NOT_CHANNEL_OWNER: felt252 = 'Channel: not channel owner'; - pub const NOT_CHANNEL_MODERATOR: felt252 = 'Channel: not channel moderator'; - pub const NOT_CHANNEL_MEMBER: felt252 = 'Channel: not channel member'; - pub const BANNED_FROM_CHANNEL: felt252 = 'Channel: banned from channel'; - pub const CHANNEL_HAS_NO_MEMBER: felt252 = 'Channel has no members'; - pub const UNAUTHORIZED_ACESS: felt252 = 'Karst : Unauthorized access'; + pub const NOT_CHANNEL_OWNER: felt252 = 'Karst: not channel owner'; + pub const NOT_CHANNEL_MODERATOR: felt252 = 'Karst: not channel moderator'; + pub const NOT_CHANNEL_MEMBER: felt252 = 'Karst: not channel member'; + pub const BANNED_FROM_CHANNEL: felt252 = 'Karst: banned from channel'; + pub const CHANNEL_HAS_NO_MEMBER: felt252 = 'Karst: channel has no members'; + pub const INVALID_LENGTH: felt252 = 'Karst: array mismatch'; } diff --git a/src/base/constants/types.cairo b/src/base/constants/types.cairo index c29cc48..1116b99 100644 --- a/src/base/constants/types.cairo +++ b/src/base/constants/types.cairo @@ -241,7 +241,7 @@ pub enum CommunityType { // ************************************************************************* #[derive(Drop, Serde, Clone, starknet::Store)] -pub struct channelDetails { +pub struct ChannelDetails { pub channel_id: u256, pub community_id: u256, pub channel_owner: ContractAddress, @@ -253,7 +253,7 @@ pub struct channelDetails { #[derive(Drop, Serde, Clone, starknet::Store)] -pub struct channelMember { +pub struct ChannelMember { pub profile: ContractAddress, pub channel_id: u256, pub total_publications: u256, diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index db176a3..68275e2 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -8,8 +8,8 @@ pub mod ChannelComponent { ContractAddress, contract_address_const, get_caller_address, get_block_timestamp, ClassHash, syscalls::deploy_syscall, SyscallResultTrait }; use starknet::storage::{ - StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry, Map, - StorageMapReadAccess, StorageMapWriteAccess, Vec, VecTrait, MutableVecTrait + StoragePointerReadAccess, StoragePointerWriteAccess, Map, + StorageMapReadAccess, StorageMapWriteAccess }; use karst::interfaces::IChannel::IChannel; use karst::interfaces::ICommunity::ICommunity; @@ -18,9 +18,9 @@ pub mod ChannelComponent { use karst::base::{ constants::errors::Errors::{ NOT_CHANNEL_OWNER, ALREADY_MEMBER, NOT_CHANNEL_MEMBER, NOT_MEMBER, BANNED_FROM_CHANNEL, - CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED_ACESS + CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED, INVALID_LENGTH }, - constants::types::{channelDetails, channelMember} + constants::types::{ChannelDetails, ChannelMember} }; // ************************************************************************* @@ -28,10 +28,10 @@ pub mod ChannelComponent { // ************************************************************************* #[storage] pub struct Storage { - channels: Map, + channels: Map, channel_counter: u256, - channel_members: Map<(u256, ContractAddress), channelMember>, - channel_moderators: Map>, + channel_members: Map<(u256, ContractAddress), ChannelMember>, + channel_moderators: Map<(u256, ContractAddress), bool>, channel_nft_classhash: ClassHash, } @@ -62,6 +62,7 @@ pub mod ChannelComponent { pub struct JoinedChannel { channel_id: u256, transaction_executor: ContractAddress, + profile: ContractAddress, token_id: u256, block_timestamp: u64, } @@ -70,6 +71,7 @@ pub mod ChannelComponent { pub struct LeftChannel { channel_id: u256, transaction_executor: ContractAddress, + profile: ContractAddress, token_id: u256, block_timestamp: u64, } @@ -78,7 +80,7 @@ pub mod ChannelComponent { pub struct ChannelModAdded { channel_id: u256, transaction_executor: ContractAddress, - mod_address: Array, + mod_address: ContractAddress, block_timestamp: u64, } @@ -86,7 +88,7 @@ pub mod ChannelComponent { pub struct ChannelModRemoved { channel_id: u256, transaction_executor: ContractAddress, - mod_address: Array, + mod_address: ContractAddress, block_timestamp: u64, } @@ -95,6 +97,7 @@ pub mod ChannelComponent { channel_id: u256, transaction_executor: ContractAddress, profile: ContractAddress, + ban_status: bool, block_timestamp: u64, } @@ -122,7 +125,7 @@ pub mod ChannelComponent { let channel_id = self.channel_counter.read() + 1; let channel_owner = get_caller_address(); - let new_channel = channelDetails { + let new_channel = ChannelDetails { channel_id: channel_id, community_id: community_id, channel_owner: channel_owner, @@ -160,7 +163,7 @@ pub mod ChannelComponent { // check user is not already a channel member and wasn't previously banned let is_channel_member = self.is_channel_member(profile, channel_id); - let is_banned = self.get_ban_status(profile, channel_id); + let is_banned = self.get_channel_ban_status(profile, channel_id); assert(!is_channel_member, ALREADY_MEMBER); assert(!is_banned, BANNED_FROM_CHANNEL); @@ -172,29 +175,29 @@ pub mod ChannelComponent { /// @notice removes a member from a channel /// @param channel_id id of channel to be left fn leave_channel(ref self: ComponentState, channel_id: u256) { + let mut channel = self.channels.read(channel_id); + let profile = get_caller_address(); + let channel_member = self.channel_members.read((channel_id, profile)); + // check that profile is a channel member - assert( - self - .channel_members - .read((channel_id, get_caller_address())) - .channel_id == channel_id - && self - .channel_members - .read((channel_id, get_caller_address())) - .profile == get_caller_address(), - NOT_CHANNEL_MEMBER - ); + assert(self.is_channel_member(profile, channel_id), NOT_CHANNEL_MEMBER); // check that channel has members - let total_members: u256 = self.channels.read(channel_id).channel_total_members; + let total_members: u256 = channel.channel_total_members; assert(total_members > 1, CHANNEL_HAS_NO_MEMBER); + // burn user's community token + self + ._burn_channel_nft( + channel.channel_nft_address, channel_member.channel_token_id + ); + // update storage self .channel_members .write( - (channel_id, get_caller_address()), - channelMember { + (channel_id, profile), + ChannelMember { profile: contract_address_const::<0>(), channel_id: 0, total_publications: 0, @@ -203,18 +206,17 @@ pub mod ChannelComponent { } ); - let mut channel: channelDetails = self.channels.read(channel_id); channel.channel_total_members -= 1; self.channels.write(channel_id, channel); - //TODO Delete the mapping at the caller address - //TODO : burn the NFT + // emit event self .emit( LeftChannel { channel_id: channel_id, transaction_executor: get_caller_address(), - token_id: 0, //TODO impl token gating self.get_user_token_id(get_caller_address()), + profile: profile, + token_id: channel_member.channel_token_id, block_timestamp: get_block_timestamp(), } ) @@ -222,19 +224,19 @@ pub mod ChannelComponent { /// @notice Set the metadata URI of the channel - /// @param channel_id: The id of the channel - /// @param metadata_uri: The new metadata URI - /// @dev Only the owner of the channel can set the metadata URI + /// @param channel_id The id of the channel + /// @param metadata_uri The new metadata URI + /// @dev Only the owner of the channel or a mod can set the metadata URI fn set_channel_metadata_uri( ref self: ComponentState, channel_id: u256, metadata_uri: ByteArray ) { - let channel_member: channelDetails = self.channels.read(channel_id); + let channel_member: ChannelDetails = self.channels.read(channel_id); assert( channel_member.channel_owner == get_caller_address() || self.is_channel_mod(get_caller_address(), channel_id), - UNAUTHORIZED_ACESS + UNAUTHORIZED ); - let mut channel: channelDetails = self.channels.read(channel_id); + let mut channel: ChannelDetails = self.channels.read(channel_id); channel.channel_metadata_uri = metadata_uri; self.channels.write(channel_id, channel); } @@ -242,38 +244,19 @@ pub mod ChannelComponent { /// @notice Add a moderator to the channel /// @param channel_id: The id of the channel - /// @param Array: The address of the moderator + /// @param Array The address of the moderator /// dev only primary moderator/owner can add the moderators fn add_channel_mods( ref self: ComponentState, channel_id: u256, - moderator: Array + moderators: Array ) { assert( self.channels.read(channel_id).channel_owner == get_caller_address(), NOT_CHANNEL_OWNER ); - for i in 0 - ..moderator - .len() { - if (!self.is_channel_mod(*moderator.at(i), channel_id)) { - self - .channel_moderators - .entry(channel_id) - .append() - .write(*moderator.at(i)); - } - }; - self - .emit( - ChannelModAdded { - channel_id: channel_id, - transaction_executor: get_caller_address(), - mod_address: moderator, - block_timestamp: get_block_timestamp(), - } - ) + self._add_channel_mods(channel_id, moderators); } @@ -284,126 +267,82 @@ pub mod ChannelComponent { fn remove_channel_mods( ref self: ComponentState, channel_id: u256, - moderator: Array + moderators: Array ) { assert( self.channels.read(channel_id).channel_owner == get_caller_address(), NOT_CHANNEL_OWNER ); - for i in 0 - ..moderator - .len() { - if (self.is_channel_mod(*moderator.at(i), channel_id)) { - let mut channe_moderators = self.channel_moderators.entry(channel_id); - for j in 0 - ..channe_moderators - .len() { - if (channe_moderators.at(j).read() == *moderator.at(i)) { - // todo zero address set - channe_moderators - .at(j) - .write(contract_address_const::<0>()); - } - }; - } - }; - - // remove at the index thus making the best thing which i can made is the person who i - // can make the best place to make the system which is todo - // first know the element and then remove the function and delete the person - self - .emit( - ChannelModRemoved { - channel_id: channel_id, - transaction_executor: get_caller_address(), - mod_address: moderator, - block_timestamp: get_block_timestamp(), - } - ) + self._remove_channel_mods(channel_id, moderators); } /// @notice Set the censorship status of the channel - /// @param channel_id: The id of the channel + /// @param channel_id The id of the channel fn set_channel_censorship_status( ref self: ComponentState, channel_id: u256, censorship_status: bool ) { - // let channel_member: channelDetails = self.channels.read(channel_id); + let mut channel = self.channels.read(channel_id); + + // check caller is owner assert( - self.channels.read(channel_id).channel_owner == get_caller_address(), + channel.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER ); - let mut channel: channelDetails = self.channels.read(channel_id); + + // update storage channel.channel_censorship = censorship_status; self.channels.write(channel_id, channel); } - ///@notice Set the ban status of a profile in the channel - /// @param channel_id: The id of the channel - /// @param profile: The address of the profile - /// @param ban_status: The ban status of the profile - fn set_ban_status( + /// @notice set the ban status of a profile in the channel + /// @param channel_id The id of the channel + /// @param profile The address of the profile + /// @param ban_status The ban status of the profile + fn set_channel_ban_status( ref self: ComponentState, channel_id: u256, - profile: ContractAddress, - ban_status: bool + profiles: Array, + ban_statuses: Array ) { - // let channel_member: channelDetails = self.channels.read(channel_id); + let mut channel = self.channels.read(channel_id); + + // check caller is owner or mod assert( - self.channels.read(channel_id).channel_owner == get_caller_address() + channel.channel_owner == get_caller_address() || self.is_channel_mod(get_caller_address(), channel_id), - UNAUTHORIZED_ACESS - ); - // check that channel exits and the profile is a member of the channel - assert( - self.channel_members.read((channel_id, profile)).profile == profile - && self.channel_members.read((channel_id, profile)).channel_id == channel_id, - NOT_CHANNEL_MEMBER + UNAUTHORIZED ); - let mut channel_member: channelMember = self - .channel_members - .read((channel_id, profile)); - channel_member.ban_status = ban_status; - self.channel_members.write((channel_id, profile), channel_member); - - self - .emit( - ChannelBanStatusUpdated { - channel_id: channel_id, - transaction_executor: get_caller_address(), - profile: profile, - block_timestamp: get_block_timestamp(), - } - ) + self._set_ban_status(channel_id, profiles, ban_statuses); } - ///@notice Get the channel parameters - /// @param channel_id: The id of the channel - /// @return The channel parameters - fn get_channel(self: @ComponentState, channel_id: u256) -> channelDetails { + /// @notice gets the channel parameters + /// @param channel_id The id of the channel + /// @return ChannelDetails The channel parameters + fn get_channel(self: @ComponentState, channel_id: u256) -> ChannelDetails { self.channels.read(channel_id) } - ///@notice Get the metadata URI of the channel - /// @param channel_id: The id of the channel - /// @return The metadata URI + /// @notice gets the metadata URI of the channel + /// @param channel_id The id of the channel + /// @return ByteArray The metadata URI fn get_channel_metadata_uri( self: @ComponentState, channel_id: u256 ) -> ByteArray { - let channel: channelDetails = self.channels.read(channel_id); - channel.channel_metadata_uri + self.channels.read(channel_id).channel_metadata_uri } - ///@notice Check if the profile is a member of the channel - ///@param profile: The address of the profile - ///@return A tuple of the membership status and the channel id + /// @notice checks if the profile is a member of the channel + /// @param profile the address of the profile + /// @param channel_id the id of the channel + /// @return bool the profile membership status fn is_channel_member( self: @ComponentState, profile: ContractAddress, channel_id: u256 ) -> bool { - let channel_member: channelMember = self.channel_members.read((channel_id, profile)); + let channel_member: ChannelMember = self.channel_members.read((channel_id, profile)); if (channel_member.channel_id == channel_id) { true } else { @@ -411,41 +350,40 @@ pub mod ChannelComponent { } } - - ///TODO :get the total number of mener of the channel + /// @notice gets the total number of members in a channel + /// @param channel_id the id of the channel + /// @return u256 the number of members in a channel fn get_total_members(self: @ComponentState, channel_id: u256) -> u256 { - let channel: channelDetails = self.channels.read(channel_id); - channel.channel_total_members + self.channels.read(channel_id).channel_total_members } - ///@notice check for moderator - /// @param channel id - /// @param profile addresss + /// @notice checks if a profile is a moderator + /// @param profile addresss to be checked + /// @param channel_id the id of the channel + /// @return bool the moderator status fn is_channel_mod( self: @ComponentState, profile: ContractAddress, channel_id: u256 ) -> bool { - let mut dd = self.channel_moderators.entry(channel_id); - let mut flag: bool = false; - for i in 0..dd.len() { - if (dd.at(i).read() == profile) { - flag = true; - break; - } - }; - flag + self.channel_moderators.read((channel_id, profile)) } + /// @notice checks if a channel is censored + /// @param channel_id the id of the channel + /// @return bool the censorship status fn get_channel_censorship_status( self: @ComponentState, channel_id: u256 ) -> bool { - let channel: channelDetails = self.channels.read(channel_id); - channel.channel_censorship + self.channels.read(channel_id).channel_censorship } - fn get_ban_status( + /// @notice checks if a profile is banned + /// @param profile addresss to be checked + /// @param channel_id the id of the channel + /// @return bool the ban status + fn get_channel_ban_status( self: @ComponentState, profile: ContractAddress, channel_id: u256 ) -> bool { - let channel_member: channelMember = self.channel_members.read((channel_id, profile)); + let channel_member: ChannelMember = self.channel_members.read((channel_id, profile)); channel_member.ban_status } } @@ -469,19 +407,22 @@ pub mod ChannelComponent { self.channel_nft_classhash.write(channel_nft_classhash.try_into().unwrap()); } + /// @notice internal function to join a channel + /// @param profile address to add to the channel + /// @param channel_id id of the channel to be joined fn _join_channel( ref self: ComponentState, profile: ContractAddress, channel_id: u256 ) { - let mut channel: channelDetails = self.channels.read(channel_id); + let mut channel: ChannelDetails = self.channels.read(channel_id); // check that user is a member of the community this channel belongs to let community_instance = get_dep_component!(@self, Community); let membership_status = community_instance.is_community_member(profile, channel.community_id); assert(membership_status, NOT_MEMBER); - let channel_member = channelMember { + let channel_member = ChannelMember { profile: get_caller_address(), channel_id: channel_id, total_publications: 0, @@ -504,12 +445,118 @@ pub mod ChannelComponent { JoinedChannel { channel_id: channel_id, transaction_executor: get_caller_address(), + profile: profile, token_id: minted_token_id, block_timestamp: get_block_timestamp(), } ) } + /// @notice internal function for adding channel mod + /// @param channel_id id of channel + /// @param moderators array of moderators + fn _add_channel_mods( + ref self: ComponentState, + channel_id: u256, + moderators: Array + ) { + let length = moderators.len(); + let mut index: u32 = 0; + + while index < length { + let moderator = *moderators.at(index); + let is_channel_member = self.is_channel_member(moderator, channel_id); + assert(is_channel_member == true, NOT_MEMBER); + self.channel_moderators.write((channel_id, moderator), true); + + // emit event + self + .emit( + ChannelModAdded { + channel_id: channel_id, + transaction_executor: get_caller_address(), + mod_address: moderator, + block_timestamp: get_block_timestamp(), + } + ); + index += 1; + }; + } + + /// @notice internal function for removing channel mod + /// @param channel_id id of channel + // @param moderators to remove + fn _remove_channel_mods( + ref self: ComponentState, + channel_id: u256, + moderators: Array + ) { + let length = moderators.len(); + let mut index: u32 = 0; + + while index < length { + let moderator = *moderators.at(index); + let is_channel_member = self.is_channel_member(moderator, channel_id); + assert(is_channel_member == true, NOT_MEMBER); + + self.channel_moderators.write((channel_id, moderator), false); + + // emit event + self + .emit( + ChannelModRemoved { + channel_id: channel_id, + mod_address: moderator, + transaction_executor: get_caller_address(), + block_timestamp: get_block_timestamp() + } + ); + index += 1; + }; + } + + /// @notice internal function for set ban status for members + /// @param channel_id id of channel to be banned or unbanned + /// @param profiles addresses + /// @param ban_statuses bool values + fn _set_ban_status( + ref self: ComponentState, + channel_id: u256, + profiles: Array, + ban_statuses: Array + ) { + assert(profiles.len() == ban_statuses.len(), INVALID_LENGTH); + let length = profiles.len(); + let mut index: u32 = 0; + + while index < length { + let profile = *profiles[index]; + let ban_status = *ban_statuses[index]; + + // check profile is a channel member + let is_channel_member = self.is_channel_member(profile, channel_id); + assert(is_channel_member == true, NOT_MEMBER); + + // update storage + let channel_member = self.channel_members.read((channel_id, profile)); + let updated_member = ChannelMember { ban_status: ban_status, ..channel_member }; + self.channel_members.write((channel_id, profile), updated_member); + + // emit event + self + .emit( + ChannelBanStatusUpdated { + channel_id: channel_id, + transaction_executor: get_caller_address(), + profile: profile, + ban_status: ban_status, + block_timestamp: get_block_timestamp() + } + ); + index += 1; + }; + } + /// @notice internal function to deploy a channel nft /// @param channel_id id of channel /// @param salt for randomization @@ -545,9 +592,9 @@ pub mod ChannelComponent { fn _mint_channel_nft( ref self: ComponentState, profile: ContractAddress, - community_nft_address: ContractAddress + channel_nft_address: ContractAddress ) -> u256 { - let token_id = ICommunityNftDispatcher { contract_address: community_nft_address } + let token_id = ICommunityNftDispatcher { contract_address: channel_nft_address } .mint_nft(profile); token_id } @@ -555,12 +602,12 @@ pub mod ChannelComponent { /// @notice internal function to burn a channel nft /// @param channel_nft_address address of channel nft /// @param token_id to burn - fn _burn_community_nft( + fn _burn_channel_nft( ref self: ComponentState, - community_nft_address: ContractAddress, + channel_nft_address: ContractAddress, token_id: u256 ) { - ICommunityNftDispatcher { contract_address: community_nft_address } + ICommunityNftDispatcher { contract_address: channel_nft_address } .burn_nft(get_caller_address(), token_id); } } diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo index 476da21..d0f190a 100644 --- a/src/interfaces/IChannel.cairo +++ b/src/interfaces/IChannel.cairo @@ -1,5 +1,5 @@ use starknet::ContractAddress; -use karst::base::constants::types::channelDetails; +use karst::base::constants::types::ChannelDetails; #[starknet::interface] pub trait IChannel { @@ -10,20 +10,21 @@ pub trait IChannel { fn join_channel(ref self: TState, channel_id: u256); fn leave_channel(ref self: TState, channel_id: u256); fn set_channel_metadata_uri(ref self: TState, channel_id: u256, metadata_uri: ByteArray); - fn add_channel_mods(ref self: TState, channel_id: u256, moderator: Array); - fn remove_channel_mods(ref self: TState, channel_id: u256, moderator: Array); + fn add_channel_mods(ref self: TState, channel_id: u256, moderators: Array); + fn remove_channel_mods(ref self: TState, channel_id: u256, moderators: Array); fn set_channel_censorship_status(ref self: TState, channel_id: u256, censorship_status: bool); - fn set_ban_status( - ref self: TState, channel_id: u256, profile: ContractAddress, ban_status: bool + fn set_channel_ban_status( + ref self: TState, channel_id: u256, profiles: Array, + ban_statuses: Array ); // ************************************************************************* // GETTERS // ************************************************************************* - fn get_channel(self: @TState, channel_id: u256) -> channelDetails; + fn get_channel(self: @TState, channel_id: u256) -> ChannelDetails; fn get_channel_metadata_uri(self: @TState, channel_id: u256) -> ByteArray; fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; fn get_total_members(self: @TState, channel_id: u256) -> u256; fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool; - fn get_ban_status(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; + fn get_channel_ban_status(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; } \ No newline at end of file diff --git a/src/presets/channel.cairo b/src/presets/channel.cairo index 6de505f..44f7398 100644 --- a/src/presets/channel.cairo +++ b/src/presets/channel.cairo @@ -1,19 +1,28 @@ #[starknet::contract] pub mod KarstChannel { use karst::channel::channel::ChannelComponent; + use karst::community::community::CommunityComponent; + component!(path: ChannelComponent, storage: channel, event: ChannelEvent); + component!(path: CommunityComponent, storage: community, event: CommunityEvent); + #[abi(embed_v0)] impl channelImpl = ChannelComponent::KarstChannel; + #[storage] struct Storage { #[substorage(v0)] channel: ChannelComponent::Storage, + #[substorage(v0)] + community: CommunityComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] - ChannelEvent: ChannelComponent::Event + ChannelEvent: ChannelComponent::Event, + #[flat] + CommunityEvent: CommunityComponent::Event } } From 627f48d9e61972101a9a6505c4c5c4e774c7af1c Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Fri, 11 Oct 2024 16:53:48 +0100 Subject: [PATCH 3/6] chore: scarb fmt --- src/channel.cairo | 2 +- src/channel/channel.cairo | 67 ++++++++++++++--------------------- src/interfaces/IChannel.cairo | 6 ++-- src/lib.cairo | 2 +- src/presets.cairo | 2 +- src/presets/channel.cairo | 2 +- tests/test_channel.cairo | 15 +++----- 7 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/channel.cairo b/src/channel.cairo index 4852e47..480cc64 100644 --- a/src/channel.cairo +++ b/src/channel.cairo @@ -1,2 +1,2 @@ pub mod channel; -pub mod channelNFT; \ No newline at end of file +pub mod channelNFT; diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index 68275e2..85f13ad 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -5,11 +5,12 @@ pub mod ChannelComponent { // ************************************************************************* use core::clone::Clone; use core::starknet::{ - ContractAddress, contract_address_const, get_caller_address, get_block_timestamp, ClassHash, syscalls::deploy_syscall, SyscallResultTrait + ContractAddress, contract_address_const, get_caller_address, get_block_timestamp, ClassHash, + syscalls::deploy_syscall, SyscallResultTrait }; use starknet::storage::{ - StoragePointerReadAccess, StoragePointerWriteAccess, Map, - StorageMapReadAccess, StorageMapWriteAccess + StoragePointerReadAccess, StoragePointerWriteAccess, Map, StorageMapReadAccess, + StorageMapWriteAccess }; use karst::interfaces::IChannel::IChannel; use karst::interfaces::ICommunity::ICommunity; @@ -113,15 +114,13 @@ pub mod ChannelComponent { // ************************************************************************* #[embeddable_as(KarstChannel)] impl ChannelImpl< - TContractState, - +HasComponent, + TContractState, + +HasComponent, +Drop, impl Community: CommunityComponent::HasComponent > of IChannel> { /// @notice creates a new channel - fn create_channel( - ref self: ComponentState, community_id: u256 - ) -> u256 { + fn create_channel(ref self: ComponentState, community_id: u256) -> u256 { let channel_id = self.channel_counter.read() + 1; let channel_owner = get_caller_address(); @@ -171,7 +170,6 @@ pub mod ChannelComponent { self._join_channel(profile, channel_id); } - /// @notice removes a member from a channel /// @param channel_id id of channel to be left fn leave_channel(ref self: ComponentState, channel_id: u256) { @@ -187,10 +185,7 @@ pub mod ChannelComponent { assert(total_members > 1, CHANNEL_HAS_NO_MEMBER); // burn user's community token - self - ._burn_channel_nft( - channel.channel_nft_address, channel_member.channel_token_id - ); + self._burn_channel_nft(channel.channel_nft_address, channel_member.channel_token_id); // update storage self @@ -222,7 +217,6 @@ pub mod ChannelComponent { ) } - /// @notice Set the metadata URI of the channel /// @param channel_id The id of the channel /// @param metadata_uri The new metadata URI @@ -241,7 +235,6 @@ pub mod ChannelComponent { self.channels.write(channel_id, channel); } - /// @notice Add a moderator to the channel /// @param channel_id: The id of the channel /// @param Array The address of the moderator @@ -259,7 +252,6 @@ pub mod ChannelComponent { self._add_channel_mods(channel_id, moderators); } - /// @notice Remove a moderator from the channel /// @param channel_id: The id of the channel /// @param moderator: The address of the moderator @@ -285,10 +277,7 @@ pub mod ChannelComponent { let mut channel = self.channels.read(channel_id); // check caller is owner - assert( - channel.channel_owner == get_caller_address(), - NOT_CHANNEL_OWNER - ); + assert(channel.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); // update storage channel.channel_censorship = censorship_status; @@ -393,16 +382,14 @@ pub mod ChannelComponent { // ************************************************************************* #[generate_trait] pub impl InternalImpl< - TContractState, + TContractState, +HasComponent, +Drop, impl Community: CommunityComponent::HasComponent > of InternalTrait { /// @notice initalizes channel component /// @param channel_nft_classhash classhash of channel NFT - fn _initializer( - ref self: ComponentState, channel_nft_classhash: felt252 - ) { + fn _initializer(ref self: ComponentState, channel_nft_classhash: felt252) { self.channel_counter.write(0); self.channel_nft_classhash.write(channel_nft_classhash.try_into().unwrap()); } @@ -411,15 +398,14 @@ pub mod ChannelComponent { /// @param profile address to add to the channel /// @param channel_id id of the channel to be joined fn _join_channel( - ref self: ComponentState, - profile: ContractAddress, - channel_id: u256 + ref self: ComponentState, profile: ContractAddress, channel_id: u256 ) { let mut channel: ChannelDetails = self.channels.read(channel_id); // check that user is a member of the community this channel belongs to let community_instance = get_dep_component!(@self, Community); - let membership_status = community_instance.is_community_member(profile, channel.community_id); + let membership_status = community_instance + .is_community_member(profile, channel.community_id); assert(membership_status, NOT_MEMBER); let channel_member = ChannelMember { @@ -431,14 +417,13 @@ pub mod ChannelComponent { }; // mint a channel token to new joiner - let minted_token_id = self - ._mint_channel_nft(profile, channel.channel_nft_address); - + let minted_token_id = self._mint_channel_nft(profile, channel.channel_nft_address); + // update storage channel.channel_total_members += 1; self.channels.write(channel_id, channel); self.channel_members.write((channel_id, profile), channel_member); - + // emit events self .emit( @@ -471,14 +456,14 @@ pub mod ChannelComponent { // emit event self - .emit( - ChannelModAdded { - channel_id: channel_id, - transaction_executor: get_caller_address(), - mod_address: moderator, - block_timestamp: get_block_timestamp(), - } - ); + .emit( + ChannelModAdded { + channel_id: channel_id, + transaction_executor: get_caller_address(), + mod_address: moderator, + block_timestamp: get_block_timestamp(), + } + ); index += 1; }; } @@ -611,4 +596,4 @@ pub mod ChannelComponent { .burn_nft(get_caller_address(), token_id); } } -} \ No newline at end of file +} diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo index d0f190a..30d12c2 100644 --- a/src/interfaces/IChannel.cairo +++ b/src/interfaces/IChannel.cairo @@ -14,7 +14,9 @@ pub trait IChannel { fn remove_channel_mods(ref self: TState, channel_id: u256, moderators: Array); fn set_channel_censorship_status(ref self: TState, channel_id: u256, censorship_status: bool); fn set_channel_ban_status( - ref self: TState, channel_id: u256, profiles: Array, + ref self: TState, + channel_id: u256, + profiles: Array, ban_statuses: Array ); // ************************************************************************* @@ -27,4 +29,4 @@ pub trait IChannel { fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool; fn get_channel_ban_status(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; -} \ No newline at end of file +} diff --git a/src/lib.cairo b/src/lib.cairo index c443612..2213bf7 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -11,4 +11,4 @@ pub mod hub; pub mod jolt; pub mod collectnft; pub mod community; -pub mod channel; \ No newline at end of file +pub mod channel; diff --git a/src/presets.cairo b/src/presets.cairo index c99076b..19fc48d 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,4 +1,4 @@ pub mod profile; pub mod publication; pub mod community; -pub mod channel; \ No newline at end of file +pub mod channel; diff --git a/src/presets/channel.cairo b/src/presets/channel.cairo index 44f7398..10ec77e 100644 --- a/src/presets/channel.cairo +++ b/src/presets/channel.cairo @@ -2,7 +2,7 @@ pub mod KarstChannel { use karst::channel::channel::ChannelComponent; use karst::community::community::CommunityComponent; - + component!(path: ChannelComponent, storage: channel, event: ChannelEvent); component!(path: CommunityComponent, storage: community, event: CommunityEvent); diff --git a/tests/test_channel.cairo b/tests/test_channel.cairo index 09812e8..2e1296c 100644 --- a/tests/test_channel.cairo +++ b/tests/test_channel.cairo @@ -12,7 +12,6 @@ // EventSpyAssertionsTrait, ContractClassTrait, DeclareResultTrait, EventSpy // }; - // use karst::channel::channel::ChannelComponent::{ // Event as ChannelEvent, ChannelCreated, JoinedChannel, LeftChannel, ChannelModAdded, // ChannelModRemoved, ChannelBanStatusUpdated @@ -86,7 +85,6 @@ // return (channel_contract_address, channel_id, USER_ONE.try_into().unwrap(), metadat_uri); // } - // // writing the test for the set_channel_metadata_uri : // #[test] // fn test_set_channel_metadata_uri_check_owner() { @@ -124,7 +122,6 @@ // stop_cheat_caller_address(channel_contract_address); // } - // // writing the test for the add_channel_mods() // #[test] // fn test_add_channel_mods_owner() { @@ -147,7 +144,6 @@ // ); // } - // #[test] // #[should_panic(expected: ('Channel: not channel owner',))] // fn test_add_channel_mods_not_owner() { @@ -303,7 +299,6 @@ // ); // } - // // set censorship to test // #[test] // fn test_set_channel_censorship_status_owner() { @@ -344,7 +339,6 @@ // assert(ban_status == true, 'invalid ban status'); // } - // #[test] // #[should_panic(expected: ('Karst : Unauthorized access',))] // fn test_set_ban_status_owner_or_moderator() { @@ -383,7 +377,6 @@ // ); // } - // #[test] // #[should_panic(expected: ('Channel: not channel member',))] // fn test_leave_channel_not_member() { @@ -395,21 +388,21 @@ // stop_cheat_caller_address(channel_contract_address); // } - // // joining the channel testing // #[test] // fn test_joining_channel() { // let (channel_contract_address, channel_id, owner, _) = __setup__(); // let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; -// let is_channel_member = dispatcher.is_channel_member(MEMBER1.try_into().unwrap(), channel_id); +// let is_channel_member = dispatcher.is_channel_member(MEMBER1.try_into().unwrap(), +// channel_id); // assert(is_channel_member == true, 'invalid channel member 1'); -// let is_channel_member = dispatcher.is_channel_member(MEMBER2.try_into().unwrap(), channel_id); +// let is_channel_member = dispatcher.is_channel_member(MEMBER2.try_into().unwrap(), +// channel_id); // assert(is_channel_member == true, 'invalid channel member 2'); // } - // // // counting of the member of the channel . // #[test] // fn test_joining_channel_total_members() { From f91093ce1e11096142f6401351a1fca4a2622952 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Mon, 14 Oct 2024 01:26:37 +0100 Subject: [PATCH 4/6] chore: fix requested changes --- src/channel/channel.cairo | 51 ++++++++++++++++++++++++----------- src/interfaces/IChannel.cairo | 4 +-- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index 85f13ad..ae3acd7 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -19,7 +19,7 @@ pub mod ChannelComponent { use karst::base::{ constants::errors::Errors::{ NOT_CHANNEL_OWNER, ALREADY_MEMBER, NOT_CHANNEL_MEMBER, NOT_MEMBER, BANNED_FROM_CHANNEL, - CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED, INVALID_LENGTH + CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED, INVALID_LENGTH, COMMUNITY_DOES_NOT_EXIST, NOT_CHANNEL_MODERATOR }, constants::types::{ChannelDetails, ChannelMember} }; @@ -123,13 +123,26 @@ pub mod ChannelComponent { fn create_channel(ref self: ComponentState, community_id: u256) -> u256 { let channel_id = self.channel_counter.read() + 1; let channel_owner = get_caller_address(); + let channel_nft_classhash = self.channel_nft_classhash.read(); + + // check that community exists + let community_instance = get_dep_component!(@self, Community); + let _community_id = community_instance + .get_community(community_id).community_id; + assert(community_id == _community_id, COMMUNITY_DOES_NOT_EXIST); + + // deploy channel nft + let channel_nft_address = self + ._deploy_channel_nft( + channel_id, channel_nft_classhash, channel_id.try_into().unwrap() + ); // use channel_id as salt since its unique let new_channel = ChannelDetails { channel_id: channel_id, community_id: community_id, channel_owner: channel_owner, channel_metadata_uri: "", - channel_nft_address: 1.try_into().unwrap(), + channel_nft_address: channel_nft_address, channel_total_members: 0, channel_censorship: false, }; @@ -161,7 +174,7 @@ pub mod ChannelComponent { let profile = get_caller_address(); // check user is not already a channel member and wasn't previously banned - let is_channel_member = self.is_channel_member(profile, channel_id); + let (is_channel_member, _) = self.is_channel_member(profile, channel_id); let is_banned = self.get_channel_ban_status(profile, channel_id); assert(!is_channel_member, ALREADY_MEMBER); assert(!is_banned, BANNED_FROM_CHANNEL); @@ -178,7 +191,8 @@ pub mod ChannelComponent { let channel_member = self.channel_members.read((channel_id, profile)); // check that profile is a channel member - assert(self.is_channel_member(profile, channel_id), NOT_CHANNEL_MEMBER); + let (is_channel_member, _) = self.is_channel_member(profile, channel_id); + assert(is_channel_member, NOT_CHANNEL_MEMBER); // check that channel has members let total_members: u256 = channel.channel_total_members; @@ -330,12 +344,12 @@ pub mod ChannelComponent { /// @return bool the profile membership status fn is_channel_member( self: @ComponentState, profile: ContractAddress, channel_id: u256 - ) -> bool { + ) -> (bool, ChannelMember) { let channel_member: ChannelMember = self.channel_members.read((channel_id, profile)); if (channel_member.channel_id == channel_id) { - true + (true, channel_member) } else { - false + (false, channel_member) } } @@ -408,17 +422,17 @@ pub mod ChannelComponent { .is_community_member(profile, channel.community_id); assert(membership_status, NOT_MEMBER); + // mint a channel token to new joiner + let minted_token_id = self._mint_channel_nft(profile, channel.channel_nft_address); + let channel_member = ChannelMember { - profile: get_caller_address(), + profile: profile, channel_id: channel_id, total_publications: 0, - channel_token_id: 0, + channel_token_id: minted_token_id, ban_status: false, }; - // mint a channel token to new joiner - let minted_token_id = self._mint_channel_nft(profile, channel.channel_nft_address); - // update storage channel.channel_total_members += 1; self.channels.write(channel_id, channel); @@ -450,8 +464,11 @@ pub mod ChannelComponent { while index < length { let moderator = *moderators.at(index); - let is_channel_member = self.is_channel_member(moderator, channel_id); + + // check moderator is a channel member + let (is_channel_member, _) = self.is_channel_member(moderator, channel_id); assert(is_channel_member == true, NOT_MEMBER); + self.channel_moderators.write((channel_id, moderator), true); // emit event @@ -481,8 +498,10 @@ pub mod ChannelComponent { while index < length { let moderator = *moderators.at(index); - let is_channel_member = self.is_channel_member(moderator, channel_id); - assert(is_channel_member == true, NOT_MEMBER); + + // check that profile is a moderator + let is_moderator = self.is_channel_mod(moderator, channel_id); + assert(is_moderator, NOT_CHANNEL_MODERATOR); self.channel_moderators.write((channel_id, moderator), false); @@ -519,7 +538,7 @@ pub mod ChannelComponent { let ban_status = *ban_statuses[index]; // check profile is a channel member - let is_channel_member = self.is_channel_member(profile, channel_id); + let (is_channel_member, _) = self.is_channel_member(profile, channel_id); assert(is_channel_member == true, NOT_MEMBER); // update storage diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo index 30d12c2..b77e391 100644 --- a/src/interfaces/IChannel.cairo +++ b/src/interfaces/IChannel.cairo @@ -1,5 +1,5 @@ use starknet::ContractAddress; -use karst::base::constants::types::ChannelDetails; +use karst::base::constants::types::{ChannelDetails, ChannelMember}; #[starknet::interface] pub trait IChannel { @@ -24,7 +24,7 @@ pub trait IChannel { // ************************************************************************* fn get_channel(self: @TState, channel_id: u256) -> ChannelDetails; fn get_channel_metadata_uri(self: @TState, channel_id: u256) -> ByteArray; - fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; + fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> (bool, ChannelMember); fn get_total_members(self: @TState, channel_id: u256) -> u256; fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool; From fbabfd6d647c89aa8bc1d92cc470fc0e08a131bc Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Mon, 14 Oct 2024 01:51:18 +0100 Subject: [PATCH 5/6] chore: update tests --- src/channel/channel.cairo | 2 +- src/community/community.cairo | 80 ++++++----------------------------- tests/test_community.cairo | 2 +- 3 files changed, 15 insertions(+), 69 deletions(-) diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index ae3acd7..f3462a4 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -418,7 +418,7 @@ pub mod ChannelComponent { // check that user is a member of the community this channel belongs to let community_instance = get_dep_component!(@self, Community); - let membership_status = community_instance + let (membership_status, _) = community_instance .is_community_member(profile, channel.community_id); assert(membership_status, NOT_MEMBER); diff --git a/src/community/community.cairo b/src/community/community.cairo index af63afd..d7ca6e8 100644 --- a/src/community/community.cairo +++ b/src/community/community.cairo @@ -30,6 +30,7 @@ pub mod CommunityComponent { pub struct Storage { community_owner: Map, // map communities: Map, // map + member_community_id: Map, // map community_member: Map< (u256, ContractAddress), CommunityMember >, // map<(community_id, member address), Member_details> @@ -150,52 +151,22 @@ pub mod CommunityComponent { ref self: ComponentState, community_type: CommunityType ) -> u256 { let community_owner = get_caller_address(); + let community_nft_classhash = self.community_nft_classhash.read(); let community_id = self.community_counter.read() + 1; + // deploy community nft let community_nft_address = self ._deploy_community_nft( community_id, community_nft_classhash, community_id.try_into().unwrap() ); // use community_id as salt since its unique - // write to storage - let community_details = CommunityDetails { - community_id: community_id, - community_owner: community_owner, - community_metadata_uri: "", - community_nft_address: community_nft_address, - community_premium_status: false, - community_total_members: 0, - community_type: CommunityType::Free, - }; - - let gate_keep_details = CommunityGateKeepDetails { - community_id: community_id, - gate_keep_type: GateKeepType::None, - community_nft_address: contract_address_const::<0>(), - entry_fee: 0 - }; - - self.communities.write(community_id, community_details); - self.community_owner.write(community_id, community_owner); - self.community_gate_keep.write(community_id, gate_keep_details); - self.community_counter.write(community_counter + 1); - - // upgrade if community type is not free - if (community_type != CommunityType::Free) { - self._upgrade_community(community_id, community_type); - } - - // emit event + // create community nft self - .emit( - CommunityCreated { - community_id: community_id, - community_owner: community_owner, - community_nft_address: community_nft_address, - block_timestamp: get_block_timestamp() - } + ._create_community( + community_owner, community_nft_address, community_id, community_type, ); + community_id } @@ -228,37 +199,12 @@ pub mod CommunityComponent { let (is_community_member, _) = self.is_community_member(profile_caller, community_id); assert(is_community_member == true, NOT_MEMBER); - // remove member details and update storage - let updated_member_details = CommunityMember { - profile_address: contract_address_const::<0>(), - community_id: 0, - total_publications: 0, - community_token_id: 0, - ban_status: true - }; - self.community_member.write((community_id, profile), updated_member_details); - let community_total_members = community.community_total_members - 1; - let updated_community = CommunityDetails { - community_total_members: community_total_members, ..community - }; - self.communities.write(community_id, updated_community); - - // burn user's community token - self - ._burn_community_nft( - community.community_nft_address, community_member_details.community_token_id - ); - - // emit event self - .emit( - LeftCommunity { - community_id: community_id, - transaction_executor: get_caller_address(), - token_id: community_member_details.community_token_id, - profile: profile, - block_timestamp: get_block_timestamp(), - } + ._leave_community( + profile_caller, + community.community_nft_address, + community_id, + community_member_details.community_token_id ); } @@ -865,4 +811,4 @@ pub mod CommunityComponent { .burn_nft(profile, token_id); } } -} +} \ No newline at end of file diff --git a/tests/test_community.cairo b/tests/test_community.cairo index 95d2ed5..6192ba3 100644 --- a/tests/test_community.cairo +++ b/tests/test_community.cairo @@ -998,7 +998,7 @@ fn test_can_only_set_ban_status_for_members() { // TEST TODO: TEST To make sure length of ban status and profiles are same #[test] -#[should_panic(expected: ('Karst: Invalid Length',))] +#[should_panic(expected: ('Karst: array mismatch',))] fn test_should_set_ban_status_for_invalid_array_length() { let community_contract_address = __setup__(); From 28660e6de672bdf32709e2895a0fff7d12f5b5c7 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Mon, 14 Oct 2024 01:51:46 +0100 Subject: [PATCH 6/6] chore: scarb fmt --- src/channel/channel.cairo | 6 +++--- src/community/community.cairo | 2 +- src/interfaces/IChannel.cairo | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index f3462a4..27f6056 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -19,7 +19,8 @@ pub mod ChannelComponent { use karst::base::{ constants::errors::Errors::{ NOT_CHANNEL_OWNER, ALREADY_MEMBER, NOT_CHANNEL_MEMBER, NOT_MEMBER, BANNED_FROM_CHANNEL, - CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED, INVALID_LENGTH, COMMUNITY_DOES_NOT_EXIST, NOT_CHANNEL_MODERATOR + CHANNEL_HAS_NO_MEMBER, UNAUTHORIZED, INVALID_LENGTH, COMMUNITY_DOES_NOT_EXIST, + NOT_CHANNEL_MODERATOR }, constants::types::{ChannelDetails, ChannelMember} }; @@ -127,8 +128,7 @@ pub mod ChannelComponent { // check that community exists let community_instance = get_dep_component!(@self, Community); - let _community_id = community_instance - .get_community(community_id).community_id; + let _community_id = community_instance.get_community(community_id).community_id; assert(community_id == _community_id, COMMUNITY_DOES_NOT_EXIST); // deploy channel nft diff --git a/src/community/community.cairo b/src/community/community.cairo index d7ca6e8..29f2d65 100644 --- a/src/community/community.cairo +++ b/src/community/community.cairo @@ -811,4 +811,4 @@ pub mod CommunityComponent { .burn_nft(profile, token_id); } } -} \ No newline at end of file +} diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo index b77e391..ec1a7d4 100644 --- a/src/interfaces/IChannel.cairo +++ b/src/interfaces/IChannel.cairo @@ -24,7 +24,9 @@ pub trait IChannel { // ************************************************************************* fn get_channel(self: @TState, channel_id: u256) -> ChannelDetails; fn get_channel_metadata_uri(self: @TState, channel_id: u256) -> ByteArray; - fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> (bool, ChannelMember); + fn is_channel_member( + self: @TState, profile: ContractAddress, channel_id: u256 + ) -> (bool, ChannelMember); fn get_total_members(self: @TState, channel_id: u256) -> u256; fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool;