From fcf13856e212ea2be8354b3a505775feccf37629 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Sun, 7 Jul 2024 11:58:54 +0100 Subject: [PATCH 1/3] chore: refactor contracts --- src/base/errors.cairo | 22 ++--- src/follownft/follownft.cairo | 25 +++++ src/karstnft/karstnft.cairo | 5 + src/mocks.cairo | 1 - src/mocks/account.cairo | 142 --------------------------- src/namespaces/handle_registry.cairo | 20 +++- 6 files changed, 60 insertions(+), 155 deletions(-) delete mode 100644 src/mocks/account.cairo diff --git a/src/base/errors.cairo b/src/base/errors.cairo index 89357b6..34b2bc8 100644 --- a/src/base/errors.cairo +++ b/src/base/errors.cairo @@ -2,15 +2,15 @@ // ERRORS // ************************************************************************* pub mod Errors { - pub const NOT_PROFILE_OWNER: felt252 = 'NOT_PROFILE_OWNER'; - pub const ALREADY_MINTED: felt252 = 'USER_ALREADY_MINTED'; - pub const INITIALIZED: felt252 = 'ALREADY_INITIALIZED'; - pub const HUB_RESTRICTED: felt252 = 'CALLER_IS_NOT_HUB'; - pub const FOLLOWING: felt252 = 'USER_ALREADY_FOLLOWING'; - pub const NOT_FOLLOWING: felt252 = 'USER_NOT_FOLLOWING'; - pub const BLOCKED_STATUS: felt252 = 'BLOCKED'; - pub const INVALID_POINTED_PUBLICATION: felt252 = 'INVALID_POINTED_PUB'; - pub const INVALID_OWNER: felt252 = 'CALLER_NOT_OWNER'; - pub const INVALID_PROFILE: felt252 = 'PROFILE_IS_NOT_OWNER'; - pub const OWNER_NOT_ZERO: felt252 = 'HANDLE_HAS_ALREADY_BEEN_LINKED'; + pub const NOT_PROFILE_OWNER: felt252 = 'Karst: not profile owner!'; + pub const ALREADY_MINTED: felt252 = 'Karst: user already minted!'; + pub const INITIALIZED: felt252 = 'Karst: already initialized!'; + pub const HUB_RESTRICTED: felt252 = 'Karst: caller is not Hub!'; + pub const FOLLOWING: felt252 = 'Karst: user already following!'; + pub const NOT_FOLLOWING: felt252 = 'Karst: user not following!'; + pub const BLOCKED_STATUS: felt252 = 'Karst: user is blocked!'; + pub const INVALID_POINTED_PUBLICATION: felt252 = 'Karst: invalid pointed pub!'; + pub const INVALID_OWNER: felt252 = 'Karst: caller is not owner!'; + pub const INVALID_PROFILE: felt252 = 'Karst: profile is not owner!'; + pub const HANDLE_ALREADY_LINKED: felt252 = 'Karst: handle already linked!'; } diff --git a/src/follownft/follownft.cairo b/src/follownft/follownft.cairo index 8151084..2d0a494 100644 --- a/src/follownft/follownft.cairo +++ b/src/follownft/follownft.cairo @@ -71,12 +71,16 @@ mod Follow { // ************************************************************************* #[abi(embed_v0)] impl FollowImpl of IFollowNFT { + /// @notice initialize follow contract + /// @param profile_address address of profile to initialize contract for fn initialize(ref self: ContractState, profile_address: ContractAddress) { assert(!self.initialized.read(), Errors::INITIALIZED); self.initialized.write(true); self.followed_profile_address.write(profile_address); } + /// @notice performs the follow action + /// @param follower_profile_address address of the user trying to perform the follow action fn follow(ref self: ContractState, follower_profile_address: ContractAddress) -> u256 { hub_only(self.karst_hub.read()); let follow_id = self @@ -86,6 +90,8 @@ mod Follow { self._follow(follower_profile_address) } + /// @notice performs the unfollow action + /// @param unfollower_profile_address address of the user trying to perform the unfollow action fn unfollow(ref self: ContractState, unfollower_profile_address: ContractAddress) { hub_only(self.karst_hub.read()); let follow_id = self @@ -95,6 +101,8 @@ mod Follow { self._unfollow(unfollower_profile_address, follow_id); } + /// @notice performs the block action + /// @param follower_profile_address address of the user to be blocked fn process_block( ref self: ContractState, follower_profile_address: ContractAddress ) -> bool { @@ -119,28 +127,40 @@ mod Follow { // ************************************************************************* // GETTERS // ************************************************************************* + + /// @notice gets the follower profile address for a follow action + /// @param follow_id ID of the follow action fn get_follower_profile_address(self: @ContractState, follow_id: u256) -> ContractAddress { let follow_data = self.follow_data_by_follow_id.read(follow_id); follow_data.follower_profile_address } + /// @notice gets the follow timestamp for a follow action + /// @param follow_id ID of the follow action fn get_follow_timestamp(self: @ContractState, follow_id: u256) -> u64 { let follow_data = self.follow_data_by_follow_id.read(follow_id); follow_data.follow_timestamp } + /// @notice gets the entire follow data for a follow action + /// @param follow_id ID of the follow action fn get_follow_data(self: @ContractState, follow_id: u256) -> FollowData { self.follow_data_by_follow_id.read(follow_id) } + /// @notice checks if a particular address is following the followed profile + /// @param follower_profile_address address of the user to check fn is_following(self: @ContractState, follower_profile_address: ContractAddress) -> bool { self.follow_id_by_follower_profile_address.read(follower_profile_address) != 0 } + /// @notice gets the follow ID for a follower_profile_address + /// @param follower_profile_address address of the profile fn get_follow_id(self: @ContractState, follower_profile_address: ContractAddress) -> u256 { self.follow_id_by_follower_profile_address.read(follower_profile_address) } + /// @notice gets the total followers for the followed profile fn get_follower_count(self: @ContractState) -> u256 { self.follower_count.read() } @@ -165,6 +185,8 @@ mod Follow { // ************************************************************************* #[generate_trait] impl Private of PrivateTrait { + /// @notice internal function that performs the follow action + /// @param follower_profile_address address of profile performing the follow action fn _follow(ref self: ContractState, follower_profile_address: ContractAddress) -> u256 { let new_follower_id = self.follower_count.read() + 1; let follow_timestamp: u64 = get_block_timestamp(); @@ -190,6 +212,9 @@ mod Follow { return (new_follower_id); } + /// @notice internal function that performs the unfollow action + /// @param unfollower address of user performing the unfollow action + /// @param follow_id ID of the initial follow action fn _unfollow(ref self: ContractState, unfollower: ContractAddress, follow_id: u256) { self.follow_id_by_follower_profile_address.write(unfollower, 0); self diff --git a/src/karstnft/karstnft.cairo b/src/karstnft/karstnft.cairo index 91b5d37..1041a29 100644 --- a/src/karstnft/karstnft.cairo +++ b/src/karstnft/karstnft.cairo @@ -118,6 +118,8 @@ pub mod KarstNFT { #[abi(embed_v0)] impl KarstImpl of IKarstNFT::IKarstNFT { + /// @notice mints the karst NFT + /// @param address address of user trying to mint the karst NFT fn mint_karstnft(ref self: ContractState, address: ContractAddress) { let balance = self.erc721.balance_of(address); assert(balance.is_zero(), ALREADY_MINTED); @@ -129,10 +131,13 @@ pub mod KarstNFT { self.last_minted_id.write(token_id); } + /// @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: ContractAddress) -> u256 { self.user_token_id.read(user) } + /// @notice gets the last minted NFT fn get_last_minted_id(self: @ContractState) -> u256 { self.last_minted_id.read() } diff --git a/src/mocks.cairo b/src/mocks.cairo index 40d936f..d108990 100644 --- a/src/mocks.cairo +++ b/src/mocks.cairo @@ -1,2 +1 @@ pub mod registry; -pub mod account; diff --git a/src/mocks/account.cairo b/src/mocks/account.cairo deleted file mode 100644 index 56ab342..0000000 --- a/src/mocks/account.cairo +++ /dev/null @@ -1,142 +0,0 @@ -// use starknet::{account::Call, ContractAddress, ClassHash}; - -// #[starknet::interface] -// trait ISimpleAccount { -// fn get_public_key(self: @TContractState) -> felt252; -// fn set_public_key(ref self: TContractState, new_public_key: felt252); -// fn is_valid_signature( -// self: @TContractState, hash: felt252, signature: Span -// ) -> felt252; -// fn __validate__(ref self: TContractState, calls: Array) -> felt252; -// fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252; -// fn __validate_deploy__( -// self: @TContractState, -// class_hash: felt252, -// contract_address_salt: felt252, -// public_key: felt252 -// ) -> felt252; -// fn __execute__(ref self: TContractState, calls: Array) -> Array>; -// } - -// #[starknet::contract(account)] -// mod SimpleAccount { -// use core::num::traits::zero::Zero; -// use starknet::{ -// get_tx_info, get_caller_address, get_contract_address, ContractAddress, account::Call, ClassHash, SyscallResultTrait -// }; -// use core::ecdsa::check_ecdsa_signature; -// use starknet::call_contract_syscall; - -// #[storage] -// struct Storage { -// _public_key: felt252, -// } - -// #[constructor] -// fn constructor(ref self: ContractState, _public_key: felt252) { -// self._public_key.write(_public_key); -// } - -// #[abi(embed_v0)] -// impl IAccountImpl of super::ISimpleAccount { -// fn get_public_key(self: @ContractState) -> felt252 { -// self._public_key.read() -// } - -// fn set_public_key(ref self: ContractState, new_public_key: felt252) { -// self.assert_only_self(); -// self._public_key.write(new_public_key); -// } - -// fn is_valid_signature( -// self: @ContractState, hash: felt252, signature: Span -// ) -> felt252 { -// self._is_valid_signature(hash, signature) -// } - -// fn __validate_deploy__( -// self: @ContractState, -// class_hash: felt252, -// contract_address_salt: felt252, -// public_key: felt252 -// ) -> felt252 { -// self.validate_transaction() -// } - -// fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 { -// self.validate_transaction() -// } - -// fn __validate__(ref self: ContractState, mut calls: Array) -> felt252 { -// self.validate_transaction() -// } - -// fn __execute__(ref self: ContractState, mut calls: Array) -> Array> { -// let caller = get_caller_address(); -// assert(caller.is_zero(), 'invalid caller'); - -// let tx_info = get_tx_info().unbox(); -// assert(tx_info.version != 0, 'invalid tx version'); - -// self._execute_calls(calls) -// } -// } - -// #[generate_trait] -// impl internalImpl of InternalTrait { -// fn assert_only_self(ref self: ContractState) { -// let caller = get_caller_address(); -// let self = get_contract_address(); -// assert(self == caller, 'Account: unathorized'); -// } - -// fn validate_transaction(self: @ContractState) -> felt252 { -// let tx_info = get_tx_info().unbox(); -// let tx_hash = tx_info.transaction_hash; -// let signature = tx_info.signature; -// assert( -// self._is_valid_signature(tx_hash, signature) == starknet::VALIDATED, -// 'Account: invalid signature' -// ); -// starknet::VALIDATED -// } - -// fn _is_valid_signature( -// self: @ContractState, hash: felt252, signature: Span -// ) -> felt252 { -// assert(signature.len() == 2_u32, 'invalid signature'); -// let public_key = self._public_key.read(); - -// if check_ecdsa_signature( -// message_hash: hash, -// public_key: public_key, -// signature_r: *signature[0_u32], -// signature_s: *signature[1_u32], -// ) { -// return starknet::VALIDATED; -// } else { -// return 0; -// } -// } - -// fn _execute_calls(ref self: ContractState, mut calls: Array) -> Array> { -// let mut result: Array> = ArrayTrait::new(); -// let mut calls = calls; - -// loop { -// match calls.pop_front() { -// Option::Some(call) => { -// match call_contract_syscall(call.to, call.selector, call.calldata) { -// Result::Ok(mut retdata) => { result.append(retdata); }, -// Result::Err(_) => { panic(array!['multicall_failed']); } -// } -// }, -// Option::None(_) => { break (); } -// }; -// }; -// result -// } -// } -// } - - diff --git a/src/namespaces/handle_registry.cairo b/src/namespaces/handle_registry.cairo index b3ed644..33b92c6 100644 --- a/src/namespaces/handle_registry.cairo +++ b/src/namespaces/handle_registry.cairo @@ -67,11 +67,17 @@ mod HandleRegistry { // ************************************************************************* #[abi(embed_v0)] impl HandleRegistryImpl of IHandleRegistry { + /// @notice links a profile address to a handle + /// @param handle_id ID of handle to be linked + /// @param profile_address address of profile to be linked fn link(ref self: ContractState, handle_id: u256, profile_address: ContractAddress) { hub_only(self.karst_hub.read()); self._link(handle_id, profile_address); } + /// @notice unlinks a profile address from a handle + /// @param handle_id ID of handle to be unlinked + /// @param profile_address address of profile to be unlinked fn unlink(ref self: ContractState, handle_id: u256, profile_address: ContractAddress) { let caller = get_caller_address(); self._unlink(handle_id, profile_address, caller); @@ -80,6 +86,9 @@ mod HandleRegistry { // ************************************************************************* // GETTERS // ************************************************************************* + + /// @notice resolves a handle to a profile address + /// @param handle_id ID of handle to be resolved fn resolve(self: @ContractState, handle_id: u256) -> ContractAddress { let it_exists = IHandleDispatcher { contract_address: self.handle_address.read() } .exists(handle_id); @@ -87,6 +96,8 @@ mod HandleRegistry { self.handle_to_profile_address.read(handle_id) } + /// @notice returns the handle linked to a profile address + /// @param profile_address address of profile to be queried fn get_handle(self: @ContractState, profile_address: ContractAddress) -> u256 { self.profile_address_to_handle.read(profile_address) } @@ -97,13 +108,16 @@ mod HandleRegistry { // ************************************************************************* #[generate_trait] impl Private of PrivateTrait { + /// @notice internal function to link a profile address to a handle + /// @param handle_id ID of handle to be linked + /// @param profile_address address of profile to be linked fn _link(ref self: ContractState, handle_id: u256, profile_address: ContractAddress) { let owner = IERC721Dispatcher { contract_address: self.handle_address.read() } .owner_of(handle_id); let handle_to_profile = self.handle_to_profile_address.read(handle_id); assert(profile_address == owner, Errors::INVALID_PROFILE); - assert(handle_to_profile.is_zero(), Errors::OWNER_NOT_ZERO); + assert(handle_to_profile.is_zero(), Errors::HANDLE_ALREADY_LINKED); self.handle_to_profile_address.write(handle_id, profile_address); self.profile_address_to_handle.write(profile_address, handle_id); @@ -119,6 +133,10 @@ mod HandleRegistry { ) } + /// @notice internal function to unlink a profile address from a handle + /// @param handle_id ID of handle to be unlinked + /// @param profile_address address of profile to be unlinked + /// @param caller address of user calling this function fn _unlink( ref self: ContractState, handle_id: u256, From fe479299d7b7507d84becc1f77064691ed8379f5 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Sun, 7 Jul 2024 13:37:04 +0100 Subject: [PATCH 2/3] chore: refactor contracts --- src/base/errors.cairo | 3 + src/interfaces/IPublication.cairo | 2 +- src/namespaces/handle_registry.cairo | 2 +- src/namespaces/handles.cairo | 34 +++++-- src/publication/publication.cairo | 127 +++++++++++++++++++-------- tests/test_follownft.cairo | 8 +- tests/test_handle_registry.cairo | 8 +- 7 files changed, 131 insertions(+), 53 deletions(-) diff --git a/src/base/errors.cairo b/src/base/errors.cairo index 34b2bc8..0d6d0ba 100644 --- a/src/base/errors.cairo +++ b/src/base/errors.cairo @@ -13,4 +13,7 @@ pub mod Errors { pub const INVALID_OWNER: felt252 = 'Karst: caller is not owner!'; pub const INVALID_PROFILE: felt252 = 'Karst: profile is not owner!'; pub const HANDLE_ALREADY_LINKED: felt252 = 'Karst: handle already linked!'; + pub const HADLE_DOES_NOT_EXIST: felt252 = 'Karst: handle does not exist!'; + pub const INVALID_LOCAL_NAME: felt252 = 'Karst: invalid local name!'; + pub const UNSUPPORTED_PUB_TYPE: felt252 = 'Karst: unsupported pub type'; } diff --git a/src/interfaces/IPublication.cairo b/src/interfaces/IPublication.cairo index 667a236..8ffeb41 100644 --- a/src/interfaces/IPublication.cairo +++ b/src/interfaces/IPublication.cairo @@ -42,7 +42,7 @@ pub trait IKarstPublications { // GETTERS // ************************************************************************* fn get_publication( - self: @TContractState, user: ContractAddress, pubIdAssigned: u256 + self: @TContractState, profile_address: ContractAddress, pub_id_assigned: u256 ) -> Publication; fn get_publication_type( self: @TContractState, profile_address: ContractAddress, pub_id_assigned: u256 diff --git a/src/namespaces/handle_registry.cairo b/src/namespaces/handle_registry.cairo index 33b92c6..868ef68 100644 --- a/src/namespaces/handle_registry.cairo +++ b/src/namespaces/handle_registry.cairo @@ -92,7 +92,7 @@ mod HandleRegistry { fn resolve(self: @ContractState, handle_id: u256) -> ContractAddress { let it_exists = IHandleDispatcher { contract_address: self.handle_address.read() } .exists(handle_id); - assert(it_exists, 'Handle ID does not exist'); + assert(it_exists, Errors::HADLE_DOES_NOT_EXIST); self.handle_to_profile_address.read(handle_id) } diff --git a/src/namespaces/handles.cairo b/src/namespaces/handles.cairo index f1edabb..5e70c84 100644 --- a/src/namespaces/handles.cairo +++ b/src/namespaces/handles.cairo @@ -139,7 +139,7 @@ mod Handles { #[constructor] fn constructor( ref self: ContractState, - admin: ContractAddress, // to perform upgrade + admin: ContractAddress, hub_address: ContractAddress ) { self.admin.write(admin); @@ -152,6 +152,9 @@ mod Handles { // ************************************************************************* #[abi(embed_v0)] impl HandlesImpl of IHandle { + /// @notice mints a handle to a profile address + /// @param address profile address to mint handle to + /// @param local_name username to be minted fn mint_handle( ref self: ContractState, address: ContractAddress, local_name: felt252, ) -> u256 { @@ -160,6 +163,8 @@ mod Handles { token_id } + /// @notice burns a handle previously minted + /// @param token_id ID of handle to be burnt fn burn_handle(ref self: ContractState, token_id: u256) { assert(get_caller_address() == self.erc721.owner_of(token_id), Errors::INVALID_OWNER); let current_supply = self.total_supply.read(); @@ -181,28 +186,39 @@ mod Handles { // ************************************************************************* // GETTERS // ************************************************************************* + + /// @notice returns Karst namespace fn get_namespace(self: @ContractState) -> felt252 { return NAMESPACE; } + /// @notice returns the local name for a user + /// @param token_id ID of handle who's local name should be returned fn get_local_name(self: @ContractState, token_id: u256) -> felt252 { self.local_names.read(token_id) } + /// @notice returns the full handle of a user + /// @param token_id ID of handle to retrieve fn get_handle(self: @ContractState, token_id: u256) -> felt252 { let local_name = self.get_local_name(token_id); let handle = NAMESPACE + '@/' + local_name; handle } + /// @notice checks if a handle exists + /// @param token_id ID of handle to be queried fn exists(self: @ContractState, token_id: u256) -> bool { self.erc721._exists(token_id) } + /// @notice returns no. of handles minted fn total_supply(self: @ContractState) -> u256 { self.total_supply.read() } + /// @notice returns the handle ID for a given local name + /// @param local_name local name to be queried fn get_token_id(self: @ContractState, local_name: felt252) -> u256 { let hash: u256 = PoseidonTrait::new() .update_with(local_name) @@ -212,6 +228,9 @@ mod Handles { hash } + /// @notice returns the token URI of a particular handle + /// @param token_id ID of handle to be queried + /// @param local_name local name of handle to be queried fn get_handle_token_uri( self: @ContractState, token_id: u256, local_name: felt252 ) -> ByteArray { @@ -225,6 +244,9 @@ mod Handles { // ************************************************************************* #[generate_trait] impl Private of PrivateTrait { + /// @notice internal function that mints a handle to a profile + /// @param address profile address to mint handle to + /// @param local_name username to be minted fn _mint_handle( ref self: ContractState, address: ContractAddress, local_name: felt252, ) -> u256 { @@ -248,20 +270,20 @@ mod Handles { token_id } - // Validates that a local name contains only [a-z,0-9,_] and does not begin with an underscore. + /// @notice validates that a local name contains only [a-z,0-9,_] and does not begin with an underscore. + /// @param local_name username to be minted fn _validate_local_name(self: @ContractState, local_name: felt252) { let mut value: u256 = local_name.into(); - let mut last_char = 0_u8; + loop { if value == 0 { break; } last_char = (value & 0xFF).try_into().unwrap(); - assert( (self._is_alpha_numeric(last_char) || last_char == ASCII_UNDERSCORE), - 'Invalid local name' + Errors::INVALID_LOCAL_NAME ); value = value / 0x100; @@ -272,6 +294,8 @@ mod Handles { assert(last_char != ASCII_UNDERSCORE.into(), 'Invalid local name'); } + // @notice checks that a character is alpha numeric + // @param char character to be validated fn _is_alpha_numeric(self: @ContractState, char: u8) -> bool { (char >= ASCII_A && char <= ASCII_Z) || (char >= ASCII_0 && char <= ASCII_9) } diff --git a/src/publication/publication.cairo b/src/publication/publication.cairo index f0e45e6..799a7da 100644 --- a/src/publication/publication.cairo +++ b/src/publication/publication.cairo @@ -1,5 +1,3 @@ -//! Contract for Karst Publications V1 -// [Len Publication Contract](https://github.com/lens-protocol/core/blob/master/contracts/libraries/PublicationLib.sol) use starknet::{ContractAddress, get_caller_address}; use karst::base::types::{ PostParams, PublicationType, CommentParams, ReferencePubParams, Publication, MirrorParams, @@ -15,30 +13,35 @@ pub mod PublicationComponent { // ************************************************************************* // IMPORTS // ************************************************************************* + use core::option::OptionTrait; use starknet::{ContractAddress, get_contract_address, get_caller_address, get_block_timestamp}; - use karst::base::types::{ - PostParams, Publication, PublicationType, ReferencePubParams, CommentParams, QuoteParams, - MirrorParams - }; use karst::interfaces::IPublication::IKarstPublications; use karst::interfaces::IProfile::{IProfileDispatcher, IProfileDispatcherTrait}; - use karst::base::errors::Errors::{NOT_PROFILE_OWNER, BLOCKED_STATUS}; + use karst::base::errors::Errors::{NOT_PROFILE_OWNER, BLOCKED_STATUS, UNSUPPORTED_PUB_TYPE}; use karst::base::{hubrestricted::HubRestricted::hub_only}; - use core::option::OptionTrait; + use karst::base::types::{ + PostParams, + Publication, + PublicationType, + ReferencePubParams, + CommentParams, + QuoteParams, + MirrorParams + }; + // ************************************************************************* // STORAGE // ************************************************************************* - #[storage] struct Storage { publication: LegacyMap<(ContractAddress, u256), Publication>, blocked_profile_address: LegacyMap<(ContractAddress, ContractAddress), bool>, karst_hub: ContractAddress } + // ************************************************************************* // EVENTS // ************************************************************************* - #[event] #[derive(Drop, starknet::Event)] pub enum Event { @@ -48,10 +51,6 @@ pub mod PublicationComponent { QuoteCreated: QuoteCreated, } - // ************************************************************************* - // STRUCTS - // ************************************************************************* - #[derive(Drop, starknet::Event)] pub struct Post { pub post: PostParams, @@ -88,7 +87,6 @@ pub mod PublicationComponent { // ************************************************************************* // EXTERNAL FUNCTIONS // ************************************************************************* - #[embeddable_as(KarstPublication)] impl PublicationsImpl< TContractState, +HasComponent @@ -96,22 +94,26 @@ pub mod PublicationComponent { // ************************************************************************* // PUBLISHING FUNCTIONS // ************************************************************************* - + /// @notice initialize publication component + /// @param hub_address address of hub contract fn initializer(ref self: ComponentState, hub_address: ContractAddress) { self.karst_hub.write(hub_address); } + /// @notice performs post action + /// @param contentURI uri of the content to be posted + /// @param profile_address address of profile performing the post action fn post( ref self: ComponentState, contentURI: ByteArray, profile_address: ContractAddress, profile_contract_address: ContractAddress ) -> u256 { - // assert that the person that created the profile can make a post let profile_owner = IProfileDispatcher { contract_address: profile_contract_address } .get_profile(profile_address) .profile_owner; assert(profile_owner == get_caller_address(), NOT_PROFILE_OWNER); + let pubIdAssigned = IProfileDispatcher { contract_address: profile_contract_address } .increment_publication_count(profile_address); let new_post = Publication { @@ -122,9 +124,16 @@ pub mod PublicationComponent { root_profile_address: 0.try_into().unwrap(), root_pub_id: 0 }; + self.publication.write((profile_address, pubIdAssigned), new_post); pubIdAssigned } + + /// @notice performs comment action + /// @param profile_address address of profile performing the comment action + /// @param reference_pub_type publication type + /// @param pointed_profile_address profile address comment points too + /// @param pointed_pub_id ID of initial publication comment points too fn comment( ref self: ComponentState, profile_address: ContractAddress, @@ -135,8 +144,7 @@ pub mod PublicationComponent { profile_contract_address: ContractAddress ) -> u256 { let reference_pub_type = self._as_reference_pub_params(reference_pub_type); - - assert(reference_pub_type == PublicationType::Comment, 'Unsupported publication type'); + assert(reference_pub_type == PublicationType::Comment, UNSUPPORTED_PUB_TYPE); let pub_id_assigned = self ._createReferencePublication( @@ -160,29 +168,19 @@ pub mod PublicationComponent { pub_id_assigned } - // /** - // * @notice Publishes a mirror to a given profile. - // * - // * @param mirrorParams the MirrorParams struct reference types.cairo to know MirrorParams. - // * - // * @return uint256 The created publication's pubId. - // */ + /// @notice performs the mirror function + /// @param mirrorParams the MirrorParams struct fn mirror( ref self: ComponentState, mirrorParams: MirrorParams, profile_contract_address: ContractAddress ) -> u256 { - assert!( - profile_contract_address.into() != 0, "Contract Profile Address cannot be zero" - ); - self._validatePointedPub(mirrorParams.profile_address, mirrorParams.pointed_pub_id); self .validateNotBlocked( mirrorParams.profile_address, mirrorParams.pointed_profile_address, false ); let ref_mirrorParams = mirrorParams.clone(); - // _processMirrorIfNeeded is not needed let profileDispatcher = IProfileDispatcher { contract_address: profile_contract_address }; @@ -200,6 +198,7 @@ pub mod PublicationComponent { PublicationType::Mirror, profile_contract_address, ); + self .emit( MirrorCreated { @@ -213,6 +212,9 @@ pub mod PublicationComponent { pub_id_assigned } + /// @notice performs the quote function + /// @param reference_pub_type publication type + /// @param quoteParams the quoteParams struct fn quote( ref self: ComponentState, reference_pub_type: PublicationType, @@ -221,8 +223,7 @@ pub mod PublicationComponent { ) -> u256 { let ref_quoteParams = quoteParams.clone(); let reference_pub_type = self._as_reference_pub_params(reference_pub_type); - - assert(reference_pub_type == PublicationType::Quote, 'Unsupported publication type'); + assert(reference_pub_type == PublicationType::Quote, UNSUPPORTED_PUB_TYPE); let pub_id_assigned = self ._createReferencePublication( @@ -245,21 +246,32 @@ pub mod PublicationComponent { ); pub_id_assigned } + // ************************************************************************* // GETTERS // ************************************************************************* + + /// @notice gets the publication's content URI + /// @param profile_address the profile address to be queried + /// @param pub_id the ID of the publication to be queried fn get_publication_content_uri( self: @ComponentState, profile_address: ContractAddress, pub_id: u256 ) -> ByteArray { self._getContentURI(profile_address, pub_id) } + /// @notice retrieves a publication + /// @param profile_address the profile address to be queried + /// @param pub_id_assigned the ID of the publication to be retrieved fn get_publication( - self: @ComponentState, user: ContractAddress, pubIdAssigned: u256 + self: @ComponentState, profile_address: ContractAddress, pub_id_assigned: u256 ) -> Publication { - self.publication.read((user, pubIdAssigned)) + self.publication.read((profile_address, pub_id_assigned)) } + /// @notice retrieves a publication type + /// @param profile_address the profile address to be queried + /// @param pub_id_assigned the ID of the publication whose type is to be retrieved fn get_publication_type( self: @ComponentState, profile_address: ContractAddress, @@ -275,6 +287,10 @@ pub mod PublicationComponent { impl InternalImpl< TContractState, +HasComponent > of InternalTrait { + /// @notice fill root of publication + /// @param profile_address the profile address creating the publication + /// @param pointed_profile_address the profile address being pointed to by this publication + /// @param pointed_pub_id the ID of the publication being pointed to by this publication fn _fillRootOfPublicationInStorage( ref self: ComponentState, profile_address: ContractAddress, @@ -302,6 +318,13 @@ pub mod PublicationComponent { let publication: Publication = self.publication.read((profile_address, pointed_pub_id)); publication.root_profile_address } + + /// @notice fill reference publication + /// @param profile_address the profile address creating the publication + /// @param content_URI uri of the publication content + /// @param pointed_profile_address the profile address being pointed to by this publication + /// @param pointed_pub_id the ID of the publication being pointed to by this publication + // @param reference_pub_type reference publication type fn _fillRefeferencePublicationStorage( ref self: ComponentState, profile_address: ContractAddress, @@ -332,6 +355,12 @@ pub mod PublicationComponent { (pub_id_assigned, root_profile_address) } + /// @notice create reference publication + /// @param profile_address the profile address creating the publication + /// @param content_URI uri of the publication content + /// @param pointed_profile_address the profile address being pointed to by this publication + /// @param pointed_pub_id the ID of the publication being pointed to by this publication + // @param reference_pub_type reference publication type fn _createReferencePublication( ref self: ComponentState, profile_address: ContractAddress, @@ -359,6 +388,8 @@ pub mod PublicationComponent { pub_id_assigned } + /// @notice returns the publication type + // @param reference_pub_type reference publication type fn _as_reference_pub_params( ref self: ComponentState, reference_pub_type: PublicationType ) -> PublicationType { @@ -369,6 +400,9 @@ pub mod PublicationComponent { } } + /// @notice fill reference publication + /// @param profile_address the blocked profile address + /// @param by_profile_address the profile address that performed the blocking fn _blockedStatus( ref self: ComponentState, profile_address: ContractAddress, @@ -379,6 +413,10 @@ pub mod PublicationComponent { status } + /// @notice check a profile is not blocked + /// @param profile_address the blocked profile address + /// @param by_profile_address the profile address that performed the blocking + /// unidirectional_check specifies if check should be one-way or both ways fn validateNotBlocked( ref self: ComponentState, profile_address: ContractAddress, @@ -391,14 +429,21 @@ pub mod PublicationComponent { && self ._blockedStatus( by_profile_address, profile_address - )))) { // return; ERROR - } + ) + ) + ) + ) { + // return; ERROR + } } + /// @notice validates pointed publication + /// @param profile_address the profile address that created the publication + /// @param pub_id the publication ID of the publication to be checked fn _validatePointedPub( ref self: ComponentState, profile_address: ContractAddress, pub_id: u256 ) { - // If it is pointing to itself it will fail because it will return a non-existent type. + // If it points to itself, it fails because type will be non existent. let pointedPubType = self._getPublicationType(profile_address, pub_id); if pointedPubType == PublicationType::Nonexistent || pointedPubType == PublicationType::Mirror { @@ -406,6 +451,9 @@ pub mod PublicationComponent { } } + /// @notice gets the publication type + /// @param profile_address the profile address that created the publication + /// @param pub_id_assigned the publication ID of the publication to be queried fn _getPublicationType( self: @ComponentState, profile_address: ContractAddress, @@ -424,6 +472,9 @@ pub mod PublicationComponent { } } + /// @notice gets the publication content URI + /// @param profile_address the profile address that created the publication + /// @param pub_id the publication ID of the publication to be queried fn _getContentURI( self: @ComponentState, profile_address: ContractAddress, pub_id: u256 ) -> ByteArray { diff --git a/tests/test_follownft.cairo b/tests/test_follownft.cairo index 75d49f9..b967123 100644 --- a/tests/test_follownft.cairo +++ b/tests/test_follownft.cairo @@ -39,7 +39,7 @@ fn test_follower_count_on_init_is_zero() { } #[test] -#[should_panic(expected: ('CALLER_IS_NOT_HUB',))] +#[should_panic(expected: ('Karst: caller is not Hub!',))] fn test_cannot_call_follow_if_not_hub() { let follow_nft_contract_address = __setup__(); let dispatcher = IFollowNFTDispatcher { contract_address: follow_nft_contract_address }; @@ -49,7 +49,7 @@ fn test_cannot_call_follow_if_not_hub() { } #[test] -#[should_panic(expected: ('CALLER_IS_NOT_HUB',))] +#[should_panic(expected: ('Karst: caller is not Hub!',))] fn test_cannot_call_unfollow_if_not_hub() { let follow_nft_contract_address = __setup__(); let dispatcher = IFollowNFTDispatcher { contract_address: follow_nft_contract_address }; @@ -59,7 +59,7 @@ fn test_cannot_call_unfollow_if_not_hub() { } #[test] -#[should_panic(expected: ('USER_ALREADY_FOLLOWING',))] +#[should_panic(expected: ('Karst: user already following!',))] fn test_cannot_follow_if_already_following() { let follow_nft_contract_address = __setup__(); let dispatcher = IFollowNFTDispatcher { contract_address: follow_nft_contract_address }; @@ -147,7 +147,7 @@ fn test_unfollow() { } #[test] -#[should_panic(expected: ('USER_NOT_FOLLOWING',))] +#[should_panic(expected: ('Karst: user not following!',))] fn test_cannot_unfollow_if_not_following() { let follow_nft_contract_address = __setup__(); let dispatcher = IFollowNFTDispatcher { contract_address: follow_nft_contract_address }; diff --git a/tests/test_handle_registry.cairo b/tests/test_handle_registry.cairo index d07064d..b2ceec2 100644 --- a/tests/test_handle_registry.cairo +++ b/tests/test_handle_registry.cairo @@ -44,7 +44,7 @@ fn __setup__() -> (ContractAddress, ContractAddress) { // ************************************************************************* #[test] -#[should_panic(expected: ('Handle ID does not exist',))] +#[should_panic(expected: ('Karst: handle does not exist!',))] fn test_cannot_resolve_if_handle_does_not_exist() { let (handle_registry_contract_address, _) = __setup__(); let dispatcher = IHandleRegistryDispatcher { @@ -106,7 +106,7 @@ fn test_link() { } #[test] -#[should_panic(expected: ('PROFILE_IS_NOT_OWNER',))] +#[should_panic(expected: ('Karst: profile is not owner!',))] fn test_linking_fails_if_profile_address_is_not_owner() { let (handle_registry_address, handle_contract_address) = __setup__(); let registryDispatcher = IHandleRegistryDispatcher { @@ -129,7 +129,7 @@ fn test_linking_fails_if_profile_address_is_not_owner() { } #[test] -#[should_panic(expected: ('HANDLE_HAS_ALREADY_BEEN_LINKED',))] +#[should_panic(expected: ('Karst: handle already linked!',))] fn test_does_not_link_twice_for_same_handle() { let (handle_registry_address, handle_contract_address) = __setup__(); let registryDispatcher = IHandleRegistryDispatcher { @@ -187,7 +187,7 @@ fn test_unlink() { #[test] -#[should_panic(expected: ('CALLER_NOT_OWNER',))] +#[should_panic(expected: ('Karst: caller is not owner!',))] fn test_unlink_fails_if_caller_is_not_owner() { let (handle_registry_address, handle_contract_address) = __setup__(); let registryDispatcher = IHandleRegistryDispatcher { From a7f8bcf13d6155ad9243e1ba2d58327bfce031ad Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Sun, 7 Jul 2024 13:48:52 +0100 Subject: [PATCH 3/3] chore: update tests --- img/logo.png | Bin 48916 -> 5182 bytes src/base/errors.cairo | 2 +- src/namespaces/handles.cairo | 8 ++------ src/publication/publication.cairo | 23 ++++++++--------------- tests/test_handle.cairo | 8 ++++---- tests/test_karstnft.cairo | 2 +- tests/test_publication.cairo | 4 ++-- 7 files changed, 18 insertions(+), 29 deletions(-) diff --git a/img/logo.png b/img/logo.png index 2f7fa36fa7de468fc9d72a222cca1a961814ef45..9f627e8b411f355e919c85a82879e4e4ed9c6464 100644 GIT binary patch literal 5182 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 z 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

