diff --git a/src/base/errors.cairo b/src/base/errors.cairo index 178e063..f553344 100644 --- a/src/base/errors.cairo +++ b/src/base/errors.cairo @@ -3,6 +3,7 @@ // ************************************************************************* 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'; diff --git a/src/base/types.cairo b/src/base/types.cairo index a1f283b..358f0c6 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -25,8 +25,8 @@ pub struct PostParams { pub struct Profile { pub_count: u256, metadata_URI: ByteArray, - // profile_address: ContractAddress, - // profile_owner: ContractAddress +// profile_address: ContractAddress, +// profile_owner: ContractAddress } diff --git a/src/interfaces/IProfile.cairo b/src/interfaces/IProfile.cairo index 3fbbbc7..1cfc8e4 100644 --- a/src/interfaces/IProfile.cairo +++ b/src/interfaces/IProfile.cairo @@ -14,10 +14,12 @@ pub trait IKarstProfile { registry_hash: felt252, implementation_hash: felt252, salt: felt252, - recipient:ContractAddress + recipient: ContractAddress ) -> ContractAddress; - fn set_profile_metadata_uri(ref self: TState, profile_address:ContractAddress, metadata_uri: ByteArray); - fn increment_publication_count(ref self: TState, profile_address:ContractAddress) -> u256; + fn set_profile_metadata_uri( + ref self: TState, profile_address: ContractAddress, metadata_uri: ByteArray + ); + fn increment_publication_count(ref self: TState, profile_address: ContractAddress) -> u256; // ************************************************************************* // GETTERS // ************************************************************************* diff --git a/src/interfaces/IPublication.cairo b/src/interfaces/IPublication.cairo index 4b3b155..ea8a5cc 100644 --- a/src/interfaces/IPublication.cairo +++ b/src/interfaces/IPublication.cairo @@ -11,7 +11,7 @@ pub trait IKarstPublications { contentURI: ByteArray, profile_address: ContractAddress, profile_contract_address: ContractAddress, - user:ContractAddress + user: ContractAddress ) -> u256; fn comment( ref self: T, diff --git a/src/karstnft/karstnft.cairo b/src/karstnft/karstnft.cairo index 3ffbd71..9a9dc03 100644 --- a/src/karstnft/karstnft.cairo +++ b/src/karstnft/karstnft.cairo @@ -38,7 +38,9 @@ pub mod KarstNFT { // IMPORTS // ************************************************************************* use starknet::{ContractAddress, get_caller_address}; + use core::num::traits::zero::Zero; use karst::interfaces::IKarstNFT; + use karst::base::{hubrestricted::HubRestricted::hub_only, errors::Errors::ALREADY_MINTED}; use openzeppelin::{ account, access::ownable::OwnableComponent, token::erc721::{ @@ -46,7 +48,6 @@ pub mod KarstNFT { }, introspection::{src5::SRC5Component} }; - use karst::base::{hubrestricted::HubRestricted::hub_only}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: ERC721Component, storage: erc721, event: ERC721Event); @@ -64,7 +65,6 @@ pub mod KarstNFT { // allow to query name of nft collection #[abi(embed_v0)] impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; - // add an owner #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; @@ -82,11 +82,14 @@ pub mod KarstNFT { src5: SRC5Component::Storage, #[substorage(v0)] ownable: OwnableComponent::Storage, - karst_hub: ContractAddress, + admin: ContractAddress, token_id: u256, user_token_id: LegacyMap, } + // ************************************************************************* + // EVENTS + // ************************************************************************* #[event] #[derive(Drop, starknet::Event)] enum Event { @@ -104,12 +107,12 @@ pub mod KarstNFT { #[constructor] fn constructor( ref self: ContractState, - hub: ContractAddress, + admin: ContractAddress, name: ByteArray, symbol: ByteArray, - base_uri: ByteArray + base_uri: ByteArray, ) { - self.karst_hub.write(hub); + self.admin.write(admin); self.erc721.initializer(name, symbol, base_uri); } @@ -117,18 +120,21 @@ pub mod KarstNFT { impl KarstImpl of IKarstNFT::IKarstNFT { /// @notice mints kartsnft fn mint_karstnft(ref self: ContractState, address: ContractAddress) { - hub_only(self.karst_hub.read()); + let balance = self.erc721.balance_of(address); + assert(balance.is_zero(), ALREADY_MINTED); let mut current_token_id = self.token_id.read(); self.erc721._mint(address, current_token_id); self.user_token_id.write(address, current_token_id); current_token_id += 1; self.token_id.write(current_token_id); } + /// @notice returns karstnft token_id /// @param user the address of user to query its token_id fn get_user_token_id(self: @ContractState, user: ContractAddress) -> u256 { self.user_token_id.read(user) } + /// @notice returns current token_id fn get_current_token_id(self: @ContractState) -> u256 { self.token_id.read() diff --git a/src/profile/profile.cairo b/src/profile/profile.cairo index aa982e1..0d6d724 100644 --- a/src/profile/profile.cairo +++ b/src/profile/profile.cairo @@ -13,7 +13,6 @@ mod KarstProfile { }; use karst::interfaces::IERC721::{IERC721Dispatcher, IERC721DispatcherTrait}; use karst::interfaces::IProfile::IKarstProfile; - use karst::base::errors::Errors::{NOT_PROFILE_OWNER}; use karst::base::types::Profile; use karst::base::{hubrestricted::HubRestricted::hub_only}; @@ -22,7 +21,7 @@ mod KarstProfile { // ************************************************************************* #[storage] struct Storage { - profile: LegacyMap, //maps profile_address => Profile + profile: LegacyMap, karst_hub: ContractAddress, } @@ -38,10 +37,10 @@ mod KarstProfile { #[derive(Drop, starknet::Event)] struct CreateProfile { #[key] - user: ContractAddress, // address of user creating a profile + user: ContractAddress, #[key] - profile_address: ContractAddress, // address of created profile - token_id: u256, // profile nft token ID + profile_address: ContractAddress, + token_id: u256, } // ************************************************************************* @@ -67,9 +66,8 @@ mod KarstProfile { registry_hash: felt252, implementation_hash: felt252, salt: felt252, - recipient:ContractAddress + recipient: ContractAddress ) -> ContractAddress { - hub_only(self.karst_hub.read()); let owns_karstnft = IERC721Dispatcher { contract_address: karstnft_contract_address } .balance_of(recipient); if owns_karstnft == 0 { @@ -83,29 +81,29 @@ mod KarstProfile { class_hash: registry_hash.try_into().unwrap() } .create_account(implementation_hash, karstnft_contract_address, token_id, salt); - let new_profile = Profile { - pub_count: 0, - metadata_URI: "", - }; + let new_profile = Profile { pub_count: 0, metadata_URI: "", }; self.profile.write(profile_address, new_profile); self.emit(CreateProfile { user: profile_address, token_id, profile_address }); profile_address } + /// @notice set profile metadata_uri (`banner_image, description, profile_image` to be uploaded to arweave or ipfs) /// @params metadata_uri the profile CID - fn set_profile_metadata_uri(ref self: ContractState, profile_address:ContractAddress, metadata_uri: ByteArray) { - hub_only(self.karst_hub.read()); + fn set_profile_metadata_uri( + ref self: ContractState, profile_address: ContractAddress, metadata_uri: ByteArray + ) { let mut profile = self.profile.read(profile_address); profile.metadata_URI = metadata_uri; self.profile.write(profile_address, profile); } - fn increment_publication_count(ref self: ContractState, profile_address:ContractAddress) -> u256 { + fn increment_publication_count( + ref self: ContractState, profile_address: ContractAddress + ) -> u256 { hub_only(self.karst_hub.read()); let mut profile = self.profile.read(profile_address); let updated_profile = Profile { - pub_count: profile.pub_count + 1, - metadata_URI: profile.metadata_URI, + pub_count: profile.pub_count + 1, metadata_URI: profile.metadata_URI, }; self.profile.write(profile_address, updated_profile); profile.pub_count @@ -117,16 +115,19 @@ mod KarstProfile { /// @notice returns user metadata /// @params user - fn get_profile_metadata(self: @ContractState, profile_address: ContractAddress) -> ByteArray { + fn get_profile_metadata( + self: @ContractState, profile_address: ContractAddress + ) -> ByteArray { self.profile.read(profile_address).metadata_URI } - fn get_profile(ref self: ContractState, profile_address: ContractAddress) -> Profile { self.profile.read(profile_address) } - fn get_user_publication_count(self: @ContractState, profile_address: ContractAddress) -> u256 { + fn get_user_publication_count( + self: @ContractState, profile_address: ContractAddress + ) -> u256 { self.profile.read(profile_address).pub_count } } diff --git a/src/publication/publication.cairo b/src/publication/publication.cairo index b45a217..5d7d73d 100644 --- a/src/publication/publication.cairo +++ b/src/publication/publication.cairo @@ -116,7 +116,8 @@ pub mod Publications { hub_only(self.karst_hub.read()); let pubIdAssigned = IKarstProfileDispatcher { contract_address: profile_contract_address - }.increment_publication_count(profile_address); + } + .increment_publication_count(profile_address); let new_post = Publication { pointed_profile_address: 0.try_into().unwrap(), pointed_pub_id: 0, @@ -172,7 +173,7 @@ pub mod Publications { impl Private of PrivateTrait { fn _fillRootOfPublicationInStorage( ref self: ContractState, - profile_address:ContractAddress, + profile_address: ContractAddress, pointed_profile_address: ContractAddress, pointed_pub_id: u256, profile_contract_address: ContractAddress @@ -212,7 +213,10 @@ pub mod Publications { .increment_publication_count(profile_address); let root_profile_address = self ._fillRootOfPublicationInStorage( - profile_address,pointed_profile_address, pointed_pub_id, profile_contract_address + profile_address, + pointed_profile_address, + pointed_pub_id, + profile_contract_address ); let update_reference = Publication { pointed_profile_address: profile_address, diff --git a/tests/test_karstnft.cairo b/tests/test_karstnft.cairo index 2ff8b4d..ccd5bb4 100644 --- a/tests/test_karstnft.cairo +++ b/tests/test_karstnft.cairo @@ -85,9 +85,20 @@ fn test_token_mint() { HUB_ADDRESS.try_into().unwrap() ); let dispatcher = IKarstProfileDispatcher { contract_address: profile_contract_address }; - dispatcher.create_profile(contract_address, registry_class_hash, acct_class_hash.into(), 2456, HUB_ADDRESS.try_into().unwrap()); + dispatcher + .create_profile( + contract_address, + registry_class_hash, + acct_class_hash.into(), + 2456, + HUB_ADDRESS.try_into().unwrap() + ); let current_token_id = karstDispatcher.get_current_token_id(); - dispatcher.set_profile_metadata_uri(HUB_ADDRESS.try_into().unwrap(), "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gQ/"); + dispatcher + .set_profile_metadata_uri( + HUB_ADDRESS.try_into().unwrap(), + "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gQ/" + ); let hub_profile_uri = dispatcher.get_profile_metadata(HUB_ADDRESS.try_into().unwrap()); assert(hub_profile_uri == "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gQ/", 'invalid'); diff --git a/tests/test_publication.cairo b/tests/test_publication.cairo index d81198b..3a5632c 100644 --- a/tests/test_publication.cairo +++ b/tests/test_publication.cairo @@ -1,10 +1,6 @@ // ************************************************************************* -// TESTING FOR PUBLICATION CONTRACT +// PUBLICATION CONTRACT TEST // ************************************************************************* -// ************************************************************************* -// IMPORT -// ************************************************************************* - use core::option::OptionTrait; use core::starknet::SyscallResultTrait; use core::result::ResultTrait; @@ -12,90 +8,69 @@ use core::traits::{TryInto, Into}; use starknet::{ContractAddress, class_hash::ClassHash}; use snforge_std::{declare, ContractClassTrait, CheatTarget, start_prank, stop_prank}; -// Account use token_bound_accounts::interfaces::IAccount::{IAccountDispatcher, IAccountDispatcherTrait}; use token_bound_accounts::presets::account::Account; -// Registry use karst::mocks::registry::Registry; use karst::interfaces::IRegistry::{IRegistryDispatcher, IRegistryDispatcherTrait}; -//KarstNFT use karst::karstnft::karstnft::KarstNFT; use karst::interfaces::IKarstNFT::{IKarstNFTDispatcher, IKarstNFTDispatcherTrait}; -//Profile use karst::interfaces::IProfile::{IKarstProfileDispatcher, IKarstProfileDispatcherTrait}; - -// Publication use karst::publication::Publication; use karst::interfaces::IPublication::{ IKarstPublicationsDispatcher, IKarstPublicationsDispatcherTrait }; -// types use karst::base::types::{PostParams, ReferencePubParams}; -// +const HUB_ADDRESS: felt252 = 'HUB'; +const USER: felt252 = 'USER'; + // ************************************************************************* // SETUP // ************************************************************************* - - -const HUB_ADDRESS: felt252 = 'HUB'; -// setup for publication contract -fn __setup__() -> ContractAddress { - let publication_contract = declare("Publications").unwrap(); - let mut publication_constructor_calldata = array![HUB_ADDRESS]; - let (contract_address, _) = publication_contract - .deploy(@publication_constructor_calldata) - .unwrap_syscall(); - contract_address -} - -fn deploy_profile() -> ContractAddress { - let profile_contract = declare("KarstProfile").unwrap(); - let mut karst_profile_constructor_calldata = array![HUB_ADDRESS]; - let (profile_contract_address, _) = profile_contract - .deploy(@karst_profile_constructor_calldata) - .unwrap(); - profile_contract_address -} - -// setup for nft contract -fn deploy_contract(name: ByteArray) -> ContractAddress { - let contract = declare(name).unwrap(); +fn __setup__() -> ( + ContractAddress, ContractAddress, ContractAddress, ContractAddress, felt252, felt252 +) { + // deploy NFT + let nft_contract = declare("KarstNFT").unwrap(); let names: ByteArray = "KarstNFT"; let symbol: ByteArray = "KNFT"; let base_uri: ByteArray = "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4gQ/"; - let mut calldata: Array = array![HUB_ADDRESS]; + let mut calldata: Array = array![USER]; names.serialize(ref calldata); symbol.serialize(ref calldata); base_uri.serialize(ref calldata); + let (nft_contract_address, _) = nft_contract.deploy(@calldata).unwrap_syscall(); - let (contract_address, _) = contract.deploy(@calldata).unwrap_syscall(); - - contract_address -} + // deploy registry + let registry_class_hash = declare("Registry").unwrap(); + let (registry_contract_address, _) = registry_class_hash.deploy(@array![]).unwrap_syscall(); + // deploy profile + let profile_contract = declare("KarstProfile").unwrap(); + let mut karst_profile_constructor_calldata = array![HUB_ADDRESS]; + let (profile_contract_address, _) = profile_contract + .deploy(@karst_profile_constructor_calldata) + .unwrap(); -fn deploy_account() -> ContractAddress { - let erc721_contract_address = deploy_contract("KarstNFT"); - // deploy account contract - let account_contract = declare("Account").unwrap(); - let mut acct_constructor_calldata: Array = array![ - erc721_contract_address.into(), 1, 0 - ]; - let (account_contract_address, _) = account_contract - .deploy(@acct_constructor_calldata) + // deploy publication + let publication_contract = declare("Publications").unwrap(); + let mut publication_constructor_calldata = array![HUB_ADDRESS]; + let (publication_contract_address, _) = publication_contract + .deploy(@publication_constructor_calldata) .unwrap_syscall(); - account_contract_address -} -fn deploy_registry() -> (ContractAddress, felt252) { - let registry_class_hash = declare("Registry").unwrap(); - let (registry_contract_address, _) = registry_class_hash.deploy(@array![]).unwrap_syscall(); - return (registry_contract_address, registry_class_hash.class_hash.into()); + // declare account + let account_class_hash = declare("Account").unwrap(); + + return ( + nft_contract_address, + registry_contract_address, + profile_contract_address, + publication_contract_address, + registry_class_hash.class_hash.into(), + account_class_hash.class_hash.into() + ); } -// ************************************************************************* -// END OF SETUP -// ************************************************************************* // ************************************************************************* // TEST @@ -103,21 +78,35 @@ fn deploy_registry() -> (ContractAddress, felt252) { #[test] fn test_post() { - let publication_contract_address = __setup__(); - let karstnft_contract_address: ContractAddress = deploy_contract("KarstNFT"); - let (_, registry_class_hash) = deploy_registry(); - let profile_contract_address = deploy_profile(); - let acct_class_hash = declare("Account").unwrap_syscall().class_hash; - + let ( + nft_contract_address, + _, + profile_contract_address, + publication_contract_address, + registry_class_hash, + account_class_hash + ) = + __setup__(); let profile_dispatcher = IKarstProfileDispatcher { contract_address: profile_contract_address }; let _publication_dispatcher = IKarstPublicationsDispatcher { contract_address: publication_contract_address }; - start_prank(CheatTarget::Multiple(array![publication_contract_address, profile_contract_address]), - HUB_ADDRESS.try_into().unwrap() + start_prank( + CheatTarget::Multiple(array![publication_contract_address, profile_contract_address]), + USER.try_into().unwrap() ); - let profile_address = profile_dispatcher.create_profile(karstnft_contract_address, registry_class_hash, acct_class_hash.into(), 2478, HUB_ADDRESS.try_into().unwrap()); - // profile_dispatcher.set_profile_metadata_uri(HUB_ADDRESS.try_into().unwrap(),"ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4ga/"); + let profile_address = profile_dispatcher + .create_profile( + nft_contract_address, + registry_class_hash, + account_class_hash, + 2478, + USER.try_into().unwrap() + ); + profile_dispatcher + .set_profile_metadata_uri( + USER.try_into().unwrap(), "ipfs://QmSkDCsS32eLpcymxtn1cEn7Rc5hfefLBgfvZyjaYXr4ga/" + ); // // POST // let contentURI: ByteArray = "ipfs://helloworld"; @@ -152,8 +141,6 @@ fn test_post() { ); } - - fn to_address(name: felt252) -> ContractAddress { name.try_into().unwrap() }