diff --git a/src/base/constants/types.cairo b/src/base/constants/types.cairo index 3605e27..076d79e 100644 --- a/src/base/constants/types.cairo +++ b/src/base/constants/types.cairo @@ -158,34 +158,33 @@ pub struct QuoteParams { } - // /** -// * @notice A struct containing the parameters which define channel +// * @notice A struct containing the parameters which define channel // * -// * @param channel_id unquie id to each channel -// * @param channel_owner primary owner of channel , who create channel -// * @param channel_metadata_uri contain channel metadata -// * @param channel_nft_address contain channel token gated nfts -// * @param channel_total_members total number of member in the channel -// * @param channel_censorship cencorship of channel -// * @param channel_premium_status +// * @param channel_id unquie id to each channel +// * @param channel_owner primary owner of channel , who create channel +// * @param channel_metadata_uri contain channel metadata +// * @param channel_nft_address contain channel token gated nfts +// * @param channel_total_members total number of member in the channel +// * @param channel_censorship cencorship of channel +// * @param channel_premium_status // */ #[derive(Drop, Serde, Clone, starknet::Store)] pub struct channelParams { - channel_id: u256, - channel_owner: ContractAddress, - channel_metadata_uri: ByteArray, - channel_nft_address: ContractAddress, - channel_total_members: u256, - channel_censorship: bool, + pub channel_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, } #[derive(Drop, Serde, Clone, starknet::Store)] pub struct channelMember { - profile: ContractAddress, - channel_id: u256, - total_publications: u256, - channel_token_id: u256, - ban_status: bool, -} \ No newline at end of file + pub profile: ContractAddress, + pub channel_id: u256, + pub total_publications: u256, + pub channel_token_id: u256, + pub ban_status: bool, +} diff --git a/src/channel.cairo b/src/channel.cairo index 4e30680..ff02972 100644 --- a/src/channel.cairo +++ b/src/channel.cairo @@ -1 +1 @@ -mod channel ; \ No newline at end of file +pub mod channel; diff --git a/src/channel/channel.cairo b/src/channel/channel.cairo index 7fe1ca1..ed484a3 100644 --- a/src/channel/channel.cairo +++ b/src/channel/channel.cairo @@ -1,8 +1,14 @@ -//TODO make the channel as the componet +//TODO make the channel as the componet #[starknet::component] -mod ChannelComponent { +pub mod ChannelComponent { use core::clone::Clone; - use core::starknet::{ContractAddress, get_caller_address, get_block_timestamp}; + use core::starknet::{ + ContractAddress, get_caller_address, get_block_timestamp, + storage::{ + StoragePointerWriteAccess, StoragePointerReadAccess, Map, StorageMapReadAccess, + StorageMapWriteAccess + } + }; use karst::interfaces::IChannel::IChannel; use karst::base::{ constants::errors::Errors::{ @@ -11,11 +17,11 @@ mod ChannelComponent { constants::types::{channelParams, channelMember} }; #[storage] - struct Storage { - channels: LegacyMap, + pub struct Storage { + channels: Map, channel_counter: u256, - channel_members: LegacyMap, - channel_moderators: LegacyMap<(u256, ContractAddress), bool>, + channel_members: Map, + channel_moderators: Map<(u256, ContractAddress), bool>, } #[derive(Drop, starknet::Event)] @@ -41,7 +47,7 @@ mod ChannelComponent { token_id: u256, block_timestamp: u64, } - // this should also contain the IChannel & IChannelNFT implmentation , waht is the self what d. + // this should also contain the IChannel & IChannelNFT implmentation , waht is the self what d. #[derive(Drop, starknet::Event)] pub struct ChannelModAdded { channel_id: u256, @@ -68,7 +74,7 @@ mod ChannelComponent { #[event] #[derive(Drop, starknet::Event)] - enum Event { + pub enum Event { ChannelCreated: ChannelCreated, JoinedChannel: JoinedChannel, LeftChannel: LeftChannel, @@ -78,28 +84,29 @@ mod ChannelComponent { } - // mnaking the constructor to store the onwer , who can set the moderators + // mnaking the constructor to store the onwer , who can set the moderators -#[embeddable_as(KarstChannel)] + #[embeddable_as(KarstChannel)] impl ChannelImpl< - TContractState, +HasComponent, +Drop> - of IChannel> { - ///@notice Create a new channel + TContractState, +HasComponent, +Drop + > of IChannel> { + ///@notice Create a new channel ///@param channel_params: The parameters of the channel - fn create_channel(ref self: ComponentState, channel_params: channelParams) -> u256 { - - let channel_id : u256 = self.channel_counter.read() ; + fn create_channel( + ref self: ComponentState, channel_params: channelParams + ) -> u256 { + let channel_id: u256 = self.channel_counter.read(); let new_channel = channelParams { - channel_id: channel_id , + channel_id: channel_id, channel_owner: channel_params.channel_owner, channel_metadata_uri: channel_params.channel_metadata_uri, channel_nft_address: channel_params.channel_nft_address, channel_total_members: 0, channel_censorship: channel_params.channel_censorship, }; - // increment - self.channels.write(channel_id , new_channel.clone()); - self.channel_counter.write(channel_id + 1 ); + // increment + self.channels.write(channel_id, new_channel.clone()); + self.channel_counter.write(channel_id + 1); self .emit( ChannelCreated { @@ -117,15 +124,15 @@ mod ChannelComponent { /// @notice Join the channel /// @param channel_id: The id of the channel fn join_channel(ref self: ComponentState, channel_id: u256) { - // check that i prioor not baned + // check that i prioor not baned let channel_member: channelMember = self.channel_members.read(get_caller_address()); - assert(channel_member.ban_status, BANNED_FROM_CHANNEL); + assert(!channel_member.ban_status, BANNED_FROM_CHANNEL); let mut new_channel: channelParams = self.channels.read(channel_id); new_channel.channel_total_members += 1; self.channels.write(channel_id, new_channel); - // calculate the total channel member add +1 is the new member id + // calculate the total channel member add +1 is the new member id self .channel_members @@ -155,14 +162,33 @@ mod ChannelComponent { /// @notice Leave the channel /// @param channel_id: The id of the channel - /// @dev The user must be a member of the channel + /// @dev The user must be a member of the channel fn leave_channel(ref self: ComponentState, channel_id: u256) { - let channel_member: channelParams = self.channels.read(channel_id); - assert(channel_member.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); + + assert( + self.channel_members.read(get_caller_address()).channel_id == channel_id, + NOT_CHANNEL_MEMBER + ); + assert(!self.channel_members.read(get_caller_address()).ban_status, BANNED_FROM_CHANNEL); + assert(self.channels.read(channel_id).channel_total_members > 1 , 'total_member > 1'); + + self.channel_members.write(get_caller_address(), channelMember { + profile: get_caller_address(), + // todo , what default channel id should set max but not optimize to store + channel_id: 10000000, + total_publications: 0, + channel_token_id: 0, + ban_status: false, + }); + + + + let mut new_channel: channelParams = self.channels.read(channel_id); + new_channel.channel_total_members -= 1; + self.channels.write(channel_id, new_channel); - //TODO Delete the mapping at the caller address - // self.channel_members.write(get_caller_address , None ); - //TODO : burn the NFT + //TODO Delete the mapping at the caller address + //TODO : burn the NFT self .emit( LeftChannel { @@ -201,8 +227,8 @@ mod ChannelComponent { fn add_channel_mods( ref self: ComponentState, channel_id: u256, moderator: ContractAddress ) { - let channel_member: channelParams = self.channels.read(channel_id); - assert(channel_member.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); + + assert(self.channels.read(channel_id).channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); self.channel_moderators.write((channel_id, moderator), true); self @@ -224,8 +250,8 @@ mod ChannelComponent { fn remove_channel_mods( ref self: ComponentState, channel_id: u256, moderator: ContractAddress ) { - let channel_member: channelParams = self.channels.read(channel_id); - assert(channel_member.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); + // let channel_member: channelParams = self.channels.read(channel_id); + assert(self.channels.read(channel_id).channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); self.channel_moderators.write((channel_id, moderator), false); @@ -241,13 +267,13 @@ mod ChannelComponent { } - /// @notice Set the censorship status of the channel + /// @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: channelParams = self.channels.read(channel_id); - assert(channel_member.channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); + // let channel_member: channelParams = self.channels.read(channel_id); + assert(self.channels.read(channel_id).channel_owner == get_caller_address(), NOT_CHANNEL_OWNER); let mut new_channel: channelParams = self.channels.read(channel_id); new_channel.channel_censorship = censorship_status; self.channels.write(channel_id, new_channel); @@ -264,11 +290,12 @@ mod ChannelComponent { profile: ContractAddress, ban_status: bool ) { + // let channel_member: channelParams = self.channels.read(channel_id); assert( - self.channel_moderators.read((channel_id, get_caller_address())), + self.channels.read(channel_id).channel_owner == get_caller_address() + || self.channel_moderators.read((channel_id, get_caller_address())), NOT_CHANNEL_MODERATOR ); - let mut new_channel_member: channelMember = self.channel_members.read(profile); new_channel_member.ban_status = ban_status; self.channel_members.write(profile, new_channel_member); @@ -317,17 +344,17 @@ mod ChannelComponent { } - ///TODO :get the total number of mener of the channel + ///TODO :get the total number of mener of the channel fn get_total_members(self: @ComponentState, channel_id: u256) -> u256 { let channel: channelParams = self.channels.read(channel_id); channel.channel_total_members } - ///@notice check for moderator - /// @param channel id - /// @param profile addresss + ///@notice check for moderator + /// @param channel id + /// @param profile addresss fn is_channel_mod( - self: @ComponentState, channel_id: u256, profile: ContractAddress + self: @ComponentState, profile: ContractAddress, channel_id: u256 ) -> bool { self.channel_moderators.read((channel_id, profile)) } @@ -339,7 +366,7 @@ mod ChannelComponent { channel.channel_censorship } - ///TODO introduce new storage for ban status + ///TODO introduce new storage for ban status fn get_ban_status(self: @ComponentState, profile: ContractAddress) -> bool { let channel_member: channelMember = self.channel_members.read(profile); channel_member.ban_status diff --git a/src/hub/hub.cairo b/src/hub/hub.cairo index 9b58c60..db0ee08 100644 --- a/src/hub/hub.cairo +++ b/src/hub/hub.cairo @@ -47,16 +47,15 @@ pub mod KarstHub { // ************************************************************************* // COMPONENTS // ************************************************************************* - component!(path: ChannelComponent , storage : channel , event : ChannelEvent ) ; + component!(path: ChannelComponent, storage: channel, event: ChannelEvent); component!(path: ProfileComponent, storage: profile, event: ProfileEvent); component!(path: PublicationComponent, storage: publication, event: PublicationEvent); - - + + impl ChannelImpl = ChannelComponent::KarstChannel; - // impl ChannelImpl = ChannelComponent::KarstChannel; impl ProfileImpl = ProfileComponent::KarstProfile; impl PublicationImpl = PublicationComponent::KarstPublication; - + // ************************************************************************* // STORAGE @@ -81,7 +80,7 @@ pub mod KarstHub { enum Event { ProfileEvent: ProfileComponent::Event, PublicationEvent: PublicationComponent::Event, - ChannelEvent : ChannelComponent::Event + ChannelEvent: ChannelComponent::Event } // ************************************************************************* diff --git a/src/interfaces.cairo b/src/interfaces.cairo index a916c68..f475568 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -8,4 +8,4 @@ pub mod IHandle; pub mod IHandleRegistry; pub mod IHub; pub mod ICollectNFT; -pub mod IChannel; \ No newline at end of file +pub mod IChannel; diff --git a/src/interfaces/IChannel.cairo b/src/interfaces/IChannel.cairo index 8b59ee1..2888f5c 100644 --- a/src/interfaces/IChannel.cairo +++ b/src/interfaces/IChannel.cairo @@ -6,7 +6,7 @@ pub trait IChannel { // ************************************************************************* // EXTERNALS // ************************************************************************* - fn create_channel(ref self: TState, channel_params: channelParams) -> u256 ; + fn create_channel(ref self: TState, channel_params: channelParams) -> 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); @@ -21,19 +21,20 @@ pub trait IChannel { // // ************************************************************************* fn get_channel(self: @TState, channel_id: u256) -> channelParams; fn get_channel_metadata_uri(self: @TState, channel_id: u256) -> ByteArray; - // so is the profile is channel member or not we have to say that which channel id , i think it will be good + // so is the profile is channel member or not we have to say that which channel id , i think it + // will be good fn is_channel_member(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; - // what is the means by the paid channel member - // how we can calcualte the get_total_member + // what is the means by the paid channel member + // how we can calcualte the get_total_member fn get_total_members(self: @TState, channel_id: u256) -> u256; // we have to pass the channel id how is the ch - fn is_channel_mod(self: @TState, channel_id: u256, profile: ContractAddress) -> bool; + fn is_channel_mod(self: @TState, profile: ContractAddress, channel_id: u256) -> bool; - // this one is easy + // this one is easy fn get_channel_censorship_status(self: @TState, channel_id: u256) -> bool; fn get_ban_status(self: @TState, profile: ContractAddress) -> bool; +} -} \ No newline at end of file diff --git a/src/presets.cairo b/src/presets.cairo index 25fb104..deb40db 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,3 +1,3 @@ pub mod profile; pub mod publication; -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 71cce8b..5a6029a 100644 --- a/src/presets/channel.cairo +++ b/src/presets/channel.cairo @@ -1,8 +1,6 @@ - #[starknet::contract] pub mod KarstChannel { use karst::channel::channel::ChannelComponent; - component!(path: ChannelComponent, storage: channel, event: ChannelEvent); #[abi(embed_v0)] impl channelImpl = ChannelComponent::KarstChannel; @@ -20,4 +18,3 @@ pub mod KarstChannel { } } -// creatin the best things in the file thankyous o much that \ No newline at end of file diff --git a/tests/test_channel.cairo b/tests/test_channel.cairo new file mode 100644 index 0000000..ee75296 --- /dev/null +++ b/tests/test_channel.cairo @@ -0,0 +1,265 @@ +// ************************************************************************* +// CHANNEL TEST +// ************************************************************************* +use core::option::OptionTrait; +use core::starknet::SyscallResultTrait; +use core::result::ResultTrait; +use core::traits::{TryInto, Into}; +use starknet::{ContractAddress, get_block_timestamp}; + + +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'; + + +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(); + + // declare account + let account_class_hash = declare("Account").unwrap().contract_class(); + + // declare follownft + let follow_nft_classhash = declare("Follow").unwrap().contract_class(); + + //declare collectnft + let collect_nft_classhash = declare("CollectNFT").unwrap().contract_class(); + + let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + + // create channel for the use1 + 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, + } + ); + + stop_cheat_caller_address(channel_contract_address); + + return (channel_contract_address, channel_id, USER_ONE.try_into().unwrap(), metadat_uri); +} + + + + + +// leave channel testing +#[test] +fn test_leave_channel() { + let (channel_contract_address, channel_id, owner, _) = __setup__(); + let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + // to leave the channel first 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); + + start_cheat_caller_address(channel_contract_address, USER_THREE.try_into().unwrap()); + dispatcher.join_channel(channel_id); + stop_cheat_caller_address(channel_contract_address); + + start_cheat_caller_address(channel_contract_address, USER_FOUR.try_into().unwrap()); + dispatcher.join_channel(channel_id); + stop_cheat_caller_address(channel_contract_address); + // check is member + assert( + dispatcher.is_channel_member(USER_TWO.try_into().unwrap(), channel_id) == true, + 'invalid channel member' + ); + + // leave the channel + start_cheat_caller_address(channel_contract_address, USER_TWO.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(USER_TWO.try_into().unwrap(), channel_id) == true, + 'channel not leaved' + ); +} + + +// // counting of the member of the channel . +#[test] +fn test_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 == 5, 'invalid total members'); +} + +#[test] +fn test_adding_modeartor() { + // user 1 is the owner of the channel + let (channel_contract_address, channel_id, owner, _) = __setup__(); + let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + + // let say add the 2 more user 2 and user3 as the moderator + start_cheat_caller_address(channel_contract_address, owner); + dispatcher.add_channel_mods(channel_id, USER_TWO.try_into().unwrap()); + dispatcher.add_channel_mods(channel_id, USER_THREE.try_into().unwrap()); + stop_cheat_caller_address(channel_contract_address); + // check the moderator + + assert( + dispatcher.is_channel_mod(USER_TWO.try_into().unwrap(), channel_id) == true, + 'user_two is not mod' + ); + assert( + dispatcher.is_channel_mod(USER_THREE.try_into().unwrap(), channel_id) == true, + 'user_three isnt mod' + ); + + // // remove the moderator user_two and keep the use three + + start_cheat_caller_address(channel_contract_address, owner); + dispatcher.remove_channel_mods(channel_id, USER_TWO.try_into().unwrap()); + stop_cheat_caller_address(channel_contract_address); + + // check the moderator + assert( + dispatcher.is_channel_mod(USER_TWO.try_into().unwrap(), channel_id) == false, + 'user_two should not be mod' + ); + assert( + dispatcher.is_channel_mod(USER_THREE.try_into().unwrap(), channel_id) == true, + 'user_three should be mod' + ); +} + + +// 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 }; + + // user + start_cheat_caller_address(channel_contract_address, USER_THREE.try_into().unwrap()); + // try to join the channel + dispatcher.join_channel(channel_id); + stop_cheat_caller_address(channel_contract_address); + + // is channel member of the channel + + start_cheat_caller_address(channel_contract_address, USER_THREE.try_into().unwrap()); + let is_channel_member = dispatcher + .is_channel_member(USER_THREE.try_into().unwrap(), channel_id); + assert(is_channel_member == true, 'invalid channel member'); + stop_cheat_caller_address(channel_contract_address); +} + +//todo working fine failed through the contract assert +// if aleready ban does not able to join the channel +// #[test] +// fn test_already_ban_cannot_join_channel() { +// let (channel_contract_address, channel_id, owner, _) = __setup__(); +// let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + +// // moderator +// 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); + + +// // user +// // cannot able to join +// start_cheat_caller_address(channel_contract_address, USER_THREE.try_into().unwrap()); +// dispatcher.join_channel(channel_id); +// stop_cheat_caller_address(channel_contract_address); + +// // is channel member of the channel + +// } + + +#[test] +fn test_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, owner); + let is_channel_member = dispatcher.is_channel_member(owner, channel_id); + assert(is_channel_member == true, 'invalid channel member'); +} + +#[test] +fn test_create_channel() { + let (channel_contract_address, channel_id, _, metadata_uri) = __setup__(); + + let dispatcher = IChannelDispatcher { contract_address: channel_contract_address }; + + start_cheat_caller_address(channel_contract_address, USER_ONE.try_into().unwrap()); + let channel_metadata_uri = dispatcher.get_channel_metadata_uri(channel_id); + assert(channel_metadata_uri == metadata_uri, 'invalid channel uri '); + stop_cheat_caller_address(channel_contract_address); +}