ey~8b0YB=w8f9c^cJHsC1@nPF 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 a{yuTd{, hub_address: ContractAddress) { self.karst_hub.write(hub_address); @@ -264,7 +259,9 @@ pub mod PublicationComponent { /// @param profile_address the profile address to be queried /// @param pub_id_assigned the ID of the publication to be retrieved fn get_publication( - self: @ComponentState, profile_address: ContractAddress, pub_id_assigned: u256 + self: @ComponentState, + profile_address: ContractAddress, + pub_id_assigned: u256 ) -> Publication { self.publication.read((profile_address, pub_id_assigned)) } @@ -429,12 +426,8 @@ pub mod PublicationComponent { && self ._blockedStatus( by_profile_address, profile_address - ) - ) - ) - ) { - // return; ERROR - } + )))) { // return; ERROR + } } /// @notice validates pointed publication diff --git a/tests/test_handle.cairo b/tests/test_handle.cairo index 64d5772..6e6a5a7 100644 --- a/tests/test_handle.cairo +++ b/tests/test_handle.cairo @@ -71,7 +71,7 @@ fn test_mint_handle_two() { } #[test] -#[should_panic(expected: ('Invalid local name',))] +#[should_panic(expected: ('Karst: invalid local name!',))] fn test_mint_handle_with_bad_local_name_1() { let handles_contract_address = __setup__(); let handles_dispatcher = IHandleDispatcher { contract_address: handles_contract_address }; @@ -81,7 +81,7 @@ fn test_mint_handle_with_bad_local_name_1() { } #[test] -#[should_panic(expected: ('Invalid local name',))] +#[should_panic(expected: ('Karst: invalid local name!',))] fn test_mint_handle_with_bad_local_name_2() { let handles_contract_address = __setup__(); let handles_dispatcher = IHandleDispatcher { contract_address: handles_contract_address }; @@ -91,7 +91,7 @@ fn test_mint_handle_with_bad_local_name_2() { } #[test] -#[should_panic(expected: ('Invalid local name',))] +#[should_panic(expected: ('Karst: invalid local name!',))] fn test_mint_handle_with_bad_local_name_3() { let handles_contract_address = __setup__(); let handles_dispatcher = IHandleDispatcher { contract_address: handles_contract_address }; @@ -163,7 +163,7 @@ fn test_burn() { } #[test] -#[should_panic(expected: ('CALLER_NOT_OWNER',))] +#[should_panic(expected: ('Karst: caller is not owner!',))] fn test_cannot_burn_if_not_owner_of() { let contract_address = __setup__(); let dispatcher = IHandleDispatcher { contract_address }; diff --git a/tests/test_karstnft.cairo b/tests/test_karstnft.cairo index 350b17a..4fed938 100644 --- a/tests/test_karstnft.cairo +++ b/tests/test_karstnft.cairo @@ -75,7 +75,7 @@ fn test_mint_karst_nft() { } #[test] -#[should_panic(expected: ('USER_ALREADY_MINTED',))] +#[should_panic(expected: ('Karst: user already minted!',))] fn test_mint_karst_nft_twice_for_the_same_user() { let nft_contract_address = __setup__(); diff --git a/tests/test_publication.cairo b/tests/test_publication.cairo index 2b02896..49ed49f 100644 --- a/tests/test_publication.cairo +++ b/tests/test_publication.cairo @@ -263,7 +263,7 @@ fn test_comment() { } #[test] -#[should_panic(expected: ('Unsupported publication type',))] +#[should_panic(expected: ('Karst: unsupported pub type!',))] fn test_with_as_reference_pub_params() { let ( _, @@ -376,7 +376,7 @@ fn test_quote() { } #[test] -#[should_panic(expected: ('Unsupported publication type',))] +#[should_panic(expected: ('Karst: unsupported pub type!',))] fn test_quote_as_reference_pub_params() { let ( _,